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;
+}