mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-03 20:15:52 +01:00
create a more flexible interface for the creation of access tokens
Provide a AccessTokenBuilderFactory to simplify the creation of access tokens and a default implementation which is based on JWT. Added also an AccessTokenCookieIssuer to unify the creation of access token cookies. Removed old BearerTokenGenerator.
This commit is contained in:
107
scm-core/src/main/java/sonia/scm/security/AccessToken.java
Normal file
107
scm-core/src/main/java/sonia/scm/security/AccessToken.java
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 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.security;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* An access token can be used to access scm-manager without providing username and password. An {@link AccessToken} can
|
||||
* be issued from a restful webservice endpoint by providing credentials. After the token was issued, the token must be
|
||||
* send along with every request. The token should be send in its compact representation as bearer authorization header
|
||||
* or as cookie.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface AccessToken {
|
||||
|
||||
/**
|
||||
* Returns unique id of the access token.
|
||||
*
|
||||
* @return unique id
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Returns name of subject which identifies the principal.
|
||||
*
|
||||
* @return name of subject
|
||||
*/
|
||||
String getSubject();
|
||||
|
||||
/**
|
||||
* Returns optional issuer. The issuer identifies the principal that issued the token.
|
||||
*
|
||||
* @return optional issuer
|
||||
*/
|
||||
Optional<String> getIssuer();
|
||||
|
||||
/**
|
||||
* Returns time at which the token was issued.
|
||||
*
|
||||
* @return time at which the token was issued
|
||||
*/
|
||||
Date getIssuedAt();
|
||||
|
||||
/**
|
||||
* Returns the expiration time of token.
|
||||
*
|
||||
* @return expiration time
|
||||
*/
|
||||
Date getExpiration();
|
||||
|
||||
/**
|
||||
* Returns the scope of the token. The scope is able to reduce the permissions of the subject in the context of this
|
||||
* token. For example we could issue a token which can only be used to read a single repository. for more informations
|
||||
* please have a look at {@link Scope}.
|
||||
*
|
||||
* @return scope of token.
|
||||
*/
|
||||
Scope getScope();
|
||||
|
||||
/**
|
||||
* Returns an optional value of a custom token field.
|
||||
*
|
||||
* @param <T> type of field
|
||||
* @param key key of token field
|
||||
*
|
||||
* @return optional value of custom field
|
||||
*/
|
||||
<T> Optional<T> getCustom(String key);
|
||||
|
||||
/**
|
||||
* Returns compact representation of token.
|
||||
*
|
||||
* @return compact representation
|
||||
*/
|
||||
String compact();
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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.security;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* The access token builder is able to create {@link AccessToken}. For more informations about access tokens have look
|
||||
* at {@link AccessToken}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface AccessTokenBuilder {
|
||||
|
||||
/**
|
||||
* Sets the subject for the token.
|
||||
* If the subject is not set the currently authenticated subject will be used instead.
|
||||
*
|
||||
* @param subject subject of token
|
||||
*
|
||||
* @return * @return {@code this}
|
||||
*/
|
||||
AccessTokenBuilder subject(String subject);
|
||||
|
||||
/**
|
||||
* Adds a custom entry to the token.
|
||||
*
|
||||
* @param key key of custom entry
|
||||
* @param value value of entry
|
||||
*
|
||||
* @return {@code this}
|
||||
*/
|
||||
AccessTokenBuilder custom(String key, Object value);
|
||||
|
||||
/**
|
||||
* Sets the issuer for the token.
|
||||
*
|
||||
* @param issuer issuer name or url
|
||||
*
|
||||
* @return {@code this}
|
||||
*/
|
||||
AccessTokenBuilder issuer(String issuer);
|
||||
|
||||
/**
|
||||
* Sets the expiration for the token.
|
||||
*
|
||||
* @param count expiration count
|
||||
* @param unit expirtation unit
|
||||
*
|
||||
* @return {@code this}
|
||||
*/
|
||||
AccessTokenBuilder expiresIn(long count, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Reduces the permissions of the token by providing a scope.
|
||||
*
|
||||
* @param scope scope of token
|
||||
*
|
||||
* @return {@code this}
|
||||
*/
|
||||
AccessTokenBuilder scope(Scope scope);
|
||||
|
||||
/**
|
||||
* Creates a new {@link AccessToken} with the provided settings.
|
||||
*
|
||||
* @return new {@link AccessToken}
|
||||
*/
|
||||
AccessToken build();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.security;
|
||||
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
|
||||
/**
|
||||
* Creates new {@link AccessTokenBuilder}. The AccessTokenBuilderFactory resolves all required dependencies for the
|
||||
* access token builder. The builder factory is the main entry point for creating {@link AccessToken}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ExtensionPoint(multi = false)
|
||||
public interface AccessTokenBuilderFactory {
|
||||
|
||||
/**
|
||||
* Creates a new {@link AccessTokenBuilder}.
|
||||
*
|
||||
* @return new {@link AccessTokenBuilder}
|
||||
*/
|
||||
AccessTokenBuilder create();
|
||||
}
|
||||
@@ -86,6 +86,9 @@ public final class HttpUtil
|
||||
|
||||
/**
|
||||
* Name of bearer authentication cookie.
|
||||
*
|
||||
* TODO find a better place
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public static final String COOKIE_BEARER_AUTHENTICATION = "X-Bearer-Token";
|
||||
|
||||
@@ -57,17 +57,8 @@ import sonia.scm.ScmState;
|
||||
import sonia.scm.ScmStateFactory;
|
||||
import sonia.scm.api.rest.RestActionResult;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.security.BearerTokenGenerator;
|
||||
import sonia.scm.security.Tokens;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -84,6 +75,10 @@ import javax.ws.rs.core.Response;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import sonia.scm.security.AccessToken;
|
||||
import sonia.scm.security.AccessTokenBuilder;
|
||||
import sonia.scm.security.AccessTokenBuilderFactory;
|
||||
import sonia.scm.security.AccessTokenCookieIssuer;
|
||||
import sonia.scm.security.Scope;
|
||||
|
||||
/**
|
||||
@@ -118,15 +113,17 @@ public class AuthenticationResource
|
||||
*
|
||||
* @param configuration
|
||||
* @param stateFactory
|
||||
* @param tokenGenerator
|
||||
* @param tokenBuilderFactory
|
||||
* @param cookieIssuer
|
||||
*/
|
||||
@Inject
|
||||
public AuthenticationResource(ScmConfiguration configuration,
|
||||
ScmStateFactory stateFactory, BearerTokenGenerator tokenGenerator)
|
||||
ScmStateFactory stateFactory, AccessTokenBuilderFactory tokenBuilderFactory, AccessTokenCookieIssuer cookieIssuer)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
this.stateFactory = stateFactory;
|
||||
this.tokenGenerator = tokenGenerator;
|
||||
this.tokenBuilderFactory = tokenBuilderFactory;
|
||||
this.cookieIssuer = cookieIssuer;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
@@ -170,33 +167,20 @@ public class AuthenticationResource
|
||||
|
||||
try
|
||||
{
|
||||
subject.login(Tokens.createAuthenticationToken(request, username,
|
||||
password));
|
||||
|
||||
User user = subject.getPrincipals().oneByType(User.class);
|
||||
|
||||
String token = tokenGenerator.createBearerToken(user, scope != null ? Scope.valueOf(scope) : Scope.empty());
|
||||
subject.login(Tokens.createAuthenticationToken(request, username, password));
|
||||
AccessTokenBuilder tokenBuilder = tokenBuilderFactory.create();
|
||||
if ( scope != null ) {
|
||||
tokenBuilder.scope(Scope.valueOf(scope));
|
||||
}
|
||||
AccessToken token = tokenBuilder.build();
|
||||
|
||||
ScmState state;
|
||||
|
||||
if (cookie)
|
||||
{
|
||||
Cookie c = new Cookie(HttpUtil.COOKIE_BEARER_AUTHENTICATION, token);
|
||||
|
||||
c.setPath(request.getContextPath());
|
||||
|
||||
// TODO: should be configureable
|
||||
c.setMaxAge((int) TimeUnit.SECONDS.convert(10, TimeUnit.HOURS));
|
||||
|
||||
// set http only flag only xsrf protection is disabled,
|
||||
// because we have to extract the xsrf key with javascript in the wui
|
||||
c.setHttpOnly(!configuration.isEnabledXsrfProtection());
|
||||
response.addCookie(c);
|
||||
if (cookie) {
|
||||
cookieIssuer.authenticate(request, response, token);
|
||||
state = stateFactory.createState(subject);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = stateFactory.createState(subject, token);
|
||||
} else {
|
||||
state = stateFactory.createState(subject, token.compact());
|
||||
}
|
||||
|
||||
res = Response.ok(state).build();
|
||||
@@ -276,16 +260,8 @@ public class AuthenticationResource
|
||||
|
||||
subject.logout();
|
||||
|
||||
// remove bearer authentication cookie
|
||||
Cookie c = new Cookie(
|
||||
HttpUtil.COOKIE_BEARER_AUTHENTICATION,
|
||||
Util.EMPTY_STRING
|
||||
);
|
||||
c.setPath(request.getContextPath());
|
||||
c.setMaxAge(0);
|
||||
c.setHttpOnly(true);
|
||||
|
||||
response.addCookie(c);
|
||||
// remove authentication cookie
|
||||
cookieIssuer.invalidate(request, response);
|
||||
|
||||
Response resp;
|
||||
|
||||
@@ -481,5 +457,8 @@ public class AuthenticationResource
|
||||
private final ScmStateFactory stateFactory;
|
||||
|
||||
/** Field description */
|
||||
private final BearerTokenGenerator tokenGenerator;
|
||||
private final AccessTokenBuilderFactory tokenBuilderFactory;
|
||||
|
||||
/** Field description */
|
||||
private final AccessTokenCookieIssuer cookieIssuer;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 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.security;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import java.util.Date;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
/**
|
||||
* Generates cookies and invalidates access token cookies.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class AccessTokenCookieIssuer {
|
||||
|
||||
/**
|
||||
* the logger for AccessTokenCookieIssuer
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AccessTokenCookieIssuer.class);
|
||||
|
||||
private final ScmConfiguration configuration;
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param configuration scm main configuration
|
||||
*/
|
||||
@Inject
|
||||
public AccessTokenCookieIssuer(ScmConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cookie for token authentication and attaches it to the response.
|
||||
*
|
||||
* @param request http servlet request
|
||||
* @param response http servlet response
|
||||
* @param accessToken access token
|
||||
*/
|
||||
public void authenticate(HttpServletRequest request, HttpServletResponse response, AccessToken accessToken) {
|
||||
LOG.trace("create and attach cookie for access token {}", accessToken.getId());
|
||||
Cookie c = new Cookie(HttpUtil.COOKIE_BEARER_AUTHENTICATION, accessToken.compact());
|
||||
c.setPath(request.getContextPath());
|
||||
c.setMaxAge(getMaxAge(accessToken));
|
||||
c.setHttpOnly(isHttpOnly());
|
||||
c.setSecure(isSecure(request));
|
||||
|
||||
// attach cookie to response
|
||||
response.addCookie(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the authentication cookie.
|
||||
*
|
||||
* @param request http servlet request
|
||||
* @param response http servlet response
|
||||
*/
|
||||
public void invalidate(HttpServletRequest request, HttpServletResponse response) {
|
||||
LOG.trace("invalidates access token cookie");
|
||||
|
||||
Cookie c = new Cookie(HttpUtil.COOKIE_BEARER_AUTHENTICATION, Util.EMPTY_STRING);
|
||||
c.setPath(request.getContextPath());
|
||||
c.setMaxAge(0);
|
||||
c.setHttpOnly(isHttpOnly());
|
||||
c.setSecure(isSecure(request));
|
||||
|
||||
// attach empty cookie, that the browser can remove it
|
||||
response.addCookie(c);
|
||||
}
|
||||
|
||||
private int getMaxAge(AccessToken accessToken){
|
||||
long maxAgeMs = accessToken.getExpiration().getTime() - new Date().getTime();
|
||||
return (int) TimeUnit.MILLISECONDS.toSeconds(maxAgeMs);
|
||||
}
|
||||
|
||||
private boolean isSecure(HttpServletRequest request){
|
||||
boolean secure = request.isSecure();
|
||||
if (!secure) {
|
||||
LOG.warn("issuet a non secure cookie, protect your scm-manager instance with tls https://goo.gl/lVm0ph");
|
||||
}
|
||||
return secure;
|
||||
}
|
||||
|
||||
private boolean isHttpOnly(){
|
||||
// set http only flag only xsrf protection is disabled,
|
||||
// because we have to extract the xsrf key with javascript in the wui
|
||||
return !configuration.isEnabledXsrfProtection();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
/**
|
||||
* 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.security;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.user.User;
|
||||
|
||||
import static com.google.common.base.Preconditions.*;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Creates bearer token for a given user.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class BearerTokenGenerator
|
||||
{
|
||||
|
||||
/**
|
||||
* the logger for BearerTokenGenerator
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(BearerTokenGenerator.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs a new token generator.
|
||||
*
|
||||
*
|
||||
* @param keyGenerator key generator
|
||||
* @param keyResolver secure key resolver
|
||||
* @param enrichers token claims modifier
|
||||
*/
|
||||
@Inject
|
||||
public BearerTokenGenerator(
|
||||
KeyGenerator keyGenerator, SecureKeyResolver keyResolver, Set<TokenClaimsEnricher> enrichers
|
||||
) {
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.keyResolver = keyResolver;
|
||||
this.enrichers = enrichers;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates a new bearer token for the given user.
|
||||
*
|
||||
*
|
||||
* @param user user
|
||||
* @param scope scope of token
|
||||
*
|
||||
* @return bearer token
|
||||
*/
|
||||
public String createBearerToken(User user, Scope scope) {
|
||||
checkNotNull(user, "user is required");
|
||||
|
||||
String username = user.getName();
|
||||
|
||||
String id = keyGenerator.createKey();
|
||||
|
||||
logger.trace("create new token {} for user {}", id, username);
|
||||
|
||||
SecureKey key = keyResolver.getSecureKey(username);
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
// TODO: should be configurable
|
||||
long expiration = TimeUnit.MILLISECONDS.convert(10, TimeUnit.HOURS);
|
||||
|
||||
Map<String,Object> claims = Maps.newHashMap();
|
||||
|
||||
// add scope to claims
|
||||
Scopes.toClaims(claims, scope);
|
||||
|
||||
// enrich claims with registered enrichers
|
||||
enrichers.forEach((enricher) -> {
|
||||
enricher.enrich(claims);
|
||||
});
|
||||
|
||||
//J-
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(username)
|
||||
.setId(id)
|
||||
.signWith(SignatureAlgorithm.HS256, key.getBytes())
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(new Date(now.getTime() + expiration))
|
||||
.compact();
|
||||
//J+
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** token claims modifier **/
|
||||
private final Set<TokenClaimsEnricher> enrichers;
|
||||
|
||||
/** key generator */
|
||||
private final KeyGenerator keyGenerator;
|
||||
|
||||
/** secure key resolver */
|
||||
private final SecureKeyResolver keyResolver;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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.security;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Jwt implementation of {@link AccessToken}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class JwtAccessToken implements AccessToken {
|
||||
|
||||
private final Claims claims;
|
||||
private final String compact;
|
||||
|
||||
JwtAccessToken(Claims claims, String compact) {
|
||||
this.claims = claims;
|
||||
this.compact = compact;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return claims.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSubject() {
|
||||
return claims.getSubject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getIssuer() {
|
||||
return Optional.ofNullable(claims.getIssuer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getIssuedAt() {
|
||||
return claims.getIssuedAt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getExpiration() {
|
||||
return claims.getExpiration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope getScope() {
|
||||
return Scopes.fromClaims(claims);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Object> getCustom(String key) {
|
||||
return Optional.ofNullable(claims.get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String compact() {
|
||||
return compact;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* 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.security;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Maps;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Jwt implementation of {@link AccessTokenBuilder}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
|
||||
|
||||
/**
|
||||
* the logger for JwtAccessTokenBuilder
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JwtAccessTokenBuilder.class);
|
||||
|
||||
private final KeyGenerator keyGenerator;
|
||||
private final SecureKeyResolver keyResolver;
|
||||
private final Set<TokenClaimsEnricher> enrichers;
|
||||
|
||||
private String subject;
|
||||
private String issuer;
|
||||
private long expiresIn = 10l;
|
||||
private TimeUnit expiresInUnit = TimeUnit.MINUTES;
|
||||
private Scope scope = Scope.empty();
|
||||
|
||||
private final Map<String,Object> custom = Maps.newHashMap();
|
||||
|
||||
JwtAccessTokenBuilder(
|
||||
KeyGenerator keyGenerator, SecureKeyResolver keyResolver, Set<TokenClaimsEnricher> enrichers
|
||||
) {
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.keyResolver = keyResolver;
|
||||
this.enrichers = enrichers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtAccessTokenBuilder subject(String subject) {
|
||||
Preconditions.checkArgument(!Strings.isNullOrEmpty(subject), "null or empty value not allowed");
|
||||
this.subject = subject;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtAccessTokenBuilder custom(String key, Object value) {
|
||||
Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "null or empty value not allowed");
|
||||
Preconditions.checkArgument(value != null, "null or empty value not allowed");
|
||||
this.custom.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtAccessTokenBuilder scope(Scope scope) {
|
||||
Preconditions.checkArgument(scope != null, "scope can not be null");
|
||||
this.scope = scope;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtAccessTokenBuilder issuer(String issuer) {
|
||||
Preconditions.checkArgument(!Strings.isNullOrEmpty(issuer), "null or empty value not allowed");
|
||||
this.issuer = issuer;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtAccessTokenBuilder expiresIn(long count, TimeUnit unit) {
|
||||
Preconditions.checkArgument(count > 0, "expires in must be greater than 0");
|
||||
Preconditions.checkArgument(unit != null, "unit can not be null");
|
||||
|
||||
this.expiresIn = count;
|
||||
this.expiresInUnit = unit;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private String getSubject(){
|
||||
if (subject == null) {
|
||||
Subject currentSubject = SecurityUtils.getSubject();
|
||||
// TODO find a better way
|
||||
currentSubject.checkRole(Role.USER);
|
||||
return currentSubject.getPrincipal().toString();
|
||||
}
|
||||
return subject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtAccessToken build() {
|
||||
String id = keyGenerator.createKey();
|
||||
|
||||
String sub = getSubject();
|
||||
|
||||
LOG.trace("create new token {} for user {}", id, subject);
|
||||
SecureKey key = keyResolver.getSecureKey(sub);
|
||||
|
||||
Map<String,Object> customClaims = new HashMap<>(custom);
|
||||
|
||||
// add scope to custom claims
|
||||
Scopes.toClaims(customClaims, scope);
|
||||
|
||||
// enrich claims with registered enrichers
|
||||
enrichers.forEach((enricher) -> {
|
||||
enricher.enrich(customClaims);
|
||||
});
|
||||
|
||||
Date now = new Date();
|
||||
long expiration = expiresInUnit.toMillis(expiresIn);
|
||||
|
||||
Claims claims = Jwts.claims(customClaims)
|
||||
.setSubject(sub)
|
||||
.setId(id)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(new Date(now.getTime() + expiration));
|
||||
|
||||
if ( issuer != null ) {
|
||||
claims.setIssuer(issuer);
|
||||
}
|
||||
|
||||
// sign token and create compact version
|
||||
String compact = Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.signWith(SignatureAlgorithm.HS256, key.getBytes())
|
||||
.compact();
|
||||
|
||||
return new JwtAccessToken(claims, compact);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.security;
|
||||
|
||||
import java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import sonia.scm.plugin.Extension;
|
||||
|
||||
/**
|
||||
* Jwt implementation of {@link AccessTokenBuilderFactory}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Extension
|
||||
public final class JwtAccessTokenBuilderFactory implements AccessTokenBuilderFactory {
|
||||
|
||||
private final KeyGenerator keyGenerator;
|
||||
private final SecureKeyResolver keyResolver;
|
||||
private final Set<TokenClaimsEnricher> enrichers;
|
||||
|
||||
@Inject
|
||||
public JwtAccessTokenBuilderFactory(
|
||||
KeyGenerator keyGenerator, SecureKeyResolver keyResolver, Set<TokenClaimsEnricher> enrichers
|
||||
) {
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.keyResolver = keyResolver;
|
||||
this.enrichers = enrichers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtAccessTokenBuilder create() {
|
||||
return new JwtAccessTokenBuilder(keyGenerator, keyResolver, enrichers);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -72,7 +72,7 @@ public final class Scopes {
|
||||
public static Scope fromClaims(Map<String,Object> claims) {
|
||||
Scope scope = Scope.empty();
|
||||
if (claims.containsKey(Scopes.CLAIMS_KEY)) {
|
||||
scope = Scope.valueOf((List<String>)claims.get(Scopes.CLAIMS_KEY));
|
||||
scope = Scope.valueOf((Iterable<String>)claims.get(Scopes.CLAIMS_KEY));
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.security.BearerAuthenticationToken;
|
||||
|
||||
@@ -41,6 +40,7 @@ import sonia.scm.security.BearerAuthenticationToken;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
|
||||
/**
|
||||
* Creates an {@link BearerAuthenticationToken} from the {@link #COOKIE_NAME}
|
||||
@@ -53,12 +53,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||
public class CookieBearerWebTokenGenerator implements WebTokenGenerator
|
||||
{
|
||||
|
||||
/** cookie name */
|
||||
@VisibleForTesting
|
||||
static final String COOKIE_NAME = "X-Bearer-Token";
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates an {@link BearerAuthenticationToken} from the {@link #COOKIE_NAME}
|
||||
* cookie.
|
||||
@@ -77,7 +71,7 @@ public class CookieBearerWebTokenGenerator implements WebTokenGenerator
|
||||
{
|
||||
for (Cookie cookie : cookies)
|
||||
{
|
||||
if (COOKIE_NAME.equals(cookie.getName()))
|
||||
if (HttpUtil.COOKIE_BEARER_AUTHENTICATION.equals(cookie.getName()))
|
||||
{
|
||||
token = new BearerAuthenticationToken(cookie.getValue());
|
||||
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
/**
|
||||
* 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.security;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserTestData;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Tests {@link BearerTokenGenerator}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class BearerTokenGeneratorTest
|
||||
{
|
||||
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
|
||||
@Mock
|
||||
private KeyGenerator keyGenerator;
|
||||
|
||||
@Mock
|
||||
private SecureKeyResolver keyResolver;
|
||||
|
||||
private BearerTokenGenerator tokenGenerator;
|
||||
|
||||
/**
|
||||
* Set up mocks and object under test.
|
||||
*/
|
||||
@Before
|
||||
public void setUp() {
|
||||
Set<TokenClaimsEnricher> enrichers = Sets.newHashSet();
|
||||
enrichers.add((claims) -> {claims.put("abc", "123");});
|
||||
tokenGenerator = new BearerTokenGenerator(keyGenerator, keyResolver, enrichers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link BearerTokenGenerator#createBearerToken(User, Scope)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateBearerToken()
|
||||
{
|
||||
Claims claims = createAssertAndParseToken(UserTestData.createTrillian(), "sid", Scope.empty());
|
||||
|
||||
assertEquals("123", claims.get("abc"));
|
||||
assertNull(claims.get(Scopes.CLAIMS_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link BearerTokenGenerator#createBearerToken(User, Scope)} with scope.
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testCreateBearerTokenWithScope(){
|
||||
Claims claims = createAssertAndParseToken(UserTestData.createTrillian(), "sid", Scope.valueOf("repo:*", "user:*"));
|
||||
assertEquals("123", claims.get("abc"));
|
||||
|
||||
Scope scope = Scopes.fromClaims(claims);
|
||||
assertThat(scope, containsInAnyOrder("repo:*", "user:*"));
|
||||
}
|
||||
|
||||
private Claims createAssertAndParseToken(User user, String id, Scope scope){
|
||||
SecureKey key = createSecureKey();
|
||||
|
||||
when(keyGenerator.createKey()).thenReturn(id);
|
||||
when(keyResolver.getSecureKey(user.getName())).thenReturn(key);
|
||||
|
||||
String token = tokenGenerator.createBearerToken(user, scope);
|
||||
|
||||
assertThat(token, not(isEmptyOrNullString()));
|
||||
assertTrue(Jwts.parser().isSigned(token));
|
||||
|
||||
Claims claims = Jwts.parser().setSigningKey(key.getBytes()).parseClaimsJws(token).getBody();
|
||||
|
||||
assertEquals(user.getName(), claims.getSubject());
|
||||
assertEquals(id, claims.getId());
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
private SecureKey createSecureKey() {
|
||||
byte[] bytes = new byte[32];
|
||||
random.nextBytes(bytes);
|
||||
return new SecureKey(bytes, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* 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.security;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import com.google.common.collect.Sets;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import static org.mockito.Mockito.*;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
/**
|
||||
* Unit test for {@link JwtAccessTokenBuilder}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class JwtAccessTokenBuilderTest {
|
||||
|
||||
@Mock
|
||||
private KeyGenerator keyGenerator;
|
||||
|
||||
@Mock
|
||||
private SecureKeyResolver secureKeyResolver;
|
||||
|
||||
private Set<TokenClaimsEnricher> enrichers;
|
||||
|
||||
private JwtAccessTokenBuilder builder;
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
/**
|
||||
* Prepare mocks and set up object under test.
|
||||
*/
|
||||
@Before
|
||||
public void setUpObjectUnderTest() {
|
||||
when(keyGenerator.createKey()).thenReturn("42");
|
||||
when(secureKeyResolver.getSecureKey(anyString())).thenReturn(createSecureKey());
|
||||
enrichers = Sets.newHashSet();
|
||||
JwtAccessTokenBuilderFactory factory = new JwtAccessTokenBuilderFactory(keyGenerator, secureKeyResolver, enrichers);
|
||||
builder = factory.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link JwtAccessTokenBuilder#build()} with subject from shiro context.
|
||||
*/
|
||||
@Test
|
||||
@SubjectAware(
|
||||
configuration = "classpath:sonia/scm/shiro-001.ini",
|
||||
username = "trillian",
|
||||
password = "secret"
|
||||
)
|
||||
public void testBuildWithoutSubject() {
|
||||
JwtAccessToken token = builder.build();
|
||||
assertEquals("trillian", token.getSubject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link JwtAccessTokenBuilder#build()} with explicit subject.
|
||||
*/
|
||||
@Test
|
||||
public void testBuildWithSubject() {
|
||||
JwtAccessToken token = builder.subject("dent").build();
|
||||
assertEquals("dent", token.getSubject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link JwtAccessTokenBuilder#build()} with enricher.
|
||||
*/
|
||||
@Test
|
||||
public void testBuildWithEnricher() {
|
||||
enrichers.add((claims) -> claims.put("c", "d"));
|
||||
JwtAccessToken token = builder.subject("dent").build();
|
||||
assertEquals("d", token.getCustom("c").get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link JwtAccessTokenBuilder#build()}.
|
||||
*/
|
||||
@Test
|
||||
public void testBuild(){
|
||||
JwtAccessToken token = builder.subject("dent")
|
||||
.issuer("https://www.scm-manager.org")
|
||||
.expiresIn(5, TimeUnit.SECONDS)
|
||||
.custom("a", "b")
|
||||
.scope(Scope.valueOf("repo:*"))
|
||||
.build();
|
||||
|
||||
// assert claims
|
||||
assertClaims(token);
|
||||
|
||||
// reparse and assert again
|
||||
String compact = token.compact();
|
||||
assertThat(compact, not(isEmptyOrNullString()));
|
||||
Claims claims = Jwts.parser()
|
||||
.setSigningKey(secureKeyResolver.getSecureKey("dent").getBytes())
|
||||
.parseClaimsJws(compact)
|
||||
.getBody();
|
||||
assertClaims(new JwtAccessToken(claims, compact));
|
||||
}
|
||||
|
||||
private void assertClaims(JwtAccessToken token){
|
||||
assertThat(token.getId(), not(isEmptyOrNullString()));
|
||||
assertNotNull( token.getIssuedAt() );
|
||||
assertNotNull( token.getExpiration());
|
||||
assertTrue(token.getExpiration().getTime() > token.getIssuedAt().getTime());
|
||||
assertEquals("dent", token.getSubject());
|
||||
assertTrue(token.getIssuer().isPresent());
|
||||
assertEquals(token.getIssuer().get(), "https://www.scm-manager.org");
|
||||
assertEquals("b", token.getCustom("a").get());
|
||||
assertEquals("[\"repo:*\"]", token.getScope().toString());
|
||||
}
|
||||
|
||||
private SecureKey createSecureKey() {
|
||||
byte[] bytes = new byte[32];
|
||||
new Random().nextBytes(bytes);
|
||||
return new SecureKey(bytes, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,6 +51,7 @@ import static org.mockito.Mockito.*;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -69,7 +70,7 @@ public class CookieBearerWebTokenGeneratorTest
|
||||
{
|
||||
Cookie c = mock(Cookie.class);
|
||||
|
||||
when(c.getName()).thenReturn(CookieBearerWebTokenGenerator.COOKIE_NAME);
|
||||
when(c.getName()).thenReturn(HttpUtil.COOKIE_BEARER_AUTHENTICATION);
|
||||
when(c.getValue()).thenReturn("value");
|
||||
when(request.getCookies()).thenReturn(new Cookie[] { c });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user