James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
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
136      * @param action
137      * @return authentication type header
138      */
139     protected String getAuthenticationHeader(String action) {
140         return "WWW-Authenticate";
141     }
142     
143     /**
144      * Allows request headers to be used as part of filtering
145      * @param request
146      * @return true (default) if headers are valid, false otherwise
147      */
148     protected boolean hasValidRequestHeader(String action, HttpServletRequest request) {
149         return true;
150     }
151     
72cb19 152     /**
892570 153      * doFilter does the actual work of preprocessing the request to ensure that
JM 154      * the user may proceed.
699e71 155      *
88598b 156      * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
JM 157      *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
892570 158      */
8c9a20 159     @Override
JM 160     public void doFilter(final ServletRequest request, final ServletResponse response,
161             final FilterChain chain) throws IOException, ServletException {
162
163         HttpServletRequest httpRequest = (HttpServletRequest) request;
164         HttpServletResponse httpResponse = (HttpServletResponse) response;
165
ca9d0f 166         String fullUrl = getFullUrl(httpRequest);
78753b 167         String repository = extractRepositoryName(fullUrl);
2916cf 168         if (StringUtils.isEmpty(repository)) {
JM 169             httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
170             return;
171         }
db4f6b 172
JM 173         if (repositoryManager.isCollectingGarbage(repository)) {
e92c6d 174             logger.info(MessageFormat.format("ARF: Rejecting request for {0}, busy collecting garbage!", repository));
JM 175             httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
176             return;
177         }
8c9a20 178
JM 179         // Determine if the request URL is restricted
180         String fullSuffix = fullUrl.substring(repository.length());
892570 181         String urlRequestType = getUrlRequestAction(fullSuffix);
8c9a20 182
72cb19 183         UserModel user = getUser(httpRequest);
JM 184
8c9a20 185         // Load the repository model
db4f6b 186         RepositoryModel model = repositoryManager.getRepositoryModel(repository);
8c9a20 187         if (model == null) {
bd0e83 188             if (isCreationAllowed(urlRequestType)) {
72cb19 189                 if (user == null) {
JM 190                     // challenge client to provide credentials for creation. send 401.
db4f6b 191                     if (runtimeManager.isDebugMode()) {
72cb19 192                         logger.info(MessageFormat.format("ARF: CREATE CHALLENGE {0}", fullUrl));
JM 193                     }
bd0e83 194                     
PM 195                     httpResponse.setHeader(getAuthenticationHeader(urlRequestType), CHALLENGE);
72cb19 196                     httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
JM 197                     return;
198                 } else {
199                     // see if we can create a repository for this request
200                     model = createRepository(user, repository, urlRequestType);
201                 }
202             }
699e71 203
72cb19 204             if (model == null) {
JM 205                 // repository not found. send 404.
206                 logger.info(MessageFormat.format("ARF: {0} ({1})", fullUrl,
207                         HttpServletResponse.SC_NOT_FOUND));
208                 httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
209                 return;
210             }
8c9a20 211         }
699e71 212
b74031 213         // Confirm that the action may be executed on the repository
bd0e83 214         if (!isActionAllowed(model, urlRequestType, httpRequest.getMethod())) {
b74031 215             logger.info(MessageFormat.format("ARF: action {0} on {1} forbidden ({2})",
JM 216                     urlRequestType, model, HttpServletResponse.SC_FORBIDDEN));
217             httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
218             return;
219         }
8c9a20 220
ca9d0f 221         // Wrap the HttpServletRequest with the AccessRestrictionRequest which
JM 222         // overrides the servlet container user principal methods.
223         // JGit requires either:
224         //
225         // 1. servlet container authenticated user
226         // 2. http.receivepack = true in each repository's config
227         //
228         // Gitblit must conditionally authenticate users per-repository so just
229         // enabling http.receivepack is insufficient.
230         AuthenticatedRequest authenticatedRequest = new AuthenticatedRequest(httpRequest);
231         if (user != null) {
232             authenticatedRequest.setUser(user);
233         }
234
8c9a20 235         // BASIC authentication challenge and response processing
bd0e83 236         if (!StringUtils.isEmpty(urlRequestType) && requiresAuthentication(model, urlRequestType,  httpRequest.getMethod())) {
ca9d0f 237             if (user == null) {
JM 238                 // challenge client to provide credentials. send 401.
db4f6b 239                 if (runtimeManager.isDebugMode()) {
ca9d0f 240                     logger.info(MessageFormat.format("ARF: CHALLENGE {0}", fullUrl));
8c9a20 241                 }
bd0e83 242                 httpResponse.setHeader(getAuthenticationHeader(urlRequestType), CHALLENGE);
ca9d0f 243                 httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
JM 244                 return;
245             } else {
246                 // check user access for request
7f7051 247                 if (user.canAdmin() || canAccess(model, user, urlRequestType)) {
ca9d0f 248                     // authenticated request permitted.
JM 249                     // pass processing to the restricted servlet.
250                     newSession(authenticatedRequest, httpResponse);
251                     logger.info(MessageFormat.format("ARF: {0} ({1}) authenticated", fullUrl,
252                             HttpServletResponse.SC_CONTINUE));
253                     chain.doFilter(authenticatedRequest, httpResponse);
254                     return;
255                 }
256                 // valid user, but not for requested access. send 403.
db4f6b 257                 if (runtimeManager.isDebugMode()) {
ca9d0f 258                     logger.info(MessageFormat.format("ARF: {0} forbidden to access {1}",
JM 259                             user.username, fullUrl));
260                 }
261                 httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
262                 return;
8c9a20 263             }
JM 264         }
265
db4f6b 266         if (runtimeManager.isDebugMode()) {
ca9d0f 267             logger.info(MessageFormat.format("ARF: {0} ({1}) unauthenticated", fullUrl,
JM 268                     HttpServletResponse.SC_CONTINUE));
8c9a20 269         }
JM 270         // unauthenticated request permitted.
271         // pass processing to the restricted servlet.
ca9d0f 272         chain.doFilter(authenticatedRequest, httpResponse);
8c9a20 273     }
bd0e83 274     
PM 275     public static boolean hasContentInRequestHeader(HttpServletRequest request, String headerName, String content)
276     {
277         Iterator<String> headerItr = Collections.list(request.getHeaders(headerName)).iterator();
278         
279         while (headerItr.hasNext()) {
280             if (headerItr.next().contains(content)) {
281                 return true;
282             }
283         }
284
285         return false;
286     }
8c9a20 287 }