Skip to content

Commit

Permalink
feat: update codebase to use Config Injection (#491)
Browse files Browse the repository at this point in the history
  • Loading branch information
paullatzelsperger authored Nov 18, 2024
1 parent cdb8379 commit 892b4a4
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,11 @@ public class DefaultServicesExtension implements ServiceExtension {

public static final String NAME = "IdentityHub Default Services Extension";
public static final long DEFAULT_REVOCATION_CACHE_VALIDITY_MILLIS = 15 * 60 * 1000L;
@Setting(value = "Validity period of cached StatusList2021 credential entries in milliseconds.", defaultValue = DEFAULT_REVOCATION_CACHE_VALIDITY_MILLIS + "", type = "long")
public static final String REVOCATION_CACHE_VALIDITY = "edc.iam.credential.revocation.cache.validity";

@Setting(value = "Activates the JTI check: access tokens can only be used once to guard against replay attacks", defaultValue = "false", type = "boolean")
public static final String ACCESSTOKEN_JTI_VALIDATION_ACTIVATE = "edc.iam.accesstoken.jti.validation";

static final String ACCESSTOKEN_JTI_VALIDATION_ACTIVATE = "edc.iam.accesstoken.jti.validation";
@Setting(description = "Activates the JTI check: access tokens can only be used once to guard against replay attacks", defaultValue = "false", key = ACCESSTOKEN_JTI_VALIDATION_ACTIVATE)
private boolean activateJtiCheck;
@Setting(description = "Validity period of cached StatusList2021 credential entries in milliseconds.", defaultValue = DEFAULT_REVOCATION_CACHE_VALIDITY_MILLIS + "", key = "edc.iam.credential.revocation.cache.validity")
private long revocationCacheValidity;
@Inject
private TokenValidationRulesRegistry registry;
@Inject
Expand All @@ -86,7 +85,7 @@ public void initialize(ServiceExtensionContext context) {
var scopeIsPresentRule = new ClaimIsPresentRule(ACCESS_TOKEN_SCOPE_CLAIM);
registry.addRule(DCP_ACCESS_TOKEN_CONTEXT, scopeIsPresentRule);

if (context.getSetting(ACCESSTOKEN_JTI_VALIDATION_ACTIVATE, false)) {
if (activateJtiCheck) {
registry.addRule(DCP_ACCESS_TOKEN_CONTEXT, new JtiValidationRule(jwtValidationStore, context.getMonitor()));
} else {
context.getMonitor().warning("JWT Token ID (\"jti\" claim) Validation is not active. Please consider setting '%s=true' for protection against replay attacks".formatted(ACCESSTOKEN_JTI_VALIDATION_ACTIVATE));
Expand All @@ -111,17 +110,16 @@ public KeyPairResourceStore createDefaultKeyPairResourceStore() {
@Provider(isDefault = true)
public ScopeToCriterionTransformer createScopeTransformer(ServiceExtensionContext context) {
context.getMonitor().warning("Using the default EdcScopeToCriterionTransformer. This is not intended for production use and should be replaced " +
"with a specialized implementation for your dataspace");
"with a specialized implementation for your dataspace");
return new EdcScopeToCriterionTransformer();
}

@Provider(isDefault = true)
public RevocationServiceRegistry createRevocationListService(ServiceExtensionContext context) {
if (revocationService == null) {
revocationService = new RevocationServiceRegistryImpl(context.getMonitor());
var validity = context.getConfig().getLong(REVOCATION_CACHE_VALIDITY, DEFAULT_REVOCATION_CACHE_VALIDITY_MILLIS);
revocationService.addService(StatusList2021Status.TYPE, new StatusList2021RevocationService(typeManager.getMapper(), validity));
revocationService.addService(BitstringStatusListStatus.TYPE, new BitstringStatusListRevocationService(typeManager.getMapper(), validity));
revocationService.addService(StatusList2021Status.TYPE, new StatusList2021RevocationService(typeManager.getMapper(), revocationCacheValidity));
revocationService.addService(BitstringStatusListStatus.TYPE, new BitstringStatusListRevocationService(typeManager.getMapper(), revocationCacheValidity));
}
return revocationService;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@

package org.eclipse.edc.identityhub;

import org.eclipse.edc.boot.system.injection.ObjectFactory;
import org.eclipse.edc.identityhub.accesstoken.rules.ClaimIsPresentRule;
import org.eclipse.edc.junit.extensions.DependencyInjectionExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.spi.system.configuration.ConfigFactory;
import org.eclipse.edc.token.spi.TokenValidationRulesRegistry;
import org.eclipse.edc.verifiablecredentials.jwt.rules.JtiValidationRule;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import java.util.Map;

import static org.eclipse.edc.identityhub.DefaultServicesExtension.ACCESSTOKEN_JTI_VALIDATION_ACTIVATE;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.mock;
Expand All @@ -50,10 +53,11 @@ void initialize_verifyTokenRules(DefaultServicesExtension extension, ServiceExte
}

@Test
void initialize_verifyTokenRules_withJtiRule(DefaultServicesExtension extension, ServiceExtensionContext context) {
when(context.getSetting(eq(ACCESSTOKEN_JTI_VALIDATION_ACTIVATE), anyBoolean()))
.thenReturn(true);
extension.initialize(context);
void initialize_verifyTokenRules_withJtiRule(ServiceExtensionContext context, ObjectFactory factory) {
when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of(ACCESSTOKEN_JTI_VALIDATION_ACTIVATE, Boolean.TRUE.toString())));


factory.constructInstance(DefaultServicesExtension.class).initialize(context);
verify(registry).addRule(eq("dcp-si"), isA(ClaimIsPresentRule.class));
verify(registry).addRule(eq("dcp-access-token"), isA(ClaimIsPresentRule.class));
verify(registry).addRule(eq("dcp-access-token"), isA(JtiValidationRule.class));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ class IdentityHubWithSts extends SmokeTest {
put("web.http.accounts.path", "/api/accounts");
put("web.http.version.port", valueOf(getFreePort()));
put("web.http.version.path", "/api/version");
put("web.http.sts.port", valueOf(getFreePort()));
put("web.http.sts.path", "/api/sts");
put("edc.api.accounts.key", "password");
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,25 @@
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static java.util.Optional.ofNullable;
import static org.eclipse.edc.identityhub.common.credentialwatchdog.CredentialWatchdogExtension.NAME;

@Extension(value = NAME)
public class CredentialWatchdogExtension implements ServiceExtension {
public static final String NAME = "VerifiableCredential Watchdog Extension";

public static final int DEFAULT_WATCHDOG_PERIOD = 60;
@Setting(value = "Period (in seconds) at which the Watchdog thread checks all stored credentials for their status. Configuring a number <=0 disables the Watchdog.",
type = "integer", min = 0, defaultValue = DEFAULT_WATCHDOG_PERIOD + "")
public static final String WATCHDOG_PERIOD_PROPERTY = "edc.iam.credential.status.check.period";

public static final int DEFAULT_WATCHDOG_INITIAL_DELAY = 5;
@Setting(value = "Initial delay (in seconds) before the Watchdog thread begins its work.",
type = "integer", min = 0, defaultValue = "random number [1.." + DEFAULT_WATCHDOG_INITIAL_DELAY + "]")
public static final String WATCHDOG_DELAY_PROPERTY = "edc.iam.credential.status.check.delay";
public static final String CREDENTIAL_WATCHDOG = "CredentialWatchdog";
private final SecureRandom random = new SecureRandom();

@Setting(description = "Period (in seconds) at which the Watchdog thread checks all stored credentials for their status. Configuring a number <=0 disables the Watchdog.",
min = 0, defaultValue = DEFAULT_WATCHDOG_PERIOD + "", key = "edc.iam.credential.status.check.period")
private int watchdogPeriod;
@Setting(description = "Initial delay (in seconds) before the Watchdog thread begins its work.",
min = 0, key = "edc.iam.credential.status.check.delay", required = false)
private Integer initialDelay;

@Inject
private ExecutorInstrumentation executorInstrumentation;
@Inject
Expand All @@ -56,9 +58,7 @@ public class CredentialWatchdogExtension implements ServiceExtension {
@Inject
private TransactionContext transactionContext;
private ScheduledExecutorService scheduledExecutorService;
private Integer watchdogPeriod;
private Monitor monitor;
private int initialDelay;

@Override
public String name() {
Expand All @@ -67,15 +67,14 @@ public String name() {

@Override
public void initialize(ServiceExtensionContext context) {
watchdogPeriod = context.getSetting(WATCHDOG_PERIOD_PROPERTY, DEFAULT_WATCHDOG_PERIOD);
monitor = context.getMonitor().withPrefix(CREDENTIAL_WATCHDOG);

if (watchdogPeriod <= 0) {
monitor.debug(() -> "Config property '%s' was <= 0 (%d). The Credential Watchdog is disabled.".formatted(WATCHDOG_PERIOD_PROPERTY, watchdogPeriod));
} else {
initialDelay = context.getSetting(WATCHDOG_DELAY_PROPERTY, randomDelay());
if (watchdogPeriod > 0) {
initialDelay = ofNullable(initialDelay).orElseGet((this::randomDelay));
monitor.debug(() -> "Credential watchdog will run with a delay of %d seconds, at an interval of %d seconds".formatted(initialDelay, watchdogPeriod));
scheduledExecutorService = executorInstrumentation.instrument(Executors.newSingleThreadScheduledExecutor(), CREDENTIAL_WATCHDOG);
} else {
monitor.debug(() -> "The Credential Watchdog is disabled.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
package org.eclipse.edc.identityhub.common.credentialwatchdog;


import org.eclipse.edc.boot.system.injection.ObjectFactory;
import org.eclipse.edc.junit.extensions.DependencyInjectionExtension;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.system.ExecutorInstrumentation;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.spi.system.configuration.ConfigFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -27,15 +29,13 @@
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentMatchers;

import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import static org.eclipse.edc.identityhub.common.credentialwatchdog.CredentialWatchdogExtension.CREDENTIAL_WATCHDOG;
import static org.eclipse.edc.identityhub.common.credentialwatchdog.CredentialWatchdogExtension.WATCHDOG_DELAY_PROPERTY;
import static org.eclipse.edc.identityhub.common.credentialwatchdog.CredentialWatchdogExtension.WATCHDOG_PERIOD_PROPERTY;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.mock;
Expand All @@ -48,6 +48,8 @@
class CredentialWatchdogExtensionTest {


private static final String WATCHDOG_PERIOD_PROPERTY = "edc.iam.credential.status.check.period";
private static final String WATCHDOG_DELAY_PROPERTY = "edc.iam.credential.status.check.delay";
private final ExecutorInstrumentation executorInstrumentationMock = mock();
private Monitor monitor;

Expand All @@ -62,12 +64,12 @@ void setUp(ServiceExtensionContext context) {
@DisplayName("Disable watchdog on negative or zero second period")
@ParameterizedTest(name = "Disable on delay of {0} seconds")
@ValueSource(ints = { 0, -1, -100 })
void initialize_whenNegativePeriod_shouldDisable(int period, CredentialWatchdogExtension extension, ServiceExtensionContext context) {
when(context.getSetting(eq(WATCHDOG_PERIOD_PROPERTY), anyInt())).thenReturn(period);
extension.initialize(context);
void initialize_whenNegativePeriod_shouldDisable(int period, ServiceExtensionContext context, ObjectFactory objectFactory) {
when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of(WATCHDOG_PERIOD_PROPERTY, String.valueOf(period))));
objectFactory.constructInstance(CredentialWatchdogExtension.class).initialize(context);

verifyNoInteractions(executorInstrumentationMock);
verify(monitor).debug(ArgumentMatchers.<Supplier<String>>argThat(stringSupplier -> stringSupplier.get().contains("Credential Watchdog is disabled")));
verifyNoInteractions(executorInstrumentationMock);
}

@DisplayName("Verify random delay [1..5] if no initial delay is configured")
Expand All @@ -82,20 +84,24 @@ void initialize_whenNoDelay_shouldUseRandomBetweenZeroAndFive(CredentialWatchdog

@DisplayName("Verify a configured delay is used")
@Test
void initialize_whenDelay_shouldUseConfiguredDelay(CredentialWatchdogExtension extension, ServiceExtensionContext context) {
when(context.getSetting(eq(WATCHDOG_DELAY_PROPERTY), anyInt())).thenReturn(42);
void initialize_whenDelay_shouldUseConfiguredDelay(ServiceExtensionContext context, ObjectFactory factory) {
when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of(
WATCHDOG_DELAY_PROPERTY, String.valueOf(42)
)));
factory.constructInstance(CredentialWatchdogExtension.class).initialize(context);

extension.initialize(context);
verify(monitor).debug(ArgumentMatchers.<Supplier<String>>argThat(stringSupplier ->
stringSupplier.get().endsWith("delay of 42 seconds, at an interval of 60 seconds")));
verify(executorInstrumentationMock).instrument(any(), eq(CREDENTIAL_WATCHDOG));
}

@DisplayName("Verify the watchdog is not start if a <=0 period is configured")
@Test
void start_whenWatchdogDisabled_shouldNotStart(CredentialWatchdogExtension extension, ServiceExtensionContext context) {
when(context.getSetting(eq(WATCHDOG_PERIOD_PROPERTY), anyInt())).thenReturn(-10);

void start_whenWatchdogDisabled_shouldNotStart(ServiceExtensionContext context, ObjectFactory factory) {
when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of(
WATCHDOG_PERIOD_PROPERTY, String.valueOf(-10)
)));
var extension = factory.constructInstance(CredentialWatchdogExtension.class);
extension.initialize(context);
extension.start();
verifyNoInteractions(executorInstrumentationMock);
Expand All @@ -105,13 +111,16 @@ void start_whenWatchdogDisabled_shouldNotStart(CredentialWatchdogExtension exten

@DisplayName("Verify watchdog starts when properly configured, at the expected timing intervals")
@Test
void start_shouldStartWatchdog(CredentialWatchdogExtension extension, ServiceExtensionContext context) {
when(context.getSetting(eq(WATCHDOG_PERIOD_PROPERTY), anyInt())).thenReturn(1);
when(context.getSetting(eq(WATCHDOG_DELAY_PROPERTY), anyInt())).thenReturn(1);
void start_shouldStartWatchdog(ServiceExtensionContext context, ObjectFactory factory) {
when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of(
WATCHDOG_PERIOD_PROPERTY, String.valueOf(1),
WATCHDOG_DELAY_PROPERTY, String.valueOf(1)
)));
var executorMock = mock(ScheduledExecutorService.class);
when(executorInstrumentationMock.instrument(any(), eq(CREDENTIAL_WATCHDOG)))
.thenReturn(executorMock);
when(executorMock.isShutdown()).thenReturn(false);
var extension = factory.constructInstance(CredentialWatchdogExtension.class);
extension.initialize(context);
extension.start();

Expand All @@ -122,11 +131,12 @@ void start_shouldStartWatchdog(CredentialWatchdogExtension extension, ServiceExt

@DisplayName("Verify shutting down the extension is a NOOP if the watchdog is not started")
@Test
void shutdown_whenNotRunning_shouldNoop(CredentialWatchdogExtension extension, ServiceExtensionContext context) {
when(context.getSetting(eq(WATCHDOG_PERIOD_PROPERTY), anyInt())).thenReturn(-1); // executor will not be initialized
when(context.getSetting(eq(WATCHDOG_DELAY_PROPERTY), anyInt())).thenReturn(1);
var executorMock = mock(ScheduledExecutorService.class);
void shutdown_whenNotRunning_shouldNoop(ServiceExtensionContext context, ObjectFactory factory) {
when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of(
WATCHDOG_PERIOD_PROPERTY, String.valueOf(-1),
WATCHDOG_DELAY_PROPERTY, String.valueOf(1))));

var extension = factory.constructInstance(CredentialWatchdogExtension.class);
extension.initialize(context);

extension.shutdown();
Expand All @@ -136,9 +146,12 @@ void shutdown_whenNotRunning_shouldNoop(CredentialWatchdogExtension extension, S

@DisplayName("Verify shutting down the extension stops the watchdog thread")
@Test
void shutdown_whenRunning_shouldStop(CredentialWatchdogExtension extension, ServiceExtensionContext context) {
when(context.getSetting(eq(WATCHDOG_PERIOD_PROPERTY), anyInt())).thenReturn(1);
when(context.getSetting(eq(WATCHDOG_DELAY_PROPERTY), anyInt())).thenReturn(1);
void shutdown_whenRunning_shouldStop(ServiceExtensionContext context, ObjectFactory factory) {
when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of(
WATCHDOG_PERIOD_PROPERTY, String.valueOf(1),
WATCHDOG_DELAY_PROPERTY, String.valueOf(1))));

var extension = factory.constructInstance(CredentialWatchdogExtension.class);
var executorMock = mock(ScheduledExecutorService.class);

when(executorInstrumentationMock.instrument(any(), eq(CREDENTIAL_WATCHDOG))).thenReturn(executorMock);
Expand Down
Loading

0 comments on commit 892b4a4

Please sign in to comment.