PageRenderTime 21ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java

https://bitbucket.org/sdorra/scm-manager/
Java | 332 lines | 149 code | 43 blank | 140 comment | 15 complexity | 851afdf729328501eb3e1f17bb06c924 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. /**
  2. * Copyright (c) 2010, Sebastian Sdorra
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice,
  9. * this list of conditions and the following disclaimer.
  10. * 2. Redistributions in binary form must reproduce the above copyright notice,
  11. * this list of conditions and the following disclaimer in the documentation
  12. * and/or other materials provided with the distribution.
  13. * 3. Neither the name of SCM-Manager; nor the names of its
  14. * contributors may be used to endorse or promote products derived from this
  15. * software without specific prior written permission.
  16. *
  17. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  18. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  19. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  20. * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
  21. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  22. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  23. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  24. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  26. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. *
  28. * http://bitbucket.org/sdorra/scm-manager
  29. *
  30. */
  31. package sonia.scm.web.filter;
  32. //~--- non-JDK imports --------------------------------------------------------
  33. import com.google.inject.Inject;
  34. import com.google.inject.Singleton;
  35. import org.apache.shiro.SecurityUtils;
  36. import org.apache.shiro.authc.AuthenticationException;
  37. import org.apache.shiro.authc.AuthenticationToken;
  38. import org.apache.shiro.subject.Subject;
  39. import org.slf4j.Logger;
  40. import org.slf4j.LoggerFactory;
  41. import sonia.scm.SCMContext;
  42. import sonia.scm.config.ScmConfiguration;
  43. import sonia.scm.util.HttpUtil;
  44. import sonia.scm.util.Util;
  45. import sonia.scm.web.WebTokenGenerator;
  46. //~--- JDK imports ------------------------------------------------------------
  47. import java.io.IOException;
  48. import java.util.Set;
  49. import javax.servlet.FilterChain;
  50. import javax.servlet.ServletException;
  51. import javax.servlet.http.HttpServletRequest;
  52. import javax.servlet.http.HttpServletResponse;
  53. /**
  54. * Handles authentication, if a one of the {@link WebTokenGenerator} returns
  55. * an {@link AuthenticationToken}.
  56. *
  57. * @author Sebastian Sdorra
  58. * @since 2.0.0
  59. */
  60. @Singleton
  61. public class AuthenticationFilter extends HttpFilter
  62. {
  63. /** marker for failed authentication */
  64. private static final String ATTRIBUTE_FAILED_AUTH = "sonia.scm.auth.failed";
  65. /** Field description */
  66. private static final String HEADER_AUTHORIZATION = "Authorization";
  67. /** the logger for AuthenticationFilter */
  68. private static final Logger logger =
  69. LoggerFactory.getLogger(AuthenticationFilter.class);
  70. //~--- constructors ---------------------------------------------------------
  71. /**
  72. * Constructs a new basic authenticaton filter.
  73. *
  74. * @param configuration scm-manager global configuration
  75. * @param tokenGenerators web token generators
  76. */
  77. @Inject
  78. public AuthenticationFilter(ScmConfiguration configuration,
  79. Set<WebTokenGenerator> tokenGenerators)
  80. {
  81. this.configuration = configuration;
  82. this.tokenGenerators = tokenGenerators;
  83. }
  84. //~--- methods --------------------------------------------------------------
  85. /**
  86. * Handles authentication, if a one of the {@link WebTokenGenerator} returns
  87. * an {@link AuthenticationToken}.
  88. *
  89. *
  90. * @param request servlet request
  91. * @param response servlet response
  92. * @param chain filter chain
  93. *
  94. * @throws IOException
  95. * @throws ServletException
  96. */
  97. @Override
  98. protected void doFilter(HttpServletRequest request,
  99. HttpServletResponse response, FilterChain chain)
  100. throws IOException, ServletException
  101. {
  102. Subject subject = SecurityUtils.getSubject();
  103. AuthenticationToken token = createToken(request);
  104. if (token != null)
  105. {
  106. logger.trace(
  107. "found authentication token on request, start authentication");
  108. handleAuthentication(request, response, chain, subject, token);
  109. }
  110. else if (subject.isAuthenticated())
  111. {
  112. logger.trace("user is allready authenticated");
  113. processChain(request, response, chain, subject);
  114. }
  115. else if (isAnonymousAccessEnabled())
  116. {
  117. logger.trace("anonymous access granted");
  118. processChain(request, response, chain, subject);
  119. }
  120. else
  121. {
  122. logger.trace("could not find user send unauthorized");
  123. handleUnauthorized(request, response, chain);
  124. }
  125. }
  126. /**
  127. * Sends status code 403 back to client, if the authentication has failed.
  128. * In all other cases the method will send status code 403 back to client.
  129. *
  130. * @param request servlet request
  131. * @param response servlet response
  132. * @param chain filter chain
  133. *
  134. * @throws IOException
  135. * @throws ServletException
  136. *
  137. * @since 1.8
  138. */
  139. protected void handleUnauthorized(HttpServletRequest request,
  140. HttpServletResponse response, FilterChain chain)
  141. throws IOException, ServletException
  142. {
  143. // send only forbidden, if the authentication has failed.
  144. // see https://bitbucket.org/sdorra/scm-manager/issue/545/git-clone-with-username-in-url-does-not
  145. if (Boolean.TRUE.equals(request.getAttribute(ATTRIBUTE_FAILED_AUTH)))
  146. {
  147. sendFailedAuthenticationError(request, response);
  148. }
  149. else
  150. {
  151. sendUnauthorizedError(request, response);
  152. }
  153. }
  154. /**
  155. * Sends an error for a failed authentication back to client.
  156. *
  157. *
  158. * @param request http request
  159. * @param response http response
  160. *
  161. * @throws IOException
  162. */
  163. protected void sendFailedAuthenticationError(HttpServletRequest request,
  164. HttpServletResponse response)
  165. throws IOException
  166. {
  167. HttpUtil.sendUnauthorized(request, response,
  168. configuration.getRealmDescription());
  169. }
  170. /**
  171. * Sends an unauthorized error back to client.
  172. *
  173. *
  174. * @param request http request
  175. * @param response http response
  176. *
  177. * @throws IOException
  178. */
  179. protected void sendUnauthorizedError(HttpServletRequest request,
  180. HttpServletResponse response)
  181. throws IOException
  182. {
  183. HttpUtil.sendUnauthorized(request, response,
  184. configuration.getRealmDescription());
  185. }
  186. /**
  187. * Iterates all {@link WebTokenGenerator} and creates an
  188. * {@link AuthenticationToken} from the given request.
  189. *
  190. *
  191. * @param request http servlet request
  192. *
  193. * @return authentication token of {@code null}
  194. */
  195. private AuthenticationToken createToken(HttpServletRequest request)
  196. {
  197. AuthenticationToken token = null;
  198. for (WebTokenGenerator generator : tokenGenerators)
  199. {
  200. token = generator.createToken(request);
  201. if (token != null)
  202. {
  203. logger.trace("generated web token {} from generator {}",
  204. token.getClass(), generator.getClass());
  205. break;
  206. }
  207. }
  208. return token;
  209. }
  210. /**
  211. * Handle authentication with the given {@link AuthenticationToken}.
  212. *
  213. *
  214. * @param request http servlet request
  215. * @param response http servlet response
  216. * @param chain filter chain
  217. * @param subject subject
  218. * @param token authentication token
  219. *
  220. * @throws IOException
  221. * @throws ServletException
  222. */
  223. private void handleAuthentication(HttpServletRequest request,
  224. HttpServletResponse response, FilterChain chain, Subject subject,
  225. AuthenticationToken token)
  226. throws IOException, ServletException
  227. {
  228. logger.trace("found basic authorization header, start authentication");
  229. try
  230. {
  231. subject.login(token);
  232. processChain(request, response, chain, subject);
  233. }
  234. catch (AuthenticationException ex)
  235. {
  236. logger.warn("authentication failed", ex);
  237. handleUnauthorized(request, response, chain);
  238. }
  239. }
  240. /**
  241. * Process the filter chain.
  242. *
  243. *
  244. * @param request http servlet request
  245. * @param response http servlet response
  246. * @param chain filter chain
  247. * @param subject subject
  248. *
  249. * @throws IOException
  250. * @throws ServletException
  251. */
  252. private void processChain(HttpServletRequest request,
  253. HttpServletResponse response, FilterChain chain, Subject subject)
  254. throws IOException, ServletException
  255. {
  256. String username = Util.EMPTY_STRING;
  257. if (!subject.isAuthenticated())
  258. {
  259. // anonymous access
  260. username = SCMContext.USER_ANONYMOUS;
  261. }
  262. else
  263. {
  264. Object obj = subject.getPrincipal();
  265. if (obj != null)
  266. {
  267. username = obj.toString();
  268. }
  269. }
  270. chain.doFilter(new SecurityHttpServletRequestWrapper(request, username),
  271. response);
  272. }
  273. //~--- get methods ----------------------------------------------------------
  274. /**
  275. * Returns {@code true} if anonymous access is enabled.
  276. *
  277. *
  278. * @return {@code true} if anonymous access is enabled
  279. */
  280. private boolean isAnonymousAccessEnabled()
  281. {
  282. return (configuration != null) && configuration.isAnonymousAccessEnabled();
  283. }
  284. //~--- fields ---------------------------------------------------------------
  285. /** set of web token generators */
  286. private final Set<WebTokenGenerator> tokenGenerators;
  287. /** scm main configuration */
  288. protected ScmConfiguration configuration;
  289. }