From 7dcca948f9953a76bf76417d9db02c1775d57074 Mon Sep 17 00:00:00 2001 From: Milton Ch <86965029+Milton-Ch@users.noreply.github.com> Date: Mon, 30 Jan 2023 15:44:22 -0400 Subject: [PATCH] feat(jans-auth-server): new endpoint for get jwt of ssa based on jti. (#3724) feat(docs): updated swagger for new endpoint get jwt of ssa, also added more documentation for scopes. --- .../as/client/ssa/jwtssa/SsaGetJwtClient.java | 64 ++++++++ .../client/ssa/jwtssa/SsaGetJwtRequest.java | 49 ++++++ .../client/ssa/jwtssa/SsaGetJwtResponse.java | 59 +++++++ .../jans/as/client/client/AssertBuilder.java | 5 + .../SsaGetJwtAssertBuilder.java | 48 ++++++ .../io/jans/as/client/ssa/SsaGetJwtTest.java | 117 ++++++++++++++ .../client/src/test/resources/testng.xml | 6 + jans-auth-server/docs/swagger.yaml | 105 +++++++++++-- .../as/server/auth/AuthenticationFilter.java | 6 +- .../server/ssa/ws/rs/SsaContextBuilder.java | 20 +-- .../server/ssa/ws/rs/SsaRestWebService.java | 16 ++ .../ssa/ws/rs/SsaRestWebServiceImpl.java | 27 +++- .../ssa/ws/rs/SsaRestWebServiceValidator.java | 29 ++++ .../jans/as/server/ssa/ws/rs/SsaService.java | 44 ++++-- .../ssa/ws/rs/action/SsaCreateAction.java | 12 +- .../server/ssa/ws/rs/action/SsaGetAction.java | 10 +- .../ssa/ws/rs/action/SsaGetJwtAction.java | 96 ++++++++++++ .../ssa/ws/rs/action/SsaRevokeAction.java | 12 +- .../ssa/ws/rs/action/SsaValidateAction.java | 20 +-- .../ws/rs/SsaRestWebServiceValidatorTest.java | 55 +++++++ .../as/server/ssa/ws/rs/SsaServiceTest.java | 145 +++++------------- .../ssa/ws/rs/action/SsaCreateActionTest.java | 8 +- .../ssa/ws/rs/action/SsaGetActionTest.java | 2 +- .../ssa/ws/rs/action/SsaGetJwtActionTest.java | 125 +++++++++++++++ .../ssa/ws/rs/action/SsaRevokeActionTest.java | 2 +- .../ws/rs/action/SsaValidateActionTest.java | 106 ++++++------- 26 files changed, 943 insertions(+), 245 deletions(-) create mode 100644 jans-auth-server/client/src/main/java/io/jans/as/client/ssa/jwtssa/SsaGetJwtClient.java create mode 100644 jans-auth-server/client/src/main/java/io/jans/as/client/ssa/jwtssa/SsaGetJwtRequest.java create mode 100644 jans-auth-server/client/src/main/java/io/jans/as/client/ssa/jwtssa/SsaGetJwtResponse.java create mode 100644 jans-auth-server/client/src/test/java/io/jans/as/client/client/assertbuilders/SsaGetJwtAssertBuilder.java create mode 100644 jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaGetJwtTest.java create mode 100644 jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaGetJwtAction.java create mode 100644 jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaGetJwtActionTest.java diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/jwtssa/SsaGetJwtClient.java b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/jwtssa/SsaGetJwtClient.java new file mode 100644 index 00000000000..a08386a3118 --- /dev/null +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/jwtssa/SsaGetJwtClient.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.jwtssa; + +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; +import org.jetbrains.annotations.NotNull; + +public class SsaGetJwtClient extends BaseClient { + + private static final Logger log = Logger.getLogger(SsaGetJwtClient.class); + + public SsaGetJwtClient(String url) { + super(url + "/jwt"); + } + + @Override + public String getHttpMethod() { + return HttpMethod.GET; + } + + public SsaGetJwtResponse execGetJwtSsa(@NotNull String accessToken, @NotNull String jti) { + SsaGetJwtRequest ssaGetRequest = new SsaGetJwtRequest(); + ssaGetRequest.setJti(jti); + ssaGetRequest.setAccessToken(accessToken); + setRequest(ssaGetRequest); + return exec(); + } + + public SsaGetJwtResponse 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.build(getHttpMethod()).invoke(); + final SsaGetJwtResponse res = new SsaGetJwtResponse(clientResponse); + res.injectDataFromJson(); + 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/jwtssa/SsaGetJwtRequest.java b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/jwtssa/SsaGetJwtRequest.java new file mode 100644 index 00000000000..b2008b0285a --- /dev/null +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/jwtssa/SsaGetJwtRequest.java @@ -0,0 +1,49 @@ +/* + * 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.jwtssa; + +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 SsaGetJwtRequest extends BaseRequest { + + private String jti; + + private String accessToken; + + public SsaGetJwtRequest() { + setContentType(MediaType.APPLICATION_JSON); + setMediaType(MediaType.APPLICATION_JSON); + setAuthorizationMethod(AuthorizationMethod.AUTHORIZATION_REQUEST_HEADER_FIELD); + } + + public String getJti() { + return jti; + } + + public void setJti(String jti) { + this.jti = jti; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + @Override + public String getQueryString() { + QueryBuilder builder = QueryBuilder.instance(); + builder.append(SsaRequestParam.JTI.getName(), jti); + return builder.toString(); + } +} diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/jwtssa/SsaGetJwtResponse.java b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/jwtssa/SsaGetJwtResponse.java new file mode 100644 index 00000000000..43bd5bec0f3 --- /dev/null +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/jwtssa/SsaGetJwtResponse.java @@ -0,0 +1,59 @@ +/* + * 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.jwtssa; + +import io.jans.as.client.BaseResponseWithErrors; +import io.jans.as.model.ssa.SsaErrorResponseType; +import jakarta.ws.rs.core.Response; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.json.JSONException; +import org.json.JSONObject; + +import static io.jans.as.model.ssa.SsaRequestParam.SSA; + +public class SsaGetJwtResponse extends BaseResponseWithErrors { + + private static final Logger log = Logger.getLogger(SsaGetJwtResponse.class); + + private String ssa; + + public SsaGetJwtResponse(Response clientResponse) { + super(clientResponse); + } + + @Override + public SsaErrorResponseType fromString(String p_str) { + return SsaErrorResponseType.fromString(p_str); + } + + public void injectDataFromJson() { + injectDataFromJson(entity); + } + + @Override + public void injectDataFromJson(String json) { + if (StringUtils.isNotBlank(entity)) { + try { + JSONObject jsonObj = new JSONObject(entity); + if (jsonObj.has(SSA.getName())) { + ssa = jsonObj.getString(SSA.getName()); + } + } catch (JSONException e) { + log.error("Error on inject data from json: " + e.getMessage(), e); + } + } + } + + public String getSsa() { + return ssa; + } + + public void setSsa(String ssa) { + this.ssa = ssa; + } +} \ No newline at end of file diff --git a/jans-auth-server/client/src/test/java/io/jans/as/client/client/AssertBuilder.java b/jans-auth-server/client/src/test/java/io/jans/as/client/client/AssertBuilder.java index 242604deba7..0ddc66365d9 100644 --- a/jans-auth-server/client/src/test/java/io/jans/as/client/client/AssertBuilder.java +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/client/AssertBuilder.java @@ -12,6 +12,7 @@ import io.jans.as.client.ssa.create.SsaCreateRequest; import io.jans.as.client.ssa.create.SsaCreateResponse; import io.jans.as.client.ssa.get.SsaGetResponse; +import io.jans.as.client.ssa.jwtssa.SsaGetJwtResponse; import io.jans.as.model.exception.InvalidJwtException; import io.jans.as.model.jwe.Jwe; import io.jans.as.model.jwt.Jwt; @@ -65,4 +66,8 @@ public static SsaCreateAssertBuilder ssaCreate(SsaCreateRequest request, SsaCrea public static SsaGetAssertBuilder ssaGet(SsaGetResponse response) { return new SsaGetAssertBuilder(response); } + + public static SsaGetJwtAssertBuilder ssaGetJwt(SsaGetJwtResponse response) { + return new SsaGetJwtAssertBuilder(response); + } } diff --git a/jans-auth-server/client/src/test/java/io/jans/as/client/client/assertbuilders/SsaGetJwtAssertBuilder.java b/jans-auth-server/client/src/test/java/io/jans/as/client/client/assertbuilders/SsaGetJwtAssertBuilder.java new file mode 100644 index 00000000000..e6db2bf43fe --- /dev/null +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/client/assertbuilders/SsaGetJwtAssertBuilder.java @@ -0,0 +1,48 @@ +package io.jans.as.client.client.assertbuilders; + +import io.jans.as.client.ssa.jwtssa.SsaGetJwtResponse; +import io.jans.as.model.jwt.Jwt; +import io.jans.as.model.jwt.JwtClaims; +import org.apache.http.HttpStatus; + +import static io.jans.as.model.ssa.SsaRequestParam.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +public class SsaGetJwtAssertBuilder extends BaseAssertBuilder { + + private final SsaGetJwtResponse response; + private int status; + + public SsaGetJwtAssertBuilder(SsaGetJwtResponse response) { + this.response = response; + } + + public SsaGetJwtAssertBuilder status(int status) { + this.status = status; + return this; + } + + @Override + public void check() { + assertNotNull(response, "SsaGetJwtResponse is null"); + assertEquals(response.getStatus(), status, "Unexpected HTTP status response: " + response.getStatus()); + if (status == HttpStatus.SC_OK) { + assertNotNull(response.getEntity(), "The entity is null"); + assertNotNull(response.getSsa(), "The ssa token is null"); + + Jwt jwt = Jwt.parseSilently(response.getSsa()); + assertNotNull(jwt, "The jwt is null"); + JwtClaims jwtClaims = jwt.getClaims(); + assertNotNull(jwtClaims.getClaim(ORG_ID.getName()), "The org_id in jwt is null"); + assertNotNull(jwtClaims.getClaim(SOFTWARE_ID.getName()), "The software_id in jwt is null"); + assertNotNull(jwtClaims.getClaim(SOFTWARE_ROLES.getName()), "The software_roles in jwt is null"); + assertNotNull(jwtClaims.getClaim(GRANT_TYPES.getName()), "The grant_types in jwt is null"); + + assertNotNull(jwtClaims.getClaim(JTI.getName()), "The jti in jwt is null"); + assertNotNull(jwtClaims.getClaim(ISS.getName()), "The iss in jwt is null"); + assertNotNull(jwtClaims.getClaim(IAT.getName()), "The iat in jwt is null"); + assertNotNull(jwtClaims.getClaim(EXP.getName()), "The exp in jwt is null"); + } + } +} diff --git a/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaGetJwtTest.java b/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaGetJwtTest.java new file mode 100644 index 00000000000..60a64120cfe --- /dev/null +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaGetJwtTest.java @@ -0,0 +1,117 @@ +/* + * 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.client.AssertBuilder; +import io.jans.as.client.ssa.create.SsaCreateResponse; +import io.jans.as.client.ssa.jwtssa.SsaGetJwtClient; +import io.jans.as.client.ssa.jwtssa.SsaGetJwtResponse; +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; + +public class SsaGetJwtTest extends BaseTest { + + @Parameters({"redirectUris", "sectorIdentifierUri"}) + @Test + public void getJwtSsaWithJtiValidResponseOkWithJwtSsa(final String redirectUris, final String sectorIdentifierUri) { + showTitle("getJwtSsaWithJtiValidResponseOkWithJwtSsa"); + 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(); + + // Get Jwt of SSA + SsaGetJwtClient ssaGetJwtClient = new SsaGetJwtClient(ssaEndpoint); + SsaGetJwtResponse ssaGetJwtResponse = ssaGetJwtClient.execGetJwtSsa(accessToken, jti); + showClient(ssaGetJwtClient); + AssertBuilder.ssaGetJwt(ssaGetJwtResponse).status(HttpStatus.SC_OK).check(); + } + + @Parameters({"redirectUris", "sectorIdentifierUri"}) + @Test + public void getJwtSsaWithJtiWrongResponse422(final String redirectUris, final String sectorIdentifierUri) { + showTitle("getJwtSsaWithJtiWrongResponse422"); + 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(); + + String jti = "wrong-jti"; + + // Get Jwt of SSA + SsaGetJwtClient ssaGetJwtClient = new SsaGetJwtClient(ssaEndpoint); + SsaGetJwtResponse ssaGetJwtResponse = ssaGetJwtClient.execGetJwtSsa(accessToken, jti); + showClient(ssaGetJwtClient); + AssertBuilder.ssaGetJwt(ssaGetJwtResponse).status(422).check(); + } + + @Parameters({"redirectUris", "sectorIdentifierUri"}) + @Test + public void getJwtSsaWithSsaPortalScopeResponseUnauthorized(final String redirectUris, final String sectorIdentifierUri) { + showTitle("getJwtSsaWithSsaPortalScopeResponseUnauthorized"); + List scopes = Collections.singletonList(SsaScopeType.SSA_ADMIN.getValue()); + + // Register client with scope admin + 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(); + + // Create a new client with scope portal + scopes = Collections.singletonList(SsaScopeType.SSA_PORTAL.getValue()); + registerResponse = registerClient(redirectUris, Collections.singletonList(ResponseType.CODE), + Collections.singletonList(GrantType.CLIENT_CREDENTIALS), scopes, sectorIdentifierUri); + clientId = registerResponse.getClientId(); + clientSecret = registerResponse.getClientSecret(); + + // Access token + tokenResponse = tokenClientCredentialsGrant(SsaScopeType.SSA_ADMIN.getValue(), clientId, clientSecret); + accessToken = tokenResponse.getAccessToken(); + + // Get JWT of SSA + SsaGetJwtClient ssaGetJwtClient = new SsaGetJwtClient(ssaEndpoint); + SsaGetJwtResponse ssaGetJwtResponse = ssaGetJwtClient.execGetJwtSsa(accessToken, jti); + showClient(ssaGetJwtClient); + AssertBuilder.ssaGetJwt(ssaGetJwtResponse).status(401).check(); + } +} \ No newline at end of file diff --git a/jans-auth-server/client/src/test/resources/testng.xml b/jans-auth-server/client/src/test/resources/testng.xml index 1b0dcce656c..1bdf9006a5c 100644 --- a/jans-auth-server/client/src/test/resources/testng.xml +++ b/jans-auth-server/client/src/test/resources/testng.xml @@ -1195,4 +1195,10 @@ + + + + + + diff --git a/jans-auth-server/docs/swagger.yaml b/jans-auth-server/docs/swagger.yaml index 1cc0578555b..def79fa6dc7 100644 --- a/jans-auth-server/docs/swagger.yaml +++ b/jans-auth-server/docs/swagger.yaml @@ -4388,7 +4388,20 @@ paths: tags: - SSA summary: Create SSA. - description: Create `SSA` for the organization with `expiration` (optional). + description: |- + # Create SSA for the organization with `expiration` (optional). + ---- + + **Security: Bearer Auth** + + Provide your bearer token in the Authorization header when making requests to protected resources. + + Example: `Authorization: Bearer {{your-access-token}}` + + **Security: OAuth 2.0** + + Scopes: + - `https://jans.io/auth/ssa.admin` - **SSA Admin**, You can create `SSA`. operationId: post-register-ssa security: - bearer: [ ] @@ -4454,12 +4467,26 @@ paths: $ref: '#/components/responses/UnauthorizedSSA' 500: $ref: '#/components/responses/InternalServerErrorSSA' - get: tags: - SSA summary: Get list of SSAs - description: Get all `SSA` list with filters. + description: |- + # Get all SSA list based on `jti` or `org_id`. + ---- + + **Security: Bearer Auth** + + Provide your bearer token in the Authorization header when making requests to protected resources. + + Example: `Authorization: Bearer {{your-access-token}}` + + **Security: OAuth 2.0** + + Scopes: + - `https://jans.io/auth/ssa.admin` - **SSA Admin**, Retrieves all `SSA` + - `https://jans.io/auth/ssa.portal` - **SSA Portal**, Retrieves all `SSA` + - `https://jans.io/auth/ssa.developer` - **SSA Developer**, Retrieves the `SSA` created by the same client operationId: get-ssa security: - bearer: [ ] @@ -4468,7 +4495,7 @@ paths: type: string in: query name: jti - description: JTI + description: Unique Identifier - schema: type: integer in: query @@ -4537,19 +4564,21 @@ paths: $ref: '#/components/responses/UnauthorizedSSA' 500: $ref: '#/components/responses/InternalServerErrorSSA' - head: tags: - SSA summary: Validate SSA - description: Validates that a given SSA JTI exists and is valid. - operationId: head-ssa + description: |- + # Validates that a given SSA `jti` exists and is valid + ---- + + This endpoint does not have any security, it is an open endpoint. parameters: - schema: type: string in: header name: jti - description: JTI + description: Unique Identifier required: true responses: 200: @@ -4558,15 +4587,27 @@ paths: description: Not found. 500: $ref: '#/components/responses/InternalServerErrorSSA' - delete: tags: - SSA summary: Revoke SSA description: |- - Revokes existing active SSA based on `jti` or `org_id`. + # Revokes existing active SSA based on `jti` or `org_id` + ---- + - `jti` - for delete only one SSA, the specified by `jti` - `org_id` - for delete all SSA of the specified organization. + + **Security: Bearer Auth** + + Provide your bearer token in the Authorization header when making requests to protected resources. + + Example: `Authorization: Bearer {{your-access-token}}` + + **Security: OAuth 2.0** + + Scopes: + - `https://jans.io/auth/ssa.admin` - **SSA Admin**, You can revoke `SSA`. operationId: delete-ssa security: - bearer: [ ] @@ -4593,6 +4634,50 @@ paths: 500: $ref: '#/components/responses/InternalServerErrorSSA' + /ssa/jwt: + get: + tags: + - SSA + summary: Get JWT of SSA based on JTI. + description: |- + # Get JWT of SSA based on `jti`. + ---- + + **Security: Bearer Auth** + + Provide your bearer token in the Authorization header when making requests to protected resources. + + Example: `Authorization: Bearer {{your-access-token}}` + + **Security: OAuth 2.0** + + Scopes: + - `https://jans.io/auth/ssa.admin` - **SSA Admin**, Retrieve `JWT` of `SSA` + operationId: get-jwt-ssa + security: + - bearer: [ ] + parameters: + - schema: + type: string + in: query + name: jti + description: Unique Identifier. + responses: + 200: + description: The response will return the `JWT` of `SSA`. + content: + application/json: + schema: + type: object + properties: + ssa: + type: string + example: eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3BvcnRhbC5nbHV1Lm9yZyIsImlhdCI6IjE2NTUzMTkyNjgiLCJqdGkiOiJmN2I1OTkxYy00YzE4LTRjODEtYTY2NC1lNmY4NjcwZjVkNTEiLCJzb2Z0d2FyZV9pZCI6ImdsdXUtc2Nhbi1hcGkiLCJvcmdfaWQiOjEsInNvZnR3YXJlX3JvbGVzIjpbInBhc3N3dXJkIl0sImp3a3NfdXJpIjoiaHR0cHM6Ly9jbG91ZC1kZXYuZ2x1dS5jbG91ZC9wb3J0YWwvandrcyIsImdyYW50X3R5cGVzIjpbImNsaWVudF9jcmVkZW50aWFscyJdLCJleHAiOjE2NTkzMTkyNjh9.MkE-47SvBshmazBfyhAcHsqPpFIbg5CpA8k2TxDWhxc + 401: + $ref: '#/components/responses/UnauthorizedSSA' + 500: + $ref: '#/components/responses/InternalServerErrorSSA' + components: responses: Found: #302 - FOUND 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 7a531656cc3..5b130a1a077 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 @@ -92,7 +92,8 @@ "/restv1/par", "/restv1/device_authorization", "/restv1/register", - "/restv1/ssa" + "/restv1/ssa", + "/restv1/ssa/jwt", }, displayName = "oxAuth") public class AuthenticationFilter implements Filter { @@ -168,6 +169,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo boolean isParEndpoint = requestUrl.endsWith("/par"); boolean ssaEndpoint = requestUrl.endsWith("/ssa") && (Arrays.asList(HttpMethod.POST, HttpMethod.GET, HttpMethod.DELETE).contains(httpRequest.getMethod())); + boolean ssaJwtEndpoint = requestUrl.endsWith("/ssa/jwt") && httpRequest.getMethod().equals(HttpMethod.GET); String authorizationHeader = httpRequest.getHeader(Constants.AUTHORIZATION); String dpopHeader = httpRequest.getHeader("DPoP"); @@ -181,7 +183,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo return; } - if (tokenEndpoint || revokeSessionEndpoint || tokenRevocationEndpoint || deviceAuthorizationEndpoint || isParEndpoint || ssaEndpoint) { + if (tokenEndpoint || revokeSessionEndpoint || tokenRevocationEndpoint || deviceAuthorizationEndpoint || isParEndpoint || ssaEndpoint || ssaJwtEndpoint) { log.debug("Starting endpoint authentication {}", requestUrl); // #686 : allow authenticated client via user access_token diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaContextBuilder.java b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaContextBuilder.java index 7016e264008..2c8d6719389 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaContextBuilder.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaContextBuilder.java @@ -8,9 +8,9 @@ import io.jans.as.common.model.registration.Client; import io.jans.as.common.service.AttributeService; import io.jans.as.model.configuration.AppConfiguration; -import io.jans.as.server.model.common.AuthorizationGrant; import io.jans.as.server.service.external.context.ModifySsaResponseContext; import jakarta.ejb.Stateless; +import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.servlet.http.HttpServletRequest; @@ -21,6 +21,12 @@ @Named public class SsaContextBuilder { + @Inject + private AppConfiguration appConfiguration; + + @Inject + private AttributeService attributeService; + /** * ModifySsaResponseContext instance for use in the SSA custom script call. *

@@ -28,16 +34,12 @@ public class SsaContextBuilder { * it internally call {@link io.jans.service.cdi.util.CdiUtil} and cannot be mocked *

* - * @param httpRequest Http request - * @param grant Grant type - * @param client Client - * @param appConfiguration App configuration - * @param attributeService Attribute service + * @param httpRequest Http request + * @param client Client * @return New instance of {@link ModifySsaResponseContext} */ @Deprecated - public ModifySsaResponseContext buildModifySsaResponseContext(HttpServletRequest httpRequest, AuthorizationGrant grant, - Client client, AppConfiguration appConfiguration, AttributeService attributeService) { - return new ModifySsaResponseContext(httpRequest, grant, client, appConfiguration, attributeService); + public ModifySsaResponseContext buildModifySsaResponseContext(HttpServletRequest httpRequest, Client client) { + return new ModifySsaResponseContext(httpRequest, null, client, appConfiguration, attributeService); } } 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 cc564b01664..a14f19f61f9 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 @@ -84,4 +84,20 @@ Response revoke( @QueryParam("org_id") Long orgId, @Context HttpServletRequest httpRequest ); + + /** + * Get JWT from existing active SSA based on "jti". + * + * @param jti Unique identifier + * @return {@link Response} with status {@code 200 (Ok)} and the body containing JWT of SSA. + * or with status {@code 401} if this functionality is not enabled, request has to have at least scope "ssa.admin", + * or with status {@code 422} if the SSA does not exist, is expired or used, + * or with status {@code 500} in case an uncontrolled error occurs when processing the method. + */ + @GET + @Path("/ssa/jwt") + @Produces({MediaType.APPLICATION_JSON}) + Response getSsaJwtByJti( + @QueryParam("jti") String jti + ); } \ 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 34a84c47520..99f79046205 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 @@ -6,13 +6,11 @@ package io.jans.as.server.ssa.ws.rs; -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 io.jans.as.server.ssa.ws.rs.action.*; import jakarta.inject.Inject; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.Path; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Response; /** @@ -33,6 +31,9 @@ public class SsaRestWebServiceImpl implements SsaRestWebService { @Inject private SsaRevokeAction ssaRevokeAction; + @Inject + private SsaGetJwtAction ssaGetJwtAction; + /** * Creates an SSA from the requested parameters. *

@@ -93,4 +94,22 @@ public Response validate(String jti) { public Response revoke(String jti, Long orgId, HttpServletRequest httpRequest) { return ssaRevokeAction.revoke(jti, orgId, httpRequest); } + + /** + * Get JWT from existing active SSA based on "jti". + * + *

+ * Method will return the following exceptions: + * - {@link WebApplicationException} with status {@code 401} if this functionality is not enabled, request has to have at least scope "ssa.admin". + * - {@link WebApplicationException} with status {@code 422} if the SSA does not exist, is expired or used. + * - {@link WebApplicationException} with status {@code 500} in case an uncontrolled error occurs when processing the method. + *

+ * + * @param jti Unique identifier + * @return {@link Response} with status {@code 200 (Ok)} and the body containing JWT of SSA. + */ + @Override + public Response getSsaJwtByJti(String jti) { + return ssaGetJwtAction.getJwtSsa(jti); + } } \ No newline at end of file diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceValidator.java b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceValidator.java index db86919ac82..b760bb60ad1 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceValidator.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceValidator.java @@ -7,6 +7,8 @@ package io.jans.as.server.ssa.ws.rs; 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.error.ErrorResponseFactory; import io.jans.as.model.ssa.SsaErrorResponseType; import io.jans.as.server.model.session.SessionClient; @@ -20,7 +22,9 @@ import org.slf4j.Logger; import java.util.Arrays; +import java.util.Calendar; import java.util.List; +import java.util.TimeZone; import java.util.stream.Collectors; /** @@ -42,6 +46,9 @@ public class SsaRestWebServiceValidator { @Inject private ScopeService scopeService; + @Inject + private SsaService ssaService; + /** * Get client from session * @@ -87,4 +94,26 @@ public void checkScopesPolicy(Client client, List scopeList) throws WebA throw errorResponseFactory.createWebApplicationException(Response.Status.UNAUTHORIZED, SsaErrorResponseType.UNAUTHORIZED_CLIENT, "Unauthorized client"); } } + + /** + * Find SSA based on "jti" and validated + *

+ * This method returns {@link WebApplicationException} with status 422 if the SSA does not exist or if it is in + * state (expired, used or revoked). + * Otherwise it will return the valid SSA + *

+ * + * @param jti Unique identifier + * @return Ssa valid + */ + public Ssa getValidSsaByJti(String jti) { + Ssa ssa = ssaService.findSsaByJti(jti); + if (ssa == null || + Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTime().after(ssa.getExpirationDate()) || + !ssa.getState().equals(SsaState.ACTIVE)) { + log.warn("Ssa jti: '{}' is null or status (expired, used or revoked)", jti); + throw new WebApplicationException(Response.status(422).build()); + } + return ssa; + } } 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 de422f591cf..6ce0973da6d 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 @@ -54,6 +54,12 @@ public class SsaService { @Inject private StaticConfiguration staticConfiguration; + @Inject + private WebKeysConfiguration webKeysConfiguration; + + @Inject + private AbstractCryptoProvider cryptoProvider; + /** * Persist SSA in to the database * @@ -133,13 +139,35 @@ public List getSsaList(String jti, Long orgId, SsaState status, String clie * Method executes a postProcessor in case it has been sent in the execution context parameter. *

* - * @param ssa Ssa - * @param executionContext Execution context - * @param webKeysConfiguration Web keys configuration - * @param cryptoProvider Crypto provider + * @param ssa Ssa + * @param executionContext Execution context * @return Jwt with SSA structure */ - public Jwt generateJwt(Ssa ssa, ExecutionContext executionContext, WebKeysConfiguration webKeysConfiguration, AbstractCryptoProvider cryptoProvider) { + public Jwt generateJwt(Ssa ssa, ExecutionContext executionContext) { + try { + Jwt jwr = generateJwt(ssa); + if (executionContext.getPostProcessor() != null) { + executionContext.getPostProcessor().apply(jwr); + } + return jwr; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Generates a new JWT using a given SSA. + *

+ * Method throws an {@link RuntimeException} if it fails to create the jwt + *

+ *

+ * Method executes a postProcessor in case it has been sent in the execution context parameter. + *

+ * + * @param ssa Ssa + * @return Jwt with SSA structure + */ + public Jwt generateJwt(Ssa ssa) { try { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.fromString(appConfiguration.getSsaConfiguration().getSsaSigningAlg()); JwtSigner jwtSigner = new JwtSigner(appConfiguration, webKeysConfiguration, signatureAlgorithm, null, null, cryptoProvider); @@ -152,11 +180,7 @@ public Jwt generateJwt(Ssa ssa, ExecutionContext executionContext, WebKeysConfig jwt.getClaims().setClaim(SOFTWARE_ROLES.getName(), ssa.getAttributes().getSoftwareRoles()); jwt.getClaims().setClaim(GRANT_TYPES.getName(), ssa.getAttributes().getGrantTypes()); - Jwt jwr = jwtSigner.sign(); - if (executionContext.getPostProcessor() != null) { - executionContext.getPostProcessor().apply(jwr); - } - return jwr; + return jwtSigner.sign(); } catch (Exception e) { if (log.isErrorEnabled()) log.error("Failed to sign session jwt! " + e.getMessage(), e); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaCreateAction.java b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaCreateAction.java index 57bec1f8a7e..86f69416c30 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaCreateAction.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaCreateAction.java @@ -10,13 +10,11 @@ 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.common.service.AttributeService; import io.jans.as.common.service.common.InumService; import io.jans.as.model.common.CreatorType; import io.jans.as.model.common.FeatureFlagType; import io.jans.as.model.config.Constants; import io.jans.as.model.config.StaticConfiguration; -import io.jans.as.model.config.WebKeysConfiguration; import io.jans.as.model.configuration.AppConfiguration; import io.jans.as.model.error.ErrorResponseFactory; import io.jans.as.model.jwt.Jwt; @@ -73,18 +71,12 @@ public class SsaCreateAction { @Inject private AppConfiguration appConfiguration; - @Inject - private AttributeService attributeService; - @Inject private ModifySsaResponseService modifySsaResponseService; @Inject private SsaRestWebServiceValidator ssaRestWebServiceValidator; - @Inject - private WebKeysConfiguration webKeysConfiguration; - @Inject private SsaContextBuilder ssaContextBuilder; @@ -149,12 +141,12 @@ public Response create(String requestParams, HttpServletRequest httpRequest) { ssaService.persist(ssa); log.info("Ssa created: {}", ssa); - ModifySsaResponseContext context = ssaContextBuilder.buildModifySsaResponseContext(httpRequest, null, client, appConfiguration, attributeService); + ModifySsaResponseContext context = ssaContextBuilder.buildModifySsaResponseContext(httpRequest, client); Function postProcessor = modifySsaResponseService.buildCreateProcessor(context); final ExecutionContext executionContext = context.toExecutionContext(); executionContext.setPostProcessor(postProcessor); - Jwt jwt = ssaService.generateJwt(ssa, executionContext, webKeysConfiguration, null); + Jwt jwt = ssaService.generateJwt(ssa, executionContext); JSONObject jsonResponse = ssaJsonService.getJSONObject(jwt.toString()); builder.entity(ssaJsonService.jsonObjectToString(jsonResponse)); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaGetAction.java b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaGetAction.java index 5d32f4fbfde..a418a9c1245 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaGetAction.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaGetAction.java @@ -9,10 +9,8 @@ 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.common.service.AttributeService; 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; @@ -55,12 +53,6 @@ public class SsaGetAction { @Inject private SsaService ssaService; - @Inject - private AppConfiguration appConfiguration; - - @Inject - private AttributeService attributeService; - @Inject private ModifySsaResponseService modifySsaResponseService; @@ -105,7 +97,7 @@ public Response get(String jti, Long orgId, HttpServletRequest httpRequest) { final List ssaList = ssaService.getSsaList(jti, orgId, SsaState.ACTIVE, client.getClientId(), client.getScopes()); JSONArray jsonArray = ssaJsonService.getJSONArray(ssaList); - ModifySsaResponseContext context = ssaContextBuilder.buildModifySsaResponseContext(httpRequest, null, client, appConfiguration, attributeService); + ModifySsaResponseContext context = ssaContextBuilder.buildModifySsaResponseContext(httpRequest, client); jsonArray = modifyGetScript(jsonArray, context, ssaList); builder.entity(ssaJsonService.jsonArrayToString(jsonArray)); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaGetJwtAction.java b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaGetJwtAction.java new file mode 100644 index 00000000000..6c636aa8801 --- /dev/null +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaGetJwtAction.java @@ -0,0 +1,96 @@ +/* + * 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.model.common.FeatureFlagType; +import io.jans.as.model.config.Constants; +import io.jans.as.model.error.ErrorResponseFactory; +import io.jans.as.model.jwt.Jwt; +import io.jans.as.model.ssa.SsaErrorResponseType; +import io.jans.as.model.ssa.SsaScopeType; +import io.jans.as.server.ssa.ws.rs.SsaJsonService; +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.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.json.JSONObject; +import org.slf4j.Logger; + +/** + * Provides the method to get JWT of SSA existing based on certain conditions. + */ +@Stateless +@Named +public class SsaGetJwtAction { + + @Inject + private Logger log; + + @Inject + private ErrorResponseFactory errorResponseFactory; + + @Inject + private SsaJsonService ssaJsonService; + + @Inject + private SsaService ssaService; + + @Inject + private SsaRestWebServiceValidator ssaRestWebServiceValidator; + + /** + * Get JWT from existing active SSA based on "jti". + * + *

+ * Method will return the following exceptions: + * - {@link WebApplicationException} with status {@code 401} if this functionality is not enabled, request has to have at least scope "ssa.admin". + * - {@link WebApplicationException} with status {@code 422} if the SSA does not exist, is expired or used. + * - {@link WebApplicationException} with status {@code 500} in case an uncontrolled error occurs when processing the method. + *

+ * + * @param jti Unique identifier + * @return {@link Response} with status {@code 200 (Ok)} and the body containing JWT of SSA. + */ + public Response getJwtSsa(String jti) { + log.debug("Attempting to get JWT of SSA, jti: {}", jti); + + errorResponseFactory.validateFeatureEnabled(FeatureFlagType.SSA); + Response.ResponseBuilder builder = Response.ok(); + try { + final Client client = ssaRestWebServiceValidator.getClientFromSession(); + ssaRestWebServiceValidator.checkScopesPolicy(client, SsaScopeType.SSA_ADMIN.getValue()); + + Ssa ssa = ssaRestWebServiceValidator.getValidSsaByJti(jti); + + Jwt jwt = ssaService.generateJwt(ssa); + JSONObject jsonResponse = ssaJsonService.getJSONObject(jwt.toString()); + builder.entity(ssaJsonService.jsonObjectToString(jsonResponse)); + + } 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(); + } +} 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 index c1d67f78075..9f5030a6e4b 100644 --- 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 @@ -11,11 +11,9 @@ 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; @@ -59,12 +57,6 @@ public class SsaRevokeAction { @Inject private SsaContextBuilder ssaContextBuilder; - @Inject - private AppConfiguration appConfiguration; - - @Inject - private AttributeService attributeService; - /** * Revoked existing active SSA based on "jti" or "org_id". * @@ -113,7 +105,7 @@ public Response revoke(String jti, Long orgId, HttpServletRequest httpRequest) { log.info("Ssa jti: '{}' updated status to '{}'", ssa.getId(), ssa.getState().getValue()); } - ModifySsaResponseContext context = ssaContextBuilder.buildModifySsaResponseContext(httpRequest, null, client, appConfiguration, attributeService); + ModifySsaResponseContext context = ssaContextBuilder.buildModifySsaResponseContext(httpRequest, client); modifySsaResponseService.revoke(ssaList, context); } catch (WebApplicationException e) { @@ -136,7 +128,7 @@ public Response revoke(String jti, Long orgId, HttpServletRequest httpRequest) { /** * Validate "jti" or "org_id" parameters * - * @param jti Unique identifier + * @param jti Unique identifier * @param orgId Organization ID * @return true if the parameters are valid or false otherwise. */ diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaValidateAction.java b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaValidateAction.java index 18ec93e01d6..7a1b6e57769 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaValidateAction.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaValidateAction.java @@ -12,6 +12,7 @@ import io.jans.as.model.config.Constants; import io.jans.as.model.error.ErrorResponseFactory; import io.jans.as.model.ssa.SsaErrorResponseType; +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; @@ -22,9 +23,6 @@ import jakarta.ws.rs.core.Response; import org.slf4j.Logger; -import java.util.Calendar; -import java.util.TimeZone; - /** * Provides the method to validate an existing SSA considering certain conditions. */ @@ -41,6 +39,9 @@ public class SsaValidateAction { @Inject private SsaService ssaService; + @Inject + private SsaRestWebServiceValidator ssaRestWebServiceValidator; + /** * Validates an existing SSA for a given "jti". * @@ -60,19 +61,18 @@ public Response validate(String jti) { errorResponseFactory.validateFeatureEnabled(FeatureFlagType.SSA); Response.ResponseBuilder builder = Response.ok(); try { - Ssa ssa = ssaService.findSsaByJti(jti); - if (ssa == null || - Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTime().after(ssa.getExpirationDate()) || - !ssa.getState().equals(SsaState.ACTIVE)) { - log.warn("Ssa jti: '{}' is null or status (expired, used or revoked)", jti); - return ssaService.createUnprocessableEntityResponse().build(); - } + Ssa ssa = ssaRestWebServiceValidator.getValidSsaByJti(jti); if (ssa.getAttributes().getOneTimeUse()) { ssa.setState(SsaState.USED); ssaService.merge(ssa); log.info("Ssa jti: '{}', updated with status: {}", ssa.getId(), ssa.getState()); } + } 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"); diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceValidatorTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceValidatorTest.java index 297b647ebd3..bd7f14dc159 100644 --- a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceValidatorTest.java +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaRestWebServiceValidatorTest.java @@ -1,6 +1,8 @@ package io.jans.as.server.ssa.ws.rs; 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.error.ErrorResponseFactory; import io.jans.as.model.ssa.SsaErrorResponseType; import io.jans.as.model.ssa.SsaScopeType; @@ -17,8 +19,10 @@ import org.testng.annotations.Listeners; import org.testng.annotations.Test; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.List; import static org.mockito.ArgumentMatchers.anyString; @@ -43,6 +47,9 @@ public class SsaRestWebServiceValidatorTest { @Mock private ScopeService scopeService; + @Mock + private SsaService ssaService; + @Test public void getClientFromSession_sessionClient_validClient() { SessionClient sessionClient = new SessionClient(); @@ -197,4 +204,52 @@ public void checkScopesPolicyListScope_clientAndScopeNotContains_unauthorizedRes verify(errorResponseFactory).createWebApplicationException(any(), any(), anyString()); verifyNoMoreInteractions(errorResponseFactory); } + + @Test + public void getValidSsaByJti_validJti_validSsa() { + String jti = "test-jti"; + Ssa ssa = new Ssa(); + ssa.setExpirationDate(Date.from(ZonedDateTime.now().plusHours(24).toInstant())); + ssa.setState(SsaState.ACTIVE); + when(ssaService.findSsaByJti(jti)).thenReturn(ssa); + + Ssa result = ssaRestWebServiceValidator.getValidSsaByJti(jti); + assertNotNull(result, "ssa is null"); + verifyNoInteractions(log); + } + + @Test + public void getValidSsaByJti_ssaNull_422Status() { + String jti = "test-jti"; + when(ssaService.findSsaByJti(jti)).thenReturn(null); + + WebApplicationException ex = expectThrows(WebApplicationException.class, () -> ssaRestWebServiceValidator.getValidSsaByJti(jti)); + assertEquals(ex.getResponse().getStatus(), 422); + verify(log).warn(anyString(), eq(jti)); + } + + @Test + public void getValidSsaByJti_ssaExpired_422Status() { + String jti = "test-jti"; + Ssa ssa = new Ssa(); + ssa.setExpirationDate(Date.from(ZonedDateTime.now().minusHours(24).toInstant())); + when(ssaService.findSsaByJti(jti)).thenReturn(ssa); + + WebApplicationException ex = expectThrows(WebApplicationException.class, () -> ssaRestWebServiceValidator.getValidSsaByJti(jti)); + assertEquals(ex.getResponse().getStatus(), 422); + verify(log).warn(anyString(), eq(jti)); + } + + @Test + public void getValidSsaByJti_ssaWithUsedStatus_422Status() { + String jti = "test-jti"; + Ssa ssa = new Ssa(); + ssa.setExpirationDate(Date.from(ZonedDateTime.now().plusHours(24).toInstant())); + ssa.setState(SsaState.USED); + when(ssaService.findSsaByJti(jti)).thenReturn(ssa); + + WebApplicationException ex = expectThrows(WebApplicationException.class, () -> ssaRestWebServiceValidator.getValidSsaByJti(jti)); + assertEquals(ex.getResponse().getStatus(), 422); + verify(log).warn(anyString(), eq(jti)); + } } \ 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 061959b2804..62bfc14ba0b 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 @@ -1,8 +1,5 @@ package io.jans.as.server.ssa.ws.rs; -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jose.jwk.RSAKey; import io.jans.as.common.model.ssa.Ssa; import io.jans.as.common.model.ssa.SsaState; import io.jans.as.model.config.BaseDnConfiguration; @@ -10,22 +7,16 @@ import io.jans.as.model.config.WebKeysConfiguration; import io.jans.as.model.configuration.AppConfiguration; import io.jans.as.model.crypto.AbstractCryptoProvider; -import io.jans.as.model.crypto.signature.SignatureAlgorithm; -import io.jans.as.model.exception.CryptoProviderException; -import io.jans.as.model.jwk.Algorithm; -import io.jans.as.model.jwk.JSONWebKey; import io.jans.as.model.jwt.Jwt; import io.jans.as.model.jwt.JwtClaims; import io.jans.as.model.jwt.JwtHeader; import io.jans.as.model.ssa.SsaConfiguration; import io.jans.as.model.ssa.SsaScopeType; -import io.jans.as.model.util.Base64Util; import io.jans.as.server.model.common.ExecutionContext; import io.jans.orm.PersistenceEntryManager; import io.jans.orm.exception.EntryPersistenceException; import jakarta.ws.rs.core.Response; import org.apache.http.HttpStatus; -import org.json.JSONObject; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -35,9 +26,6 @@ import org.testng.annotations.Listeners; import org.testng.annotations.Test; -import java.security.*; -import java.security.interfaces.RSAPrivateKey; -import java.text.ParseException; import java.util.*; import static io.jans.as.model.ssa.SsaRequestParam.*; @@ -47,18 +35,6 @@ @Listeners(MockitoTestNGListener.class) public class SsaServiceTest { - private final String senderJwkJson = "{\n" + - " \"kty\": \"RSA\",\n" + - " \"d\": \"iSx-zxihgOITpEhz6WwGiiCZjxx597wqblhSYgFWa_bL9esLY3FT_Kq9sdvGPiI8QmObRxPZuTi4n3BVKYUWcfjVz3swq7VmESxnJJZE-vMI9NTaZ-CT2b4I-c3qwAsejhWagJf899I3MRtPOnyxMimyOw4_5YYvXjBkXkCMfCsbj5TBR3RbtMrUYzDMXsVT1EJ_7H76DPBFJx5JptsEAA17VMtqwvWhRutnPyQOftDGPxD-1aGgpteKOUCv7Lx-mFX-zV6nnPB8vmgTgaMqCbCFKSZI567p714gzWBkwnNdRHleX8wos8yZAGbdwGqqUz5x3iKKdn3c7U9TTU7DAQ\",\n" + - " \"e\": \"AQAB\",\n" + - " \"use\": \"sig\",\n" + - " \"kid\": \"1\",\n" + - " \"alg\": \"RS256\",\n" + - " \"n\": \"i6tdK2fREwykTUU-qkYkiSHgg9B31-8EjVCbH0iyrewY9s7_WYPT7I3argjcmiDkufnVfGGW0FadtO3br-Qgk_N2e9LqGMtjUoGMZKFS3fJhqjnLYDi_E5l2FYU_ilw4EXPsZJY0CaM7BxjwUBoCjopYrgvtdxA9G6gpGoAH4LopAkgX-gkawVLpB4NpLvA09FLF2OlYZL7aaybvM2Lz_IXEPa-LSOwLum80Et-_A1-YMx_Z767Iwl1pGTpgZ87jrDD1vEdMdiLcWFG3UIYAAIxtg6X23cvQVLMaXKpyV0USDCWRJrZYxEDgZngbDRj3Sd2-LnixPkMWAfo_D9lBVQ\"\n" + - "}"; - - private AbstractCryptoProvider cryptoProvider; - @Mock private Logger log; @@ -74,64 +50,16 @@ public class SsaServiceTest { @Mock private StaticConfiguration staticConfiguration; + @Mock + private WebKeysConfiguration webKeysConfiguration; + + @Mock + private AbstractCryptoProvider cryptoProvider; + private Ssa ssa; @BeforeMethod public void setUp() { - Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); - cryptoProvider = new AbstractCryptoProvider() { - - @Override - public JSONObject generateKey(Algorithm algorithm, Long expirationTime) throws CryptoProviderException { - return null; - } - - @Override - public JSONObject generateKey(Algorithm algorithm, Long expirationTime, int keyLength) throws CryptoProviderException { - return null; - } - - @Override - public String sign(String signingInput, String keyId, String sharedSecret, SignatureAlgorithm signatureAlgorithm) throws CryptoProviderException { - try { - RSAPrivateKey privateKey = ((RSAKey) JWK.parse(senderJwkJson)).toRSAPrivateKey(); - Signature signature = Signature.getInstance(signatureAlgorithm.getAlgorithm(), "BC"); - signature.initSign(privateKey); - signature.update(signingInput.getBytes()); - - return Base64Util.base64urlencode(signature.sign()); - } catch (JOSEException | ParseException | NoSuchAlgorithmException | NoSuchProviderException | - InvalidKeyException | SignatureException e) { - throw new CryptoProviderException(e); - } - } - - @Override - public boolean verifySignature(String signingInput, String encodedSignature, String keyId, JSONObject jwks, String sharedSecret, SignatureAlgorithm signatureAlgorithm) throws CryptoProviderException { - return false; - } - - @Override - public boolean deleteKey(String keyId) throws CryptoProviderException { - return false; - } - - @Override - public boolean containsKey(String keyId) { - return false; - } - - @Override - public PrivateKey getPrivateKey(String keyId) throws CryptoProviderException { - return null; - } - - @Override - public PublicKey getPublicKey(String alias) throws CryptoProviderException { - return null; - } - }; - Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); calendar.add(Calendar.HOUR, 24); ssa = new Ssa(); @@ -289,60 +217,65 @@ public void getSsaList_withNullParam_valid() { @Test public void generateJwt_executionContextWithPostProcessorNull_jwtValid() { - JSONWebKey jsonWebKey = JSONWebKey.fromJSONObject(new JSONObject(senderJwkJson)); - WebKeysConfiguration webKeysConfiguration = new WebKeysConfiguration(); - webKeysConfiguration.setKeys(Collections.singletonList(jsonWebKey)); - SsaConfiguration ssaConfiguration = new SsaConfiguration(); String issuer = "https://jans.io"; when(appConfiguration.getSsaConfiguration()).thenReturn(ssaConfiguration); when(appConfiguration.getIssuer()).thenReturn(issuer); - ExecutionContext executionContext = mock(ExecutionContext.class); - Jwt jwt = ssaService.generateJwt(ssa, executionContext, webKeysConfiguration, cryptoProvider); - assertSsaJwt(jsonWebKey, ssaConfiguration.getSsaSigningAlg(), issuer, ssa, jwt); + + Jwt jwt = ssaService.generateJwt(ssa, executionContext); + assertSsaJwt(ssaConfiguration.getSsaSigningAlg(), issuer, ssa, jwt); verify(executionContext).getPostProcessor(); + verifyNoMoreInteractions(executionContext); } @Test public void generateJwt_executionContextWithPostProcessor_jwtValid() { - JSONWebKey jsonWebKey = JSONWebKey.fromJSONObject(new JSONObject(senderJwkJson)); - WebKeysConfiguration webKeysConfiguration = new WebKeysConfiguration(); - webKeysConfiguration.setKeys(Collections.singletonList(jsonWebKey)); - SsaConfiguration ssaConfiguration = new SsaConfiguration(); String issuer = "https://jans.io"; when(appConfiguration.getSsaConfiguration()).thenReturn(ssaConfiguration); when(appConfiguration.getIssuer()).thenReturn(issuer); - ExecutionContext executionContext = mock(ExecutionContext.class); when(executionContext.getPostProcessor()).thenReturn(jsonWebResponse -> null); - Jwt jwt = ssaService.generateJwt(ssa, executionContext, webKeysConfiguration, cryptoProvider); - assertSsaJwt(jsonWebKey, ssaConfiguration.getSsaSigningAlg(), issuer, ssa, jwt); + Jwt jwt = ssaService.generateJwt(ssa, executionContext); + assertSsaJwt(ssaConfiguration.getSsaSigningAlg(), issuer, ssa, jwt); verify(executionContext, times(2)).getPostProcessor(); } @Test - public void generateJwt_exceptionWithIsErrorEnabledFalse_runtimeException() { + public void generateJwt_executionContextNotSsaConfiguration_exception() { + ExecutionContext executionContext = mock(ExecutionContext.class); + assertThrows(Exception.class, () -> ssaService.generateJwt(ssa, executionContext)); + verifyNoInteractions(executionContext); + } + + @Test + public void generateJwt_ssa_jwtValid() { + SsaConfiguration ssaConfiguration = new SsaConfiguration(); + String issuer = "https://jans.io"; + when(appConfiguration.getSsaConfiguration()).thenReturn(ssaConfiguration); + when(appConfiguration.getIssuer()).thenReturn(issuer); + + Jwt jwt = ssaService.generateJwt(ssa); + assertSsaJwt(ssaConfiguration.getSsaSigningAlg(), issuer, ssa, jwt); + verifyNoInteractions(log); + } + + @Test + public void generateJwt_ssaLogErrorEnabledFalse_exception() { when(log.isErrorEnabled()).thenReturn(false); - try { - ssaService.generateJwt(ssa, mock(ExecutionContext.class), mock(WebKeysConfiguration.class), cryptoProvider); - } catch (Exception e) { - assertNotNull(e, "Exception is null"); - } + + assertThrows(Exception.class, () -> ssaService.generateJwt(ssa)); verify(log).isErrorEnabled(); verifyNoMoreInteractions(log); } @Test - public void generateJwt_exceptionWithIsErrorEnabledTrue_runtimeException() { + public void generateJwt_ssaLogErrorEnabledTrue_exception() { when(log.isErrorEnabled()).thenReturn(true); - try { - ssaService.generateJwt(ssa, mock(ExecutionContext.class), mock(WebKeysConfiguration.class), cryptoProvider); - } catch (Exception e) { - assertNotNull(e, "Exception is null"); - } + + assertThrows(Exception.class, () -> ssaService.generateJwt(ssa)); verify(log).isErrorEnabled(); verify(log).error(anyString(), any(Throwable.class)); } @@ -361,14 +294,12 @@ public void createUnprocessableEntityResponse_valid_response() { assertEquals(response.getStatus(), HttpStatus.SC_UNPROCESSABLE_ENTITY); } - private static void assertSsaJwt(JSONWebKey jsonWebKey, String ssaSigningAlg, String issuer, Ssa ssa, Jwt jwt) { + private static void assertSsaJwt(String ssaSigningAlg, String issuer, Ssa ssa, Jwt jwt) { assertNotNull(jwt, "The jwt is null"); JwtHeader jwtHeader = jwt.getHeader(); assertNotNull(jwtHeader.getSignatureAlgorithm().getJwsAlgorithm(), "The alg in jwt is null"); assertEquals(jwtHeader.getSignatureAlgorithm().getJwsAlgorithm().toString(), ssaSigningAlg); - assertNotNull(jwtHeader.getKeyId(), "The kid in jwt is null"); - assertEquals(jwtHeader.getKeyId(), jsonWebKey.getKid()); assertNotNull(jwtHeader.getType(), "The type in jwt is null"); assertEquals(jwtHeader.getType().toString(), "jwt"); diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaCreateActionTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaCreateActionTest.java index c845734ce18..6c22dadb6d5 100644 --- a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaCreateActionTest.java +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaCreateActionTest.java @@ -123,10 +123,10 @@ public void create_request_valid() { ExecutionContext executionContext = mock(ExecutionContext.class); ModifySsaResponseContext context = mock(ModifySsaResponseContext.class); - when(ssaContextBuilder.buildModifySsaResponseContext(any(), any(), any(), any(), any())).thenReturn(context); + when(ssaContextBuilder.buildModifySsaResponseContext(any(), any())).thenReturn(context); when(modifySsaResponseService.buildCreateProcessor(any())).thenReturn(jsonWebResponse -> null); when(context.toExecutionContext()).thenReturn(executionContext); - when(ssaService.generateJwt(any(), any(), any(), any())).thenReturn(mock(Jwt.class)); + when(ssaService.generateJwt(any(), any())).thenReturn(mock(Jwt.class)); when(ssaJsonService.getJSONObject(anyString())).thenReturn(mock(JSONObject.class)); when(ssaJsonService.jsonObjectToString(any())).thenReturn("{\"ssa\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\"}"); when(appConfiguration.getSsaConfiguration()).thenReturn(new SsaConfiguration()); @@ -143,10 +143,10 @@ public void create_request_valid() { verify(ssaRestWebServiceValidator).checkScopesPolicy(any(), anyString()); verify(ssaService).persist(any()); verify(log).info(anyString(), any(Ssa.class)); - verify(ssaContextBuilder).buildModifySsaResponseContext(any(), any(), any(), any(), any()); + verify(ssaContextBuilder).buildModifySsaResponseContext(any(), any()); verify(modifySsaResponseService).buildCreateProcessor(any()); verify(context).toExecutionContext(); - verify(ssaService).generateJwt(any(), any(), any(), any()); + verify(ssaService).generateJwt(any(), any()); verify(ssaJsonService).getJSONObject(anyString()); verify(ssaJsonService).jsonObjectToString(any()); diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaGetActionTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaGetActionTest.java index ec49b30b04d..bbfb89010e8 100644 --- a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaGetActionTest.java +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaGetActionTest.java @@ -63,7 +63,7 @@ public void get_withAllParam_valid() { assertEquals(response.getStatus(), Response.Status.OK.getStatusCode()); verify(log).debug(anyString(), any(), any()); verify(errorResponseFactory).validateFeatureEnabled(any()); - verify(ssaContextBuilder).buildModifySsaResponseContext(any(), any(), any(), any(), any()); + verify(ssaContextBuilder).buildModifySsaResponseContext(any(), any()); verify(ssaJsonService).jsonArrayToString(any()); verify(modifySsaResponseService).get(any(), any()); verifyNoMoreInteractions(log, errorResponseFactory); diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaGetJwtActionTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaGetJwtActionTest.java new file mode 100644 index 00000000000..30ca9a0919b --- /dev/null +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaGetJwtActionTest.java @@ -0,0 +1,125 @@ +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.model.common.FeatureFlagType; +import io.jans.as.model.error.ErrorResponseFactory; +import io.jans.as.model.jwt.Jwt; +import io.jans.as.server.ssa.ws.rs.SsaJsonService; +import io.jans.as.server.ssa.ws.rs.SsaRestWebServiceValidator; +import io.jans.as.server.ssa.ws.rs.SsaService; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Response; +import org.json.JSONObject; +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 static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.testng.Assert.*; + +@Listeners(MockitoTestNGListener.class) +public class SsaGetJwtActionTest { + + @InjectMocks + private SsaGetJwtAction ssaGetJwtAction; + + @Mock + private Logger log; + + @Mock + private ErrorResponseFactory errorResponseFactory; + + @Mock + private SsaService ssaService; + + @Mock + private SsaRestWebServiceValidator ssaRestWebServiceValidator; + + @Mock + private SsaJsonService ssaJsonService; + + @Test + public void testGetJwtSsa_jti_validStatus() { + String jti = "test-jti"; + String jwt = "jwt-test"; + Ssa ssa = new Ssa(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("ssa", jwt); + when(ssaRestWebServiceValidator.getClientFromSession()).thenReturn(mock(Client.class)); + when(ssaRestWebServiceValidator.getValidSsaByJti(jti)).thenReturn(ssa); + when(ssaService.generateJwt(ssa)).thenReturn(mock(Jwt.class)); + when(ssaJsonService.getJSONObject(any())).thenReturn(jsonObject); + when(ssaJsonService.jsonObjectToString(jsonObject)).thenReturn(jsonObject.toString()); + + Response response = ssaGetJwtAction.getJwtSsa(jti); + assertNotNull(response); + assertEquals(response.getStatus(), 200); + assertNotNull(response.getEntity()); + JSONObject aux = new JSONObject(response.getEntity().toString()); + assertTrue(aux.has("ssa")); + + verify(log).debug(anyString(), eq(jti)); + verify(errorResponseFactory).validateFeatureEnabled(eq(FeatureFlagType.SSA)); + verify(ssaRestWebServiceValidator).checkScopesPolicy(any(), anyString()); + verifyNoMoreInteractions(log, errorResponseFactory); + } + + @Test + public void testGetJwtSsa_jwtWithErrorEnabledFalse_422Status() { + String jti = "test-jti"; + when(ssaRestWebServiceValidator.getClientFromSession()).thenReturn(mock(Client.class)); + when(ssaRestWebServiceValidator.getValidSsaByJti(jti)).thenThrow(new WebApplicationException(Response.status(422).build())); + when(log.isErrorEnabled()).thenReturn(false); + + WebApplicationException ex = expectThrows(WebApplicationException.class, () -> ssaGetJwtAction.getJwtSsa(jti)); + assertNotNull(ex); + assertEquals(ex.getResponse().getStatus(), 422); + + verify(log).debug(anyString(), eq(jti)); + verify(errorResponseFactory).validateFeatureEnabled(eq(FeatureFlagType.SSA)); + verify(ssaRestWebServiceValidator).checkScopesPolicy(any(), anyString()); + verifyNoInteractions(ssaService, ssaJsonService); + verifyNoMoreInteractions(log, errorResponseFactory); + } + + @Test + public void testGetJwtSsa_jwtWithErrorEnabledTrue_422Status() { + String jti = "test-jti"; + when(ssaRestWebServiceValidator.getClientFromSession()).thenReturn(mock(Client.class)); + when(ssaRestWebServiceValidator.getValidSsaByJti(jti)).thenThrow(new WebApplicationException(Response.status(422).build())); + when(log.isErrorEnabled()).thenReturn(true); + + WebApplicationException ex = expectThrows(WebApplicationException.class, () -> ssaGetJwtAction.getJwtSsa(jti)); + assertNotNull(ex); + assertEquals(ex.getResponse().getStatus(), 422); + + verify(log).debug(anyString(), eq(jti)); + verify(errorResponseFactory).validateFeatureEnabled(eq(FeatureFlagType.SSA)); + verify(ssaRestWebServiceValidator).checkScopesPolicy(any(), anyString()); + verifyNoInteractions(ssaService, ssaJsonService); + verify(log).error(anyString(), eq(ex)); + verifyNoMoreInteractions(log, errorResponseFactory); + } + + @Test + public void testGetJwtSsa_jtiNullPointerException_500Status() { + String jti = "test-jti"; + when(ssaRestWebServiceValidator.getClientFromSession()).thenThrow(new NullPointerException("test null message")); + when(errorResponseFactory.createWebApplicationException(any(), any(), anyString())).thenThrow(new WebApplicationException(Response.status(500).build())); + + WebApplicationException ex = expectThrows(WebApplicationException.class, () -> ssaGetJwtAction.getJwtSsa(jti)); + assertNotNull(ex); + assertEquals(ex.getResponse().getStatus(), 500); + + verify(log).debug(anyString(), eq(jti)); + verify(errorResponseFactory).validateFeatureEnabled(eq(FeatureFlagType.SSA)); + verify(log).error(anyString(), any(Exception.class)); + verifyNoMoreInteractions(ssaRestWebServiceValidator, log); + verifyNoInteractions(ssaService, ssaJsonService); + } +} \ No newline at end of file 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 index a641758c69c..56749b6a088 100644 --- 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 @@ -119,7 +119,7 @@ public void revoke_validJti_200Status() { assertEquals(ssa.getState(), SsaState.REVOKED); verify(log).info(anyString(), eq(jti), eq(SsaState.REVOKED.getValue())); - verify(ssaContextBuilder).buildModifySsaResponseContext(any(), any(), any(), any(), any()); + verify(ssaContextBuilder).buildModifySsaResponseContext(any(), any()); verify(modifySsaResponseService).revoke(any(), any()); verifyNoMoreInteractions(ssaService, log, errorResponseFactory); } diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaValidateActionTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaValidateActionTest.java index b676898bac2..c4cf9bd4bd0 100644 --- a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaValidateActionTest.java +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaValidateActionTest.java @@ -3,8 +3,10 @@ import io.jans.as.common.model.ssa.Ssa; import io.jans.as.common.model.ssa.SsaAttributes; import io.jans.as.common.model.ssa.SsaState; +import io.jans.as.model.common.FeatureFlagType; import io.jans.as.model.error.ErrorResponseFactory; import io.jans.as.model.ssa.SsaErrorResponseType; +import io.jans.as.server.ssa.ws.rs.SsaRestWebServiceValidator; import io.jans.as.server.ssa.ws.rs.SsaService; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Response; @@ -16,9 +18,6 @@ import org.testng.annotations.Listeners; import org.testng.annotations.Test; -import java.util.Calendar; -import java.util.TimeZone; - import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.testng.Assert.*; @@ -38,72 +37,37 @@ public class SsaValidateActionTest { @Mock private SsaService ssaService; - @Test - public void validate_ssaNull_422Status() { - String jti = "e1440ecf-4b68-467c-a032-be1c43183e0c"; - when(ssaService.findSsaByJti(jti)).thenReturn(null); - when(ssaService.createUnprocessableEntityResponse()).thenReturn(Response.status(422)); - - Response response = ssaValidateAction.validate(jti); - assertNotNull(response); - assertEquals(response.getStatus(), 422); - verify(log).debug(anyString(), eq(jti)); - verify(errorResponseFactory).validateFeatureEnabled(any()); - verify(log).warn(anyString(), eq(jti)); - verifyNoMoreInteractions(log, ssaService, errorResponseFactory); - } - - @Test - public void validate_ssaExpired_422Status() { - String jti = "e1440ecf-4b68-467c-a032-be1c43183e0c"; - Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - calendar.add(Calendar.HOUR, -24); - Ssa ssa = new Ssa(); - ssa.setExpirationDate(calendar.getTime()); - when(ssaService.findSsaByJti(jti)).thenReturn(ssa); - when(ssaService.createUnprocessableEntityResponse()).thenReturn(Response.status(422)); - - Response response = ssaValidateAction.validate(jti); - assertNotNull(response); - assertEquals(response.getStatus(), 422); - verify(log).debug(anyString(), eq(jti)); - verify(errorResponseFactory).validateFeatureEnabled(any()); - verify(log).warn(anyString(), eq(jti)); - verifyNoMoreInteractions(log, ssaService, errorResponseFactory); - } + @Mock + private SsaRestWebServiceValidator ssaRestWebServiceValidator; @Test - public void validate_ssaInactive_422Status() { - String jti = "e1440ecf-4b68-467c-a032-be1c43183e0c"; - Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - calendar.add(Calendar.HOUR, 24); + public void validate_ssaWithOneTimeUseFalse_validStatus() { + String jti = "test-jti"; + SsaAttributes attributes = new SsaAttributes(); + attributes.setOneTimeUse(false); Ssa ssa = new Ssa(); - ssa.setExpirationDate(calendar.getTime()); - ssa.setState(SsaState.USED); - when(ssaService.findSsaByJti(jti)).thenReturn(ssa); - when(ssaService.createUnprocessableEntityResponse()).thenReturn(Response.status(422)); + ssa.setState(SsaState.ACTIVE); + ssa.setAttributes(attributes); + when(ssaRestWebServiceValidator.getValidSsaByJti(jti)).thenReturn(ssa); Response response = ssaValidateAction.validate(jti); assertNotNull(response); - assertEquals(response.getStatus(), 422); + assertEquals(response.getStatus(), 200); verify(log).debug(anyString(), eq(jti)); - verify(errorResponseFactory).validateFeatureEnabled(any()); - verify(log).warn(anyString(), eq(jti)); - verifyNoMoreInteractions(log, ssaService, errorResponseFactory); + verify(errorResponseFactory).validateFeatureEnabled(FeatureFlagType.SSA); + verifyNoInteractions(ssaService); + verifyNoMoreInteractions(log, errorResponseFactory); } @Test public void validate_ssaWithOneTimeUseTrue_validStatus() { - String jti = "e1440ecf-4b68-467c-a032-be1c43183e0c"; - Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - calendar.add(Calendar.HOUR, 24); + String jti = "test-jti"; SsaAttributes attributes = new SsaAttributes(); attributes.setOneTimeUse(true); Ssa ssa = new Ssa(); - ssa.setExpirationDate(calendar.getTime()); ssa.setState(SsaState.ACTIVE); ssa.setAttributes(attributes); - when(ssaService.findSsaByJti(jti)).thenReturn(ssa); + when(ssaRestWebServiceValidator.getValidSsaByJti(jti)).thenReturn(ssa); Response response = ssaValidateAction.validate(jti); assertNotNull(response); @@ -122,16 +86,42 @@ public void validate_ssaWithOneTimeUseTrue_validStatus() { assertEquals(ssaAux.getState(), SsaState.USED); } + @Test + public void validate_ssaInvalidWithErrorEnabledFalse_422Status() { + String jti = "test-jti"; + when(ssaRestWebServiceValidator.getValidSsaByJti(jti)).thenThrow(new WebApplicationException(Response.status(422).build())); + when(log.isErrorEnabled()).thenReturn(false); + + WebApplicationException ex = expectThrows(WebApplicationException.class, () -> ssaValidateAction.validate(jti)); + assertNotNull(ex); + assertEquals(ex.getResponse().getStatus(), 422); + verify(log).debug(anyString(), eq(jti)); + verify(errorResponseFactory).validateFeatureEnabled(eq(FeatureFlagType.SSA)); + verifyNoMoreInteractions(ssaRestWebServiceValidator, log, errorResponseFactory, ssaService); + } + + @Test + public void validate_ssaInvalidWithErrorEnabledTrue_422Status() { + String jti = "test-jti"; + when(ssaRestWebServiceValidator.getValidSsaByJti(jti)).thenThrow(new WebApplicationException(Response.status(422).build())); + when(log.isErrorEnabled()).thenReturn(true); + + WebApplicationException ex = expectThrows(WebApplicationException.class, () -> ssaValidateAction.validate(jti)); + assertNotNull(ex); + assertEquals(ex.getResponse().getStatus(), 422); + verify(log).debug(anyString(), eq(jti)); + verify(errorResponseFactory).validateFeatureEnabled(eq(FeatureFlagType.SSA)); + verify(log).error(anyString(), eq(ex)); + verifyNoMoreInteractions(log, errorResponseFactory, ssaService); + } + @Test public void validate_ssaAttributesNullInternalServerError_badRequestResponse() { - String jti = "e1440ecf-4b68-467c-a032-be1c43183e0c"; - Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - calendar.add(Calendar.HOUR, 24); + String jti = "test-jti"; Ssa ssa = new Ssa(); - ssa.setExpirationDate(calendar.getTime()); ssa.setState(SsaState.ACTIVE); ssa.setAttributes(null); - when(ssaService.findSsaByJti(jti)).thenReturn(ssa); + when(ssaRestWebServiceValidator.getValidSsaByJti(jti)).thenReturn(ssa); WebApplicationException error = new WebApplicationException( Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Unknown error")