diff --git a/docs/janssen-server/auth-server/tokens/oauth-tx-tokens.md b/docs/janssen-server/auth-server/tokens/oauth-tx-tokens.md index bd024e97794..f987228bd99 100644 --- a/docs/janssen-server/auth-server/tokens/oauth-tx-tokens.md +++ b/docs/janssen-server/auth-server/tokens/oauth-tx-tokens.md @@ -143,6 +143,119 @@ transaction token (not regular `access_token`). - View full obtain execution log [here](../../../assets/log/tx-token-request-run-log.txt) - View full replace execution log [here](../../../assets/log/tx-token-replace-run-log.txt) +### TxToken custom script + +Custom script allows to modify TxToken JWT payload claim, response or lifetime. + +The TxTokenType interception script extends the base script type with the `init`, `destroy` and `getApiVersion` methods: + +| Inherited Methods | Method description | +|:-----|:------| +| `def init(self, customScript, configurationAttributes)` | This method is only called once during the script initialization. It can be used for global script initialization, initiate objects etc | +| `def destroy(self, configurationAttributes)` | This method is called once to destroy events. It can be used to free resource and objects created in the `init()` method | +| `def getApiVersion(self, configurationAttributes, customScript)` | The getApiVersion method allows API changes in order to do transparent migration from an old script to a new API. Only include the customScript variable if the value for getApiVersion is greater than 10 | + +The `configurationAttributes` parameter is `java.util.Map`. + +The TxTokenType interception script also adds the following method(s): + +| Method | Method description | +|:----------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `def getTxTokenLifetimeInSeconds(self, context)` | Used to modify Tx Token lifetime in seconds.
`context` is `io.jans.as.server.service.external.context.ExternalScriptContext` | +| `def modifyTokenPayload(self, jsonWebResponse, context)` | Used to modify TxToken object before it is persisted.
`jsonWebResponse` is `io.jans.as.model.token.JsonWebResponse`
`context` is `io.jans.as.server.service.external.context.ExternalScriptContext` | +| `def modifyResponse(self, response, context)` | Used to modify response from `/token` endpoint for transaction tokens.
`response` is `org.json.JSONObject`
`context` is `io.jans.as.server.service.external.context.ExternalScriptContext` | + + +```java + + +import io.jans.as.model.token.JsonWebResponse; +import io.jans.as.server.service.external.context.ExternalScriptContext; +import io.jans.model.SimpleCustomProperty; +import io.jans.model.custom.script.model.CustomScript; +import io.jans.model.custom.script.type.token.TxTokenType; +import io.jans.service.custom.script.CustomScriptManager; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +public class TxToken implements TxTokenType { + + private static final Logger scriptLogger = LoggerFactory.getLogger(CustomScriptManager.class); + + /** + * + * @param context context refers to io.jans.as.server.service.external.context.ExternalScriptContext + * @return lifetime of tx_token in seconds. It must be more then 0 or otherwise it will be ignored by server. + */ + @Override + public int getTxTokenLifetimeInSeconds(Object context) { + boolean condition = false; // under some condition return 1 day lifetime + if (condition) { + return 86400; + } + return 0; + } + + /** + * + * @param jsonWebResponse refers to io.jans.as.model.token.JsonWebResponse + * @param context refers to io.jans.as.server.service.external.context.ExternalScriptContext + * @return true if tx_token should be created or false to forbid tx_token creation. + */ + @Override + public boolean modifyTokenPayload(Object jsonWebResponse, Object context) { + ExternalScriptContext scriptContext = (ExternalScriptContext) context; + JsonWebResponse jwr = (JsonWebResponse) jsonWebResponse; + jwr.getClaims().setClaim("custom_claim", "custom_value"); + + return true; + } + + /** + * @param responseAsJsonObject - response represented by org.json.JSONObject + * @param context - script context represented by io.jans.as.server.service.external.context.ExternalScriptContext + * @return true if changes must be applied to final response or false if whatever made in this method has to be cancelled + */ + @Override + public boolean modifyResponse(Object responseAsJsonObject, Object context) { + ExternalScriptContext scriptContext = (ExternalScriptContext) context; + + JSONObject json = (JSONObject) responseAsJsonObject; + json.accumulate("custom_key", "custom_value"); + return true; + } + + @Override + public boolean init(Map configurationAttributes) { + scriptLogger.info("Initialized TxToken Java custom script."); + return true; + } + + @Override + public boolean init(CustomScript customScript, Map configurationAttributes) { + scriptLogger.info("Initialized TxToken Java custom script."); + return true; + } + + @Override + public boolean destroy(Map configurationAttributes) { + scriptLogger.info("Destroyed TxToken Java custom script."); + return true; + } + + @Override + public int getApiVersion() { + return 11; + } +} + + +``` + + ### References - Transaction Tokens [spec](https://drafts.oauth.net/oauth-transaction-tokens/draft-ietf-oauth-transaction-tokens.html) \ No newline at end of file diff --git a/docs/script-catalog/tx_token/TxToken.java b/docs/script-catalog/tx_token/TxToken.java new file mode 100644 index 00000000000..b68381ae0c1 --- /dev/null +++ b/docs/script-catalog/tx_token/TxToken.java @@ -0,0 +1,83 @@ + +import io.jans.as.model.token.JsonWebResponse; +import io.jans.as.server.service.external.context.ExternalScriptContext; +import io.jans.model.SimpleCustomProperty; +import io.jans.model.custom.script.model.CustomScript; +import io.jans.model.custom.script.type.token.TxTokenType; +import io.jans.service.custom.script.CustomScriptManager; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +public class TxToken implements TxTokenType { + + private static final Logger scriptLogger = LoggerFactory.getLogger(CustomScriptManager.class); + + /** + * + * @param context context refers to io.jans.as.server.service.external.context.ExternalScriptContext + * @return lifetime of tx_token in seconds. It must be more then 0 or otherwise it will be ignored by server. + */ + @Override + public int getTxTokenLifetimeInSeconds(Object context) { + boolean condition = false; // under some condition return 1 day lifetime + if (condition) { + return 86400; + } + return 0; + } + + /** + * + * @param jsonWebResponse refers to io.jans.as.model.token.JsonWebResponse + * @param context refers to io.jans.as.server.service.external.context.ExternalScriptContext + * @return true if tx_token should be created or false to forbid tx_token creation. + */ + @Override + public boolean modifyTokenPayload(Object jsonWebResponse, Object context) { + ExternalScriptContext scriptContext = (ExternalScriptContext) context; + JsonWebResponse jwr = (JsonWebResponse) jsonWebResponse; + jwr.getClaims().setClaim("custom_claim", "custom_value"); + + return true; + } + + /** + * @param responseAsJsonObject - response represented by org.json.JSONObject + * @param context - script context represented by io.jans.as.server.service.external.context.ExternalScriptContext + * @return true if changes must be applied to final response or false if whatever made in this method has to be cancelled + */ + @Override + public boolean modifyResponse(Object responseAsJsonObject, Object context) { + ExternalScriptContext scriptContext = (ExternalScriptContext) context; + + JSONObject json = (JSONObject) responseAsJsonObject; + json.accumulate("custom_key", "custom_value"); + return true; + } + + @Override + public boolean init(Map configurationAttributes) { + scriptLogger.info("Initialized TxToken Java custom script."); + return true; + } + + @Override + public boolean init(CustomScript customScript, Map configurationAttributes) { + scriptLogger.info("Initialized TxToken Java custom script."); + return true; + } + + @Override + public boolean destroy(Map configurationAttributes) { + scriptLogger.info("Destroyed TxToken Java custom script."); + return true; + } + + @Override + public int getApiVersion() { + return 11; + } +} diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/RegisterRequest.java b/jans-auth-server/client/src/main/java/io/jans/as/client/RegisterRequest.java index a7bb996c71e..d59dc7fe0ba 100644 --- a/jans-auth-server/client/src/main/java/io/jans/as/client/RegisterRequest.java +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/RegisterRequest.java @@ -87,6 +87,7 @@ public class RegisterRequest extends BaseRequest { private List updateTokenScriptDns; private List logoutStatusJwtScriptDns; private List parScriptDns; + private List txTokenScriptDns; private List postAuthnScriptDns; private List tokenExchangeScriptDns; private List consentGatheringScriptDns; @@ -204,6 +205,7 @@ public RegisterRequest() { this.updateTokenScriptDns = new ArrayList<>(); this.logoutStatusJwtScriptDns = new ArrayList<>(); this.parScriptDns = new ArrayList<>(); + this.txTokenScriptDns = new ArrayList<>(); this.postAuthnScriptDns = new ArrayList<>(); this.tokenExchangeScriptDns = new ArrayList<>(); this.consentGatheringScriptDns = new ArrayList<>(); @@ -1738,6 +1740,26 @@ public RegisterRequest setParScriptDns(List parScriptDns) { return this; } + /** + * Gets tx token script dns + * + * @return tx token script dns + */ + public List getTxTokenScriptDns() { + return txTokenScriptDns; + } + + /** + * Sets tx token script dns + * + * @param txTokenScriptDns tx token script dns + * @return register request + */ + public RegisterRequest setTxTokenScriptDns(List txTokenScriptDns) { + this.txTokenScriptDns = txTokenScriptDns; + return this; + } + /** * Gets logout status jwt script dns * @@ -1949,6 +1971,7 @@ public static RegisterRequest fromJson(JSONObject requestObject) throws JSONExce result.setUpdateTokenScriptDns(extractListByKey(requestObject, UPDATE_TOKEN_SCRIPT_DNS.toString())); result.setLogoutStatusJwtScriptDns(extractListByKey(requestObject, LOGOUT_STATUS_JWT_SCRIPT_DNS.toString())); result.setParScriptDns(extractListByKey(requestObject, PAR_SCRIPT_DNS.toString())); + result.setTxTokenScriptDns(extractListByKey(requestObject, TX_TOKEN_SCRIPT_DNS.toString())); result.setPostAuthnScriptDns(extractListByKey(requestObject, POST_AUTHN_SCRIPT_DNS.toString())); result.setTokenExchangeScriptDns(extractListByKey(requestObject, TOKEN_EXCHANGE_SCRIPT_DNS.toString())); result.setConsentGatheringScriptDns(extractListByKey(requestObject, CONSENT_GATHERING_SCRIPT_DNS.toString())); @@ -2280,6 +2303,7 @@ public void getParameters(BiFunction function) { applyArray(function, UPDATE_TOKEN_SCRIPT_DNS, updateTokenScriptDns); applyArray(function, LOGOUT_STATUS_JWT_SCRIPT_DNS, logoutStatusJwtScriptDns); applyArray(function, PAR_SCRIPT_DNS, parScriptDns); + applyArray(function, TX_TOKEN_SCRIPT_DNS, txTokenScriptDns); applyArray(function, POST_AUTHN_SCRIPT_DNS, postAuthnScriptDns); applyArray(function, TOKEN_EXCHANGE_SCRIPT_DNS, tokenExchangeScriptDns); applyArray(function, CONSENT_GATHERING_SCRIPT_DNS, consentGatheringScriptDns); diff --git a/jans-auth-server/model/src/main/java/io/jans/as/model/register/RegisterRequestParam.java b/jans-auth-server/model/src/main/java/io/jans/as/model/register/RegisterRequestParam.java index ce67974e391..f38b9584cfa 100644 --- a/jans-auth-server/model/src/main/java/io/jans/as/model/register/RegisterRequestParam.java +++ b/jans-auth-server/model/src/main/java/io/jans/as/model/register/RegisterRequestParam.java @@ -408,6 +408,11 @@ public enum RegisterRequestParam { */ PAR_SCRIPT_DNS("par_script_dns"), + /** + * list of tx token script dns + */ + TX_TOKEN_SCRIPT_DNS("tx_token_script_dns"), + /** * list of post authn script dns */ diff --git a/jans-auth-server/persistence-model/src/main/java/io/jans/as/persistence/model/ClientAttributes.java b/jans-auth-server/persistence-model/src/main/java/io/jans/as/persistence/model/ClientAttributes.java index f906424e3dc..7ca2dad9899 100644 --- a/jans-auth-server/persistence-model/src/main/java/io/jans/as/persistence/model/ClientAttributes.java +++ b/jans-auth-server/persistence-model/src/main/java/io/jans/as/persistence/model/ClientAttributes.java @@ -56,6 +56,9 @@ public class ClientAttributes implements Serializable { @JsonProperty("parScriptDns") private List parScriptDns = Lists.newArrayList(); + @JsonProperty("txTokenScriptDns") + private List txTokenScriptDns = Lists.newArrayList(); + @JsonProperty("backchannelLogoutUri") private List backchannelLogoutUri; @@ -475,6 +478,16 @@ public ClientAttributes setParScriptDns(List parScriptDns) { return this; } + public List getTxTokenScriptDns() { + if (txTokenScriptDns == null) txTokenScriptDns = Lists.newArrayList(); + return txTokenScriptDns; + } + + public ClientAttributes setTxTokenScriptDns(List txTokenScriptDns) { + this.txTokenScriptDns = txTokenScriptDns; + return this; + } + public Boolean getRunIntrospectionScriptBeforeJwtCreation() { if (runIntrospectionScriptBeforeJwtCreation == null) { runIntrospectionScriptBeforeJwtCreation = false; @@ -602,6 +615,7 @@ public String toString() { ", updateTokenScriptDns=" + updateTokenScriptDns + ", logoutStatusJwtScriptDns=" + logoutStatusJwtScriptDns + ", parScriptDns=" + parScriptDns + + ", txTokenScriptDns=" + txTokenScriptDns + ", backchannelLogoutUri=" + backchannelLogoutUri + ", backchannelLogoutSessionRequired=" + backchannelLogoutSessionRequired + ", additionalAudience=" + additionalAudience + diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/register/ws/rs/RegisterService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/register/ws/rs/RegisterService.java index 0ac19618e6b..fe7a8434c8b 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/register/ws/rs/RegisterService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/register/ws/rs/RegisterService.java @@ -262,6 +262,9 @@ public void updateClientFromRequestObject(Client client, RegisterRequest request if (requestObject.getParScriptDns() != null) { client.getAttributes().setParScriptDns(requestObject.getParScriptDns()); } + if (requestObject.getTxTokenScriptDns() != null) { + client.getAttributes().setTxTokenScriptDns(requestObject.getTxTokenScriptDns()); + } if (requestObject.getPostAuthnScriptDns() != null) { client.getAttributes().setPostAuthnScripts(requestObject.getPostAuthnScriptDns()); } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalParService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalParService.java index c3ac9fde222..7bd924c43ac 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalParService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalParService.java @@ -49,7 +49,7 @@ public boolean createPar(CustomScriptConfiguration script, Par par, ExternalScri public boolean createPar(Par par, ExternalScriptContext context) { List scripts = getScripts(context); if (scripts.isEmpty()) { - return false; + return true; } log.trace("Executing {} 'createPar' scripts.", scripts.size()); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalTxTokenService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalTxTokenService.java new file mode 100644 index 00000000000..b1de7197c9a --- /dev/null +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalTxTokenService.java @@ -0,0 +1,153 @@ +package io.jans.as.server.service.external; + +import com.google.common.collect.Lists; +import io.jans.as.model.token.JsonWebResponse; +import io.jans.as.server.model.common.ExecutionContext; +import io.jans.as.server.service.external.context.ExternalScriptContext; +import io.jans.model.custom.script.CustomScriptType; +import io.jans.model.custom.script.conf.CustomScriptConfiguration; +import io.jans.model.custom.script.type.token.TxTokenType; +import io.jans.service.custom.script.ExternalScriptService; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.WebApplicationException; +import org.jetbrains.annotations.NotNull; +import org.json.JSONObject; + +import java.util.List; + +@ApplicationScoped +public class ExternalTxTokenService extends ExternalScriptService { + + public ExternalTxTokenService() { + super(CustomScriptType.TX_TOKEN); + } + + public boolean modifyTokenPayload(CustomScriptConfiguration script, JsonWebResponse jsonWebResponse, ExternalScriptContext context) { + try { + log.trace("Executing 'modifyTokenPayload' method, script name: {}, jsonWebResponse: {}, context: {}", script.getName(), jsonWebResponse, context); + context.getExecutionContext().setScript(script); + + TxTokenType scriptType = (TxTokenType) script.getExternalType(); + final boolean result = scriptType.modifyTokenPayload(jsonWebResponse, context); + log.trace("Finished 'modifyTokenPayload' method, script name: {}, jsonWebResponse: {}, context: {}, result: {}", script.getName(), jsonWebResponse, context, result); + + context.throwWebApplicationExceptionIfSet(); + return result; + } catch (WebApplicationException e) { + throw e; + } catch (Exception ex) { + log.error(ex.getMessage(), ex); + saveScriptError(script.getCustomScript(), ex); + } + + return false; + } + + public boolean modifyTokenPayload(JsonWebResponse jsonWebResponse, ExternalScriptContext context) { + List scripts = getScripts(context.getExecutionContext()); + if (scripts.isEmpty()) { + return true; + } + log.trace("Executing {} 'modifyTokenPayload' scripts.", scripts.size()); + + for (CustomScriptConfiguration script : scripts) { + if (!modifyTokenPayload(script, jsonWebResponse, context)) { + return false; + } + } + + return true; + } + + public boolean modifyResponse(CustomScriptConfiguration script, JSONObject response, ExternalScriptContext context) { + try { + log.trace("Executing 'modifyResponse' method, script name: {}, response: {}, context: {}", script.getName(), response, context); + context.getExecutionContext().setScript(script); + + TxTokenType scriptType = (TxTokenType) script.getExternalType(); + final boolean result = scriptType.modifyResponse(response, context); + log.trace("Finished 'modifyResponse' method, script name: {}, response: {}, context: {}, result: {}", script.getName(), response, context, result); + + context.throwWebApplicationExceptionIfSet(); + return result; + } catch (WebApplicationException e) { + throw e; + } catch (Exception ex) { + log.error(ex.getMessage(), ex); + saveScriptError(script.getCustomScript(), ex); + } + + return false; + } + + public boolean modifyResponse(JSONObject response, ExternalScriptContext context) { + List scripts = getScripts(context.getExecutionContext()); + if (scripts.isEmpty()) { + log.trace("No TxTokenType scripts found."); + return true; + } + + log.trace("Executing {} 'modifyResponse' scripts.", scripts.size()); + + for (CustomScriptConfiguration script : scripts) { + if (!modifyResponse(script, response, context)) { + return false; + } + } + + return true; + } + + public int getTxTokenLifetimeInSeconds(CustomScriptConfiguration script, ExternalScriptContext context) { + try { + log.trace("Executing 'getTxTokenLifetimeInSeconds' method, script name: {}, context: {}", script.getName(), context); + context.getExecutionContext().setScript(script); + + TxTokenType txTokenType = (TxTokenType) script.getExternalType(); + final int result = txTokenType.getTxTokenLifetimeInSeconds(context); + log.trace("Finished 'getTxTokenLifetimeInSeconds' method, script name: {}, context: {}, result: {}", script.getName(), context, result); + + context.throwWebApplicationExceptionIfSet(); + return result; + } catch (WebApplicationException e) { + throw e; + } catch (Exception ex) { + log.error(ex.getMessage(), ex); + saveScriptError(script.getCustomScript(), ex); + } + return 0; + } + + public int getTxTokenLifetimeInSeconds(ExternalScriptContext context) { + List scripts = getScripts(context.getExecutionContext()); + if (scripts.isEmpty()) { + return 0; + } + log.trace("Executing {} 'getTxTokenLifetimeInSeconds' scripts.", scripts.size()); + + for (CustomScriptConfiguration script : scripts) { + final int lifetime = getTxTokenLifetimeInSeconds(script, context); + if (lifetime > 0) { + log.trace("Finished 'getTxTokenLifetimeInSeconds' methods, lifetime: {}", lifetime); + return lifetime; + } + } + return 0; + } + + @NotNull + private List getScripts(@NotNull ExecutionContext context) { + if (customScriptConfigurations == null || customScriptConfigurations.isEmpty() || context.getClient() == null) { + log.trace("No TxToken scripts or client is null."); + return Lists.newArrayList(); + } + + final List scripts = getCustomScriptConfigurationsByDns(context.getClient().getAttributes().getTxTokenScriptDns()); + if (!scripts.isEmpty()) { + return scripts; + } + + log.trace("No TxToken scripts associated with client {}", context.getClient().getClientId()); + return Lists.newArrayList(); + } +} 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 277fce5e30e..685dfc2ada3 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 @@ -58,7 +58,7 @@ public boolean modifyIdTokenMethod(CustomScriptConfiguration script, JsonWebResp public boolean modifyIdTokenMethods(JsonWebResponse jsonWebResponse, ExternalUpdateTokenContext context) { List scripts = getScripts(context); if (scripts.isEmpty()) { - return false; + return true; } log.trace("Executing {} update-token scripts.", scripts.size()); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TxTokenService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TxTokenService.java index b59348bac0a..90b7958b871 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TxTokenService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TxTokenService.java @@ -34,6 +34,8 @@ import io.jans.as.server.model.token.JwtSigner; import io.jans.as.server.service.ClientService; import io.jans.as.server.service.ServerCryptoProvider; +import io.jans.as.server.service.external.ExternalTxTokenService; +import io.jans.as.server.service.external.context.ExternalScriptContext; import io.jans.as.server.util.ServerUtil; import io.jans.model.token.TokenEntity; import io.jans.util.security.StringEncrypter; @@ -41,6 +43,7 @@ import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; @@ -88,6 +91,9 @@ public class TxTokenService { @Inject private AuthorizationGrantList authorizationGrantList; + @Inject + private ExternalTxTokenService externalTxTokenService; + public Response processTxToken(ExecutionContext executionContext) throws Exception { final JSONObject responseJson = process(executionContext); final String entity = responseJson.toString(); @@ -106,7 +112,14 @@ private JSONObject process(ExecutionContext executionContext) throws Exception { TxToken txToken = createTxToken(executionContext, subjectGrant); - return createResponse(txToken.getCode()); + JSONObject response = createResponse(txToken.getCode()); + JSONObject responseForScript = new JSONObject(response.toString()); + + if (externalTxTokenService.modifyResponse(responseForScript, ExternalScriptContext.of(executionContext))) { + // change response only if external script returned `true` + response = responseForScript; + } + return response; } public static JSONObject createResponse(String txToken) { @@ -133,7 +146,7 @@ private TxToken createTxToken(ExecutionContext executionContext, AuthorizationGr final JsonWebResponse jwr = createTxTokenJwr(audience, requestContext, requestDetails, executionContext, subjectGrant); final String jwrString = jwr.toString(); - final int txTokenLifetime = getTxTokenLifetime(client); + final int txTokenLifetime = getTxTokenLifetime(executionContext); TxToken txToken = new TxToken(txTokenLifetime); txToken.setCode(jwrString); @@ -147,10 +160,9 @@ private void fillPayload(JsonWebResponse jwr, String audience, String requestCon Calendar calendar = Calendar.getInstance(); Date issuedAt = calendar.getTime(); - calendar.add(Calendar.SECOND, getTxTokenLifetime(client)); + calendar.add(Calendar.SECOND, getTxTokenLifetime(executionContext)); Date expiration = calendar.getTime(); - jwr.getClaims().setIssuer(appConfiguration.getIssuer()); jwr.getClaims().setExpirationTime(expiration); jwr.getClaims().setIat(issuedAt); @@ -180,6 +192,20 @@ private void fillPayload(JsonWebResponse jwr, String audience, String requestCon azd.put("client_id", client.getClientId()); jwr.getClaims().setClaim("azd", azd); + + boolean externalOk = externalTxTokenService.modifyTokenPayload(jwr, ExternalScriptContext.of(executionContext)); + if (!externalOk) { + final String reason = "External TxToken script forbids tx_token creation."; + log.trace(reason); + + throw new WebApplicationException(Response + .status(Response.Status.FORBIDDEN) + .type(MediaType.APPLICATION_JSON_TYPE) + .cacheControl(ServerUtil.cacheControl(true, false)) + .header("Pragma", "no-cache") + .entity(errorResponseFactory.errorAsJson(TokenErrorResponseType.ACCESS_DENIED, reason)) + .build()); + } } private static JSONObject decodeJson(String jsonString) { @@ -194,11 +220,18 @@ private static JSONObject decodeJson(String jsonString) { } } - private int getTxTokenLifetime(Client client) { + private int getTxTokenLifetime(ExecutionContext executionContext) { + Client client = executionContext.getClient(); if (client.getAttributes().getTxTokenLifetime() != null && client.getAttributes().getTxTokenLifetime() > 0) { log.trace("Override TxToken lifetime with value {} from client: {}", client.getAttributes().getTxTokenLifetime(), client.getClientId()); return client.getAttributes().getTxTokenLifetime(); } + + int lifetimeFromScript = externalTxTokenService.getTxTokenLifetimeInSeconds(ExternalScriptContext.of(executionContext)); + if (lifetimeFromScript > 0) { + log.trace("Override TxToken lifetime with value {} from script.", lifetimeFromScript); + return lifetimeFromScript; + } return appConfiguration.getTxTokenLifetime(); } diff --git a/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java b/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java index dd06a3cff61..ff64d5ddab6 100644 --- a/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java +++ b/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java @@ -131,6 +131,7 @@ public enum CustomScriptType implements AttributeEnum { HEALTH_CHECK("health_check", "Health Check", HealthCheckType.class, CustomScript.class, "HealthCheck", new DummyHealthCheck()), AUTHZ_DETAIL("authz_detail", "Authorization Detail", AuthzDetailType.class, CustomScript.class, "AuthzDetail", new DummyAuthzDetail()), UPDATE_TOKEN("update_token", "Update Token", UpdateTokenType.class, CustomScript.class, "UpdateToken", new DummyUpdateTokenType()), + TX_TOKEN("tx_token", "Transaction Token", TxTokenType.class, CustomScript.class, "TxToken", new DummyTxTokenType()), LOGOUT_STATUS_JWT("logout_status_jwt", "Logout Status Jwt", LogoutStatusJwtType.class, CustomScript.class, "LogoutStatusJwt", new DummyLogoutStatusJwtType()), PAR("par", "Pushed Authorization Request", ParType.class, CustomScript.class, "Par", new DummyParType()), CONFIG_API("config_api_auth", "Config Api Auth", ConfigApiType.class, CustomScript.class,"ConfigApiAuthorization", new DummyConfigApiType()), diff --git a/jans-core/script/src/main/java/io/jans/model/custom/script/type/token/DummyTxTokenType.java b/jans-core/script/src/main/java/io/jans/model/custom/script/type/token/DummyTxTokenType.java new file mode 100644 index 00000000000..3f43fc43e53 --- /dev/null +++ b/jans-core/script/src/main/java/io/jans/model/custom/script/type/token/DummyTxTokenType.java @@ -0,0 +1,45 @@ +package io.jans.model.custom.script.type.token; + +import io.jans.model.SimpleCustomProperty; +import io.jans.model.custom.script.model.CustomScript; + +import java.util.Map; + +public class DummyTxTokenType implements TxTokenType { + + + @Override + public boolean init(Map configurationAttributes) { + return true; + } + + @Override + public boolean init(CustomScript customScript, Map configurationAttributes) { + return true; + } + + @Override + public boolean destroy(Map configurationAttributes) { + return true; + } + + @Override + public int getApiVersion() { + return 1; + } + + @Override + public int getTxTokenLifetimeInSeconds(Object context) { + return 0; + } + + @Override + public boolean modifyTokenPayload(Object jsonWebResponse, Object context) { + return true; + } + + @Override + public boolean modifyResponse(Object responseAsJsonObject, Object context) { + return true; + } +} diff --git a/jans-core/script/src/main/java/io/jans/model/custom/script/type/token/TxTokenType.java b/jans-core/script/src/main/java/io/jans/model/custom/script/type/token/TxTokenType.java new file mode 100644 index 00000000000..53187728cdc --- /dev/null +++ b/jans-core/script/src/main/java/io/jans/model/custom/script/type/token/TxTokenType.java @@ -0,0 +1,12 @@ +package io.jans.model.custom.script.type.token; + +import io.jans.model.custom.script.type.BaseExternalType; + +public interface TxTokenType extends BaseExternalType { + + int getTxTokenLifetimeInSeconds(Object context); + + boolean modifyTokenPayload(Object jsonWebResponse, Object context); + + boolean modifyResponse(Object responseAsJsonObject, Object context); +}