Skip to content

Commit

Permalink
P4ADEV-788 applied requested changes
Browse files Browse the repository at this point in the history
  • Loading branch information
macacia committed Nov 8, 2024
1 parent 6dabf7b commit 3d95284
Show file tree
Hide file tree
Showing 21 changed files with 342 additions and 291 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ public class A2ALegacyClaims2UserInfoMapper {

private static final String A2A_PREFIX = "A2A-";

public UserInfo map(String subject) {
public UserInfo map(String issuer) {
return UserInfo.builder()
.issuer(subject)
.userId(A2A_PREFIX + subject)
.name(subject)
.familyName(subject)
.fiscalCode(A2A_PREFIX + subject)
.issuer(issuer)
.userId(A2A_PREFIX + issuer)
.name(issuer)
.familyName(issuer)
.fiscalCode(A2A_PREFIX + issuer)
.organizations(Collections.singletonList(UserOrganizationRoles.builder()
.organizationIpaCode(subject)
.organizationIpaCode(issuer)
.roles(Collections.singletonList(Constants.ROLE_ADMIN))
.build()))
.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package it.gov.pagopa.payhub.auth.security;

import com.auth0.jwt.JWT;
import com.auth0.jwt.RegisteredClaims;
import com.auth0.jwt.interfaces.DecodedJWT;
import it.gov.pagopa.payhub.auth.exception.custom.InvalidAccessTokenException;
import it.gov.pagopa.payhub.auth.service.AccessTokenBuilderService;
import it.gov.pagopa.payhub.auth.service.AuthnService;
Expand Down Expand Up @@ -34,11 +31,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final AuthnService authnService;
private final ValidateTokenService validateTokenService;
private final JWTLegacyHandlerService jwtLegacyHandlerService;
private final AccessTokenBuilderService accessTokenBuilderService;

public JwtAuthenticationFilter(AuthnService authnService, ValidateTokenService validateTokenService, JWTLegacyHandlerService jwtLegacyHandlerService) {
public JwtAuthenticationFilter(AuthnService authnService, ValidateTokenService validateTokenService, JWTLegacyHandlerService jwtLegacyHandlerService, AccessTokenBuilderService accessTokenBuilderService) {
this.authnService = authnService;
this.validateTokenService = validateTokenService;
this.jwtLegacyHandlerService = jwtLegacyHandlerService;
this.accessTokenBuilderService = accessTokenBuilderService;
}

@Override
Expand All @@ -47,7 +46,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
if (StringUtils.hasText(authorization)) {
String token = authorization.replace("Bearer ", "");
UserInfo userInfo = getUserInfoByTokenHeaderClaim(token);
UserInfo userInfo = validateToken(token);
Collection<? extends GrantedAuthority> authorities = null;
if (userInfo.getOrganizationAccess() != null) {
authorities = userInfo.getOrganizations().stream()
Expand All @@ -65,13 +64,11 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
} catch (Exception e){
log.error("Something gone wrong while retrieving UserInfo", e);
}

filterChain.doFilter(request, response);
}

private UserInfo getUserInfoByTokenHeaderClaim(String token) {
DecodedJWT jwt = JWT.decode(token);
if (!AccessTokenBuilderService.ISSUER.equalsIgnoreCase(jwt.getHeaderClaim(RegisteredClaims.ISSUER).asString()))
private UserInfo validateToken(String token) {
if (!token.startsWith(accessTokenBuilderService.getHeaderPrefix()))
return jwtLegacyHandlerService.handleLegacyToken(token);

validateTokenService.validate(token);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package it.gov.pagopa.payhub.auth.service;

import com.auth0.jwt.HeaderParams;
import com.auth0.jwt.JWT;
import com.auth0.jwt.RegisteredClaims;
import com.auth0.jwt.algorithms.Algorithm;
import it.gov.pagopa.payhub.auth.utils.CertUtils;
import it.gov.pagopa.payhub.model.generated.AccessToken;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

Expand All @@ -16,28 +14,33 @@
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Service
public class AccessTokenBuilderService {
public static final String ISSUER = "p4pa-auth";
public static final String ACCESS_TOKEN_TYPE = "at+JWT";
private final String allowedAudience;
private final int expireIn;

private final RSAPublicKey rsaPublicKey;
private final RSAPrivateKey rsaPrivateKey;
private final String kid;
private final DataCipherService dataCipherService;

public AccessTokenBuilderService(
@Value("${jwt.audience}") String allowedAudience,
@Value("${jwt.access-token.expire-in}") int expireIn,
@Value("${jwt.access-token.private-key}") String privateKey,
@Value("${jwt.access-token.public-key}") String publicKey
) {
@Value("${jwt.audience}") String allowedAudience,
@Value("${jwt.access-token.expire-in}") int expireIn,
@Value("${jwt.access-token.private-key}") String privateKey,
@Value("${jwt.access-token.public-key}") String publicKey, DataCipherService dataCipherService) {
this.allowedAudience = allowedAudience;
this.expireIn = expireIn;
this.dataCipherService = dataCipherService;
byte[] hashed = dataCipherService.hash(publicKey);
this.kid = UUID.nameUUIDFromBytes(hashed).toString();

try {
try {
rsaPrivateKey = CertUtils.pemKey2PrivateKey(privateKey);
rsaPublicKey = CertUtils.pemPub2PublicKey(publicKey);
} catch (InvalidKeySpecException | NoSuchAlgorithmException | IOException e) {
Expand All @@ -48,11 +51,11 @@ public AccessTokenBuilderService(
public AccessToken build(){
Algorithm algorithm = Algorithm.RSA512(rsaPublicKey, rsaPrivateKey);
Map<String, Object> headerClaims = new HashMap<>();
headerClaims.put(RegisteredClaims.ISSUER, ISSUER);
headerClaims.put(HeaderParams.KEY_ID, kid);
headerClaims.put("typ", ACCESS_TOKEN_TYPE);
String tokenType = "bearer";
String token = JWT.create()
.withHeader(headerClaims)
.withHeader(headerClaims)
.withClaim("typ", tokenType)
.withIssuer(allowedAudience)
.withJWTId(UUID.randomUUID().toString())
Expand All @@ -61,4 +64,9 @@ public AccessToken build(){
.sign(algorithm);
return new AccessToken(token, tokenType, expireIn);
}

public String getHeaderPrefix() {
var prefix = String.format("{\"kid\":\"%s\"", kid);
return Base64.getEncoder().encodeToString(prefix.getBytes());
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,41 @@
package it.gov.pagopa.payhub.auth.service.a2a.legacy;

import it.gov.pagopa.payhub.auth.exception.custom.InvalidTokenException;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Component
@ConfigurationProperties(prefix = "m2m.legacy.public")
@ConfigurationProperties(prefix = "m2m.legacy")
@Data
public class A2AClientLegacyPropConfig {
private Map<String, String> secrets;
private Map<String, String> publicKeys;

public Map<String, PublicKey> getPublicKeysAsMap() {
return Optional.ofNullable(publicKeys)
.orElse(Map.of())
.entrySet().stream()
.collect(Collectors.toUnmodifiableMap(
Map.Entry::getKey,
entry -> getPublicKeyFromString(entry.getKey(), entry.getValue())
));
}

private PublicKey getPublicKeyFromString(String keyName, String encodedKey) {
try {
X509EncodedKeySpec publicKeyX509 = new X509EncodedKeySpec(Base64.getDecoder().decode(encodedKey));
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(publicKeyX509);
} catch (Exception e){
throw new InvalidTokenException("invalid public key for: " + keyName);
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package it.gov.pagopa.payhub.auth.service.a2a.legacy;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.security.PublicKey;
import java.util.Map;

@Service
@Slf4j
public class A2ALegacySecretsService {
private final A2AClientLegacyPropConfig a2AClientLegacyPropConfig;

public A2ALegacySecretsService(A2AClientLegacyPropConfig a2AClientLegacyPropConfig) {
this.a2AClientLegacyPropConfig = a2AClientLegacyPropConfig;
}

public Map<String, PublicKey> getLegacySecrets() {
return a2AClientLegacyPropConfig.getPublicKeysAsMap();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,35 @@
import it.gov.pagopa.payhub.auth.utils.JWTValidator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Service;

import java.security.PublicKey;
import java.time.Instant;
import java.util.Map;
import java.util.Objects;

@Service
@Slf4j
public class ValidateJWTLegacyService {
public static final String TOKEN_TYPE_A2A = "a2a";

private final A2ALegacySecretsRetrieverService a2ALegacySecretsRetrieverService;
private final A2ALegacySecretsService a2ALegacySecretsService;
private final JWTValidator jwtValidator;

public ValidateJWTLegacyService(A2ALegacySecretsRetrieverService a2ALegacySecretsRetrieverService, JWTValidator jwtValidator) {
this.a2ALegacySecretsRetrieverService = a2ALegacySecretsRetrieverService;
public ValidateJWTLegacyService(A2ALegacySecretsService a2ALegacySecretsService, JWTValidator jwtValidator) {
this.a2ALegacySecretsService = a2ALegacySecretsService;
this.jwtValidator = jwtValidator;
}

public Pair<String, Map<String, Claim>> validate(String token) {
Map<String, PublicKey> clientApplicationsPublicKeyMap = a2ALegacySecretsRetrieverService.envToMap();
verifyEnvMap(clientApplicationsPublicKeyMap);
Map<String, PublicKey> clientApplicationsPublicKeyMap = a2ALegacySecretsService.getLegacySecrets();
Pair<String, Map<String, Claim>> claims = validateToken(clientApplicationsPublicKeyMap, token);
validateM2MType(claims.getRight());
validateClaims(claims.getRight());

return claims;
}

private void verifyEnvMap(Map<String, PublicKey> clientApplicationsPublicKeyMap) {
if (clientApplicationsPublicKeyMap.isEmpty()){
throw new InvalidTokenException("The PublicKey is not present");
}
}

private void validateM2MType(Map<String, Claim> claims){
if (!TOKEN_TYPE_A2A.equals(claims.get("type").asString()))
Expand All @@ -63,9 +55,7 @@ private void validateClaims(Map<String, Claim> claims) {

private Pair<String, Map<String, Claim>> validateToken(Map<String, PublicKey> clientApplicationsPublicKeyMap, String token) {
return clientApplicationsPublicKeyMap.keySet().stream()
.map(key -> new ImmutablePair<>(key, clientApplicationsPublicKeyMap.get(key)))
.map(pair -> jwtValidator.validateLegacyToken(pair.getLeft(), token, pair.getRight()))
.filter(Objects::nonNull)
.map(key -> Pair.of(key, jwtValidator.validate(token, clientApplicationsPublicKeyMap.get(key))))
.findFirst()
.orElseThrow(() -> new InvalidTokenException("Invalid token for A2A call"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
public class IamUserInfoDTO2UserInfoMapper implements Function<IamUserInfoDTO, UserInfo> {

private static final String WS_USER_SUFFIX = "-WS_USER";

private final UsersRepository usersRepository;
private final OperatorsRepository operatorsRepository;

Expand Down
16 changes: 6 additions & 10 deletions src/main/java/it/gov/pagopa/payhub/auth/utils/JWTValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@
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 java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Map;


Expand Down Expand Up @@ -94,22 +91,21 @@ public void validateInternalToken(String token) {
/**
* Validates JWT signature with publickey.
*
* @param applicationName the application name for which to validate the token
* @param token the JWT to validate
* @param publicKey the key to use in the verify instance.
* @throws TokenExpiredException if the token has expired.
* @throws InvalidTokenException if the token is invalid for any other reason
* (e.g., signature verification failure).
*/
public Pair<String, Map<String, Claim>> validateLegacyToken(String applicationName, String token, PublicKey publicKey) {
public Map<String, Claim> validate(String token, PublicKey publicKey) {
try{
DecodedJWT jwt = JWT.decode(token);

Algorithm algorithm = Algorithm.RSA512((RSAPublicKey) publicKey, null);
JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(jwt);

return new ImmutablePair<>(applicationName, jwt.getClaims());
return jwt.getClaims();
} catch (com.auth0.jwt.exceptions.TokenExpiredException e){
throw new TokenExpiredException(e.getMessage());
} catch (JWTVerificationException ex) {
Expand Down
9 changes: 7 additions & 2 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,10 @@ data-chiper:

m2m:
piattaforma-unitaria-client-secret: "\${PIATTAFORMA_UNITARIA_CLIENT_SECRET:SECRET}"
# list here the keys used to identify external apps
# for example if an app is called "acme" then you should define its public key in the property "m2m.legacy.public.acme"
# legacy:
# public-keys:
# foo: "\${FOO_APPLICATION_PUBLIC_KEY:FOO_PUCLIC_KEY}"
# bar: "\${BAR_APPLICATION_PUBLIC_KEY:BAR_PUCLIC_KEY}"
# list here the keys/values used to identify external apps
# the key naming is mandatory to be the value of the organization IPA code
# you should define its public key in the property like this for example --> "m2m.legacy.public-keys.IPACODE"
Loading

0 comments on commit 3d95284

Please sign in to comment.