Skip to content

Commit 34831a4

Browse files
committed
Passport refresh token
Ego issue #723
1 parent 002742f commit 34831a4

12 files changed

+310
-113
lines changed

src/main/java/bio/overture/ego/config/OAuth2AccessTokenResponseConverterWithDefaults.java

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ public class OAuth2AccessTokenResponseConverterWithDefaults
2020
OAuth2ParameterNames.ACCESS_TOKEN,
2121
OAuth2ParameterNames.TOKEN_TYPE,
2222
OAuth2ParameterNames.EXPIRES_IN,
23-
OAuth2ParameterNames.REFRESH_TOKEN,
2423
OAuth2ParameterNames.SCOPE)
2524
.collect(Collectors.toSet());
2625

src/main/java/bio/overture/ego/controller/AuthController.java

+79-38
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static bio.overture.ego.model.enums.JavaFields.REFRESH_ID;
2020
import static bio.overture.ego.utils.SwaggerConstants.AUTH_CONTROLLER;
21+
import static bio.overture.ego.utils.TypeUtils.isValidUUID;
2122
import static org.springframework.http.HttpStatus.*;
2223
import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
2324
import static org.springframework.web.bind.annotation.RequestMethod.*;
@@ -27,9 +28,7 @@
2728
import bio.overture.ego.model.exceptions.InvalidTokenException;
2829
import bio.overture.ego.provider.google.GoogleTokenService;
2930
import bio.overture.ego.security.CustomOAuth2User;
30-
import bio.overture.ego.service.PassportService;
31-
import bio.overture.ego.service.RefreshContextService;
32-
import bio.overture.ego.service.TokenService;
31+
import bio.overture.ego.service.*;
3332
import bio.overture.ego.token.IDToken;
3433
import bio.overture.ego.token.signer.TokenSigner;
3534
import bio.overture.ego.utils.Tokens;
@@ -50,6 +49,7 @@
5049
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
5150
import org.springframework.util.StringUtils;
5251
import org.springframework.web.bind.annotation.*;
52+
import org.springframework.web.client.HttpClientErrorException;
5353

