From 5851971cb8e9d4783b51357b49c237d6e28777c9 Mon Sep 17 00:00:00 2001 From: YuriyZ Date: Wed, 22 May 2024 14:37:11 +0300 Subject: [PATCH 1/9] feat(jans-auth-server): added support for Global Token Revocation spec https://github.com/JanssenProject/jans/issues/8398 Signed-off-by: YuriyZ --- .../auth-server/session-management/README.md | 2 +- docs/admin/install/install-faq.md | 3 +- .../as/client/OpenIdConfigurationClient.java | 1 + .../client/OpenIdConfigurationResponse.java | 19 +++ jans-auth-server/docs/swagger.yaml | 34 +++++ .../jans/as/model/common/FeatureFlagType.java | 3 + .../java/io/jans/as/model/common/SubId.java | 31 +++++ .../ConfigurationResponseClaim.java | 1 + .../revoke/GlobalTokenRevocationRequest.java | 21 +++ jans-auth-server/server/conf/jans-config.json | 1 + .../as/server/auth/AuthenticationFilter.java | 5 +- .../io/jans/as/server/auth/Authenticator.java | 1 + .../as/server/model/config/Constants.java | 1 + .../GlobalTokenRevocationRestWebService.java | 131 ++++++++++++++++++ .../as/server/service/DiscoveryService.java | 4 + .../jans/as/server/service/GrantService.java | 13 ++ .../as/server/service/SessionIdService.java | 4 + .../servlet/FapiOpenIdConfiguration.java | 1 + .../jans_setup/setup_app/test_data_loader.py | 2 +- .../templates/jans-auth/jans-auth-config.json | 1 + .../jans_setup/templates/scopes.ldif | 11 ++ 21 files changed, 286 insertions(+), 4 deletions(-) create mode 100644 jans-auth-server/model/src/main/java/io/jans/as/model/common/SubId.java create mode 100644 jans-auth-server/model/src/main/java/io/jans/as/model/revoke/GlobalTokenRevocationRequest.java create mode 100644 jans-auth-server/server/src/main/java/io/jans/as/server/revoke/GlobalTokenRevocationRestWebService.java diff --git a/docs/admin/auth-server/session-management/README.md b/docs/admin/auth-server/session-management/README.md index 502db31e5d4..bb2d2f8dbe6 100644 --- a/docs/admin/auth-server/session-management/README.md +++ b/docs/admin/auth-server/session-management/README.md @@ -61,7 +61,7 @@ Jans Auth Server updates `lastUsedAt` property of the session object: The [End Session endpoint](../endpoints/end-session.md) (`/end_session`) is where the user can end their own session. See [OpenID Logout](../openid-features/logout/README.md) for more information. -To end another person's session, Jans Auth Server has a [Session Revocation Endpoint](../endpoints/session-revocation.md) (`/revoke_session`). +To end another person's session, Jans Auth Server supports both [Session Revocation Endpoint](../endpoints/session-revocation.md) (`/revoke_session`) and [Global Session Revocation Endpoint](../endpoints/global-session-revocation.md) (`/global-token-revocation`'). ## Session Event Interception Scripts diff --git a/docs/admin/install/install-faq.md b/docs/admin/install/install-faq.md index 060f00801e6..37749ba22be 100644 --- a/docs/admin/install/install-faq.md +++ b/docs/admin/install/install-faq.md @@ -82,8 +82,9 @@ specification. Sample below: "request_object_signing_alg_values_supported" : [ "none", "HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "ES512", "PS256", "PS384", "PS512" ], "request_object_encryption_alg_values_supported" : [ "RSA1_5", "RSA-OAEP", "A128KW", "A256KW" ], "session_revocation_endpoint" : "https://janssen-host-name/jans-auth/restv1/revoke_session", + "global_token_revocation_endpoint" : "https://janssen-host-name/jans-auth/restv1/global-token-revocation", "check_session_iframe" : "https://janssen-host-name/jans-auth/opiframe.htm", - "scopes_supported" : [ "https://jans.io/scim/all-resources.search", "address", "user_name", "clientinfo", "openid", "https://jans.io/scim/fido2.write", "profile", "uma_protection", "permission", "https://jans.io/scim/fido.read", "https://jans.io/scim/users.write", "https://jans.io/scim/groups.read", "revoke_session", "https://jans.io/scim/fido.write", "https://jans.io/scim/bulk", "https://jans.io/scim/users.read", "phone", "mobile_phone", "offline_access", "https://jans.io/scim/groups.write", "email", "https://jans.io/scim/fido2.read", "jans_client_api" ], + "scopes_supported" : [ "https://jans.io/scim/all-resources.search", "address", "user_name", "clientinfo", "openid", "https://jans.io/scim/fido2.write", "profile", "uma_protection", "permission", "https://jans.io/scim/fido.read", "https://jans.io/scim/users.write", "https://jans.io/scim/groups.read", "revoke_session", "global_token_revocation", "https://jans.io/scim/fido.write", "https://jans.io/scim/bulk", "https://jans.io/scim/users.read", "phone", "mobile_phone", "offline_access", "https://jans.io/scim/groups.write", "email", "https://jans.io/scim/fido2.read", "jans_client_api" ], "backchannel_logout_supported" : true, "acr_values_supported" : [ "simple_password_auth" ], "request_object_encryption_enc_values_supported" : [ "A128CBC+HS256", "A256CBC+HS512", "A128GCM", "A256GCM" ], diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/OpenIdConfigurationClient.java b/jans-auth-server/client/src/main/java/io/jans/as/client/OpenIdConfigurationClient.java index 94b2d425719..60c11117619 100644 --- a/jans-auth-server/client/src/main/java/io/jans/as/client/OpenIdConfigurationClient.java +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/OpenIdConfigurationClient.java @@ -108,6 +108,7 @@ public static void parse(String json, OpenIdConfigurationResponse response) { response.setTokenEndpoint(jsonObj.optString(TOKEN_ENDPOINT, null)); response.setRevocationEndpoint(jsonObj.optString(REVOCATION_ENDPOINT, null)); response.setSessionRevocationEndpoint(jsonObj.optString(SESSION_REVOCATION_ENDPOINT, null)); + response.setGlobalTokenRevocationEndpoint(jsonObj.optString(GLOBAL_TOKEN_REVOCATION_ENDPOINT, null)); response.setUserInfoEndpoint(jsonObj.optString(USER_INFO_ENDPOINT, null)); response.setClientInfoEndpoint(jsonObj.optString(CLIENT_INFO_ENDPOINT, null)); response.setCheckSessionIFrame(jsonObj.optString(CHECK_SESSION_IFRAME, null)); diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/OpenIdConfigurationResponse.java b/jans-auth-server/client/src/main/java/io/jans/as/client/OpenIdConfigurationResponse.java index 91963134101..88d1722c5ec 100644 --- a/jans-auth-server/client/src/main/java/io/jans/as/client/OpenIdConfigurationResponse.java +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/OpenIdConfigurationResponse.java @@ -31,6 +31,7 @@ public class OpenIdConfigurationResponse extends BaseResponse implements Seriali private String tokenEndpoint; private String revocationEndpoint; private String sessionRevocationEndpoint; + private String globalTokenRevocationEndpoint; private String userInfoEndpoint; private String clientInfoEndpoint; private String checkSessionIFrame; @@ -264,6 +265,24 @@ public void setSessionRevocationEndpoint(String sessionRevocationEndpoint) { this.sessionRevocationEndpoint = sessionRevocationEndpoint; } + /** + * Gets global token revocation endpoint + * + * @return global token revocation endpoint + */ + public String getGlobalTokenRevocationEndpoint() { + return globalTokenRevocationEndpoint; + } + + /** + * Sets global token revocation endpoint + * + * @param globalTokenRevocationEndpoint global token revocation endpoint + */ + public void setGlobalTokenRevocationEndpoint(String globalTokenRevocationEndpoint) { + this.globalTokenRevocationEndpoint = globalTokenRevocationEndpoint; + } + /** * Returns the URL of the Token Revocation endpoint. * diff --git a/jans-auth-server/docs/swagger.yaml b/jans-auth-server/docs/swagger.yaml index fba23c256a2..9d0b098a3be 100644 --- a/jans-auth-server/docs/swagger.yaml +++ b/jans-auth-server/docs/swagger.yaml @@ -2674,6 +2674,40 @@ paths: $ref: '#/components/responses/Unauthorized' 500: $ref: '#/components/responses/InternalServerError' + /global-token-revocation: + post: + tags: + - Global Token Revocation + summary: Global Token Revocation + description: Revoke all tokens for user (requires global_token_revocation scope). + operationId: global-token-revocation + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + sub_id: + type: object + properties: + format: + type: string + description: any user attribute that identifies user. E.g. uid or mail. + example: uid + id: + type: string + example: 46dkfde5 + required: + - format + - id + responses: + 204: + description: OK - Returned if request was processed successfully. Means user is correctly idendified and AS cleared tokens and sessions associated with it. + 401: + $ref: '#/components/responses/Unauthorized' + 500: + $ref: '#/components/responses/InternalServerError' /end_session: get: tags: diff --git a/jans-auth-server/model/src/main/java/io/jans/as/model/common/FeatureFlagType.java b/jans-auth-server/model/src/main/java/io/jans/as/model/common/FeatureFlagType.java index 653638e5b13..fc8b601a325 100644 --- a/jans-auth-server/model/src/main/java/io/jans/as/model/common/FeatureFlagType.java +++ b/jans-auth-server/model/src/main/java/io/jans/as/model/common/FeatureFlagType.java @@ -39,6 +39,9 @@ public enum FeatureFlagType { @DocFeatureFlag(description = "Enable/Disable session revocation endpoint", defaultValue = "Enabled") REVOKE_SESSION("revoke_session"), + @DocFeatureFlag(description = "Enable/Disable global token revocation endpoint", + defaultValue = "Enabled") + GLOBAL_TOKEN_REVOCATION("global_token_revocation"), @DocFeatureFlag(description = "Enable/Disable active session endpoint", defaultValue = "Enabled") ACTIVE_SESSION("active_session"), diff --git a/jans-auth-server/model/src/main/java/io/jans/as/model/common/SubId.java b/jans-auth-server/model/src/main/java/io/jans/as/model/common/SubId.java new file mode 100644 index 00000000000..cc66027612d --- /dev/null +++ b/jans-auth-server/model/src/main/java/io/jans/as/model/common/SubId.java @@ -0,0 +1,31 @@ +package io.jans.as.model.common; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Yuriy Z + */ +public class SubId { + + @JsonProperty("format") + private String format; + + @JsonProperty("id") + private String id; + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/ConfigurationResponseClaim.java b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/ConfigurationResponseClaim.java index 27419bac1d0..3c771928b33 100644 --- a/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/ConfigurationResponseClaim.java +++ b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/ConfigurationResponseClaim.java @@ -21,6 +21,7 @@ private ConfigurationResponseClaim() { public static final String TOKEN_ENDPOINT = "token_endpoint"; public static final String REVOCATION_ENDPOINT = "revocation_endpoint"; public static final String SESSION_REVOCATION_ENDPOINT = "session_revocation_endpoint"; + public static final String GLOBAL_TOKEN_REVOCATION_ENDPOINT = "global_token_revocation_endpoint"; public static final String USER_INFO_ENDPOINT = "userinfo_endpoint"; public static final String CLIENT_INFO_ENDPOINT = "clientinfo_endpoint"; public static final String CHECK_SESSION_IFRAME = "check_session_iframe"; diff --git a/jans-auth-server/model/src/main/java/io/jans/as/model/revoke/GlobalTokenRevocationRequest.java b/jans-auth-server/model/src/main/java/io/jans/as/model/revoke/GlobalTokenRevocationRequest.java new file mode 100644 index 00000000000..15f6df1c896 --- /dev/null +++ b/jans-auth-server/model/src/main/java/io/jans/as/model/revoke/GlobalTokenRevocationRequest.java @@ -0,0 +1,21 @@ +package io.jans.as.model.revoke; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.jans.as.model.common.SubId; + +/** + * @author Yuriy Z + */ +public class GlobalTokenRevocationRequest { + + @JsonProperty("sub_id") + private SubId subId; + + public SubId getSubId() { + return subId; + } + + public void setSubId(SubId subId) { + this.subId = subId; + } +} diff --git a/jans-auth-server/server/conf/jans-config.json b/jans-auth-server/server/conf/jans-config.json index 155d0b15f9f..39801452327 100644 --- a/jans-auth-server/server/conf/jans-config.json +++ b/jans-auth-server/server/conf/jans-config.json @@ -8,6 +8,7 @@ "introspection", "revoke_token", "revoke_session", + "global_token_revocation", "active_session", "end_session", "status_session", diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/auth/AuthenticationFilter.java b/jans-auth-server/server/src/main/java/io/jans/as/server/auth/AuthenticationFilter.java index 5f4cf7bdae6..0b51789292c 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/auth/AuthenticationFilter.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/auth/AuthenticationFilter.java @@ -79,6 +79,7 @@ "/restv1/userinfo", "/restv1/revoke", "/restv1/revoke_session", + "/restv1/global-token-revocation", "/restv1/bc-authorize", "/restv1/par", "/restv1/device_authorization", @@ -170,6 +171,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo boolean backchannelAuthenticationEnpoint = requestUrl.endsWith("/bc-authorize"); boolean deviceAuthorizationEndpoint = requestUrl.endsWith("/device_authorization"); boolean revokeSessionEndpoint = requestUrl.endsWith("/revoke_session"); + boolean globalTokenRevocationEndpoint = requestUrl.endsWith("/global-token-revocation"); boolean isParEndpoint = requestUrl.endsWith("/par"); boolean ssaEndpoint = requestUrl.endsWith("/ssa") && (Arrays.asList(HttpMethod.POST, HttpMethod.GET, HttpMethod.DELETE).contains(httpRequest.getMethod())); @@ -187,7 +189,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo return; } - if (tokenEndpoint || revokeSessionEndpoint || tokenRevocationEndpoint || deviceAuthorizationEndpoint || isParEndpoint || ssaEndpoint || ssaJwtEndpoint) { + if (tokenEndpoint || revokeSessionEndpoint || tokenRevocationEndpoint || deviceAuthorizationEndpoint || isParEndpoint || ssaEndpoint || ssaJwtEndpoint || globalTokenRevocationEndpoint) { log.debug("Starting endpoint authentication {}", requestUrl); // #686 : allow authenticated client via user access_token @@ -416,6 +418,7 @@ private void processBasicAuth(HttpServletRequest servletRequest, HttpServletResp if (servletRequest.getRequestURI().endsWith("/token") || servletRequest.getRequestURI().endsWith("/revoke") || servletRequest.getRequestURI().endsWith("/revoke_session") + || servletRequest.getRequestURI().endsWith("/global-token-revocation") || servletRequest.getRequestURI().endsWith("/userinfo") || servletRequest.getRequestURI().endsWith("/bc-authorize") || servletRequest.getRequestURI().endsWith("/par") diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/auth/Authenticator.java b/jans-auth-server/server/src/main/java/io/jans/as/server/auth/Authenticator.java index 48bee7b18d5..4abad25f5b0 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/auth/Authenticator.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/auth/Authenticator.java @@ -206,6 +206,7 @@ public boolean isServiceAuthentication(boolean service, boolean skipPassword, Ht && (servletRequest.getRequestURI().endsWith("/token") || servletRequest.getRequestURI().endsWith("/revoke") || servletRequest.getRequestURI().endsWith("/revoke_session") + || servletRequest.getRequestURI().endsWith("/global-token-revocation") || servletRequest.getRequestURI().endsWith("/userinfo") || servletRequest.getRequestURI().endsWith("/bc-authorize") || servletRequest.getRequestURI().endsWith("/par") diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/model/config/Constants.java b/jans-auth-server/server/src/main/java/io/jans/as/server/model/config/Constants.java index f0e8fcda3b7..8c0153f2794 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/model/config/Constants.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/model/config/Constants.java @@ -28,6 +28,7 @@ public final class Constants { public static final String OX_AUTH_SCOPE_TYPE_OPENID = "openid"; public static final String REVOKE_SESSION_SCOPE = "revoke_session"; + public static final String GLOBAL_TOKEN_REVOCATION_SCOPE = "global_token_revocation"; public static final String AUTHORIZATION_CHALLENGE_SCOPE = "authorization_challenge"; public static final String REVOKE_ANY_TOKEN_SCOPE = "revoke_any_token"; diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/revoke/GlobalTokenRevocationRestWebService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/revoke/GlobalTokenRevocationRestWebService.java new file mode 100644 index 00000000000..37b71cc46cc --- /dev/null +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/revoke/GlobalTokenRevocationRestWebService.java @@ -0,0 +1,131 @@ +package io.jans.as.server.revoke; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import io.jans.as.common.model.common.User; +import io.jans.as.common.model.session.SessionId; +import io.jans.as.model.authorize.AuthorizeErrorResponseType; +import io.jans.as.model.common.FeatureFlagType; +import io.jans.as.model.error.ErrorResponseFactory; +import io.jans.as.model.revoke.GlobalTokenRevocationRequest; +import io.jans.as.model.session.EndSessionErrorResponseType; +import io.jans.as.server.model.config.Constants; +import io.jans.as.server.model.session.SessionClient; +import io.jans.as.server.security.Identity; +import io.jans.as.server.service.GrantService; +import io.jans.as.server.service.ScopeService; +import io.jans.as.server.service.SessionIdService; +import io.jans.as.server.service.UserService; +import io.jans.as.server.util.ServerUtil; +import io.jans.model.token.TokenEntity; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.apache.commons.lang.ArrayUtils; +import org.slf4j.Logger; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** + * @author Yuriy Z + */ +@Path("/") +public class GlobalTokenRevocationRestWebService { + + @Inject + private Logger log; + + @Inject + private UserService userService; + + @Inject + private SessionIdService sessionIdService; + + @Inject + private ErrorResponseFactory errorResponseFactory; + + @Inject + private Identity identity; + + @Inject + private ScopeService scopeService; + + @Inject + private GrantService grantService; + + @POST + @Path("/global-token-revocation") + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + public Response requestGlobalTokenRevocation(String requestAsString) { + try { + log.debug("Attempt for global token revocation: request = {}, ", requestAsString); + + errorResponseFactory.validateFeatureEnabled(FeatureFlagType.GLOBAL_TOKEN_REVOCATION); + + validateAccess(); + + GlobalTokenRevocationRequest parsedRequest = parseRequest(requestAsString); + + final String key = parsedRequest.getSubId().getFormat(); + final String value = parsedRequest.getSubId().getId(); + + final User user = userService.getUserByAttribute(key, value); + if (user == null) { + log.trace("Unable to find user by {}={}", key, value); + return Response.noContent().build(); // no error because we don't want to disclose internal AS info about users + } + + // remove sessions + List sessionIdList = sessionIdService.findByUser(user.getDn()); + sessionIdService.remove(sessionIdList); + + log.debug("Revoked {} user's sessions (user: {})", sessionIdList != null ? sessionIdList.size() : 0, user.getUserId()); + + // remove tokens + final List grants = grantService.getGrantsByUserId(user.getUserId()); + grantService.removeSilently(grants); + + return Response.noContent().build(); + } catch (WebApplicationException e) { + throw e; + } catch (Exception e) { + log.error(e.getMessage(), e); + return Response.status(500).build(); + } + } + + private void validateAccess() { + SessionClient sessionClient = identity.getSessionClient(); + if (sessionClient == null || sessionClient.getClient() == null || ArrayUtils.isEmpty(sessionClient.getClient().getScopes())) { + log.debug("Client failed to authenticate."); + throw new WebApplicationException( + Response.status(Response.Status.UNAUTHORIZED.getStatusCode()) + .entity(errorResponseFactory.getErrorAsJson(EndSessionErrorResponseType.INVALID_REQUEST)) + .build()); + } + + List scopesAllowedIds = scopeService.getScopeIdsByDns(Arrays.asList(sessionClient.getClient().getScopes())); + + if (!scopesAllowedIds.contains(Constants.GLOBAL_TOKEN_REVOCATION_SCOPE)) { + log.debug("Client does not have required global_token_revocation scope."); + throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED.getStatusCode()) + .entity(errorResponseFactory.getErrorAsJson(EndSessionErrorResponseType.INVALID_REQUEST)) + .build()); + } + } + + private GlobalTokenRevocationRequest parseRequest(String requestAsString) { + final ObjectMapper mapper = ServerUtil.createJsonMapper().configure(SerializationFeature.WRAP_ROOT_VALUE, false); + try { + return mapper.readValue(requestAsString, GlobalTokenRevocationRequest.class); + } catch (IOException e) { + log.error("Failed to parse " + requestAsString, e); + } + + throw errorResponseFactory.createWebApplicationException(Response.Status.BAD_REQUEST, AuthorizeErrorResponseType.INVALID_REQUEST, "Failed to parse GlobalTokenRevocationRequest."); + } +} diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/DiscoveryService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/DiscoveryService.java index 56d44d3c846..280dadbd95d 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/DiscoveryService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/DiscoveryService.java @@ -78,6 +78,8 @@ public JSONObject process() { jsonObj.put(REVOCATION_ENDPOINT, appConfiguration.getTokenRevocationEndpoint()); if (appConfiguration.isFeatureEnabled(FeatureFlagType.REVOKE_SESSION)) jsonObj.put(SESSION_REVOCATION_ENDPOINT, endpointUrl("/revoke_session")); + if (appConfiguration.isFeatureEnabled(FeatureFlagType.GLOBAL_TOKEN_REVOCATION)) + jsonObj.put(GLOBAL_TOKEN_REVOCATION_ENDPOINT, endpointUrl("/global-token-revocation")); if (appConfiguration.isFeatureEnabled(FeatureFlagType.USERINFO)) jsonObj.put(USER_INFO_ENDPOINT, appConfiguration.getUserInfoEndpoint()); if (appConfiguration.isFeatureEnabled(FeatureFlagType.CLIENTINFO)) @@ -285,6 +287,8 @@ private void addMtlsAliases(JSONObject jsonObj) { aliases.put(REVOCATION_ENDPOINT, appConfiguration.getMtlsTokenRevocationEndpoint()); if (appConfiguration.isFeatureEnabled(FeatureFlagType.REVOKE_SESSION) && StringUtils.isNotBlank(appConfiguration.getMtlsEndSessionEndpoint())) aliases.put(SESSION_REVOCATION_ENDPOINT, StringUtils.replace(appConfiguration.getMtlsEndSessionEndpoint(), "/end_session", "/revoke_session")); + if (appConfiguration.isFeatureEnabled(FeatureFlagType.GLOBAL_TOKEN_REVOCATION)) + jsonObj.put(GLOBAL_TOKEN_REVOCATION_ENDPOINT, StringUtils.replace(appConfiguration.getMtlsEndSessionEndpoint(), "/end_session", "/global-token-revocation")); if (appConfiguration.isFeatureEnabled(FeatureFlagType.USERINFO) && StringUtils.isNotBlank(appConfiguration.getMtlsUserInfoEndpoint())) aliases.put(USER_INFO_ENDPOINT, appConfiguration.getMtlsUserInfoEndpoint()); if (appConfiguration.isFeatureEnabled(FeatureFlagType.CLIENTINFO) && StringUtils.isNotBlank(appConfiguration.getMtlsClientInfoEndpoint())) diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/GrantService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/GrantService.java index 2e0565dda24..0e8849da0a2 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/GrantService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/GrantService.java @@ -271,6 +271,19 @@ public List getGrantsBySessionDn(String sessionDn) { return grants; } + public List getGrantsByUserId(String userId) { + List grants = new ArrayList<>(); + try { + List tokenEntities = persistenceEntryManager.findEntries(tokenBaseDn(), TokenEntity.class, Filter.createEqualityFilter("ssnId", userId)); + if (tokenEntities != null) { + grants.addAll(tokenEntities); + } + } catch (Exception e) { + logException(e); + } + return grants; + } + public void logout(String sessionDn) { final List tokens = getGrantsBySessionDn(sessionDn); filterOutRefreshTokenFromDeletion(tokens); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/SessionIdService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/SessionIdService.java index 8745a0fe8d3..af9ac605cdf 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/SessionIdService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/SessionIdService.java @@ -858,6 +858,10 @@ public boolean remove(SessionId sessionId) { } public void remove(List list) { + if (list == null || list.isEmpty()) { + return; // nothing to do + } + for (SessionId id : list) { try { remove(id); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/servlet/FapiOpenIdConfiguration.java b/jans-auth-server/server/src/main/java/io/jans/as/server/servlet/FapiOpenIdConfiguration.java index d289f660788..4f1fd437ddc 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/servlet/FapiOpenIdConfiguration.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/servlet/FapiOpenIdConfiguration.java @@ -206,6 +206,7 @@ protected void processRequest(HttpServletRequest servletRequest, HttpServletResp jsonObj.put(TOKEN_ENDPOINT, appConfiguration.getTokenEndpoint()); jsonObj.put(REVOCATION_ENDPOINT, appConfiguration.getTokenRevocationEndpoint()); jsonObj.put(SESSION_REVOCATION_ENDPOINT, endpointUrl("/revoke_session")); + jsonObj.put(GLOBAL_TOKEN_REVOCATION_ENDPOINT, endpointUrl("/global-token-revocation")); jsonObj.put(USER_INFO_ENDPOINT, appConfiguration.getUserInfoEndpoint()); jsonObj.put(CLIENT_INFO_ENDPOINT, appConfiguration.getClientInfoEndpoint()); jsonObj.put(CHECK_SESSION_IFRAME, appConfiguration.getCheckSessionIFrame()); diff --git a/jans-linux-setup/jans_setup/setup_app/test_data_loader.py b/jans-linux-setup/jans_setup/setup_app/test_data_loader.py index 6b1300a5d6e..0d8310a33a0 100644 --- a/jans-linux-setup/jans_setup/setup_app/test_data_loader.py +++ b/jans-linux-setup/jans_setup/setup_app/test_data_loader.py @@ -298,7 +298,7 @@ def load_test_data(self): 'tokenEndpointAuthMethodsSupported': [ 'client_secret_basic', 'client_secret_post', 'client_secret_jwt', 'private_key_jwt', 'tls_client_auth', 'self_signed_tls_client_auth', 'none' ], 'sessionIdRequestParameterEnabled': True, 'skipRefreshTokenDuringRefreshing': False, - 'featureFlags': ['unknown', 'health_check', 'userinfo', 'clientinfo', 'id_generation', 'registration', 'introspection', 'revoke_token', 'revoke_session', 'end_session', 'status_session', 'jans_configuration', 'ciba', 'uma', 'u2f', 'device_authz', 'stat', 'par', 'ssa'], + 'featureFlags': ['unknown', 'health_check', 'userinfo', 'clientinfo', 'id_generation', 'registration', 'introspection', 'revoke_token', 'revoke_session', 'global_token_revocation', 'end_session', 'status_session', 'jans_configuration', 'ciba', 'uma', 'u2f', 'device_authz', 'stat', 'par', 'ssa'], 'cleanServiceInterval':7200, 'loggingLevel': 'TRACE', } diff --git a/jans-linux-setup/jans_setup/templates/jans-auth/jans-auth-config.json b/jans-linux-setup/jans_setup/templates/jans-auth/jans-auth-config.json index ae08f68fb10..0d56d16e628 100644 --- a/jans-linux-setup/jans_setup/templates/jans-auth/jans-auth-config.json +++ b/jans-linux-setup/jans_setup/templates/jans-auth/jans-auth-config.json @@ -8,6 +8,7 @@ "introspection", "revoke_token", "revoke_session", + "global_token_revocation", "active_session", "end_session", "status_session", diff --git a/jans-linux-setup/jans_setup/templates/scopes.ldif b/jans-linux-setup/jans_setup/templates/scopes.ldif index 2a6ccf38c53..9523e48f2c8 100644 --- a/jans-linux-setup/jans_setup/templates/scopes.ldif +++ b/jans-linux-setup/jans_setup/templates/scopes.ldif @@ -163,6 +163,17 @@ jansScopeTyp: oauth objectClass: top objectClass: jansScope +dn: inum=7D92,ou=scopes,o=jans +description: global_token_revocation scope is required to be able call /global-token-revocation endpoint +displayName: global_token_revocation +inum: 7D92 +jansAttrs: {"spontaneousClientId":"","spontaneousClientScopes":[],"showInConfigurationEndpoint":true} +jansDefScope: false +jansId: global_token_revocation +jansScopeTyp: openid +objectClass: top +objectClass: jansScope + dn: inum=8A01,ou=scopes,o=jans description: View your mobile phone number. displayName: view_mobile_phone_number From 7431b15f3a9af86571b0fd9f67f0b5cf0493fd1d Mon Sep 17 00:00:00 2001 From: YuriyZ Date: Fri, 24 May 2024 14:23:48 +0300 Subject: [PATCH 2/9] feat(jans-auth-server): covered Global Token Revocation service with tests https://github.com/JanssenProject/jans/issues/8398 Signed-off-by: YuriyZ --- .../GlobalTokenRevocationRestWebService.java | 100 +------------- .../token/GlobalTokenRevocationService.java | 122 ++++++++++++++++++ .../GlobalTokenRevocationServiceTest.java | 115 +++++++++++++++++ .../server/src/test/resources/testng.xml | 2 + 4 files changed, 242 insertions(+), 97 deletions(-) create mode 100644 jans-auth-server/server/src/main/java/io/jans/as/server/service/token/GlobalTokenRevocationService.java create mode 100644 jans-auth-server/server/src/test/java/io/jans/as/server/service/token/GlobalTokenRevocationServiceTest.java diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/revoke/GlobalTokenRevocationRestWebService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/revoke/GlobalTokenRevocationRestWebService.java index 37b71cc46cc..48d37ac5200 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/revoke/GlobalTokenRevocationRestWebService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/revoke/GlobalTokenRevocationRestWebService.java @@ -1,34 +1,12 @@ package io.jans.as.server.revoke; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import io.jans.as.common.model.common.User; -import io.jans.as.common.model.session.SessionId; -import io.jans.as.model.authorize.AuthorizeErrorResponseType; -import io.jans.as.model.common.FeatureFlagType; -import io.jans.as.model.error.ErrorResponseFactory; -import io.jans.as.model.revoke.GlobalTokenRevocationRequest; -import io.jans.as.model.session.EndSessionErrorResponseType; -import io.jans.as.server.model.config.Constants; -import io.jans.as.server.model.session.SessionClient; -import io.jans.as.server.security.Identity; -import io.jans.as.server.service.GrantService; -import io.jans.as.server.service.ScopeService; -import io.jans.as.server.service.SessionIdService; -import io.jans.as.server.service.UserService; -import io.jans.as.server.util.ServerUtil; -import io.jans.model.token.TokenEntity; +import io.jans.as.server.service.token.GlobalTokenRevocationService; import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.apache.commons.lang.ArrayUtils; import org.slf4j.Logger; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - /** * @author Yuriy Z */ @@ -39,22 +17,7 @@ public class GlobalTokenRevocationRestWebService { private Logger log; @Inject - private UserService userService; - - @Inject - private SessionIdService sessionIdService; - - @Inject - private ErrorResponseFactory errorResponseFactory; - - @Inject - private Identity identity; - - @Inject - private ScopeService scopeService; - - @Inject - private GrantService grantService; + private GlobalTokenRevocationService globalTokenRevocationService; @POST @Path("/global-token-revocation") @@ -62,33 +25,7 @@ public class GlobalTokenRevocationRestWebService { @Produces({MediaType.APPLICATION_JSON}) public Response requestGlobalTokenRevocation(String requestAsString) { try { - log.debug("Attempt for global token revocation: request = {}, ", requestAsString); - - errorResponseFactory.validateFeatureEnabled(FeatureFlagType.GLOBAL_TOKEN_REVOCATION); - - validateAccess(); - - GlobalTokenRevocationRequest parsedRequest = parseRequest(requestAsString); - - final String key = parsedRequest.getSubId().getFormat(); - final String value = parsedRequest.getSubId().getId(); - - final User user = userService.getUserByAttribute(key, value); - if (user == null) { - log.trace("Unable to find user by {}={}", key, value); - return Response.noContent().build(); // no error because we don't want to disclose internal AS info about users - } - - // remove sessions - List sessionIdList = sessionIdService.findByUser(user.getDn()); - sessionIdService.remove(sessionIdList); - - log.debug("Revoked {} user's sessions (user: {})", sessionIdList != null ? sessionIdList.size() : 0, user.getUserId()); - - // remove tokens - final List grants = grantService.getGrantsByUserId(user.getUserId()); - grantService.removeSilently(grants); - + globalTokenRevocationService.requestGlobalTokenRevocation(requestAsString); return Response.noContent().build(); } catch (WebApplicationException e) { throw e; @@ -97,35 +34,4 @@ public Response requestGlobalTokenRevocation(String requestAsString) { return Response.status(500).build(); } } - - private void validateAccess() { - SessionClient sessionClient = identity.getSessionClient(); - if (sessionClient == null || sessionClient.getClient() == null || ArrayUtils.isEmpty(sessionClient.getClient().getScopes())) { - log.debug("Client failed to authenticate."); - throw new WebApplicationException( - Response.status(Response.Status.UNAUTHORIZED.getStatusCode()) - .entity(errorResponseFactory.getErrorAsJson(EndSessionErrorResponseType.INVALID_REQUEST)) - .build()); - } - - List scopesAllowedIds = scopeService.getScopeIdsByDns(Arrays.asList(sessionClient.getClient().getScopes())); - - if (!scopesAllowedIds.contains(Constants.GLOBAL_TOKEN_REVOCATION_SCOPE)) { - log.debug("Client does not have required global_token_revocation scope."); - throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED.getStatusCode()) - .entity(errorResponseFactory.getErrorAsJson(EndSessionErrorResponseType.INVALID_REQUEST)) - .build()); - } - } - - private GlobalTokenRevocationRequest parseRequest(String requestAsString) { - final ObjectMapper mapper = ServerUtil.createJsonMapper().configure(SerializationFeature.WRAP_ROOT_VALUE, false); - try { - return mapper.readValue(requestAsString, GlobalTokenRevocationRequest.class); - } catch (IOException e) { - log.error("Failed to parse " + requestAsString, e); - } - - throw errorResponseFactory.createWebApplicationException(Response.Status.BAD_REQUEST, AuthorizeErrorResponseType.INVALID_REQUEST, "Failed to parse GlobalTokenRevocationRequest."); - } } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/token/GlobalTokenRevocationService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/token/GlobalTokenRevocationService.java new file mode 100644 index 00000000000..6f9fb18a636 --- /dev/null +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/token/GlobalTokenRevocationService.java @@ -0,0 +1,122 @@ +package io.jans.as.server.service.token; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import io.jans.as.common.model.common.User; +import io.jans.as.common.model.session.SessionId; +import io.jans.as.model.authorize.AuthorizeErrorResponseType; +import io.jans.as.model.common.FeatureFlagType; +import io.jans.as.model.error.ErrorResponseFactory; +import io.jans.as.model.revoke.GlobalTokenRevocationRequest; +import io.jans.as.model.session.EndSessionErrorResponseType; +import io.jans.as.server.model.config.Constants; +import io.jans.as.server.model.session.SessionClient; +import io.jans.as.server.security.Identity; +import io.jans.as.server.service.GrantService; +import io.jans.as.server.service.ScopeService; +import io.jans.as.server.service.SessionIdService; +import io.jans.as.server.service.UserService; +import io.jans.as.server.util.ServerUtil; +import io.jans.model.token.TokenEntity; +import jakarta.ejb.Stateless; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Response; +import org.apache.commons.lang.ArrayUtils; +import org.slf4j.Logger; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** + * @author Yuriy Z + */ +@Stateless +@Named +public class GlobalTokenRevocationService { + + @Inject + private Logger log; + + @Inject + private UserService userService; + + @Inject + private SessionIdService sessionIdService; + + @Inject + private ErrorResponseFactory errorResponseFactory; + + @Inject + private Identity identity; + + @Inject + private ScopeService scopeService; + + @Inject + private GrantService grantService; + + public void requestGlobalTokenRevocation(String requestAsString) { + log.debug("Attempt for global token revocation: request = {}, ", requestAsString); + + errorResponseFactory.validateFeatureEnabled(FeatureFlagType.GLOBAL_TOKEN_REVOCATION); + + validateAccess(); + + GlobalTokenRevocationRequest parsedRequest = parseRequest(requestAsString); + + final String key = parsedRequest.getSubId().getFormat(); + final String value = parsedRequest.getSubId().getId(); + + final User user = userService.getUserByAttribute(key, value); + if (user == null) { + log.trace("Unable to find user by {}={}", key, value); + return; // no error because we don't want to disclose internal AS info about users + } + + // remove sessions + List sessionIdList = sessionIdService.findByUser(user.getDn()); + sessionIdService.remove(sessionIdList); + + log.debug("Revoked {} user's sessions (user: {})", sessionIdList != null ? sessionIdList.size() : 0, user.getUserId()); + + // remove tokens + final List grants = grantService.getGrantsByUserId(user.getUserId()); + grantService.removeSilently(grants); + + log.debug("Revoked {} tokens (user: {})", grants != null ? grants.size() : 0, user.getUserId()); + } + + public void validateAccess() { + SessionClient sessionClient = identity.getSessionClient(); + if (sessionClient == null || sessionClient.getClient() == null || ArrayUtils.isEmpty(sessionClient.getClient().getScopes())) { + log.debug("Client failed to authenticate."); + throw new WebApplicationException( + Response.status(Response.Status.UNAUTHORIZED.getStatusCode()) + .entity(errorResponseFactory.getErrorAsJson(EndSessionErrorResponseType.INVALID_REQUEST)) + .build()); + } + + List scopesAllowedIds = scopeService.getScopeIdsByDns(Arrays.asList(sessionClient.getClient().getScopes())); + + if (!scopesAllowedIds.contains(Constants.GLOBAL_TOKEN_REVOCATION_SCOPE)) { + log.debug("Client does not have required global_token_revocation scope."); + throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED.getStatusCode()) + .entity(errorResponseFactory.getErrorAsJson(EndSessionErrorResponseType.INVALID_REQUEST)) + .build()); + } + } + + public GlobalTokenRevocationRequest parseRequest(String requestAsString) { + final ObjectMapper mapper = ServerUtil.createJsonMapper().configure(SerializationFeature.WRAP_ROOT_VALUE, false); + try { + return mapper.readValue(requestAsString, GlobalTokenRevocationRequest.class); + } catch (IOException e) { + log.error("Failed to parse " + requestAsString, e); + } + + throw errorResponseFactory.createWebApplicationException(Response.Status.BAD_REQUEST, AuthorizeErrorResponseType.INVALID_REQUEST, "Failed to parse GlobalTokenRevocationRequest."); + } +} diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/service/token/GlobalTokenRevocationServiceTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/service/token/GlobalTokenRevocationServiceTest.java new file mode 100644 index 00000000000..d6dc415e924 --- /dev/null +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/service/token/GlobalTokenRevocationServiceTest.java @@ -0,0 +1,115 @@ +package io.jans.as.server.service.token; + +import com.google.common.collect.Lists; +import io.jans.as.common.model.registration.Client; +import io.jans.as.model.error.ErrorResponseFactory; +import io.jans.as.model.revoke.GlobalTokenRevocationRequest; +import io.jans.as.model.session.EndSessionErrorResponseType; +import io.jans.as.server.model.session.SessionClient; +import io.jans.as.server.security.Identity; +import io.jans.as.server.service.GrantService; +import io.jans.as.server.service.ScopeService; +import io.jans.as.server.service.SessionIdService; +import io.jans.as.server.service.UserService; +import jakarta.ws.rs.WebApplicationException; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.slf4j.Logger; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import java.util.Arrays; + +import static org.mockito.Mockito.when; +import static org.testng.AssertJUnit.assertEquals; + +/** + * @author Yuriy Z + */ +@Listeners(MockitoTestNGListener.class) +public class GlobalTokenRevocationServiceTest { + + @InjectMocks + private GlobalTokenRevocationService globalTokenRevocationService; + + @Mock + private Logger log; + + @Mock + private UserService userService; + + @Mock + private SessionIdService sessionIdService; + + @Mock + private ErrorResponseFactory errorResponseFactory; + + @Mock + private Identity identity; + + @Mock + private ScopeService scopeService; + + @Mock + private GrantService grantService; + + @Test + public void parseRequest_forValidRequest_shouldParseCorrectly() { + String json = "{\n" + + " \"sub_id\": {\n" + + " \"format\": \"mail\",\n" + + " \"id\": \"user@example.com\"\n" + + " }\n" + + "}"; + + final GlobalTokenRevocationRequest request = globalTokenRevocationService.parseRequest(json); + + assertEquals("mail", request.getSubId().getFormat()); + assertEquals("user@example.com", request.getSubId().getId()); + } + + @Test + public void validateAccess_withClientThatHasValidScope_shouldPassSuccessfully() { + String[] scopes = {"11"}; + + Client client = new Client(); + client.setScopes(scopes); + + SessionClient sessionClient = new SessionClient(); + sessionClient.setClient(client); + + when(identity.getSessionClient()).thenReturn(sessionClient); + when(scopeService.getScopeIdsByDns(Arrays.asList(scopes))).thenReturn(Lists.newArrayList("global_token_revocation")); + + globalTokenRevocationService.validateAccess();; + } + + @Test(expectedExceptions = WebApplicationException.class) + public void validateAccess_withClientThatHasInvalidScope_shouldThrowError() { + String[] scopes = {"22"}; + + Client client = new Client(); + client.setScopes(scopes); + + SessionClient sessionClient = new SessionClient(); + sessionClient.setClient(client); + + when(identity.getSessionClient()).thenReturn(sessionClient); + when(scopeService.getScopeIdsByDns(Arrays.asList(scopes))).thenReturn(Lists.newArrayList("edit")); + when(errorResponseFactory.getErrorAsJson(EndSessionErrorResponseType.INVALID_REQUEST)).thenReturn("{}"); + + globalTokenRevocationService.validateAccess();; + } + + @Test(expectedExceptions = WebApplicationException.class) + public void validateAccess_withoutAuthenticatedClient_shouldThrowException() { + String[] scopes = {"11"}; + + when(identity.getSessionClient()).thenReturn(null); + when(scopeService.getScopeIdsByDns(Arrays.asList(scopes))).thenReturn(Lists.newArrayList("global_token_revocation")); + when(errorResponseFactory.getErrorAsJson(EndSessionErrorResponseType.INVALID_REQUEST)).thenReturn("{}"); + + globalTokenRevocationService.validateAccess();; + } +} diff --git a/jans-auth-server/server/src/test/resources/testng.xml b/jans-auth-server/server/src/test/resources/testng.xml index 96ee7bf821c..4df7b212808 100644 --- a/jans-auth-server/server/src/test/resources/testng.xml +++ b/jans-auth-server/server/src/test/resources/testng.xml @@ -26,6 +26,8 @@ + + From 8ed8d8ab7e680b2a9295374bc79e1e4c60940486 Mon Sep 17 00:00:00 2001 From: YuriyZ Date: Fri, 24 May 2024 14:45:59 +0300 Subject: [PATCH 3/9] fix(jans-auth-server): corrected RedirectUriTest https://github.com/JanssenProject/jans/issues/8398 Signed-off-by: YuriyZ --- .../jans/as/common/util/RedirectUriTest.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/jans-auth-server/common/src/test/java/io/jans/as/common/util/RedirectUriTest.java b/jans-auth-server/common/src/test/java/io/jans/as/common/util/RedirectUriTest.java index c60aeca9e7c..d1229bd49eb 100644 --- a/jans-auth-server/common/src/test/java/io/jans/as/common/util/RedirectUriTest.java +++ b/jans-auth-server/common/src/test/java/io/jans/as/common/util/RedirectUriTest.java @@ -109,7 +109,7 @@ public void getQueryString_responseModeFormPostJWT_responseEncodedNoEmpty() thro System.out.println(queryResult); //No empty Result assertTrue(queryResult.length() > 0); - assertEquals(queryResult, "eyJraWQiOiJrZXkxMjMiLCJ0eXAiOiJqd3QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ1c2VyMTIzIiwiZXhwIjoiMzAwMCIsImV4cGlyZXNfaW4iOiIzMDAwIn0.12345"); + assertEquals(queryResult, "eyJraWQiOiJrZXkxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ1c2VyMTIzIiwiZXhwIjoiMzAwMCIsImV4cGlyZXNfaW4iOiIzMDAwIn0.12345"); } @Test @@ -137,7 +137,7 @@ public void getQueryString_withEncriptionAlgorithmRSANoSignatureAlgorithm_respon String queryResult = redirectUri.getQueryString(); System.out.println(queryResult); assertNoEmptyQueryString(queryResult, RESPONSE, 1); - assertTrue(queryResult.startsWith("response=eyJ0eXAiOiJqd3QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBMV81In0.")); + assertTrue(queryResult.startsWith("response=eyJ0eXAiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBMV81In0.")); } @Test @@ -154,7 +154,7 @@ public void getQueryString_withEncriptionAlgorithm128NoSignatureAlgorithm_respon String queryResult = redirectUri.getQueryString(); System.out.println(queryResult); assertNoEmptyQueryString(queryResult, RESPONSE, 1); - assertTrue(queryResult.startsWith("response=eyJ0eXAiOiJqd3QiLCJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTEyOEtXIn0.")); + assertTrue(queryResult.startsWith("response=eyJ0eXAiOiJKV1QiLCJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTEyOEtXIn0.")); } @Test @@ -171,7 +171,7 @@ public void getQueryString_withEncriptionAlgorithmRSAAndSignatureAlgorithm_respo String queryResult = redirectUri.getQueryString(); System.out.println(queryResult); assertNoEmptyQueryString(queryResult, RESPONSE, 1); - assertTrue(queryResult.startsWith("response=eyJjdHkiOiJqd3QiLCJ0eXAiOiJqd3QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBMV81In0.")); + assertTrue(queryResult.startsWith("response=eyJjdHkiOiJKV1QiLCJ0eXAiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBMV81In0.")); } @Test @@ -190,7 +190,7 @@ public void getQueryString_withEncriptionAlgorithm128KWAndSignatureAlgorithm_res System.out.println(queryResult); assertNoEmptyQueryString(queryResult, RESPONSE, 1); - assertTrue(queryResult.startsWith("response=eyJjdHkiOiJqd3QiLCJ0eXAiOiJqd3QiLCJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTEyOEtXIn0.")); + assertTrue(queryResult.startsWith("response=eyJjdHkiOiJKV1QiLCJ0eXAiOiJKV1QiLCJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTEyOEtXIn0.")); } @Test @@ -204,12 +204,12 @@ public void getQueryString_noEncriptionAlgorithmNoSignatureAlgorithm_responseEnc System.out.println(queryResult); assertNoEmptyQueryString(queryResult, RESPONSE, 1); - assertTrue(queryResult.startsWith("response=eyJraWQiOiJrZXkxMjMiLCJ0eXAiOiJqd3QiLCJhbGciOiJSUzI1NiJ9.")); + assertTrue(queryResult.startsWith("response=eyJraWQiOiJrZXkxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.")); } @Test public void toString_withResponseModeFormPostJwt_validHtmlFormResponse() throws CryptoProviderException { - String valTestCase = "Submit This Form
"; + String expected = "Submit This Form
"; RedirectUri redirectUri = getRedirectUriTemplateToString(); redirectUri.setResponseMode(ResponseMode.FORM_POST_JWT); AbstractCryptoProvider cryptoProvider = mock(AbstractCryptoProvider.class); @@ -219,7 +219,7 @@ public void toString_withResponseModeFormPostJwt_validHtmlFormResponse() throws redirectUri.setSharedSecret("shared_secret"); redirectUri.setNestedSharedSecret("nested_shared_secret"); - assertEquals(redirectUri.toString(), valTestCase); + assertEquals(redirectUri.toString(), expected); } @Test @@ -251,7 +251,7 @@ public void toString_withResponseModeQuery_validURLQueryString() { @Test public void toString_withResponseModeJwtAndresponseTypeToken_validURLFragmentString() throws CryptoProviderException { - String valTestCase = "http://redirecturl.com/#response=eyJraWQiOiJrZXkxMjMiLCJ0eXAiOiJqd3QiLCJhbGciOiJSUzI1NiJ9.eyJleHAiOiIxNjQ0MjcwNDczMzAxIiwiZXhwaXJlc19pbiI6IjE2NDQyNzA0NzMzMDEiLCJjbGllbnRfaWQiOiIxMjMifQ.12345"; + String expected = "http://redirecturl.com/#response=eyJraWQiOiJrZXkxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJleHAiOiIxNjQ0MjcwNDczMzAxIiwiZXhwaXJlc19pbiI6IjE2NDQyNzA0NzMzMDEiLCJjbGllbnRfaWQiOiIxMjMifQ.12345"; List typeList = new ArrayList<>(); typeList.add(ResponseType.TOKEN); RedirectUri redirectUri = new RedirectUri("http://redirecturl.com/", typeList, ResponseMode.JWT); @@ -265,12 +265,12 @@ public void toString_withResponseModeJwtAndresponseTypeToken_validURLFragmentStr when(cryptoProvider.sign(anyString(), anyString(), anyString(), any(SignatureAlgorithm.class))).thenReturn("12345"); redirectUri.setCryptoProvider(cryptoProvider); - assertEquals(redirectUri.toString(), valTestCase); + assertEquals(redirectUri.toString(), expected); } @Test public void toString_withResponseModeJwtAndresponseTypeCode_validURLQueryString() throws CryptoProviderException { - String valTestCase = "http://redirecturl.com/?response=eyJraWQiOiJrZXkxMjMiLCJ0eXAiOiJqd3QiLCJhbGciOiJSUzI1NiJ9.eyJleHAiOiIxNjQ0MjcwNDczMzAxIiwiZXhwaXJlc19pbiI6IjE2NDQyNzA0NzMzMDEiLCJjbGllbnRfaWQiOiIxMjMifQ.12345"; + String expected = "http://redirecturl.com/?response=eyJraWQiOiJrZXkxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJleHAiOiIxNjQ0MjcwNDczMzAxIiwiZXhwaXJlc19pbiI6IjE2NDQyNzA0NzMzMDEiLCJjbGllbnRfaWQiOiIxMjMifQ.12345"; List typeList = new ArrayList<>(); typeList.add(ResponseType.CODE); RedirectUri redirectUri = new RedirectUri("http://redirecturl.com/", typeList, ResponseMode.JWT); @@ -283,7 +283,7 @@ public void toString_withResponseModeJwtAndresponseTypeCode_validURLQueryString( when(cryptoProvider.sign(anyString(), anyString(), anyString(), any(SignatureAlgorithm.class))).thenReturn("12345"); redirectUri.setCryptoProvider(cryptoProvider); - assertEquals(redirectUri.toString(), valTestCase); + assertEquals(redirectUri.toString(), expected); } @Test From a963dd4c94eac9abd2cac3434c6e3fc8a0cc577a Mon Sep 17 00:00:00 2001 From: YuriyZ Date: Fri, 24 May 2024 16:25:35 +0300 Subject: [PATCH 4/9] fix(jans-auth-server): corrected TxTokenHttpTest https://github.com/JanssenProject/jans/issues/8398 Signed-off-by: YuriyZ --- .../auth-server/tokens/oauth-tx-tokens.md | 2 +- docs/assets/log/tx-token-replace-run-log.txt | 4 +- docs/assets/log/tx-token-request-run-log.txt | 2 +- .../test/java/io/jans/as/client/BaseTest.java | 1 + .../client/ws/rs/token/TxTokenHttpTest.java | 6 +-- .../io/jans/as/model/common/GrantType.java | 5 --- .../model/common/AuthorizationGrantList.java | 15 +------ .../model/common/IAuthorizationGrantList.java | 2 - .../as/server/model/common/TxTokenGrant.java | 43 ------------------- .../as/server/token/ws/rs/TxTokenService.java | 2 +- 10 files changed, 10 insertions(+), 72 deletions(-) delete mode 100644 jans-auth-server/server/src/main/java/io/jans/as/server/model/common/TxTokenGrant.java diff --git a/docs/admin/auth-server/tokens/oauth-tx-tokens.md b/docs/admin/auth-server/tokens/oauth-tx-tokens.md index 4e84d1dc42a..74906c37eab 100644 --- a/docs/admin/auth-server/tokens/oauth-tx-tokens.md +++ b/docs/admin/auth-server/tokens/oauth-tx-tokens.md @@ -94,7 +94,7 @@ POST /jans-auth/restv1/token HTTP/1.1 Host: yuriyz-adjusted-coyote.gluu.info Content-Type: application/x-www-form-urlencoded -grant_type=tx_token&audience=http%3A%2F%2Ftrusted.com&subject_token=5fb696ac-638a-4dbf-81cd-27daeb61caf9&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Atxn_token&rctx=%7B%22req_ip%22%3A%2269.151.72.123%22%7D +grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&audience=http%3A%2F%2Ftrusted.com&subject_token=5fb696ac-638a-4dbf-81cd-27daeb61caf9&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Atxn_token&rctx=%7B%22req_ip%22%3A%2269.151.72.123%22%7D ``` **Sample response** diff --git a/docs/assets/log/tx-token-replace-run-log.txt b/docs/assets/log/tx-token-replace-run-log.txt index 93b00276c42..360c44a44af 100644 --- a/docs/assets/log/tx-token-replace-run-log.txt +++ b/docs/assets/log/tx-token-replace-run-log.txt @@ -246,7 +246,7 @@ POST /jans-auth/restv1/token HTTP/1.1 Host: yuriyz-adjusted-coyote.gluu.info Content-Type: application/x-www-form-urlencoded -grant_type=tx_token&audience=http%3A%2F%2Ftrusted.com&subject_token=15c8e08a-fc90-4e34-b378-4c9e8f603e2a&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Atxn_token&request_context=eyAiaXBfYWRkcmVzcyI6ICIxMjcuMC4wLjEiLCAiY2xpZW50IjogIm1vYmlsZS1hcHAiLCAiY2xpZW50X3ZlcnNpb24iOiAidjExIiB9 +grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&audience=http%3A%2F%2Ftrusted.com&subject_token=15c8e08a-fc90-4e34-b378-4c9e8f603e2a&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Atxn_token&request_context=eyAiaXBfYWRkcmVzcyI6ICIxMjcuMC4wLjEiLCAiY2xpZW50IjogIm1vYmlsZS1hcHAiLCAiY2xpZW50X3ZlcnNpb24iOiAidjExIiB9 ------------------------------------------------------- RESPONSE: @@ -282,7 +282,7 @@ POST /jans-auth/restv1/token HTTP/1.1 Host: yuriyz-adjusted-coyote.gluu.info Content-Type: application/x-www-form-urlencoded -grant_type=tx_token&audience=http%3A%2F%2Ftrusted2.com&subject_token=eyJraWQiOiJjb25uZWN0XzBlZGMxOTIyLTk1MjAtNDFkNi1iZGMyLTk3ZjdmYWMwMzRkMl9zaWdfcnMyNTYiLCJ0eXAiOiJqd3QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsiN2ZmNTM1ZGQtODk0ZS00OTg2LThjZjQtNzFiYzRiNjUxZWE0IiwiaHR0cDovL3RydXN0ZWQuY29tIl0sInJlcV9jdHgiOnsicmVxX2lwIjoiNjkuMTUxLjcyLjEyMyJ9LCJzdWJfaWQiOiIiLCJpc3MiOiJodHRwczovL3l1cml5ei1hZGp1c3RlZC1jb3lvdGUuZ2x1dS5pbmZvIiwiYXpkIjp7ImNsaWVudF9pZCI6IjdmZjUzNWRkLTg5NGUtNDk4Ni04Y2Y0LTcxYmM0YjY1MWVhNCJ9LCJ0eG4iOiI2OWFhZjk4Ni00OTkxLTRhZDYtYmZlZi1mMjJjZjY1NGE3NTkiLCJleHAiOjE3MDUwNTQ5MzMsImlhdCI6MTcwNTA1NDc1M30.Q3KndMBp4Md2OhWi-hvEWsbtj8lOtuyjCZxyZeSI9YbIHuZNdb4a_7S3TlLF8cVNvtgWTyCxn0lSZprupMy-om-6EcWbkYW0xCnqe2OwX_8aTn3HaPFvHC-orpMvOXsV5goFHD9KiWOQcQoijPeQC4TlhiwIsyVw9z81HfpAzjwJ0CZW7dgzM8tWT9K08aSMDbO8k2m6RvkqaRh8t60skEtyaaTQqlyc1zlW8VTXDSjCay2sEDcU_DkSzBduEqHr67b8zxKgi83KAiqGhlAey913iXI2V4UIIS6MegvcF9Z7APjVZXw5EyNz6Xc_eg31gyxYR4hbYBOqDxiGtikqAQ&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Atxn_token&request_context=eyAiaXBfYWRkcmVzcyI6ICIxMjcuMC4wLjEiLCAiY2xpZW50IjogIm1vYmlsZS1hcHAiLCAiY2xpZW50X3ZlcnNpb24iOiAidjExIiB9 +grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&audience=http%3A%2F%2Ftrusted2.com&subject_token=eyJraWQiOiJjb25uZWN0XzBlZGMxOTIyLTk1MjAtNDFkNi1iZGMyLTk3ZjdmYWMwMzRkMl9zaWdfcnMyNTYiLCJ0eXAiOiJqd3QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsiN2ZmNTM1ZGQtODk0ZS00OTg2LThjZjQtNzFiYzRiNjUxZWE0IiwiaHR0cDovL3RydXN0ZWQuY29tIl0sInJlcV9jdHgiOnsicmVxX2lwIjoiNjkuMTUxLjcyLjEyMyJ9LCJzdWJfaWQiOiIiLCJpc3MiOiJodHRwczovL3l1cml5ei1hZGp1c3RlZC1jb3lvdGUuZ2x1dS5pbmZvIiwiYXpkIjp7ImNsaWVudF9pZCI6IjdmZjUzNWRkLTg5NGUtNDk4Ni04Y2Y0LTcxYmM0YjY1MWVhNCJ9LCJ0eG4iOiI2OWFhZjk4Ni00OTkxLTRhZDYtYmZlZi1mMjJjZjY1NGE3NTkiLCJleHAiOjE3MDUwNTQ5MzMsImlhdCI6MTcwNTA1NDc1M30.Q3KndMBp4Md2OhWi-hvEWsbtj8lOtuyjCZxyZeSI9YbIHuZNdb4a_7S3TlLF8cVNvtgWTyCxn0lSZprupMy-om-6EcWbkYW0xCnqe2OwX_8aTn3HaPFvHC-orpMvOXsV5goFHD9KiWOQcQoijPeQC4TlhiwIsyVw9z81HfpAzjwJ0CZW7dgzM8tWT9K08aSMDbO8k2m6RvkqaRh8t60skEtyaaTQqlyc1zlW8VTXDSjCay2sEDcU_DkSzBduEqHr67b8zxKgi83KAiqGhlAey913iXI2V4UIIS6MegvcF9Z7APjVZXw5EyNz6Xc_eg31gyxYR4hbYBOqDxiGtikqAQ&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Atxn_token&request_context=eyAiaXBfYWRkcmVzcyI6ICIxMjcuMC4wLjEiLCAiY2xpZW50IjogIm1vYmlsZS1hcHAiLCAiY2xpZW50X3ZlcnNpb24iOiAidjExIiB9 ------------------------------------------------------- RESPONSE: diff --git a/docs/assets/log/tx-token-request-run-log.txt b/docs/assets/log/tx-token-request-run-log.txt index a67ce046d19..48ed2b3eeda 100644 --- a/docs/assets/log/tx-token-request-run-log.txt +++ b/docs/assets/log/tx-token-request-run-log.txt @@ -246,7 +246,7 @@ POST /jans-auth/restv1/token HTTP/1.1 Host: yuriyz-adjusted-coyote.gluu.info Content-Type: application/x-www-form-urlencoded -grant_type=tx_token&audience=http%3A%2F%2Ftrusted.com&subject_token=5fb696ac-638a-4dbf-81cd-27daeb61caf9&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Atxn_token&request_context=eyAiaXBfYWRkcmVzcyI6ICIxMjcuMC4wLjEiLCAiY2xpZW50IjogIm1vYmlsZS1hcHAiLCAiY2xpZW50X3ZlcnNpb24iOiAidjExIiB9 +grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&audience=http%3A%2F%2Ftrusted.com&subject_token=5fb696ac-638a-4dbf-81cd-27daeb61caf9&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Atxn_token&request_context=eyAiaXBfYWRkcmVzcyI6ICIxMjcuMC4wLjEiLCAiY2xpZW50IjogIm1vYmlsZS1hcHAiLCAiY2xpZW50X3ZlcnNpb24iOiAidjExIiB9 ------------------------------------------------------- RESPONSE: diff --git a/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java b/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java index 693804b15f2..0c5f60e8832 100644 --- a/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java @@ -263,6 +263,7 @@ public void initTestSuite(ITestContext context) throws IOException { if (StringHelper.isEmpty(propertiesFile)) { propertiesFile = "target/test-classes/testng.properties"; } + propertiesFile = "U:\\janssen\\jans\\jans-auth-server\\client\\src\\test\\resources\\easycloud.properties"; FileInputStream conf = new FileInputStream(propertiesFile); Properties prop = new Properties(); diff --git a/jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/token/TxTokenHttpTest.java b/jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/token/TxTokenHttpTest.java index 7d645eacdcd..e6b8ed1527c 100644 --- a/jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/token/TxTokenHttpTest.java +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/token/TxTokenHttpTest.java @@ -58,7 +58,7 @@ public void txTokenRequest(final String redirectUris) throws InvalidJwtException String subjectToken = subjectTokenResponse.getAccessToken(); // 3. Request tx token using the subject token - TokenRequest txTokenRequest = new TokenRequest(GrantType.TX_TOKEN); + TokenRequest txTokenRequest = new TokenRequest(GrantType.TOKEN_EXCHANGE); txTokenRequest.setSubjectToken(subjectToken); txTokenRequest.setSubjectTokenType(SubjectTokenType.ACCESS_TOKEN.getName()); txTokenRequest.setRequestedTokenType(ExchangeTokenType.TX_TOKEN.getName()); @@ -91,7 +91,7 @@ public void txTokenRequest(final String redirectUris) throws InvalidJwtException public void txTokenReplace() throws InvalidJwtException { showTitle("txTokenReplace"); - TokenRequest txTokenRequest = new TokenRequest(GrantType.TX_TOKEN); + TokenRequest txTokenRequest = new TokenRequest(GrantType.TOKEN_EXCHANGE); txTokenRequest.setSubjectToken(txToken); txTokenRequest.setSubjectTokenType(SubjectTokenType.ACCESS_TOKEN.getName()); txTokenRequest.setRequestedTokenType(ExchangeTokenType.TX_TOKEN.getName()); @@ -114,7 +114,7 @@ public RegisterResponse registerClient(final String redirectUris, List Date: Fri, 24 May 2024 16:27:23 +0300 Subject: [PATCH 5/9] removed line used for debug Signed-off-by: YuriyZ --- .../client/src/test/java/io/jans/as/client/BaseTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java b/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java index 0c5f60e8832..693804b15f2 100644 --- a/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java @@ -263,7 +263,6 @@ public void initTestSuite(ITestContext context) throws IOException { if (StringHelper.isEmpty(propertiesFile)) { propertiesFile = "target/test-classes/testng.properties"; } - propertiesFile = "U:\\janssen\\jans\\jans-auth-server\\client\\src\\test\\resources\\easycloud.properties"; FileInputStream conf = new FileInputStream(propertiesFile); Properties prop = new Properties(); From ae71ccb956e297e474e63865747bc3b0bf4b84d6 Mon Sep 17 00:00:00 2001 From: YuriyZ Date: Fri, 24 May 2024 18:06:05 +0300 Subject: [PATCH 6/9] feat(jans-auth-server): added global token revocation client https://github.com/JanssenProject/jans/issues/8398 Signed-off-by: YuriyZ --- .../client/GlobalTokenRevocationClient.java | 69 +++++++++++++++++++ .../GlobalTokenRevocationClientRequest.java | 60 ++++++++++++++++ .../client/GlobalTokenRevocationResponse.java | 27 ++++++++ 3 files changed, 156 insertions(+) create mode 100644 jans-auth-server/client/src/main/java/io/jans/as/client/GlobalTokenRevocationClient.java create mode 100644 jans-auth-server/client/src/main/java/io/jans/as/client/GlobalTokenRevocationClientRequest.java create mode 100644 jans-auth-server/client/src/main/java/io/jans/as/client/GlobalTokenRevocationResponse.java diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/GlobalTokenRevocationClient.java b/jans-auth-server/client/src/main/java/io/jans/as/client/GlobalTokenRevocationClient.java new file mode 100644 index 00000000000..5d2592c59a6 --- /dev/null +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/GlobalTokenRevocationClient.java @@ -0,0 +1,69 @@ +package io.jans.as.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jans.as.model.common.SubId; +import io.jans.as.model.revoke.GlobalTokenRevocationRequest; +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.Invocation; +import org.apache.log4j.Logger; + +/** + * @author Yuriy Z + */ +public class GlobalTokenRevocationClient extends BaseClient { + + private static final Logger LOG = Logger.getLogger(GlobalTokenRevocationClient.class); + private static final ObjectMapper MAPPER = new ObjectMapper(); + + /** + * Constructs a client by providing a REST url where the global token revocation endpoint + * is located. + * + * @param url global token revocation endpoint + */ + public GlobalTokenRevocationClient(String url) { + super(url); + } + + @Override + public String getHttpMethod() { + return HttpMethod.POST; + } + + public GlobalTokenRevocationResponse exec(GlobalTokenRevocationClientRequest request) { + setRequest(request); + return exec(); + } + + public GlobalTokenRevocationResponse exec() { + initClient(); + + Invocation.Builder clientRequest = webTarget.request(); + applyCookies(clientRequest); + + new ClientAuthnEnabler(clientRequest, requestForm).exec(request); + + clientRequest.header("Content-Type", request.getContentType()); + + SubId subId = new SubId(); + subId.setFormat(getRequest().getFormat()); + subId.setId(getRequest().getId()); + + GlobalTokenRevocationRequest model = new GlobalTokenRevocationRequest(); + model.setSubId(subId); + + try { + clientResponse = clientRequest.buildPost(Entity.json(MAPPER.writeValueAsString(model))).invoke(); + + final GlobalTokenRevocationResponse response = new GlobalTokenRevocationResponse(clientResponse); + setResponse(response); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + } finally { + closeConnection(); + } + + return getResponse(); + } +} diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/GlobalTokenRevocationClientRequest.java b/jans-auth-server/client/src/main/java/io/jans/as/client/GlobalTokenRevocationClientRequest.java new file mode 100644 index 00000000000..25f4ccefdd2 --- /dev/null +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/GlobalTokenRevocationClientRequest.java @@ -0,0 +1,60 @@ +package io.jans.as.client; + +import io.jans.as.model.util.QueryBuilder; +import jakarta.ws.rs.core.MediaType; + +/** + * @author Yuriy Z + */ +public class GlobalTokenRevocationClientRequest extends ClientAuthnRequest { + + private String format; + private String id; + + public GlobalTokenRevocationClientRequest() { + this(null, null); + } + + public GlobalTokenRevocationClientRequest(String format, String id) { + this.format = format; + this.id = id; + + setContentType(MediaType.APPLICATION_JSON); + setMediaType(MediaType.APPLICATION_JSON); + } + + @Override + public String getQueryString() { + QueryBuilder builder = QueryBuilder.instance(); + + builder.append("format", format); + builder.append("id", id); + appendClientAuthnToQuery(builder); + + return builder.toString(); + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String toString() { + return "GlobalTokenRevocationRequest{" + + "format='" + format + '\'' + + ", id='" + id + '\'' + + "} " + super.toString(); + } +} diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/GlobalTokenRevocationResponse.java b/jans-auth-server/client/src/main/java/io/jans/as/client/GlobalTokenRevocationResponse.java new file mode 100644 index 00000000000..f05ea37d38e --- /dev/null +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/GlobalTokenRevocationResponse.java @@ -0,0 +1,27 @@ +package io.jans.as.client; + +import io.jans.as.model.session.EndSessionErrorResponseType; +import jakarta.ws.rs.core.Response; + +/** + * @author Yuriy Z + */ +public class GlobalTokenRevocationResponse extends BaseResponseWithErrors { + + public GlobalTokenRevocationResponse() { + } + + public GlobalTokenRevocationResponse(Response clientResponse) { + super(clientResponse); + injectDataFromJson(); + } + + @Override + public EndSessionErrorResponseType fromString(String params) { + return EndSessionErrorResponseType.fromString(params); + } + + public void injectDataFromJson() { + injectDataFromJson(entity); + } +} From bd515dc408cf0ea88ef05ea4db69e2c21ab938ee Mon Sep 17 00:00:00 2001 From: YuriyZ Date: Fri, 24 May 2024 19:24:34 +0300 Subject: [PATCH 7/9] fix(jans-auth-server): corrected configs https://github.com/JanssenProject/jans/issues/8398 Signed-off-by: YuriyZ --- jans-auth-server/server/conf/jans-config.json | 3 ++- .../jans_setup/templates/jans-auth/jans-auth-config.json | 3 ++- jans-linux-setup/jans_setup/templates/scopes.ldif | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/jans-auth-server/server/conf/jans-config.json b/jans-auth-server/server/conf/jans-config.json index 39801452327..76c7f7d947f 100644 --- a/jans-auth-server/server/conf/jans-config.json +++ b/jans-auth-server/server/conf/jans-config.json @@ -18,7 +18,8 @@ "metric", "stat", "par", - "ssa" + "ssa", + "global_token_revocation" ], "issuer":"${config.oxauth.issuer}", "loginPage":"${config.oxauth.contextPath}/login.htm", diff --git a/jans-linux-setup/jans_setup/templates/jans-auth/jans-auth-config.json b/jans-linux-setup/jans_setup/templates/jans-auth/jans-auth-config.json index 0d56d16e628..20ad97b6c38 100644 --- a/jans-linux-setup/jans_setup/templates/jans-auth/jans-auth-config.json +++ b/jans-linux-setup/jans_setup/templates/jans-auth/jans-auth-config.json @@ -18,7 +18,8 @@ "metric", "stat", "par", - "ssa" + "ssa", + "global_token_revocation" ], "issuer":"https://%(hostname)s", "baseEndpoint":"https://%(hostname)s/jans-auth/restv1", diff --git a/jans-linux-setup/jans_setup/templates/scopes.ldif b/jans-linux-setup/jans_setup/templates/scopes.ldif index 9523e48f2c8..6c6ba73e7be 100644 --- a/jans-linux-setup/jans_setup/templates/scopes.ldif +++ b/jans-linux-setup/jans_setup/templates/scopes.ldif @@ -168,7 +168,7 @@ description: global_token_revocation scope is required to be able call /global-t displayName: global_token_revocation inum: 7D92 jansAttrs: {"spontaneousClientId":"","spontaneousClientScopes":[],"showInConfigurationEndpoint":true} -jansDefScope: false +jansDefScope: true jansId: global_token_revocation jansScopeTyp: openid objectClass: top From 9941c75c442e69a7386c1906bac06cf76fa6dac8 Mon Sep 17 00:00:00 2001 From: YuriyZ Date: Fri, 24 May 2024 19:54:28 +0300 Subject: [PATCH 8/9] feat(jans-auth-server): added global token revocation integration test https://github.com/JanssenProject/jans/issues/8398 Signed-off-by: YuriyZ --- .../test/java/io/jans/as/client/BaseTest.java | 11 ++ .../revoke/GlobalTokenRevocationHttpTest.java | 105 ++++++++++++++++++ .../client/src/test/resources/testng.xml | 5 + .../as/server/service/DiscoveryService.java | 6 +- .../jans/as/server/service/GrantService.java | 4 +- .../server/service/ResteasyInitializer.java | 2 + .../token/GlobalTokenRevocationService.java | 2 +- 7 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/revoke/GlobalTokenRevocationHttpTest.java diff --git a/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java b/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java index 693804b15f2..75f43cfe31b 100644 --- a/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java @@ -101,6 +101,7 @@ public abstract class BaseTest { protected String jwksUri; protected String archivedJwksUri; protected String registrationEndpoint; + protected String globalTokenRevocationEndpoint; protected String configurationEndpoint; protected String introspectionEndpoint; protected String deviceAuthzEndpoint; @@ -375,6 +376,14 @@ public void setRegistrationEndpoint(String registrationEndpoint) { this.registrationEndpoint = registrationEndpoint; } + public String getGlobalTokenRevocationEndpoint() { + return globalTokenRevocationEndpoint; + } + + public void setGlobalTokenRevocationEndpoint(String globalTokenRevocationEndpoint) { + this.globalTokenRevocationEndpoint = globalTokenRevocationEndpoint; + } + public String getIntrospectionEndpoint() { return introspectionEndpoint; } @@ -1000,6 +1009,7 @@ public void discovery(ITestContext context) throws Exception { jwksUri = response.getJwksUri(); archivedJwksUri = response.getArchivedJwksUri(); registrationEndpoint = response.getRegistrationEndpoint(); + globalTokenRevocationEndpoint = response.getGlobalTokenRevocationEndpoint(); introspectionEndpoint = response.getIntrospectionEndpoint(); parEndpoint = response.getParEndpoint(); deviceAuthzEndpoint = response.getDeviceAuthzEndpoint(); @@ -1023,6 +1033,7 @@ public void discovery(ITestContext context) throws Exception { jwksUri = context.getCurrentXmlTest().getParameter("jwksUri"); archivedJwksUri = context.getCurrentXmlTest().getParameter("archivedJwksUri"); registrationEndpoint = context.getCurrentXmlTest().getParameter("registrationEndpoint"); + globalTokenRevocationEndpoint = context.getCurrentXmlTest().getParameter("globalTokenRevocationEndpoint"); configurationEndpoint = context.getCurrentXmlTest().getParameter("configurationEndpoint"); introspectionEndpoint = context.getCurrentXmlTest().getParameter("introspectionEndpoint"); parEndpoint = context.getCurrentXmlTest().getParameter("parEndpoint"); diff --git a/jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/revoke/GlobalTokenRevocationHttpTest.java b/jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/revoke/GlobalTokenRevocationHttpTest.java new file mode 100644 index 00000000000..8785e7380b8 --- /dev/null +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/revoke/GlobalTokenRevocationHttpTest.java @@ -0,0 +1,105 @@ +package io.jans.as.client.ws.rs.revoke; + +import com.google.common.collect.Lists; +import io.jans.as.client.*; +import io.jans.as.client.client.AssertBuilder; +import io.jans.as.model.common.AuthenticationMethod; +import io.jans.as.model.common.GrantType; +import io.jans.as.model.common.ResponseType; +import io.jans.as.model.common.SubjectType; +import io.jans.as.model.register.ApplicationType; +import io.jans.as.model.util.StringUtils; +import org.testng.annotations.Parameters; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.testng.Assert.assertNotNull; + +/** + * @author Yuriy Z + */ +public class GlobalTokenRevocationHttpTest extends BaseTest { + + @Parameters({"redirectUris", "userId", "userSecret", "redirectUri"}) + @Test + public void globalTokenRevocation( + final String redirectUris, final String userId, final String userSecret, final String redirectUri) { + showTitle("globalTokenRevocation"); + + final AuthenticationMethod authnMethod = AuthenticationMethod.CLIENT_SECRET_BASIC; + + // 1. Register client + List responseTypes = Collections.singletonList(ResponseType.CODE); + + RegisterRequest registerRequest = new RegisterRequest(ApplicationType.WEB, "jans test app", + StringUtils.spaceSeparatedToList(redirectUris)); + + registerRequest.setTokenEndpointAuthMethod(authnMethod); + registerRequest.setResponseTypes(responseTypes); + registerRequest.setScope(Lists.newArrayList("global_token_revocation", "openid")); + registerRequest.setSubjectType(SubjectType.PUBLIC); + + RegisterClient registerClient = newRegisterClient(registerRequest); + RegisterResponse registerResponse = registerClient.exec(); + + showClient(registerClient); + AssertBuilder.registerResponse(registerResponse).created().check(); + + // 3. Request authorization + List scopes = Arrays.asList( + "openid", + "profile", + "address", + "email"); + String state = UUID.randomUUID().toString(); + String nonce = UUID.randomUUID().toString(); + + AuthorizationRequest authorizationRequest = new AuthorizationRequest(responseTypes, registerResponse.getClientId(), scopes, redirectUri, nonce); + authorizationRequest.setState(state); + + AuthorizationResponse authorizationResponse = authenticateResourceOwnerAndGrantAccess( + authorizationEndpoint, authorizationRequest, userId, userSecret); + + assertNotNull(authorizationResponse.getLocation(), "The location is null"); + assertNotNull(authorizationResponse.getCode(), "The authorization code is null"); + + // 4. Request access token using the authorization code. + TokenRequest tokenRequest = new TokenRequest(GrantType.AUTHORIZATION_CODE); + tokenRequest.setCode(authorizationResponse.getCode()); + tokenRequest.setRedirectUri(redirectUri); + tokenRequest.setAuthUsername(registerResponse.getClientId()); + tokenRequest.setAuthPassword(registerResponse.getClientSecret()); + tokenRequest.setAuthenticationMethod(AuthenticationMethod.CLIENT_SECRET_BASIC); + + TokenClient tokenClient = newTokenClient(tokenRequest); + tokenClient.setRequest(tokenRequest); + TokenResponse tokenResponse = tokenClient.exec(); + showClient(tokenClient); + + // 5. request User Info with access token + UserInfoClient userInfoClient = new UserInfoClient(userInfoEndpoint); + userInfoClient.execUserInfo(tokenResponse.getAccessToken()); + showClient(userInfoClient); + + // 6. revoke token by user uid=userId + GlobalTokenRevocationClientRequest revocationRequest = new GlobalTokenRevocationClientRequest(); + revocationRequest.setFormat("uid"); + revocationRequest.setId(userId); + revocationRequest.setAuthUsername(registerResponse.getClientId()); + revocationRequest.setAuthPassword(registerResponse.getClientSecret()); + revocationRequest.setAuthenticationMethod(AuthenticationMethod.CLIENT_SECRET_BASIC); + + GlobalTokenRevocationClient globalTokenRevocationClient = new GlobalTokenRevocationClient(globalTokenRevocationEndpoint); + globalTokenRevocationClient.exec(revocationRequest); + showClient(globalTokenRevocationClient); + + // 7. request User Info with access token which is revoked + userInfoClient = new UserInfoClient(userInfoEndpoint); + userInfoClient.execUserInfo(tokenResponse.getAccessToken()); + showClient(userInfoClient); + } +} diff --git a/jans-auth-server/client/src/test/resources/testng.xml b/jans-auth-server/client/src/test/resources/testng.xml index 874f2d3d7d1..8d25c292eed 100644 --- a/jans-auth-server/client/src/test/resources/testng.xml +++ b/jans-auth-server/client/src/test/resources/testng.xml @@ -6,6 +6,11 @@ + + + + + diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/DiscoveryService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/DiscoveryService.java index 280dadbd95d..22d256a3c10 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/DiscoveryService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/DiscoveryService.java @@ -37,7 +37,7 @@ public class DiscoveryService { @Inject - private transient Logger log; + private Logger log; @Inject private transient AppConfiguration appConfiguration; @@ -287,8 +287,8 @@ private void addMtlsAliases(JSONObject jsonObj) { aliases.put(REVOCATION_ENDPOINT, appConfiguration.getMtlsTokenRevocationEndpoint()); if (appConfiguration.isFeatureEnabled(FeatureFlagType.REVOKE_SESSION) && StringUtils.isNotBlank(appConfiguration.getMtlsEndSessionEndpoint())) aliases.put(SESSION_REVOCATION_ENDPOINT, StringUtils.replace(appConfiguration.getMtlsEndSessionEndpoint(), "/end_session", "/revoke_session")); - if (appConfiguration.isFeatureEnabled(FeatureFlagType.GLOBAL_TOKEN_REVOCATION)) - jsonObj.put(GLOBAL_TOKEN_REVOCATION_ENDPOINT, StringUtils.replace(appConfiguration.getMtlsEndSessionEndpoint(), "/end_session", "/global-token-revocation")); + if (appConfiguration.isFeatureEnabled(FeatureFlagType.GLOBAL_TOKEN_REVOCATION) && StringUtils.isNotBlank(appConfiguration.getMtlsEndSessionEndpoint())) + aliases.put(GLOBAL_TOKEN_REVOCATION_ENDPOINT, StringUtils.replace(appConfiguration.getMtlsEndSessionEndpoint(), "/end_session", "/global-token-revocation")); if (appConfiguration.isFeatureEnabled(FeatureFlagType.USERINFO) && StringUtils.isNotBlank(appConfiguration.getMtlsUserInfoEndpoint())) aliases.put(USER_INFO_ENDPOINT, appConfiguration.getMtlsUserInfoEndpoint()); if (appConfiguration.isFeatureEnabled(FeatureFlagType.CLIENTINFO) && StringUtils.isNotBlank(appConfiguration.getMtlsClientInfoEndpoint())) diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/GrantService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/GrantService.java index 0e8849da0a2..8db9cf86ffd 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/GrantService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/GrantService.java @@ -271,10 +271,10 @@ public List getGrantsBySessionDn(String sessionDn) { return grants; } - public List getGrantsByUserId(String userId) { + public List getGrantsByUserDn(String userDn) { List grants = new ArrayList<>(); try { - List tokenEntities = persistenceEntryManager.findEntries(tokenBaseDn(), TokenEntity.class, Filter.createEqualityFilter("ssnId", userId)); + List tokenEntities = persistenceEntryManager.findEntries(tokenBaseDn(), TokenEntity.class, Filter.createEqualityFilter("jansUsrDN", userDn)); if (tokenEntities != null) { grants.addAll(tokenEntities); } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/ResteasyInitializer.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/ResteasyInitializer.java index 1e6fe535f08..1bae2506ddd 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/ResteasyInitializer.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/ResteasyInitializer.java @@ -18,6 +18,7 @@ import io.jans.as.server.jwk.ws.rs.JwkRestWebServiceImpl; import io.jans.as.server.par.ws.rs.ParRestWebService; import io.jans.as.server.register.ws.rs.RegisterRestWebServiceImpl; +import io.jans.as.server.revoke.GlobalTokenRevocationRestWebService; import io.jans.as.server.revoke.RevokeRestWebServiceImpl; import io.jans.as.server.revoke.RevokeSessionRestWebService; import io.jans.as.server.session.ws.rs.CheckSessionStatusRestWebServiceImpl; @@ -54,6 +55,7 @@ public Set> getClasses() { classes.add(ClientInfoRestWebServiceImpl.class); classes.add(RevokeRestWebServiceImpl.class); classes.add(RevokeSessionRestWebService.class); + classes.add(GlobalTokenRevocationRestWebService.class); classes.add(JwkRestWebServiceImpl.class); classes.add(ArchivedJwksWebServiceImpl.class); classes.add(IntrospectionWebService.class); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/token/GlobalTokenRevocationService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/token/GlobalTokenRevocationService.java index 6f9fb18a636..bd53d374422 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/token/GlobalTokenRevocationService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/token/GlobalTokenRevocationService.java @@ -83,7 +83,7 @@ public void requestGlobalTokenRevocation(String requestAsString) { log.debug("Revoked {} user's sessions (user: {})", sessionIdList != null ? sessionIdList.size() : 0, user.getUserId()); // remove tokens - final List grants = grantService.getGrantsByUserId(user.getUserId()); + final List grants = grantService.getGrantsByUserDn(user.getDn()); grantService.removeSilently(grants); log.debug("Revoked {} tokens (user: {})", grants != null ? grants.size() : 0, user.getUserId()); From 36794f91f31e615a820a2d1b425b8e39bf56cc11 Mon Sep 17 00:00:00 2001 From: YuriyZ Date: Fri, 24 May 2024 20:06:31 +0300 Subject: [PATCH 9/9] doc(jans-auth-server): added docs for global token revocation https://github.com/JanssenProject/jans/issues/8398 Signed-off-by: YuriyZ --- .../endpoints/global-token-revocation.md | 54 +++++++++++++++++++ .../auth-server/oauth-features/README.md | 1 + .../auth-server/session-management/README.md | 2 +- .../log/global-token-revocation-run-log.txt | 0 .../revoke/GlobalTokenRevocationHttpTest.java | 12 +++-- 5 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 docs/admin/auth-server/endpoints/global-token-revocation.md create mode 100644 docs/assets/log/global-token-revocation-run-log.txt diff --git a/docs/admin/auth-server/endpoints/global-token-revocation.md b/docs/admin/auth-server/endpoints/global-token-revocation.md new file mode 100644 index 00000000000..902464908a3 --- /dev/null +++ b/docs/admin/auth-server/endpoints/global-token-revocation.md @@ -0,0 +1,54 @@ +--- +tags: +- administration +- auth-server +- session-revocation +- endpoint +--- + +# Overview + +Janssen Server provides global token revocation endpoint to enable the client to revoke all tokens and sessions of a user. +Janssen Server provides this endpoint to allow greater +control and better management of sessions on OP. + +URL to access revocation endpoint on Janssen Server is listed in the response of Janssen Server's well-known +[configuration endpoint](./configuration.md) given below. + +```text +https://janssen.server.host/jans-auth/.well-known/openid-configuration +``` + +`global_token_revocation_endpoint` claim in the response specifies the URL for global token revocation endpoint. By default, global token revocation endpoint +looks like below: + +``` +https://janssen.server.host/jans-auth/restv1/global-token-revocation +``` + +More information about request and response of the global token revocation endpoint can be found in +the OpenAPI specification of [jans-auth-server module](https://gluu.org/swagger-ui/?url=https://raw.githubusercontent.com/JanssenProject/jans/vreplace-janssen-version/jans-auth-server/docs/swagger.yaml). + +## Usage + +A request to this endpoint can revoke all tokens and sessions of one particular user. Use the request parameters to specify +criteria to select the user. If there are multiple users matching the given criteria, the first found user will be affected. + +- View full sample execution log [here](../../../assets/log/global-token-revocation-run-log.txt) + +## Disabling The Endpoint Using Feature Flag + +`Global Token Revocation` endpoint can be enabled or disable using [GLOBAL_TOKEN_REVOCATION feature flag](../../reference/json/feature-flags/janssenauthserver-feature-flags.md#globaltokenrevocation). +Use [Janssen Text-based UI(TUI)](../../config-guide/config-tools/jans-tui/README.md) or [Janssen command-line interface](../../config-guide/config-tools/jans-cli/README.md) to perform this task. + +When using TUI, navigate via `Auth Server`->`Properties`->`enabledFeatureFlags` to screen below. From here, enable or +disable `GLOBAL_TOKEN_REVOCATION` flag as required. + +![](../../../assets/image-tui-enable-components.png) + +## Required Scopes + +A client must have the following scope in order to use this endpoint: + +- `global_token_revocation` + diff --git a/docs/admin/auth-server/oauth-features/README.md b/docs/admin/auth-server/oauth-features/README.md index e04ac1fa66d..bdf780078e2 100644 --- a/docs/admin/auth-server/oauth-features/README.md +++ b/docs/admin/auth-server/oauth-features/README.md @@ -30,6 +30,7 @@ The [Janssen Authentication Server](https://github.com/JanssenProject/jans/tree/ - The Use of Attestation in OAuth 2.0 Dynamic Client Registration [(spec draft)](https://www.ietf.org/id/draft-tschofenig-oauth-attested-dclient-reg-00.html) - OpenID Connect Core Error Code unmet_authentication_requirements [(spec)](https://openid.net/specs/openid-connect-unmet-authentication-requirements-1_0.html) - Transaction Tokens [(spec)](https://drafts.oauth.net/oauth-transaction-tokens/draft-ietf-oauth-transaction-tokens.html) +- Global Token Revocation [(spec)](https://www.ietf.org/archive/id/draft-parecki-oauth-global-token-revocation-03.html) ## Protocol Overview diff --git a/docs/admin/auth-server/session-management/README.md b/docs/admin/auth-server/session-management/README.md index bb2d2f8dbe6..aaa2c3121cc 100644 --- a/docs/admin/auth-server/session-management/README.md +++ b/docs/admin/auth-server/session-management/README.md @@ -61,7 +61,7 @@ Jans Auth Server updates `lastUsedAt` property of the session object: The [End Session endpoint](../endpoints/end-session.md) (`/end_session`) is where the user can end their own session. See [OpenID Logout](../openid-features/logout/README.md) for more information. -To end another person's session, Jans Auth Server supports both [Session Revocation Endpoint](../endpoints/session-revocation.md) (`/revoke_session`) and [Global Session Revocation Endpoint](../endpoints/global-session-revocation.md) (`/global-token-revocation`'). +To end another person's session, Jans Auth Server supports both [Session Revocation Endpoint](../endpoints/session-revocation.md) (`/revoke_session`) and [Global Session Revocation Endpoint](../endpoints/global-token-revocation.md) (`/global-token-revocation`'). ## Session Event Interception Scripts diff --git a/docs/assets/log/global-token-revocation-run-log.txt b/docs/assets/log/global-token-revocation-run-log.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/revoke/GlobalTokenRevocationHttpTest.java b/jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/revoke/GlobalTokenRevocationHttpTest.java index 8785e7380b8..b550d675f00 100644 --- a/jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/revoke/GlobalTokenRevocationHttpTest.java +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/revoke/GlobalTokenRevocationHttpTest.java @@ -17,7 +17,7 @@ import java.util.List; import java.util.UUID; -import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.*; /** * @author Yuriy Z @@ -82,9 +82,11 @@ public void globalTokenRevocation( // 5. request User Info with access token UserInfoClient userInfoClient = new UserInfoClient(userInfoEndpoint); - userInfoClient.execUserInfo(tokenResponse.getAccessToken()); + UserInfoResponse userInfoResponse = userInfoClient.execUserInfo(tokenResponse.getAccessToken()); showClient(userInfoClient); + assertNull(userInfoResponse.getErrorType()); // no error + // 6. revoke token by user uid=userId GlobalTokenRevocationClientRequest revocationRequest = new GlobalTokenRevocationClientRequest(); revocationRequest.setFormat("uid"); @@ -97,9 +99,11 @@ public void globalTokenRevocation( globalTokenRevocationClient.exec(revocationRequest); showClient(globalTokenRevocationClient); - // 7. request User Info with access token which is revoked + // 7. request User Info with access token which is revoked -> error type is not null userInfoClient = new UserInfoClient(userInfoEndpoint); - userInfoClient.execUserInfo(tokenResponse.getAccessToken()); + userInfoResponse = userInfoClient.execUserInfo(tokenResponse.getAccessToken()); showClient(userInfoClient); + + assertNotNull(userInfoResponse.getErrorType()); // no error } }