From a4fa6cc37d72e57796616fd87716fef059770e76 Mon Sep 17 00:00:00 2001 From: Ronak Thacker Date: Tue, 23 Jul 2024 19:34:06 +0530 Subject: [PATCH] feat: test cases added, file header updated and detail log added for security events --- .../config/security/SecurityEvents.java | 6 +- .../controller/DidDocumentController.java | 2 +- .../controller/WalletController.java | 2 +- .../utils/TokenParsingUtils.java | 67 +++++ .../utils/TokenParsingUtilsTest.java | 228 ++++++++++++++++++ 5 files changed, 302 insertions(+), 3 deletions(-) create mode 100644 miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtilsTest.java diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityEvents.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityEvents.java index 841bd3fdf..90dc1f86e 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityEvents.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityEvents.java @@ -38,7 +38,11 @@ public void onFailure(AbstractAuthenticationFailureEvent failures) { @EventListener public void onFailure(AuthorizationDeniedEvent failure) { - log.warn("Failed Authorization: Missing 'Authorization' header."); + if (failure.getAuthorizationDecision() != null) { + log.warn("Failed Authorization: {}",failure.getAuthorizationDecision().toString()); + } else { + log.warn("Failed Authorization: Missing 'Authorization' header."); + } } } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/DidDocumentController.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/DidDocumentController.java index 298fdc102..7c3a8ad8a 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/DidDocumentController.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/DidDocumentController.java @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/WalletController.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/WalletController.java index d38e353e2..4b1ded210 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/WalletController.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/WalletController.java @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java index 88b6bddbf..7e6d3cad7 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java @@ -41,13 +41,31 @@ import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.ACCESS_TOKEN; import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.SCOPE; +/** + * The type Token parsing utils. + */ @UtilityClass public class TokenParsingUtils { + /** + * The constant PARSING_TOKEN_ERROR. + */ public static final String PARSING_TOKEN_ERROR = "Could not parse jwt token"; + /** + * The constant BEARER_ACCESS_SCOPE. + */ public static final String BEARER_ACCESS_SCOPE = "bearer_access_scope"; + /** + * The constant ACCESS_TOKEN_ERROR. + */ public static final String ACCESS_TOKEN_ERROR = "Access token not present"; + /** + * Gets claims set. + * + * @param tokenParsed the token parsed + * @return the claims set + */ public static JWTClaimsSet getClaimsSet(SignedJWT tokenParsed) { try { return tokenParsed.getJWTClaimsSet(); @@ -56,6 +74,12 @@ public static JWTClaimsSet getClaimsSet(SignedJWT tokenParsed) { } } + /** + * Parse token signed jwt. + * + * @param token the token + * @return the signed jwt + */ public static SignedJWT parseToken(String token) { try { return SignedJWT.parse(token); @@ -64,6 +88,13 @@ public static SignedJWT parseToken(String token) { } } + /** + * Gets string claim. + * + * @param claimsSet the claims set + * @param name the name + * @return the string claim + */ public static String getStringClaim(JWTClaimsSet claimsSet, String name) { try { return claimsSet.getStringClaim(name); @@ -72,6 +103,12 @@ public static String getStringClaim(JWTClaimsSet claimsSet, String name) { } } + /** + * Gets access token. + * + * @param claims the claims + * @return the access token + */ public static Optional getAccessToken(JWTClaimsSet claims) { try { String accessTokenValue = claims.getStringClaim(ACCESS_TOKEN); @@ -81,6 +118,12 @@ public static Optional getAccessToken(JWTClaimsSet claims) { } } + /** + * Gets access token. + * + * @param outerToken the outer token + * @return the access token + */ public static SignedJWT getAccessToken(String outerToken) { SignedJWT jwtOuter = parseToken(outerToken); JWTClaimsSet claimsSet = getClaimsSet(jwtOuter); @@ -88,6 +131,12 @@ public static SignedJWT getAccessToken(String outerToken) { return accessToken.map(TokenParsingUtils::parseToken).orElseThrow(() -> new BadDataException(ACCESS_TOKEN_ERROR)); } + /** + * Gets scope. + * + * @param jwtClaimsSet the jwt claims set + * @return the scope + */ public static String getScope(JWTClaimsSet jwtClaimsSet) { try { String scopes = jwtClaimsSet.getStringClaim(SCOPE); @@ -100,6 +149,12 @@ public static String getScope(JWTClaimsSet jwtClaimsSet) { } } + /** + * Gets jti access token. + * + * @param accessToken the access token + * @return the jti access token + */ public static String getJtiAccessToken(JWT accessToken) { try { return getStringClaim(accessToken.getJWTClaimsSet(), JTI); @@ -108,6 +163,12 @@ public static String getJtiAccessToken(JWT accessToken) { } } + /** + * Gets nonce access token. + * + * @param accessToken the access token + * @return the nonce access token + */ public static String getNonceAccessToken(JWT accessToken) { try { return accessToken.getJWTClaimsSet().getStringClaim(NONCE); @@ -116,6 +177,12 @@ public static String getNonceAccessToken(JWT accessToken) { } } + /** + * Gets bpn from token. + * + * @param authentication the authentication + * @return the bpn from token + */ public static String getBPNFromToken(Authentication authentication) { Jwt jwt = ((JwtAuthenticationToken) authentication).getToken(); // this will misbehave if we have more then one claims with different case diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtilsTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtilsTest.java new file mode 100644 index 000000000..858b26774 --- /dev/null +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtilsTest.java @@ -0,0 +1,228 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ****************************************************************************** + */ + +package org.eclipse.tractusx.managedidentitywallets.utils; + +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtClaimNames; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; + +import java.text.ParseException; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class TokenParsingUtilsTest { + + @Test + void parseTokenShouldReturnSignedJWTWhenTokenIsValid() throws ParseException { + String token = "valid.token.here"; + SignedJWT signedJWT = mock(SignedJWT.class); + + try (MockedStatic mockedSignedJWT = mockStatic(SignedJWT.class)) { + mockedSignedJWT.when(() -> SignedJWT.parse(token)).thenReturn(signedJWT); + + SignedJWT result = TokenParsingUtils.parseToken(token); + + assertEquals(signedJWT, result); + } + } + + @Test + void parseTokenShouldThrowBadDataExceptionWhenParseExceptionOccurs() throws ParseException { + String token = "invalid.token.here"; + + try (MockedStatic mockedSignedJWT = mockStatic(SignedJWT.class)) { + mockedSignedJWT.when(() -> SignedJWT.parse(token)).thenThrow(ParseException.class); + + BadDataException exception = assertThrows(BadDataException.class, () -> TokenParsingUtils.parseToken(token)); + + assertEquals(TokenParsingUtils.PARSING_TOKEN_ERROR, exception.getMessage()); + } + } + + @Test + void getAccessTokenShouldReturnInnerSignedJWTWhenAccessTokenIsPresent() throws ParseException { + String outerToken = "outer.token.here"; + SignedJWT outerSignedJWT = mock(SignedJWT.class); + JWTClaimsSet outerClaimsSet = new JWTClaimsSet.Builder().claim("access_token", "inner.token.here").build(); + SignedJWT innerSignedJWT = mock(SignedJWT.class); + + try (MockedStatic mockedSignedJWT = mockStatic(SignedJWT.class)) { + mockedSignedJWT.when(() -> SignedJWT.parse(outerToken)).thenReturn(outerSignedJWT); + mockedSignedJWT.when(() -> SignedJWT.parse("inner.token.here")).thenReturn(innerSignedJWT); + when(outerSignedJWT.getJWTClaimsSet()).thenReturn(outerClaimsSet); + + SignedJWT result = TokenParsingUtils.getAccessToken(outerToken); + + assertEquals(innerSignedJWT, result); + } + } + + @Test + void getAccessTokenShouldThrowBadDataExceptionWhenAccessTokenIsNotPresent() throws ParseException { + String outerToken = "outer.token.here"; + SignedJWT outerSignedJWT = mock(SignedJWT.class); + JWTClaimsSet outerClaimsSet = new JWTClaimsSet.Builder().build(); + + try (MockedStatic mockedSignedJWT = mockStatic(SignedJWT.class)) { + mockedSignedJWT.when(() -> SignedJWT.parse(outerToken)).thenReturn(outerSignedJWT); + when(outerSignedJWT.getJWTClaimsSet()).thenReturn(outerClaimsSet); + + BadDataException exception = assertThrows(BadDataException.class, () -> TokenParsingUtils.getAccessToken(outerToken)); + + assertEquals(TokenParsingUtils.ACCESS_TOKEN_ERROR, exception.getMessage()); + } + } + + @Test + void getBPNFromTokenShouldReturnBPNWhenBPNClaimIsPresent() { + Authentication authentication = mock(JwtAuthenticationToken.class); + Jwt jwt = mock(Jwt.class); + when(((JwtAuthenticationToken) authentication).getToken()).thenReturn(jwt); + Map claims = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + claims.put(StringPool.BPN, "123456"); + when(jwt.getClaims()).thenReturn(claims); + + String result = TokenParsingUtils.getBPNFromToken(authentication); + + assertEquals("123456", result); + } + + // Other test methods for TokenParsingUtils... + + @Test + void getStringClaimShouldReturnClaimValueWhenClaimIsPresent() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("claim")).thenReturn("value"); + + String result = TokenParsingUtils.getStringClaim(claimsSet, "claim"); + + assertEquals("value", result); + } + + @Test + void getStringClaimShouldThrowBadDataExceptionWhenParseExceptionOccurs() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("claim")).thenThrow(ParseException.class); + + BadDataException exception = assertThrows(BadDataException.class, () -> TokenParsingUtils.getStringClaim(claimsSet, "claim")); + + assertEquals(TokenParsingUtils.PARSING_TOKEN_ERROR, exception.getMessage()); + } + + @Test + void getAccessTokenShouldReturnAccessTokenWhenAccessTokenIsPresent() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("access_token")).thenReturn("accessToken"); + + Optional result = TokenParsingUtils.getAccessToken(claimsSet); + + assertTrue(result.isPresent()); + assertEquals("accessToken", result.get()); + } + + @Test + void getAccessTokenShouldReturnEmptyOptionalWhenAccessTokenIsNotPresent() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("access_token")).thenReturn(null); + + Optional result = TokenParsingUtils.getAccessToken(claimsSet); + + assertFalse(result.isPresent()); + } + + @Test + void getAccessTokenShouldThrowBadDataExceptionWhenParseExceptionOccurs() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("access_token")).thenThrow(ParseException.class); + + BadDataException exception = assertThrows(BadDataException.class, () -> TokenParsingUtils.getAccessToken(claimsSet)); + + assertEquals(TokenParsingUtils.PARSING_TOKEN_ERROR, exception.getMessage()); + } + + @Test + void getScopeShouldReturnScopeWhenScopeIsPresent() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("scope")).thenReturn("scope1 scope2"); + + String result = TokenParsingUtils.getScope(claimsSet); + + assertEquals("scope1 scope2", result); + } + + @Test + void getScopeShouldReturnBearerAccessScopeWhenScopeIsNotPresentButBearerAccessScopeIs() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("scope")).thenReturn(null); + when(claimsSet.getStringClaim(TokenParsingUtils.BEARER_ACCESS_SCOPE)).thenReturn("bearerAccessScope"); + + String result = TokenParsingUtils.getScope(claimsSet); + + assertEquals("bearerAccessScope", result); + } + + @Test + void getScopeShouldThrowBadDataExceptionWhenParseExceptionOccurs() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("scope")).thenThrow(ParseException.class); + + BadDataException exception = assertThrows(BadDataException.class, () -> TokenParsingUtils.getScope(claimsSet)); + + assertEquals("Token does not contain scope claim", exception.getMessage()); + } + + @Test + void getJtiAccessTokenShouldReturnJtiWhenClaimIsPresent() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim(JwtClaimNames.JTI)).thenReturn("jtiValue"); + SignedJWT signedJWT = mock(SignedJWT.class); + when(signedJWT.getJWTClaimsSet()).thenReturn(claimsSet); + + String result = TokenParsingUtils.getJtiAccessToken(signedJWT); + + assertEquals("jtiValue", result); + } + + @Test + void getJtiAccessTokenShouldThrowBadDataExceptionWhenParseExceptionOccurs() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim(JwtClaimNames.JTI)).thenThrow(ParseException.class); + SignedJWT signedJWT = mock(SignedJWT.class); + when(signedJWT.getJWTClaimsSet()).thenReturn(claimsSet); + + BadDataException exception = assertThrows(BadDataException.class, () -> TokenParsingUtils.getJtiAccessToken(signedJWT)); + + assertEquals(TokenParsingUtils.PARSING_TOKEN_ERROR, exception.getMessage()); + } + +}