diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/get/SsaGetClient.java b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/get/SsaGetClient.java index e31fad182c5..3e4e319ed4a 100644 --- a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/get/SsaGetClient.java +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/get/SsaGetClient.java @@ -30,19 +30,10 @@ public String getHttpMethod() { public SsaGetResponse execSsaGet(String accessToken, String jti, Long orgId, Boolean softwareRoles) { SsaGetRequest ssaGetRequest = new SsaGetRequest(); ssaGetRequest.setAccessToken(accessToken); + ssaGetRequest.setJti(jti); + ssaGetRequest.setOrgId(orgId); + ssaGetRequest.setSoftwareRoles(softwareRoles); setRequest(ssaGetRequest); - - URIBuilder uriBuilder = new URIBuilder(); - if (StringUtils.isNotBlank(jti)) { - uriBuilder.addParameter("jti", jti); - } - if (orgId != null && orgId > 0) { - uriBuilder.addParameter("org_id", orgId.toString()); - } - if (softwareRoles != null) { - uriBuilder.addParameter("software_roles", softwareRoles.toString()); - } - setUrl(getUrl() + uriBuilder); return exec(); } @@ -50,7 +41,8 @@ public SsaGetResponse exec() { try { initClient(); - Builder clientRequest = webTarget.request(); + String uriWithParams = getUrl() + "?" + getRequest().getQueryString(); + Builder clientRequest = resteasyClient.target(uriWithParams).request(); applyCookies(clientRequest); clientRequest.header("Content-Type", request.getContentType()); diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/get/SsaGetRequest.java b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/get/SsaGetRequest.java index efada1cc61e..2d92972e52f 100644 --- a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/get/SsaGetRequest.java +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/get/SsaGetRequest.java @@ -8,12 +8,20 @@ import io.jans.as.client.BaseRequest; import io.jans.as.model.common.AuthorizationMethod; +import io.jans.as.model.ssa.SsaRequestParam; +import io.jans.as.model.util.QueryBuilder; import jakarta.ws.rs.core.MediaType; public class SsaGetRequest extends BaseRequest { private String accessToken; + private String jti; + + private Long orgId; + + private Boolean softwareRoles; + public SsaGetRequest() { setContentType(MediaType.APPLICATION_JSON); setMediaType(MediaType.APPLICATION_JSON); @@ -28,8 +36,36 @@ public void setAccessToken(String accessToken) { this.accessToken = accessToken; } + public String getJti() { + return jti; + } + + public void setJti(String jti) { + this.jti = jti; + } + + public Long getOrgId() { + return orgId; + } + + public void setOrgId(Long orgId) { + this.orgId = orgId; + } + + public Boolean getSoftwareRoles() { + return softwareRoles; + } + + public void setSoftwareRoles(Boolean softwareRoles) { + this.softwareRoles = softwareRoles; + } + @Override public String getQueryString() { - return null; + QueryBuilder builder = QueryBuilder.instance(); + builder.append(SsaRequestParam.JTI.getName(), jti); + builder.append(SsaRequestParam.ORG_ID.getName(), orgId != null ? orgId.toString() : ""); + builder.append(SsaRequestParam.SOFTWARE_ROLES.getName(), softwareRoles != null ? softwareRoles.toString() : ""); + return builder.toString(); } } diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/revoke/SsaRevokeClient.java b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/revoke/SsaRevokeClient.java new file mode 100644 index 00000000000..23460ecccfd --- /dev/null +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/revoke/SsaRevokeClient.java @@ -0,0 +1,64 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.as.client.ssa.revoke; + +import io.jans.as.client.BaseClient; +import io.jans.as.model.config.Constants; +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.client.Invocation.Builder; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; + +public class SsaRevokeClient extends BaseClient { + + private static final Logger LOG = Logger.getLogger(SsaRevokeClient.class); + + public SsaRevokeClient(String url) { + super(url); + } + + @Override + public String getHttpMethod() { + return HttpMethod.DELETE; + } + + public SsaRevokeResponse execSsaRevoke(String accessToken, String jti, Long orgId) { + SsaRevokeRequest req = new SsaRevokeRequest(); + req.setAccessToken(accessToken); + req.setJti(jti); + req.setOrgId(orgId); + setRequest(req); + return exec(); + } + + public SsaRevokeResponse exec() { + try { + initClient(); + + String uriWithParams = getUrl() + "?" + getRequest().getQueryString(); + Builder clientRequest = resteasyClient.target(uriWithParams).request(); + applyCookies(clientRequest); + + clientRequest.header("Content-Type", request.getContentType()); + if (StringUtils.isNotBlank(request.getAccessToken())) { + clientRequest.header(Constants.AUTHORIZATION, "Bearer ".concat(request.getAccessToken())); + } + + clientResponse = clientRequest.buildDelete().invoke(); + final SsaRevokeResponse res = new SsaRevokeResponse(clientResponse); + setResponse(res); + + } 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/ssa/revoke/SsaRevokeRequest.java b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/revoke/SsaRevokeRequest.java new file mode 100644 index 00000000000..787c4bfa48a --- /dev/null +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/revoke/SsaRevokeRequest.java @@ -0,0 +1,60 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.as.client.ssa.revoke; + +import io.jans.as.client.BaseRequest; +import io.jans.as.model.common.AuthorizationMethod; +import io.jans.as.model.ssa.SsaRequestParam; +import io.jans.as.model.util.QueryBuilder; +import jakarta.ws.rs.core.MediaType; + +public class SsaRevokeRequest extends BaseRequest { + + private String accessToken; + + private String jti; + + private Long orgId; + + public SsaRevokeRequest() { + setContentType(MediaType.APPLICATION_JSON); + setMediaType(MediaType.APPLICATION_JSON); + setAuthorizationMethod(AuthorizationMethod.AUTHORIZATION_REQUEST_HEADER_FIELD); + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getJti() { + return jti; + } + + public void setJti(String jti) { + this.jti = jti; + } + + public Long getOrgId() { + return orgId; + } + + public void setOrgId(Long orgId) { + this.orgId = orgId; + } + + @Override + public String getQueryString() { + QueryBuilder builder = QueryBuilder.instance(); + builder.append(SsaRequestParam.JTI.getName(), jti); + builder.append(SsaRequestParam.ORG_ID.getName(), orgId != null ? orgId.toString() : ""); + return builder.toString(); + } +} diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/revoke/SsaRevokeResponse.java b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/revoke/SsaRevokeResponse.java new file mode 100644 index 00000000000..39916239aa2 --- /dev/null +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/revoke/SsaRevokeResponse.java @@ -0,0 +1,27 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.as.client.ssa.revoke; + +import io.jans.as.client.BaseResponseWithErrors; +import io.jans.as.model.ssa.SsaErrorResponseType; +import jakarta.ws.rs.core.Response; + +public class SsaRevokeResponse extends BaseResponseWithErrors { + + public SsaRevokeResponse(Response clientResponse) { + super(clientResponse); + } + + @Override + public SsaErrorResponseType fromString(String p_str) { + return SsaErrorResponseType.fromString(p_str); + } + + @Override + public void injectDataFromJson(String json) { + } +} \ No newline at end of file diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/validate/SsaValidateClient.java b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/validate/SsaValidateClient.java index 7a21bdee036..7277aae2e0b 100644 --- a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/validate/SsaValidateClient.java +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/validate/SsaValidateClient.java @@ -22,7 +22,7 @@ public SsaValidateClient(String url) { @Override public String getHttpMethod() { - return HttpMethod.GET; + return HttpMethod.HEAD; } public SsaValidateResponse execSsaValidate(@NotNull String jti) { diff --git a/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaCreateRestTest.java b/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaCreateTest.java similarity index 99% rename from jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaCreateRestTest.java rename to jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaCreateTest.java index a06a71840a2..6abfc5df42f 100644 --- a/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaCreateRestTest.java +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaCreateTest.java @@ -22,7 +22,7 @@ import java.util.*; -public class SsaCreateRestTest extends BaseTest { +public class SsaCreateTest extends BaseTest { @Parameters({"redirectUris", "sectorIdentifierUri"}) @Test diff --git a/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaGetRestTest.java b/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaGetTest.java similarity index 99% rename from jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaGetRestTest.java rename to jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaGetTest.java index 58aad88d3cf..6e3436ad5b2 100644 --- a/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaGetRestTest.java +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaGetTest.java @@ -25,7 +25,7 @@ import java.util.Collections; import java.util.List; -public class SsaGetRestTest extends BaseTest { +public class SsaGetTest extends BaseTest { @Parameters({"redirectUris", "sectorIdentifierUri"}) @Test diff --git a/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaRevokeTest.java b/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaRevokeTest.java new file mode 100644 index 00000000000..626d3ea0153 --- /dev/null +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaRevokeTest.java @@ -0,0 +1,159 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.as.client.ssa; + +import io.jans.as.client.BaseTest; +import io.jans.as.client.RegisterResponse; +import io.jans.as.client.TokenResponse; +import io.jans.as.client.ssa.create.SsaCreateResponse; +import io.jans.as.client.ssa.get.SsaGetClient; +import io.jans.as.client.ssa.get.SsaGetResponse; +import io.jans.as.client.ssa.revoke.SsaRevokeClient; +import io.jans.as.client.ssa.revoke.SsaRevokeResponse; +import io.jans.as.model.common.GrantType; +import io.jans.as.model.common.ResponseType; +import io.jans.as.model.ssa.SsaScopeType; +import org.apache.http.HttpStatus; +import org.testng.annotations.Parameters; +import org.testng.annotations.Test; + +import java.util.Collections; +import java.util.List; + +import static org.testng.Assert.*; + +public class SsaRevokeTest extends BaseTest { + + @Parameters({"redirectUris", "sectorIdentifierUri"}) + @Test + public void revokeWithJtiResponseOK(final String redirectUris, final String sectorIdentifierUri) { + showTitle("revokeWithJtiResponseOK"); + List scopes = Collections.singletonList(SsaScopeType.SSA_ADMIN.getValue()); + + // Register client + RegisterResponse registerResponse = registerClient(redirectUris, Collections.singletonList(ResponseType.CODE), + Collections.singletonList(GrantType.CLIENT_CREDENTIALS), scopes, sectorIdentifierUri); + String clientId = registerResponse.getClientId(); + String clientSecret = registerResponse.getClientSecret(); + + // Access token + TokenResponse tokenResponse = tokenClientCredentialsGrant(SsaScopeType.SSA_ADMIN.getValue(), clientId, clientSecret); + String accessToken = tokenResponse.getAccessToken(); + + // Create ssa + SsaCreateResponse ssaCreateResponse = createSsaWithDefaultValues(accessToken, null, null, Boolean.TRUE); + String jti = ssaCreateResponse.getJti(); + + // Ssa first revocation + Long orgId = null; + SsaRevokeClient ssaRevokeClient = new SsaRevokeClient(ssaEndpoint); + SsaRevokeResponse firstSsaRevokeResponse = ssaRevokeClient.execSsaRevoke(accessToken, jti, orgId); + showClient(ssaRevokeClient); + assertNotNull(firstSsaRevokeResponse, "Response is null"); + assertEquals(firstSsaRevokeResponse.getStatus(), HttpStatus.SC_OK); + + // Ssa second revocation + SsaRevokeResponse secondSsaRevokeResponse = ssaRevokeClient.execSsaRevoke(accessToken, jti, orgId); + showClient(ssaRevokeClient); + assertNotNull(secondSsaRevokeResponse, "Response is null"); + assertEquals(secondSsaRevokeResponse.getStatus(), 422); + + // Ssa get + SsaGetClient ssaGetClient = new SsaGetClient(ssaEndpoint); + SsaGetResponse ssaGetResponse = ssaGetClient.execSsaGet(accessToken, jti, orgId, false); + showClient(ssaGetClient); + assertNotNull(ssaGetResponse, "Ssa get response is null"); + assertTrue(ssaGetResponse.getSsaList().isEmpty()); + } + + @Parameters({"redirectUris", "sectorIdentifierUri"}) + @Test + public void revokeWithOrgIdResponseOK(final String redirectUris, final String sectorIdentifierUri) { + showTitle("revokeWithOrgIdResponseOK"); + List scopes = Collections.singletonList(SsaScopeType.SSA_ADMIN.getValue()); + + // Register client + RegisterResponse registerResponse = registerClient(redirectUris, Collections.singletonList(ResponseType.CODE), + Collections.singletonList(GrantType.CLIENT_CREDENTIALS), scopes, sectorIdentifierUri); + String clientId = registerResponse.getClientId(); + String clientSecret = registerResponse.getClientSecret(); + + // Access token + TokenResponse tokenResponse = tokenClientCredentialsGrant(SsaScopeType.SSA_ADMIN.getValue(), clientId, clientSecret); + String accessToken = tokenResponse.getAccessToken(); + + // Create ssa + Long orgId = 10L; + createSsaWithDefaultValues(accessToken, orgId, null, Boolean.TRUE); + + // Ssa first revocation + String jti = null; + SsaRevokeClient ssaRevokeClient = new SsaRevokeClient(ssaEndpoint); + SsaRevokeResponse firstSsaRevokeResponse = ssaRevokeClient.execSsaRevoke(accessToken, jti, orgId); + showClient(ssaRevokeClient); + assertNotNull(firstSsaRevokeResponse, "Response is null"); + assertEquals(firstSsaRevokeResponse.getStatus(), HttpStatus.SC_OK); + + // Ssa second revocation + SsaRevokeResponse secondSsaRevokeResponse = ssaRevokeClient.execSsaRevoke(accessToken, jti, orgId); + showClient(ssaRevokeClient); + assertNotNull(secondSsaRevokeResponse, "Response is null"); + assertEquals(secondSsaRevokeResponse.getStatus(), 422); + } + + @Parameters({"redirectUris", "sectorIdentifierUri"}) + @Test + public void revokeWithSsaListNotFoundResponse422(final String redirectUris, final String sectorIdentifierUri) { + showTitle("revokeWithSsaListNotFoundResponse422"); + List scopes = Collections.singletonList(SsaScopeType.SSA_ADMIN.getValue()); + + // Register client + RegisterResponse registerResponse = registerClient(redirectUris, Collections.singletonList(ResponseType.CODE), + Collections.singletonList(GrantType.CLIENT_CREDENTIALS), scopes, sectorIdentifierUri); + String clientId = registerResponse.getClientId(); + String clientSecret = registerResponse.getClientSecret(); + + // Access token + TokenResponse tokenResponse = tokenClientCredentialsGrant(SsaScopeType.SSA_ADMIN.getValue(), clientId, clientSecret); + String accessToken = tokenResponse.getAccessToken(); + + // Ssa revocation + String jti = "WRONG-JTI"; + Long orgId = null; + SsaRevokeClient ssaRevokeClient = new SsaRevokeClient(ssaEndpoint); + SsaRevokeResponse ssaRevokeResponse = ssaRevokeClient.execSsaRevoke(accessToken, jti, orgId); + showClient(ssaRevokeClient); + assertNotNull(ssaRevokeResponse, "Response is null"); + assertEquals(ssaRevokeResponse.getStatus(), 422); + } + + @Parameters({"redirectUris", "sectorIdentifierUri"}) + @Test + public void revokeWithQueryParamNullResponse406(final String redirectUris, final String sectorIdentifierUri) { + showTitle("revokeWithQueryParamNullResponse406"); + List scopes = Collections.singletonList(SsaScopeType.SSA_ADMIN.getValue()); + + // Register client + RegisterResponse registerResponse = registerClient(redirectUris, Collections.singletonList(ResponseType.CODE), + Collections.singletonList(GrantType.CLIENT_CREDENTIALS), scopes, sectorIdentifierUri); + String clientId = registerResponse.getClientId(); + String clientSecret = registerResponse.getClientSecret(); + + // Access token + TokenResponse tokenResponse = tokenClientCredentialsGrant(SsaScopeType.SSA_ADMIN.getValue(), clientId, clientSecret); + String accessToken = tokenResponse.getAccessToken(); + + // Ssa revocation + String jti = null; + Long orgId = null; + SsaRevokeClient ssaRevokeClient = new SsaRevokeClient(ssaEndpoint); + SsaRevokeResponse ssaRevokeResponse = ssaRevokeClient.execSsaRevoke(accessToken, jti, orgId); + showClient(ssaRevokeClient); + assertNotNull(ssaRevokeResponse, "Response is null"); + assertEquals(ssaRevokeResponse.getStatus(), 406); + } +} \ No newline at end of file diff --git a/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaValidateRestTest.java b/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaValidateTest.java similarity index 99% rename from jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaValidateRestTest.java rename to jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaValidateTest.java index e2ed18ce60d..0b25edacafb 100644 --- a/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaValidateRestTest.java +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaValidateTest.java @@ -28,7 +28,7 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; -public class SsaValidateRestTest extends BaseTest { +public class SsaValidateTest extends BaseTest { @Parameters({"redirectUris", "sectorIdentifierUri"}) @Test diff --git a/jans-auth-server/client/src/test/resources/testng.xml b/jans-auth-server/client/src/test/resources/testng.xml index 8c72e78444f..1b0dcce656c 100644 --- a/jans-auth-server/client/src/test/resources/testng.xml +++ b/jans-auth-server/client/src/test/resources/testng.xml @@ -1177,12 +1177,22 @@ - + - + + + + + + + + + + + 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 05ef363b5f9..7a531656cc3 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 @@ -167,7 +167,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo boolean revokeSessionEndpoint = requestUrl.endsWith("/revoke_session"); boolean isParEndpoint = requestUrl.endsWith("/par"); boolean ssaEndpoint = requestUrl.endsWith("/ssa") && - (Arrays.asList(HttpMethod.POST, HttpMethod.GET).contains(httpRequest.getMethod())); + (Arrays.asList(HttpMethod.POST, HttpMethod.GET, HttpMethod.DELETE).contains(httpRequest.getMethod())); String authorizationHeader = httpRequest.getHeader(Constants.AUTHORIZATION); String dpopHeader = httpRequest.getHeader("DPoP"); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ModifySsaResponseService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ModifySsaResponseService.java index da5ccf34c15..17044141ef9 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ModifySsaResponseService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ModifySsaResponseService.java @@ -6,6 +6,7 @@ package io.jans.as.server.service.external; +import io.jans.as.common.model.ssa.Ssa; import io.jans.as.model.token.JsonWebResponse; import io.jans.as.server.service.external.context.ModifySsaResponseContext; import io.jans.model.custom.script.CustomScriptType; @@ -35,7 +36,7 @@ public boolean create(CustomScriptConfiguration script, JsonWebResponse jsonWebR ModifySsaResponseType modifySsaResponseType = (ModifySsaResponseType) script.getExternalType(); final boolean result = modifySsaResponseType.create(jsonWebResponse, context); - log.trace("Finished modify-ssa-response method, script name: {}, jwt: {}, context: {}, result: {}", script.getName(), jsonWebResponse, context, result); + log.trace("Finished modify-ssa-response method create, script name: {}, jwt: {}, context: {}, result: {}", script.getName(), jsonWebResponse, context, result); return result; } catch (Exception ex) { @@ -64,7 +65,7 @@ public boolean create(JsonWebResponse jsonWebResponse, ModifySsaResponseContext public boolean get(JSONArray jsonArray, ModifySsaResponseContext context) { List scriptList = getCustomScript(); - if (scriptList == null || scriptList.isEmpty()) { + if (scriptList.isEmpty()) { return false; } for (CustomScriptConfiguration script : scriptList) { @@ -79,7 +80,32 @@ public boolean get(JSONArray jsonArray, ModifySsaResponseContext context) { log.error(e.getMessage(), e); saveScriptError(script.getCustomScript(), e); } - log.trace("Finished modify-ssa-response method, script name: {}, jsonArray: {}, context: {}, result: {}", script.getName(), jsonArray, context, result); + log.trace("Finished modify-ssa-response method get, script name: {}, jsonArray: {}, context: {}, result: {}", script.getName(), jsonArray, context, result); + if (!result) { + return false; + } + } + return true; + } + + public boolean revoke(List ssaList, ModifySsaResponseContext context) { + List scriptList = getCustomScript(); + if (scriptList.isEmpty()) { + return false; + } + for (CustomScriptConfiguration script : scriptList) { + log.trace("Executing python modify-ssa-response method revoke, script name: {}, ssaList: {}, context: {}", script.getName(), ssaList, context); + context.setScript(script); + + ModifySsaResponseType modifySsaResponseType = (ModifySsaResponseType) script.getExternalType(); + boolean result = false; + try { + result = modifySsaResponseType.revoke(ssaList, context); + } catch (Exception e) { + log.error(e.getMessage(), e); + saveScriptError(script.getCustomScript(), e); + } + log.trace("Finished modify-ssa-response method revoke, script name: {}, ssaList: {}, context: {}, result: {}", script.getName(), ssaList, context, result); if (!result) { return false; } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaRestWebService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaRestWebService.java index 3c6ca2818c8..e2b4c84a069 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaRestWebService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaRestWebService.java @@ -36,4 +36,13 @@ Response get( @Path("/ssa") @Produces({MediaType.APPLICATION_JSON}) Response validate(@HeaderParam("jti") String jti); + + @DELETE + @Path("/ssa") + @Produces({MediaType.APPLICATION_JSON}) + Response revoke( + @QueryParam("jti") String jti, + @QueryParam("org_id") Long orgId, + @Context HttpServletRequest httpRequest + ); } \ No newline at end of file diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceImpl.java b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceImpl.java index f903461ff62..86fc795282f 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceImpl.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceImpl.java @@ -8,6 +8,7 @@ import io.jans.as.server.ssa.ws.rs.action.SsaCreateAction; import io.jans.as.server.ssa.ws.rs.action.SsaGetAction; +import io.jans.as.server.ssa.ws.rs.action.SsaRevokeAction; import io.jans.as.server.ssa.ws.rs.action.SsaValidateAction; import jakarta.inject.Inject; import jakarta.servlet.http.HttpServletRequest; @@ -26,6 +27,9 @@ public class SsaRestWebServiceImpl implements SsaRestWebService { @Inject private SsaValidateAction ssaValidateAction; + @Inject + private SsaRevokeAction ssaRevokeAction; + @Override public Response create(String requestParams, HttpServletRequest httpRequest) { return ssaCreateAction.create(requestParams, httpRequest); @@ -40,4 +44,9 @@ public Response get(Boolean softwareRoles, String jti, Long orgId, HttpServletRe public Response validate(String jti) { return ssaValidateAction.validate(jti); } + + @Override + public Response revoke(String jti, Long orgId, HttpServletRequest httpRequest) { + return ssaRevokeAction.revoke(jti, orgId, httpRequest); + } } \ No newline at end of file diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaService.java index 69629142d9f..a355662d998 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaService.java @@ -118,6 +118,10 @@ public Response.ResponseBuilder createUnprocessableEntityResponse() { return Response.status(HttpStatus.SC_UNPROCESSABLE_ENTITY).type(MediaType.APPLICATION_JSON_TYPE); } + public Response.ResponseBuilder createNotAcceptableResponse() { + return Response.status(HttpStatus.SC_NOT_ACCEPTABLE).type(MediaType.APPLICATION_JSON_TYPE); + } + private boolean hasPortalScope(List scopes) { Iterator scopesIterator = scopes.iterator(); boolean result = false; diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaRevokeAction.java b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaRevokeAction.java new file mode 100644 index 00000000000..5cbcd0b3c4c --- /dev/null +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaRevokeAction.java @@ -0,0 +1,111 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2022, Janssen Project + */ + +package io.jans.as.server.ssa.ws.rs.action; + +import io.jans.as.common.model.registration.Client; +import io.jans.as.common.model.ssa.Ssa; +import io.jans.as.common.model.ssa.SsaState; +import io.jans.as.model.common.FeatureFlagType; +import io.jans.as.model.config.Constants; +import io.jans.as.model.configuration.AppConfiguration; +import io.jans.as.model.error.ErrorResponseFactory; +import io.jans.as.model.ssa.SsaErrorResponseType; +import io.jans.as.model.ssa.SsaScopeType; +import io.jans.as.server.service.AttributeService; +import io.jans.as.server.service.external.ModifySsaResponseService; +import io.jans.as.server.service.external.context.ModifySsaResponseContext; +import io.jans.as.server.ssa.ws.rs.SsaContextBuilder; +import io.jans.as.server.ssa.ws.rs.SsaRestWebServiceValidator; +import io.jans.as.server.ssa.ws.rs.SsaService; +import io.jans.as.server.util.ServerUtil; +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; +import org.slf4j.Logger; + +import java.util.List; + +@Stateless +@Named +public class SsaRevokeAction { + + @Inject + private Logger log; + + @Inject + private ErrorResponseFactory errorResponseFactory; + + @Inject + private SsaService ssaService; + + @Inject + private SsaRestWebServiceValidator ssaRestWebServiceValidator; + + @Inject + private ModifySsaResponseService modifySsaResponseService; + + @Inject + private SsaContextBuilder ssaContextBuilder; + + @Inject + private AppConfiguration appConfiguration; + + @Inject + private AttributeService attributeService; + + public Response revoke(String jti, Long orgId, HttpServletRequest httpRequest) { + log.debug("Attempting to revoke ssa, jti: '{}', orgId: {}", jti, orgId); + + errorResponseFactory.validateFeatureEnabled(FeatureFlagType.SSA); + Response.ResponseBuilder builder = Response.ok(); + try { + if (isNotValidParam(jti, orgId)) { + return ssaService.createNotAcceptableResponse().build(); + } + + final Client client = ssaRestWebServiceValidator.getClientFromSession(); + ssaRestWebServiceValidator.checkScopesPolicy(client, SsaScopeType.SSA_ADMIN.getValue()); + + final List ssaList = ssaService.getSsaList(jti, orgId, SsaState.ACTIVE, client.getClientId(), client.getScopes()); + if (ssaList.isEmpty()) { + return ssaService.createUnprocessableEntityResponse().build(); + } + for (Ssa ssa : ssaList) { + ssa.setState(SsaState.REVOKED); + ssaService.merge(ssa); + log.info("Ssa jti: '{}' updated status to '{}'", ssa.getId(), ssa.getState().getValue()); + } + + ModifySsaResponseContext context = ssaContextBuilder.buildModifySsaResponseContext(httpRequest, null, client, appConfiguration, attributeService); + modifySsaResponseService.revoke(ssaList, context); + + } catch (WebApplicationException e) { + if (log.isErrorEnabled()) { + log.error(e.getMessage(), e); + } + throw e; + + } catch (Exception e) { + log.error(e.getMessage(), e); + throw errorResponseFactory.createWebApplicationException(Response.Status.INTERNAL_SERVER_ERROR, SsaErrorResponseType.UNKNOWN_ERROR, "Unknown error"); + } + + builder.cacheControl(ServerUtil.cacheControl(true, false)); + builder.header(Constants.PRAGMA, Constants.NO_CACHE); + builder.type(MediaType.APPLICATION_JSON_TYPE); + return builder.build(); + } + + private boolean isNotValidParam(String jti, Long orgId) { + return StringUtils.isBlank(jti) && orgId == null; + } +} diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceImplTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceImplTest.java index 746cf525d64..33bd1aaa6c4 100644 --- a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceImplTest.java +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceImplTest.java @@ -2,6 +2,7 @@ import io.jans.as.server.ssa.ws.rs.action.SsaCreateAction; import io.jans.as.server.ssa.ws.rs.action.SsaGetAction; +import io.jans.as.server.ssa.ws.rs.action.SsaRevokeAction; import io.jans.as.server.ssa.ws.rs.action.SsaValidateAction; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.core.Response; @@ -31,6 +32,9 @@ public class SsaRestWebServiceImplTest { @Mock private SsaValidateAction ssaValidateAction; + @Mock + private SsaRevokeAction ssaRevokeAction; + @Test public void create_validParams_validResponse() { when(ssaCreateAction.create(anyString(), any())).thenReturn(mock(Response.class)); @@ -60,4 +64,14 @@ public void validate_validParams_validResponse() { verify(ssaValidateAction).validate(anyString()); verifyNoMoreInteractions(ssaValidateAction); } + + @Test + public void revoke_validParams_validResponse() { + when(ssaRevokeAction.revoke(anyString(), any(), any())).thenReturn(mock(Response.class)); + + Response response = ssaRestWebServiceImpl.revoke("testJti", 1000L, mock(HttpServletRequest.class)); + assertNotNull(response, "response is null"); + verify(ssaRevokeAction).revoke(anyString(), any(), any()); + verifyNoMoreInteractions(ssaRevokeAction); + } } \ No newline at end of file diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaServiceTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaServiceTest.java index 21163ed07e0..71e5b10fb6b 100644 --- a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaServiceTest.java +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaServiceTest.java @@ -347,6 +347,13 @@ public void generateJwt_exceptionWithIsErrorEnabledTrue_runtimeException() { verify(log).error(anyString(), any(Throwable.class)); } + @Test + public void createNotAcceptableResponse_valid_response() { + Response response = ssaService.createNotAcceptableResponse().build(); + assertNotNull(response, "Response is null"); + assertEquals(response.getStatus(), HttpStatus.SC_NOT_ACCEPTABLE); + } + @Test public void createUnprocessableEntityResponse_valid_response() { Response response = ssaService.createUnprocessableEntityResponse().build(); diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaRevokeActionTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaRevokeActionTest.java new file mode 100644 index 00000000000..a641758c69c --- /dev/null +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaRevokeActionTest.java @@ -0,0 +1,186 @@ +package io.jans.as.server.ssa.ws.rs.action; + +import io.jans.as.common.model.registration.Client; +import io.jans.as.common.model.ssa.Ssa; +import io.jans.as.common.model.ssa.SsaState; +import io.jans.as.model.common.FeatureFlagType; +import io.jans.as.model.configuration.AppConfiguration; +import io.jans.as.model.error.ErrorResponseFactory; +import io.jans.as.server.service.AttributeService; +import io.jans.as.server.service.external.ModifySsaResponseService; +import io.jans.as.server.ssa.ws.rs.SsaContextBuilder; +import io.jans.as.server.ssa.ws.rs.SsaRestWebServiceValidator; +import io.jans.as.server.ssa.ws.rs.SsaService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Response; +import org.mockito.ArgumentCaptor; +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.Collections; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.testng.Assert.*; + +@Listeners(MockitoTestNGListener.class) +public class SsaRevokeActionTest { + + @InjectMocks + private SsaRevokeAction ssaRevokeAction; + + @Mock + private Logger log; + + @Mock + private ErrorResponseFactory errorResponseFactory; + + @Mock + private SsaService ssaService; + + @Mock + private SsaRestWebServiceValidator ssaRestWebServiceValidator; + + @Mock + private ModifySsaResponseService modifySsaResponseService; + + @Mock + private SsaContextBuilder ssaContextBuilder; + + @Mock + private AppConfiguration appConfiguration; + + @Mock + private AttributeService attributeService; + + @Test + public void revoke_notValidParam_406Status() { + when(ssaService.createNotAcceptableResponse()).thenReturn(Response.status(406)); + + String jti = null; + Long orgId = null; + Response response = ssaRevokeAction.revoke(jti, orgId, mock(HttpServletRequest.class)); + assertNotNull(response); + assertEquals(response.getStatus(), 406); + verify(log).debug(anyString(), eq(jti), eq(orgId)); + verify(errorResponseFactory).validateFeatureEnabled(eq(FeatureFlagType.SSA)); + verifyNoMoreInteractions(ssaRestWebServiceValidator, ssaService, log, errorResponseFactory); + verifyNoInteractions(ssaContextBuilder, modifySsaResponseService, appConfiguration, attributeService); + } + + @Test + public void revoke_ssaListEmpty_422Status() { + Client client = new Client(); + client.setDn("inum=0000,ou=clients,o=jans"); + when(ssaRestWebServiceValidator.getClientFromSession()).thenReturn(client); + when(ssaService.createUnprocessableEntityResponse()).thenReturn(Response.status(422)); + + String jti = "test-jti"; + Long orgId = 1000L; + Response response = ssaRevokeAction.revoke(jti, orgId, mock(HttpServletRequest.class)); + assertNotNull(response); + assertEquals(response.getStatus(), 422); + verify(log).debug(anyString(), eq(jti), eq(orgId)); + verify(errorResponseFactory).validateFeatureEnabled(eq(FeatureFlagType.SSA)); + verify(ssaRestWebServiceValidator).checkScopesPolicy(any(Client.class), anyString()); + verify(ssaService).getSsaList(anyString(), any(), any(), any(), any()); + verifyNoMoreInteractions(ssaService, log, errorResponseFactory); + verifyNoInteractions(ssaContextBuilder, modifySsaResponseService, appConfiguration, attributeService); + } + + @Test + public void revoke_validJti_200Status() { + String jti = "test-jti"; + Long orgId = null; + Client client = new Client(); + client.setDn("inum=0000,ou=clients,o=jans"); + when(ssaRestWebServiceValidator.getClientFromSession()).thenReturn(client); + Ssa ssa = new Ssa(); + ssa.setId(jti); + when(ssaService.getSsaList(anyString(), any(), any(), any(), any())).thenReturn(Collections.singletonList(ssa)); + + Response response = ssaRevokeAction.revoke(jti, orgId, mock(HttpServletRequest.class)); + assertNotNull(response); + assertEquals(response.getStatus(), 200); + verify(log).debug(anyString(), eq(jti), eq(orgId)); + verify(errorResponseFactory).validateFeatureEnabled(eq(FeatureFlagType.SSA)); + verify(ssaRestWebServiceValidator).checkScopesPolicy(any(Client.class), anyString()); + + ArgumentCaptor ssaCaptor = ArgumentCaptor.forClass(Ssa.class); + verify(ssaService).merge(ssaCaptor.capture()); + Ssa ssaAux = ssaCaptor.getValue(); + assertNotNull(ssaAux, "Ssa after merge is null"); + assertNotNull(ssaAux.getState(), "Ssa state is null"); + assertEquals(ssa.getState(), SsaState.REVOKED); + + verify(log).info(anyString(), eq(jti), eq(SsaState.REVOKED.getValue())); + verify(ssaContextBuilder).buildModifySsaResponseContext(any(), any(), any(), any(), any()); + verify(modifySsaResponseService).revoke(any(), any()); + verifyNoMoreInteractions(ssaService, log, errorResponseFactory); + } + + @Test + public void revoke_invalidClientAndIsErrorEnabledFalse_badRequestResponse() { + WebApplicationException error = new WebApplicationException( + Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid client") + .build()); + doThrow(error).when(ssaRestWebServiceValidator).getClientFromSession(); + when(log.isErrorEnabled()).thenReturn(Boolean.FALSE); + + String jti = "test-jti"; + Long orgId = 1000L; + assertThrows(WebApplicationException.class, () -> ssaRevokeAction.revoke(jti, orgId, mock(HttpServletRequest.class))); + verify(log).debug(anyString(), eq(jti), eq(orgId)); + verify(errorResponseFactory).validateFeatureEnabled(eq(FeatureFlagType.SSA)); + verify(log).isErrorEnabled(); + verify(log, never()).error(anyString(), any(WebApplicationException.class)); + verifyNoMoreInteractions(log, errorResponseFactory, ssaService, ssaRestWebServiceValidator); + verifyNoInteractions(ssaContextBuilder, modifySsaResponseService, appConfiguration, attributeService); + } + + @Test + public void revoke_invalidClientAndIsErrorEnabledTrue_badRequestResponse() { + WebApplicationException error = new WebApplicationException( + Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid client") + .build()); + doThrow(error).when(ssaRestWebServiceValidator).getClientFromSession(); + when(log.isErrorEnabled()).thenReturn(Boolean.TRUE); + + String jti = "test-jti"; + Long orgId = 1000L; + assertThrows(WebApplicationException.class, () -> ssaRevokeAction.revoke(jti, orgId, mock(HttpServletRequest.class))); + verify(log).debug(anyString(), eq(jti), eq(orgId)); + verify(errorResponseFactory).validateFeatureEnabled(eq(FeatureFlagType.SSA)); + verify(log).isErrorEnabled(); + verify(log).error(anyString(), any(WebApplicationException.class)); + verifyNoMoreInteractions(log, errorResponseFactory, ssaService, ssaRestWebServiceValidator); + verifyNoInteractions(ssaContextBuilder, modifySsaResponseService, appConfiguration, attributeService); + } + + @Test + public void revoke_withNullPointException_badRequestResponse() { + WebApplicationException error = new WebApplicationException( + Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Unknown error") + .build()); + when(errorResponseFactory.createWebApplicationException(any(), any(), anyString())).thenThrow(error); + + String jti = "test-jti"; + Long orgId = 1000L; + assertThrows(WebApplicationException.class, () -> ssaRevokeAction.revoke(jti, orgId, mock(HttpServletRequest.class))); + verify(log).debug(anyString(), eq(jti), eq(orgId)); + verify(errorResponseFactory).validateFeatureEnabled(eq(FeatureFlagType.SSA)); + verify(log).error(eq(null), any(NullPointerException.class)); + verify(ssaRestWebServiceValidator).getClientFromSession(); + verify(ssaRestWebServiceValidator).checkScopesPolicy(any(), anyString()); + verifyNoMoreInteractions(log, errorResponseFactory, ssaRestWebServiceValidator, ssaService); + verifyNoInteractions(ssaContextBuilder, modifySsaResponseService, appConfiguration, attributeService); + } +} \ No newline at end of file diff --git a/jans-core/script/src/main/java/io/jans/model/custom/script/type/ssa/DummyModifySsaResponseType.java b/jans-core/script/src/main/java/io/jans/model/custom/script/type/ssa/DummyModifySsaResponseType.java index 75762a07c71..17f08db5959 100644 --- a/jans-core/script/src/main/java/io/jans/model/custom/script/type/ssa/DummyModifySsaResponseType.java +++ b/jans-core/script/src/main/java/io/jans/model/custom/script/type/ssa/DummyModifySsaResponseType.java @@ -42,4 +42,9 @@ public boolean create(Object jwr, Object tokenContext) { public boolean get(Object jsonArray, Object ssaContext) { return true; } + + @Override + public boolean revoke(Object ssaList, Object ssaContext) { + return false; + } } \ No newline at end of file diff --git a/jans-core/script/src/main/java/io/jans/model/custom/script/type/ssa/ModifySsaResponseType.java b/jans-core/script/src/main/java/io/jans/model/custom/script/type/ssa/ModifySsaResponseType.java index e7ce92ccb95..926cc96f67a 100644 --- a/jans-core/script/src/main/java/io/jans/model/custom/script/type/ssa/ModifySsaResponseType.java +++ b/jans-core/script/src/main/java/io/jans/model/custom/script/type/ssa/ModifySsaResponseType.java @@ -13,4 +13,6 @@ public interface ModifySsaResponseType extends BaseExternalType { boolean create(Object jsonWebResponse, Object ssaContext); boolean get(Object jsonArray, Object ssaContext); + + boolean revoke(Object ssaList, Object ssaContext); } diff --git a/jans-linux-setup/jans_setup/static/extension/ssa_modify_response/ssa_modify_response.py b/jans-linux-setup/jans_setup/static/extension/ssa_modify_response/ssa_modify_response.py index 067714a8474..1ed0c162e72 100644 --- a/jans-linux-setup/jans_setup/static/extension/ssa_modify_response/ssa_modify_response.py +++ b/jans-linux-setup/jans_setup/static/extension/ssa_modify_response/ssa_modify_response.py @@ -29,4 +29,8 @@ def create(self, jsonWebResponse, context): def get(self, jsonArray, context): print "Modify ssa response script. Modify get ssa list: %s" % jsonArray + return True + + def revoke(self, ssaList, context): + print "Modify ssa response script. Modify revoke ssaList: %s" % ssaList return True \ No newline at end of file