Skip to content

Commit

Permalink
Passport refresh token
Browse files Browse the repository at this point in the history
Ego issue #723
  • Loading branch information
leoraba committed Jun 29, 2023
1 parent 002742f commit 34831a4
Show file tree
Hide file tree
Showing 12 changed files with 310 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ public class OAuth2AccessTokenResponseConverterWithDefaults
OAuth2ParameterNames.ACCESS_TOKEN,
OAuth2ParameterNames.TOKEN_TYPE,
OAuth2ParameterNames.EXPIRES_IN,
OAuth2ParameterNames.REFRESH_TOKEN,
OAuth2ParameterNames.SCOPE)
.collect(Collectors.toSet());

Expand Down
117 changes: 79 additions & 38 deletions src/main/java/bio/overture/ego/controller/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static bio.overture.ego.model.enums.JavaFields.REFRESH_ID;
import static bio.overture.ego.utils.SwaggerConstants.AUTH_CONTROLLER;
import static bio.overture.ego.utils.TypeUtils.isValidUUID;
import static org.springframework.http.HttpStatus.*;
import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
import static org.springframework.web.bind.annotation.RequestMethod.*;
Expand All @@ -27,9 +28,7 @@
import bio.overture.ego.model.exceptions.InvalidTokenException;
import bio.overture.ego.provider.google.GoogleTokenService;
import bio.overture.ego.security.CustomOAuth2User;
import bio.overture.ego.service.PassportService;
import bio.overture.ego.service.RefreshContextService;
import bio.overture.ego.service.TokenService;
import bio.overture.ego.service.*;
import bio.overture.ego.token.IDToken;
import bio.overture.ego.token.signer.TokenSigner;
import bio.overture.ego.utils.Tokens;
Expand All @@ -50,6 +49,7 @@
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpClientErrorException;

@Slf4j
@RestController
Expand All @@ -65,6 +65,7 @@ public class AuthController {
private final GoogleTokenService googleTokenService;
private final TokenSigner tokenSigner;
private final RefreshContextService refreshContextService;
private final UserService userService;
private final String GA4GH_PASSPORT_SCOPE = "ga4gh_passport_v1";

@Autowired
Expand All @@ -73,12 +74,14 @@ public AuthController(
@NonNull PassportService passportService,
@NonNull GoogleTokenService googleTokenService,
@NonNull TokenSigner tokenSigner,
@NonNull RefreshContextService refreshContextService) {
@NonNull RefreshContextService refreshContextService,
@NonNull UserService userService) {
this.tokenService = tokenService;
this.passportService = passportService;
this.googleTokenService = googleTokenService;
this.tokenSigner = tokenSigner;
this.refreshContextService = refreshContextService;
this.userService = userService;
}

@RequestMapping(method = GET, value = "/google/token")
Expand Down Expand Up @@ -126,42 +129,54 @@ public ResponseEntity<String> user(
throw new RuntimeException("no user");
}

val user = (CustomOAuth2User) authentication.getPrincipal();
val oAuth2User = (CustomOAuth2User) authentication.getPrincipal();

val passportJwtToken =
(oAuth2User.getClaim(GA4GH_PASSPORT_SCOPE) != null)
? passportService.getPassportToken(
authentication.getAuthorizedClientRegistrationId(), oAuth2User.getAccessToken())
: null;

val passportJwtToken = (user.getClaim(GA4GH_PASSPORT_SCOPE) != null) ?
passportService.getPassportToken(
authentication.getAuthorizedClientRegistrationId(),
user.getAccessToken()) :
null;
Optional<ProviderType> providerType =
ProviderType.findIfExist(authentication.getAuthorizedClientRegistrationId());

Optional<ProviderType> providerType = ProviderType
.findIfExist(authentication.getAuthorizedClientRegistrationId());

if(user.getClaim(GA4GH_PASSPORT_SCOPE) != null && providerType.isEmpty()){
if (oAuth2User.getClaim(GA4GH_PASSPORT_SCOPE) != null && providerType.isEmpty()) {
providerType = Optional.of(ProviderType.PASSPORT);
}

String token =
val idToken =
IDToken.builder()
.providerSubjectId(oAuth2User.getSubjectId())
.email(oAuth2User.getEmail())
.familyName(oAuth2User.getFamilyName())
.givenName(oAuth2User.getGivenName())
.providerType(providerType.get())
.providerIssuerUri(oAuth2User.getIssuer().toString())
.build();

val egoToken =
tokenService.generateUserToken(
IDToken.builder()
.providerSubjectId(user.getSubjectId())
.email(user.getEmail())
.familyName(user.getFamilyName())
.givenName(user.getGivenName())
.providerType(providerType.get())
.providerIssuerUri(user.getIssuer().toString())
.build(),
passportJwtToken,
authentication.getAuthorizedClientRegistrationId());

val outgoingRefreshContext = refreshContextService.createInitialRefreshContext(token);
val cookie =
refreshContextService.createRefreshCookie(outgoingRefreshContext.getRefreshToken());
response.addCookie(cookie);
idToken, passportJwtToken, authentication.getAuthorizedClientRegistrationId());

if (oAuth2User.getClaim(GA4GH_PASSPORT_SCOPE) != null && oAuth2User.getRefreshToken() != null) {
// create a cookie with passport refresh token
val user = userService.getUserByToken(idToken);
val outgoingRefreshContext =
refreshContextService.createPassportRefreshToken(user, oAuth2User.getRefreshToken());
val cookie =
refreshContextService.createPassportRefreshCookie(
outgoingRefreshContext, oAuth2User.getRefreshToken());
response.addCookie(cookie);
} else {
// create a cookie with refreshId
val outgoingRefreshContext = refreshContextService.createInitialRefreshContext(egoToken);
val cookie =
refreshContextService.createRefreshCookie(outgoingRefreshContext.getRefreshToken());
response.addCookie(cookie);
}

SecurityContextHolder.getContext().setAuthentication(null);
return new ResponseEntity<>(token, OK);
return new ResponseEntity<>(egoToken, OK);
}

