diff --git a/openapi/p4pa-auth.openapi.yaml b/openapi/p4pa-auth.openapi.yaml index b3d52bb3..728f46aa 100644 --- a/openapi/p4pa-auth.openapi.yaml +++ b/openapi/p4pa-auth.openapi.yaml @@ -163,7 +163,28 @@ components: issuer: type: string organization: + $ref: components/schemas/UserOrganizationRoles + UserOrganizationRoles: + type: object + required: + - id + - name + - fiscalCode + - ipaCode + - roles + properties: + id: type: string + name: + type: string + fiscalCode: + type: string + ipaCode: + type: string + roles: + type: array + items: + type: string AuthErrorDTO: type: object required: diff --git a/src/main/java/it/gov/pagopa/payhub/auth/exception/custom/InvalidTokenException.java b/src/main/java/it/gov/pagopa/payhub/auth/exception/custom/InvalidTokenException.java index 192b14ca..20b939de 100644 --- a/src/main/java/it/gov/pagopa/payhub/auth/exception/custom/InvalidTokenException.java +++ b/src/main/java/it/gov/pagopa/payhub/auth/exception/custom/InvalidTokenException.java @@ -8,4 +8,7 @@ public class InvalidTokenException extends RuntimeException { public InvalidTokenException(String message) { super(message); } + public InvalidTokenException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/src/main/java/it/gov/pagopa/payhub/auth/service/TokenStoreService.java b/src/main/java/it/gov/pagopa/payhub/auth/service/TokenStoreService.java index a622c829..95ba3f70 100644 --- a/src/main/java/it/gov/pagopa/payhub/auth/service/TokenStoreService.java +++ b/src/main/java/it/gov/pagopa/payhub/auth/service/TokenStoreService.java @@ -1,9 +1,9 @@ package it.gov.pagopa.payhub.auth.service; -import java.util.Map; +import it.gov.pagopa.payhub.model.generated.UserInfo; public interface TokenStoreService { - Map save(String accessToken, Map idTokenClaims); - Map load(String accessToken); + UserInfo save(String accessToken, UserInfo userInfo); + UserInfo load(String accessToken); void delete(String accessToken); } diff --git a/src/main/java/it/gov/pagopa/payhub/auth/service/TokenStoreServiceImpl.java b/src/main/java/it/gov/pagopa/payhub/auth/service/TokenStoreServiceImpl.java index 9592f8ee..10beeb2e 100644 --- a/src/main/java/it/gov/pagopa/payhub/auth/service/TokenStoreServiceImpl.java +++ b/src/main/java/it/gov/pagopa/payhub/auth/service/TokenStoreServiceImpl.java @@ -1,26 +1,25 @@ package it.gov.pagopa.payhub.auth.service; import it.gov.pagopa.payhub.auth.configuration.RedisConfig; +import it.gov.pagopa.payhub.model.generated.UserInfo; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; -import java.util.Map; - @Service @CacheConfig(cacheNames = RedisConfig.CACHE_NAME_ACCESS_TOKEN) class TokenStoreServiceImpl implements TokenStoreService{ @Override @CachePut - public Map save(String accessToken, Map idTokenClaims) { + public UserInfo save(String accessToken, UserInfo idTokenClaims) { return idTokenClaims; } @Override @Cacheable - public Map load(String accessToken) { + public UserInfo load(String accessToken) { return null; } diff --git a/src/main/java/it/gov/pagopa/payhub/auth/service/exchange/ExchangeTokenServiceImpl.java b/src/main/java/it/gov/pagopa/payhub/auth/service/exchange/ExchangeTokenServiceImpl.java index 464054c4..c9d4c18d 100644 --- a/src/main/java/it/gov/pagopa/payhub/auth/service/exchange/ExchangeTokenServiceImpl.java +++ b/src/main/java/it/gov/pagopa/payhub/auth/service/exchange/ExchangeTokenServiceImpl.java @@ -1,5 +1,6 @@ package it.gov.pagopa.payhub.auth.service.exchange; +import com.auth0.jwt.interfaces.Claim; import it.gov.pagopa.payhub.auth.service.TokenStoreService; import it.gov.pagopa.payhub.model.generated.AccessToken; import lombok.extern.slf4j.Slf4j; @@ -14,20 +15,22 @@ public class ExchangeTokenServiceImpl implements ExchangeTokenService{ private final ValidateExternalTokenService validateExternalTokenService; private final AccessTokenBuilderService accessTokenBuilderService; private final TokenStoreService tokenStoreService; + private final IDTokenClaims2UserInfoMapper idTokenClaimsMapper; - public ExchangeTokenServiceImpl(ValidateExternalTokenService validateExternalTokenService, AccessTokenBuilderService accessTokenBuilderService, TokenStoreService tokenStoreService) { + public ExchangeTokenServiceImpl(ValidateExternalTokenService validateExternalTokenService, AccessTokenBuilderService accessTokenBuilderService, TokenStoreService tokenStoreService, IDTokenClaims2UserInfoMapper idTokenClaimsMapper) { this.validateExternalTokenService = validateExternalTokenService; this.accessTokenBuilderService = accessTokenBuilderService; this.tokenStoreService = tokenStoreService; + this.idTokenClaimsMapper = idTokenClaimsMapper; } @Override public AccessToken postToken(String clientId, String grantType, String subjectToken, String subjectIssuer, String subjectTokenType, String scope) { log.info("Client {} requested to exchange a {} token provided by {} asking for grant type {} and scope {}", clientId, subjectTokenType, subjectIssuer, grantType, scope); - Map claims = validateExternalTokenService.validate(clientId, grantType, subjectToken, subjectIssuer, subjectTokenType, scope); + Map claims = validateExternalTokenService.validate(clientId, grantType, subjectToken, subjectIssuer, subjectTokenType, scope); AccessToken accessToken = accessTokenBuilderService.build(); - tokenStoreService.save(accessToken.getAccessToken(), claims); + tokenStoreService.save(accessToken.getAccessToken(), idTokenClaimsMapper.apply(claims)); return accessToken; } } diff --git a/src/main/java/it/gov/pagopa/payhub/auth/service/exchange/IDTokenClaims2UserInfoMapper.java b/src/main/java/it/gov/pagopa/payhub/auth/service/exchange/IDTokenClaims2UserInfoMapper.java new file mode 100644 index 00000000..3bd6df0b --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/auth/service/exchange/IDTokenClaims2UserInfoMapper.java @@ -0,0 +1,61 @@ +package it.gov.pagopa.payhub.auth.service.exchange; + +import com.auth0.jwt.interfaces.Claim; +import io.jsonwebtoken.Claims; +import it.gov.pagopa.payhub.auth.exception.custom.InvalidTokenException; +import it.gov.pagopa.payhub.model.generated.UserInfo; +import it.gov.pagopa.payhub.model.generated.UserOrganizationRoles; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +@Service +public class IDTokenClaims2UserInfoMapper implements Function, UserInfo> { + @Override + public UserInfo apply(Map claims) { + try { + return UserInfo.builder() + .issuer(claims.get(Claims.ISSUER).asString()) + .userId(claims.get("uid").asString()) + .name(claims.get("name").asString()) + .familyName(claims.get("family_name").asString()) + .fiscalCode(claims.get("fiscal_number").asString()) + .organization(buildUserOrganizationRoles(claims)) + .build(); + } catch (Exception e){ + throw new InvalidTokenException("Unexpected IDToken structure", e); + } + } + + private UserOrganizationRoles buildUserOrganizationRoles(Map claims) { + Map organizationClaim = claims.get("organization").asMap(); + + List roles = readUserOrganizationRoles(organizationClaim); + return UserOrganizationRoles.builder() + .id((String)organizationClaim.get("id")) + .name((String)organizationClaim.get("name")) + .fiscalCode((String)organizationClaim.get("fiscal_code")) + .ipaCode((String)organizationClaim.get("ipaCode")) + .roles(roles) + .build(); + } + + private static List readUserOrganizationRoles(Map organizationClaim) { + List out; + if(organizationClaim.get("roles") instanceof List roles){ + out = roles.stream().map(o -> (o instanceof Map orgMap) ? (String) orgMap.get("role") : null) + .filter(Objects::nonNull) + .toList(); + } else { + out = List.of(); + } + if(out.isEmpty()){ + throw new InvalidTokenException("No organization roles provided"); + } else { + return out; + } + } +} diff --git a/src/main/java/it/gov/pagopa/payhub/auth/service/exchange/ValidateExternalTokenService.java b/src/main/java/it/gov/pagopa/payhub/auth/service/exchange/ValidateExternalTokenService.java index 06321402..718ff10f 100644 --- a/src/main/java/it/gov/pagopa/payhub/auth/service/exchange/ValidateExternalTokenService.java +++ b/src/main/java/it/gov/pagopa/payhub/auth/service/exchange/ValidateExternalTokenService.java @@ -1,5 +1,6 @@ package it.gov.pagopa.payhub.auth.service.exchange; +import com.auth0.jwt.interfaces.Claim; import io.jsonwebtoken.Claims; import it.gov.pagopa.payhub.auth.exception.custom.*; import it.gov.pagopa.payhub.auth.utils.JWTValidator; @@ -11,7 +12,7 @@ @Service @Slf4j -class ValidateExternalTokenService { +public class ValidateExternalTokenService { public static final String ALLOWED_CLIENT_ID = "piattaforma-unitaria"; public static final String ALLOWED_GRANT_TYPE="urn:ietf:params:oauth:grant-type:token-exchange"; public static final String ALLOWED_SUBJECT_TOKEN_TYPE="urn:ietf:params:oauth:token-type:jwt"; @@ -33,11 +34,11 @@ public ValidateExternalTokenService(@Value("${jwt.audience:}")String allowedAudi this.jwtValidator = jwtValidator; } - public Map validate(String clientId, String grantType, String subjectToken, String subjectIssuer, String subjectTokenType, String scope) { + public Map validate(String clientId, String grantType, String subjectToken, String subjectIssuer, String subjectTokenType, String scope) { validateClient(clientId); validateProtocolConfiguration(grantType, subjectTokenType, scope); validateSubjectTokenIssuer(subjectIssuer); - Map claims = validateSubjectToken(subjectToken); + Map claims = validateSubjectToken(subjectToken); log.info("SubjectToken authorized"); return claims; } @@ -66,12 +67,12 @@ private void validateSubjectTokenIssuer(String subjectIssuer) { } } - private Map validateSubjectToken(String subjectToken) { - Map claims = jwtValidator.validate(subjectToken, urlJwkProvider); - if (!allowedAudience.equals(claims.get(Claims.AUDIENCE))){ + private Map validateSubjectToken(String subjectToken) { + Map claims = jwtValidator.validate(subjectToken, urlJwkProvider); + if (!allowedAudience.equals(claims.get(Claims.AUDIENCE).asString())){ throw new InvalidTokenException("Invalid audience: " + allowedAudience); } - if (!allowedIssuer.equals(claims.get(Claims.ISSUER))){ + if (!allowedIssuer.equals(claims.get(Claims.ISSUER).asString())){ throw new InvalidTokenException("Invalid issuer: " + allowedIssuer); } return claims; diff --git a/src/main/java/it/gov/pagopa/payhub/auth/service/user/UserServiceImpl.java b/src/main/java/it/gov/pagopa/payhub/auth/service/user/UserServiceImpl.java index 6b1a9f9c..4cc7bb8e 100644 --- a/src/main/java/it/gov/pagopa/payhub/auth/service/user/UserServiceImpl.java +++ b/src/main/java/it/gov/pagopa/payhub/auth/service/user/UserServiceImpl.java @@ -1,12 +1,25 @@ package it.gov.pagopa.payhub.auth.service.user; +import it.gov.pagopa.payhub.auth.exception.custom.InvalidAccessTokenException; +import it.gov.pagopa.payhub.auth.service.TokenStoreService; import it.gov.pagopa.payhub.model.generated.UserInfo; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService{ + + private final TokenStoreService tokenStoreService; + public UserServiceImpl(TokenStoreService tokenStoreService) { + this.tokenStoreService = tokenStoreService; + } + @Override public UserInfo getUserInfo(String accessToken) { - return null; //TODO + UserInfo userInfo = tokenStoreService.load(accessToken); + if(userInfo==null){ + throw new InvalidAccessTokenException("AccessToken not found"); + } else { + return userInfo; + } } } diff --git a/src/main/java/it/gov/pagopa/payhub/auth/utils/JWTValidator.java b/src/main/java/it/gov/pagopa/payhub/auth/utils/JWTValidator.java index 16ce116a..2d7c1e25 100644 --- a/src/main/java/it/gov/pagopa/payhub/auth/utils/JWTValidator.java +++ b/src/main/java/it/gov/pagopa/payhub/auth/utils/JWTValidator.java @@ -8,13 +8,13 @@ import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import it.gov.pagopa.payhub.auth.exception.custom.InvalidTokenException; import it.gov.pagopa.payhub.auth.exception.custom.TokenExpiredException; import org.springframework.stereotype.Component; import java.security.interfaces.RSAPublicKey; -import java.util.HashMap; import java.util.Map; @@ -35,7 +35,7 @@ public class JWTValidator { * @throws InvalidTokenException if the token is invalid for any other reason */ - public Map validate(String token, String urlJwkProvider) { + public Map validate(String token, String urlJwkProvider) { try { DecodedJWT jwt = JWT.decode(token); @@ -45,10 +45,7 @@ public Map validate(String token, String urlJwkProvider) { JWTVerifier verifier = JWT.require(algorithm).build(); verifier.verify(token); - Map claimsMap = new HashMap<>(); - jwt.getClaims().forEach((key, value) -> claimsMap.put(key, value.asString())); - - return claimsMap; + return jwt.getClaims(); } catch (com.auth0.jwt.exceptions.TokenExpiredException e){ throw new TokenExpiredException(e.getMessage()); diff --git a/src/test/java/it/gov/pagopa/payhub/auth/service/TokenStoreServiceTest.java b/src/test/java/it/gov/pagopa/payhub/auth/service/TokenStoreServiceTest.java index 482c6287..5d806f40 100644 --- a/src/test/java/it/gov/pagopa/payhub/auth/service/TokenStoreServiceTest.java +++ b/src/test/java/it/gov/pagopa/payhub/auth/service/TokenStoreServiceTest.java @@ -1,11 +1,9 @@ package it.gov.pagopa.payhub.auth.service; +import it.gov.pagopa.payhub.model.generated.UserInfo; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.HashMap; -import java.util.Map; - class TokenStoreServiceTest { private final TokenStoreService service = new TokenStoreServiceImpl(); @@ -13,14 +11,14 @@ class TokenStoreServiceTest { @Test void givenClaimsWhenSaveThenReturnThem(){ // Given - HashMap idTokenClaims = new HashMap<>(); + UserInfo userInfo = new UserInfo(); String accessToken = "AccessToken"; // When - Map result = service.save(accessToken, idTokenClaims); + UserInfo result = service.save(accessToken, userInfo); // Then - Assertions.assertSame(idTokenClaims, result); + Assertions.assertSame(userInfo, result); } @Test @@ -29,7 +27,7 @@ void givenAccessTokenWhenSaveThenNull(){ String accessToken = "AccessToken"; // When - Map result = service.load(accessToken); + UserInfo result = service.load(accessToken); // Then Assertions.assertNull(result); diff --git a/src/test/java/it/gov/pagopa/payhub/auth/service/exchange/ExchangeTokenServiceTest.java b/src/test/java/it/gov/pagopa/payhub/auth/service/exchange/ExchangeTokenServiceTest.java index 313e2be5..42bc3716 100644 --- a/src/test/java/it/gov/pagopa/payhub/auth/service/exchange/ExchangeTokenServiceTest.java +++ b/src/test/java/it/gov/pagopa/payhub/auth/service/exchange/ExchangeTokenServiceTest.java @@ -1,7 +1,9 @@ package it.gov.pagopa.payhub.auth.service.exchange; +import com.auth0.jwt.interfaces.Claim; import it.gov.pagopa.payhub.auth.service.TokenStoreService; import it.gov.pagopa.payhub.model.generated.AccessToken; +import it.gov.pagopa.payhub.model.generated.UserInfo; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -22,12 +24,14 @@ class ExchangeTokenServiceTest { private AccessTokenBuilderService accessTokenBuilderServiceMock; @Mock private TokenStoreService tokenStoreServiceMock; + @Mock + private IDTokenClaims2UserInfoMapper idTokenClaimsMapperMock; private ExchangeTokenService service; @BeforeEach void init(){ - service = new ExchangeTokenServiceImpl(validateExternalTokenServiceMock, accessTokenBuilderServiceMock, tokenStoreServiceMock); + service = new ExchangeTokenServiceImpl(validateExternalTokenServiceMock, accessTokenBuilderServiceMock, tokenStoreServiceMock, idTokenClaimsMapperMock); } @AfterEach @@ -35,7 +39,8 @@ void verifyNotMoreInteractions(){ Mockito.verifyNoMoreInteractions( validateExternalTokenServiceMock, accessTokenBuilderServiceMock, - tokenStoreServiceMock + tokenStoreServiceMock, + idTokenClaimsMapperMock ); } @@ -49,7 +54,7 @@ void givenValidTokenWhenPostTokenThenSuccess(){ String subjectTokenType="SUBJECT_TOKEN_TYPE"; String scope="SCOPE"; - HashMap expectedClaims = new HashMap<>(); + HashMap expectedClaims = new HashMap<>(); Mockito.when(validateExternalTokenServiceMock.validate(clientId, grantType, subjectToken, subjectIssuer, subjectTokenType, scope)) .thenReturn(expectedClaims); @@ -57,11 +62,15 @@ void givenValidTokenWhenPostTokenThenSuccess(){ Mockito.when(accessTokenBuilderServiceMock.build()) .thenReturn(expectedAccessToken); + UserInfo userInfo = new UserInfo(); + Mockito.when(idTokenClaimsMapperMock.apply(expectedClaims)) + .thenReturn(userInfo); + // When AccessToken result = service.postToken(clientId, grantType, subjectToken, subjectIssuer, subjectTokenType, scope); // Then Assertions.assertSame(expectedAccessToken, result); - Mockito.verify(tokenStoreServiceMock).save(Mockito.same(expectedAccessToken.getAccessToken()), Mockito.same(expectedClaims)); + Mockito.verify(tokenStoreServiceMock).save(Mockito.same(expectedAccessToken.getAccessToken()), Mockito.same(userInfo)); } } diff --git a/src/test/java/it/gov/pagopa/payhub/auth/service/exchange/IDTokenClaims2UserInfoMapperTest.java b/src/test/java/it/gov/pagopa/payhub/auth/service/exchange/IDTokenClaims2UserInfoMapperTest.java new file mode 100644 index 00000000..6cfb640c --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/auth/service/exchange/IDTokenClaims2UserInfoMapperTest.java @@ -0,0 +1,74 @@ +package it.gov.pagopa.payhub.auth.service.exchange; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.Claim; +import it.gov.pagopa.payhub.auth.exception.custom.InvalidTokenException; +import it.gov.pagopa.payhub.model.generated.UserInfo; +import it.gov.pagopa.payhub.model.generated.UserOrganizationRoles; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +class IDTokenClaims2UserInfoMapperTest { + + private IDTokenClaims2UserInfoMapper mapper; + + @BeforeEach + void init(){ + mapper = new IDTokenClaims2UserInfoMapper(); + } + + @Test + void givenEmptyClaimsThenInvalidTokenException(){ + Map claims = Collections.emptyMap(); + Assertions.assertThrows(InvalidTokenException.class, () -> mapper.apply(claims)); + } + + @Test + void givenIDTokenClaimsThenOk() { + // Given + UserInfo expectedUserInfo = UserInfo.builder() + .userId("e1d9c534-86a9-4039-80da-8aa7a33ac9e7") + .name("demo") + .familyName("demosurname") + .fiscalCode("DMEMPY15L21L736U") + .issuer("https://dev.selfcare.pagopa.it") + .organization(UserOrganizationRoles.builder() + .id("133e9c1b-dfc5-43ea-98a7-f64f30613074") + .name("Ente P4PA intermediato 1") + .fiscalCode("99999999990") + .ipaCode("SELC_99999999990") + .roles(List.of("ROLE_ADMIN")) + .build()) + .build(); + Map claims = JWT.decode("eyJraWQiOiJqd3QtZXhjaGFuZ2VfZTA6OTQ6M2Q6NWI6YWY6ODY6YWU6YWM6YzM6ZGI6OWM6MzI6NTc6NWE6YTA6NDciLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ0eXAiOiJJRCIsImZhbWlseV9uYW1lIjoiZGVtb3N1cm5hbWUiLCJmaXNjYWxfbnVtYmVyIjoiRE1FTVBZMTVMMjFMNzM2VSIsIm5hbWUiOiJkZW1vIiwic3BpZF9sZXZlbCI6Imh0dHBzOi8vd3d3LnNwaWQuZ292Lml0L1NwaWRMMiIsImZyb21fYWEiOmZhbHNlLCJzdWIiOiJlMWQ5YzUzNC04NmE5LTQwMzktODBkYS04YWE3YTMzYWM5ZTciLCJ1aWQiOiJlMWQ5YzUzNC04NmE5LTQwMzktODBkYS04YWE3YTMzYWM5ZTciLCJsZXZlbCI6IkwyIiwiaWF0IjoxNzE0OTgwNTY4LCJleHAiOjU3MTQ5ODE0NjgsImF1ZCI6ImRldi5waWF0dGFmb3JtYXVuaXRhcmlhLnBhZ29wYS5pdCIsImlzcyI6Imh0dHBzOi8vZGV2LnNlbGZjYXJlLnBhZ29wYS5pdCIsImp0aSI6IjkyYzQ3OWI1LTUxMTUtNGQzMS1iMDliLTVjZmFmNTQ1Mzc3NCIsImVtYWlsIjoiZWVAZWUuaXQiLCJvcmdhbml6YXRpb24iOnsiaWQiOiIxMzNlOWMxYi1kZmM1LTQzZWEtOThhNy1mNjRmMzA2MTMwNzQiLCJuYW1lIjoiRW50ZSBQNFBBIGludGVybWVkaWF0byAxIiwicm9sZXMiOlt7InBhcnR5Um9sZSI6IkRFTEVHQVRFIiwicm9sZSI6IlJPTEVfQURNSU4ifV0sImZpc2NhbF9jb2RlIjoiOTk5OTk5OTk5OTAiLCJpcGFDb2RlIjoiU0VMQ185OTk5OTk5OTk5MCJ9LCJkZXNpcmVkX2V4cCI6NTcxNTAxMjg5NH0.I1LBk-69DTROtoLqS5wN3oHC-9W5GBUqiDe907ak5mVNwE00SnMxLFTzTPLfrSCy6zw1PaSx_Xcju2PiK6Xax6gkEz49-LeZ2KFFla1Al1bP35lrO8JRfoo7EefgMNRhXnnYUL6klVIVDpW1hz6G0Lx5p7WZZfU9Kq-hvf8cofkQs3mVIzEF_kvYtrJNug2liZmEzGqwYlgeHE0P3wWPDLKYELHfACiYgw9JMAktPmMsupyyDMfGLfJkZ62z_QovktuFYAqgV6METGwuJP0u3T3XxJy1NspsOudJo_r5r5o3oR14NOpR_PezzDO8ZhcXBRtb11v6mcuvLQD6VSPQ0A").getClaims(); + + // When + UserInfo result = mapper.apply(claims); + + // Then + Assertions.assertEquals(expectedUserInfo, result); + } + + @Test + void givenIDTokenNoOrganizationThenInvalidTokenException() { + // Given + Map claims = JWT.decode("eyJraWQiOiJqd3QtZXhjaGFuZ2VfZTA6OTQ6M2Q6NWI6YWY6ODY6YWU6YWM6YzM6ZGI6OWM6MzI6NTc6NWE6YTA6NDciLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ0eXAiOiJJRCIsImZhbWlseV9uYW1lIjoiZGVtb3N1cm5hbWUiLCJmaXNjYWxfbnVtYmVyIjoiRE1FTVBZMTVMMjFMNzM2VSIsIm5hbWUiOiJkZW1vIiwic3BpZF9sZXZlbCI6Imh0dHBzOi8vd3d3LnNwaWQuZ292Lml0L1NwaWRMMiIsImZyb21fYWEiOmZhbHNlLCJzdWIiOiJlMWQ5YzUzNC04NmE5LTQwMzktODBkYS04YWE3YTMzYWM5ZTciLCJ1aWQiOiJlMWQ5YzUzNC04NmE5LTQwMzktODBkYS04YWE3YTMzYWM5ZTciLCJsZXZlbCI6IkwyIiwiaWF0IjoxNzE0OTgwNTY4LCJleHAiOjU3MTQ5ODE0NjgsImF1ZCI6ImRldi5waWF0dGFmb3JtYXVuaXRhcmlhLnBhZ29wYS5pdCIsImlzcyI6Imh0dHBzOi8vZGV2LnNlbGZjYXJlLnBhZ29wYS5pdCIsImp0aSI6IjkyYzQ3OWI1LTUxMTUtNGQzMS1iMDliLTVjZmFmNTQ1Mzc3NCIsImVtYWlsIjoiZWVAZWUuaXQiLCJkZXNpcmVkX2V4cCI6NTcxNTAxMjg5NH0.aJIhnNEOXNx8wDTSmAVv7NJio_J9muU2Jq9bY3AX0rL09LIvoGoxH1-u2-gdz_2LpxfZjluS0PUNKvAVgAbtK7o_vNPnThbCaQjVvtBPPBSWcjXSxbi7uJ9r89CMyu1CGim5tXz9cPB9vTBsdPElQ4xGnWGUBl8nYvUZ9oayYxDpPBdEjbHUEAvjjiaEHFjt5bmzYeyOnh4g_qe6m_j6JzsijRNXh87Ple4Awb72CPaf4gwTXIPQqoHIISSKfBYOIU_zl5mg5e-p3pTdsNNBR30vKViiolde4EIuY33IB5hSAjk5Pvw_5TD26sEI581Zra9ccaUsoPW2watGAFa6kg").getClaims(); + + // When + Assertions.assertThrows(InvalidTokenException.class, () -> mapper.apply(claims)); + } + + @Test + void givenIDTokenNoOrganizationRolesThenInvalidTokenException() { + // Given + Map claims = JWT.decode("eyJraWQiOiJqd3QtZXhjaGFuZ2VfZTA6OTQ6M2Q6NWI6YWY6ODY6YWU6YWM6YzM6ZGI6OWM6MzI6NTc6NWE6YTA6NDciLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ0eXAiOiJJRCIsImZhbWlseV9uYW1lIjoiZGVtb3N1cm5hbWUiLCJmaXNjYWxfbnVtYmVyIjoiRE1FTVBZMTVMMjFMNzM2VSIsIm5hbWUiOiJkZW1vIiwic3BpZF9sZXZlbCI6Imh0dHBzOi8vd3d3LnNwaWQuZ292Lml0L1NwaWRMMiIsImZyb21fYWEiOmZhbHNlLCJzdWIiOiJlMWQ5YzUzNC04NmE5LTQwMzktODBkYS04YWE3YTMzYWM5ZTciLCJ1aWQiOiJlMWQ5YzUzNC04NmE5LTQwMzktODBkYS04YWE3YTMzYWM5ZTciLCJsZXZlbCI6IkwyIiwiaWF0IjoxNzE0OTgwNTY4LCJleHAiOjU3MTQ5ODE0NjgsImF1ZCI6ImRldi5waWF0dGFmb3JtYXVuaXRhcmlhLnBhZ29wYS5pdCIsImlzcyI6Imh0dHBzOi8vZGV2LnNlbGZjYXJlLnBhZ29wYS5pdCIsImp0aSI6IjkyYzQ3OWI1LTUxMTUtNGQzMS1iMDliLTVjZmFmNTQ1Mzc3NCIsImVtYWlsIjoiZWVAZWUuaXQiLCJvcmdhbml6YXRpb24iOnsiaWQiOiIxMzNlOWMxYi1kZmM1LTQzZWEtOThhNy1mNjRmMzA2MTMwNzQiLCJuYW1lIjoiRW50ZSBQNFBBIGludGVybWVkaWF0byAxIiwiZmlzY2FsX2NvZGUiOiI5OTk5OTk5OTk5MCIsImlwYUNvZGUiOiJTRUxDXzk5OTk5OTk5OTkwIn0sImRlc2lyZWRfZXhwIjo1NzE1MDEyODk0fQ.qx8bpuH4GG3B_VTkeirqZZovAPIgg9JeHhkS_fJXHgCKrL35uJfCGEcX9GuHNvOhaHPTM8nHwM7tMS8dgo0RAT7RQMQ-qwoOhhFVK0QX7D9dDe6C1sEviBaItMzpHnr5UKgavxStYI5KreLaIFSY89hyLeXIjlXizqQ4D5QHPhwfFDGFkDnKFW2h2CY5Usd4W4ebGPNRCmp9vNcfqVnvzi3lv3-SsVuCl80DyRLza5Rv-ce06pb-JI-axxu2VuLKwp2ghG8YZgiMZxNLw5Ew48MtX-5beHkDYkwJQITxaCAwv2Lj_6quLqGCsD_nbxh77aLhKSWWbjAj6DpiMBjOLA").getClaims(); + + // When + Assertions.assertThrows(InvalidTokenException.class, () -> mapper.apply(claims)); + } +} diff --git a/src/test/java/it/gov/pagopa/payhub/auth/service/exchange/ValidateExternalTokenServiceTest.java b/src/test/java/it/gov/pagopa/payhub/auth/service/exchange/ValidateExternalTokenServiceTest.java index ec9680fd..805601fe 100644 --- a/src/test/java/it/gov/pagopa/payhub/auth/service/exchange/ValidateExternalTokenServiceTest.java +++ b/src/test/java/it/gov/pagopa/payhub/auth/service/exchange/ValidateExternalTokenServiceTest.java @@ -1,5 +1,8 @@ package it.gov.pagopa.payhub.auth.service.exchange; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.Claim; import com.github.tomakehurst.wiremock.WireMockServer; import it.gov.pagopa.payhub.auth.exception.custom.*; import it.gov.pagopa.payhub.auth.utils.JWTValidator; @@ -12,8 +15,8 @@ import org.mockito.Mockito; import org.springframework.test.context.junit.jupiter.SpringExtension; +import java.time.Instant; import java.util.Date; -import java.util.HashMap; import java.util.Map; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; @@ -51,7 +54,7 @@ void clean(){ @Test void givenValidRequestThenOk() throws Exception { String subjectToken = utils.generateJWK(EXPIRES_AT); - Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, ALLOWED_AUDIENCE); + Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, ALLOWED_AUDIENCE); String wireMockUrl = utils.getUrlJwkProvider(); when(jwtValidator.validate(subjectToken, wireMockUrl)).thenReturn(claimsMap); @@ -63,7 +66,7 @@ void givenValidRequestThenOk() throws Exception { @Test void givenInvalidClientThenInvalidExchangeClientException() throws Exception { String subjectToken = utils.generateJWK(EXPIRES_AT); - Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, ALLOWED_AUDIENCE); + Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, ALLOWED_AUDIENCE); String wireMockUrl = utils.getUrlJwkProvider(); when(jwtValidator.validate(subjectToken, wireMockUrl)).thenReturn(claimsMap); @@ -75,7 +78,7 @@ void givenInvalidClientThenInvalidExchangeClientException() throws Exception { @Test void givenInvalidGrantTypeException() throws Exception { String subjectToken = utils.generateJWK(EXPIRES_AT); - Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, ALLOWED_AUDIENCE); + Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, ALLOWED_AUDIENCE); String wireMockUrl = utils.getUrlJwkProvider(); when(jwtValidator.validate(subjectToken, wireMockUrl)).thenReturn(claimsMap); @@ -87,7 +90,7 @@ void givenInvalidGrantTypeException() throws Exception { @Test void givenInvalidSubjectTokenIssuerThenInvalidTokenIssuerException() throws Exception { String subjectToken = utils.generateJWK(EXPIRES_AT); - Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, ALLOWED_AUDIENCE); + Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, ALLOWED_AUDIENCE); String wireMockUrl = utils.getUrlJwkProvider(); when(jwtValidator.validate(subjectToken, wireMockUrl)).thenReturn(claimsMap); @@ -99,7 +102,7 @@ void givenInvalidSubjectTokenIssuerThenInvalidTokenIssuerException() throws Exce @Test void givenInvalidSubjectTypeThenInvalidTokenException() throws Exception { String subjectToken = utils.generateJWK(EXPIRES_AT); - Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, ALLOWED_AUDIENCE); + Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, ALLOWED_AUDIENCE); String wireMockUrl = utils.getUrlJwkProvider(); when(jwtValidator.validate(subjectToken, wireMockUrl)).thenReturn(claimsMap); @@ -111,7 +114,7 @@ void givenInvalidSubjectTypeThenInvalidTokenException() throws Exception { @Test void givenInvalidScopeThenInvalidExchangeRequestException() throws Exception { String subjectToken = utils.generateJWK(EXPIRES_AT); - Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, ALLOWED_AUDIENCE); + Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, ALLOWED_AUDIENCE); String wireMockUrl = utils.getUrlJwkProvider(); when(jwtValidator.validate(subjectToken, wireMockUrl)).thenReturn(claimsMap); @@ -123,7 +126,7 @@ void givenInvalidScopeThenInvalidExchangeRequestException() throws Exception { @Test void givenInvalidIssuerClaimThenInvalidTokenException() throws Exception { String subjectToken = utils.generateJWK(EXPIRES_AT); - Map claimsMap = createJWKClaims("ISS_FAKE", ALLOWED_AUDIENCE); + Map claimsMap = createJWKClaims("ISS_FAKE", ALLOWED_AUDIENCE); String wireMockUrl = utils.getUrlJwkProvider(); when(jwtValidator.validate(subjectToken, wireMockUrl)).thenReturn(claimsMap); @@ -136,7 +139,7 @@ void givenInvalidIssuerClaimThenInvalidTokenException() throws Exception { @Test void givenInvalidAudienceClaimThenInvalidTokenException() throws Exception { String subjectToken = utils.generateJWK(EXPIRES_AT); - Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, "AUD_FAKE"); + Map claimsMap = createJWKClaims(ALLOWED_SUBECJECT_ISSUER, "AUD_FAKE"); String wireMockUrl = utils.getUrlJwkProvider(); when(jwtValidator.validate(subjectToken, wireMockUrl)).thenReturn(claimsMap); @@ -146,12 +149,12 @@ void givenInvalidAudienceClaimThenInvalidTokenException() throws Exception { } - private Map createJWKClaims (String iss, String aud){ - Map claims = new HashMap<>(); - claims.put("iss", iss); - claims.put("aud", aud); - claims.put("exp", "1715267318"); - claims.put("jti", "my-key-id"); - return claims; + private Map createJWKClaims (String iss, String aud){ + return JWT.decode(JWT.create() + .withIssuer(iss) + .withAudience(aud) + .withExpiresAt(Instant.ofEpochSecond(1715267318)) + .withJWTId("my-key-id") + .sign(Algorithm.none())).getClaims(); } } diff --git a/src/test/java/it/gov/pagopa/payhub/auth/service/user/UserServiceTest.java b/src/test/java/it/gov/pagopa/payhub/auth/service/user/UserServiceTest.java new file mode 100644 index 00000000..829c59e7 --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/auth/service/user/UserServiceTest.java @@ -0,0 +1,58 @@ +package it.gov.pagopa.payhub.auth.service.user; + +import it.gov.pagopa.payhub.auth.exception.custom.InvalidAccessTokenException; +import it.gov.pagopa.payhub.auth.service.TokenStoreService; +import it.gov.pagopa.payhub.model.generated.UserInfo; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class UserServiceTest { + + @Mock + private TokenStoreService tokenStoreServiceMock; + + private UserService service; + + @BeforeEach + void init(){ + service = new UserServiceImpl(tokenStoreServiceMock); + } + + @AfterEach + void verifyNotMoreInteractions(){ + Mockito.verifyNoMoreInteractions(tokenStoreServiceMock); + } + + @Test + void givenNotExistentTokenWhenGetUserInfoThenInvalidAccessTokenException(){ + // Given + String accessToken = "accessToken"; + + // When, Then + Assertions.assertThrows(InvalidAccessTokenException.class, ()->service.getUserInfo(accessToken)); + + Mockito.verify(tokenStoreServiceMock).load(accessToken); + } + + @Test + void givenAccessTokenWhenGetUserInfoThenOk(){ + // Given + String accessToken = "accessToken"; + + UserInfo expectedUserInfo = new UserInfo(); + Mockito.when(tokenStoreServiceMock.load(accessToken)).thenReturn(expectedUserInfo); + + // When + UserInfo result = service.getUserInfo(accessToken); + + // Then + Assertions.assertSame(expectedUserInfo, result); + } +} diff --git a/src/test/java/it/gov/pagopa/payhub/auth/utils/JWTValidatorTest.java b/src/test/java/it/gov/pagopa/payhub/auth/utils/JWTValidatorTest.java index d827c4fa..43321e1c 100644 --- a/src/test/java/it/gov/pagopa/payhub/auth/utils/JWTValidatorTest.java +++ b/src/test/java/it/gov/pagopa/payhub/auth/utils/JWTValidatorTest.java @@ -1,5 +1,6 @@ package it.gov.pagopa.payhub.auth.utils; +import com.auth0.jwt.interfaces.Claim; import com.github.tomakehurst.wiremock.WireMockServer; import it.gov.pagopa.payhub.auth.exception.custom.InvalidTokenException; import it.gov.pagopa.payhub.auth.exception.custom.TokenExpiredException; @@ -42,7 +43,7 @@ void givenValidTokenThenSuccess() throws Exception { String urlJwkProvider = utils.getUrlJwkProvider(); - Map claimsMap = jwtValidator.validate(token, urlJwkProvider); + Map claimsMap = jwtValidator.validate(token, urlJwkProvider); assertNotNull(claimsMap); }