James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
commit | author | age
f13c4c 1 /*
JM 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  */
1f9dae 16 package com.gitblit.wicket.pages;
5fe7df 17
5e3521 18 import java.io.IOException;
JM 19 import java.io.InputStream;
b0e164 20 import java.text.MessageFormat;
13a3f5 21 import java.util.ArrayList;
JM 22 import java.util.Calendar;
23 import java.util.Collections;
24 import java.util.Date;
25 import java.util.HashSet;
f98825 26 import java.util.LinkedHashMap;
13a3f5 27 import java.util.List;
f98825 28 import java.util.Map;
9adf62 29 import java.util.ResourceBundle;
13a3f5 30 import java.util.Set;
bc10f9 31 import java.util.TimeZone;
13a3f5 32 import java.util.regex.Pattern;
bc10f9 33
cebf45 34 import javax.servlet.http.HttpServletRequest;
JM 35
5e3521 36 import org.apache.commons.io.IOUtils;
3cc6e2 37 import org.apache.wicket.Application;
1078f8 38 import org.apache.wicket.Page;
5fe7df 39 import org.apache.wicket.PageParameters;
d97e52 40 import org.apache.wicket.RedirectToUrlException;
62cec2 41 import org.apache.wicket.markup.html.CSSPackageResource;
5fe7df 42 import org.apache.wicket.markup.html.basic.Label;
94750e 43 import org.apache.wicket.markup.html.link.ExternalLink;
c1c3c6 44 import org.apache.wicket.markup.html.panel.FeedbackPanel;
bae957 45 import org.apache.wicket.markup.html.resources.JavascriptResourceReference;
T 46 import org.apache.wicket.markup.repeater.RepeatingView;
d97e52 47 import org.apache.wicket.protocol.http.RequestUtils;
a7db57 48 import org.apache.wicket.protocol.http.WebResponse;
cebf45 49 import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
2598bf 50 import org.apache.wicket.request.target.basic.RedirectRequestTarget;
a7db57 51 import org.apache.wicket.util.time.Duration;
JM 52 import org.apache.wicket.util.time.Time;
5fe7df 53 import org.slf4j.Logger;
JM 54 import org.slf4j.LoggerFactory;
55
cebf45 56 import com.gitblit.Constants;
b0e164 57 import com.gitblit.Constants.AccessPermission;
f98825 58 import com.gitblit.Constants.AccessRestrictionType;
092f0a 59 import com.gitblit.Constants.AuthorizationControl;
831469 60 import com.gitblit.Constants.FederationStrategy;
155bf7 61 import com.gitblit.Keys;
13a3f5 62 import com.gitblit.models.ProjectModel;
JM 63 import com.gitblit.models.TeamModel;
85c2e6 64 import com.gitblit.models.UserModel;
13a3f5 65 import com.gitblit.utils.StringUtils;
9adf62 66 import com.gitblit.utils.TimeUtils;
a7db57 67 import com.gitblit.wicket.CacheControl;
6ef8d7 68 import com.gitblit.wicket.GitBlitWebApp;
1f9dae 69 import com.gitblit.wicket.GitBlitWebSession;
94750e 70 import com.gitblit.wicket.WicketUtils;
5fe7df 71
0f3cb2 72 public abstract class BasePage extends SessionPage {
5fe7df 73
e92cef 74     private transient Logger logger;
699e71 75
9adf62 76     private transient TimeUtils timeUtils;
5fe7df 77
JM 78     public BasePage() {
79         super();
62cec2 80         customizeHeader();
5fe7df 81     }
JM 82
83     public BasePage(PageParameters params) {
84         super(params);
62cec2 85         customizeHeader();
e92cef 86     }
JM 87
88     protected Logger logger() {
89         if (logger == null) {
90             logger = LoggerFactory.getLogger(getClass());
91         }
92         return logger;
85c2e6 93     }
699e71 94
62cec2 95     private void customizeHeader() {
99d0d4 96         if (app().settings().getBoolean(Keys.web.useResponsiveLayout, true)) {
62cec2 97             add(CSSPackageResource.getHeaderContribution("bootstrap/css/bootstrap-responsive.css"));
JM 98         }
5ec752 99         if (app().settings().getBoolean(Keys.web.hideHeader, false)) {
JM 100             add(CSSPackageResource.getHeaderContribution("hideheader.css"));
101         }
62cec2 102     }
699e71 103
ff17f7 104     protected String getContextUrl() {
JM 105         return getRequest().getRelativePathPrefixToContextRoot();
106     }
107
307910 108     protected String getCanonicalUrl() {
JM 109         return getCanonicalUrl(getClass(), getPageParameters());
110     }
111
112     protected String getCanonicalUrl(Class<? extends BasePage> clazz, PageParameters params) {
113         String relativeUrl = urlFor(clazz, params).toString();
114         String canonicalUrl = RequestUtils.toAbsolutePath(relativeUrl);
115         return canonicalUrl;
2598bf 116     }
JM 117
60bbdf 118     protected void redirectTo(Class<? extends BasePage> pageClass) {
JM 119         redirectTo(pageClass, null);
2598bf 120     }
JM 121
60bbdf 122     protected void redirectTo(Class<? extends BasePage> pageClass, PageParameters parameters) {
2598bf 123         String absoluteUrl = getCanonicalUrl(pageClass, parameters);
JM 124         getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
307910 125     }
JM 126
9adf62 127     protected String getLanguageCode() {
JM 128         return GitBlitWebSession.get().getLocale().getLanguage();
129     }
699e71 130
55037b 131     protected String getCountryCode() {
JM 132         return GitBlitWebSession.get().getLocale().getCountry().toLowerCase();
133     }
699e71 134
9adf62 135     protected TimeUtils getTimeUtils() {
JM 136         if (timeUtils == null) {
699e71 137             ResourceBundle bundle;
9adf62 138             try {
JM 139                 bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp", GitBlitWebSession.get().getLocale());
140             } catch (Throwable t) {
141                 bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp");
142             }
9b26b7 143             timeUtils = new TimeUtils(bundle, getTimeZone());
9adf62 144         }
JM 145         return timeUtils;
146     }
699e71 147
3cc6e2 148     @Override
JM 149     protected void onBeforeRender() {
99d0d4 150         if (app().isDebugMode()) {
3cc6e2 151             // strip Wicket tags in debug mode for jQuery DOM traversal
JM 152             Application.get().getMarkupSettings().setStripWicketTags(true);
153         }
154         super.onBeforeRender();
155     }
156
157     @Override
158     protected void onAfterRender() {
99d0d4 159         if (app().isDebugMode()) {
3cc6e2 160             // restore Wicket debug tags
JM 161             Application.get().getMarkupSettings().setStripWicketTags(false);
162         }
163         super.onAfterRender();
a7db57 164     }
699e71 165
a7db57 166     @Override
JM 167     protected void setHeaders(WebResponse response)    {
307910 168         // set canonical link as http header for SEO (issue-304)
JM 169         // https://support.google.com/webmasters/answer/139394?hl=en
99d0d4 170         response.setHeader("Link", MessageFormat.format("<{0}>; rel=\"canonical\"", getCanonicalUrl()));
JM 171         int expires = app().settings().getInteger(Keys.web.pageCacheExpires, 0);
a7db57 172         if (expires > 0) {
JM 173             // pages are personalized for the authenticated user so they must be
174             // marked private to prohibit proxy servers from caching them
175             response.setHeader("Cache-Control", "private, must-revalidate");
176             setLastModified();
177         } else {
178             // use default Wicket caching behavior
179             super.setHeaders(response);
180         }
71647a 181
JM 182         // XRF vulnerability. issue-500 / ticket-166
183         response.setHeader("X-Frame-Options", "SAMEORIGIN");
a7db57 184     }
699e71 185
a7db57 186     /**
JM 187      * Sets the last-modified header date, if appropriate, for this page.  The
188      * date used is determined by the CacheControl annotation.
699e71 189      *
a7db57 190      */
JM 191     protected void setLastModified() {
192         if (getClass().isAnnotationPresent(CacheControl.class)) {
193             CacheControl cacheControl = getClass().getAnnotation(CacheControl.class);
194             switch (cacheControl.value()) {
195             case ACTIVITY:
99d0d4 196                 setLastModified(app().getLastActivityDate());
a7db57 197                 break;
JM 198             case BOOT:
99d0d4 199                 setLastModified(app().getBootDate());
a7db57 200                 break;
JM 201             case NONE:
202                 break;
203             default:
e92cef 204                 logger().warn(getClass().getSimpleName() + ": unhandled LastModified type " + cacheControl.value());
a7db57 205                 break;
JM 206             }
207         }
208     }
699e71 209
a7db57 210     /**
JM 211      * Sets the last-modified header field and the expires field.
699e71 212      *
a7db57 213      * @param when
JM 214      */
215     protected final void setLastModified(Date when) {
216         if (when == null) {
217             return;
218         }
699e71 219
99d0d4 220         if (when.before(app().getBootDate())) {
a7db57 221             // last-modified can not be before the Gitblit boot date
JM 222             // this helps ensure that pages are properly refreshed after a
223             // server config change
99d0d4 224             when = app().getBootDate();
a7db57 225         }
699e71 226
99d0d4 227         int expires = app().settings().getInteger(Keys.web.pageCacheExpires, 0);
a7db57 228         WebResponse response = (WebResponse) getResponse();
JM 229         response.setLastModifiedTime(Time.valueOf(when));
230         response.setDateHeader("Expires", System.currentTimeMillis() + Duration.minutes(expires).getMilliseconds());
231     }
155bf7 232
81c90e 233     protected String getPageTitle(String repositoryName) {
99d0d4 234         String siteName = app().settings().getString(Keys.web.siteName, Constants.NAME);
9b26b7 235         if (StringUtils.isEmpty(siteName)) {
JM 236             siteName = Constants.NAME;
237         }
cebf45 238         if (repositoryName != null && repositoryName.trim().length() > 0) {
81c90e 239             return repositoryName + " - " + siteName;
cebf45 240         } else {
81c90e 241             return siteName;
cebf45 242         }
81c90e 243     }
JM 244
245     protected void setupPage(String repositoryName, String pageName) {
246         add(new Label("title", getPageTitle(repositoryName)));
9f92fb 247         getBottomScriptContainer();
99d0d4 248         String rootLinkUrl = app().settings().getString(Keys.web.rootLink, urlFor(GitBlitWebApp.get().getHomePage(), null).toString());
a0831d 249         ExternalLink rootLink = new ExternalLink("rootLink", rootLinkUrl);
99d0d4 250         WicketUtils.setHtmlTooltip(rootLink, app().settings().getString(Keys.web.siteName, Constants.NAME));
94750e 251         add(rootLink);
JM 252
c1c3c6 253         // Feedback panel for info, warning, and non-fatal error messages
JM 254         add(new FeedbackPanel("feedback"));
20165d 255
f6b200 256         add(new Label("gbVersion", "v" + Constants.getVersion()));
99d0d4 257         if (app().settings().getBoolean(Keys.web.aggressiveHeapManagement, false)) {
cebf45 258             System.gc();
JM 259         }
260     }
155bf7 261
f98825 262     protected Map<AccessRestrictionType, String> getAccessRestrictions() {
JM 263         Map<AccessRestrictionType, String> map = new LinkedHashMap<AccessRestrictionType, String>();
264         for (AccessRestrictionType type : AccessRestrictionType.values()) {
265             switch (type) {
266             case NONE:
267                 map.put(type, getString("gb.notRestricted"));
268                 break;
269             case PUSH:
270                 map.put(type, getString("gb.pushRestricted"));
271                 break;
272             case CLONE:
273                 map.put(type, getString("gb.cloneRestricted"));
274                 break;
275             case VIEW:
276                 map.put(type, getString("gb.viewRestricted"));
277                 break;
278             }
279         }
280         return map;
281     }
699e71 282
b0e164 283     protected Map<AccessPermission, String> getAccessPermissions() {
JM 284         Map<AccessPermission, String> map = new LinkedHashMap<AccessPermission, String>();
285         for (AccessPermission type : AccessPermission.values()) {
286             switch (type) {
287             case NONE:
288                 map.put(type, MessageFormat.format(getString("gb.noPermission"), type.code));
289                 break;
2d48e2 290             case EXCLUDE:
JM 291                 map.put(type, MessageFormat.format(getString("gb.excludePermission"), type.code));
292                 break;
b0e164 293             case VIEW:
JM 294                 map.put(type, MessageFormat.format(getString("gb.viewPermission"), type.code));
295                 break;
296             case CLONE:
297                 map.put(type, MessageFormat.format(getString("gb.clonePermission"), type.code));
298                 break;
299             case PUSH:
300                 map.put(type, MessageFormat.format(getString("gb.pushPermission"), type.code));
301                 break;
302             case CREATE:
303                 map.put(type, MessageFormat.format(getString("gb.createPermission"), type.code));
304                 break;
305             case DELETE:
306                 map.put(type, MessageFormat.format(getString("gb.deletePermission"), type.code));
307                 break;
308             case REWIND:
309                 map.put(type, MessageFormat.format(getString("gb.rewindPermission"), type.code));
310                 break;
311             }
312         }
313         return map;
314     }
699e71 315
831469 316     protected Map<FederationStrategy, String> getFederationTypes() {
JM 317         Map<FederationStrategy, String> map = new LinkedHashMap<FederationStrategy, String>();
318         for (FederationStrategy type : FederationStrategy.values()) {
319             switch (type) {
320             case EXCLUDE:
321                 map.put(type, getString("gb.excludeFromFederation"));
322                 break;
323             case FEDERATE_THIS:
324                 map.put(type, getString("gb.federateThis"));
325                 break;
326             case FEDERATE_ORIGIN:
327                 map.put(type, getString("gb.federateOrigin"));
328                 break;
329             }
330         }
331         return map;
332     }
699e71 333
092f0a 334     protected Map<AuthorizationControl, String> getAuthorizationControls() {
JM 335         Map<AuthorizationControl, String> map = new LinkedHashMap<AuthorizationControl, String>();
336         for (AuthorizationControl type : AuthorizationControl.values()) {
337             switch (type) {
338             case AUTHENTICATED:
339                 map.put(type, getString("gb.allowAuthenticatedDescription"));
340                 break;
341             case NAMED:
342                 map.put(type, getString("gb.allowNamedDescription"));
343                 break;
344             }
345         }
346         return map;
347     }
f98825 348
bc10f9 349     protected TimeZone getTimeZone() {
99d0d4 350         return app().settings().getBoolean(Keys.web.useClientTimezone, false) ? GitBlitWebSession.get()
JM 351                 .getTimezone() : app().getTimezone();
bc10f9 352     }
155bf7 353
cebf45 354     protected String getServerName() {
JM 355         ServletWebRequest servletWebRequest = (ServletWebRequest) getRequest();
356         HttpServletRequest req = servletWebRequest.getHttpServletRequest();
357         return req.getServerName();
358     }
699e71 359
13a3f5 360     protected List<ProjectModel> getProjectModels() {
JM 361         final UserModel user = GitBlitWebSession.get().getUser();
99d0d4 362         List<ProjectModel> projects = app().projects().getProjectModels(user, true);
13a3f5 363         return projects;
JM 364     }
699e71 365
13a3f5 366     protected List<ProjectModel> getProjects(PageParameters params) {
JM 367         if (params == null) {
368             return getProjectModels();
369         }
370
371         boolean hasParameter = false;
372         String regex = WicketUtils.getRegEx(params);
373         String team = WicketUtils.getTeam(params);
374         int daysBack = params.getInt("db", 0);
99d0d4 375         int maxDaysBack = app().settings().getInteger(Keys.web.activityDurationMaximum, 30);
13a3f5 376
JM 377         List<ProjectModel> availableModels = getProjectModels();
378         Set<ProjectModel> models = new HashSet<ProjectModel>();
379
380         if (!StringUtils.isEmpty(regex)) {
381             // filter the projects by the regex
382             hasParameter = true;
383             Pattern pattern = Pattern.compile(regex);
384             for (ProjectModel model : availableModels) {
385                 if (pattern.matcher(model.name).find()) {
386                     models.add(model);
387                 }
388             }
389         }
390
391         if (!StringUtils.isEmpty(team)) {
392             // filter the projects by the specified teams
393             hasParameter = true;
394             List<String> teams = StringUtils.getStringsFromValue(team, ",");
395
396             // need TeamModels first
397             List<TeamModel> teamModels = new ArrayList<TeamModel>();
398             for (String name : teams) {
99d0d4 399                 TeamModel teamModel = app().users().getTeamModel(name);
13a3f5 400                 if (teamModel != null) {
JM 401                     teamModels.add(teamModel);
402                 }
403             }
404
405             // brute-force our way through finding the matching models
406             for (ProjectModel projectModel : availableModels) {
407                 for (String repositoryName : projectModel.repositories) {
408                     for (TeamModel teamModel : teamModels) {
20714a 409                         if (teamModel.hasRepositoryPermission(repositoryName)) {
13a3f5 410                             models.add(projectModel);
JM 411                         }
412                     }
413                 }
414             }
415         }
416
417         if (!hasParameter) {
418             models.addAll(availableModels);
419         }
420
421         // time-filter the list
422         if (daysBack > 0) {
da7151 423             if (maxDaysBack > 0 && daysBack > maxDaysBack) {
OL 424                 daysBack = maxDaysBack;
425             }
13a3f5 426             Calendar cal = Calendar.getInstance();
JM 427             cal.set(Calendar.HOUR_OF_DAY, 0);
428             cal.set(Calendar.MINUTE, 0);
429             cal.set(Calendar.SECOND, 0);
430             cal.set(Calendar.MILLISECOND, 0);
431             cal.add(Calendar.DATE, -1 * daysBack);
432             Date threshold = cal.getTime();
433             Set<ProjectModel> timeFiltered = new HashSet<ProjectModel>();
434             for (ProjectModel model : models) {
435                 if (model.lastChange.after(threshold)) {
436                     timeFiltered.add(model);
437                 }
438             }
439             models = timeFiltered;
440         }
441
442         List<ProjectModel> list = new ArrayList<ProjectModel>(models);
443         Collections.sort(list);
444         return list;
445     }
85c2e6 446
5450d0 447     public void warn(String message, Throwable t) {
e92cef 448         logger().warn(message, t);
5450d0 449     }
699e71 450
bc9d4a 451     public void error(String message, boolean redirect) {
1078f8 452         error(message, null, redirect ? getApplication().getHomePage() : null);
bc9d4a 453     }
JM 454
455     public void error(String message, Throwable t, boolean redirect) {
1078f8 456         error(message, t, getApplication().getHomePage());
JM 457     }
699e71 458
1078f8 459     public void error(String message, Throwable t, Class<? extends Page> toPage) {
JM 460         error(message, t, toPage, null);
461     }
699e71 462
1078f8 463     public void error(String message, Throwable t, Class<? extends Page> toPage, PageParameters params) {
JM 464         if (t == null) {
e92cef 465             logger().error(message  + " for " + GitBlitWebSession.get().getUsername());
1078f8 466         } else {
e92cef 467             logger().error(message  + " for " + GitBlitWebSession.get().getUsername(), t);
1078f8 468         }
JM 469         if (toPage != null) {
bc9d4a 470             GitBlitWebSession.get().cacheErrorMessage(message);
1078f8 471             String relativeUrl = urlFor(toPage, params).toString();
JM 472             String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
473             throw new RedirectToUrlException(absoluteUrl);
bc9d4a 474         } else {
JM 475             super.error(message);
476         }
5fe7df 477     }
85c2e6 478
JM 479     public void authenticationError(String message) {
e92cef 480         logger().error(getRequest().getURL() + " for " + GitBlitWebSession.get().getUsername());
d97e52 481         if (!GitBlitWebSession.get().isLoggedIn()) {
JM 482             // cache the request if we have not authenticated.
483             // the request will continue after authentication.
484             GitBlitWebSession.get().cacheRequest(getClass());
85c2e6 485         }
d97e52 486         error(message, true);
20165d 487     }
5e3521 488
JM 489     protected String readResource(String resource) {
490         StringBuilder sb = new StringBuilder();
491         InputStream is = null;
492         try {
493             is = getClass().getResourceAsStream(resource);
494             List<String> lines = IOUtils.readLines(is);
495             for (String line : lines) {
496                 sb.append(line).append('\n');
497             }
498         } catch (IOException e) {
499
500         } finally {
501             if (is != null) {
502                 try {
503                     is.close();
504                 } catch (IOException e) {
505                 }
506             }
507         }
508         return sb.toString();
509     }
2598bf 510
9f92fb 511     private RepeatingView getBottomScriptContainer() {
JM 512         RepeatingView bottomScriptContainer = (RepeatingView) get("bottomScripts");
513         if (bottomScriptContainer == null) {
514             bottomScriptContainer = new RepeatingView("bottomScripts");
515             bottomScriptContainer.setRenderBodyOnly(true);
516             add(bottomScriptContainer);
517         }
518         return bottomScriptContainer;
519     }
520
bae957 521     /**
T 522      * Adds a HTML script element loading the javascript designated by the given path.
523      *
524      * @param scriptPath
b95f47 525      *            page-relative path to the Javascript resource; normally starts with "scripts/"
bae957 526      */
T 527     protected void addBottomScript(String scriptPath) {
9f92fb 528         RepeatingView bottomScripts = getBottomScriptContainer();
JM 529         Label script = new Label(bottomScripts.newChildId(), "<script type='text/javascript' src='"
530                 + urlFor(new JavascriptResourceReference(this.getClass(), scriptPath)) + "'></script>\n");
531         bottomScripts.add(script.setEscapeModelStrings(false).setRenderBodyOnly(true));
bae957 532     }
T 533
534     /**
535      * Adds a HTML script element containing the given code.
536      *
537      * @param code
538      *            inline script code
539      */
540     protected void addBottomScriptInline(String code) {
9f92fb 541         RepeatingView bottomScripts = getBottomScriptContainer();
JM 542         Label script = new Label(bottomScripts.newChildId(),
543                 "<script type='text/javascript'>/*<![CDATA[*/\n" + code + "\n//]]>\n</script>\n");
544         bottomScripts.add(script.setEscapeModelStrings(false).setRenderBodyOnly(true));
bae957 545     }
T 546
5fe7df 547 }