Skip to content

Commit

Permalink
multiple passport brokers
Browse files Browse the repository at this point in the history
  • Loading branch information
leoraba committed Jun 13, 2023
1 parent 855ca11 commit b2ccb87
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 53 deletions.
13 changes: 8 additions & 5 deletions src/main/java/bio/overture/ego/controller/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -128,8 +127,11 @@ public ResponseEntity<String> 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 =
Expand All @@ -143,7 +145,8 @@ public ResponseEntity<String> user(
ProviderType.resolveProviderType(
authentication.getAuthorizedClientRegistrationId()))
.build(),
passportJwtToken);
passportJwtToken,
authentication.getAuthorizedClientRegistrationId());

val outgoingRefreshContext = refreshContextService.createInitialRefreshContext(token);
val cookie =
Expand Down
50 changes: 21 additions & 29 deletions src/main/java/bio/overture/ego/service/PassportService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(
Expand All @@ -74,14 +67,14 @@ public PassportService(
this.visaPermissionService = visaPermissionService;
}

public List<VisaPermission> getPermissions(String authToken)
public List<VisaPermission> 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<PassportVisa> visas = getVisas(parsedPassport);
List<PassportVisa> visas = getVisas(parsedPassport, providerType);
// Fetches visa permissions for extracted visas
List<VisaPermission> visaPermissions = getVisaPermissions(visas);
// removes deduplicates from visaPermissions
Expand All @@ -90,22 +83,22 @@ public List<VisaPermission> 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<PassportVisa> getVisas(Passport passport) {
private List<PassportVisa> getVisas(Passport passport, @NonNull String providerType) {
List<PassportVisa> 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);
Expand Down Expand Up @@ -134,8 +127,8 @@ private List<VisaPermission> getVisaPermissions(List<PassportVisa> visas) {
return visaPermissions;
}

public Set<Scope> extractScopes(@NonNull String passportJwtToken) throws ParseException, JwkException, JsonProcessingException {
val resolvedPermissions = getPermissions(passportJwtToken);
public Set<Scope> 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());
Expand All @@ -162,19 +155,18 @@ private List<VisaPermission> deDupeVisaPermissions(List<VisaPermission> 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,
Expand All @@ -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()
Expand All @@ -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;
Expand Down
16 changes: 8 additions & 8 deletions src/main/java/bio/overture/ego/service/TokenService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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<String> scopes = extractExplicitScopes(u);

if (passportJwtToken != null){
Set<String> scopesFromVisas = extractExplicitScopes(passportJwtToken);
if (passportJwtToken != null && providerType != null){
Set<String> scopesFromVisas = extractExplicitScopes(passportJwtToken, providerType);
scopes = mergeScopes(scopes, scopesFromVisas);
}

Expand Down Expand Up @@ -601,8 +601,8 @@ private static Set<String> extractExplicitScopes(Application a) {
}

@SneakyThrows
private Set<String> extractExplicitScopes(String passportJwtToken){
return mapToSet(explicitScopes(passportService.extractScopes(passportJwtToken)), Scope::toString);
private Set<String> extractExplicitScopes(String passportJwtToken, String providerType){
return mapToSet(explicitScopes(passportService.extractScopes(passportJwtToken, providerType)), Scope::toString);
}

private Set<String> mergeScopes(Set<String> scopeSet, Set<String> scopeSetAdditional){
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/bio/overture/ego/service/VisaService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
15 changes: 9 additions & 6 deletions src/main/java/bio/overture/ego/utils/CacheUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<String, Jwk> getPassportBrokerPublicKey() throws JsonProcessingException {
public Map<String, Jwk> getPassportBrokerPublicKey(String providerType) {
ResponseEntity<Map<String, List>> response;
Map<String, Jwk> jwkMap = new HashMap();

val clientRegistration = clientRegistrationRepository.findByRegistrationId(providerType);
try {
restTemplate = new RestTemplate();
response =
restTemplate.exchange(
brokerPublicKeyUrl,
clientRegistration.getProviderDetails().getJwkSetUri(),
GET,
null,
new ParameterizedTypeReference<Map<String, List>>() {});
Expand Down
5 changes: 2 additions & 3 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ spring:
- openid
- email
- profile

# Passport brokers must have ga4gh_passport_v1 scope
passport:
clientId: ego-client
clientSecret:
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit b2ccb87

Please sign in to comment.