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.

AccessRestrictionFilter.java 11KB

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