diff --git a/docs/script-catalog/update_token/update-token.md b/docs/script-catalog/update_token/update-token.md index 3edf645bcd5..3a798092702 100644 --- a/docs/script-catalog/update_token/update-token.md +++ b/docs/script-catalog/update_token/update-token.md @@ -104,7 +104,7 @@ Jans AS->>RP: return token(s) (Access token, ID token or Refresh Token) reflecti ## Associate an Update Token script to a client (RP) [optional step] -📝 Note: If the Update token script is not associated with a client, then it will be applicable to all clients registered in the Jans Server. Which implies that all tokens obtained using the Jans server will reflect modifications as per the script. +📝 Note: If the Update token script is not associated with a client, then it will not be executed. To run all scripts globally (independent from what is assigned to client) please set global AS configuration property `runAllUpdateTokenScripts` is set to `true`. Which implies that all tokens obtained using the Jans server will reflect modifications as per the script.
To Associate an Update Token script to a client (RP), execute the command below with appropriate values for: - inum of the client diff --git a/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/AppConfiguration.java b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/AppConfiguration.java index 8bffdc441d1..e66b95a4f58 100644 --- a/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/AppConfiguration.java +++ b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/AppConfiguration.java @@ -571,6 +571,9 @@ public class AppConfiguration implements Configuration { @DocProperty(description = "Boolean value specifying whether to disable prompt=consent", defaultValue = "false") private Boolean disablePromptConsent = false; + @DocProperty(description = "Boolean value specifying whether to run all Update Token scripts", defaultValue = "false") + private Boolean runAllUpdateTokenScripts = false; + @DocProperty(description = "The lifetime of Logout Status JWT. If not set falls back to 1 day", defaultValue = "86400") private Integer logoutStatusJwtLifetime = DEFAULT_LOGOUT_STATUS_JWT_LIFETIME; @@ -1442,6 +1445,15 @@ public void setDisablePromptConsent(Boolean disablePromptConsent) { this.disablePromptConsent = disablePromptConsent; } + public Boolean getRunAllUpdateTokenScripts() { + if (runAllUpdateTokenScripts == null) runAllUpdateTokenScripts = false; + return runAllUpdateTokenScripts; + } + + public void setRunAllUpdateTokenScripts(Boolean runAllUpdateTokenScripts) { + this.runAllUpdateTokenScripts = runAllUpdateTokenScripts; + } + public Boolean getIncludeSidInResponse() { if (includeSidInResponse == null) includeSidInResponse = false; return includeSidInResponse; diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalUpdateTokenService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalUpdateTokenService.java index 685dfc2ada3..76944c71a06 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalUpdateTokenService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalUpdateTokenService.java @@ -7,6 +7,7 @@ package io.jans.as.server.service.external; import com.google.common.collect.Lists; +import io.jans.as.model.configuration.AppConfiguration; import io.jans.as.model.token.JsonWebResponse; import io.jans.as.server.model.common.AccessToken; import io.jans.as.server.model.common.RefreshToken; @@ -16,12 +17,15 @@ import io.jans.model.custom.script.type.token.UpdateTokenType; import io.jans.service.custom.script.ExternalScriptService; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import jakarta.ws.rs.WebApplicationException; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.function.Function; +import static org.apache.commons.lang3.BooleanUtils.isTrue; + /** * @author Yuriy Movchan */ @@ -29,6 +33,8 @@ public class ExternalUpdateTokenService extends ExternalScriptService { private static final long serialVersionUID = -1033475075863270259L; + @Inject + private AppConfiguration appConfiguration; public ExternalUpdateTokenService() { super(CustomScriptType.UPDATE_TOKEN); @@ -117,9 +123,20 @@ public int getRefreshTokenLifetimeInSeconds(ExternalUpdateTokenContext context) } @NotNull - private List getScripts(@NotNull ExternalUpdateTokenContext context) { - if (customScriptConfigurations == null || customScriptConfigurations.isEmpty() || context.getClient() == null) { - log.trace("No UpdateToken scripts or client is null."); + protected List getScripts(@NotNull ExternalUpdateTokenContext context) { + List customScriptConfigurations = getCustomScriptConfigurations(); + if (customScriptConfigurations == null || customScriptConfigurations.isEmpty()) { + log.trace("No UpdateToken scripts."); + return Lists.newArrayList(); + } + + if (isTrue(appConfiguration.getRunAllUpdateTokenScripts())) { + log.trace("Run all UpdateToken scripts."); + return customScriptConfigurations; + } + + if (context.getClient() == null) { + log.trace("No client."); return Lists.newArrayList(); } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/context/ExternalScriptContext.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/context/ExternalScriptContext.java index e2410f8ddc4..f6d8037750d 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/context/ExternalScriptContext.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/context/ExternalScriptContext.java @@ -34,7 +34,7 @@ public class ExternalScriptContext extends io.jans.service.external.context.Exte private static final Logger log = LoggerFactory.getLogger(ExternalScriptContext.class); - private final PersistenceEntryManager persistenceEntryManager; + private PersistenceEntryManager persistenceEntryManager; private ExecutionContext executionContext; @@ -51,7 +51,6 @@ public ExternalScriptContext(HttpServletRequest httpRequest) { public ExternalScriptContext(HttpServletRequest httpRequest, HttpServletResponse httpResponse) { super(httpRequest, httpResponse); - this.persistenceEntryManager = ServerUtil.getLdapManager(); } public static ExternalScriptContext of(ExecutionContext executionContext) { @@ -75,6 +74,13 @@ public AuthzDetail getAuthzDetail() { } public PersistenceEntryManager getPersistenceEntryManager() { + if (persistenceEntryManager == null) { + synchronized (this) { + if (persistenceEntryManager == null) { + persistenceEntryManager = ServerUtil.getLdapManager(); + } + } + } return persistenceEntryManager; } @@ -89,7 +95,7 @@ public boolean isInNetwork(String cidrNotation) { protected CustomEntry getEntryByDn(String dn, String... ldapReturnAttributes) { try { - return persistenceEntryManager.find(dn, CustomEntry.class, ldapReturnAttributes); + return getPersistenceEntryManager().find(dn, CustomEntry.class, ldapReturnAttributes); } catch (EntryPersistenceException epe) { log.error("Failed to find entry '{}'", dn); } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/context/ExternalUpdateTokenContext.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/context/ExternalUpdateTokenContext.java index 4d59b6d88d1..5415d521260 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/context/ExternalUpdateTokenContext.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/context/ExternalUpdateTokenContext.java @@ -33,17 +33,21 @@ public class ExternalUpdateTokenContext extends ExternalScriptContext { private static final Logger log = LoggerFactory.getLogger(ExternalUpdateTokenContext.class); - private final Client client; + private Client client; private AuthorizationGrant grant; - private final AppConfiguration appConfiguration; - private final AttributeService attributeService; + private AppConfiguration appConfiguration; + private AttributeService attributeService; private CustomScriptConfiguration script; @Nullable private ExecutionContext executionContext; private JwtSigner jwtSigner; + public ExternalUpdateTokenContext() { + super((HttpServletRequest) null); + } + public ExternalUpdateTokenContext(HttpServletRequest httpRequest, AuthorizationGrant grant, Client client, AppConfiguration appConfiguration, AttributeService attributeService) { super(httpRequest); @@ -116,6 +120,10 @@ public Client getClient() { return client; } + public void setClient(Client client) { + this.client = client; + } + public AuthorizationGrant getGrant() { return grant; } diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/service/external/ExternalUpdateTokenServiceTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/service/external/ExternalUpdateTokenServiceTest.java new file mode 100644 index 00000000000..19d4cadcd32 --- /dev/null +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/service/external/ExternalUpdateTokenServiceTest.java @@ -0,0 +1,101 @@ +package io.jans.as.server.service.external; + +import io.jans.as.common.model.registration.Client; +import io.jans.as.model.configuration.AppConfiguration; +import io.jans.as.model.error.ErrorResponseFactory; +import io.jans.as.server.service.external.context.ExternalUpdateTokenContext; +import io.jans.model.custom.script.conf.CustomScriptConfiguration; +import io.jans.model.custom.script.model.CustomScript; +import io.jans.model.custom.script.type.token.DummyUpdateTokenType; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.testng.MockitoTestNGListener; +import org.slf4j.Logger; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertTrue; + +@Listeners(MockitoTestNGListener.class) +public class ExternalUpdateTokenServiceTest { + + @Spy + @InjectMocks + private ExternalUpdateTokenService externalUpdateTokenService; + + @Mock + private Logger log; + + @Mock + private AppConfiguration appConfiguration; + + @Mock + private ErrorResponseFactory errorResponseFactory; + + @Test + public void getScripts_whenNoScripts_shouldReturnEmptyList() { + assertTrue(externalUpdateTokenService.getScripts(new ExternalUpdateTokenContext()).isEmpty()); + } + + @Test + public void getScripts_whenScriptsPresentAndRunAllIsTrue_shouldReturnNonEmptyList() { + List scripts = new ArrayList<>(); + scripts.add(new CustomScriptConfiguration(new CustomScript("", "", ""), new DummyUpdateTokenType(), new HashMap<>())); + + when(appConfiguration.getRunAllUpdateTokenScripts()).thenReturn(true); + when(externalUpdateTokenService.getCustomScriptConfigurations()).thenReturn(scripts); + + assertFalse(externalUpdateTokenService.getScripts(new ExternalUpdateTokenContext()).isEmpty()); + } + + @Test + public void getScripts_whenScriptsPresentAndRunAllIsFalse_shouldReturnEmptyList() { + List scripts = new ArrayList<>(); + scripts.add(new CustomScriptConfiguration(new CustomScript("", "", ""), new DummyUpdateTokenType(), new HashMap<>())); + + when(appConfiguration.getRunAllUpdateTokenScripts()).thenReturn(false); + when(externalUpdateTokenService.getCustomScriptConfigurations()).thenReturn(scripts); + + assertTrue(externalUpdateTokenService.getScripts(new ExternalUpdateTokenContext()).isEmpty()); + } + + @Test + public void getScripts_whenScriptsPresentAndRunAllIsFalseAndClientHasNoScripts_shouldReturnEmptyList() { + List scripts = new ArrayList<>(); + scripts.add(new CustomScriptConfiguration(new CustomScript("", "", ""), new DummyUpdateTokenType(), new HashMap<>())); + + when(appConfiguration.getRunAllUpdateTokenScripts()).thenReturn(false); + when(externalUpdateTokenService.getCustomScriptConfigurations()).thenReturn(scripts); + + ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(); + context.setClient(new Client()); + + assertTrue(externalUpdateTokenService.getScripts(context).isEmpty()); + } + + @Test + public void getScripts_whenScriptsPresentAndRunAllIsFalseAndClientHasScripts_shouldReturnNonEmptyList() { + List scripts = new ArrayList<>(); + scripts.add(new CustomScriptConfiguration(new CustomScript("dummy", "", ""), new DummyUpdateTokenType(), new HashMap<>())); + + when(appConfiguration.getRunAllUpdateTokenScripts()).thenReturn(false); + when(externalUpdateTokenService.getCustomScriptConfigurations()).thenReturn(scripts); + when(externalUpdateTokenService.getCustomScriptConfigurationsByDns(anyList())).thenReturn(scripts); + + Client client = new Client(); + client.getAttributes().setUpdateTokenScriptDns(List.of("dummy")); + + ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(); + context.setClient(client); + + assertFalse(externalUpdateTokenService.getScripts(context).isEmpty()); + } +}