From 722b6bfdae4f5e06aeea68c00647e498af7c7f41 Mon Sep 17 00:00:00 2001 From: madsleegiil Date: Wed, 16 Feb 2022 08:24:41 +0100 Subject: [PATCH 1/4] =?UTF-8?q?St=C3=B8tt=20bruk=20av=20asterisk=20som=20c?= =?UTF-8?q?laims-verdi=20i=20ProtectedWithClaims?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../support/core/jwt/JwtTokenClaims.java | 4 ++-- .../support/core/jwt/JwtTokenClaimsTest.java | 21 ++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/token-validation-core/src/main/java/no/nav/security/token/support/core/jwt/JwtTokenClaims.java b/token-validation-core/src/main/java/no/nav/security/token/support/core/jwt/JwtTokenClaims.java index 4c234b58..d6669755 100644 --- a/token-validation-core/src/main/java/no/nav/security/token/support/core/jwt/JwtTokenClaims.java +++ b/token-validation-core/src/main/java/no/nav/security/token/support/core/jwt/JwtTokenClaims.java @@ -55,11 +55,11 @@ public boolean containsClaim(String name, String value) { } if (claim instanceof String) { String claimAsString = (String) claim; - return claimAsString.equals(value); + return claimAsString.equals(value) || value.equals("*"); } if (claim instanceof Collection) { Collection claimasList = (Collection) claim; - return claimasList.contains(value); + return claimasList.contains(value) || (value.equals("*") && !claimasList.isEmpty()); } return false; } diff --git a/token-validation-core/src/test/java/no/nav/security/token/support/core/jwt/JwtTokenClaimsTest.java b/token-validation-core/src/test/java/no/nav/security/token/support/core/jwt/JwtTokenClaimsTest.java index cab846ec..cb262813 100644 --- a/token-validation-core/src/test/java/no/nav/security/token/support/core/jwt/JwtTokenClaimsTest.java +++ b/token-validation-core/src/test/java/no/nav/security/token/support/core/jwt/JwtTokenClaimsTest.java @@ -14,13 +14,32 @@ class JwtTokenClaimsTest { @Test void containsClaimShouldHandleBothStringAndListClaim() { assertThat( - withClaim("arrayClaim", List.of("1","2")).containsClaim("arrayClaim", "1") + withClaim("arrayClaim", List.of("1", "2")).containsClaim("arrayClaim", "1") ).isTrue(); assertThat( withClaim("stringClaim", "1").containsClaim("stringClaim", "1") ).isTrue(); } + @Test + void containsClaimShouldHandleAsterisk() { + assertThat( + withClaim("stringClaim", "1").containsClaim("stringClaim", "*") + ).isTrue(); + assertThat( + withClaim("emptyStringClaim", "").containsClaim("emptyStringClaim", "*") + ).isTrue(); + assertThat( + withClaim("nullStringClaim", null).containsClaim("nullStringClaim", "*") + ).isFalse(); + assertThat( + withClaim("arrayClaim", List.of("1", "2")).containsClaim("arrayClaim", "*") + ).isTrue(); + assertThat( + withClaim("emptyArrayClaim", List.of()).containsClaim("emptyArrayClaim", "*") + ).isFalse(); + } + private JwtTokenClaims withClaim(String name, Object value) { var claims = new JWTClaimsSet.Builder().claim(name, value).build(); //do json parsing to simulate usage when creating from token From da9dc039484cd5e1540b2dd0b01cc2e1532060df Mon Sep 17 00:00:00 2001 From: madsleegiil Date: Wed, 16 Feb 2022 08:33:39 +0100 Subject: [PATCH 2/4] Tom liste skal tolkes som at claim er tilstede --- .../token/support/core/api/ProtectedWithClaims.java | 5 +++-- .../security/token/support/core/jwt/JwtTokenClaims.java | 7 +++++-- .../token/support/core/jwt/JwtTokenClaimsTest.java | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/token-validation-core/src/main/java/no/nav/security/token/support/core/api/ProtectedWithClaims.java b/token-validation-core/src/main/java/no/nav/security/token/support/core/api/ProtectedWithClaims.java index cf66b11f..71ccf985 100644 --- a/token-validation-core/src/main/java/no/nav/security/token/support/core/api/ProtectedWithClaims.java +++ b/token-validation-core/src/main/java/no/nav/security/token/support/core/api/ProtectedWithClaims.java @@ -11,10 +11,11 @@ @Target({ TYPE, METHOD }) @Protected public @interface ProtectedWithClaims { - + String issuer(); /** - * Required claims in token in key=value format + * Required claims in token in key=value format. + * If the value is an asterisk (*), it checks that the required key is present. * @return array containing claims as key=value */ String[] claimMap() default {}; diff --git a/token-validation-core/src/main/java/no/nav/security/token/support/core/jwt/JwtTokenClaims.java b/token-validation-core/src/main/java/no/nav/security/token/support/core/jwt/JwtTokenClaims.java index d6669755..5134de7f 100644 --- a/token-validation-core/src/main/java/no/nav/security/token/support/core/jwt/JwtTokenClaims.java +++ b/token-validation-core/src/main/java/no/nav/security/token/support/core/jwt/JwtTokenClaims.java @@ -53,13 +53,16 @@ public boolean containsClaim(String name, String value) { if (claim == null) { return false; } + if (value.equals("*")) { + return true; + } if (claim instanceof String) { String claimAsString = (String) claim; - return claimAsString.equals(value) || value.equals("*"); + return claimAsString.equals(value); } if (claim instanceof Collection) { Collection claimasList = (Collection) claim; - return claimasList.contains(value) || (value.equals("*") && !claimasList.isEmpty()); + return claimasList.contains(value); } return false; } diff --git a/token-validation-core/src/test/java/no/nav/security/token/support/core/jwt/JwtTokenClaimsTest.java b/token-validation-core/src/test/java/no/nav/security/token/support/core/jwt/JwtTokenClaimsTest.java index cb262813..e6299154 100644 --- a/token-validation-core/src/test/java/no/nav/security/token/support/core/jwt/JwtTokenClaimsTest.java +++ b/token-validation-core/src/test/java/no/nav/security/token/support/core/jwt/JwtTokenClaimsTest.java @@ -37,7 +37,7 @@ void containsClaimShouldHandleAsterisk() { ).isTrue(); assertThat( withClaim("emptyArrayClaim", List.of()).containsClaim("emptyArrayClaim", "*") - ).isFalse(); + ).isTrue(); } private JwtTokenClaims withClaim(String name, Object value) { From aa14ac430593d436601c503c9a7c01e94baeb003 Mon Sep 17 00:00:00 2001 From: madsleegiil Date: Wed, 16 Feb 2022 12:20:19 +0100 Subject: [PATCH 3/4] =?UTF-8?q?Oppdater=20README=20til=20=C3=A5=20reflekte?= =?UTF-8?q?re=20hvordan=20annotasjoner=20fungerer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4be50dcc..1958db2c 100644 --- a/README.md +++ b/README.md @@ -158,9 +158,9 @@ new ResourceConfig() This example shows - First method - An unprotected endpoint. No token is required to use this endpoint. -- Second method - A protected endpoint. This endpoint will require a valid token from the "employee" issuer. -- Third method - A protected endpoint. This endpoint will require a valid token from one of the configured issuers. -- Fourth method - A non-annotated endpoint. This endpoint will not be accessible from outside the server (will return a 501 NOT_IMPLEMENTED). +- Second method - A protected endpoint. This endpoint will require a valid token from one of the configured issuers. +- Third method - A protected endpoint. This endpoint will require a valid token from the "employee" or "manager" issuer. +- Fourth method - A protected endpoint. This endpoint will require a valid token from the "manager" issuer and a claim where key is "acr" and value is "Level4" ```java @Path("/rest") @@ -181,6 +181,16 @@ public class ProductResource { public Product add(Product product) { return service.create(product); } + + @PUT + @PATH("/product") + @RequiredIssuers(value = { + ProtectedWithClaims(issuer = "employee"), + ProtectedWithClaims(issuer = "manager") + }) + public Product add(Product product) { + return service.update(product); + } @DELETE @PATH("/product/{id}") @@ -188,9 +198,12 @@ public class ProductResource { public void add(String id) { return service.delete(id); } - } ``` + +The claimMap in **`@ProtectedWithClaims`** can contain entries where the expected value is an asterisk, e.g.: **`"acr=*"`**. This will require that the claim is present in the token, without regards to its value. + + ### token-validation-ktor See demo application in **`token-validation-ktor-demo`** for example configurations and setups. From be16418adb40d87d9c2a971196be3c4dc7ef4661 Mon Sep 17 00:00:00 2001 From: madsleegiil Date: Wed, 16 Feb 2022 12:36:37 +0100 Subject: [PATCH 4/4] =?UTF-8?q?Legg=20tilbake=20eksempel=20p=C3=A5=20endep?= =?UTF-8?q?unkt=20uten=20annotasjon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1958db2c..aae66eb6 100644 --- a/README.md +++ b/README.md @@ -160,8 +160,8 @@ This example shows - First method - An unprotected endpoint. No token is required to use this endpoint. - Second method - A protected endpoint. This endpoint will require a valid token from one of the configured issuers. - Third method - A protected endpoint. This endpoint will require a valid token from the "employee" or "manager" issuer. -- Fourth method - A protected endpoint. This endpoint will require a valid token from the "manager" issuer and a claim where key is "acr" and value is "Level4" - +- Fourth method - A protected endpoint. This endpoint will require a valid token from the "manager" issuer and a claim where key is "acr" and value is "Level4". +- Fifth method - A non-annotated endpoint. This endpoint will not be accessible from outside the server (will return a 501 NOT_IMPLEMENTED). ```java @Path("/rest") public class ProductResource { @@ -198,6 +198,12 @@ public class ProductResource { public void add(String id) { return service.delete(id); } + + @GET + @PATH("/product/{id}") + public void add(String id) { + return service.get(id); + } } ```