Merged in feature/changes-for-cas-plugin (pull request #135)

Changes for cas plugin
This commit is contained in:
René Pfeuffer
2018-12-18 14:57:00 +00:00
13 changed files with 220 additions and 12 deletions

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
} }

View 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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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<>();
} }
} }

View File

@@ -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);
}
}

View File

@@ -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<>();
}
}

View File

@@ -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);
} }

View File

@@ -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;
} }

View File

@@ -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());
} }

View File

@@ -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" +

View File

@@ -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