diff --git a/pom.xml b/pom.xml
index 910287abf0..7a8e16b869 100644
--- a/pom.xml
+++ b/pom.xml
@@ -839,7 +839,7 @@
2.3.0
- 1.5.1
+ 1.6.0
9.4.22.v20191022
diff --git a/scm-core/src/main/java/sonia/scm/security/BearerToken.java b/scm-core/src/main/java/sonia/scm/security/BearerToken.java
index 4dd60a09c7..43a225b5b6 100644
--- a/scm-core/src/main/java/sonia/scm/security/BearerToken.java
+++ b/scm-core/src/main/java/sonia/scm/security/BearerToken.java
@@ -37,6 +37,8 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import org.apache.shiro.authc.AuthenticationToken;
+import javax.annotation.Nullable;
+
/**
* Token used for authentication with bearer tokens.
*
@@ -45,20 +47,23 @@ import org.apache.shiro.authc.AuthenticationToken;
*/
public final class BearerToken implements AuthenticationToken {
+ private final SessionId sessionId;
private final String raw;
/**
* Constructs a new instance.
- *
+ *
+ * @param sessionId session id of the client
* @param raw raw bearer token
*/
- private BearerToken(String raw) {
+ private BearerToken(SessionId sessionId, String raw) {
+ this.sessionId = sessionId;
this.raw = raw;
}
-
+
/**
* Returns the wrapped raw format of the token.
- *
+ *
* @return raw format
*/
@Override
@@ -67,24 +72,41 @@ public final class BearerToken implements AuthenticationToken {
}
/**
- * Returns always {@code null}.
- *
- * @return {@code null}
+ * Returns the session id or {@code null}.
+ *
+ * @return session id or {@code null}
*/
@Override
- public Object getPrincipal() {
- return null;
+ public SessionId getPrincipal() {
+ return sessionId;
}
-
+
/**
* Creates a new {@link BearerToken} from raw string representation.
- *
+ *
* @param raw string representation
- *
+ *
* @return new bearer token
*/
public static BearerToken valueOf(String raw){
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);
}
}
diff --git a/scm-core/src/main/java/sonia/scm/security/DAORealmHelper.java b/scm-core/src/main/java/sonia/scm/security/DAORealmHelper.java
index 6ec64a67de..6987fab0e1 100644
--- a/scm-core/src/main/java/sonia/scm/security/DAORealmHelper.java
+++ b/scm-core/src/main/java/sonia/scm/security/DAORealmHelper.java
@@ -67,13 +67,13 @@ public final class DAORealmHelper {
private static final Logger LOG = LoggerFactory.getLogger(DAORealmHelper.class);
private final LoginAttemptHandler loginAttemptHandler;
-
+
private final UserDAO userDAO;
-
+
private final String realm;
-
+
//~--- constructors ---------------------------------------------------------
-
+
/**
* Constructs a new instance. Consider to use {@link DAORealmHelperFactory} which
* handles dependency injection.
@@ -92,9 +92,9 @@ public final class DAORealmHelper {
/**
* Wraps credentials matcher and applies login attempt policies.
- *
+ *
* @param credentialsMatcher credentials matcher to wrap
- *
+ *
* @return wrapped credentials matcher
*/
public CredentialsMatcher wrapCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
@@ -115,7 +115,7 @@ public final class DAORealmHelper {
UsernamePasswordToken upt = (UsernamePasswordToken) token;
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);
}
-
- private AuthenticationInfo getAuthenticationInfo(String principal, String credentials, Scope scope) {
+ private SimpleAuthenticationInfo getAuthenticationInfo(
+ String principal, String credentials, Scope scope, SessionId sessionId
+ ) {
checkArgument(!Strings.isNullOrEmpty(principal), "username is required");
LOG.debug("try to authenticate {}", principal);
@@ -150,6 +151,10 @@ public final class DAORealmHelper {
collection.add(user, realm);
collection.add(MoreObjects.firstNonNull(scope, Scope.empty()), realm);
+ if (sessionId != null) {
+ collection.add(sessionId, realm);
+ }
+
String creds = credentials;
if (credentials == null) {
@@ -170,7 +175,7 @@ public final class DAORealmHelper {
private String credentials;
private Scope scope;
- private Iterable groups = Collections.emptySet();
+ private SessionId sessionId;
private AuthenticationInfoBuilder(String principal) {
this.principal = principal;
@@ -201,17 +206,17 @@ public final class DAORealmHelper {
return this;
}
-// /**
-// * With groups adds extra groups, besides those which come from the {@link GroupDAO}, to the authentication info.
-// *
-// * @param groups extra groups
-// *
-// * @return {@code this}
-// */
-// public AuthenticationInfoBuilder withGroups(Iterable groups) {
-// this.groups = groups;
-// return this;
-// }
+ /**
+ * With the session id.
+ *
+ * @param sessionId session id
+ *
+ * @return {@code this}
+ */
+ public AuthenticationInfoBuilder withSessionId(SessionId sessionId) {
+ this.sessionId = sessionId;
+ return this;
+ }
/**
* Build creates the authentication info from the given information.
@@ -219,7 +224,7 @@ public final class DAORealmHelper {
* @return authentication info
*/
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.credentialsMatcher = credentialsMatcher;
}
-
+
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
loginAttemptHandler.beforeAuthentication(token);
@@ -245,7 +250,7 @@ public final class DAORealmHelper {
}
return result;
}
-
+
}
-
+
}
diff --git a/scm-core/src/main/java/sonia/scm/security/SessionID.java b/scm-core/src/main/java/sonia/scm/security/SessionID.java
new file mode 100644
index 0000000000..3c4fb126ff
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/security/SessionID.java
@@ -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);
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java
index 2744addc62..8edc0c8e14 100644
--- a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java
+++ b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java
@@ -116,6 +116,12 @@ public final class HttpUtil
*/
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 */
public static final String HEADER_USERAGENT = "User-Agent";
@@ -698,8 +704,10 @@ public final class HttpUtil
String defaultValue)
{
String value = request.getHeader(header);
-
- return MoreObjects.firstNonNull(value, defaultValue);
+ if (value == null) {
+ value = defaultValue;
+ }
+ return value;
}
/**
diff --git a/scm-core/src/test/java/sonia/scm/security/DAORealmHelperTest.java b/scm-core/src/test/java/sonia/scm/security/DAORealmHelperTest.java
index 0fbcc20ac0..cc6d4020c1 100644
--- a/scm-core/src/test/java/sonia/scm/security/DAORealmHelperTest.java
+++ b/scm-core/src/test/java/sonia/scm/security/DAORealmHelperTest.java
@@ -27,9 +27,6 @@ class DAORealmHelperTest {
@Mock
private UserDAO userDAO;
- @Mock
- private GroupDAO groupDAO;
-
private DAORealmHelper helper;
@BeforeEach
@@ -87,6 +84,21 @@ class DAORealmHelperTest {
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
void shouldReturnAuthenticationInfoWithCredentials() {
User user = new User("trillian");
diff --git a/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java b/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java
index 5e6fa3e10e..c827764a92 100644
--- a/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java
+++ b/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java
@@ -54,6 +54,31 @@ import javax.servlet.http.HttpServletRequest;
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
public void concatenateTest() {
assertEquals(
diff --git a/scm-ui/ui-components/src/apiclient.ts b/scm-ui/ui-components/src/apiclient.ts
index 396200f1c1..60789ff5dd 100644
--- a/scm-ui/ui-components/src/apiclient.ts
+++ b/scm-ui/ui-components/src/apiclient.ts
@@ -2,12 +2,20 @@ import { contextPath } from "./urls";
import { createBackendError, ForbiddenError, isBackendError, UnauthorizedError } 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 => {
o.credentials = "same-origin";
o.headers = {
Cache: "no-cache",
// identify the request as ajax request
- "X-Requested-With": "XMLHttpRequest"
+ "X-Requested-With": "XMLHttpRequest",
+ "X-SCM-Session-ID": sessionId
};
return o;
};
diff --git a/scm-webapp/src/main/java/sonia/scm/security/BearerRealm.java b/scm-webapp/src/main/java/sonia/scm/security/BearerRealm.java
index 324dfe9082..a2064575db 100644
--- a/scm-webapp/src/main/java/sonia/scm/security/BearerRealm.java
+++ b/scm-webapp/src/main/java/sonia/scm/security/BearerRealm.java
@@ -56,7 +56,7 @@ import static com.google.common.base.Preconditions.checkArgument;
@Extension
public class BearerRealm extends AuthenticatingRealm
{
-
+
/** realm name */
@VisibleForTesting
static final String REALM = "BearerRealm";
@@ -104,6 +104,7 @@ public class BearerRealm extends AuthenticatingRealm
return helper.authenticationInfoBuilder(accessToken.getSubject())
.withCredentials(bt.getCredentials())
.withScope(Scopes.fromClaims(accessToken.getClaims()))
+ .withSessionId(bt.getPrincipal())
.build();
}
diff --git a/scm-webapp/src/main/java/sonia/scm/web/BearerWebTokenGenerator.java b/scm-webapp/src/main/java/sonia/scm/web/BearerWebTokenGenerator.java
index 57f83b4c35..4bb79e7b9a 100644
--- a/scm-webapp/src/main/java/sonia/scm/web/BearerWebTokenGenerator.java
+++ b/scm-webapp/src/main/java/sonia/scm/web/BearerWebTokenGenerator.java
@@ -44,7 +44,7 @@ import javax.servlet.http.HttpServletRequest;
/**
* Creates a {@link BearerToken} from an authorization header with
* bearer authorization.
- *
+ *
* @author Sebastian Sdorra
* @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.
*
* @param request http servlet request
@@ -70,7 +70,8 @@ public class BearerWebTokenGenerator extends SchemeBasedWebTokenGenerator
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;
diff --git a/scm-webapp/src/main/java/sonia/scm/web/CookieBearerWebTokenGenerator.java b/scm-webapp/src/main/java/sonia/scm/web/CookieBearerWebTokenGenerator.java
index 27d82f5a41..2a0a97c07e 100644
--- a/scm-webapp/src/main/java/sonia/scm/web/CookieBearerWebTokenGenerator.java
+++ b/scm-webapp/src/main/java/sonia/scm/web/CookieBearerWebTokenGenerator.java
@@ -43,7 +43,7 @@ import javax.servlet.http.HttpServletRequest;
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.
*
* @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.
*
* @param request http servlet request
@@ -73,7 +73,8 @@ public class CookieBearerWebTokenGenerator implements WebTokenGenerator
{
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;
}
diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java b/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java
index b62b6c63f3..43238e3c3f 100644
--- a/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java
+++ b/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java
@@ -177,45 +177,26 @@ public class DefaultAdministrationContext implements AdministrationContext
//J+
}
- /**
- * Method description
- *
- *
- * @param action
- */
- private void doRunAsInNonWebSessionContext(PrivilegedAction action)
- {
- if (logger.isTraceEnabled())
- {
- logger.trace("bind shiro security manager to current thread");
- }
+ private void doRunAsInNonWebSessionContext(PrivilegedAction action) {
+ logger.trace("bind shiro security manager to current thread");
- try
- {
+ try {
SecurityUtils.setSecurityManager(securityManager);
Subject subject = createAdminSubject();
ThreadState state = new SubjectThreadState(subject);
state.bind();
-
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();
+ } finally {
+ logger.trace("restore current thread state");
+ state.restore();
}
- finally
- {
- state.clear();
- }
- }
- finally
- {
+ } finally {
SecurityUtils.setSecurityManager(null);
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java b/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java
index d458f9c72c..897b251cfa 100644
--- a/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java
@@ -52,7 +52,7 @@ import static org.mockito.Mockito.when;
/**
* Unit tests for {@link BearerRealm}.
- *
+ *
* @author Sebastian Sdorra
*/
@ExtendWith(MockitoExtension.class)
@@ -84,11 +84,9 @@ class BearerRealmTest {
@Test
void shouldDoGetAuthentication() {
- BearerToken bearerToken = BearerToken.valueOf("__bearer__");
+ BearerToken bearerToken = BearerToken.create("__session__", "__bearer__");
AccessToken accessToken = mock(AccessToken.class);
- Set groups = ImmutableSet.of("HeartOfGold", "Puzzle42");
-
when(accessToken.getSubject()).thenReturn("trillian");
when(accessToken.getClaims()).thenReturn(new HashMap<>());
when(accessTokenResolver.resolve(bearerToken)).thenReturn(accessToken);
@@ -96,6 +94,7 @@ class BearerRealmTest {
when(realmHelper.authenticationInfoBuilder("trillian")).thenReturn(builder);
when(builder.withCredentials("__bearer__")).thenReturn(builder);
when(builder.withScope(any(Scope.class))).thenReturn(builder);
+ when(builder.withSessionId(any(SessionId.class))).thenReturn(builder);
when(builder.build()).thenReturn(authenticationInfo);
AuthenticationInfo result = realm.doGetAuthenticationInfo(bearerToken);
diff --git a/scm-webapp/src/test/java/sonia/scm/web/BearerWebTokenGeneratorTest.java b/scm-webapp/src/test/java/sonia/scm/web/BearerWebTokenGeneratorTest.java
index 71195b7f48..1c054111b7 100644
--- a/scm-webapp/src/test/java/sonia/scm/web/BearerWebTokenGeneratorTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/web/BearerWebTokenGeneratorTest.java
@@ -1,10 +1,10 @@
/**
* 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,
@@ -13,7 +13,7 @@
* 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
@@ -24,56 +24,82 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
+ *
* http://bitbucket.org/sdorra/scm-manager
- *
+ *
*/
package sonia.scm.web;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.authc.AuthenticationToken;
-import org.junit.Test;
-import static org.junit.Assert.*;
-import static org.hamcrest.Matchers.*;
-import org.junit.runner.RunWith;
+
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
+
+import static org.assertj.core.api.Assertions.assertThat;
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.SessionId;
+import sonia.scm.util.HttpUtil;
/**
*
* @author Sebastian Sdorra
*/
-@RunWith(MockitoJUnitRunner.class)
-public class BearerWebTokenGeneratorTest {
+@ExtendWith(MockitoExtension.class)
+class BearerWebTokenGeneratorTest {
+
+ private final BearerWebTokenGenerator tokenGenerator = new BearerWebTokenGenerator();
@Mock
private HttpServletRequest request;
-
- private final BearerWebTokenGenerator tokenGenerator = new BearerWebTokenGenerator();
@Test
- public void testCreateTokenWithWrongScheme()
- {
+ void shouldNotCreateTokenWithWrongScheme() {
when(request.getHeader("Authorization")).thenReturn("BASIC ASD");
- assertNull(tokenGenerator.createToken(request));
- }
-
- @Test
- public void testCreateTokenWithoutAuthorizationHeader(){
- assertNull(tokenGenerator.createToken(request));
- }
-
- @Test
- public void testCreateToken(){
- when(request.getHeader("Authorization")).thenReturn("Bearer asd");
+
AuthenticationToken token = tokenGenerator.createToken(request);
- assertNotNull(token);
- assertThat(token, instanceOf(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;
- 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");
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/web/CookieBearerWebTokenGeneratorTest.java b/scm-webapp/src/test/java/sonia/scm/web/CookieBearerWebTokenGeneratorTest.java
index 115b52fa36..45d2c46b83 100644
--- a/scm-webapp/src/test/java/sonia/scm/web/CookieBearerWebTokenGeneratorTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/web/CookieBearerWebTokenGeneratorTest.java
@@ -35,82 +35,81 @@ package sonia.scm.web;
//~--- non-JDK imports --------------------------------------------------------
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
+import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.security.BearerToken;
-
-import static org.junit.Assert.*;
-
-import static org.mockito.Mockito.*;
-
-//~--- JDK imports ------------------------------------------------------------
+import sonia.scm.security.SessionId;
+import sonia.scm.util.HttpUtil;
import javax.servlet.http.Cookie;
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
*/
-@RunWith(MockitoJUnitRunner.class)
-public class CookieBearerWebTokenGeneratorTest
-{
+@ExtendWith(MockitoExtension.class)
+class CookieBearerWebTokenGeneratorTest {
+
+ private final CookieBearerWebTokenGenerator tokenGenerator = new CookieBearerWebTokenGenerator();
+
+ @Mock
+ private HttpServletRequest request;
- /**
- * Method description
- *
- */
@Test
- public void testCreateToken()
- {
- 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 });
+ void shouldCreateToken() {
+ assignBearerCookie("value");
BearerToken token = tokenGenerator.createToken(request);
- assertNotNull(token);
- assertEquals("value", token.getCredentials());
+ assertThat(token).isNotNull();
+ assertThat(token.getPrincipal()).isNull();
+ assertThat(token.getCredentials()).isEqualTo("value");
}
- /**
- * Method description
- *
- */
@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);
- when(c.getName()).thenReturn("other-cookie");
- when(request.getCookies()).thenReturn(new Cookie[] { c });
- assertNull(tokenGenerator.createToken(request));
+ when(c.getName()).thenReturn(name);
+ lenient().when(c.getValue()).thenReturn(value);
+ when(request.getCookies()).thenReturn(new Cookie[]{c});
}
- /**
- * Method description
- *
- */
@Test
- public void testCreateTokenWithoutCookies()
- {
- assertNull(tokenGenerator.createToken(request));
+ void shouldNotCreateTokenForWrongCookie() {
+ assignCookie("other-cookie", "with-some-value");
+
+ BearerToken token = tokenGenerator.createToken(request);
+ assertThat(token).isNull();
}
- //~--- fields ---------------------------------------------------------------
-
- /** Field description */
- private final CookieBearerWebTokenGenerator tokenGenerator =
- new CookieBearerWebTokenGenerator();
-
- /** Field description */
- @Mock
- private HttpServletRequest request;
+ @Test
+ void shouldNotCreateTokenWithoutCookies() {
+ BearerToken token = tokenGenerator.createToken(request);
+ assertThat(token).isNull();
+ }
}
diff --git a/scm-webapp/src/test/java/sonia/scm/web/security/DefaultAdministrationContextTest.java b/scm-webapp/src/test/java/sonia/scm/web/security/DefaultAdministrationContextTest.java
new file mode 100644
index 0000000000..7771559809
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/web/security/DefaultAdministrationContextTest.java
@@ -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();
+ }
+ }
+
+}