From de8a86ed1eb29bd02546e9e22fc6f668ac3217c4 Mon Sep 17 00:00:00 2001 From: Milton Ch <86965029+Milton-Ch@users.noreply.github.com> Date: Wed, 2 Nov 2022 13:47:46 -0400 Subject: [PATCH] feat(jans-auth-server): ssa validation endpoint (#2842) --- .../java/io/jans/as/client/BaseResponse.java | 4 +- .../as/client/ssa/create/SsaCreateClient.java | 5 +- .../ssa/validate/SsaValidateClient.java | 58 +++++++ .../ssa/validate/SsaValidateRequest.java | 35 ++++ .../ssa/validate/SsaValidateResponse.java | 27 +++ .../test/java/io/jans/as/client/BaseTest.java | 44 ++++- .../jans/as/client/ssa/SsaCreateRestTest.java | 15 +- .../io/jans/as/client/ssa/SsaGetRestTest.java | 20 +-- .../as/client/ssa/SsaValidateRestTest.java | 154 ++++++++++++++++++ .../java/io/jans/as/common/model/ssa/Ssa.java | 12 ++ .../io/jans/as/common/model/ssa/SsaState.java | 30 ++++ .../as/model/ssa/SsaErrorResponseType.java | 3 +- .../as/server/auth/AuthenticationFilter.java | 5 +- .../server/ssa/ws/rs/SsaRestWebService.java | 7 +- .../ssa/ws/rs/SsaRestWebServiceImpl.java | 11 +- .../jans/as/server/ssa/ws/rs/SsaService.java | 33 +++- .../ssa/ws/rs/action/SsaCreateAction.java | 3 + .../server/ssa/ws/rs/action/SsaGetAction.java | 5 +- .../ssa/ws/rs/action/SsaValidateAction.java | 69 ++++++++ .../server/ssa/ws/rs/SsaJsonServiceTest.java | 2 +- .../ssa/ws/rs/SsaRestWebServiceImplTest.java | 28 +++- .../as/server/ssa/ws/rs/SsaServiceTest.java | 78 ++++++++- .../ssa/ws/rs/action/SsaCreateActionTest.java | 5 +- .../ssa/ws/rs/action/SsaGetActionTest.java | 8 +- .../ws/rs/action/SsaValidateActionTest.java | 147 +++++++++++++++++ .../jans_setup/schema/jans_schema.json | 1 + 26 files changed, 750 insertions(+), 59 deletions(-) create mode 100644 jans-auth-server/client/src/main/java/io/jans/as/client/ssa/validate/SsaValidateClient.java create mode 100644 jans-auth-server/client/src/main/java/io/jans/as/client/ssa/validate/SsaValidateRequest.java create mode 100644 jans-auth-server/client/src/main/java/io/jans/as/client/ssa/validate/SsaValidateResponse.java create mode 100644 jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaValidateRestTest.java create mode 100644 jans-auth-server/common/src/main/java/io/jans/as/common/model/ssa/SsaState.java create mode 100644 jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaValidateAction.java create mode 100644 jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaValidateActionTest.java diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/BaseResponse.java b/jans-auth-server/client/src/main/java/io/jans/as/client/BaseResponse.java index b34c275e29a..30529b20049 100644 --- a/jans-auth-server/client/src/main/java/io/jans/as/client/BaseResponse.java +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/BaseResponse.java @@ -35,7 +35,9 @@ protected BaseResponse(Response clientResponse) { if (clientResponse.getLocation() != null) { location = clientResponse.getLocation().toString(); } - entity = clientResponse.readEntity(String.class); + if (clientResponse.getEntity() != null) { + entity = clientResponse.readEntity(String.class); + } headers = clientResponse.getMetadata(); } } diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/create/SsaCreateClient.java b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/create/SsaCreateClient.java index 8a3a97a7463..63a33f422d9 100644 --- a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/create/SsaCreateClient.java +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/create/SsaCreateClient.java @@ -31,7 +31,8 @@ public String getHttpMethod() { } public SsaCreateResponse execSsaCreate(String accessToken, Long orgId, Long expirationDate, String description, - String softwareId, List softwareRoles, List grantTypes) { + String softwareId, List softwareRoles, List grantTypes, + Boolean oneTimeUse, Boolean rotateSsa) { SsaCreateRequest ssaCreateRequest = new SsaCreateRequest(); ssaCreateRequest.setAccessToken(accessToken); ssaCreateRequest.setOrgId(orgId); @@ -40,6 +41,8 @@ public SsaCreateResponse execSsaCreate(String accessToken, Long orgId, Long expi ssaCreateRequest.setSoftwareId(softwareId); ssaCreateRequest.setSoftwareRoles(softwareRoles); ssaCreateRequest.setGrantTypes(grantTypes); + ssaCreateRequest.setOneTimeUse(oneTimeUse); + ssaCreateRequest.setRotateSsa(rotateSsa); setRequest(ssaCreateRequest); return exec(); } 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 new file mode 100644 index 00000000000..7a21bdee036 --- /dev/null +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/validate/SsaValidateClient.java @@ -0,0 +1,58 @@ +/* + * 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.validate; + +import io.jans.as.client.BaseClient; +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.client.Invocation.Builder; +import org.apache.log4j.Logger; +import org.jetbrains.annotations.NotNull; + +public class SsaValidateClient extends BaseClient { + + private static final Logger LOG = Logger.getLogger(SsaValidateClient.class); + + public SsaValidateClient(String url) { + super(url); + } + + @Override + public String getHttpMethod() { + return HttpMethod.GET; + } + + public SsaValidateResponse execSsaValidate(@NotNull String jti) { + SsaValidateRequest ssaGetRequest = new SsaValidateRequest(); + ssaGetRequest.setJti(jti); + setRequest(ssaGetRequest); + return exec(); + } + + public SsaValidateResponse exec() { + try { + initClient(); + + Builder clientRequest = webTarget.request(); + applyCookies(clientRequest); + + clientRequest.header("Content-Type", request.getContentType()); + clientRequest.header("jti", request.getJti()); + + clientResponse = clientRequest.build(HttpMethod.HEAD).invoke(); + final SsaValidateResponse res = new SsaValidateResponse(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/validate/SsaValidateRequest.java b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/validate/SsaValidateRequest.java new file mode 100644 index 00000000000..8442f0ddd21 --- /dev/null +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/validate/SsaValidateRequest.java @@ -0,0 +1,35 @@ +/* + * 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.validate; + +import io.jans.as.client.BaseRequest; +import io.jans.as.model.common.AuthorizationMethod; +import jakarta.ws.rs.core.MediaType; + +public class SsaValidateRequest extends BaseRequest { + + private String jti; + + public SsaValidateRequest() { + 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; + } + + @Override + public String getQueryString() { + return null; + } +} diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/validate/SsaValidateResponse.java b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/validate/SsaValidateResponse.java new file mode 100644 index 00000000000..42bbdbe21cf --- /dev/null +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/validate/SsaValidateResponse.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.validate; + +import io.jans.as.client.BaseResponseWithErrors; +import io.jans.as.model.ssa.SsaErrorResponseType; +import jakarta.ws.rs.core.Response; + +public class SsaValidateResponse extends BaseResponseWithErrors { + + public SsaValidateResponse(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/test/java/io/jans/as/client/BaseTest.java b/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java index 3d0ad3ee3aa..c051eeba762 100644 --- a/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/BaseTest.java @@ -14,6 +14,8 @@ import io.jans.as.client.page.PageConfig; import io.jans.as.client.par.ParClient; import io.jans.as.client.par.ParRequest; +import io.jans.as.client.ssa.create.SsaCreateClient; +import io.jans.as.client.ssa.create.SsaCreateResponse; import io.jans.as.model.common.GrantType; import io.jans.as.model.common.ResponseMode; import io.jans.as.model.common.ResponseType; @@ -25,6 +27,7 @@ import io.jans.as.model.crypto.signature.SignatureAlgorithm; import io.jans.as.model.error.IErrorType; import io.jans.as.model.register.ApplicationType; +import io.jans.as.model.util.DateUtil; import io.jans.as.model.util.SecurityProviderUtility; import io.jans.as.model.util.Util; import io.jans.util.StringHelper; @@ -1161,11 +1164,11 @@ public RegisterResponse registerClient( return registerResponse; } - - public RegisterResponse registerClient(final String redirectUris, final List responseTypes, - final List grantTypes, final String sectorIdentifierUri, final String clientJwksUri, - final SignatureAlgorithm signatureAlgorithm, final KeyEncryptionAlgorithm keyEncryptionAlgorithm, - final BlockEncryptionAlgorithm blockEncryptionAlgorithm) { + + public RegisterResponse registerClient(final String redirectUris, final List responseTypes, + final List grantTypes, final String sectorIdentifierUri, final String clientJwksUri, + final SignatureAlgorithm signatureAlgorithm, final KeyEncryptionAlgorithm keyEncryptionAlgorithm, + final BlockEncryptionAlgorithm blockEncryptionAlgorithm) { RegisterRequest registerRequest = new RegisterRequest(ApplicationType.WEB, "jans test app", io.jans.as.model.util.StringUtils.spaceSeparatedToList(redirectUris)); registerRequest.setResponseTypes(responseTypes); @@ -1190,7 +1193,7 @@ public RegisterResponse registerClient(final String redirectUris, final List responseTypes, final ResponseMode responseMode, final ResponseMode expectedResponseMode, @@ -1243,4 +1246,33 @@ public TokenResponse tokenClientCredentialsGrant(String scope, String clientId, AssertBuilder.tokenResponse(tokenResponse).ok().check(); return tokenResponse; } + + public SsaCreateResponse createSsaWithDefaultValues(String accessToken, Long orgId, Long expiration, Boolean oneTimeUse) { + Long orgIdAux = orgId != null ? orgId : 1000L; + String descriptionAux = "test description"; + String softwareIdAux = "gluu-scan-api"; + Long expirationAux; + if (expiration == null) { + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.add(Calendar.HOUR, 24); + expirationAux = DateUtil.dateToUnixEpoch(calendar.getTime()); + } else { + expirationAux = expiration; + } + List softwareRolesAux = Collections.singletonList("password"); + List grantTypesAux = Collections.singletonList("client_credentials"); + return createSsa(accessToken, orgIdAux, expirationAux, descriptionAux, softwareIdAux, softwareRolesAux, + grantTypesAux, oneTimeUse, Boolean.TRUE); + } + + public SsaCreateResponse createSsa(String accessToken, Long orgId, Long expiration, String description, + String softwareId, List softwareRoles, List grantTypes, + Boolean oneTimeUse, Boolean rotateSsa) { + SsaCreateClient ssaCreateClient = new SsaCreateClient(ssaEndpoint); + SsaCreateResponse response = ssaCreateClient.execSsaCreate(accessToken, orgId, expiration, description, softwareId, + softwareRoles, grantTypes, oneTimeUse, rotateSsa); + showClient(ssaCreateClient); + AssertBuilder.ssaCreate(ssaCreateClient.getRequest(), response); + return response; + } } 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/SsaCreateRestTest.java index 4d9897e661a..a06a71840a2 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/SsaCreateRestTest.java @@ -48,9 +48,10 @@ public void createSsaValid(final String redirectUris, final String sectorIdentif Long expirationDate = DateUtil.dateToUnixEpoch(calendar.getTime()); String description = "test description"; String softwareId = "gluu-scan-api"; - List softwareRoles = Collections.singletonList("passwurd"); + List softwareRoles = Collections.singletonList("password"); List ssaGrantTypes = Collections.singletonList("client_credentials"); - SsaCreateResponse ssaCreateResponse = ssaCreateClient.execSsaCreate(accessToken, orgId, expirationDate, description, softwareId, softwareRoles, ssaGrantTypes); + SsaCreateResponse ssaCreateResponse = ssaCreateClient.execSsaCreate(accessToken, orgId, expirationDate, description, + softwareId, softwareRoles, ssaGrantTypes, Boolean.TRUE, Boolean.TRUE); showClient(ssaCreateClient); AssertBuilder.ssaCreate(ssaCreateClient.getRequest(), ssaCreateResponse).check(); @@ -80,9 +81,10 @@ public void createSsaInvalidWithoutScopeAdmin(final String redirectUris, final S Long expirationDate = DateUtil.dateToUnixEpoch(calendar.getTime()); String description = "test description"; String softwareId = "gluu-scan-api"; - List softwareRoles = Collections.singletonList("passwurd"); + List softwareRoles = Collections.singletonList("password"); List ssaGrantTypes = Collections.singletonList("client_credentials"); - SsaCreateResponse ssaCreateResponse = ssaCreateClient.execSsaCreate(accessToken, orgId, expirationDate, description, softwareId, softwareRoles, ssaGrantTypes); + SsaCreateResponse ssaCreateResponse = ssaCreateClient.execSsaCreate(accessToken, orgId, expirationDate, description, + softwareId, softwareRoles, ssaGrantTypes, Boolean.TRUE, Boolean.TRUE); showClient(ssaCreateClient); AssertBuilder.ssaCreate(ssaCreateClient.getRequest(), ssaCreateResponse) @@ -110,9 +112,10 @@ public void createSsaValidWithoutExpiration(final String redirectUris, final Str Long orgId = 1L; String description = "test description"; String softwareId = "gluu-scan-api"; - List softwareRoles = Collections.singletonList("passwurd"); + List softwareRoles = Collections.singletonList("password"); List ssaGrantTypes = Collections.singletonList("client_credentials"); - SsaCreateResponse ssaCreateResponse = ssaCreateClient.execSsaCreate(accessToken, orgId, null, description, softwareId, softwareRoles, ssaGrantTypes); + SsaCreateResponse ssaCreateResponse = ssaCreateClient.execSsaCreate(accessToken, orgId, null, description, + softwareId, softwareRoles, ssaGrantTypes, Boolean.FALSE, Boolean.FALSE); showClient(ssaCreateClient); AssertBuilder.ssaCreate(ssaCreateClient.getRequest(), ssaCreateResponse).check(); 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/SsaGetRestTest.java index 0983fbc1cbe..58aad88d3cf 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/SsaGetRestTest.java @@ -10,7 +10,6 @@ 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.SsaCreateClient; import io.jans.as.client.ssa.create.SsaCreateResponse; import io.jans.as.client.ssa.get.SsaGetClient; import io.jans.as.client.ssa.get.SsaGetResponse; @@ -48,8 +47,7 @@ public void getSsaSearchByOrgId(final String redirectUris, final String sectorId Long orgId1 = 1000L; Long orgId2 = 2000L; List ssaCreateOrgId = Arrays.asList(orgId1, orgId1, orgId2); - SsaCreateClient ssaCreateClient = new SsaCreateClient(ssaEndpoint); - List jtiList = createSsaList(ssaCreateClient, accessToken, ssaCreateOrgId); + List jtiList = createSsaList(accessToken, ssaCreateOrgId); // Ssa get SsaGetClient ssaGetClient = new SsaGetClient(ssaEndpoint); @@ -79,8 +77,7 @@ public void getSsaSearchByJti(final String redirectUris, final String sectorIden // Create ssa Long orgId1 = 1000L; List ssaCreateOrgId = Arrays.asList(orgId1, orgId1); - SsaCreateClient ssaCreateClient = new SsaCreateClient(ssaEndpoint); - List jtiList = createSsaList(ssaCreateClient, accessToken, ssaCreateOrgId); + List jtiList = createSsaList(accessToken, ssaCreateOrgId); String jti = jtiList.get(0); // Ssa get @@ -112,8 +109,7 @@ public void getSsaSearchByOrgIdAndJti(final String redirectUris, final String se Long orgId1 = 1000L; Long orgId2 = 2000L; List ssaCreateOrgId = Arrays.asList(orgId1, orgId1, orgId2); - SsaCreateClient ssaCreateClient = new SsaCreateClient(ssaEndpoint); - List jtiList = createSsaList(ssaCreateClient, accessToken, ssaCreateOrgId); + List jtiList = createSsaList(accessToken, ssaCreateOrgId); String jti = jtiList.get(0); // Ssa get @@ -144,8 +140,7 @@ public void getSsaSearchByJtiNotExits(final String redirectUris, final String se // Create ssa Long orgId1 = 1000L; List ssaCreateOrgId = Arrays.asList(orgId1, orgId1); - SsaCreateClient ssaCreateClient = new SsaCreateClient(ssaEndpoint); - List jtiList = createSsaList(ssaCreateClient, accessToken, ssaCreateOrgId); + List jtiList = createSsaList(accessToken, ssaCreateOrgId); String jti = "jti-not-found"; // Ssa get @@ -157,14 +152,11 @@ public void getSsaSearchByJtiNotExits(final String redirectUris, final String se .check(); } - private List createSsaList(SsaCreateClient ssaCreateClient, String accessToken, List ssaCreateRequestList) { + private List createSsaList(String accessToken, List ssaCreateRequestList) { List jtiList = new ArrayList<>(); for (int i = 0; i < ssaCreateRequestList.size(); i++) { Long orgId = ssaCreateRequestList.get(i); - SsaCreateResponse ssaCreateResponse = ssaCreateClient.execSsaCreate(accessToken, orgId, null, - "test description", "gluu-scan-api", Collections.singletonList("passwurd"), - Collections.singletonList("client_credentials")); - showClient(ssaCreateClient); + SsaCreateResponse ssaCreateResponse = createSsaWithDefaultValues(accessToken, orgId, null, Boolean.TRUE); Assert.assertNotNull(ssaCreateResponse, "Ssa create response is null, index: " + i); jtiList.add(ssaCreateResponse.getJti()); } 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/SsaValidateRestTest.java new file mode 100644 index 00000000000..e2ed18ce60d --- /dev/null +++ b/jans-auth-server/client/src/test/java/io/jans/as/client/ssa/SsaValidateRestTest.java @@ -0,0 +1,154 @@ +/* + * 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.validate.SsaValidateClient; +import io.jans.as.client.ssa.validate.SsaValidateResponse; +import io.jans.as.model.common.GrantType; +import io.jans.as.model.common.ResponseType; +import io.jans.as.model.ssa.SsaScopeType; +import io.jans.as.model.util.DateUtil; +import org.apache.http.HttpStatus; +import org.testng.annotations.Parameters; +import org.testng.annotations.Test; + +import java.util.Calendar; +import java.util.Collections; +import java.util.List; +import java.util.TimeZone; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +public class SsaValidateRestTest extends BaseTest { + + @Parameters({"redirectUris", "sectorIdentifierUri"}) + @Test + public void validateWithOneTimeUseTrueResponseOK(final String redirectUris, final String sectorIdentifierUri) { + showTitle("validateWithOneTimeUseTrueResponseOK"); + 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 validation + SsaValidateClient ssaValidateClient = new SsaValidateClient(ssaEndpoint); + SsaValidateResponse ssaGetResponseFirst = ssaValidateClient.execSsaValidate(jti); + showClient(ssaValidateClient); + assertNotNull(ssaGetResponseFirst, "Response is null"); + assertEquals(ssaGetResponseFirst.getStatus(), HttpStatus.SC_OK); + + // Ssa second validation + SsaValidateResponse ssaGetResponseSecond = ssaValidateClient.execSsaValidate(jti); + showClient(ssaValidateClient); + assertNotNull(ssaGetResponseSecond, "Response is null"); + assertEquals(ssaGetResponseSecond.getStatus(), 422); + } + + @Parameters({"redirectUris", "sectorIdentifierUri"}) + @Test + public void validateWithOneTimeUseFalseResponseOK(final String redirectUris, final String sectorIdentifierUri) { + showTitle("validateWithOneTimeUseFalseResponseOK"); + 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.FALSE); + String jti = ssaCreateResponse.getJti(); + + // Ssa first validation + SsaValidateClient ssaValidateClient = new SsaValidateClient(ssaEndpoint); + SsaValidateResponse ssaGetResponseFirst = ssaValidateClient.execSsaValidate(jti); + showClient(ssaValidateClient); + assertNotNull(ssaGetResponseFirst, "Response is null"); + assertEquals(ssaGetResponseFirst.getStatus(), HttpStatus.SC_OK); + + // Ssa second validation + SsaValidateResponse ssaGetResponseSecond = ssaValidateClient.execSsaValidate(jti); + showClient(ssaValidateClient); + assertNotNull(ssaGetResponseSecond, "Response is null"); + assertEquals(ssaGetResponseSecond.getStatus(), HttpStatus.SC_OK); + } + + @Parameters({"redirectUris", "sectorIdentifierUri"}) + @Test + public void validateWithNotFoundSsaResponse422(final String redirectUris, final String sectorIdentifierUri) { + showTitle("validateWithNotFoundSsaResponse422"); + 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"; + + // Ssa validation + SsaValidateClient ssaValidateClient = new SsaValidateClient(ssaEndpoint); + SsaValidateResponse ssaGetResponse = ssaValidateClient.execSsaValidate(jti); + assertNotNull(ssaGetResponse, "Response is null"); + assertEquals(ssaGetResponse.getStatus(), 422); + } + + @Parameters({"redirectUris", "sectorIdentifierUri"}) + @Test + public void validateWithExpiredSsaResponse422(final String redirectUris, final String sectorIdentifierUri) { + showTitle("validateWithExpiredSsaResponse422"); + 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 + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.add(Calendar.HOUR, -24); + SsaCreateResponse ssaCreateResponse = createSsaWithDefaultValues(accessToken, null, DateUtil.dateToUnixEpoch(calendar.getTime()), Boolean.FALSE); + String jti = ssaCreateResponse.getJti(); + + // Ssa validation + SsaValidateClient ssaValidateClient = new SsaValidateClient(ssaEndpoint); + SsaValidateResponse ssaGetResponse = ssaValidateClient.execSsaValidate(jti); + assertNotNull(ssaGetResponse, "Response is null"); + assertEquals(ssaGetResponse.getStatus(), 422); + } +} \ No newline at end of file diff --git a/jans-auth-server/common/src/main/java/io/jans/as/common/model/ssa/Ssa.java b/jans-auth-server/common/src/main/java/io/jans/as/common/model/ssa/Ssa.java index bf50d447219..b4b4c6d87f0 100644 --- a/jans-auth-server/common/src/main/java/io/jans/as/common/model/ssa/Ssa.java +++ b/jans-auth-server/common/src/main/java/io/jans/as/common/model/ssa/Ssa.java @@ -36,6 +36,9 @@ public class Ssa extends DeletableEntity implements Serializable { @AttributeName(name = "description") private String description; + @AttributeName(name = "jansState") + private SsaState state; + @AttributeName(name = "creationDate") private Date creationDate = new Date(); @@ -107,6 +110,14 @@ public void setDescription(String description) { this.description = description; } + public SsaState getState() { + return state; + } + + public void setState(SsaState state) { + this.state = state; + } + public Date getCreationDate() { return creationDate; } @@ -122,6 +133,7 @@ public String toString() { ", attributes=" + attributes + ", orgId='" + orgId + '\'' + ", description='" + description + '\'' + + ", state='" + state + '\'' + ", creationDate=" + creationDate + ", creatorId='" + creatorId + '\'' + ", creatorType=" + creatorType + diff --git a/jans-auth-server/common/src/main/java/io/jans/as/common/model/ssa/SsaState.java b/jans-auth-server/common/src/main/java/io/jans/as/common/model/ssa/SsaState.java new file mode 100644 index 00000000000..f3183385aef --- /dev/null +++ b/jans-auth-server/common/src/main/java/io/jans/as/common/model/ssa/SsaState.java @@ -0,0 +1,30 @@ +/* + * 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.common.model.ssa; + +import io.jans.orm.annotation.AttributeEnum; + +import java.util.Arrays; + +public enum SsaState implements AttributeEnum { + + ACTIVE, + EXPIRED, + REVOKED, + USED, + ; + + @Override + public String getValue() { + return name(); + } + + @Override + public Enum resolveByValue(String s) { + return Arrays.stream(values()).filter(x -> x.name().equalsIgnoreCase(s)).findFirst().orElse(null); + } +} diff --git a/jans-auth-server/model/src/main/java/io/jans/as/model/ssa/SsaErrorResponseType.java b/jans-auth-server/model/src/main/java/io/jans/as/model/ssa/SsaErrorResponseType.java index 089712f3289..5d70a39529d 100644 --- a/jans-auth-server/model/src/main/java/io/jans/as/model/ssa/SsaErrorResponseType.java +++ b/jans-auth-server/model/src/main/java/io/jans/as/model/ssa/SsaErrorResponseType.java @@ -23,7 +23,8 @@ public enum SsaErrorResponseType implements IErrorType { /** * When creating an ssa, if you get an internal error. */ - UNKNOWN_ERROR("unknown_error"); + UNKNOWN_ERROR("unknown_error"), + ; private final String paramName; 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 6ba50b87d91..05ef363b5f9 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 @@ -44,6 +44,7 @@ import io.jans.model.security.Identity; import io.jans.service.CacheService; import io.jans.util.StringHelper; +import jakarta.ws.rs.HttpMethod; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpHeaders; @@ -67,6 +68,7 @@ import java.io.PrintWriter; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Date; import java.util.List; @@ -164,7 +166,8 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo boolean deviceAuthorizationEndpoint = requestUrl.endsWith("/device_authorization"); boolean revokeSessionEndpoint = requestUrl.endsWith("/revoke_session"); boolean isParEndpoint = requestUrl.endsWith("/par"); - boolean ssaEndpoint = requestUrl.endsWith("/ssa"); + boolean ssaEndpoint = requestUrl.endsWith("/ssa") && + (Arrays.asList(HttpMethod.POST, HttpMethod.GET).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/ssa/ws/rs/SsaRestWebService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaRestWebService.java index 1aa591708f9..3c6ca2818c8 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 @@ -28,7 +28,12 @@ Response create( Response get( @QueryParam("software_roles") Boolean softwareRoles, @QueryParam("jti") String jti, - @QueryParam("org_id") String orgId, + @QueryParam("org_id") Long orgId, @Context HttpServletRequest httpRequest ); + + @HEAD + @Path("/ssa") + @Produces({MediaType.APPLICATION_JSON}) + Response validate(@HeaderParam("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 942de37be62..f903461ff62 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.SsaValidateAction; import jakarta.inject.Inject; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.Path; @@ -22,13 +23,21 @@ public class SsaRestWebServiceImpl implements SsaRestWebService { @Inject private SsaGetAction ssaGetAction; + @Inject + private SsaValidateAction ssaValidateAction; + @Override public Response create(String requestParams, HttpServletRequest httpRequest) { return ssaCreateAction.create(requestParams, httpRequest); } @Override - public Response get(Boolean softwareRoles, String jti, String orgId, HttpServletRequest httpRequest) { + public Response get(Boolean softwareRoles, String jti, Long orgId, HttpServletRequest httpRequest) { return ssaGetAction.get(softwareRoles, jti, orgId, httpRequest); } + + @Override + public Response validate(String jti) { + return ssaValidateAction.validate(jti); + } } \ 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 6a0ba660590..69629142d9f 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 @@ -6,6 +6,7 @@ package io.jans.as.server.ssa.ws.rs; import io.jans.as.common.model.ssa.Ssa; +import io.jans.as.common.model.ssa.SsaState; import io.jans.as.model.config.StaticConfiguration; import io.jans.as.model.config.WebKeysConfiguration; import io.jans.as.model.configuration.AppConfiguration; @@ -16,10 +17,15 @@ import io.jans.as.server.model.common.ExecutionContext; import io.jans.as.server.model.token.JwtSigner; import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.exception.EntryPersistenceException; import io.jans.orm.search.filter.Filter; +import io.jans.util.StringHelper; import jakarta.ejb.Stateless; import jakarta.inject.Inject; import jakarta.inject.Named; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.apache.http.HttpStatus; import org.slf4j.Logger; import java.util.ArrayList; @@ -53,9 +59,15 @@ public void merge(Ssa ssa) { persistenceEntryManager.merge(ssa); } - public List getSsaList(String jti, String orgId, String clientId, String[] scopes) { - String dn = staticConfiguration.getBaseDn().getSsa(); + public Ssa findSsaByJti(String jti) { + try { + return persistenceEntryManager.find(Ssa.class, getDnForSsa(jti)); + } catch (EntryPersistenceException e) { + return null; + } + } + public List getSsaList(String jti, Long orgId, SsaState status, String clientId, String[] scopes) { List filters = new ArrayList<>(); if (hasPortalScope(Arrays.asList(scopes))) { filters.add(Filter.createEqualityFilter("creatorId", clientId)); @@ -66,12 +78,15 @@ public List getSsaList(String jti, String orgId, String clientId, String[] if (orgId != null) { filters.add(Filter.createEqualityFilter("o", orgId)); } + if (status != null) { + filters.add(Filter.createEqualityFilter("jansState", status)); + } Filter filter = null; if (!filters.isEmpty()) { filter = Filter.createANDFilter(filters); log.trace("Filter with AND created: " + filters); } - return persistenceEntryManager.findEntries(dn, Ssa.class, filter); + return persistenceEntryManager.findEntries(getDnForSsa(null), Ssa.class, filter); } public Jwt generateJwt(Ssa ssa, ExecutionContext executionContext, WebKeysConfiguration webKeysConfiguration, AbstractCryptoProvider cryptoProvider) { @@ -99,6 +114,10 @@ public Jwt generateJwt(Ssa ssa, ExecutionContext executionContext, WebKeysConfig } } + public Response.ResponseBuilder createUnprocessableEntityResponse() { + return Response.status(HttpStatus.SC_UNPROCESSABLE_ENTITY).type(MediaType.APPLICATION_JSON_TYPE); + } + private boolean hasPortalScope(List scopes) { Iterator scopesIterator = scopes.iterator(); boolean result = false; @@ -112,4 +131,12 @@ private boolean hasPortalScope(List scopes) { } return result; } + + private String getDnForSsa(String ssaId) { + String baseDn = staticConfiguration.getBaseDn().getSsa(); + if (StringHelper.isEmpty(ssaId)) { + return baseDn; + } + return String.format("inum=%s,%s", ssaId, baseDn); + } } 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 fcb5d40c34f..6e22d96aebf 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 @@ -9,6 +9,7 @@ import io.jans.as.client.ssa.create.SsaCreateRequest; 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; @@ -91,6 +92,7 @@ public Response create(String requestParams, HttpServletRequest httpRequest) { JSONObject jsonRequest = new JSONObject(requestParams); final SsaCreateRequest ssaCreateRequest = SsaCreateRequest.fromJson(jsonRequest); log.debug("Attempting to create ssa: {}", ssaCreateRequest); + log.trace("Ssa request = {}", requestParams); String ssaBaseDN = staticConfiguration.getBaseDn().getSsa(); String inum = inumService.generateDefaultId(); @@ -116,6 +118,7 @@ public Response create(String requestParams, HttpServletRequest httpRequest) { ssa.getAttributes().setOneTimeUse(ssaCreateRequest.getOneTimeUse()); ssa.getAttributes().setRotateSsa(ssaCreateRequest.getRotateSsa()); ssa.setCreatorType(CreatorType.CLIENT); + ssa.setState(SsaState.ACTIVE); ssa.setCreatorId(client.getClientId()); ssa.setCreationDate(creationDate); 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 ad852935d29..46c1fce17ee 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 @@ -8,6 +8,7 @@ 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; @@ -66,7 +67,7 @@ public class SsaGetAction { @Inject private SsaContextBuilder ssaContextBuilder; - public Response get(Boolean softwareRoles, String jti, String orgId, HttpServletRequest httpRequest) { + public Response get(Boolean softwareRoles, String jti, Long orgId, HttpServletRequest httpRequest) { log.debug("Attempting to read ssa: softwareRoles = {}, jti = '{}', orgId = {}", softwareRoles, jti, orgId); errorResponseFactory.validateFeatureEnabled(FeatureFlagType.SSA); @@ -75,7 +76,7 @@ public Response get(Boolean softwareRoles, String jti, String orgId, HttpServlet final Client client = ssaRestWebServiceValidator.getClientFromSession(); ssaRestWebServiceValidator.checkScopesPolicy(client, Arrays.asList(SsaScopeType.SSA_ADMIN.getValue(), SsaScopeType.SSA_PORTAL.getValue())); - final List ssaList = ssaService.getSsaList(jti, orgId, client.getClientId(), client.getScopes()); + 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); 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 new file mode 100644 index 00000000000..547b2b5890c --- /dev/null +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/action/SsaValidateAction.java @@ -0,0 +1,69 @@ +/* + * 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.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.error.ErrorResponseFactory; +import io.jans.as.model.ssa.SsaErrorResponseType; +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.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.slf4j.Logger; + +import java.util.Calendar; +import java.util.TimeZone; + +@Stateless +@Named +public class SsaValidateAction { + + @Inject + private Logger log; + + @Inject + private ErrorResponseFactory errorResponseFactory; + + @Inject + private SsaService ssaService; + + public Response validate(String jti) { + log.debug("Attempting to validate ssa jti: '{}'", 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(); + } + if (ssa.getAttributes().getOneTimeUse()) { + ssa.setState(SsaState.USED); + ssaService.merge(ssa); + log.info("Ssa jti: '{}', updated with status: {}", ssa.getId(), ssa.getState()); + } + + } 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/test/java/io/jans/as/server/ssa/ws/rs/SsaJsonServiceTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaJsonServiceTest.java index 92432c73047..c04ea862544 100644 --- a/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaJsonServiceTest.java +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/SsaJsonServiceTest.java @@ -67,7 +67,7 @@ public void getJSONArray_ssaList_validJson() { ssa.setExpirationDate(calendar.getTime()); ssa.setDescription("Test description"); ssa.getAttributes().setSoftwareId("scan-api-test"); - ssa.getAttributes().setSoftwareRoles(Collections.singletonList("passwurd")); + ssa.getAttributes().setSoftwareRoles(Collections.singletonList("password")); ssa.getAttributes().setGrantTypes(Collections.singletonList("client_credentials")); ssa.getAttributes().setOneTimeUse(true); ssa.getAttributes().setRotateSsa(true); 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 0572daef9e1..746cf525d64 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.SsaValidateAction; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.core.Response; import org.mockito.InjectMocks; @@ -19,7 +20,7 @@ public class SsaRestWebServiceImplTest { @InjectMocks - private SsaRestWebServiceImpl ssaRestWebService; + private SsaRestWebServiceImpl ssaRestWebServiceImpl; @Mock private SsaCreateAction ssaCreateAction; @@ -27,23 +28,36 @@ public class SsaRestWebServiceImplTest { @Mock private SsaGetAction ssaGetAction; + @Mock + private SsaValidateAction ssaValidateAction; + @Test public void create_validParams_validResponse() { when(ssaCreateAction.create(anyString(), any())).thenReturn(mock(Response.class)); - Response response = ssaRestWebService.create("test request", mock(HttpServletRequest.class)); - assertNotNull(response, "response is null"); + Response response = ssaRestWebServiceImpl.create("test request", mock(HttpServletRequest.class)); + assertNotNull(response, "response is null"); verify(ssaCreateAction).create(anyString(), any()); verifyNoMoreInteractions(ssaCreateAction); } @Test public void get_validParams_validResponse() { - when(ssaGetAction.get(anyBoolean(), anyString(), anyString(), any())).thenReturn(mock(Response.class)); - Response response = ssaRestWebService.get(false, "testJti", "testOrgId", mock(HttpServletRequest.class)); - assertNotNull(response, "response is null"); + when(ssaGetAction.get(anyBoolean(), anyString(), any(), any())).thenReturn(mock(Response.class)); - verify(ssaGetAction).get(anyBoolean(), anyString(), anyString(), any()); + Response response = ssaRestWebServiceImpl.get(false, "testJti", 1000L, mock(HttpServletRequest.class)); + assertNotNull(response, "response is null"); + verify(ssaGetAction).get(anyBoolean(), anyString(), any(), any()); verifyNoMoreInteractions(ssaGetAction); } + + @Test + public void validate_validParams_validResponse() { + when(ssaValidateAction.validate(anyString())).thenReturn(mock(Response.class)); + + Response response = ssaRestWebServiceImpl.validate("testJti"); + assertNotNull(response, "response is null"); + verify(ssaValidateAction).validate(anyString()); + verifyNoMoreInteractions(ssaValidateAction); + } } \ 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 bdb0f92cd8f..21163ed07e0 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 @@ -4,6 +4,7 @@ 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; import io.jans.as.model.config.StaticConfiguration; import io.jans.as.model.config.WebKeysConfiguration; @@ -21,6 +22,9 @@ 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; @@ -136,7 +140,7 @@ public PublicKey getPublicKey(String alias) throws CryptoProviderException { ssa.setExpirationDate(calendar.getTime()); ssa.setDescription("Test description"); ssa.getAttributes().setSoftwareId("scan-api-test"); - ssa.getAttributes().setSoftwareRoles(Collections.singletonList("passwurd")); + ssa.getAttributes().setSoftwareRoles(Collections.singletonList("password")); ssa.getAttributes().setGrantTypes(Collections.singletonList("client_credentials")); ssa.getAttributes().setOneTimeUse(true); ssa.getAttributes().setRotateSsa(true); @@ -166,6 +170,33 @@ public void merge_ssa_valid() { assertSsaWithAux(ssa, ssaArgumentCaptor.getValue()); } + @Test + public void findSsaByJti_jtiValid_ssaValid() { + String jti = "my-jti"; + BaseDnConfiguration baseDnConfiguration = new BaseDnConfiguration(); + baseDnConfiguration.setSsa("ou=ssa,o=jans"); + when(staticConfiguration.getBaseDn()).thenReturn(baseDnConfiguration); + when(persistenceEntryManager.find(any(), anyString())).thenReturn(ssa); + + Ssa ssaAux = ssaService.findSsaByJti(jti); + assertNotNull(ssaAux, "ssa is null"); + verifyNoMoreInteractions(persistenceEntryManager); + } + + @Test + public void findSsaByJti_jtiNotFound_ssaNull() { + String jti = "my-jti"; + BaseDnConfiguration baseDnConfiguration = new BaseDnConfiguration(); + baseDnConfiguration.setSsa("ou=ssa,o=jans"); + when(staticConfiguration.getBaseDn()).thenReturn(baseDnConfiguration); + EntryPersistenceException error = new EntryPersistenceException(" Failed to lookup entry by key"); + when(persistenceEntryManager.find(any(), anyString())).thenThrow(error); + + Ssa ssaAux = ssaService.findSsaByJti(jti); + assertNull(ssaAux, "ssa is not null"); + verifyNoMoreInteractions(persistenceEntryManager); + } + @Test public void getSsaList_withPortalScope_valid() { BaseDnConfiguration baseDnConfiguration = new BaseDnConfiguration(); @@ -173,10 +204,11 @@ public void getSsaList_withPortalScope_valid() { when(staticConfiguration.getBaseDn()).thenReturn(baseDnConfiguration); String jti = null; - String orgId = null; + Long orgId = null; + SsaState status = null; String clientId = "test-client"; String[] scopes = new String[]{SsaScopeType.SSA_PORTAL.getValue()}; - List ssaList = ssaService.getSsaList(jti, orgId, clientId, scopes); + List ssaList = ssaService.getSsaList(jti, orgId, status, clientId, scopes); assertNotNull(ssaList); verify(log).trace(eq("Filter with AND created: " + String.format("[(creatorId=%s)]", clientId))); verify(persistenceEntryManager).findEntries(any(), any(), any()); @@ -190,10 +222,11 @@ public void getSsaList_withJti_valid() { when(staticConfiguration.getBaseDn()).thenReturn(baseDnConfiguration); String jti = "test-jti"; - String orgId = null; + Long orgId = null; + SsaState status = null; String clientId = "test-client"; String[] scopes = new String[]{}; - List ssaList = ssaService.getSsaList(jti, orgId, clientId, scopes); + List ssaList = ssaService.getSsaList(jti, orgId, status, clientId, scopes); assertNotNull(ssaList); verify(log).trace(eq("Filter with AND created: " + String.format("[(inum=%s)]", jti))); verify(persistenceEntryManager).findEntries(any(), any(), any()); @@ -207,16 +240,35 @@ public void getSsaList_withOrgId_valid() { when(staticConfiguration.getBaseDn()).thenReturn(baseDnConfiguration); String jti = null; - String orgId = "test-org"; + Long orgId = 1000L; + SsaState status = null; String clientId = "test-client"; String[] scopes = new String[]{}; - List ssaList = ssaService.getSsaList(jti, orgId, clientId, scopes); + List ssaList = ssaService.getSsaList(jti, orgId, status, clientId, scopes); assertNotNull(ssaList); verify(log).trace(eq("Filter with AND created: " + String.format("[(o=%s)]", orgId))); verify(persistenceEntryManager).findEntries(any(), any(), any()); verifyNoMoreInteractions(log); } + @Test + public void getSsaList_withStatus_valid() { + BaseDnConfiguration baseDnConfiguration = new BaseDnConfiguration(); + baseDnConfiguration.setSsa("ou=ssa,o=jans"); + when(staticConfiguration.getBaseDn()).thenReturn(baseDnConfiguration); + + String jti = null; + Long orgId = null; + SsaState status = SsaState.ACTIVE; + String clientId = "test-client"; + String[] scopes = new String[]{}; + List ssaList = ssaService.getSsaList(jti, orgId, status, clientId, scopes); + assertNotNull(ssaList); + verify(log).trace(eq("Filter with AND created: " + String.format("[(jansState=%s)]", status))); + verify(persistenceEntryManager).findEntries(any(), any(), any()); + verifyNoMoreInteractions(log); + } + @Test public void getSsaList_withNullParam_valid() { BaseDnConfiguration baseDnConfiguration = new BaseDnConfiguration(); @@ -224,10 +276,11 @@ public void getSsaList_withNullParam_valid() { when(staticConfiguration.getBaseDn()).thenReturn(baseDnConfiguration); String jti = null; - String orgId = null; + Long orgId = null; + SsaState status = null; String clientId = null; String[] scopes = new String[]{}; - List ssaList = ssaService.getSsaList(jti, orgId, clientId, scopes); + List ssaList = ssaService.getSsaList(jti, orgId, status, clientId, scopes); assertNotNull(ssaList); assertTrue(ssaList.isEmpty()); verify(persistenceEntryManager).findEntries(any(), any(), any()); @@ -294,6 +347,13 @@ public void generateJwt_exceptionWithIsErrorEnabledTrue_runtimeException() { verify(log).error(anyString(), any(Throwable.class)); } + @Test + public void createUnprocessableEntityResponse_valid_response() { + Response response = ssaService.createUnprocessableEntityResponse().build(); + assertNotNull(response, "Response is null"); + assertEquals(response.getStatus(), HttpStatus.SC_UNPROCESSABLE_ENTITY); + } + private static void assertSsaJwt(JSONWebKey jsonWebKey, String ssaSigningAlg, String issuer, Ssa ssa, Jwt jwt) { assertNotNull(jwt, "The jwt is null"); 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 f44ae9a2a92..c845734ce18 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 @@ -3,6 +3,7 @@ import io.jans.as.client.ssa.create.SsaCreateRequest; 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.config.BaseDnConfiguration; @@ -94,7 +95,7 @@ public void setUp() { ssa.setExpirationDate(calendar.getTime()); ssa.setDescription("test description"); ssa.getAttributes().setSoftwareId("gluu-scan-api"); - ssa.getAttributes().setSoftwareRoles(Collections.singletonList("passwurd")); + ssa.getAttributes().setSoftwareRoles(Collections.singletonList("password")); ssa.getAttributes().setGrantTypes(Collections.singletonList("client_credentials")); ssa.getAttributes().setOneTimeUse(true); ssa.getAttributes().setRotateSsa(true); @@ -170,6 +171,8 @@ public void create_request_valid() { assertEquals(ssaAux.getAttributes().getOneTimeUse(), ssa.getAttributes().getOneTimeUse()); assertNotNull(ssaAux.getAttributes().getRotateSsa(), "ssa rotate_ssa is null"); assertEquals(ssaAux.getAttributes().getRotateSsa(), ssa.getAttributes().getRotateSsa()); + assertNotNull(ssaAux.getState(), "ssa state is null"); + assertEquals(ssaAux.getState(), SsaState.ACTIVE); } @Test 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 e6253646717..c6ab02f411b 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 @@ -57,7 +57,7 @@ public void get_withAllParam_valid() { boolean softwareRoles = false; String jti = "my-jti"; - String orgId = "1"; + Long orgId = 1000L; Response response = ssaGetAction.get(softwareRoles, jti, orgId, mock(HttpServletRequest.class)); assertNotNull(response, "response is null"); assertNotNull(response.getEntity(), "response entity is null"); @@ -81,7 +81,7 @@ public void get_invalidClientAndIsErrorEnabledFalse_badRequestResponse() { boolean softwareRoles = false; String jti = "my-jti"; - String orgId = "1"; + Long orgId = 1000L; assertThrows(WebApplicationException.class, () -> ssaGetAction.get(softwareRoles, jti, orgId, mock(HttpServletRequest.class))); verify(log).debug(anyString(), any(), any(), any()); verify(ssaRestWebServiceValidator).getClientFromSession(); @@ -102,7 +102,7 @@ public void get_invalidClientAndIsErrorEnabledTrue_badRequestResponse() { boolean softwareRoles = false; String jti = "my-jti"; - String orgId = "1"; + Long orgId = 1000L; assertThrows(WebApplicationException.class, () -> ssaGetAction.get(softwareRoles, jti, orgId, mock(HttpServletRequest.class))); verify(log).debug(anyString(), any(), any(), any()); verify(ssaRestWebServiceValidator).getClientFromSession(); @@ -122,7 +122,7 @@ public void get_invalidClientInternalServer_badRequestResponse() { boolean softwareRoles = false; String jti = "my-jti"; - String orgId = "1"; + Long orgId = 1000L; assertThrows(WebApplicationException.class, () -> ssaGetAction.get(softwareRoles, jti, orgId, mock(HttpServletRequest.class))); verify(log).debug(anyString(), any(), any(), any()); verify(ssaRestWebServiceValidator).getClientFromSession(); 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 new file mode 100644 index 00000000000..b676898bac2 --- /dev/null +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/ssa/ws/rs/action/SsaValidateActionTest.java @@ -0,0 +1,147 @@ +package io.jans.as.server.ssa.ws.rs.action; + +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.error.ErrorResponseFactory; +import io.jans.as.model.ssa.SsaErrorResponseType; +import io.jans.as.server.ssa.ws.rs.SsaService; +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.Calendar; +import java.util.TimeZone; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.testng.Assert.*; + +@Listeners(MockitoTestNGListener.class) +public class SsaValidateActionTest { + + @InjectMocks + private SsaValidateAction ssaValidateAction; + + @Mock + private Logger log; + + @Mock + private ErrorResponseFactory errorResponseFactory; + + @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); + } + + @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); + 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)); + + 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_ssaWithOneTimeUseTrue_validStatus() { + String jti = "e1440ecf-4b68-467c-a032-be1c43183e0c"; + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.add(Calendar.HOUR, 24); + 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); + + Response response = ssaValidateAction.validate(jti); + assertNotNull(response); + assertEquals(response.getStatus(), 200); + + verify(log).debug(anyString(), eq(jti)); + verify(errorResponseFactory).validateFeatureEnabled(any()); + verify(ssaService, never()).createUnprocessableEntityResponse(); + verify(log).info(anyString(), any(), any()); + verifyNoMoreInteractions(log, errorResponseFactory); + + ArgumentCaptor ssaCaptor = ArgumentCaptor.forClass(Ssa.class); + verify(ssaService).merge(ssaCaptor.capture()); + Ssa ssaAux = ssaCaptor.getValue(); + assertNotNull(ssaAux.getState(), "ssa state is null"); + assertEquals(ssaAux.getState(), SsaState.USED); + } + + @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); + Ssa ssa = new Ssa(); + ssa.setExpirationDate(calendar.getTime()); + ssa.setState(SsaState.ACTIVE); + ssa.setAttributes(null); + when(ssaService.findSsaByJti(jti)).thenReturn(ssa); + WebApplicationException error = new WebApplicationException( + Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Unknown error") + .build()); + when(errorResponseFactory.createWebApplicationException(any(Response.Status.class), any(SsaErrorResponseType.class), anyString())).thenThrow(error); + + assertThrows(WebApplicationException.class, () -> ssaValidateAction.validate(jti)); + verify(log).debug(anyString(), eq(jti)); + verify(errorResponseFactory).validateFeatureEnabled(any()); + verify(log).error(eq(null), any(NullPointerException.class)); + verifyNoMoreInteractions(log, ssaService, errorResponseFactory); + } +} \ No newline at end of file diff --git a/jans-linux-setup/jans_setup/schema/jans_schema.json b/jans-linux-setup/jans_setup/schema/jans_schema.json index 8c05c23f9d5..0ada2ef4cbf 100644 --- a/jans-linux-setup/jans_setup/schema/jans_schema.json +++ b/jans-linux-setup/jans_setup/schema/jans_schema.json @@ -3359,6 +3359,7 @@ "description", "exp", "del", + "jansState", "creatorId", "creatorTyp", "creationDate"