Search in sources :

Example 1 with ConsentRequiredException

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);
}
Also used : App(org.sagebionetworks.bridge.models.apps.App) ConsentRequiredException(org.sagebionetworks.bridge.exceptions.ConsentRequiredException) UserSession(org.sagebionetworks.bridge.models.accounts.UserSession) OAuthAuthorizationToken(org.sagebionetworks.bridge.models.oauth.OAuthAuthorizationToken) CriteriaContext(org.sagebionetworks.bridge.models.CriteriaContext) PostMapping(org.springframework.web.bind.annotation.PostMapping)

Example 2 with ConsentRequiredException

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);
    }
}
Also used : ConsentRequiredException(org.sagebionetworks.bridge.exceptions.ConsentRequiredException) StudyParticipant(org.sagebionetworks.bridge.models.accounts.StudyParticipant) Test(org.testng.annotations.Test)

Example 3 with ConsentRequiredException

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);
}
Also used : InOrder(org.mockito.InOrder) ConsentRequiredException(org.sagebionetworks.bridge.exceptions.ConsentRequiredException) UserSession(org.sagebionetworks.bridge.models.accounts.UserSession) Enrollment(org.sagebionetworks.bridge.models.studies.Enrollment) CriteriaContext(org.sagebionetworks.bridge.models.CriteriaContext) Test(org.testng.annotations.Test)

Example 4 with ConsentRequiredException

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;
}
Also used : Account(org.sagebionetworks.bridge.models.accounts.Account) ConsentRequiredException(org.sagebionetworks.bridge.exceptions.ConsentRequiredException) UserSession(org.sagebionetworks.bridge.models.accounts.UserSession)

Example 5 with ConsentRequiredException

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;
}
Also used : App(org.sagebionetworks.bridge.models.apps.App) Account(org.sagebionetworks.bridge.models.accounts.Account) AccountId(org.sagebionetworks.bridge.models.accounts.AccountId) AuthenticationFailedException(org.sagebionetworks.bridge.exceptions.AuthenticationFailedException) ConsentRequiredException(org.sagebionetworks.bridge.exceptions.ConsentRequiredException) UserSession(org.sagebionetworks.bridge.models.accounts.UserSession) EntityNotFoundException(org.sagebionetworks.bridge.exceptions.EntityNotFoundException) CacheKey(org.sagebionetworks.bridge.cache.CacheKey) AccountDisabledException(org.sagebionetworks.bridge.exceptions.AccountDisabledException)

Aggregations

ConsentRequiredException (org.sagebionetworks.bridge.exceptions.ConsentRequiredException)23 UserSession (org.sagebionetworks.bridge.models.accounts.UserSession)13 Test (org.testng.annotations.Test)12 App (org.sagebionetworks.bridge.models.apps.App)8 CriteriaContext (org.sagebionetworks.bridge.models.CriteriaContext)7 StudyParticipant (org.sagebionetworks.bridge.models.accounts.StudyParticipant)6 SignIn (org.sagebionetworks.bridge.models.accounts.SignIn)5 PostMapping (org.springframework.web.bind.annotation.PostMapping)5 BadRequestException (org.sagebionetworks.bridge.exceptions.BadRequestException)3 Account (org.sagebionetworks.bridge.models.accounts.Account)3 JsonNode (com.fasterxml.jackson.databind.JsonNode)2 EntityNotFoundException (org.sagebionetworks.bridge.exceptions.EntityNotFoundException)2 AccountId (org.sagebionetworks.bridge.models.accounts.AccountId)2 ObjectMapper (com.fasterxml.jackson.databind.ObjectMapper)1 ArrayNode (com.fasterxml.jackson.databind.node.ArrayNode)1 ObjectNode (com.fasterxml.jackson.databind.node.ObjectNode)1 InOrder (org.mockito.InOrder)1 RequestContext (org.sagebionetworks.bridge.RequestContext)1 Roles (org.sagebionetworks.bridge.Roles)1 TestSurvey (org.sagebionetworks.bridge.TestSurvey)1