From 79d0353db3c653c8a8eef0ca2faba1dd42f7b92d Mon Sep 17 00:00:00 2001 From: rmiccoli Date: Wed, 13 Sep 2023 17:15:54 +0200 Subject: [PATCH 1/3] Add OpenID Connect standard claims in ATs for WLCG JWT profile. --- .../profile/wlcg/WLCGProfileAccessTokenBuilder.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WLCGProfileAccessTokenBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WLCGProfileAccessTokenBuilder.java index d096d0843..831746dc6 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WLCGProfileAccessTokenBuilder.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WLCGProfileAccessTokenBuilder.java @@ -79,6 +79,15 @@ public JWTClaimsSet buildAccessToken(OAuth2AccessTokenEntity token, builder.claim(ATTR_SCOPE, attributeHelper .getAttributeMapFromUserInfo(((UserInfoAdapter) userInfo).getUserinfo())); } + if (properties.getAccessToken().isIncludeAuthnInfo()) { + if (token.getScope().contains("email")) { + builder.claim("email", userInfo.getEmail()); + } + if (token.getScope().contains("profile")) { + builder.claim("preferred_username", userInfo.getPreferredUsername()); + builder.claim("name", userInfo.getName()); + } + } } if (!hasAudienceRequest(authentication) && !hasRefreshTokenAudienceRequest(authentication)) { From d7d60411e0d30a6ffaf0a281eca283ff6a952394 Mon Sep 17 00:00:00 2001 From: rmiccoli Date: Thu, 14 Sep 2023 10:54:22 +0200 Subject: [PATCH 2/3] Add tests --- .../profile/WLCGProfileIntegrationTests.java | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/WLCGProfileIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/WLCGProfileIntegrationTests.java index d01bd692b..88745f37d 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/WLCGProfileIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/WLCGProfileIntegrationTests.java @@ -78,6 +78,7 @@ @TestPropertySource(properties = { // @formatter:off "iam.jwt-profile.default-profile=wlcg", + "iam.access_token.include_authn_info=true", "scope.matchers[0].name=storage.read", "scope.matchers[0].type=path", "scope.matchers[0].prefix=storage.read", @@ -736,7 +737,7 @@ public void attributesAreNotIncludedInAccessTokenWhenNotRequested() throws Excep } @Test - public void attributesAreIncludedInAccessTokenWhenNotRequested() throws Exception { + public void attributesAreIncludedInAccessTokenWhenRequested() throws Exception { IamAccount testAccount = repo.findByUsername(TEST_USER).orElseThrow(assertionError(EXPECTED_USER_NOT_FOUND)); @@ -762,4 +763,55 @@ public void attributesAreIncludedInAccessTokenWhenNotRequested() throws Exceptio assertThat(claims.getJSONObjectClaim("attr").get("test"), is("test")); } + @Test + public void additionalClaimsAreIncludedInAccessTokenWhenRequested() throws Exception { + + String tokenResponseJson = mvc + .perform(post("/token").param("grant_type", "password") + .param("client_id", CLIENT_ID) + .param("client_secret", CLIENT_SECRET) + .param("username", "test") + .param("password", "password") + .param("scope", "openid profile email")) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + JWTClaimsSet claims = + JWTParser.parse(mapper.readTree(tokenResponseJson).get("access_token").asText()) + .getJWTClaimsSet(); + + assertThat(claims.getClaim("email"), notNullValue()); + assertThat(claims.getClaim("email"), is("test@iam.test")); + assertThat(claims.getClaim("name"), notNullValue()); + assertThat(claims.getClaim("name"), is("Test User")); + assertThat(claims.getClaim("preferred_username"), notNullValue()); + assertThat(claims.getClaim("preferred_username"), is("test")); + } + + @Test + public void additionalClaimsAreNotIncludedInAccessTokenWhenRNotequested() throws Exception { + + String tokenResponseJson = mvc + .perform(post("/token").param("grant_type", "password") + .param("client_id", CLIENT_ID) + .param("client_secret", CLIENT_SECRET) + .param("username", "test") + .param("password", "password") + .param("scope", "openid address")) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + JWTClaimsSet claims = + JWTParser.parse(mapper.readTree(tokenResponseJson).get("access_token").asText()) + .getJWTClaimsSet(); + + assertThat(claims.getClaim("email"), nullValue()); + assertThat(claims.getClaim("name"), nullValue()); + assertThat(claims.getClaim("preferred_username"), nullValue()); + } + } From 1a6171c158e561bbc8fe6d6675322aae1fe798a0 Mon Sep 17 00:00:00 2001 From: rmiccoli Date: Thu, 28 Sep 2023 18:21:07 +0200 Subject: [PATCH 3/3] Refactor method to reduce cognitive complexity --- .../wlcg/WLCGProfileAccessTokenBuilder.java | 62 +++++++++++++------ 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WLCGProfileAccessTokenBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WLCGProfileAccessTokenBuilder.java index 831746dc6..f1a064ea7 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WLCGProfileAccessTokenBuilder.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WLCGProfileAccessTokenBuilder.java @@ -61,40 +61,62 @@ public JWTClaimsSet buildAccessToken(OAuth2AccessTokenEntity token, builder.notBeforeTime(Date.from(issueTime)); - if (!token.getScope().isEmpty()) { - builder.claim(SCOPE_CLAIM_NAME, token.getScope().stream().collect(joining(SPACE))); - } - - builder.claim(WLCG_VER_CLAIM, PROFILE_VERSION); + addScopeClaim(builder, token); + addWlcgVerClaim(builder); if (!isNull(userInfo)) { Set groupNames = groupHelper.resolveGroupNames(token, ((UserInfoAdapter) userInfo).getUserinfo()); - - if (!groupNames.isEmpty()) { - builder.claim(WLCGGroupHelper.WLCG_GROUPS_SCOPE, groupNames); - } + addWlcgGroupsScopeClaim(builder, groupNames); if (token.getScope().contains(ATTR_SCOPE)) { - builder.claim(ATTR_SCOPE, attributeHelper - .getAttributeMapFromUserInfo(((UserInfoAdapter) userInfo).getUserinfo())); + addAttributeScopeClaim(builder, userInfo); } if (properties.getAccessToken().isIncludeAuthnInfo()) { - if (token.getScope().contains("email")) { - builder.claim("email", userInfo.getEmail()); - } - if (token.getScope().contains("profile")) { - builder.claim("preferred_username", userInfo.getPreferredUsername()); - builder.claim("name", userInfo.getName()); - } + addAuthnInfoClaims(builder, token.getScope(), userInfo); } } + addAudience(builder, authentication); + + return builder.build(); + } + + private void addScopeClaim(Builder builder, OAuth2AccessTokenEntity token) { + if (!token.getScope().isEmpty()) { + builder.claim(SCOPE_CLAIM_NAME, token.getScope().stream().collect(joining(SPACE))); + } + } + + private void addWlcgVerClaim(Builder builder) { + builder.claim(WLCG_VER_CLAIM, PROFILE_VERSION); + } + + private void addWlcgGroupsScopeClaim(Builder builder, Set groupNames) { + if (!groupNames.isEmpty()) { + builder.claim(WLCGGroupHelper.WLCG_GROUPS_SCOPE, groupNames); + } + } + + private void addAttributeScopeClaim(Builder builder, UserInfo userInfo) { + builder.claim(ATTR_SCOPE, + attributeHelper.getAttributeMapFromUserInfo(((UserInfoAdapter) userInfo).getUserinfo())); + } + + private void addAuthnInfoClaims(Builder builder, Set scopes, UserInfo userInfo) { + if (scopes.contains("email")) { + builder.claim("email", userInfo.getEmail()); + } + if (scopes.contains("profile")) { + builder.claim("preferred_username", userInfo.getPreferredUsername()); + builder.claim("name", userInfo.getName()); + } + } + + private void addAudience(Builder builder, OAuth2Authentication authentication) { if (!hasAudienceRequest(authentication) && !hasRefreshTokenAudienceRequest(authentication)) { builder.audience(ALL_AUDIENCES_VALUE); } - - return builder.build(); } }