use of org.sagebionetworks.bridge.exceptions.ConsentRequiredException in project BridgeServer2 by Sage-Bionetworks.
the class AuthenticationController method oauthSignIn.
@PostMapping("/v3/auth/oauth/signIn")
public JsonNode oauthSignIn() {
OAuthAuthorizationToken token = parseJson(OAuthAuthorizationToken.class);
App app = appService.getApp(token.getAppId());
CriteriaContext context = getCriteriaContext(app.getIdentifier());
UserSession session = null;
try {
session = authenticationService.oauthSignIn(context, token);
} catch (ConsentRequiredException e) {
session = e.getUserSession();
throw e;
} finally {
updateRequestInfoFromSession(session);
}
return UserSessionInfo.toJSON(session);
}
use of org.sagebionetworks.bridge.exceptions.ConsentRequiredException in project BridgeServer2 by Sage-Bionetworks.
the class AuthenticationServiceTest method phoneSignInThrowsConsentRequired.
@Test
public void phoneSignInThrowsConsentRequired() {
// Put some stuff in participant to verify session is initialized
StudyParticipant participant = new StudyParticipant.Builder().withId(TEST_USER_ID).withEmail(RECIPIENT_EMAIL).withFirstName("Test").withLastName("Tester").build();
doReturn(participant).when(participantService).getParticipant(app, account, false);
when(cacheProvider.getObject(CACHE_KEY_PHONE_SIGNIN, String.class)).thenReturn(TOKEN_UNFORMATTED);
doReturn(UNCONSENTED_STATUS_MAP).when(consentService).getConsentStatuses(any(), any());
doReturn(Optional.of(account)).when(accountService).getAccount(SIGN_IN_WITH_PHONE.getAccountId());
try {
service.phoneSignIn(CONTEXT, SIGN_IN_WITH_PHONE);
fail("Should have thrown exception");
} catch (ConsentRequiredException e) {
verify(cacheProvider).setUserSession(e.getUserSession());
assertEquals(e.getUserSession().getConsentStatuses(), UNCONSENTED_STATUS_MAP);
}
}
use of org.sagebionetworks.bridge.exceptions.ConsentRequiredException in project BridgeServer2 by Sage-Bionetworks.
the class AuthenticationServiceTest method signInThrowsConsentRequiredException.
@Test
public void signInThrowsConsentRequiredException() {
app.setReauthenticationEnabled(true);
Enrollment en1 = Enrollment.create(TEST_APP_ID, "studyA", TEST_USER_ID);
Enrollment en2 = Enrollment.create(TEST_APP_ID, "studyB", TEST_USER_ID);
account.setReauthToken("REAUTH_TOKEN");
account.setHealthCode(HEALTH_CODE);
account.setEnrollments(ImmutableSet.of(en1, en2));
account.setId(TEST_USER_ID);
setIpAddress(IP_ADDRESS);
CriteriaContext context = new CriteriaContext.Builder().withAppId(TEST_APP_ID).withLanguages(LANGUAGES).withClientInfo(ClientInfo.fromUserAgentCache("app/13")).build();
doReturn(account).when(accountService).authenticate(app, EMAIL_PASSWORD_SIGN_IN);
doReturn(PARTICIPANT_WITH_ATTRIBUTES).when(participantService).getParticipant(app, account, false);
doReturn(UNCONSENTED_STATUS_MAP).when(consentService).getConsentStatuses(contextCaptor.capture(), any());
doReturn(REAUTH_TOKEN).when(service).generateReauthToken();
doReturn(Environment.PROD).when(config).getEnvironment();
UserSession session = null;
try {
session = service.signIn(app, context, EMAIL_PASSWORD_SIGN_IN);
fail("Should have thrown exception");
} catch (ConsentRequiredException e) {
session = e.getUserSession();
}
InOrder inOrder = Mockito.inOrder(cacheProvider, accountService);
inOrder.verify(accountService).deleteReauthToken(account);
inOrder.verify(cacheProvider).removeSessionByUserId(TEST_USER_ID);
inOrder.verify(cacheProvider).setUserSession(session);
assertEquals(session.getConsentStatuses(), UNCONSENTED_STATUS_MAP);
assertTrue(session.isAuthenticated());
assertEquals(session.getIpAddress(), IP_ADDRESS);
assertEquals(session.getSessionToken(), SESSION_TOKEN);
assertEquals(session.getInternalSessionToken(), SESSION_TOKEN);
assertEquals(session.getReauthToken(), REAUTH_TOKEN);
assertEquals(session.getEnvironment(), Environment.PROD);
assertEquals(session.getAppId(), TEST_APP_ID);
// updated context
CriteriaContext updatedContext = contextCaptor.getValue();
assertEquals(updatedContext.getHealthCode(), HEALTH_CODE);
assertEquals(updatedContext.getLanguages(), LANGUAGES);
assertEquals(updatedContext.getUserDataGroups(), DATA_GROUP_SET);
assertEquals(updatedContext.getUserStudyIds(), TestConstants.USER_STUDY_IDS);
assertEquals(updatedContext.getUserId(), TEST_USER_ID);
verify(accountSecretDao).createSecret(AccountSecretType.REAUTH, TEST_USER_ID, REAUTH_TOKEN);
}
use of org.sagebionetworks.bridge.exceptions.ConsentRequiredException in project BridgeServer2 by Sage-Bionetworks.
the class AuthenticationService method reauthenticate.
public UserSession reauthenticate(App app, CriteriaContext context, SignIn signIn) throws EntityNotFoundException {
checkNotNull(app);
checkNotNull(context);
checkNotNull(signIn);
Validate.entityThrowingException(SignInValidator.REAUTH_SIGNIN, signIn);
// Reauth token is a 21-character alphanumeric (upper, lower, numbers), generated by a SecureRandom. This is
// 125 bits of entropy. To see if apps are sending the old reauth token after successfully reauthenticating, we
// will call .hashCode() on the reauth token, mod 1000, and log it (about 10 bits). Even if attackers steal
// this hash-mod from the logs, they still need to determine the remaining 115 bits of the reauth token (about
// 19 alphanumeric characters worth), and they have 5 minutes to do it before the grace period expires.
//
// This is effectively equivalent to the app submitting an token identification token and a 19-character reauth
// token, which is still reasonably secure.
int reauthHashMod = signIn.getReauthToken().hashCode() % 1000;
LOG.debug("Reauth token hash-mod " + reauthHashMod + " submitted in request " + RequestContext.get().getId());
Account account = accountService.reauthenticate(app, signIn);
UserSession session = getSessionFromAccount(app, context, account);
// If session exists, preserve the session token. Reauthenticating before the session times out will not
// refresh the token or change the timeout, but it is harmless. At the time the session is set to
// time out, it will still time out and the client will need to reauthenticate again.
UserSession existing = cacheProvider.getUserSessionByUserId(account.getId());
if (existing != null) {
session.setSessionToken(existing.getSessionToken());
session.setInternalSessionToken(existing.getInternalSessionToken());
}
cacheProvider.setUserSession(session);
if (!session.doesConsent() && !session.isInRole(ADMINISTRATIVE_ROLES)) {
throw new ConsentRequiredException(session);
}
return session;
}
use of org.sagebionetworks.bridge.exceptions.ConsentRequiredException in project BridgeServer2 by Sage-Bionetworks.
the class AuthenticationService method channelSignIn.
private UserSession channelSignIn(ChannelType channelType, CriteriaContext context, SignIn signIn, Validator validator) {
Validate.entityThrowingException(validator, signIn);
// Verify sign-in token.
CacheKey cacheKey = getCacheKeyForChannelSignIn(channelType, signIn);
String storedToken = cacheProvider.getObject(cacheKey, String.class);
String unformattedSubmittedToken = signIn.getToken().replaceAll("[-\\s]", "");
if (storedToken == null || !storedToken.equals(unformattedSubmittedToken)) {
throw new AuthenticationFailedException();
}
AccountId accountId = signIn.getAccountId();
Account account = accountService.getAccount(accountId).orElseThrow(() -> new EntityNotFoundException(Account.class));
if (account.getStatus() == AccountStatus.DISABLED) {
throw new AccountDisabledException();
}
// Update account state before we create the session, so it's accurate...
accountService.verifyChannel(channelType, account);
// Check if we have a cached session for this sign-in token.
UserSession cachedSession = null;
CacheKey sessionCacheKey = CacheKey.channelSignInToSessionToken(storedToken);
String cachedSessionToken = cacheProvider.getObject(sessionCacheKey, String.class);
if (cachedSessionToken != null) {
cachedSession = cacheProvider.getUserSession(cachedSessionToken);
}
UserSession session;
if (cachedSession != null) {
// If we have a cached session, then just use that session.
session = cachedSession;
} else {
// We don't have a cached session. This is a new sign-in. Clear all old sessions for security reasons.
// Then, create a new session.
clearSession(context.getAppId(), account);
App app = appService.getApp(signIn.getAppId());
session = getSessionFromAccount(app, context, account);
// Check intent to participate.
if (!session.doesConsent() && intentService.registerIntentToParticipate(app, account)) {
account = accountService.getAccount(accountId).orElseThrow(() -> new EntityNotFoundException(Account.class));
session = getSessionFromAccount(app, context, account);
}
cacheProvider.setUserSession(session);
// Set the sign-in token cache key to the 5 minute grace period. This means that if the app successfully
// signs in, but there's a network glitch and they don't get the session token, they can try again with the
// same token.
cacheProvider.setExpiration(cacheKey, SIGNIN_GRACE_PERIOD_SECONDS);
// Cache the session token under the sign-in token, so that if the same token comes in during the grace
// period, we can return the same session with the same token.
cacheProvider.setObject(sessionCacheKey, session.getSessionToken(), SIGNIN_GRACE_PERIOD_SECONDS);
}
if (!session.doesConsent() && !session.isInRole(ADMINISTRATIVE_ROLES)) {
throw new ConsentRequiredException(session);
}
return session;
}
Aggregations