diff --git a/src/main/java/bio/overture/ego/controller/AuthController.java b/src/main/java/bio/overture/ego/controller/AuthController.java index b3f311d8..006834b0 100644 --- a/src/main/java/bio/overture/ego/controller/AuthController.java +++ b/src/main/java/bio/overture/ego/controller/AuthController.java @@ -64,8 +64,7 @@ public class AuthController { private final GoogleTokenService googleTokenService; private final TokenSigner tokenSigner; private final RefreshContextService refreshContextService; - - private final String PASSPORT_CLIENT_NAME = "passport"; + private final String GA4GH_PASSPORT_SCOPE = "ga4gh_passport_v1"; @Autowired public AuthController( @@ -128,8 +127,11 @@ public ResponseEntity user( val user = (CustomOAuth2User) authentication.getPrincipal(); - val passportJwtToken = (authentication.getAuthorizedClientRegistrationId().equals(PASSPORT_CLIENT_NAME)) ? - passportService.getPassportToken(((CustomOAuth2User) authentication.getPrincipal()).getAccessToken()) : + + val passportJwtToken = (user.getClaim(GA4GH_PASSPORT_SCOPE) != null) ? + passportService.getPassportToken( + authentication.getAuthorizedClientRegistrationId(), + user.getAccessToken()) : null; String token = @@ -143,7 +145,8 @@ public ResponseEntity user( ProviderType.resolveProviderType( authentication.getAuthorizedClientRegistrationId())) .build(), - passportJwtToken); + passportJwtToken, + authentication.getAuthorizedClientRegistrationId()); val outgoingRefreshContext = refreshContextService.createInitialRefreshContext(token); val cookie = diff --git a/src/main/java/bio/overture/ego/service/PassportService.java b/src/main/java/bio/overture/ego/service/PassportService.java index 42c1eb7e..a32237ec 100644 --- a/src/main/java/bio/overture/ego/service/PassportService.java +++ b/src/main/java/bio/overture/ego/service/PassportService.java @@ -26,10 +26,11 @@ import lombok.val; import org.apache.commons.codec.binary.Base64; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.LinkedMultiValueMap; @@ -52,20 +53,12 @@ public class PassportService { @Autowired private CacheUtil cacheUtil; + @Autowired private ClientRegistrationRepository clientRegistrationRepository; + private final String REQUESTED_TOKEN_TYPE = "urn:ga4gh:params:oauth:token-type:passport"; private final String SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"; private final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"; - @Value("${spring.security.oauth2.client.registration.passport.clientId}") - private String clientId; - - @Value("${spring.security.oauth2.client.registration.passport.clientSecret}") - private String clientSecret; - - @Value("${spring.security.oauth2.client.provider.passport.issuer-uri}") - private String passportIssuerUri; - - @Autowired public PassportService( @@ -74,14 +67,14 @@ public PassportService( this.visaPermissionService = visaPermissionService; } - public List getPermissions(String authToken) + public List getPermissions(String authToken, String providerType) throws JsonProcessingException, ParseException, JwkException { // Validates passport auth token - isValidPassport(authToken); + isValidPassport(authToken, providerType); // Parses passport JWT token Passport parsedPassport = parsePassport(authToken); // Fetches visas for parsed passport - List visas = getVisas(parsedPassport); + List visas = getVisas(parsedPassport, providerType); // Fetches visa permissions for extracted visas List visaPermissions = getVisaPermissions(visas); // removes deduplicates from visaPermissions @@ -90,22 +83,22 @@ public List getPermissions(String authToken) } // Validates passport token based on public key - private void isValidPassport(@NonNull String authToken) - throws ParseException, JwkException, JsonProcessingException { + private void isValidPassport(@NonNull String authToken, @NonNull String providerType) + throws JwkException { DecodedJWT jwt = JWT.decode(authToken); - Jwk jwk = cacheUtil.getPassportBrokerPublicKey().get(jwt.getKeyId()); + Jwk jwk = cacheUtil.getPassportBrokerPublicKey(providerType).get(jwt.getKeyId()); Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null); algorithm.verify(jwt); } // Extracts Visas from parsed passport object - private List getVisas(Passport passport) { + private List getVisas(Passport passport, @NonNull String providerType) { List visas = new ArrayList<>(); passport.getGa4ghPassportV1().stream() .forEach( visaJwt -> { try { - visaService.isValidVisa(visaJwt); + visaService.isValidVisa(visaJwt, providerType); PassportVisa visa = visaService.parseVisa(visaJwt); if (visa != null) { visas.add(visa); @@ -134,8 +127,8 @@ private List getVisaPermissions(List visas) { return visaPermissions; } - public Set extractScopes(@NonNull String passportJwtToken) throws ParseException, JwkException, JsonProcessingException { - val resolvedPermissions = getPermissions(passportJwtToken); + public Set extractScopes(@NonNull String passportJwtToken, @NonNull String providerType) throws ParseException, JwkException, JsonProcessingException { + val resolvedPermissions = getPermissions(passportJwtToken, providerType); val output = mapToSet(resolvedPermissions, AbstractPermissionService::buildScope); if (output.isEmpty()) { output.add(Scope.defaultScope()); @@ -162,19 +155,18 @@ private List deDupeVisaPermissions(List visaPerm return permissionsSet.stream().collect(Collectors.toList()); } - public String getPassportToken(String accessToken) { + public String getPassportToken(String providerId, String accessToken) { if (accessToken == null || accessToken.isEmpty()) return null; - val params = passportTokenParams(accessToken); + val clientRegistration = clientRegistrationRepository.findByRegistrationId(providerId); val uri = UriComponentsBuilder - .fromUriString(passportIssuerUri) - .path("/token") - .queryParams(params) + .fromUriString(clientRegistration.getProviderDetails().getTokenUri()) + .queryParams(passportTokenParams(accessToken)) .toUriString(); - val passportToken = getTemplate(clientId, clientSecret) + val passportToken = getTemplate(clientRegistration) .exchange(uri, HttpMethod.POST, null, @@ -186,7 +178,7 @@ public String getPassportToken(String accessToken) { null; } - private RestTemplate getTemplate(String clientId, String clientSecret) { + private RestTemplate getTemplate(ClientRegistration clientRegistration) { RestTemplate restTemplate = new RestTemplate(); restTemplate .getInterceptors() @@ -195,7 +187,7 @@ private RestTemplate getTemplate(String clientId, String clientSecret) { x.getHeaders() .set( HttpHeaders.AUTHORIZATION, - "Basic " + getBasicAuthHeader(clientId, clientSecret)); + "Basic " + getBasicAuthHeader(clientRegistration.getClientId(), clientRegistration.getClientSecret())); return z.execute(x, y); }); return restTemplate; diff --git a/src/main/java/bio/overture/ego/service/TokenService.java b/src/main/java/bio/overture/ego/service/TokenService.java index 67da3279..66feb55b 100644 --- a/src/main/java/bio/overture/ego/service/TokenService.java +++ b/src/main/java/bio/overture/ego/service/TokenService.java @@ -141,12 +141,12 @@ public ApiKey getWithRelationships(@NonNull UUID id) { } public String generateUserToken(IDToken idToken) { - return generateUserToken(idToken, null); + return generateUserToken(idToken, null, null); } - public String generateUserToken(IDToken idToken, String passportJwtToken) { + public String generateUserToken(IDToken idToken, String passportJwtToken, String providerType) { val user = userService.getUserByToken(idToken); - return generateUserToken(user, passportJwtToken); + return generateUserToken(user, passportJwtToken, providerType); } public String updateUserToken(String accessToken) { @@ -175,12 +175,12 @@ public String generateUserToken(User u) { } @SneakyThrows - public String generateUserToken(User u, String passportJwtToken) { + public String generateUserToken(User u, String passportJwtToken, String providerType) { Set scopes = extractExplicitScopes(u); - if (passportJwtToken != null){ - Set scopesFromVisas = extractExplicitScopes(passportJwtToken); + if (passportJwtToken != null && providerType != null){ + Set scopesFromVisas = extractExplicitScopes(passportJwtToken, providerType); scopes = mergeScopes(scopes, scopesFromVisas); } @@ -601,8 +601,8 @@ private static Set extractExplicitScopes(Application a) { } @SneakyThrows - private Set extractExplicitScopes(String passportJwtToken){ - return mapToSet(explicitScopes(passportService.extractScopes(passportJwtToken)), Scope::toString); + private Set extractExplicitScopes(String passportJwtToken, String providerType){ + return mapToSet(explicitScopes(passportService.extractScopes(passportJwtToken, providerType)), Scope::toString); } private Set mergeScopes(Set scopeSet, Set scopeSetAdditional){ diff --git a/src/main/java/bio/overture/ego/service/VisaService.java b/src/main/java/bio/overture/ego/service/VisaService.java index 1638d2af..8a26ad18 100644 --- a/src/main/java/bio/overture/ego/service/VisaService.java +++ b/src/main/java/bio/overture/ego/service/VisaService.java @@ -105,9 +105,9 @@ public PassportVisa parseVisa(@NonNull String visaJwtToken) throws JsonProcessin } // Checks if the visa is a valid visa - public void isValidVisa(@NonNull String authToken) throws JwkException, JsonProcessingException { + public void isValidVisa(@NonNull String authToken, @NonNull String providerType) throws JwkException, JsonProcessingException { DecodedJWT jwt = JWT.decode(authToken); - Jwk jwk = cacheUtil.getPassportBrokerPublicKey().get(jwt.getKeyId()); + Jwk jwk = cacheUtil.getPassportBrokerPublicKey(providerType).get(jwt.getKeyId()); Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null); algorithm.verify(jwt); } diff --git a/src/main/java/bio/overture/ego/utils/CacheUtil.java b/src/main/java/bio/overture/ego/utils/CacheUtil.java index 3ea88951..ac9d43b9 100644 --- a/src/main/java/bio/overture/ego/utils/CacheUtil.java +++ b/src/main/java/bio/overture/ego/utils/CacheUtil.java @@ -4,17 +4,18 @@ import static org.springframework.http.HttpMethod.GET; import com.auth0.jwk.Jwk; -import com.fasterxml.jackson.core.JsonProcessingException; import java.util.HashMap; import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; +import lombok.val; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.ResponseEntity; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.stereotype.Component; import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; @@ -23,20 +24,22 @@ @Component public class CacheUtil { - @Value("${broker.publicKey.url}") - private String brokerPublicKeyUrl; + @Autowired + private ClientRegistrationRepository clientRegistrationRepository; private RestTemplate restTemplate; @Cacheable("getPassportBrokerPublicKey") - public Map getPassportBrokerPublicKey() throws JsonProcessingException { + public Map getPassportBrokerPublicKey(String providerType) { ResponseEntity> response; Map jwkMap = new HashMap(); + + val clientRegistration = clientRegistrationRepository.findByRegistrationId(providerType); try { restTemplate = new RestTemplate(); response = restTemplate.exchange( - brokerPublicKeyUrl, + clientRegistration.getProviderDetails().getJwkSetUri(), GET, null, new ParameterizedTypeReference>() {}); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3a45ac08..b2757b4a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -70,6 +70,8 @@ spring: - openid - email - profile + + # Passport brokers must have ga4gh_passport_v1 scope passport: clientId: ego-client clientSecret: @@ -197,9 +199,6 @@ token: private-key: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDSU6oy48sJW6xzqzOSU1dAvUUeFKQSBHsCf7wGWUGpOxEczhtFiiyx4YUJtg+fyvwWxa4wO3GnQLBPIxBHY8JsnvjQN2lsTUoLqMB9nGpwF617uA/S2igm1u+cDpfi82kbi6SG1Sg30PM047R6oxTRGDLLkeMRF1gRaTBM0HfSL0j6ccU5KPgwYsFLE2We6jeR56iYJGC2KYLH4v8rcc2jRAdMbUntHMtUByF9BPSW7elQnyQH5Qzr/o0b59XLKwnJFn2Bp2yviC8cdyTDyhQGna0e+oESQR1j6u3Ux/mOmm3slRXscA8sH+pHmOEAtjYVf/ww36U8uZv+ctBCJyFVAgMBAAECggEBALrEeJqAFUfWFCkSmdUSFKT0bW/svFUTjXgGnZy1ncz9GpENpMH3lQDQVibteKpYwcom+Cr0XlQ66VUcudPrDjcOY7vhuMfnSh1YWLYyM4IeRHtcUxDVkFoM+vEFNHLf2zIOqqbgmboW3iDVIurT7iRO7KxAe/YtWJL9aVqMtBn7Lu7S7OvAU4ji5iLIBxjl82JYA+9lu/aQ6YGaoZuSO7bcU8Sivi+DKAahqN9XMKiB1XpC+PpaS/aec2S7xIlTdzoDGxEALRGlMe+xBEeQTBVJHBWrRIDPoHLTREeRC/9Pp+1Y4Dz8hd5Bi0n8/5r/q0liD+0vtmjsdU4E2QrktYECgYEA73qWvhCYHPMREAFtwz1mpp9ZhDCW6SF+njG7fBKcjz8OLcy15LXiTGc268ewtQqTMjPQlm1n2C6hGccGAIlMibQJo3KZHlTs125FUzDpTVgdlei6vU7M+gmfRSZed00J6jC04/qMR1tnV3HME3np7eRTKTA6Ts+zBwEvkbCetSkCgYEA4NY5iSBO1ybouIecDdD15uI2ItLPCBNMzu7IiK7IygIzuf+SyKyjhtFSR4vEi0gScOM7UMlwCMOVU10e4nMDknIWCDG9iFvmIEkGHGxgRrN5hX1Wrq74wF212lvvagH1IVWSHa8cVpMe+UwKu5Q1h4yzuYt6Q9wPQ7Qtn5emBE0CgYB2syispMUA9GnsqQii0Xhj9nAEWaEzhOqhtrzbTs5TIkoA4Yr3BkBY5oAOdjhcRBWZuJ0XMrtaKCKqCEAtW+CYEKkGXvMOWcHbNkkeZwv8zkQ73dNRqhFnjgVn3RDNyV20uteueK23YNLkQP+KV89fnuCpdcIw9joiqq/NYuIHoQKBgB5WaZ8KH/lCA8babYEjv/pubZWXUl4plISbja17wBYZ4/bl+F1hhhMr7Wk//743dF2NG7TT6W0VTvHXr9IoaMP65uQmKgfbNpsGn294ZClGEFClz+t0KpZyTpZvL0fjibr8u+GLfkxkP5qt2wjif7KRlrKjklTTva+KAVn2cW1FAoGBAMkX9ekIwhx/7uY6ndxKl8ZMDerjr6MhV0b08hHp3RxHbYVbcpN0UKspoYvZVgHwP18xlDij8yWRE2fapwgi4m82ZmYlg0qqJmyqIU9vBB3Jow903h1KPQrkmQEZxJ/4H8yrbgVf2HT+WUfjTFgaDZRl01bI3YkydCw91/Ub9HU6 public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0lOqMuPLCVusc6szklNXQL1FHhSkEgR7An+8BllBqTsRHM4bRYosseGFCbYPn8r8FsWuMDtxp0CwTyMQR2PCbJ740DdpbE1KC6jAfZxqcBete7gP0tooJtbvnA6X4vNpG4ukhtUoN9DzNOO0eqMU0Rgyy5HjERdYEWkwTNB30i9I+nHFOSj4MGLBSxNlnuo3keeomCRgtimCx+L/K3HNo0QHTG1J7RzLVAchfQT0lu3pUJ8kB+UM6/6NG+fVyysJyRZ9gadsr4gvHHckw8oUBp2tHvqBEkEdY+rt1Mf5jppt7JUV7HAPLB/qR5jhALY2FX/8MN+lPLmb/nLQQichVQIDAQAB -broker: - publicKey: - url: https://login.elixir-czech.org/oidc/jwk # Default values available for creation of entities default: