mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 08:55:44 +01:00
X-SCM-Session-ID and X-SCM-Client could now be send via query parameter
The use of query parameters is required for SSE, because the standard does not support header. This works currently only for GET request to avoid parsing of request body.
This commit is contained in:
@@ -96,17 +96,13 @@ public final class BearerToken implements AuthenticationToken {
|
|||||||
/**
|
/**
|
||||||
* Creates a new {@link BearerToken} from raw string representation for the given ui session id.
|
* Creates a new {@link BearerToken} from raw string representation for the given ui session id.
|
||||||
*
|
*
|
||||||
* @param sessionId session id of the client
|
* @param session session id of the client
|
||||||
* @param rawToken bearer token string representation
|
* @param rawToken bearer token string representation
|
||||||
*
|
*
|
||||||
* @return new bearer token
|
* @return new bearer token
|
||||||
*/
|
*/
|
||||||
public static BearerToken create(@Nullable String sessionId, String rawToken) {
|
public static BearerToken create(@Nullable SessionId session, String rawToken) {
|
||||||
Preconditions.checkArgument(!Strings.isNullOrEmpty(rawToken), "raw token is required");
|
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);
|
return new BearerToken(session, rawToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
package sonia.scm.security;
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import sonia.scm.util.HttpUtil;
|
||||||
|
|
||||||
import java.util.Objects;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client side session id.
|
* Client side session id.
|
||||||
*/
|
*/
|
||||||
public final class SessionId {
|
@EqualsAndHashCode
|
||||||
|
public final class SessionId implements Serializable {
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public static final String PARAMETER = "X-SCM-Session-ID";
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
|
|
||||||
@@ -16,24 +25,15 @@ public final class SessionId {
|
|||||||
this.value = 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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Optional<SessionId> from(HttpServletRequest request) {
|
||||||
|
return HttpUtil.getHeaderOrGetParameter(request, PARAMETER).map(SessionId::valueOf);
|
||||||
|
}
|
||||||
|
|
||||||
public static SessionId valueOf(String value) {
|
public static SessionId valueOf(String value) {
|
||||||
Preconditions.checkArgument(!Strings.isNullOrEmpty(value), "session id could not be empty or null");
|
Preconditions.checkArgument(!Strings.isNullOrEmpty(value), "session id could not be empty or null");
|
||||||
return new SessionId(value);
|
return new SessionId(value);
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ package sonia.scm.util;
|
|||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.MoreObjects;
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -52,6 +51,7 @@ import java.net.URLDecoder;
|
|||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -116,12 +116,6 @@ 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";
|
||||||
|
|
||||||
@@ -830,6 +824,24 @@ public final class HttpUtil
|
|||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns header value or query parameter if the request is a get request.
|
||||||
|
*
|
||||||
|
* @param request http request
|
||||||
|
* @param parameter name of header/parameter
|
||||||
|
*
|
||||||
|
* @return header value or query parameter
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public static Optional<String> getHeaderOrGetParameter(HttpServletRequest request, String parameter) {
|
||||||
|
String value = request.getHeader(parameter);
|
||||||
|
if (Strings.isNullOrEmpty(value) && "GET".equalsIgnoreCase(request.getMethod())) {
|
||||||
|
value = request.getParameter(parameter);
|
||||||
|
}
|
||||||
|
return Optional.ofNullable(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the given uri without leading separator.
|
* Returns the given uri without leading separator.
|
||||||
*
|
*
|
||||||
@@ -882,16 +894,14 @@ public final class HttpUtil
|
|||||||
/**
|
/**
|
||||||
* Returns true if the http request is send by the scm-manager web interface.
|
* Returns true if the http request is send by the scm-manager web interface.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param request http request
|
* @param request http request
|
||||||
*
|
*
|
||||||
* @return true if the request comes from the web interface.
|
* @return true if the request comes from the web interface.
|
||||||
* @since 1.19
|
* @since 1.19
|
||||||
*/
|
*/
|
||||||
public static boolean isWUIRequest(HttpServletRequest request)
|
public static boolean isWUIRequest(HttpServletRequest request) {
|
||||||
{
|
Optional<String> client = getHeaderOrGetParameter(request, HEADER_SCM_CLIENT);
|
||||||
return SCM_CLIENT_WUI.equalsIgnoreCase(
|
return client.isPresent() && SCM_CLIENT_WUI.equalsIgnoreCase(client.get());
|
||||||
request.getHeader(HEADER_SCM_CLIENT));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|||||||
42
scm-core/src/test/java/sonia/scm/security/SessionIdTest.java
Normal file
42
scm-core/src/test/java/sonia/scm/security/SessionIdTest.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class SessionIdTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HttpServletRequest request;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnSessionIdFromHeader() {
|
||||||
|
when(request.getHeader(SessionId.PARAMETER)).thenReturn("abc42");
|
||||||
|
|
||||||
|
assertThat(SessionId.from(request)).contains(SessionId.valueOf("abc42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnSessionIdFromQueryParameter() {
|
||||||
|
when(request.getMethod()).thenReturn("GET");
|
||||||
|
when(request.getParameter(SessionId.PARAMETER)).thenReturn("abc42");
|
||||||
|
|
||||||
|
assertThat(SessionId.from(request)).contains(SessionId.valueOf("abc42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnSessionIdFromQueryParameterOnlyForGetRequest() {
|
||||||
|
when(request.getMethod()).thenReturn("POST");
|
||||||
|
lenient().when(request.getParameter(SessionId.PARAMETER)).thenReturn("abc42");
|
||||||
|
|
||||||
|
assertThat(SessionId.from(request)).isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,9 @@ package sonia.scm.util;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
|
import sonia.scm.security.SessionId;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
@@ -466,4 +468,47 @@ public class HttpUtilTest
|
|||||||
assertEquals("test/two/three",
|
assertEquals("test/two/three",
|
||||||
HttpUtil.getUriWithoutStartSeperator("test/two/three"));
|
HttpUtil.getUriWithoutStartSeperator("test/two/three"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetHeaderOrGetParameterWithHeader() {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.getHeader("Domain")).thenReturn("hitchhiker");
|
||||||
|
|
||||||
|
assertThat(HttpUtil.getHeaderOrGetParameter(request, "Domain")).contains("hitchhiker");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetHeaderOrGetParameterWithParameter() {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.getMethod()).thenReturn("GET");
|
||||||
|
when(request.getParameter("Domain")).thenReturn("hitchhiker");
|
||||||
|
|
||||||
|
assertThat(HttpUtil.getHeaderOrGetParameter(request, "Domain")).contains("hitchhiker");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetHeaderOrGetParameterOnPost() {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.getMethod()).thenReturn("POST");
|
||||||
|
lenient().when(request.getParameter("Domain")).thenReturn("hitchhiker");
|
||||||
|
|
||||||
|
assertThat(HttpUtil.getHeaderOrGetParameter(request, "Domain")).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsWUIRequestWithHeader() {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI);
|
||||||
|
|
||||||
|
assertThat(HttpUtil.isWUIRequest(request)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsWUIRequestWithParameter() {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.getMethod()).thenReturn("GET");
|
||||||
|
when(request.getParameter(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI);
|
||||||
|
|
||||||
|
assertThat(HttpUtil.isWUIRequest(request)).isTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ package sonia.scm.web;
|
|||||||
|
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
import sonia.scm.security.BearerToken;
|
import sonia.scm.security.BearerToken;
|
||||||
|
import sonia.scm.security.SessionId;
|
||||||
import sonia.scm.util.HttpUtil;
|
import sonia.scm.util.HttpUtil;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
@@ -68,10 +69,8 @@ public class BearerWebTokenGenerator extends SchemeBasedWebTokenGenerator
|
|||||||
{
|
{
|
||||||
BearerToken token = null;
|
BearerToken token = null;
|
||||||
|
|
||||||
if (HttpUtil.AUTHORIZATION_SCHEME_BEARER.equalsIgnoreCase(scheme))
|
if (HttpUtil.AUTHORIZATION_SCHEME_BEARER.equalsIgnoreCase(scheme)) {
|
||||||
{
|
token = BearerToken.create(SessionId.from(request).orElse(null), authorization);
|
||||||
String sessionId = request.getHeader(HttpUtil.HEADER_SCM_SESSION);
|
|
||||||
token = BearerToken.create(sessionId, authorization);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ import sonia.scm.security.BearerToken;
|
|||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
import javax.servlet.http.Cookie;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import sonia.scm.security.SessionId;
|
||||||
import sonia.scm.util.HttpUtil;
|
import sonia.scm.util.HttpUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,19 +64,14 @@ public class CookieBearerWebTokenGenerator implements WebTokenGenerator
|
|||||||
* @return {@link BearerToken} or {@code null}
|
* @return {@link BearerToken} or {@code null}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public BearerToken createToken(HttpServletRequest request)
|
public BearerToken createToken(HttpServletRequest request) {
|
||||||
{
|
|
||||||
BearerToken token = null;
|
BearerToken token = null;
|
||||||
Cookie[] cookies = request.getCookies();
|
Cookie[] cookies = request.getCookies();
|
||||||
|
|
||||||
if (cookies != null)
|
if (cookies != null) {
|
||||||
{
|
for (Cookie cookie : cookies) {
|
||||||
for (Cookie cookie : cookies)
|
if (HttpUtil.COOKIE_BEARER_AUTHENTICATION.equals(cookie.getName())) {
|
||||||
{
|
token = BearerToken.create(SessionId.from(request).orElse(null), cookie.getValue());
|
||||||
if (HttpUtil.COOKIE_BEARER_AUTHENTICATION.equals(cookie.getName()))
|
|
||||||
{
|
|
||||||
String sessionId = HttpUtil.getHeader(request, HttpUtil.HEADER_SCM_SESSION, null);
|
|
||||||
token = BearerToken.create(sessionId, cookie.getValue());
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
|
|
||||||
package sonia.scm.security;
|
package sonia.scm.security;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import org.apache.shiro.authc.AuthenticationInfo;
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
import org.apache.shiro.authc.UsernamePasswordToken;
|
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@@ -42,7 +41,6 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
@@ -84,7 +82,7 @@ class BearerRealmTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldDoGetAuthentication() {
|
void shouldDoGetAuthentication() {
|
||||||
BearerToken bearerToken = BearerToken.create("__session__", "__bearer__");
|
BearerToken bearerToken = BearerToken.create(SessionId.valueOf("__session__"), "__bearer__");
|
||||||
AccessToken accessToken = mock(AccessToken.class);
|
AccessToken accessToken = mock(AccessToken.class);
|
||||||
|
|
||||||
when(accessToken.getSubject()).thenReturn("trillian");
|
when(accessToken.getSubject()).thenReturn("trillian");
|
||||||
|
|||||||
@@ -31,20 +31,19 @@
|
|||||||
|
|
||||||
package sonia.scm.web;
|
package sonia.scm.web;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import org.apache.shiro.authc.AuthenticationToken;
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
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 org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import sonia.scm.security.BearerToken;
|
import sonia.scm.security.BearerToken;
|
||||||
import sonia.scm.security.SessionId;
|
import sonia.scm.security.SessionId;
|
||||||
import sonia.scm.util.HttpUtil;
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -90,7 +89,7 @@ class BearerWebTokenGeneratorTest {
|
|||||||
@Test
|
@Test
|
||||||
void shouldCreateTokenWithSessionId(){
|
void shouldCreateTokenWithSessionId(){
|
||||||
doReturn("Bearer asd").when(request).getHeader("Authorization");
|
doReturn("Bearer asd").when(request).getHeader("Authorization");
|
||||||
doReturn("bcd123").when(request).getHeader(HttpUtil.HEADER_SCM_SESSION);
|
doReturn("bcd123").when(request).getHeader(SessionId.PARAMETER);
|
||||||
|
|
||||||
AuthenticationToken token = tokenGenerator.createToken(request);
|
AuthenticationToken token = tokenGenerator.createToken(request);
|
||||||
assertThat(token)
|
assertThat(token)
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class CookieBearerWebTokenGeneratorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCreateTokenWithSessionId() {
|
void shouldCreateTokenWithSessionId() {
|
||||||
when(request.getHeader(HttpUtil.HEADER_SCM_SESSION)).thenReturn("abc123");
|
when(request.getHeader(SessionId.PARAMETER)).thenReturn("abc123");
|
||||||
|
|
||||||
assignBearerCookie("authc");
|
assignBearerCookie("authc");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user