@RequestMapping(
Expand Down Expand Up @@ -200,15 +215,41 @@ public ResponseEntity<String> refreshEgoToken(
return new ResponseEntity<>("Please login", UNAUTHORIZED);
}
val currentToken = Tokens.removeTokenPrefix(authorization, TOKEN_PREFIX);
// TODO: [anncatton] validate jwt before proceeding to service call.

val outboundUserToken =
refreshContextService.validateAndReturnNewUserToken(refreshId, currentToken);
val newRefreshToken = tokenService.getTokenUserInfo(outboundUserToken).getRefreshToken();
val newCookie = refreshContextService.createRefreshCookie(newRefreshToken);
response.addCookie(newCookie);
try {
if (isValidUUID(refreshId)) {
val outboundUserToken =
refreshContextService.validateAndReturnNewUserToken(refreshId, currentToken);
val newRefreshToken = tokenService.getTokenUserInfo(outboundUserToken).getRefreshToken();
val newCookie = refreshContextService.createRefreshCookie(newRefreshToken);
response.addCookie(newCookie);

return new ResponseEntity<>(outboundUserToken, OK);
} else {

val user = tokenService.getTokenUserInfo(currentToken);

return new ResponseEntity<>(outboundUserToken, OK);
val clientRegistration =
passportService.getPassportClientRegistrations().get(user.getProviderIssuerUri());

val passportResponse =
passportService.refreshToken(clientRegistration.getRegistrationId(), refreshId);

val egoToken = tokenService.generatePassportEgoToken(user, passportResponse.getAccess_token(), clientRegistration.getRegistrationId());

val outgoingRefreshContext =
refreshContextService.createPassportRefreshToken(
user, passportResponse.getRefresh_token());
val newCookie =
refreshContextService.createPassportRefreshCookie(
outgoingRefreshContext, passportResponse.getRefresh_token());
response.addCookie(newCookie);

return new ResponseEntity<>(egoToken, OK);
}
}catch (HttpClientErrorException e){
return new ResponseEntity<>(e.getResponseBodyAsString(), e.getStatusCode());
}
}

@ExceptionHandler({InvalidTokenException.class})
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/bio/overture/ego/model/dto/PassportRefreshToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package bio.overture.ego.model.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.val;

import java.util.Calendar;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class PassportRefreshToken {
private String iss;
private String aud;
private Long exp; // in seconds
private String jti;

public Long getSecondsUntilExpiry() {
val seconds = this.exp - Calendar.getInstance().getTime().getTime() / 1000L;
return seconds > 0 ? seconds : 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package bio.overture.ego.model.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class PassportRefreshTokenResponse {
private String access_token;
private String token_type;
private String refresh_token;
private Long expires_in;
private String scope;
private String id_token;
}
3 changes: 3 additions & 0 deletions src/main/java/bio/overture/ego/security/CustomOAuth2User.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class CustomOAuth2User implements OidcUser {
private String email;
private OAuth2User oauth2User;
private String accessToken;
private String refreshToken;

@Override
public Map<String, Object> getAttributes() {
Expand All @@ -50,6 +51,8 @@ public String getFamilyName() {

public String getAccessToken() { return this.accessToken; }

public String getRefreshToken() {return this.refreshToken; }

public String getSubjectId() {
return oauth2User.getAttributes().containsKey(IdTokenClaimNames.SUB)
? oauth2User.getAttributes().get(IdTokenClaimNames.SUB).toString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
Expand Down Expand Up @@ -47,13 +48,16 @@ public OidcUser loadUser(OidcUserRequest oAuth2UserRequest) throws OAuth2Authent
.givenName(info.getOrDefault(GIVEN_NAME, "").toString())
.build();
}

val refreshToken = getRefreshToken(oAuth2UserRequest);
return CustomOAuth2User.builder()
.oauth2User(oidcUser)
.subjectId(oidcUser.getSubject())
.email(oidcUser.getEmail())
.familyName(oidcUser.getFamilyName())
.givenName(oidcUser.getGivenName())
.accessToken(oAuth2UserRequest.getAccessToken().getTokenValue())
.refreshToken(refreshToken)
.build();
} catch (AuthenticationException ex) {
throw ex;
Expand Down Expand Up @@ -87,4 +91,11 @@ private RestTemplate getTemplate(OAuth2UserRequest oAuth2UserRequest) {
});
return restTemplate;
}

private String getRefreshToken(OAuth2UserRequest oAuth2UserRequest) {
val refreshToken =
(String)
oAuth2UserRequest.getAdditionalParameters().get(OAuth2ParameterNames.REFRESH_TOKEN);
return refreshToken;
}
}
Loading

0 comments on commit 34831a4

Please sign in to comment.