5454
@Slf4j
5555
@RestController
@@ -65,6 +65,7 @@ public class AuthController {
6565
private final GoogleTokenService googleTokenService;
6666
private final TokenSigner tokenSigner;
6767
private final RefreshContextService refreshContextService;
68+
private final UserService userService;
6869
private final String GA4GH_PASSPORT_SCOPE = "ga4gh_passport_v1";
6970

7071
@Autowired
@@ -73,12 +74,14 @@ public AuthController(
7374
@NonNull PassportService passportService,
7475
@NonNull GoogleTokenService googleTokenService,
7576
@NonNull TokenSigner tokenSigner,
76-
@NonNull RefreshContextService refreshContextService) {
77+
@NonNull RefreshContextService refreshContextService,
78+
@NonNull UserService userService) {
7779
this.tokenService = tokenService;
7880
this.passportService = passportService;
7981
this.googleTokenService = googleTokenService;
8082
this.tokenSigner = tokenSigner;
8183
this.refreshContextService = refreshContextService;
84+
this.userService = userService;
8285
}
8386

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

129-
val user = (CustomOAuth2User) authentication.getPrincipal();
132+
val oAuth2User = (CustomOAuth2User) authentication.getPrincipal();
130133

134+
val passportJwtToken =
135+
(oAuth2User.getClaim(GA4GH_PASSPORT_SCOPE) != null)
136+
? passportService.getPassportToken(
137+
authentication.getAuthorizedClientRegistrationId(), oAuth2User.getAccessToken())
138+
: null;
131139

132-
val passportJwtToken = (user.getClaim(GA4GH_PASSPORT_SCOPE) != null) ?
133-
passportService.getPassportToken(
134-
authentication.getAuthorizedClientRegistrationId(),
135-
user.getAccessToken()) :
136-
null;
140+
Optional<ProviderType> providerType =
141+
ProviderType.findIfExist(authentication.getAuthorizedClientRegistrationId());
137142

138-
Optional<ProviderType> providerType = ProviderType
139-
.findIfExist(authentication.getAuthorizedClientRegistrationId());
140-
141-
if(user.getClaim(GA4GH_PASSPORT_SCOPE) != null && providerType.isEmpty()){
143+
if (oAuth2User.getClaim(GA4GH_PASSPORT_SCOPE) != null && providerType.isEmpty()) {
142144
providerType = Optional.of(ProviderType.PASSPORT);
143145
}
144146

145-
String token =
147+
val idToken =
148+
IDToken.builder()
149+
.providerSubjectId(oAuth2User.getSubjectId())
150+
.email(oAuth2User.getEmail())
151+
.familyName(oAuth2User.getFamilyName())
152+
.givenName(oAuth2User.getGivenName())
153+
.providerType(providerType.get())
154+
.providerIssuerUri(oAuth2User.getIssuer().toString())
155+
.build();
156+
157+
val egoToken =
146158
tokenService.generateUserToken(
147-
IDToken.builder()
148-
.providerSubjectId(user.getSubjectId())
149-
.email(user.getEmail())
150-
.familyName(user.getFamilyName())
151-
.givenName(user.getGivenName())
152-
.providerType(providerType.get())
153-
.providerIssuerUri(user.getIssuer().toString())
154-
.build(),
155-
passportJwtToken,
156-
authentication.getAuthorizedClientRegistrationId());
157-
158-
val outgoingRefreshContext = refreshContextService.createInitialRefreshContext(token);
159-
val cookie =
160-
refreshContextService.createRefreshCookie(outgoingRefreshContext.getRefreshToken());
161-
response.addCookie(cookie);
159+
idToken, passportJwtToken, authentication.getAuthorizedClientRegistrationId());
160+
161+
if (oAuth2User.getClaim(GA4GH_PASSPORT_SCOPE) != null && oAuth2User.getRefreshToken() != null) {
162+
// create a cookie with passport refresh token
163+
val user = userService.getUserByToken(idToken);
164+
val outgoingRefreshContext =
165+
refreshContextService.createPassportRefreshToken(user, oAuth2User.getRefreshToken());
166+
val cookie =
167+
refreshContextService.createPassportRefreshCookie(
168+
outgoingRefreshContext, oAuth2User.getRefreshToken());
169+
response.addCookie(cookie);
170+
} else {
171+
// create a cookie with refreshId
172+
val outgoingRefreshContext = refreshContextService.createInitialRefreshContext(egoToken);
173+
val cookie =
174+
refreshContextService.createRefreshCookie(outgoingRefreshContext.getRefreshToken());
175+
response.addCookie(cookie);
176+
}
162177

163178
SecurityContextHolder.getContext().setAuthentication(null);
164-
return new ResponseEntity<>(token, OK);
179+
return new ResponseEntity<>(egoToken, OK);
165180
}
166181

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

205-
val outboundUserToken =
206-
refreshContextService.validateAndReturnNewUserToken(refreshId, currentToken);
207-
val newRefreshToken = tokenService.getTokenUserInfo(outboundUserToken).getRefreshToken();
208-
val newCookie = refreshContextService.createRefreshCookie(newRefreshToken);
209-
response.addCookie(newCookie);
219+
try {
220+
if (isValidUUID(refreshId)) {
221+
val outboundUserToken =
222+
refreshContextService.validateAndReturnNewUserToken(refreshId, currentToken);
223+
val newRefreshToken = tokenService.getTokenUserInfo(outboundUserToken).getRefreshToken();
224+
val newCookie = refreshContextService.createRefreshCookie(newRefreshToken);
225+
response.addCookie(newCookie);
226+
227+
return new ResponseEntity<>(outboundUserToken, OK);
228+
} else {
229+
230+
val user = tokenService.getTokenUserInfo(currentToken);
210231

211-
return new ResponseEntity<>(outboundUserToken, OK);
232+
val clientRegistration =
233+
passportService.getPassportClientRegistrations().get(user.getProviderIssuerUri());
234+
235+
val passportResponse =
236+
passportService.refreshToken(clientRegistration.getRegistrationId(), refreshId);
237+
238+
val egoToken = tokenService.generatePassportEgoToken(user, passportResponse.getAccess_token(), clientRegistration.getRegistrationId());
239+
240+
val outgoingRefreshContext =
241+
refreshContextService.createPassportRefreshToken(
242+
user, passportResponse.getRefresh_token());
243+
val newCookie =
244+
refreshContextService.createPassportRefreshCookie(
245+
outgoingRefreshContext, passportResponse.getRefresh_token());
246+
response.addCookie(newCookie);
247+
248+
return new ResponseEntity<>(egoToken, OK);
249+
}
250+
}catch (HttpClientErrorException e){
251+
return new ResponseEntity<>(e.getResponseBodyAsString(), e.getStatusCode());
252+
}
212253
}
213254

