mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 07:25:44 +01:00
Merged in feature/changes-for-cas-plugin (pull request #135)
Changes for cas plugin
This commit is contained in:
@@ -80,8 +80,20 @@ public interface AccessToken {
|
|||||||
*/
|
*/
|
||||||
Date getExpiration();
|
Date getExpiration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns refresh expiration of token.
|
||||||
|
*
|
||||||
|
* @return refresh expiration
|
||||||
|
*/
|
||||||
Optional<Date> getRefreshExpiration();
|
Optional<Date> getRefreshExpiration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns id of the parent key.
|
||||||
|
*
|
||||||
|
* @return parent key id
|
||||||
|
*/
|
||||||
|
Optional<String> getParentKey();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the scope of the token. The scope is able to reduce the permissions of the subject in the context of this
|
* Returns the scope of the token. The scope is able to reduce the permissions of the subject in the context of this
|
||||||
* token. For example we could issue a token which can only be used to read a single repository. for more informations
|
* token. For example we could issue a token which can only be used to read a single repository. for more informations
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates cookies and invalidates access token cookies.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public interface AccessTokenCookieIssuer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a cookie for token authentication and attaches it to the response.
|
||||||
|
*
|
||||||
|
* @param request http servlet request
|
||||||
|
* @param response http servlet response
|
||||||
|
* @param accessToken access token
|
||||||
|
*/
|
||||||
|
void authenticate(HttpServletRequest request, HttpServletResponse response, AccessToken accessToken);
|
||||||
|
/**
|
||||||
|
* Invalidates the authentication cookie.
|
||||||
|
*
|
||||||
|
* @param request http servlet request
|
||||||
|
* @param response http servlet response
|
||||||
|
*/
|
||||||
|
void invalidate(HttpServletRequest request, HttpServletResponse response);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -164,7 +164,7 @@ public class DefaultCipherHandler implements CipherHandler {
|
|||||||
String result = null;
|
String result = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] encodedInput = Base64.getDecoder().decode(value);
|
byte[] encodedInput = Base64.getUrlDecoder().decode(value);
|
||||||
byte[] salt = new byte[SALT_LENGTH];
|
byte[] salt = new byte[SALT_LENGTH];
|
||||||
byte[] encoded = new byte[encodedInput.length - SALT_LENGTH];
|
byte[] encoded = new byte[encodedInput.length - SALT_LENGTH];
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ public class DefaultCipherHandler implements CipherHandler {
|
|||||||
System.arraycopy(salt, 0, result, 0, SALT_LENGTH);
|
System.arraycopy(salt, 0, result, 0, SALT_LENGTH);
|
||||||
System.arraycopy(encodedInput, 0, result, SALT_LENGTH,
|
System.arraycopy(encodedInput, 0, result, SALT_LENGTH,
|
||||||
result.length - SALT_LENGTH);
|
result.length - SALT_LENGTH);
|
||||||
res = new String(Base64.getEncoder().encode(result), ENCODING);
|
res = new String(Base64.getUrlEncoder().encode(result), ENCODING);
|
||||||
} catch (IOException | GeneralSecurityException ex) {
|
} catch (IOException | GeneralSecurityException ex) {
|
||||||
throw new CipherException("could not encode string", ex);
|
throw new CipherException("could not encode string", ex);
|
||||||
}
|
}
|
||||||
|
|||||||
25
scm-core/src/main/java/sonia/scm/xml/XmlInstantAdapter.java
Normal file
25
scm-core/src/main/java/sonia/scm/xml/XmlInstantAdapter.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package sonia.scm.xml;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.adapters.XmlAdapter;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.temporal.TemporalAccessor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JAXB adapter for {@link Instant} objects.
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public class XmlInstantAdapter extends XmlAdapter<String, Instant> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String marshal(Instant instant) {
|
||||||
|
return DateTimeFormatter.ISO_INSTANT.format(instant);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant unmarshal(String text) {
|
||||||
|
TemporalAccessor parsed = DateTimeFormatter.ISO_INSTANT.parse(text);
|
||||||
|
return Instant.from(parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package sonia.scm.xml;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junitpioneer.jupiter.TempDirectory;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXB;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@ExtendWith(TempDirectory.class)
|
||||||
|
class XmlInstantAdapterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMarshalAndUnmarshalInstant(@TempDirectory.TempDir Path tempDirectory) {
|
||||||
|
Path path = tempDirectory.resolve("instant.xml");
|
||||||
|
|
||||||
|
Instant instant = Instant.now();
|
||||||
|
InstantObject object = new InstantObject(instant);
|
||||||
|
JAXB.marshal(object, path.toFile());
|
||||||
|
|
||||||
|
InstantObject unmarshaled = JAXB.unmarshal(path.toFile(), InstantObject.class);
|
||||||
|
assertEquals(instant, unmarshaled.instant);
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlRootElement(name = "instant-object")
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public static class InstantObject {
|
||||||
|
|
||||||
|
@XmlJavaTypeAdapter(XmlInstantAdapter.class)
|
||||||
|
private Instant instant;
|
||||||
|
|
||||||
|
public InstantObject() {
|
||||||
|
}
|
||||||
|
|
||||||
|
InstantObject(Instant instant) {
|
||||||
|
this.instant = instant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -42,8 +42,20 @@ package sonia.scm.store;
|
|||||||
*/
|
*/
|
||||||
public class InMemoryConfigurationStoreFactory implements ConfigurationStoreFactory {
|
public class InMemoryConfigurationStoreFactory implements ConfigurationStoreFactory {
|
||||||
|
|
||||||
|
private ConfigurationStore store;
|
||||||
|
|
||||||
|
public InMemoryConfigurationStoreFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public InMemoryConfigurationStoreFactory(ConfigurationStore store) {
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ConfigurationStore getStore(TypedStoreParameters storeParameters) {
|
public ConfigurationStore getStore(TypedStoreParameters storeParameters) {
|
||||||
|
if (store != null) {
|
||||||
|
return store;
|
||||||
|
}
|
||||||
return new InMemoryConfigurationStore<>();
|
return new InMemoryConfigurationStore<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package sonia.scm.store;
|
||||||
|
|
||||||
|
import sonia.scm.security.KeyGenerator;
|
||||||
|
import sonia.scm.security.UUIDKeyGenerator;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In memory store implementation of {@link DataStore}.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
*
|
||||||
|
* @param <T> type of stored object
|
||||||
|
*/
|
||||||
|
public class InMemoryDataStore<T> implements DataStore<T> {
|
||||||
|
|
||||||
|
private final Map<String, T> store = new HashMap<>();
|
||||||
|
private KeyGenerator generator = new UUIDKeyGenerator();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String put(T item) {
|
||||||
|
String key = generator.createKey();
|
||||||
|
store.put(key, item);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(String id, T item) {
|
||||||
|
store.put(id, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, T> getAll() {
|
||||||
|
return Collections.unmodifiableMap(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
store.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String id) {
|
||||||
|
store.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get(String id) {
|
||||||
|
return store.get(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package sonia.scm.store;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In memory configuration store factory for testing purposes.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
*/
|
||||||
|
public class InMemoryDataStoreFactory implements DataStoreFactory {
|
||||||
|
|
||||||
|
private InMemoryDataStore store;
|
||||||
|
|
||||||
|
public InMemoryDataStoreFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public InMemoryDataStoreFactory(InMemoryDataStore store) {
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> DataStore<T> getStore(TypedStoreParameters<T> storeParameters) {
|
||||||
|
if (store != null) {
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
return new InMemoryDataStore<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,14 +79,14 @@ import sonia.scm.repository.spi.HookEventFacade;
|
|||||||
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||||
import sonia.scm.schedule.QuartzScheduler;
|
import sonia.scm.schedule.QuartzScheduler;
|
||||||
import sonia.scm.schedule.Scheduler;
|
import sonia.scm.schedule.Scheduler;
|
||||||
|
import sonia.scm.security.AccessTokenCookieIssuer;
|
||||||
import sonia.scm.security.AuthorizationChangedEventProducer;
|
import sonia.scm.security.AuthorizationChangedEventProducer;
|
||||||
import sonia.scm.security.CipherHandler;
|
import sonia.scm.security.CipherHandler;
|
||||||
import sonia.scm.security.CipherUtil;
|
import sonia.scm.security.CipherUtil;
|
||||||
import sonia.scm.security.ConfigurableLoginAttemptHandler;
|
import sonia.scm.security.ConfigurableLoginAttemptHandler;
|
||||||
import sonia.scm.security.DefaultJwtAccessTokenRefreshStrategy;
|
import sonia.scm.security.DefaultAccessTokenCookieIssuer;
|
||||||
import sonia.scm.security.DefaultKeyGenerator;
|
import sonia.scm.security.DefaultKeyGenerator;
|
||||||
import sonia.scm.security.DefaultSecuritySystem;
|
import sonia.scm.security.DefaultSecuritySystem;
|
||||||
import sonia.scm.security.JwtAccessTokenRefreshStrategy;
|
|
||||||
import sonia.scm.security.KeyGenerator;
|
import sonia.scm.security.KeyGenerator;
|
||||||
import sonia.scm.security.LoginAttemptHandler;
|
import sonia.scm.security.LoginAttemptHandler;
|
||||||
import sonia.scm.security.SecuritySystem;
|
import sonia.scm.security.SecuritySystem;
|
||||||
@@ -320,6 +320,7 @@ public class ScmServletModule extends ServletModule
|
|||||||
// bind events
|
// bind events
|
||||||
// bind(LastModifiedUpdateListener.class);
|
// bind(LastModifiedUpdateListener.class);
|
||||||
|
|
||||||
|
bind(AccessTokenCookieIssuer.class).to(DefaultAccessTokenCookieIssuer.class);
|
||||||
bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class);
|
bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public final class AccessTokenCookieIssuer {
|
public final class DefaultAccessTokenCookieIssuer implements AccessTokenCookieIssuer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the logger for AccessTokenCookieIssuer
|
* the logger for DefaultAccessTokenCookieIssuer
|
||||||
*/
|
*/
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AccessTokenCookieIssuer.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DefaultAccessTokenCookieIssuer.class);
|
||||||
|
|
||||||
private final ScmConfiguration configuration;
|
private final ScmConfiguration configuration;
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ public final class AccessTokenCookieIssuer {
|
|||||||
* @param configuration scm main configuration
|
* @param configuration scm main configuration
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public AccessTokenCookieIssuer(ScmConfiguration configuration) {
|
public DefaultAccessTokenCookieIssuer(ScmConfiguration configuration) {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +87,7 @@ public final class JwtAccessToken implements AccessToken {
|
|||||||
return ofNullable(claims.get(REFRESHABLE_UNTIL_CLAIM_KEY, Date.class));
|
return ofNullable(claims.get(REFRESHABLE_UNTIL_CLAIM_KEY, Date.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Optional<String> getParentKey() {
|
public Optional<String> getParentKey() {
|
||||||
return ofNullable(claims.get(PARENT_TOKEN_ID_CLAIM_KEY).toString());
|
return ofNullable(claims.get(PARENT_TOKEN_ID_CLAIM_KEY).toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import sonia.scm.security.AccessToken;
|
|||||||
import sonia.scm.security.AccessTokenBuilder;
|
import sonia.scm.security.AccessTokenBuilder;
|
||||||
import sonia.scm.security.AccessTokenBuilderFactory;
|
import sonia.scm.security.AccessTokenBuilderFactory;
|
||||||
import sonia.scm.security.AccessTokenCookieIssuer;
|
import sonia.scm.security.AccessTokenCookieIssuer;
|
||||||
|
import sonia.scm.security.DefaultAccessTokenCookieIssuer;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
@@ -46,7 +47,7 @@ public class AuthenticationResourceTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private AccessTokenBuilder accessTokenBuilder;
|
private AccessTokenBuilder accessTokenBuilder;
|
||||||
|
|
||||||
private AccessTokenCookieIssuer cookieIssuer = new AccessTokenCookieIssuer(mock(ScmConfiguration.class));
|
private AccessTokenCookieIssuer cookieIssuer = new DefaultAccessTokenCookieIssuer(mock(ScmConfiguration.class));
|
||||||
|
|
||||||
private static final String AUTH_JSON_TRILLIAN = "{\n" +
|
private static final String AUTH_JSON_TRILLIAN = "{\n" +
|
||||||
"\t\"cookie\": true,\n" +
|
"\t\"cookie\": true,\n" +
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ import static org.mockito.Mockito.verify;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class AccessTokenCookieIssuerTest {
|
public class DefaultAccessTokenCookieIssuerTest {
|
||||||
|
|
||||||
private ScmConfiguration configuration;
|
private ScmConfiguration configuration;
|
||||||
|
|
||||||
private AccessTokenCookieIssuer issuer;
|
private DefaultAccessTokenCookieIssuer issuer;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private HttpServletRequest request;
|
private HttpServletRequest request;
|
||||||
@@ -41,7 +41,7 @@ public class AccessTokenCookieIssuerTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
configuration = new ScmConfiguration();
|
configuration = new ScmConfiguration();
|
||||||
issuer = new AccessTokenCookieIssuer(configuration);
|
issuer = new DefaultAccessTokenCookieIssuer(configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
Reference in New Issue
Block a user