From be385e4f2eee380a464c266f52024a5638cb37c9 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 21 Feb 2015 21:06:35 +0100 Subject: [PATCH] implement a new authentication filter, which uses a set of WebTokenGenerator to handle authentication requests --- scm-core/pom.xml | 9 + .../scm/web/SchemeBasedWebTokenGenerator.java | 114 ++++++++ .../java/sonia/scm/web/WebTokenGenerator.java | 63 ++++ ...nFilter.java => AuthenticationFilter.java} | 239 ++++++++------- .../SecurityHttpServletRequestWrapper.java | 58 +--- .../web/filter/AuthenticationFilterTest.java | 271 ++++++++++++++++++ .../src/test/resources/sonia/scm/shiro.ini | 6 + .../scm/web/GitBasicAuthenticationFilter.java | 12 +- .../scm/web/HgBasicAuthenticationFilter.java | 10 +- .../scm/web/SvnBasicAuthenticationFilter.java | 12 +- .../java/sonia/scm/web/SvnServletModule.java | 2 +- .../java/sonia/scm/filter/SecurityFilter.java | 28 +- .../sonia/scm/web/BasicWebTokenGenerator.java | 117 ++++++++ .../scm/web/BearerWebTokenGenerator.java | 80 ++++++ .../web/CookieBearerWebTokenGenerator.java | 89 ++++++ ...lter.java => ApiAuthenticationFilter.java} | 16 +- .../scm/web/BasicWebTokenGeneratorTest.java | 142 +++++++++ .../scm/web/BearerWebTokenGeneratorTest.java | 79 +++++ .../CookieBearerWebTokenGeneratorTest.java | 115 ++++++++ 19 files changed, 1271 insertions(+), 191 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/web/SchemeBasedWebTokenGenerator.java create mode 100644 scm-core/src/main/java/sonia/scm/web/WebTokenGenerator.java rename scm-core/src/main/java/sonia/scm/web/filter/{BasicAuthenticationFilter.java => AuthenticationFilter.java} (58%) create mode 100644 scm-core/src/test/java/sonia/scm/web/filter/AuthenticationFilterTest.java create mode 100644 scm-core/src/test/resources/sonia/scm/shiro.ini create mode 100644 scm-webapp/src/main/java/sonia/scm/web/BasicWebTokenGenerator.java create mode 100644 scm-webapp/src/main/java/sonia/scm/web/BearerWebTokenGenerator.java create mode 100644 scm-webapp/src/main/java/sonia/scm/web/CookieBearerWebTokenGenerator.java rename scm-webapp/src/main/java/sonia/scm/web/security/{ApiBasicAuthenticationFilter.java => ApiAuthenticationFilter.java} (89%) create mode 100644 scm-webapp/src/test/java/sonia/scm/web/BasicWebTokenGeneratorTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/web/BearerWebTokenGeneratorTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/web/CookieBearerWebTokenGeneratorTest.java diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 9953cc5fee..2bbb656ea0 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -113,6 +113,15 @@ 2.0.0-SNAPSHOT provided + + + + + com.github.sdorra + shiro-unit + 1.0.0 + test + diff --git a/scm-core/src/main/java/sonia/scm/web/SchemeBasedWebTokenGenerator.java b/scm-core/src/main/java/sonia/scm/web/SchemeBasedWebTokenGenerator.java new file mode 100644 index 0000000000..d503745d86 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/SchemeBasedWebTokenGenerator.java @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Strings; + +import org.apache.shiro.authc.AuthenticationToken; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.http.HttpServletRequest; + +/** + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +public abstract class SchemeBasedWebTokenGenerator implements WebTokenGenerator +{ + + /** authorization header */ + private static final String HEADER_AUTHORIZATION = "Authorization"; + + /** + * the logger for SchemeBasedWebTokenGenerator + */ + private static final Logger logger = + LoggerFactory.getLogger(SchemeBasedWebTokenGenerator.class); + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * @param scheme + * @param authorization + * + * @return + */ + protected abstract AuthenticationToken createToken( + HttpServletRequest request, String scheme, String authorization); + + /** + * Method description + * + * + * @param request + * + * @return + */ + @Override + public AuthenticationToken createToken(HttpServletRequest request) + { + AuthenticationToken token = null; + String authorization = request.getHeader(HEADER_AUTHORIZATION); + + if (!Strings.isNullOrEmpty(authorization)) + { + String[] parts = authorization.split("\\s+"); + + if (parts.length > 0) + { + token = createToken(request, parts[0], parts[1]); + + if (token == null) + { + logger.warn("could not create token from authentication header"); + } + } + else + { + logger.warn("found malformed authentication header"); + } + } + + return token; + } +} diff --git a/scm-core/src/main/java/sonia/scm/web/WebTokenGenerator.java b/scm-core/src/main/java/sonia/scm/web/WebTokenGenerator.java new file mode 100644 index 0000000000..64adfce036 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/WebTokenGenerator.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.apache.shiro.authc.AuthenticationToken; + +import sonia.scm.plugin.ExtensionPoint; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.http.HttpServletRequest; + +/** + * Creates an {@link AuthenticationToken} from a {@link HttpServletRequest}. + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +@ExtensionPoint +public interface WebTokenGenerator +{ + + /** + * Returns an {@link AuthenticationToken} or {@code null}. + * + * + * @param request http servlet request + * + * @return {@link AuthenticationToken} or {@code null} + */ + public AuthenticationToken createToken(HttpServletRequest request); +} diff --git a/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java similarity index 58% rename from scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java rename to scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java index 00494090fb..ec95d665a4 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java @@ -40,7 +40,7 @@ import com.google.inject.Singleton; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; @@ -48,44 +48,41 @@ import org.slf4j.LoggerFactory; import sonia.scm.SCMContext; import sonia.scm.config.ScmConfiguration; -import sonia.scm.user.User; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; +import sonia.scm.web.WebTokenGenerator; //~--- JDK imports ------------------------------------------------------------ -import com.sun.jersey.core.util.Base64; - import java.io.IOException; +import java.util.Set; + import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** + * Handles authentication, if a one of the {@link WebTokenGenerator} returns + * an {@link AuthenticationToken}. * * @author Sebastian Sdorra + * @since 2.0.0 */ @Singleton -public class BasicAuthenticationFilter extends HttpFilter +public class AuthenticationFilter extends HttpFilter { - /** Field description */ - public static final String AUTHORIZATION_BASIC_PREFIX = "BASIC"; - - /** Field description */ - public static final String CREDENTIAL_SEPARATOR = ":"; - - /** Field description */ - public static final String HEADER_AUTHORIZATION = "Authorization"; - /** marker for failed authentication */ private static final String ATTRIBUTE_FAILED_AUTH = "sonia.scm.auth.failed"; - /** the logger for BasicAuthenticationFilter */ + /** Field description */ + private static final String HEADER_AUTHORIZATION = "Authorization"; + + /** the logger for AuthenticationFilter */ private static final Logger logger = - LoggerFactory.getLogger(BasicAuthenticationFilter.class); + LoggerFactory.getLogger(AuthenticationFilter.class); //~--- constructors --------------------------------------------------------- @@ -93,24 +90,26 @@ public class BasicAuthenticationFilter extends HttpFilter * Constructs a new basic authenticaton filter. * * @param configuration scm-manager global configuration - * - * @since 1.21 + * @param tokenGenerators web token generators */ @Inject - public BasicAuthenticationFilter(ScmConfiguration configuration) + public AuthenticationFilter(ScmConfiguration configuration, + Set tokenGenerators) { this.configuration = configuration; + this.tokenGenerators = tokenGenerators; } //~--- methods -------------------------------------------------------------- /** - * Method description + * Handles authentication, if a one of the {@link WebTokenGenerator} returns + * an {@link AuthenticationToken}. * * - * @param request - * @param response - * @param chain + * @param request servlet request + * @param response servlet response + * @param chain filter chain * * @throws IOException * @throws ServletException @@ -121,54 +120,29 @@ public class BasicAuthenticationFilter extends HttpFilter throws IOException, ServletException { Subject subject = SecurityUtils.getSubject(); - User user = null; - String authentication = request.getHeader(HEADER_AUTHORIZATION); + AuthenticationToken token = createToken(request); - if (Util.startWithIgnoreCase(authentication, AUTHORIZATION_BASIC_PREFIX)) + if (token != null) { - logger.trace("found basic authorization header, start authentication"); - - user = authenticate(request, response, subject, authentication); - - if (logger.isTraceEnabled()) - { - if (user != null) - { - logger.trace("user {} successfully authenticated", user.getName()); - } - else - { - logger.trace("authentcation failed, user object is null"); - } - } + logger.trace( + "found authentication token on request, start authentication"); + handleAuthentication(request, response, chain, subject, token); } else if (subject.isAuthenticated()) { logger.trace("user is allready authenticated"); - user = subject.getPrincipals().oneByType(User.class); + processChain(request, response, chain, subject); } - else if ((configuration != null) - && configuration.isAnonymousAccessEnabled()) + else if (isAnonymousAccessEnabled()) { - if (logger.isTraceEnabled()) - { - logger.trace("anonymous access granted"); - } - - user = SCMContext.ANONYMOUS; - } - - if (user == null) - { - logger.trace("could not find user send unauthorized"); - - handleUnauthorized(request, response, chain); + logger.trace("anonymous access granted"); + processChain(request, response, chain, subject); } else { - chain.doFilter(new SecurityHttpServletRequestWrapper(request, user), - response); + logger.trace("could not find user send unauthorized"); + handleUnauthorized(request, response, chain); } } @@ -237,75 +211,122 @@ public class BasicAuthenticationFilter extends HttpFilter } /** - * Method description + * Iterates all {@link WebTokenGenerator} and creates an + * {@link AuthenticationToken} from the given request. * * - * @param request - * @param response - * @param securityContext - * @param subject - * @param authentication + * @param request http servlet request * - * @return + * @return authentication token of {@code null} */ - private User authenticate(HttpServletRequest request, - HttpServletResponse response, Subject subject, String authentication) + private AuthenticationToken createToken(HttpServletRequest request) { - String token = authentication.substring(6); + AuthenticationToken token = null; - token = new String(Base64.decode(token.getBytes())); - - int index = token.indexOf(CREDENTIAL_SEPARATOR); - User user = null; - - if ((index > 0) && (index < token.length())) + for (WebTokenGenerator generator : tokenGenerators) { - String username = token.substring(0, index); - String password = token.substring(index + 1); + token = generator.createToken(request); - if (Util.isNotEmpty(username) && Util.isNotEmpty(password)) + if (token != null) { - logger.trace("try to authenticate user {}", username); + logger.trace("generated web token {} from generator {}", + token.getClass(), generator.getClass()); - try - { - - subject.login(new UsernamePasswordToken(username, password, - request.getRemoteAddr())); - user = subject.getPrincipals().oneByType(User.class); - } - catch (AuthenticationException ex) - { - - // add a marker to the request that the authentication has failed - request.setAttribute(ATTRIBUTE_FAILED_AUTH, Boolean.TRUE); - - if (logger.isTraceEnabled()) - { - logger.trace("authentication failed for user ".concat(username), - ex); - } - else if (logger.isWarnEnabled()) - { - logger.warn("authentication failed for user {}", username); - } - } - } - else if (logger.isWarnEnabled()) - { - logger.warn("username or password is null/empty"); + break; } } - else if (logger.isWarnEnabled()) + + return token; + } + + /** + * Handle authentication with the given {@link AuthenticationToken}. + * + * + * @param request http servlet request + * @param response http servlet response + * @param chain filter chain + * @param subject subject + * @param token authentication token + * + * @throws IOException + * @throws ServletException + */ + private void handleAuthentication(HttpServletRequest request, + HttpServletResponse response, FilterChain chain, Subject subject, + AuthenticationToken token) + throws IOException, ServletException + { + logger.trace("found basic authorization header, start authentication"); + + try { - logger.warn("failed to read basic auth credentials"); + subject.login(token); + processChain(request, response, chain, subject); + } + catch (AuthenticationException ex) + { + logger.warn("authentication failed", ex); + handleUnauthorized(request, response, chain); + } + } + + /** + * Process the filter chain. + * + * + * @param request http servlet request + * @param response http servlet response + * @param chain filter chain + * @param subject subject + * + * @throws IOException + * @throws ServletException + */ + private void processChain(HttpServletRequest request, + HttpServletResponse response, FilterChain chain, Subject subject) + throws IOException, ServletException + { + String username = Util.EMPTY_STRING; + + if (!subject.isAuthenticated()) + { + + // anonymous access + username = SCMContext.USER_ANONYMOUS; + } + else + { + Object obj = subject.getPrincipal(); + + if (obj != null) + { + username = obj.toString(); + } } - return user; + chain.doFilter(new SecurityHttpServletRequestWrapper(request, username), + response); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Returns {@code true} if anonymous access is enabled. + * + * + * @return {@code true} if anonymous access is enabled + */ + private boolean isAnonymousAccessEnabled() + { + return (configuration != null) && configuration.isAnonymousAccessEnabled(); } //~--- fields --------------------------------------------------------------- + /** set of web token generators */ + private final Set tokenGenerators; + /** scm main configuration */ protected ScmConfiguration configuration; } diff --git a/scm-core/src/main/java/sonia/scm/web/filter/SecurityHttpServletRequestWrapper.java b/scm-core/src/main/java/sonia/scm/web/filter/SecurityHttpServletRequestWrapper.java index 5739f00e36..2fdbdd546a 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/SecurityHttpServletRequestWrapper.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/SecurityHttpServletRequestWrapper.java @@ -33,14 +33,8 @@ package sonia.scm.web.filter; -//~--- non-JDK imports -------------------------------------------------------- - -import sonia.scm.user.User; - //~--- JDK imports ------------------------------------------------------------ -import java.security.Principal; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; @@ -56,68 +50,28 @@ public class SecurityHttpServletRequestWrapper extends HttpServletRequestWrapper * * * @param request - * @param user + * @param principal */ public SecurityHttpServletRequestWrapper(HttpServletRequest request, - User user) + String principal) { super(request); - this.user = user; + this.principal = principal; } //~--- get methods ---------------------------------------------------------- /** - * Method description - * - * - * @return - */ - @Override - public String getAuthType() - { - return (user != null) - ? BASIC_AUTH - : null; - } - - /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public String getRemoteUser() { - return user.getName(); - } - - /** - * Method description - * - * - * @return - */ - public User getUser() - { - return user; - } - - /** - * Method description - * - * - * @return - */ - @Override - public Principal getUserPrincipal() - { - return user; + return principal; } //~--- fields --------------------------------------------------------------- /** Field description */ - private User user; + private final String principal; } diff --git a/scm-core/src/test/java/sonia/scm/web/filter/AuthenticationFilterTest.java b/scm-core/src/test/java/sonia/scm/web/filter/AuthenticationFilterTest.java new file mode 100644 index 0000000000..052f6e1417 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/web/filter/AuthenticationFilterTest.java @@ -0,0 +1,271 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web.filter; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; + +import com.google.common.collect.ImmutableSet; + +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.UsernamePasswordToken; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import sonia.scm.config.ScmConfiguration; +import sonia.scm.web.WebTokenGenerator; + +import static org.mockito.Mockito.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +@SubjectAware(configuration = "classpath:sonia/scm/shiro.ini") +public class AuthenticationFilterTest +{ + + /** + * Method description + * + * + * @throws IOException + * @throws ServletException + */ + @Test + @SubjectAware(username = "trillian", password = "secret") + public void testDoFilterAuthenticated() throws IOException, ServletException + { + AuthenticationFilter filter = createAuthenticationFilter(); + + filter.doFilter(request, response, chain); + verify(chain).doFilter(any(HttpServletRequest.class), + any(HttpServletResponse.class)); + } + + /** + * Method description + * + * + * @throws IOException + * @throws ServletException + */ + @Test + public void testDoFilterUnauthorized() throws IOException, ServletException + { + AuthenticationFilter filter = createAuthenticationFilter(); + + filter.doFilter(request, response, chain); + verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED, + "Authorization Required"); + } + + /** + * Method description + * + * + * @throws IOException + * @throws ServletException + */ + @Test + public void testDoFilterWithAnonymousAccess() + throws IOException, ServletException + { + configuration.setAnonymousAccessEnabled(true); + + AuthenticationFilter filter = createAuthenticationFilter(); + + filter.doFilter(request, response, chain); + verify(chain).doFilter(any(HttpServletRequest.class), + any(HttpServletResponse.class)); + } + + /** + * Method description + * + * + * @throws IOException + * @throws ServletException + */ + @Test + public void testDoFilterWithAuthenticationFailed() + throws IOException, ServletException + { + AuthenticationFilter filter = + createAuthenticationFilter(new DemoWebTokenGenerator("trillian", "sec")); + + filter.doFilter(request, response, chain); + verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED, + "Authorization Required"); + } + + /** + * Method description + * + * + * @throws IOException + * @throws ServletException + */ + @Test + public void testDoFilterWithAuthenticationSuccess() + throws IOException, ServletException + { + AuthenticationFilter filter = + createAuthenticationFilter(new DemoWebTokenGenerator("trillian", + "secret")); + + filter.doFilter(request, response, chain); + verify(chain).doFilter(any(HttpServletRequest.class), + any(HttpServletResponse.class)); + } + + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + */ + @Before + public void setUp() + { + configuration = new ScmConfiguration(); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param generators + * + * @return + */ + private AuthenticationFilter createAuthenticationFilter( + WebTokenGenerator... generators) + { + return new AuthenticationFilter(configuration, + ImmutableSet.copyOf(generators)); + } + + //~--- inner classes -------------------------------------------------------- + + /** + * Class description + * + * + * @version Enter version here..., 15/02/21 + * @author Enter your name here... + */ + private static class DemoWebTokenGenerator implements WebTokenGenerator + { + + /** + * Constructs ... + * + * + * @param username + * @param password + */ + public DemoWebTokenGenerator(String username, String password) + { + this.username = username; + this.password = password; + } + + //~--- methods ------------------------------------------------------------ + + /** + * Method description + * + * + * @param request + * + * @return + */ + @Override + public AuthenticationToken createToken(HttpServletRequest request) + { + return new UsernamePasswordToken(username, password); + } + + //~--- fields ------------------------------------------------------------- + + /** Field description */ + private final String password; + + /** Field description */ + private final String username; + } + + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @Rule + public ShiroRule shiro = new ShiroRule(); + + /** Field description */ + @Mock + private FilterChain chain; + + /** Field description */ + private ScmConfiguration configuration; + + /** Field description */ + @Mock + private HttpServletRequest request; + + /** Field description */ + @Mock + private HttpServletResponse response; +} diff --git a/scm-core/src/test/resources/sonia/scm/shiro.ini b/scm-core/src/test/resources/sonia/scm/shiro.ini new file mode 100644 index 0000000000..e87c81b097 --- /dev/null +++ b/scm-core/src/test/resources/sonia/scm/shiro.ini @@ -0,0 +1,6 @@ +[users] +trillian = secret, user + +[roles] +admin = * +user = something:* \ No newline at end of file diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitBasicAuthenticationFilter.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitBasicAuthenticationFilter.java index bce9c0eefb..1bc6ce00e1 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitBasicAuthenticationFilter.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitBasicAuthenticationFilter.java @@ -43,12 +43,14 @@ import sonia.scm.config.ScmConfiguration; import sonia.scm.filter.Filters; import sonia.scm.filter.WebElement; import sonia.scm.repository.GitUtil; -import sonia.scm.web.filter.BasicAuthenticationFilter; +import sonia.scm.web.filter.AuthenticationFilter; //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; +import java.util.Set; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -58,7 +60,7 @@ import javax.servlet.http.HttpServletResponse; */ @Priority(Filters.PRIORITY_AUTHENTICATION) @WebElement(value = GitServletModule.PATTERN_GIT) -public class GitBasicAuthenticationFilter extends BasicAuthenticationFilter +public class GitBasicAuthenticationFilter extends AuthenticationFilter { /** @@ -66,11 +68,13 @@ public class GitBasicAuthenticationFilter extends BasicAuthenticationFilter * * * @param configuration + * @param webTokenGenerators */ @Inject - public GitBasicAuthenticationFilter(ScmConfiguration configuration) + public GitBasicAuthenticationFilter(ScmConfiguration configuration, + Set webTokenGenerators) { - super(configuration); + super(configuration, webTokenGenerators); } //~--- methods -------------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgBasicAuthenticationFilter.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgBasicAuthenticationFilter.java index 3aff48f932..abc295fd68 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgBasicAuthenticationFilter.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgBasicAuthenticationFilter.java @@ -39,7 +39,7 @@ import sonia.scm.Priority; import sonia.scm.config.ScmConfiguration; import sonia.scm.filter.Filters; import sonia.scm.filter.WebElement; -import sonia.scm.web.filter.BasicAuthenticationFilter; +import sonia.scm.web.filter.AuthenticationFilter; //~--- JDK imports ------------------------------------------------------------ @@ -56,7 +56,7 @@ import javax.servlet.http.HttpServletResponse; */ @Priority(Filters.PRIORITY_AUTHENTICATION) @WebElement(value = HgServletModule.MAPPING_HG) -public class HgBasicAuthenticationFilter extends BasicAuthenticationFilter +public class HgBasicAuthenticationFilter extends AuthenticationFilter { /** @@ -64,11 +64,13 @@ public class HgBasicAuthenticationFilter extends BasicAuthenticationFilter * * * @param configuration + * @param webTokenGenerators */ @Inject - public HgBasicAuthenticationFilter(ScmConfiguration configuration) + public HgBasicAuthenticationFilter(ScmConfiguration configuration, + Set webTokenGenerators) { - super(configuration); + super(configuration, webTokenGenerators); } //~--- methods -------------------------------------------------------------- diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnBasicAuthenticationFilter.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnBasicAuthenticationFilter.java index 16ab4909e4..8ed93f05ae 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnBasicAuthenticationFilter.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnBasicAuthenticationFilter.java @@ -41,12 +41,14 @@ import sonia.scm.filter.Filters; import sonia.scm.filter.WebElement; import sonia.scm.repository.SvnUtil; import sonia.scm.util.HttpUtil; -import sonia.scm.web.filter.BasicAuthenticationFilter; +import sonia.scm.web.filter.AuthenticationFilter; //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; +import java.util.Set; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -56,7 +58,7 @@ import javax.servlet.http.HttpServletResponse; */ @Priority(Filters.PRIORITY_AUTHENTICATION) @WebElement(value = SvnServletModule.PATTERN_SVN) -public class SvnBasicAuthenticationFilter extends BasicAuthenticationFilter +public class SvnBasicAuthenticationFilter extends AuthenticationFilter { /** @@ -64,11 +66,13 @@ public class SvnBasicAuthenticationFilter extends BasicAuthenticationFilter * * * @param configuration + * @param webTokenGenerators */ @Inject - public SvnBasicAuthenticationFilter(ScmConfiguration configuration) + public SvnBasicAuthenticationFilter(ScmConfiguration configuration, + Set webTokenGenerators) { - super(configuration); + super(configuration, webTokenGenerators); } //~--- methods -------------------------------------------------------------- diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnServletModule.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnServletModule.java index dbf37b1d03..813f7fb0e6 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnServletModule.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnServletModule.java @@ -38,7 +38,7 @@ package sonia.scm.web; import com.google.inject.servlet.ServletModule; import sonia.scm.plugin.Extension; -import sonia.scm.web.filter.BasicAuthenticationFilter; +import sonia.scm.web.filter.AuthenticationFilter; //~--- JDK imports ------------------------------------------------------------ diff --git a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java index a2e80d8beb..be031f5ed1 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java @@ -41,6 +41,7 @@ import com.google.inject.Singleton; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; +import sonia.scm.Priority; import sonia.scm.SCMContext; import sonia.scm.config.ScmConfiguration; import sonia.scm.user.User; @@ -55,14 +56,14 @@ import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import sonia.scm.Priority; /** * * @author Sebastian Sdorra */ @Priority(Filters.PRIORITY_AUTHORIZATION) -@WebElement(value = Filters.PATTERN_RESTAPI, morePatterns = {Filters.PATTERN_DEBUG}) +@WebElement(value = Filters.PATTERN_RESTAPI, + morePatterns = { Filters.PATTERN_DEBUG }) public class SecurityFilter extends HttpFilter { @@ -111,7 +112,7 @@ public class SecurityFilter extends HttpFilter if (hasPermission(subject)) { chain.doFilter(new SecurityHttpServletRequestWrapper(request, - getUser(subject)), response); + getUsername(subject)), response); } else if (subject.isAuthenticated() || subject.isRemembered()) { @@ -144,7 +145,9 @@ public class SecurityFilter extends HttpFilter */ protected boolean hasPermission(Subject subject) { - return ((configuration != null) && configuration.isAnonymousAccessEnabled()) || subject.isAuthenticated() || subject.isRemembered(); + return ((configuration != null) + && configuration.isAnonymousAccessEnabled()) || subject.isAuthenticated() + || subject.isRemembered(); } /** @@ -155,20 +158,21 @@ public class SecurityFilter extends HttpFilter * * @return */ - private User getUser(Subject subject) + private String getUsername(Subject subject) { - User user = null; + String username = SCMContext.USER_ANONYMOUS; if (subject.isAuthenticated() || subject.isRemembered()) { - user = subject.getPrincipals().oneByType(User.class); - } - else - { - user = SCMContext.ANONYMOUS; + Object obj = subject.getPrincipal(); + + if (obj != null) + { + username = obj.toString(); + } } - return user; + return username; } //~--- fields --------------------------------------------------------------- diff --git a/scm-webapp/src/main/java/sonia/scm/web/BasicWebTokenGenerator.java b/scm-webapp/src/main/java/sonia/scm/web/BasicWebTokenGenerator.java new file mode 100644 index 0000000000..04bf59bd1b --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/web/BasicWebTokenGenerator.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.codec.Base64; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.plugin.Extension; +import sonia.scm.util.Util; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.http.HttpServletRequest; + +/** + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +@Extension +public class BasicWebTokenGenerator extends SchemeBasedWebTokenGenerator +{ + + /** Field description */ + public static final String AUTHORIZATION_BASIC_PREFIX = "basic"; + + /** Field description */ + public static final String CREDENTIAL_SEPARATOR = ":"; + + /** + * the logger for BasicWebTokenGenerator + */ + private static final Logger logger = + LoggerFactory.getLogger(BasicWebTokenGenerator.class); + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * @param scheme + * @param authorization + * + * @return + */ + @Override + protected UsernamePasswordToken createToken(HttpServletRequest request, + String scheme, String authorization) + { + UsernamePasswordToken authToken = null; + + if (AUTHORIZATION_BASIC_PREFIX.equalsIgnoreCase(scheme)) + { + String token = new String(Base64.decode(authorization.getBytes())); + + int index = token.indexOf(CREDENTIAL_SEPARATOR); + + if ((index > 0) && (index < token.length())) + { + String username = token.substring(0, index); + String password = token.substring(index + 1); + + if (Util.isNotEmpty(username) && Util.isNotEmpty(password)) + { + logger.trace("try to authenticate user {}", username); + authToken = new UsernamePasswordToken(username, password); + } + else if (logger.isWarnEnabled()) + { + logger.warn("username or password is null/empty"); + } + } + else if (logger.isWarnEnabled()) + { + logger.warn("failed to read basic auth credentials"); + } + } + + return authToken; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/web/BearerWebTokenGenerator.java b/scm-webapp/src/main/java/sonia/scm/web/BearerWebTokenGenerator.java new file mode 100644 index 0000000000..14c9408b2e --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/web/BearerWebTokenGenerator.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import sonia.scm.security.BearerAuthenticationToken; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.http.HttpServletRequest; +import sonia.scm.plugin.Extension; + +/** + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +@Extension +public class BearerWebTokenGenerator extends SchemeBasedWebTokenGenerator +{ + + /** Field description */ + public static final String AUTHORIZATION_BEARER_PREFIX = "BEARER"; + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * @param scheme + * @param authorization + * + * @return + */ + @Override + protected BearerAuthenticationToken createToken(HttpServletRequest request, + String scheme, String authorization) + { + BearerAuthenticationToken token = null; + + if (AUTHORIZATION_BEARER_PREFIX.equalsIgnoreCase(scheme)) + { + token = new BearerAuthenticationToken(authorization); + } + + return token; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/web/CookieBearerWebTokenGenerator.java b/scm-webapp/src/main/java/sonia/scm/web/CookieBearerWebTokenGenerator.java new file mode 100644 index 0000000000..47dcf78dc7 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/web/CookieBearerWebTokenGenerator.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.annotations.VisibleForTesting; +import sonia.scm.plugin.Extension; +import sonia.scm.security.BearerAuthenticationToken; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +/** + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +@Extension +public class CookieBearerWebTokenGenerator implements WebTokenGenerator +{ + + /** Field description */ + @VisibleForTesting + static final String COOKIE_NAME = "X-Bearer-Token"; + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * + * @return + */ + @Override + public BearerAuthenticationToken createToken(HttpServletRequest request) + { + BearerAuthenticationToken token = null; + Cookie[] cookies = request.getCookies(); + + if (cookies != null) + { + for (Cookie cookie : cookies) + { + if (COOKIE_NAME.equals(cookie.getName())) + { + token = new BearerAuthenticationToken(cookie.getValue()); + + break; + } + } + } + + return token; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/ApiBasicAuthenticationFilter.java b/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java similarity index 89% rename from scm-webapp/src/main/java/sonia/scm/web/security/ApiBasicAuthenticationFilter.java rename to scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java index e6fb91a3e3..00f0de8e36 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/ApiBasicAuthenticationFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java @@ -41,12 +41,15 @@ import sonia.scm.Priority; import sonia.scm.config.ScmConfiguration; import sonia.scm.filter.Filters; import sonia.scm.filter.WebElement; -import sonia.scm.web.filter.BasicAuthenticationFilter; +import sonia.scm.web.filter.AuthenticationFilter; +import sonia.scm.web.WebTokenGenerator; //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; +import java.util.Set; + import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -57,8 +60,9 @@ import javax.servlet.http.HttpServletResponse; * @author Sebastian Sdorra */ @Priority(Filters.PRIORITY_AUTHENTICATION) -@WebElement(value = Filters.PATTERN_RESTAPI, morePatterns = { Filters.PATTERN_DEBUG }) -public class ApiBasicAuthenticationFilter extends BasicAuthenticationFilter +@WebElement(value = Filters.PATTERN_RESTAPI, + morePatterns = { Filters.PATTERN_DEBUG }) +public class ApiAuthenticationFilter extends AuthenticationFilter { /** Field description */ @@ -77,11 +81,13 @@ public class ApiBasicAuthenticationFilter extends BasicAuthenticationFilter * * * @param configuration + * @param tokenGenerators */ @Inject - public ApiBasicAuthenticationFilter(ScmConfiguration configuration) + public ApiAuthenticationFilter(ScmConfiguration configuration, + Set tokenGenerators) { - super(configuration); + super(configuration, tokenGenerators); } //~--- methods -------------------------------------------------------------- diff --git a/scm-webapp/src/test/java/sonia/scm/web/BasicWebTokenGeneratorTest.java b/scm-webapp/src/test/java/sonia/scm/web/BasicWebTokenGeneratorTest.java new file mode 100644 index 0000000000..ff6ba8a59a --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/web/BasicWebTokenGeneratorTest.java @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.codec.Base64; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.Matchers.*; + +import static org.junit.Assert.*; + +import static org.mockito.Mockito.*; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.http.HttpServletRequest; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class BasicWebTokenGeneratorTest +{ + + /** + * Method description + * + */ + @Test + public void testCreateToken() + { + String trillian = Base64.encodeToString("trillian:secret".getBytes()); + + when(request.getHeader("Authorization")).thenReturn( + "Basic ".concat(trillian)); + + AuthenticationToken token = generator.createToken(request); + + assertThat(token, instanceOf(UsernamePasswordToken.class)); + + UsernamePasswordToken upt = (UsernamePasswordToken) token; + + assertEquals("trillian", token.getPrincipal()); + assertArrayEquals("secret".toCharArray(), upt.getPassword()); + } + + /** + * Method description + * + */ + @Test + public void testCreateTokenWithWrongAuthorizationHeader() + { + when(request.getHeader("Authorization")).thenReturn("NONBASIC ASD"); + assertNull(generator.createToken(request)); + } + + /** + * Method description + * + */ + @Test + public void testCreateTokenWithWrongBasicAuthorizationHeader() + { + when(request.getHeader("Authorization")).thenReturn("Basic ASD"); + assertNull(generator.createToken(request)); + } + + /** + * Method description + * + */ + @Test + public void testCreateTokenWithoutAuthorizationHeader() + { + assertNull(generator.createToken(request)); + } + + /** + * Method description + * + */ + @Test + public void testCreateTokenWithoutPassword() + { + String trillian = Base64.encodeToString("trillian:".getBytes()); + + when(request.getHeader("Authorization")).thenReturn( + "Basic ".concat(trillian)); + assertNull(generator.createToken(request)); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final BasicWebTokenGenerator generator = new BasicWebTokenGenerator(); + + /** Field description */ + @Mock + private HttpServletRequest request; +} diff --git a/scm-webapp/src/test/java/sonia/scm/web/BearerWebTokenGeneratorTest.java b/scm-webapp/src/test/java/sonia/scm/web/BearerWebTokenGeneratorTest.java new file mode 100644 index 0000000000..9a39175fb1 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/web/BearerWebTokenGeneratorTest.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.web; + +import javax.servlet.http.HttpServletRequest; +import org.apache.shiro.authc.AuthenticationToken; +import org.junit.Test; +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.security.BearerAuthenticationToken; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class BearerWebTokenGeneratorTest { + + @Mock + private HttpServletRequest request; + + private final BearerWebTokenGenerator tokenGenerator = new BearerWebTokenGenerator(); + + @Test + public void testCreateTokenWithWrongScheme() + { + when(request.getHeader("Authorization")).thenReturn("BASIC ASD"); + assertNull(tokenGenerator.createToken(request)); + } + + @Test + public void testCreateTokenWithoutAuthorizationHeader(){ + assertNull(tokenGenerator.createToken(request)); + } + + @Test + public void testCreateToken(){ + when(request.getHeader("Authorization")).thenReturn("Bearer asd"); + AuthenticationToken token = tokenGenerator.createToken(request); + assertNotNull(token); + assertThat(token, instanceOf(BearerAuthenticationToken.class)); + BearerAuthenticationToken bt = (BearerAuthenticationToken) token; + assertThat(bt.getCredentials(), equalTo("asd")); + } + +} \ No newline at end of file diff --git a/scm-webapp/src/test/java/sonia/scm/web/CookieBearerWebTokenGeneratorTest.java b/scm-webapp/src/test/java/sonia/scm/web/CookieBearerWebTokenGeneratorTest.java new file mode 100644 index 0000000000..c9a1400570 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/web/CookieBearerWebTokenGeneratorTest.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import sonia.scm.security.BearerAuthenticationToken; + +import static org.junit.Assert.*; + +import static org.mockito.Mockito.*; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class CookieBearerWebTokenGeneratorTest +{ + + /** + * Method description + * + */ + @Test + public void testCreateToken() + { + Cookie c = mock(Cookie.class); + + when(c.getName()).thenReturn(CookieBearerWebTokenGenerator.COOKIE_NAME); + when(c.getValue()).thenReturn("value"); + when(request.getCookies()).thenReturn(new Cookie[] { c }); + + BearerAuthenticationToken token = tokenGenerator.createToken(request); + + assertNotNull(token); + assertEquals("value", token.getCredentials()); + } + + /** + * Method description + * + */ + @Test + public void testCreateTokenWithWrongCookie() + { + Cookie c = mock(Cookie.class); + + when(c.getName()).thenReturn("other-cookie"); + when(request.getCookies()).thenReturn(new Cookie[] { c }); + assertNull(tokenGenerator.createToken(request)); + } + + /** + * Method description + * + */ + @Test + public void testCreateTokenWithoutCookies() + { + assertNull(tokenGenerator.createToken(request)); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final CookieBearerWebTokenGenerator tokenGenerator = + new CookieBearerWebTokenGenerator(); + + /** Field description */ + @Mock + private HttpServletRequest request; +}