214255
@ExceptionHandler({InvalidTokenException.class})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package bio.overture.ego.model.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import lombok.Data;
5+
import lombok.val;
6+
7+
import java.util.Calendar;
8+
9+
@Data
10+
@JsonIgnoreProperties(ignoreUnknown = true)
11+
public class PassportRefreshToken {
12+
private String iss;
13+
private String aud;
14+
private Long exp; // in seconds
15+
private String jti;
16+
17+
public Long getSecondsUntilExpiry() {
18+
val seconds = this.exp - Calendar.getInstance().getTime().getTime() / 1000L;
19+
return seconds > 0 ? seconds : 0;
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package bio.overture.ego.model.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import lombok.Data;
5+
6+
@Data
7+
@JsonIgnoreProperties(ignoreUnknown = true)
8+
public class PassportRefreshTokenResponse {
9+
private String access_token;
10+
private String token_type;
11+
private String refresh_token;
12+
private Long expires_in;
13+
private String scope;
14+
private String id_token;
15+
}

src/main/java/bio/overture/ego/security/CustomOAuth2User.java

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class CustomOAuth2User implements OidcUser {
2424
private String email;
2525
private OAuth2User oauth2User;
2626
private String accessToken;
27+
private String refreshToken;
2728

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

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

54+
public String getRefreshToken() {return this.refreshToken; }
55+
5356
public String getSubjectId() {
5457
return oauth2User.getAttributes().containsKey(IdTokenClaimNames.SUB)
5558
? oauth2User.getAttributes().get(IdTokenClaimNames.SUB).toString()

src/main/java/bio/overture/ego/security/CustomOidc2UserInfoService.java

+11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
1616
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
1717
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
18+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
1819
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
1920
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
2021
import org.springframework.security.oauth2.core.user.OAuth2User;
@@ -47,13 +48,16 @@ public OidcUser loadUser(OidcUserRequest oAuth2UserRequest) throws OAuth2Authent
4748
.givenName(info.getOrDefault(GIVEN_NAME, "").toString())
4849
.build();
4950
}
51+
52+
val refreshToken = getRefreshToken(oAuth2UserRequest);
5053
return CustomOAuth2User.builder()
5154
.oauth2User(oidcUser)
5255
.subjectId(oidcUser.getSubject())
5356
.email(oidcUser.getEmail())
5457
.familyName(oidcUser.getFamilyName())
5558
.givenName(oidcUser.getGivenName())
5659
.accessToken(oAuth2UserRequest.getAccessToken().getTokenValue())
60+
.refreshToken(refreshToken)
5761
.build();
5862
} catch (AuthenticationException ex) {
5963
throw ex;
@@ -87,4 +91,11 @@ private RestTemplate getTemplate(OAuth2UserRequest oAuth2UserRequest) {
8791
});
8892
return restTemplate;
8993
}
94+
95+
private String getRefreshToken(OAuth2UserRequest oAuth2UserRequest) {
96+
val refreshToken =
97+
(String)
98+
oAuth2UserRequest.getAdditionalParameters().get(OAuth2ParameterNames.REFRESH_TOKEN);
99+
return refreshToken;
100+
}
90101
}

0 commit comments

Comments
 (0)