mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 15:35:49 +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.
|
* Name of bearer authentication cookie.
|
||||||
|
*
|
||||||
|
* TODO find a better place
|
||||||
|
*
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public static final String COOKIE_BEARER_AUTHENTICATION = "X-Bearer-Token";
|
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.ScmStateFactory;
|
||||||
import sonia.scm.api.rest.RestActionResult;
|
import sonia.scm.api.rest.RestActionResult;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.security.BearerTokenGenerator;
|
|
||||||
import sonia.scm.security.Tokens;
|
import sonia.scm.security.Tokens;
|
||||||
import sonia.scm.user.User;
|
|
||||||
import sonia.scm.util.HttpUtil;
|
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.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
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.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
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;
|
import sonia.scm.security.Scope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,15 +113,17 @@ public class AuthenticationResource
|
|||||||
*
|
*
|
||||||
* @param configuration
|
* @param configuration
|
||||||
* @param stateFactory
|
* @param stateFactory
|
||||||
* @param tokenGenerator
|
* @param tokenBuilderFactory
|
||||||
|
* @param cookieIssuer
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public AuthenticationResource(ScmConfiguration configuration,
|
public AuthenticationResource(ScmConfiguration configuration,
|
||||||
ScmStateFactory stateFactory, BearerTokenGenerator tokenGenerator)
|
ScmStateFactory stateFactory, AccessTokenBuilderFactory tokenBuilderFactory, AccessTokenCookieIssuer cookieIssuer)
|
||||||
{
|
{
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.stateFactory = stateFactory;
|
this.stateFactory = stateFactory;
|
||||||
this.tokenGenerator = tokenGenerator;
|
this.tokenBuilderFactory = tokenBuilderFactory;
|
||||||
|
this.cookieIssuer = cookieIssuer;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -170,33 +167,20 @@ public class AuthenticationResource
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
subject.login(Tokens.createAuthenticationToken(request, username,
|
subject.login(Tokens.createAuthenticationToken(request, username, password));
|
||||||
password));
|
AccessTokenBuilder tokenBuilder = tokenBuilderFactory.create();
|
||||||
|
if ( scope != null ) {
|
||||||
User user = subject.getPrincipals().oneByType(User.class);
|
tokenBuilder.scope(Scope.valueOf(scope));
|
||||||
|
}
|
||||||
String token = tokenGenerator.createBearerToken(user, scope != null ? Scope.valueOf(scope) : Scope.empty());
|
AccessToken token = tokenBuilder.build();
|
||||||
|
|
||||||
ScmState state;
|
ScmState state;
|
||||||
|
|
||||||
if (cookie)
|
if (cookie) {
|
||||||
{
|
cookieIssuer.authenticate(request, response, token);
|
||||||
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);
|
|
||||||
state = stateFactory.createState(subject);
|
state = stateFactory.createState(subject);
|
||||||
}
|
} else {
|
||||||
else
|
state = stateFactory.createState(subject, token.compact());
|
||||||
{
|
|
||||||
state = stateFactory.createState(subject, token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res = Response.ok(state).build();
|
res = Response.ok(state).build();
|
||||||
@@ -276,16 +260,8 @@ public class AuthenticationResource
|
|||||||
|
|
||||||
subject.logout();
|
subject.logout();
|
||||||
|
|
||||||
// remove bearer authentication cookie
|
// remove authentication cookie
|
||||||
Cookie c = new Cookie(
|
cookieIssuer.invalidate(request, response);
|
||||||
HttpUtil.COOKIE_BEARER_AUTHENTICATION,
|
|
||||||
Util.EMPTY_STRING
|
|
||||||
);
|
|
||||||
c.setPath(request.getContextPath());
|
|
||||||
c.setMaxAge(0);
|
|
||||||
c.setHttpOnly(true);
|
|
||||||
|
|
||||||
response.addCookie(c);
|
|
||||||
|
|
||||||
Response resp;
|
Response resp;
|
||||||
|
|
||||||
@@ -481,5 +457,8 @@ public class AuthenticationResource
|
|||||||
private final ScmStateFactory stateFactory;
|
private final ScmStateFactory stateFactory;
|
||||||
|
|
||||||
/** Field description */
|
/** 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) {
|
public static Scope fromClaims(Map<String,Object> claims) {
|
||||||
Scope scope = Scope.empty();
|
Scope scope = Scope.empty();
|
||||||
if (claims.containsKey(Scopes.CLAIMS_KEY)) {
|
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;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ package sonia.scm.web;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
import sonia.scm.security.BearerAuthenticationToken;
|
import sonia.scm.security.BearerAuthenticationToken;
|
||||||
|
|
||||||
@@ -41,6 +40,7 @@ import sonia.scm.security.BearerAuthenticationToken;
|
|||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
import javax.servlet.http.Cookie;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import sonia.scm.util.HttpUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link BearerAuthenticationToken} from the {@link #COOKIE_NAME}
|
* Creates an {@link BearerAuthenticationToken} from the {@link #COOKIE_NAME}
|
||||||
@@ -53,12 +53,6 @@ import javax.servlet.http.HttpServletRequest;
|
|||||||
public class CookieBearerWebTokenGenerator implements WebTokenGenerator
|
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}
|
* Creates an {@link BearerAuthenticationToken} from the {@link #COOKIE_NAME}
|
||||||
* cookie.
|
* cookie.
|
||||||
@@ -77,7 +71,7 @@ public class CookieBearerWebTokenGenerator implements WebTokenGenerator
|
|||||||
{
|
{
|
||||||
for (Cookie cookie : cookies)
|
for (Cookie cookie : cookies)
|
||||||
{
|
{
|
||||||
if (COOKIE_NAME.equals(cookie.getName()))
|
if (HttpUtil.COOKIE_BEARER_AUTHENTICATION.equals(cookie.getName()))
|
||||||
{
|
{
|
||||||
token = new BearerAuthenticationToken(cookie.getValue());
|
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.Cookie;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import sonia.scm.util.HttpUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -69,7 +70,7 @@ public class CookieBearerWebTokenGeneratorTest
|
|||||||
{
|
{
|
||||||
Cookie c = mock(Cookie.class);
|
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(c.getValue()).thenReturn("value");
|
||||||
when(request.getCookies()).thenReturn(new Cookie[] { c });
|
when(request.getCookies()).thenReturn(new Cookie[] { c });
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user