Skip to content
Original file line number Diff line number Diff line change
@@ -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<CustomScriptConfiguration> scripts = getScripts(context.getExecutionContext());
if (scripts.isEmpty()) {
return false;
}
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<CustomScriptConfiguration> scripts = getScripts(context.getExecutionContext());
if (scripts.isEmpty()) {
log.trace("No TxTokenType scripts found.");
return false;
}

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<CustomScriptConfiguration> 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<CustomScriptConfiguration> 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<CustomScriptConfiguration> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@
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;
import jakarta.ejb.Stateless;
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;
Expand Down Expand Up @@ -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();
Expand All @@ -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) {
Expand All @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, SimpleCustomProperty> configurationAttributes) {
return true;
}

@Override
public boolean init(CustomScript customScript, Map<String, SimpleCustomProperty> configurationAttributes) {
return true;
}

@Override
public boolean destroy(Map<String, SimpleCustomProperty> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}