mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-04 20:45:52 +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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> 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<String> 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user