You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RootPage.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. /*
  2. * Copyright 2011 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit.wicket.pages;
  17. import java.text.MessageFormat;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Calendar;
  21. import java.util.Collections;
  22. import java.util.Date;
  23. import java.util.HashMap;
  24. import java.util.HashSet;
  25. import java.util.LinkedHashSet;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.Set;
  29. import java.util.concurrent.atomic.AtomicInteger;
  30. import java.util.regex.Pattern;
  31. import org.apache.wicket.MarkupContainer;
  32. import org.apache.wicket.PageParameters;
  33. import org.apache.wicket.markup.html.basic.Label;
  34. import org.apache.wicket.markup.html.form.PasswordTextField;
  35. import org.apache.wicket.markup.html.form.TextField;
  36. import org.apache.wicket.markup.html.link.BookmarkablePageLink;
  37. import org.apache.wicket.markup.html.panel.Fragment;
  38. import org.apache.wicket.model.IModel;
  39. import org.apache.wicket.model.Model;
  40. import org.apache.wicket.protocol.http.WebResponse;
  41. import com.gitblit.Constants;
  42. import com.gitblit.GitBlit;
  43. import com.gitblit.Keys;
  44. import com.gitblit.models.RepositoryModel;
  45. import com.gitblit.models.TeamModel;
  46. import com.gitblit.models.UserModel;
  47. import com.gitblit.utils.StringUtils;
  48. import com.gitblit.wicket.GitBlitWebSession;
  49. import com.gitblit.wicket.PageRegistration;
  50. import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
  51. import com.gitblit.wicket.SessionlessForm;
  52. import com.gitblit.wicket.WicketUtils;
  53. import com.gitblit.wicket.panels.GravatarImage;
  54. import com.gitblit.wicket.panels.NavigationPanel;
  55. /**
  56. * Root page is a topbar, navigable page like Repositories, Users, or
  57. * Federation.
  58. *
  59. * @author James Moger
  60. *
  61. */
  62. public abstract class RootPage extends BasePage {
  63. boolean showAdmin;
  64. IModel<String> username = new Model<String>("");
  65. IModel<String> password = new Model<String>("");
  66. List<RepositoryModel> repositoryModels = new ArrayList<RepositoryModel>();
  67. public RootPage() {
  68. super();
  69. }
  70. public RootPage(PageParameters params) {
  71. super(params);
  72. }
  73. @Override
  74. protected void setupPage(String repositoryName, String pageName) {
  75. boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, false);
  76. boolean authenticateAdmin = GitBlit.getBoolean(Keys.web.authenticateAdminPages, true);
  77. boolean allowAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, true);
  78. if (authenticateAdmin) {
  79. showAdmin = allowAdmin && GitBlitWebSession.get().canAdmin();
  80. // authentication requires state and session
  81. setStatelessHint(false);
  82. } else {
  83. showAdmin = allowAdmin;
  84. if (authenticateView) {
  85. // authentication requires state and session
  86. setStatelessHint(false);
  87. } else {
  88. // no authentication required, no state and no session required
  89. setStatelessHint(true);
  90. }
  91. }
  92. if (authenticateView || authenticateAdmin) {
  93. if (GitBlitWebSession.get().isLoggedIn()) {
  94. UserMenu userFragment = new UserMenu("userPanel", "userMenuFragment", RootPage.this);
  95. add(userFragment);
  96. } else {
  97. LoginForm loginForm = new LoginForm("userPanel", "loginFormFragment", RootPage.this);
  98. add(loginForm);
  99. }
  100. } else {
  101. add(new Label("userPanel").setVisible(false));
  102. }
  103. boolean showRegistrations = GitBlit.canFederate()
  104. && GitBlit.getBoolean(Keys.web.showFederationRegistrations, false);
  105. // navigation links
  106. List<PageRegistration> pages = new ArrayList<PageRegistration>();
  107. if (!authenticateView || (authenticateView && GitBlitWebSession.get().isLoggedIn())) {
  108. pages.add(new PageRegistration("gb.dashboard", DashboardPage.class,
  109. getRootPageParameters()));
  110. pages.add(new PageRegistration("gb.repositories", RepositoriesPage.class,
  111. getRootPageParameters()));
  112. pages.add(new PageRegistration("gb.activity", ActivityPage.class, getRootPageParameters()));
  113. if (GitBlit.getBoolean(Keys.web.allowLuceneIndexing, true)) {
  114. pages.add(new PageRegistration("gb.search", LuceneSearchPage.class));
  115. }
  116. if (showAdmin) {
  117. pages.add(new PageRegistration("gb.users", UsersPage.class));
  118. }
  119. if (showAdmin || showRegistrations) {
  120. pages.add(new PageRegistration("gb.federation", FederationPage.class));
  121. }
  122. if (!authenticateView || (authenticateView && GitBlitWebSession.get().isLoggedIn())) {
  123. addDropDownMenus(pages);
  124. }
  125. }
  126. NavigationPanel navPanel = new NavigationPanel("navPanel", getRootNavPageClass(), pages);
  127. add(navPanel);
  128. // display an error message cached from a redirect
  129. String cachedMessage = GitBlitWebSession.get().clearErrorMessage();
  130. if (!StringUtils.isEmpty(cachedMessage)) {
  131. error(cachedMessage);
  132. } else if (showAdmin) {
  133. int pendingProposals = GitBlit.self().getPendingFederationProposals().size();
  134. if (pendingProposals == 1) {
  135. info(getString("gb.OneProposalToReview"));
  136. } else if (pendingProposals > 1) {
  137. info(MessageFormat.format(getString("gb.nFederationProposalsToReview"),
  138. pendingProposals));
  139. }
  140. }
  141. super.setupPage(repositoryName, pageName);
  142. }
  143. protected Class<? extends BasePage> getRootNavPageClass() {
  144. return getClass();
  145. }
  146. private PageParameters getRootPageParameters() {
  147. if (reusePageParameters()) {
  148. PageParameters pp = getPageParameters();
  149. if (pp != null) {
  150. PageParameters params = new PageParameters(pp);
  151. // remove named project parameter
  152. params.remove("p");
  153. // remove named repository parameter
  154. params.remove("r");
  155. // remove named user parameter
  156. params.remove("user");
  157. // remove days back parameter if it is the default value
  158. if (params.containsKey("db")
  159. && params.getInt("db") == GitBlit.getInteger(Keys.web.activityDuration, 14)) {
  160. params.remove("db");
  161. }
  162. return params;
  163. }
  164. }
  165. return null;
  166. }
  167. protected boolean reusePageParameters() {
  168. return false;
  169. }
  170. private void loginUser(UserModel user) {
  171. if (user != null) {
  172. // Set the user into the session
  173. GitBlitWebSession session = GitBlitWebSession.get();
  174. // issue 62: fix session fixation vulnerability
  175. session.replaceSession();
  176. session.setUser(user);
  177. // Set Cookie
  178. if (GitBlit.getBoolean(Keys.web.allowCookieAuthentication, false)) {
  179. WebResponse response = (WebResponse) getRequestCycle().getResponse();
  180. GitBlit.self().setCookie(response, user);
  181. }
  182. if (!session.continueRequest()) {
  183. PageParameters params = getPageParameters();
  184. if (params == null) {
  185. // redirect to this page
  186. setResponsePage(getClass());
  187. } else {
  188. // Strip username and password and redirect to this page
  189. params.remove("username");
  190. params.remove("password");
  191. setResponsePage(getClass(), params);
  192. }
  193. }
  194. }
  195. }
  196. protected List<RepositoryModel> getRepositoryModels() {
  197. if (repositoryModels.isEmpty()) {
  198. final UserModel user = GitBlitWebSession.get().getUser();
  199. List<RepositoryModel> repositories = GitBlit.self().getRepositoryModels(user);
  200. repositoryModels.addAll(repositories);
  201. Collections.sort(repositoryModels);
  202. }
  203. return repositoryModels;
  204. }
  205. protected void addDropDownMenus(List<PageRegistration> pages) {
  206. }
  207. protected List<DropDownMenuItem> getRepositoryFilterItems(PageParameters params) {
  208. final UserModel user = GitBlitWebSession.get().getUser();
  209. Set<DropDownMenuItem> filters = new LinkedHashSet<DropDownMenuItem>();
  210. List<RepositoryModel> repositories = getRepositoryModels();
  211. // accessible repositories by federation set
  212. Map<String, AtomicInteger> setMap = new HashMap<String, AtomicInteger>();
  213. for (RepositoryModel repository : repositories) {
  214. for (String set : repository.federationSets) {
  215. String key = set.toLowerCase();
  216. if (setMap.containsKey(key)) {
  217. setMap.get(key).incrementAndGet();
  218. } else {
  219. setMap.put(key, new AtomicInteger(1));
  220. }
  221. }
  222. }
  223. if (setMap.size() > 0) {
  224. List<String> sets = new ArrayList<String>(setMap.keySet());
  225. Collections.sort(sets);
  226. for (String set : sets) {
  227. filters.add(new DropDownMenuItem(MessageFormat.format("{0} ({1})", set,
  228. setMap.get(set).get()), "set", set, params));
  229. }
  230. // divider
  231. filters.add(new DropDownMenuItem());
  232. }
  233. // user's team memberships
  234. if (user != null && user.teams.size() > 0) {
  235. List<TeamModel> teams = new ArrayList<TeamModel>(user.teams);
  236. Collections.sort(teams);
  237. for (TeamModel team : teams) {
  238. filters.add(new DropDownMenuItem(MessageFormat.format("{0} ({1})", team.name,
  239. team.repositories.size()), "team", team.name, params));
  240. }
  241. // divider
  242. filters.add(new DropDownMenuItem());
  243. }
  244. // custom filters
  245. String customFilters = GitBlit.getString(Keys.web.customFilters, null);
  246. if (!StringUtils.isEmpty(customFilters)) {
  247. boolean addedExpression = false;
  248. List<String> expressions = StringUtils.getStringsFromValue(customFilters, "!!!");
  249. for (String expression : expressions) {
  250. if (!StringUtils.isEmpty(expression)) {
  251. addedExpression = true;
  252. filters.add(new DropDownMenuItem(null, "x", expression, params));
  253. }
  254. }
  255. // if we added any custom expressions, add a divider
  256. if (addedExpression) {
  257. filters.add(new DropDownMenuItem());
  258. }
  259. }
  260. return new ArrayList<DropDownMenuItem>(filters);
  261. }
  262. protected List<DropDownMenuItem> getTimeFilterItems(PageParameters params) {
  263. // days back choices - additive parameters
  264. int daysBack = GitBlit.getInteger(Keys.web.activityDuration, 14);
  265. if (daysBack < 1) {
  266. daysBack = 14;
  267. }
  268. List<DropDownMenuItem> items = new ArrayList<DropDownMenuItem>();
  269. Set<Integer> choicesSet = new HashSet<Integer>(Arrays.asList(daysBack, 14, 28, 60, 90, 180));
  270. List<Integer> choices = new ArrayList<Integer>(choicesSet);
  271. Collections.sort(choices);
  272. String lastDaysPattern = getString("gb.lastNDays");
  273. for (Integer db : choices) {
  274. String txt = MessageFormat.format(lastDaysPattern, db);
  275. items.add(new DropDownMenuItem(txt, "db", db.toString(), params));
  276. }
  277. items.add(new DropDownMenuItem());
  278. return items;
  279. }
  280. protected List<RepositoryModel> getRepositories(PageParameters params) {
  281. if (params == null) {
  282. return getRepositoryModels();
  283. }
  284. boolean hasParameter = false;
  285. String projectName = WicketUtils.getProjectName(params);
  286. String userName = WicketUtils.getUsername(params);
  287. if (StringUtils.isEmpty(projectName)) {
  288. if (!StringUtils.isEmpty(userName)) {
  289. projectName = "~" + userName;
  290. }
  291. }
  292. String repositoryName = WicketUtils.getRepositoryName(params);
  293. String set = WicketUtils.getSet(params);
  294. String regex = WicketUtils.getRegEx(params);
  295. String team = WicketUtils.getTeam(params);
  296. int daysBack = params.getInt("db", 0);
  297. List<RepositoryModel> availableModels = getRepositoryModels();
  298. Set<RepositoryModel> models = new HashSet<RepositoryModel>();
  299. if (!StringUtils.isEmpty(repositoryName)) {
  300. // try named repository
  301. hasParameter = true;
  302. for (RepositoryModel model : availableModels) {
  303. if (model.name.equalsIgnoreCase(repositoryName)) {
  304. models.add(model);
  305. break;
  306. }
  307. }
  308. }
  309. if (!StringUtils.isEmpty(projectName)) {
  310. // try named project
  311. hasParameter = true;
  312. if (projectName.equalsIgnoreCase(GitBlit.getString(Keys.web.repositoryRootGroupName, "main"))) {
  313. // root project/group
  314. for (RepositoryModel model : availableModels) {
  315. if (model.name.indexOf('/') == -1) {
  316. models.add(model);
  317. }
  318. }
  319. } else {
  320. // named project/group
  321. String group = projectName.toLowerCase() + "/";
  322. for (RepositoryModel model : availableModels) {
  323. if (model.name.toLowerCase().startsWith(group)) {
  324. models.add(model);
  325. }
  326. }
  327. }
  328. }
  329. if (!StringUtils.isEmpty(regex)) {
  330. // filter the repositories by the regex
  331. hasParameter = true;
  332. Pattern pattern = Pattern.compile(regex);
  333. for (RepositoryModel model : availableModels) {
  334. if (pattern.matcher(model.name).find()) {
  335. models.add(model);
  336. }
  337. }
  338. }
  339. if (!StringUtils.isEmpty(set)) {
  340. // filter the repositories by the specified sets
  341. hasParameter = true;
  342. List<String> sets = StringUtils.getStringsFromValue(set, ",");
  343. for (RepositoryModel model : availableModels) {
  344. for (String curr : sets) {
  345. if (model.federationSets.contains(curr)) {
  346. models.add(model);
  347. }
  348. }
  349. }
  350. }
  351. if (!StringUtils.isEmpty(team)) {
  352. // filter the repositories by the specified teams
  353. hasParameter = true;
  354. List<String> teams = StringUtils.getStringsFromValue(team, ",");
  355. // need TeamModels first
  356. List<TeamModel> teamModels = new ArrayList<TeamModel>();
  357. for (String name : teams) {
  358. TeamModel teamModel = GitBlit.self().getTeamModel(name);
  359. if (teamModel != null) {
  360. teamModels.add(teamModel);
  361. }
  362. }
  363. // brute-force our way through finding the matching models
  364. for (RepositoryModel repositoryModel : availableModels) {
  365. for (TeamModel teamModel : teamModels) {
  366. if (teamModel.hasRepositoryPermission(repositoryModel.name)) {
  367. models.add(repositoryModel);
  368. }
  369. }
  370. }
  371. }
  372. if (!hasParameter) {
  373. models.addAll(availableModels);
  374. }
  375. // time-filter the list
  376. if (daysBack > 0) {
  377. Calendar cal = Calendar.getInstance();
  378. cal.set(Calendar.HOUR_OF_DAY, 0);
  379. cal.set(Calendar.MINUTE, 0);
  380. cal.set(Calendar.SECOND, 0);
  381. cal.set(Calendar.MILLISECOND, 0);
  382. cal.add(Calendar.DATE, -1 * daysBack);
  383. Date threshold = cal.getTime();
  384. Set<RepositoryModel> timeFiltered = new HashSet<RepositoryModel>();
  385. for (RepositoryModel model : models) {
  386. if (model.lastChange.after(threshold)) {
  387. timeFiltered.add(model);
  388. }
  389. }
  390. models = timeFiltered;
  391. }
  392. List<RepositoryModel> list = new ArrayList<RepositoryModel>(models);
  393. Collections.sort(list);
  394. return list;
  395. }
  396. /**
  397. * Inline login form.
  398. */
  399. private class LoginForm extends Fragment {
  400. private static final long serialVersionUID = 1L;
  401. public LoginForm(String id, String markupId, MarkupContainer markupProvider) {
  402. super(id, markupId, markupProvider);
  403. setRenderBodyOnly(true);
  404. SessionlessForm<Void> loginForm = new SessionlessForm<Void>("loginForm", RootPage.this.getClass(), getPageParameters()) {
  405. private static final long serialVersionUID = 1L;
  406. @Override
  407. public void onSubmit() {
  408. String username = RootPage.this.username.getObject();
  409. char[] password = RootPage.this.password.getObject().toCharArray();
  410. UserModel user = GitBlit.self().authenticate(username, password);
  411. if (user == null) {
  412. error(getString("gb.invalidUsernameOrPassword"));
  413. } else if (user.username.equals(Constants.FEDERATION_USER)) {
  414. // disallow the federation user from logging in via the
  415. // web ui
  416. error(getString("gb.invalidUsernameOrPassword"));
  417. user = null;
  418. } else {
  419. loginUser(user);
  420. }
  421. }
  422. };
  423. TextField<String> unameField = new TextField<String>("username", username);
  424. WicketUtils.setInputPlaceholder(unameField, markupProvider.getString("gb.username"));
  425. loginForm.add(unameField);
  426. PasswordTextField pwField = new PasswordTextField("password", password);
  427. WicketUtils.setInputPlaceholder(pwField, markupProvider.getString("gb.password"));
  428. loginForm.add(pwField);
  429. add(loginForm);
  430. }
  431. }
  432. /**
  433. * Menu for the authenticated user.
  434. */
  435. static class UserMenu extends Fragment {
  436. private static final long serialVersionUID = 1L;
  437. public UserMenu(String id, String markupId, MarkupContainer markupProvider) {
  438. super(id, markupId, markupProvider);
  439. setRenderBodyOnly(true);
  440. GitBlitWebSession session = GitBlitWebSession.get();
  441. UserModel user = session.getUser();
  442. boolean editCredentials = GitBlit.self().supportsCredentialChanges(user);
  443. boolean standardLogin = session.authenticationType.isStandard();
  444. if (GitBlit.getBoolean(Keys.web.allowGravatar, true)) {
  445. add(new GravatarImage("username", user.getDisplayName(),
  446. user.emailAddress, "navbarGravatar", 20, false, false));
  447. } else {
  448. add(new Label("username", user.getDisplayName()));
  449. }
  450. add(new Label("displayName", user.getDisplayName()));
  451. add(new BookmarkablePageLink<Void>("myProfile",
  452. UserPage.class, WicketUtils.newUsernameParameter(user.username)));
  453. add(new BookmarkablePageLink<Void>("changePassword",
  454. ChangePasswordPage.class).setVisible(editCredentials));
  455. add(new BookmarkablePageLink<Void>("logout",
  456. LogoutPage.class).setVisible(standardLogin));
  457. }
  458. }
  459. }