mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 23:45:44 +01:00
implement ui client session id
This changeset introduces a client side session id, which is generated once by the client (ui: apiClient) and is send with each request to server. The server makes the session id available by the PrincipalCollection of the subject.
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -839,7 +839,7 @@
|
|||||||
<jaxb.version>2.3.0</jaxb.version>
|
<jaxb.version>2.3.0</jaxb.version>
|
||||||
|
|
||||||
<!-- event bus -->
|
<!-- event bus -->
|
||||||
<legman.version>1.5.1</legman.version>
|
<legman.version>1.6.0</legman.version>
|
||||||
|
|
||||||
<!-- webserver -->
|
<!-- webserver -->
|
||||||
<jetty.version>9.4.22.v20191022</jetty.version>
|
<jetty.version>9.4.22.v20191022</jetty.version>
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ import com.google.common.base.Preconditions;
|
|||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import org.apache.shiro.authc.AuthenticationToken;
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Token used for authentication with bearer tokens.
|
* Token used for authentication with bearer tokens.
|
||||||
*
|
*
|
||||||
@@ -45,20 +47,23 @@ import org.apache.shiro.authc.AuthenticationToken;
|
|||||||
*/
|
*/
|
||||||
public final class BearerToken implements AuthenticationToken {
|
public final class BearerToken implements AuthenticationToken {
|
||||||
|
|
||||||
|
private final SessionId sessionId;
|
||||||
private final String raw;
|
private final String raw;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new instance.
|
* Constructs a new instance.
|
||||||
*
|
*
|
||||||
|
* @param sessionId session id of the client
|
||||||
* @param raw raw bearer token
|
* @param raw raw bearer token
|
||||||
*/
|
*/
|
||||||
private BearerToken(String raw) {
|
private BearerToken(SessionId sessionId, String raw) {
|
||||||
|
this.sessionId = sessionId;
|
||||||
this.raw = raw;
|
this.raw = raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the wrapped raw format of the token.
|
* Returns the wrapped raw format of the token.
|
||||||
*
|
*
|
||||||
* @return raw format
|
* @return raw format
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -67,24 +72,41 @@ public final class BearerToken implements AuthenticationToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns always {@code null}.
|
* Returns the session id or {@code null}.
|
||||||
*
|
*
|
||||||
* @return {@code null}
|
* @return session id or {@code null}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Object getPrincipal() {
|
public SessionId getPrincipal() {
|
||||||
return null;
|
return sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link BearerToken} from raw string representation.
|
* Creates a new {@link BearerToken} from raw string representation.
|
||||||
*
|
*
|
||||||
* @param raw string representation
|
* @param raw string representation
|
||||||
*
|
*
|
||||||
* @return new bearer token
|
* @return new bearer token
|
||||||
*/
|
*/
|
||||||
public static BearerToken valueOf(String raw){
|
public static BearerToken valueOf(String raw){
|
||||||
Preconditions.checkArgument(!Strings.isNullOrEmpty(raw), "raw token is required");
|
Preconditions.checkArgument(!Strings.isNullOrEmpty(raw), "raw token is required");
|
||||||
return new BearerToken(raw);
|
return new BearerToken(null, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link BearerToken} from raw string representation for the given ui session id.
|
||||||
|
*
|
||||||
|
* @param sessionId session id of the client
|
||||||
|
* @param rawToken bearer token string representation
|
||||||
|
*
|
||||||
|
* @return new bearer token
|
||||||
|
*/
|
||||||
|
public static BearerToken create(@Nullable String sessionId, String rawToken) {
|
||||||
|
Preconditions.checkArgument(!Strings.isNullOrEmpty(rawToken), "raw token is required");
|
||||||
|
SessionId session = null;
|
||||||
|
if (!Strings.isNullOrEmpty(sessionId)) {
|
||||||
|
session = SessionId.valueOf(sessionId);
|
||||||
|
}
|
||||||
|
return new BearerToken(session, rawToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,13 +67,13 @@ public final class DAORealmHelper {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(DAORealmHelper.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DAORealmHelper.class);
|
||||||
|
|
||||||
private final LoginAttemptHandler loginAttemptHandler;
|
private final LoginAttemptHandler loginAttemptHandler;
|
||||||
|
|
||||||
private final UserDAO userDAO;
|
private final UserDAO userDAO;
|
||||||
|
|
||||||
private final String realm;
|
private final String realm;
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
//~--- constructors ---------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new instance. Consider to use {@link DAORealmHelperFactory} which
|
* Constructs a new instance. Consider to use {@link DAORealmHelperFactory} which
|
||||||
* handles dependency injection.
|
* handles dependency injection.
|
||||||
@@ -92,9 +92,9 @@ public final class DAORealmHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps credentials matcher and applies login attempt policies.
|
* Wraps credentials matcher and applies login attempt policies.
|
||||||
*
|
*
|
||||||
* @param credentialsMatcher credentials matcher to wrap
|
* @param credentialsMatcher credentials matcher to wrap
|
||||||
*
|
*
|
||||||
* @return wrapped credentials matcher
|
* @return wrapped credentials matcher
|
||||||
*/
|
*/
|
||||||
public CredentialsMatcher wrapCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
|
public CredentialsMatcher wrapCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
|
||||||
@@ -115,7 +115,7 @@ public final class DAORealmHelper {
|
|||||||
UsernamePasswordToken upt = (UsernamePasswordToken) token;
|
UsernamePasswordToken upt = (UsernamePasswordToken) token;
|
||||||
String principal = upt.getUsername();
|
String principal = upt.getUsername();
|
||||||
|
|
||||||
return getAuthenticationInfo(principal, null, null);
|
return getAuthenticationInfo(principal, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,8 +129,9 @@ public final class DAORealmHelper {
|
|||||||
return new AuthenticationInfoBuilder(principal);
|
return new AuthenticationInfoBuilder(principal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SimpleAuthenticationInfo getAuthenticationInfo(
|
||||||
private AuthenticationInfo getAuthenticationInfo(String principal, String credentials, Scope scope) {
|
String principal, String credentials, Scope scope, SessionId sessionId
|
||||||
|
) {
|
||||||
checkArgument(!Strings.isNullOrEmpty(principal), "username is required");
|
checkArgument(!Strings.isNullOrEmpty(principal), "username is required");
|
||||||
|
|
||||||
LOG.debug("try to authenticate {}", principal);
|
LOG.debug("try to authenticate {}", principal);
|
||||||
@@ -150,6 +151,10 @@ public final class DAORealmHelper {
|
|||||||
collection.add(user, realm);
|
collection.add(user, realm);
|
||||||
collection.add(MoreObjects.firstNonNull(scope, Scope.empty()), realm);
|
collection.add(MoreObjects.firstNonNull(scope, Scope.empty()), realm);
|
||||||
|
|
||||||
|
if (sessionId != null) {
|
||||||
|
collection.add(sessionId, realm);
|
||||||
|
}
|
||||||
|
|
||||||
String creds = credentials;
|
String creds = credentials;
|
||||||
|
|
||||||
if (credentials == null) {
|
if (credentials == null) {
|
||||||
@@ -170,7 +175,7 @@ public final class DAORealmHelper {
|
|||||||
|
|
||||||
private String credentials;
|
private String credentials;
|
||||||
private Scope scope;
|
private Scope scope;
|
||||||
private Iterable<String> groups = Collections.emptySet();
|
private SessionId sessionId;
|
||||||
|
|
||||||
private AuthenticationInfoBuilder(String principal) {
|
private AuthenticationInfoBuilder(String principal) {
|
||||||
this.principal = principal;
|
this.principal = principal;
|
||||||
@@ -201,17 +206,17 @@ public final class DAORealmHelper {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * With groups adds extra groups, besides those which come from the {@link GroupDAO}, to the authentication info.
|
* With the session id.
|
||||||
// *
|
*
|
||||||
// * @param groups extra groups
|
* @param sessionId session id
|
||||||
// *
|
*
|
||||||
// * @return {@code this}
|
* @return {@code this}
|
||||||
// */
|
*/
|
||||||
// public AuthenticationInfoBuilder withGroups(Iterable<String> groups) {
|
public AuthenticationInfoBuilder withSessionId(SessionId sessionId) {
|
||||||
// this.groups = groups;
|
this.sessionId = sessionId;
|
||||||
// return this;
|
return this;
|
||||||
// }
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build creates the authentication info from the given information.
|
* Build creates the authentication info from the given information.
|
||||||
@@ -219,7 +224,7 @@ public final class DAORealmHelper {
|
|||||||
* @return authentication info
|
* @return authentication info
|
||||||
*/
|
*/
|
||||||
public AuthenticationInfo build() {
|
public AuthenticationInfo build() {
|
||||||
return getAuthenticationInfo(principal, credentials, scope);
|
return getAuthenticationInfo(principal, credentials, scope, sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -233,7 +238,7 @@ public final class DAORealmHelper {
|
|||||||
this.loginAttemptHandler = loginAttemptHandler;
|
this.loginAttemptHandler = loginAttemptHandler;
|
||||||
this.credentialsMatcher = credentialsMatcher;
|
this.credentialsMatcher = credentialsMatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
|
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
|
||||||
loginAttemptHandler.beforeAuthentication(token);
|
loginAttemptHandler.beforeAuthentication(token);
|
||||||
@@ -245,7 +250,7 @@ public final class DAORealmHelper {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
41
scm-core/src/main/java/sonia/scm/security/SessionID.java
Normal file
41
scm-core/src/main/java/sonia/scm/security/SessionID.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client side session id.
|
||||||
|
*/
|
||||||
|
public final class SessionId {
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
private SessionId(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
SessionId sessionID = (SessionId) o;
|
||||||
|
return Objects.equals(value, sessionID.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SessionId valueOf(String value) {
|
||||||
|
Preconditions.checkArgument(!Strings.isNullOrEmpty(value), "session id could not be empty or null");
|
||||||
|
return new SessionId(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -116,6 +116,12 @@ public final class HttpUtil
|
|||||||
*/
|
*/
|
||||||
public static final String HEADER_SCM_CLIENT = "X-SCM-Client";
|
public static final String HEADER_SCM_CLIENT = "X-SCM-Client";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* header for identifying the scm-manager client session
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public static final String HEADER_SCM_SESSION = "X-SCM-Session-ID";
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
public static final String HEADER_USERAGENT = "User-Agent";
|
public static final String HEADER_USERAGENT = "User-Agent";
|
||||||
|
|
||||||
@@ -698,8 +704,10 @@ public final class HttpUtil
|
|||||||
String defaultValue)
|
String defaultValue)
|
||||||
{
|
{
|
||||||
String value = request.getHeader(header);
|
String value = request.getHeader(header);
|
||||||
|
if (value == null) {
|
||||||
return MoreObjects.firstNonNull(value, defaultValue);
|
value = defaultValue;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ class DAORealmHelperTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private UserDAO userDAO;
|
private UserDAO userDAO;
|
||||||
|
|
||||||
@Mock
|
|
||||||
private GroupDAO groupDAO;
|
|
||||||
|
|
||||||
private DAORealmHelper helper;
|
private DAORealmHelper helper;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@@ -87,6 +84,21 @@ class DAORealmHelperTest {
|
|||||||
assertThat(principals.oneByType(Scope.class)).isSameAs(scope);
|
assertThat(principals.oneByType(Scope.class)).isSameAs(scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnAuthenticationInfoWithSessionId() {
|
||||||
|
User user = new User("trillian");
|
||||||
|
when(userDAO.get("trillian")).thenReturn(user);
|
||||||
|
|
||||||
|
SessionId session = SessionId.valueOf("abc123");
|
||||||
|
|
||||||
|
AuthenticationInfo authenticationInfo = helper.authenticationInfoBuilder("trillian")
|
||||||
|
.withSessionId(session)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
PrincipalCollection principals = authenticationInfo.getPrincipals();
|
||||||
|
assertThat(principals.oneByType(SessionId.class)).isSameAs(session);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnAuthenticationInfoWithCredentials() {
|
void shouldReturnAuthenticationInfoWithCredentials() {
|
||||||
User user = new User("trillian");
|
User user = new User("trillian");
|
||||||
|
|||||||
@@ -54,6 +54,31 @@ import javax.servlet.http.HttpServletRequest;
|
|||||||
public class HttpUtilTest
|
public class HttpUtilTest
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetHeader() {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.getHeader("Test")).thenReturn("Value-One");
|
||||||
|
|
||||||
|
String value = HttpUtil.getHeader(request, "Test", "Fallback");
|
||||||
|
assertEquals("Value-One", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetHeaderWithDefaultValue() {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
|
||||||
|
String value = HttpUtil.getHeader(request, "Test", "Fallback");
|
||||||
|
assertEquals("Fallback", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetHeaderWithNullAsDefaultValue() {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
|
||||||
|
String value = HttpUtil.getHeader(request, "Test", null);
|
||||||
|
assertNull(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void concatenateTest() {
|
public void concatenateTest() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
|||||||
@@ -2,12 +2,20 @@ import { contextPath } from "./urls";
|
|||||||
import { createBackendError, ForbiddenError, isBackendError, UnauthorizedError } from "./errors";
|
import { createBackendError, ForbiddenError, isBackendError, UnauthorizedError } from "./errors";
|
||||||
import { BackendErrorContent } from "./errors";
|
import { BackendErrorContent } from "./errors";
|
||||||
|
|
||||||
|
const sessionId = (
|
||||||
|
Date.now().toString(36) +
|
||||||
|
Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.substr(2, 5)
|
||||||
|
).toUpperCase();
|
||||||
|
|
||||||
const applyFetchOptions: (p: RequestInit) => RequestInit = o => {
|
const applyFetchOptions: (p: RequestInit) => RequestInit = o => {
|
||||||
o.credentials = "same-origin";
|
o.credentials = "same-origin";
|
||||||
o.headers = {
|
o.headers = {
|
||||||
Cache: "no-cache",
|
Cache: "no-cache",
|
||||||
// identify the request as ajax request
|
// identify the request as ajax request
|
||||||
"X-Requested-With": "XMLHttpRequest"
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-SCM-Session-ID": sessionId
|
||||||
};
|
};
|
||||||
return o;
|
return o;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
|||||||
@Extension
|
@Extension
|
||||||
public class BearerRealm extends AuthenticatingRealm
|
public class BearerRealm extends AuthenticatingRealm
|
||||||
{
|
{
|
||||||
|
|
||||||
/** realm name */
|
/** realm name */
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final String REALM = "BearerRealm";
|
static final String REALM = "BearerRealm";
|
||||||
@@ -104,6 +104,7 @@ public class BearerRealm extends AuthenticatingRealm
|
|||||||
return helper.authenticationInfoBuilder(accessToken.getSubject())
|
return helper.authenticationInfoBuilder(accessToken.getSubject())
|
||||||
.withCredentials(bt.getCredentials())
|
.withCredentials(bt.getCredentials())
|
||||||
.withScope(Scopes.fromClaims(accessToken.getClaims()))
|
.withScope(Scopes.fromClaims(accessToken.getClaims()))
|
||||||
|
.withSessionId(bt.getPrincipal())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||||||
/**
|
/**
|
||||||
* Creates a {@link BearerToken} from an authorization header with
|
* Creates a {@link BearerToken} from an authorization header with
|
||||||
* bearer authorization.
|
* bearer authorization.
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@@ -53,7 +53,7 @@ public class BearerWebTokenGenerator extends SchemeBasedWebTokenGenerator
|
|||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link BearerToken} from an authorization header
|
* Creates a {@link BearerToken} from an authorization header
|
||||||
* with bearer authorization.
|
* with bearer authorization.
|
||||||
*
|
*
|
||||||
* @param request http servlet request
|
* @param request http servlet request
|
||||||
@@ -70,7 +70,8 @@ public class BearerWebTokenGenerator extends SchemeBasedWebTokenGenerator
|
|||||||
|
|
||||||
if (HttpUtil.AUTHORIZATION_SCHEME_BEARER.equalsIgnoreCase(scheme))
|
if (HttpUtil.AUTHORIZATION_SCHEME_BEARER.equalsIgnoreCase(scheme))
|
||||||
{
|
{
|
||||||
token = BearerToken.valueOf(authorization);
|
String sessionId = request.getHeader(HttpUtil.HEADER_SCM_SESSION);
|
||||||
|
token = BearerToken.create(sessionId, authorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||||||
import sonia.scm.util.HttpUtil;
|
import sonia.scm.util.HttpUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link BearerToken} from the {@link #COOKIE_NAME}
|
* Creates an {@link BearerToken} from the {@link HttpUtil#COOKIE_BEARER_AUTHENTICATION}
|
||||||
* cookie.
|
* cookie.
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -54,7 +54,7 @@ public class CookieBearerWebTokenGenerator implements WebTokenGenerator
|
|||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link BearerToken} from the {@link #COOKIE_NAME}
|
* Creates an {@link BearerToken} from the {@link HttpUtil#COOKIE_BEARER_AUTHENTICATION}
|
||||||
* cookie.
|
* cookie.
|
||||||
*
|
*
|
||||||
* @param request http servlet request
|
* @param request http servlet request
|
||||||
@@ -73,7 +73,8 @@ public class CookieBearerWebTokenGenerator implements WebTokenGenerator
|
|||||||
{
|
{
|
||||||
if (HttpUtil.COOKIE_BEARER_AUTHENTICATION.equals(cookie.getName()))
|
if (HttpUtil.COOKIE_BEARER_AUTHENTICATION.equals(cookie.getName()))
|
||||||
{
|
{
|
||||||
token = BearerToken.valueOf(cookie.getValue());
|
String sessionId = HttpUtil.getHeader(request, HttpUtil.HEADER_SCM_SESSION, null);
|
||||||
|
token = BearerToken.create(sessionId, cookie.getValue());
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,45 +177,26 @@ public class DefaultAdministrationContext implements AdministrationContext
|
|||||||
//J+
|
//J+
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void doRunAsInNonWebSessionContext(PrivilegedAction action) {
|
||||||
* Method description
|
logger.trace("bind shiro security manager to current thread");
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param action
|
|
||||||
*/
|
|
||||||
private void doRunAsInNonWebSessionContext(PrivilegedAction action)
|
|
||||||
{
|
|
||||||
if (logger.isTraceEnabled())
|
|
||||||
{
|
|
||||||
logger.trace("bind shiro security manager to current thread");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
SecurityUtils.setSecurityManager(securityManager);
|
SecurityUtils.setSecurityManager(securityManager);
|
||||||
|
|
||||||
Subject subject = createAdminSubject();
|
Subject subject = createAdminSubject();
|
||||||
ThreadState state = new SubjectThreadState(subject);
|
ThreadState state = new SubjectThreadState(subject);
|
||||||
|
|
||||||
state.bind();
|
state.bind();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (logger.isInfoEnabled())
|
logger.info("execute action {} in administration context", action.getClass().getName());
|
||||||
{
|
|
||||||
logger.info("execute action {} in administration context",
|
|
||||||
action.getClass().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
action.run();
|
action.run();
|
||||||
|
} finally {
|
||||||
|
logger.trace("restore current thread state");
|
||||||
|
state.restore();
|
||||||
}
|
}
|
||||||
finally
|
} finally {
|
||||||
{
|
|
||||||
state.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
SecurityUtils.setSecurityManager(null);
|
SecurityUtils.setSecurityManager(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link BearerRealm}.
|
* Unit tests for {@link BearerRealm}.
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@@ -84,11 +84,9 @@ class BearerRealmTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldDoGetAuthentication() {
|
void shouldDoGetAuthentication() {
|
||||||
BearerToken bearerToken = BearerToken.valueOf("__bearer__");
|
BearerToken bearerToken = BearerToken.create("__session__", "__bearer__");
|
||||||
AccessToken accessToken = mock(AccessToken.class);
|
AccessToken accessToken = mock(AccessToken.class);
|
||||||
|
|
||||||
Set<String> groups = ImmutableSet.of("HeartOfGold", "Puzzle42");
|
|
||||||
|
|
||||||
when(accessToken.getSubject()).thenReturn("trillian");
|
when(accessToken.getSubject()).thenReturn("trillian");
|
||||||
when(accessToken.getClaims()).thenReturn(new HashMap<>());
|
when(accessToken.getClaims()).thenReturn(new HashMap<>());
|
||||||
when(accessTokenResolver.resolve(bearerToken)).thenReturn(accessToken);
|
when(accessTokenResolver.resolve(bearerToken)).thenReturn(accessToken);
|
||||||
@@ -96,6 +94,7 @@ class BearerRealmTest {
|
|||||||
when(realmHelper.authenticationInfoBuilder("trillian")).thenReturn(builder);
|
when(realmHelper.authenticationInfoBuilder("trillian")).thenReturn(builder);
|
||||||
when(builder.withCredentials("__bearer__")).thenReturn(builder);
|
when(builder.withCredentials("__bearer__")).thenReturn(builder);
|
||||||
when(builder.withScope(any(Scope.class))).thenReturn(builder);
|
when(builder.withScope(any(Scope.class))).thenReturn(builder);
|
||||||
|
when(builder.withSessionId(any(SessionId.class))).thenReturn(builder);
|
||||||
when(builder.build()).thenReturn(authenticationInfo);
|
when(builder.build()).thenReturn(authenticationInfo);
|
||||||
|
|
||||||
AuthenticationInfo result = realm.doGetAuthenticationInfo(bearerToken);
|
AuthenticationInfo result = realm.doGetAuthenticationInfo(bearerToken);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2014, Sebastian Sdorra
|
* Copyright (c) 2014, Sebastian Sdorra
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions are met:
|
* modification, are permitted provided that the following conditions are met:
|
||||||
*
|
*
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
* this list of conditions and the following disclaimer.
|
* this list of conditions and the following disclaimer.
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
* contributors may be used to endorse or promote products derived from this
|
* contributors may be used to endorse or promote products derived from this
|
||||||
* software without specific prior written permission.
|
* software without specific prior written permission.
|
||||||
*
|
*
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
@@ -24,56 +24,82 @@
|
|||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
* 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
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*
|
*
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.web;
|
package sonia.scm.web;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import org.apache.shiro.authc.AuthenticationToken;
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
import org.junit.Test;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import sonia.scm.security.BearerToken;
|
import sonia.scm.security.BearerToken;
|
||||||
|
import sonia.scm.security.SessionId;
|
||||||
|
import sonia.scm.util.HttpUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
public class BearerWebTokenGeneratorTest {
|
class BearerWebTokenGeneratorTest {
|
||||||
|
|
||||||
|
private final BearerWebTokenGenerator tokenGenerator = new BearerWebTokenGenerator();
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private HttpServletRequest request;
|
private HttpServletRequest request;
|
||||||
|
|
||||||
private final BearerWebTokenGenerator tokenGenerator = new BearerWebTokenGenerator();
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateTokenWithWrongScheme()
|
void shouldNotCreateTokenWithWrongScheme() {
|
||||||
{
|
|
||||||
when(request.getHeader("Authorization")).thenReturn("BASIC ASD");
|
when(request.getHeader("Authorization")).thenReturn("BASIC ASD");
|
||||||
assertNull(tokenGenerator.createToken(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateTokenWithoutAuthorizationHeader(){
|
|
||||||
assertNull(tokenGenerator.createToken(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateToken(){
|
|
||||||
when(request.getHeader("Authorization")).thenReturn("Bearer asd");
|
|
||||||
AuthenticationToken token = tokenGenerator.createToken(request);
|
AuthenticationToken token = tokenGenerator.createToken(request);
|
||||||
assertNotNull(token);
|
|
||||||
assertThat(token, instanceOf(BearerToken.class));
|
assertThat(token).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotCreateTokenWithoutAuthorizationHeader(){
|
||||||
|
AuthenticationToken token = tokenGenerator.createToken(request);
|
||||||
|
|
||||||
|
assertThat(token).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateToken(){
|
||||||
|
when(request.getHeader("Authorization")).thenReturn("Bearer asd");
|
||||||
|
|
||||||
|
AuthenticationToken token = tokenGenerator.createToken(request);
|
||||||
|
assertThat(token)
|
||||||
|
.isNotNull()
|
||||||
|
.isInstanceOf(BearerToken.class);
|
||||||
|
|
||||||
BearerToken bt = (BearerToken) token;
|
BearerToken bt = (BearerToken) token;
|
||||||
assertThat(bt.getCredentials(), equalTo("asd"));
|
assertThat(bt.getCredentials()).isEqualTo("asd");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateTokenWithSessionId(){
|
||||||
|
doReturn("Bearer asd").when(request).getHeader("Authorization");
|
||||||
|
doReturn("bcd123").when(request).getHeader(HttpUtil.HEADER_SCM_SESSION);
|
||||||
|
|
||||||
|
AuthenticationToken token = tokenGenerator.createToken(request);
|
||||||
|
assertThat(token)
|
||||||
|
.isNotNull()
|
||||||
|
.isInstanceOf(BearerToken.class);
|
||||||
|
|
||||||
|
BearerToken bt = (BearerToken) token;
|
||||||
|
assertThat(bt.getPrincipal()).isEqualTo(SessionId.valueOf("bcd123"));
|
||||||
|
assertThat(bt.getCredentials()).isEqualTo("asd");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,82 +35,81 @@ package sonia.scm.web;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
import sonia.scm.security.BearerToken;
|
import sonia.scm.security.BearerToken;
|
||||||
|
import sonia.scm.security.SessionId;
|
||||||
import static org.junit.Assert.*;
|
import sonia.scm.util.HttpUtil;
|
||||||
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
public class CookieBearerWebTokenGeneratorTest
|
class CookieBearerWebTokenGeneratorTest {
|
||||||
{
|
|
||||||
|
private final CookieBearerWebTokenGenerator tokenGenerator = new CookieBearerWebTokenGenerator();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HttpServletRequest request;
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateToken()
|
void shouldCreateToken() {
|
||||||
{
|
assignBearerCookie("value");
|
||||||
Cookie c = mock(Cookie.class);
|
|
||||||
|
|
||||||
when(c.getName()).thenReturn(HttpUtil.COOKIE_BEARER_AUTHENTICATION);
|
|
||||||
when(c.getValue()).thenReturn("value");
|
|
||||||
when(request.getCookies()).thenReturn(new Cookie[] { c });
|
|
||||||
|
|
||||||
BearerToken token = tokenGenerator.createToken(request);
|
BearerToken token = tokenGenerator.createToken(request);
|
||||||
|
|
||||||
assertNotNull(token);
|
assertThat(token).isNotNull();
|
||||||
assertEquals("value", token.getCredentials());
|
assertThat(token.getPrincipal()).isNull();
|
||||||
|
assertThat(token.getCredentials()).isEqualTo("value");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateTokenWithWrongCookie()
|
void shouldCreateTokenWithSessionId() {
|
||||||
{
|
when(request.getHeader(HttpUtil.HEADER_SCM_SESSION)).thenReturn("abc123");
|
||||||
|
|
||||||
|
assignBearerCookie("authc");
|
||||||
|
|
||||||
|
BearerToken token = tokenGenerator.createToken(request);
|
||||||
|
|
||||||
|
assertThat(token).isNotNull();
|
||||||
|
assertThat(token.getPrincipal()).isEqualTo(SessionId.valueOf("abc123"));
|
||||||
|
assertThat(token.getCredentials()).isEqualTo("authc");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assignBearerCookie(String value) {
|
||||||
|
assignCookie(HttpUtil.COOKIE_BEARER_AUTHENTICATION, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assignCookie(String name, String value) {
|
||||||
Cookie c = mock(Cookie.class);
|
Cookie c = mock(Cookie.class);
|
||||||
|
|
||||||
when(c.getName()).thenReturn("other-cookie");
|
when(c.getName()).thenReturn(name);
|
||||||
when(request.getCookies()).thenReturn(new Cookie[] { c });
|
lenient().when(c.getValue()).thenReturn(value);
|
||||||
assertNull(tokenGenerator.createToken(request));
|
when(request.getCookies()).thenReturn(new Cookie[]{c});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateTokenWithoutCookies()
|
void shouldNotCreateTokenForWrongCookie() {
|
||||||
{
|
assignCookie("other-cookie", "with-some-value");
|
||||||
assertNull(tokenGenerator.createToken(request));
|
|
||||||
|
BearerToken token = tokenGenerator.createToken(request);
|
||||||
|
assertThat(token).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
@Test
|
||||||
|
void shouldNotCreateTokenWithoutCookies() {
|
||||||
/** Field description */
|
BearerToken token = tokenGenerator.createToken(request);
|
||||||
private final CookieBearerWebTokenGenerator tokenGenerator =
|
assertThat(token).isNull();
|
||||||
new CookieBearerWebTokenGenerator();
|
}
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
@Mock
|
|
||||||
private HttpServletRequest request;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package sonia.scm.web.security;
|
||||||
|
|
||||||
|
import com.google.inject.Guice;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.apache.shiro.mgt.DefaultSecurityManager;
|
||||||
|
import org.apache.shiro.mgt.SecurityManager;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class DefaultAdministrationContextTest {
|
||||||
|
|
||||||
|
private DefaultAdministrationContext context;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Subject subject;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void create() {
|
||||||
|
Injector injector = Guice.createInjector();
|
||||||
|
SecurityManager securityManager = new DefaultSecurityManager();
|
||||||
|
|
||||||
|
context = new DefaultAdministrationContext(injector, securityManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldBindSubject() {
|
||||||
|
context.runAsAdmin(() -> {
|
||||||
|
Subject adminSubject = SecurityUtils.getSubject();
|
||||||
|
assertThat(adminSubject.getPrincipal()).isEqualTo("scmsystem");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldBindSubjectEvenIfAlreadyBound() {
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
try {
|
||||||
|
|
||||||
|
context.runAsAdmin(() -> {
|
||||||
|
Subject adminSubject = SecurityUtils.getSubject();
|
||||||
|
assertThat(adminSubject.getPrincipal()).isEqualTo("scmsystem");
|
||||||
|
});
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRestoreCurrentSubject() {
|
||||||
|
when(subject.getPrincipal()).thenReturn("tricia");
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
try {
|
||||||
|
context.runAsAdmin(() -> {});
|
||||||
|
Subject currentSubject = SecurityUtils.getSubject();
|
||||||
|
assertThat(currentSubject.getPrincipal()).isEqualTo("tricia");
|
||||||
|
} finally {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user