James Moger
2016-01-25 252dc07d7f85cc344b5919bb7c6166ef84b2102e
commit | author | age
8c9a20 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  */
7bf6e1 16 package com.gitblit.servlet;
8c9a20 17
JM 18 import java.io.IOException;
19 import java.text.MessageFormat;
bd0e83 20 import java.util.Collections;
PM 21 import java.util.Iterator;
8c9a20 22
JM 23 import javax.servlet.FilterChain;
24 import javax.servlet.ServletException;
25 import javax.servlet.ServletRequest;
26 import javax.servlet.ServletResponse;
27 import javax.servlet.http.HttpServletRequest;
28 import javax.servlet.http.HttpServletResponse;
29
1b34b0 30 import com.gitblit.manager.IAuthenticationManager;
db4f6b 31 import com.gitblit.manager.IRepositoryManager;
JM 32 import com.gitblit.manager.IRuntimeManager;
8c9a20 33 import com.gitblit.models.RepositoryModel;
JM 34 import com.gitblit.models.UserModel;
35 import com.gitblit.utils.StringUtils;
36
37 /**
ca9d0f 38  * The AccessRestrictionFilter is an AuthenticationFilter that confirms that the
JM 39  * requested repository can be accessed by the anonymous or named user.
699e71 40  *
892570 41  * The filter extracts the name of the repository from the url and determines if
JM 42  * the requested action for the repository requires a Basic authentication
43  * prompt. If authentication is required and no credentials are stored in the
44  * "Authorization" header, then a basic authentication challenge is issued.
699e71 45  *
8c9a20 46  * http://en.wikipedia.org/wiki/Basic_access_authentication
699e71 47  *
892570 48  * @author James Moger
699e71 49  *
8c9a20 50  */
ca9d0f 51 public abstract class AccessRestrictionFilter extends AuthenticationFilter {
cacf8b 52
65d5bb 53     protected IRuntimeManager runtimeManager;
cacf8b 54
65d5bb 55     protected IRepositoryManager repositoryManager;
cacf8b 56
1b34b0 57     protected AccessRestrictionFilter(
JM 58             IRuntimeManager runtimeManager,
59             IAuthenticationManager authenticationManager,
60             IRepositoryManager repositoryManager) {
61
62         super(authenticationManager);
63
64         this.runtimeManager = runtimeManager;
65         this.repositoryManager = repositoryManager;
cacf8b 66     }
8c9a20 67
892570 68     /**
JM 69      * Extract the repository name from the url.
699e71 70      *
892570 71      * @param url
JM 72      * @return repository name
73      */
8c9a20 74     protected abstract String extractRepositoryName(String url);
JM 75
892570 76     /**
JM 77      * Analyze the url and returns the action of the request.
699e71 78      *
892570 79      * @param url
JM 80      * @return action of the request
81      */
82     protected abstract String getUrlRequestAction(String url);
8c9a20 83
892570 84     /**
72cb19 85      * Determine if a non-existing repository can be created using this filter.
699e71 86      *
72cb19 87      * @return true if the filter allows repository creation
JM 88      */
bd0e83 89     protected abstract boolean isCreationAllowed(String action);
699e71 90
72cb19 91     /**
b74031 92      * Determine if the action may be executed on the repository.
699e71 93      *
b74031 94      * @param repository
JM 95      * @param action
bd0e83 96      * @param method
b74031 97      * @return true if the action may be performed
JM 98      */
bd0e83 99     protected abstract boolean isActionAllowed(RepositoryModel repository, String action, String method);
b74031 100
JM 101     /**
892570 102      * Determine if the repository requires authentication.
699e71 103      *
892570 104      * @param repository
3f8cd4 105      * @param action
892570 106      * @return true if authentication required
JM 107      */
bd0e83 108     protected abstract boolean requiresAuthentication(RepositoryModel repository, String action, String method);
8c9a20 109
892570 110     /**
JM 111      * Determine if the user can access the repository and perform the specified
112      * action.
699e71 113      *
892570 114      * @param repository
JM 115      * @param user
116      * @param action
117      * @return true if user may execute the action on the repository
118      */
119     protected abstract boolean canAccess(RepositoryModel repository, UserModel user, String action);
8c9a20 120
892570 121     /**
72cb19 122      * Allows a filter to create a repository, if one does not exist.
699e71 123      *
72cb19 124      * @param user
JM 125      * @param repository
126      * @param action
127      * @return the repository model, if it is created, null otherwise
128      */
129     protected RepositoryModel createRepository(UserModel user, String repository, String action) {
130         return null;
131     }
bd0e83 132     
PM 133     /**
134      * Allows authentication header to be altered based on the action requested
135      * Default is WWW-Authenticate
46f33f 136      * @param httpRequest
bd0e83 137      * @param action
PM 138      * @return authentication type header
139      */
46f33f 140     protected String getAuthenticationHeader(HttpServletRequest httpRequest, String action) {
bd0e83 141         return "WWW-Authenticate";
PM 142     }
143     
144     /**
145      * Allows request headers to be used as part of filtering
146      * @param request
147      * @return true (default) if headers are valid, false otherwise
148      */
149     protected boolean hasValidRequestHeader(String action, HttpServletRequest request) {
150         return true;
151     }
152     
72cb19 153     /**
892570 154      * doFilter does the actual work of preprocessing the request to ensure that
JM 155      * the user may proceed.
699e71 156      *
88598b 157      * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
JM 158      *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
892570 159      */
8c9a20 160     @Override
JM 161     public void doFilter(final ServletRequest request, final ServletResponse response,
162             final FilterChain chain) throws IOException, ServletException {
163
164         HttpServletRequest httpRequest = (HttpServletRequest) request;
165         HttpServletResponse httpResponse = (HttpServletResponse) response;
166
ca9d0f 167         String fullUrl = getFullUrl(httpRequest);
78753b 168         String repository = extractRepositoryName(fullUrl);
2916cf 169         if (StringUtils.isEmpty(repository)) {
JM 170             httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
171             return;
172         }
db4f6b 173
JM 174         if (repositoryManager.isCollectingGarbage(repository)) {
e92c6d 175             logger.info(MessageFormat.format("ARF: Rejecting request for {0}, busy collecting garbage!", repository));
JM 176             httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
177             return;
178         }
8c9a20 179
JM 180         // Determine if the request URL is restricted
181         String fullSuffix = fullUrl.substring(repository.length());
892570 182         String urlRequestType = getUrlRequestAction(fullSuffix);
8c9a20 183
72cb19 184         UserModel user = getUser(httpRequest);
JM 185
8c9a20 186         // Load the repository model
db4f6b 187         RepositoryModel model = repositoryManager.getRepositoryModel(repository);
8c9a20 188         if (model == null) {
bd0e83 189             if (isCreationAllowed(urlRequestType)) {
72cb19 190                 if (user == null) {
JM 191                     // challenge client to provide credentials for creation. send 401.
db4f6b 192                     if (runtimeManager.isDebugMode()) {
72cb19 193                         logger.info(MessageFormat.format("ARF: CREATE CHALLENGE {0}", fullUrl));
JM 194                     }
bd0e83 195                     
46f33f 196                     httpResponse.setHeader(getAuthenticationHeader(httpRequest, urlRequestType), CHALLENGE);
72cb19 197                     httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
JM 198                     return;
199                 } else {
200                     // see if we can create a repository for this request
201                     model = createRepository(user, repository, urlRequestType);
202                 }
203             }
699e71 204
72cb19 205             if (model == null) {
JM 206                 // repository not found. send 404.
207                 logger.info(MessageFormat.format("ARF: {0} ({1})", fullUrl,
208                         HttpServletResponse.SC_NOT_FOUND));
209                 httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
210                 return;
211             }
8c9a20 212         }
699e71 213
b74031 214         // Confirm that the action may be executed on the repository
bd0e83 215         if (!isActionAllowed(model, urlRequestType, httpRequest.getMethod())) {
b74031 216             logger.info(MessageFormat.format("ARF: action {0} on {1} forbidden ({2})",
JM 217                     urlRequestType, model, HttpServletResponse.SC_FORBIDDEN));
218             httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
219             return;
220         }
8c9a20 221
ca9d0f 222         // Wrap the HttpServletRequest with the AccessRestrictionRequest which
JM 223         // overrides the servlet container user principal methods.
224         // JGit requires either:
225         //
226         // 1. servlet container authenticated user
227         // 2. http.receivepack = true in each repository's config
228         //
229         // Gitblit must conditionally authenticate users per-repository so just
230         // enabling http.receivepack is insufficient.
231         AuthenticatedRequest authenticatedRequest = new AuthenticatedRequest(httpRequest);
232         if (user != null) {
233             authenticatedRequest.setUser(user);
234         }
235
8c9a20 236         // BASIC authentication challenge and response processing
bd0e83 237         if (!StringUtils.isEmpty(urlRequestType) && requiresAuthentication(model, urlRequestType,  httpRequest.getMethod())) {
ca9d0f 238             if (user == null) {
JM 239                 // challenge client to provide credentials. send 401.
db4f6b 240                 if (runtimeManager.isDebugMode()) {
ca9d0f 241                     logger.info(MessageFormat.format("ARF: CHALLENGE {0}", fullUrl));
8c9a20 242                 }
46f33f 243                 httpResponse.setHeader(getAuthenticationHeader(httpRequest, urlRequestType), CHALLENGE);
ca9d0f 244                 httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
JM 245                 return;
246             } else {
247                 // check user access for request
7f7051 248                 if (user.canAdmin() || canAccess(model, user, urlRequestType)) {
ca9d0f 249                     // authenticated request permitted.
JM 250                     // pass processing to the restricted servlet.
251                     newSession(authenticatedRequest, httpResponse);
c71fe2 252                     logger.info(MessageFormat.format("ARF: authenticated {0} to {1} ({2})", user.username,
JJ 253                             fullUrl, HttpServletResponse.SC_CONTINUE));
ca9d0f 254                     chain.doFilter(authenticatedRequest, httpResponse);
JM 255                     return;
256                 }
257                 // valid user, but not for requested access. send 403.
db4f6b 258                 if (runtimeManager.isDebugMode()) {
ca9d0f 259                     logger.info(MessageFormat.format("ARF: {0} forbidden to access {1}",
JM 260                             user.username, fullUrl));
261                 }
262                 httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
263                 return;
8c9a20 264             }
JM 265         }
266
db4f6b 267         if (runtimeManager.isDebugMode()) {
ca9d0f 268             logger.info(MessageFormat.format("ARF: {0} ({1}) unauthenticated", fullUrl,
JM 269                     HttpServletResponse.SC_CONTINUE));
8c9a20 270         }
JM 271         // unauthenticated request permitted.
272         // pass processing to the restricted servlet.
ca9d0f 273         chain.doFilter(authenticatedRequest, httpResponse);
8c9a20 274     }
bd0e83 275     
PM 276     public static boolean hasContentInRequestHeader(HttpServletRequest request, String headerName, String content)
277     {
278         Iterator<String> headerItr = Collections.list(request.getHeaders(headerName)).iterator();
279         
280         while (headerItr.hasNext()) {
281             if (headerItr.next().contains(content)) {
282                 return true;
283             }
284         }
285
286         return false;
287     }
8c9a20 288 }