diff --git a/docs/admin/auth-server/endpoints/ssa.md b/docs/admin/auth-server/endpoints/ssa.md index 15ad35a20b0..a3f8fca13ec 100644 --- a/docs/admin/auth-server/endpoints/ssa.md +++ b/docs/admin/auth-server/endpoints/ssa.md @@ -141,6 +141,7 @@ Returned SSA is a JWT, containing the following structure: - `software_roles` — List of string values, fixed value `["password", "notify"]`. - `grant_types` — Fixed value `["client_credentials"]`. - `exp` — Expiration Time. +- `myCustom1, myCustom2, ...`: if you have custom attributes, they will be displayed here. ### Example: @@ -214,6 +215,9 @@ Get existing active SSA based on `jti` or `org_id`. "iss": "https://jans.localhost", "exp": 1668608852, "iat": 1668608851, + "description: "test description", + "one_time_use": true, + "rotate_ssa": false }, "iss": "ed4d5f74-ce41-4180-aed4-54cffa974630", "created_at": 1668608851, @@ -232,6 +236,10 @@ Get existing active SSA based on `jti` or `org_id`. - `iss` — The "iss" (issuer) claim identifies the principal that issued the JWT. - `exp` — Expiration time. - `iat` — Creation time. + - `description` — Describe SSA. + - `one_time_use` — Defined whether the SSA will be used only once or can be used multiple times. + - `rotate_ssa` — TODO - Will be used to rotate expiration of the SSA, currently is only saved as part of the SSA. + - `myCustom1, myCustom2, ...` — if you have custom attributes, they will be displayed here. - `iss` — The "iss" is related to the client that created this SSA. - `created_at` — Creation time. - `expiration` — Expiration time. @@ -287,7 +295,10 @@ Connection: Keep-Alive ], "exp": 1668608852, "iat": 1668608851, - "jti": "c3eb1c16-be9b-4e96-974e-aea5e3cf95b0" + "jti": "c3eb1c16-be9b-4e96-974e-aea5e3cf95b0", + "description: "test description", + "one_time_use": true, + "rotate_ssa": false }, "iss": "ed4d5f74-ce41-4180-aed4-54cffa974630", "created_at": 1668608851, @@ -297,6 +308,73 @@ Connection: Keep-Alive ] ``` +## Get JWT SSA + +Get existing active SSA based on `jti`. + +### Query Parameters + +- `jti` — Unique identifier + +### Response description + +Returned SSA is a JWT, containing the following structure: + +``` +{ + "ssa": "eyJraWQiOiI1NTk3MGFkZS00M2MwLTQ4YWMtODEyZi0yZTY1MzhjMTEyN2Zfc2lnX3JzNTEyIiwidHlwIjoiand0IiwiYWxnIjoiUlM1MTIifQ.eyJzb2Z0d2FyZV9pZCI6ImdsdXUtc2Nhbi1hcGkiLCJncmFudF90eXBlcyI6WyJjbGllbnRfY3JlZGVudGlhbHMiXSwib3JnX2lkIjoxLCJpc3MiOiJodHRwczovL2phbnMubG9jYWxob3N0Iiwic29mdHdhcmVfcm9sZXMiOlsicGFzc3d1cmQiXSwiZXhwIjoxNjY4NjA5MDA1LCJpYXQiOjE2Njg2NDE5NjcsImp0aSI6ImU4OWVjYTQxLTM0ODUtNDUxNi1hMTYyLWZiODYyNjJhYmFjMyJ9.jRgh8_aiwMTJxeT9cwfup9QP9LBc6gQstvabCzUOJvELnzosxiNJHeU2mrvavaNK6BGvs_lbNjODVDeetGCD48_F2ay9r8qmo-f3GPzdzcJozKgfzonSkAE5Ran9LKcQQJpVc1rDYcV2xYiJLJ6FSuvnoClkDEE1tXysxshLPs-GXOZE7rD8XUXzezuxZWUE1jXwA-EFajoat8CP6QulHGxlcn_sKIhawhGODxJPz4Pf3jgeZVLG_7HfRJgxNiKcdzQIxnkbdpuS-0Q4-oc5yntsXhFhn31Pa3vGsiPPH9f3ndL2ZZKk3xCgyImLDJuGaxXg-qEVoIG4zNWNHMUNUQ" +} +``` + +**Header** + +- `alg` — The signature algorithm which used `RS256`. +- `typ` — The type which used. +- `kid` — The key identification `gluu-scan-api-rs256-ssa-signature-key`. + +**Payload** + +- `iat` — The time the JWT was created. +- `iss` — The "iss" (issuer) claim identifies the principal that issued the JWT. +- `jti` — The "jti" (JWT ID) claim provides a unique identifier for the JWT. +- `software_id` — The "software_id" is used for software identification. +- `org_id` — The "org_id" is used for organization identification. +- `software_roles` — List of string values, fixed value `["password", "notify"]`. +- `grant_types` — Fixed value `["client_credentials"]`. +- `exp` — Expiration Time. +- `myCustom1, myCustom2, ...`: if you have custom attributes, they will be displayed here. + +### Example: + +**Request:** + +``` +GET {{your-url}}/ssa/jwt?jti={{your-jti}} +Content-Type: application/json +Authorization: Bearer {{your-token}} +``` + +**Response:** + +``` +HTTP/1.1 201 Created +Date: Wed, 16 Nov 2022 23:39:27 GMT +Server: Apache/2.4.52 (Ubuntu) +X-Xss-Protection: 1; mode=block +X-Content-Type-Options: nosniff +Strict-Transport-Security: max-age=31536000; includeSubDomains +Cache-Control: no-store +Content-Type: application/json +Pragma: no-cache +Content-Length: 757 +Keep-Alive: timeout=5, max=100 +Connection: Keep-Alive + +{ + "ssa": "eyJraWQiOiI1NTk3MGFkZS00M2MwLTQ4YWMtODEyZi0yZTY1MzhjMTEyN2Zfc2lnX3JzNTEyIiwidHlwIjoiand0IiwiYWxnIjoiUlM1MTIifQ.eyJzb2Z0d2FyZV9pZCI6ImdsdXUtc2Nhbi1hcGkiLCJncmFudF90eXBlcyI6WyJjbGllbnRfY3JlZGVudGlhbHMiXSwib3JnX2lkIjoxLCJpc3MiOiJodHRwczovL2phbnMubG9jYWxob3N0Iiwic29mdHdhcmVfcm9sZXMiOlsicGFzc3d1cmQiXSwiZXhwIjoxNjY4NjA5MDA1LCJpYXQiOjE2Njg2NDE5NjcsImp0aSI6ImU4OWVjYTQxLTM0ODUtNDUxNi1hMTYyLWZiODYyNjJhYmFjMyJ9.jRgh8_aiwMTJxeT9cwfup9QP9LBc6gQstvabCzUOJvELnzosxiNJHeU2mrvavaNK6BGvs_lbNjODVDeetGCD48_F2ay9r8qmo-f3GPzdzcJozKgfzonSkAE5Ran9LKcQQJpVc1rDYcV2xYiJLJ6FSuvnoClkDEE1tXysxshLPs-GXOZE7rD8XUXzezuxZWUE1jXwA-EFajoat8CP6QulHGxlcn_sKIhawhGODxJPz4Pf3jgeZVLG_7HfRJgxNiKcdzQIxnkbdpuS-0Q4-oc5yntsXhFhn31Pa3vGsiPPH9f3ndL2ZZKk3xCgyImLDJuGaxXg-qEVoIG4zNWNHMUNUQ" +} +``` + ## Validate SSA Validate existing active SSA based on `jti` diff --git a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/create/SsaCreateRequest.java b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/create/SsaCreateRequest.java index 34d9d8327c8..4578a6ceb50 100644 --- a/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/create/SsaCreateRequest.java +++ b/jans-auth-server/client/src/main/java/io/jans/as/client/ssa/create/SsaCreateRequest.java @@ -176,7 +176,7 @@ public JSONObject getJSONParameters() throws JSONException { @Override public String toString() { return "SsaRequest{" + - "orgId=" + orgId + + "orgId='" + orgId + '\'' + ", expiration=" + expiration + ", description='" + description + '\'' + ", softwareId='" + softwareId + '\'' + diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaJsonService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaJsonService.java index 6ddd19fb07a..fcb1afbcb3b 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaJsonService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/ssa/ws/rs/SsaJsonService.java @@ -89,6 +89,12 @@ public JSONArray getJSONArray(List ssaList) throws JSONException { Util.addToJSONObjectIfNotNull(jsonSsa, IAT.getName(), DateUtil.dateToUnixEpoch(ssa.getCreationDate())); Util.addToJSONObjectIfNotNull(jsonSsa, EXP.getName(), DateUtil.dateToUnixEpoch(ssa.getExpirationDate())); Util.addToJSONObjectIfNotNull(jsonSsa, JTI.getName(), ssa.getId()); + Util.addToJSONObjectIfNotNull(jsonSsa, DESCRIPTION.getName(), ssa.getDescription()); + Util.addToJSONObjectIfNotNull(jsonSsa, ONE_TIME_USE.getName(), ssa.getAttributes().getOneTimeUse()); + Util.addToJSONObjectIfNotNull(jsonSsa, ROTATE_SSA.getName(), ssa.getAttributes().getRotateSsa()); + if (!ssa.getAttributes().getCustomAttributes().isEmpty()) { + ssa.getAttributes().getCustomAttributes().forEach((key, value) -> Util.addToJSONObjectIfNotNull(jsonSsa, key, value)); + } Util.addToJSONObjectIfNotNull(responseJsonObject, SSA.getName(), jsonSsa); jsonArray.put(responseJsonObject); 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 b760bb60ad1..8114964715b 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 @@ -74,8 +74,9 @@ public Client getClientFromSession() throws WebApplicationException { */ public void checkScopesPolicy(Client client, String scope) throws WebApplicationException { List scopes = scopeService.getScopeIdsByDns(Arrays.stream(client.getScopes()).collect(Collectors.toList())); - if (!scopes.contains(scope)) + if (!scopes.contains(scope)) { throw errorResponseFactory.createWebApplicationException(Response.Status.UNAUTHORIZED, SsaErrorResponseType.UNAUTHORIZED_CLIENT, "Unauthorized client"); + } } /** 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 92e235fa548..4bafeca04ce 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 @@ -170,7 +170,7 @@ public Jwt generateJwt(Ssa ssa, ExecutionContext executionContext) throws Except public Jwt generateJwt(Ssa ssa) throws Exception { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.fromString(appConfiguration.getSsaConfiguration().getSsaSigningAlg()); if (signatureAlgorithm == null) { - log.error("Invalid signature, Key is not found: {}", appConfiguration.getSsaConfiguration().getSsaSigningAlg()); + log.error("Invalid signature algorithm, not found: {}", appConfiguration.getSsaConfiguration().getSsaSigningAlg()); throw errorResponseFactory.createWebApplicationException(Response.Status.BAD_REQUEST, SsaErrorResponseType.INVALID_SIGNATURE, "Invalid signature error"); } String keyId = cryptoProvider.getKeyId(webKeysConfiguration, signatureAlgorithm.getAlg(), Use.SIGNATURE, KeyOpsType.SSA); @@ -188,6 +188,9 @@ public Jwt generateJwt(Ssa ssa) throws Exception { jwt.getClaims().setClaim(ORG_ID.getName(), ssa.getOrgId()); jwt.getClaims().setClaim(SOFTWARE_ROLES.getName(), ssa.getAttributes().getSoftwareRoles()); jwt.getClaims().setClaim(GRANT_TYPES.getName(), ssa.getAttributes().getGrantTypes()); + if (!ssa.getAttributes().getCustomAttributes().isEmpty()) { + ssa.getAttributes().getCustomAttributes().forEach((key, value) -> jwt.getClaims().setClaim(key, value)); + } String signature = cryptoProvider.sign(jwt.getSigningInput(), jwt.getHeader().getKeyId(), null, signatureAlgorithm); jwt.setEncodedSignature(signature); 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 8c91fb78ed9..aa9709d0d13 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 @@ -288,6 +288,20 @@ public void generateJwt_signatureAlgorithmNull_invalidSignature() { verifyNoInteractions(cryptoProvider, webKeysConfiguration); } + @Test + public void generateJwt_customAttributesEmpty_jwtValid() throws Exception { + SsaConfiguration ssaConfiguration = new SsaConfiguration(); + String issuer = "https://test.jans.io"; + ssa.getAttributes().setCustomAttributes(Collections.singletonMap("test-key", "test-value")); + when(appConfiguration.getSsaConfiguration()).thenReturn(ssaConfiguration); + when(appConfiguration.getIssuer()).thenReturn(issuer); + + Jwt jwt = ssaService.generateJwt(ssa); + assertSsaJwt(ssaConfiguration.getSsaSigningAlg(), issuer, ssa, jwt); + verify(cryptoProvider).sign(any(), any(), eq(null), any()); + verifyNoInteractions(log); + } + @Test public void createNotAcceptableResponse_valid_response() { Response response = ssaService.createNotAcceptableResponse().build(); @@ -329,6 +343,11 @@ private static void assertSsaJwt(String ssaSigningAlg, String issuer, Ssa ssa, J assertEquals(jwtClaims.getClaim(IAT.getName()), ssa.getCreationDate()); assertNotNull(jwtClaims.getClaim(EXP.getName()), "The exp in jwt is null"); assertEquals(jwtClaims.getClaim(EXP.getName()), ssa.getExpirationDate()); + + ssa.getAttributes().getCustomAttributes().forEach((key, value) -> { + assertTrue(jwtClaims.hasClaim(key)); + assertEquals(jwtClaims.getClaimAsString(key), value); + }); } private static void assertSsaWithAux(Ssa ssa, Ssa ssaAux) {