Search in sources :

Example 1 with AccessModuleStatus

use of org.pmiops.workbench.model.AccessModuleStatus in project workbench by all-of-us.

the class AccessModuleServiceTest method testGetClientAccessModuleStatus.

@Test
public void testGetClientAccessModuleStatus() {
    Instant now = Instant.ofEpochMilli(FakeClockConfiguration.NOW_TIME);
    long expiryDays = 365L;
    config.access.renewal.expiryDays = expiryDays;
    DbAccessModule twoFactorAuthModule = accessModuleDao.findOneByName(DbAccessModuleName.TWO_FACTOR_AUTH).get();
    DbAccessModule rtTrainingModule = accessModuleDao.findOneByName(DbAccessModuleName.RT_COMPLIANCE_TRAINING).get();
    DbAccessModule profileConfirmModule = accessModuleDao.findOneByName(DbAccessModuleName.PROFILE_CONFIRMATION).get();
    DbAccessModule publicationModule = accessModuleDao.findOneByName(DbAccessModuleName.PUBLICATION_CONFIRMATION).get();
    DbAccessModule ducc = accessModuleDao.findOneByName(DbAccessModuleName.DATA_USER_CODE_OF_CONDUCT).get();
    // 2FA module: Module is not expirable, so no expiration date present.
    Timestamp twoFactorCompletionTime = Timestamp.from(now.minus(expiryDays + 10, ChronoUnit.DAYS));
    DbUserAccessModule twoFactorAuthUserAccessModule = new DbUserAccessModule().setAccessModule(twoFactorAuthModule).setUser(user).setCompletionTime(twoFactorCompletionTime);
    AccessModuleStatus expected2FAModuleStatus = new AccessModuleStatus().moduleName(AccessModule.TWO_FACTOR_AUTH).completionEpochMillis(twoFactorCompletionTime.getTime());
    // RT Training module: Completion time + expiryDays is 10 days ahead current time, but the
    // module was bypassed, so no expiration time.
    Timestamp rtTrainingCompletionTime = Timestamp.from(now.minus(expiryDays + 10, ChronoUnit.DAYS));
    Timestamp rtTrainingBypassTime = Timestamp.from(now);
    DbUserAccessModule rtTrainingAccessModule = new DbUserAccessModule().setAccessModule(rtTrainingModule).setUser(user).setBypassTime(rtTrainingBypassTime).setCompletionTime(rtTrainingCompletionTime);
    AccessModuleStatus expectedRtTrainingModuleStatus = new AccessModuleStatus().moduleName(AccessModule.COMPLIANCE_TRAINING).completionEpochMillis(rtTrainingCompletionTime.getTime()).bypassEpochMillis(rtTrainingBypassTime.getTime());
    // Profile module: Completion time + expiryDays is 10 days before current time,
    // this module has expired for 10 days.
    Timestamp profileCompletionTime = Timestamp.from(now.minus(expiryDays + 10, ChronoUnit.DAYS));
    // It's bit wired that use the production code to extract the expiration time, but that is a
    // simple calculation, and even we wrote our own in test, it would just be the same code.
    Timestamp expectedProfileExpirationTime = deriveExpirationTimestamp(profileCompletionTime, expiryDays);
    DbUserAccessModule profileAccessModule = new DbUserAccessModule().setAccessModule(profileConfirmModule).setUser(user).setCompletionTime(profileCompletionTime);
    AccessModuleStatus expectedProfileModuleStatus = new AccessModuleStatus().moduleName(AccessModule.PROFILE_CONFIRMATION).completionEpochMillis(profileCompletionTime.getTime()).expirationEpochMillis(expectedProfileExpirationTime.getTime());
    // Publication module: Completion time + expiryDays is 10 days after current time, this module
    // expired for 10 days.
    Timestamp publicationCompletionTime = Timestamp.from(now.minus(expiryDays - 10, ChronoUnit.DAYS));
    Timestamp expectedPublicationExpirationTime = deriveExpirationTimestamp(publicationCompletionTime, expiryDays);
    DbUserAccessModule publicationAccessModule = new DbUserAccessModule().setAccessModule(publicationModule).setUser(user).setCompletionTime(publicationCompletionTime);
    AccessModuleStatus expectedPublicationModuleStatus = new AccessModuleStatus().moduleName(AccessModule.PUBLICATION_CONFIRMATION).completionEpochMillis(publicationCompletionTime.getTime()).expirationEpochMillis(expectedPublicationExpirationTime.getTime());
    // DUCC module: Completion time not present.
    DbUserAccessModule duccAccessModule = new DbUserAccessModule().setAccessModule(ducc).setUser(user);
    AccessModuleStatus expectedDuccModuleStatus = new AccessModuleStatus().moduleName(AccessModule.DATA_USER_CODE_OF_CONDUCT);
    userAccessModuleDao.saveAll(ImmutableList.of(twoFactorAuthUserAccessModule, duccAccessModule, rtTrainingAccessModule, publicationAccessModule, profileAccessModule));
    assertThat(accessModuleService.getAccessModuleStatus(user)).containsExactly(expected2FAModuleStatus, expectedDuccModuleStatus, expectedProfileModuleStatus, expectedPublicationModuleStatus, expectedRtTrainingModuleStatus);
    assertThat(accessModuleService.getAccessModuleStatus(user, DbAccessModuleName.DATA_USER_CODE_OF_CONDUCT)).isEqualTo(Optional.of(expectedDuccModuleStatus));
}
Also used : Instant(java.time.Instant) DbAccessModule(org.pmiops.workbench.db.model.DbAccessModule) AccessModuleStatus(org.pmiops.workbench.model.AccessModuleStatus) AccessModuleServiceImpl.deriveExpirationTimestamp(org.pmiops.workbench.access.AccessModuleServiceImpl.deriveExpirationTimestamp) Timestamp(java.sql.Timestamp) DbUserAccessModule(org.pmiops.workbench.db.model.DbUserAccessModule) DataJpaTest(org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest) Test(org.junit.jupiter.api.Test)

Example 2 with AccessModuleStatus

use of org.pmiops.workbench.model.AccessModuleStatus in project workbench by all-of-us.

the class CloudTaskUserControllerTest method testSynchronizeAccess.

@Test
public void testSynchronizeAccess() {
    when(mockAccessModuleService.getAccessModuleStatus(userA, DbAccessModuleName.TWO_FACTOR_AUTH)).thenReturn(Optional.of(new AccessModuleStatus().completionEpochMillis(123L)));
    when(mockAccessModuleService.getAccessModuleStatus(userB, DbAccessModuleName.TWO_FACTOR_AUTH)).thenReturn(Optional.of(new AccessModuleStatus()));
    // kluge to prevent test NPEs on the return value of syncDuccVersionStatus()
    when(mockUserService.syncDuccVersionStatus(userA, Agent.asSystem())).thenReturn(userA);
    when(mockUserService.syncDuccVersionStatus(userB, Agent.asSystem())).thenReturn(userB);
    controller.synchronizeUserAccess(new SynchronizeUserAccessRequest().addUserIdsItem(userA.getUserId()).addUserIdsItem(userB.getUserId()));
    // Ideally we would use a real implementation of UserService and mock its external deps, but
    // unfortunately UserService is too sprawling to replicate in a unit test.
    // we sync DUCC for all users
    verify(mockUserService).syncDuccVersionStatus(userA, Agent.asSystem());
    verify(mockUserService).syncDuccVersionStatus(userB, Agent.asSystem());
    // we only sync 2FA users with completed 2FA
    verify(mockUserService).syncTwoFactorAuthStatus(userA, Agent.asSystem());
    // normally we would expect the userService sync methods to add to this count, but userService
    // is mocked so we only see the direct calls from synchronizeUserAccess(), one per user
    verify(mockUserService, times(2)).updateUserAccessTiers(any(), any());
    verifyNoMoreInteractions(mockUserService);
}
Also used : SynchronizeUserAccessRequest(org.pmiops.workbench.model.SynchronizeUserAccessRequest) AccessModuleStatus(org.pmiops.workbench.model.AccessModuleStatus) DataJpaTest(org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest) Test(org.junit.jupiter.api.Test)

Example 3 with AccessModuleStatus

use of org.pmiops.workbench.model.AccessModuleStatus in project workbench by all-of-us.

the class CloudTaskUserController method synchronizeUserAccess.

@Override
public ResponseEntity<Void> synchronizeUserAccess(SynchronizeUserAccessRequest request) {
    int errorCount = 0;
    for (long userId : request.getUserIds()) {
        DbUser user = userDao.findUserByUserId(userId);
        try {
            user = userService.syncDuccVersionStatus(user, Agent.asSystem());
            // bother checking them either.
            if (!user.getDisabled()) {
                Optional<AccessModuleStatus> status = accessModuleService.getAccessModuleStatus(user, DbAccessModuleName.TWO_FACTOR_AUTH);
                if (status.isPresent() && status.get().getCompletionEpochMillis() != null) {
                    user = userService.syncTwoFactorAuthStatus(user, Agent.asSystem());
                }
            }
            // Always synchronize for consistency. Under normal operation only the passage of time
            // should result in access expiration changes here, but this is also a backstop for ensuring
            // the database is consistent with our access tier groups, e.g. due to partial system
            // failures or bugs.
            userService.updateUserAccessTiers(user, Agent.asSystem());
        } catch (WorkbenchException e) {
            log.log(Level.SEVERE, "failed to synchronize access for user " + user.getUsername(), e);
            errorCount++;
        }
    }
    if (errorCount > 0) {
        log.severe(String.format("encountered errors on %d/%d users", errorCount, request.getUserIds().size()));
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }
    log.info(String.format("successfully synchronized %d users", request.getUserIds().size()));
    return ResponseEntity.noContent().build();
}
Also used : AccessModuleStatus(org.pmiops.workbench.model.AccessModuleStatus) WorkbenchException(org.pmiops.workbench.exceptions.WorkbenchException) DbUser(org.pmiops.workbench.db.model.DbUser)

Example 4 with AccessModuleStatus

use of org.pmiops.workbench.model.AccessModuleStatus in project workbench by all-of-us.

the class UserServiceImpl method syncComplianceTrainingStatusV2.

/**
 * Updates the given user's training status from Moodle.
 *
 * <p>We can fetch Moodle data for arbitrary users since we use an API key to access Moodle,
 * rather than user-specific OAuth tokens.
 *
 * <p>Using the user's email, we can get their badges from Moodle's APIs. If the badges are marked
 * valid, we store their completion dates in the database. If they are marked invalid, we clear
 * the completion dates from the database as the user will need to complete a new training.
 */
@Override
public DbUser syncComplianceTrainingStatusV2(DbUser dbUser, Agent agent) throws org.pmiops.workbench.moodle.ApiException, NotFoundException {
    // Skip sync for service account user rows.
    if (isServiceAccount(dbUser)) {
        return dbUser;
    }
    try {
        Map<BadgeName, BadgeDetailsV2> userBadgesByName = complianceService.getUserBadgesByBadgeName(dbUser.getUsername());
        /**
         * Determine the logical completion time for this user for the given compliance access module.
         * Three logical outcomes are possible:
         *
         * <ul>
         *   <li>Incomplete or invalid training badge: empty
         *   <li>Badge has been issued for the first time, or has been reissued since we last marked
         *       the training complete: now
         *   <li>Else: existing completion time, i.e. no change
         * </ul>
         */
        Function<BadgeName, Optional<Timestamp>> determineCompletionTime = (badgeName) -> {
            Optional<BadgeDetailsV2> badge = Optional.ofNullable(userBadgesByName.get(badgeName)).filter(BadgeDetailsV2::getValid);
            if (!badge.isPresent()) {
                return Optional.empty();
            }
            if (badge.get().getLastissued() == null) {
                log.warning(String.format("badge %s is indicated as valid by Moodle, but is missing the lastissued " + "time, this is unexpected - treating this as an incomplete training", badgeName));
                return Optional.empty();
            }
            Instant badgeTime = Instant.ofEpochSecond(badge.get().getLastissued());
            Instant dbCompletionTime = accessModuleService.getAccessModuleStatus(dbUser, accessModuleNameMapper.moduleFromBadge(badgeName)).map(AccessModuleStatus::getCompletionEpochMillis).map(Instant::ofEpochMilli).orElse(Instant.EPOCH);
            if (badgeTime.isAfter(dbCompletionTime)) {
                // time in the past.
                return Optional.of(clockNow());
            }
            // No change
            return Optional.of(Timestamp.from(dbCompletionTime));
        };
        Map<DbAccessModuleName, Optional<Timestamp>> completionTimes = Arrays.stream(BadgeName.values()).collect(Collectors.toMap(accessModuleNameMapper::moduleFromBadge, determineCompletionTime));
        completionTimes.forEach((accessModuleName, timestamp) -> accessModuleService.updateCompletionTime(dbUser, accessModuleName, timestamp.orElse(null)));
        return updateUserAccessTiers(dbUser, agent);
    } catch (NumberFormatException e) {
        log.severe("Incorrect date expire format from Moodle");
        throw e;
    } catch (org.pmiops.workbench.moodle.ApiException ex) {
        if (ex.getCode() == HttpStatus.NOT_FOUND.value()) {
            log.severe(String.format("Error while querying Moodle for badges for %s: %s ", dbUser.getUsername(), ex.getMessage()));
            throw new NotFoundException(ex.getMessage());
        } else {
            log.severe(String.format("Error while syncing compliance training: %s", ex.getMessage()));
        }
        throw ex;
    }
}
Also used : UserServiceAuditor(org.pmiops.workbench.actionaudit.auditors.UserServiceAuditor) Arrays(java.util.Arrays) ObjectOptimisticLockingFailureException(org.springframework.orm.ObjectOptimisticLockingFailureException) Provider(javax.inject.Provider) Agent(org.pmiops.workbench.actionaudit.Agent) AccessModuleStatus(org.pmiops.workbench.model.AccessModuleStatus) FireCloudService(org.pmiops.workbench.firecloud.FireCloudService) MessagingException(javax.mail.MessagingException) Autowired(org.springframework.beans.factory.annotation.Autowired) CONTROLLED_TIER_SHORT_NAME(org.pmiops.workbench.access.AccessTierService.CONTROLLED_TIER_SHORT_NAME) Random(java.util.Random) Degree(org.pmiops.workbench.model.Degree) DirectoryService(org.pmiops.workbench.google.DirectoryService) DbUserTermsOfService(org.pmiops.workbench.db.model.DbUserTermsOfService) Authority(org.pmiops.workbench.model.Authority) FirecloudNihStatus(org.pmiops.workbench.firecloud.model.FirecloudNihStatus) GenericJDBCException(org.hibernate.exception.GenericJDBCException) DataIntegrityViolationException(org.springframework.dao.DataIntegrityViolationException) Map(java.util.Map) InstitutionService(org.pmiops.workbench.institution.InstitutionService) AccessBypassRequest(org.pmiops.workbench.model.AccessBypassRequest) Sort(org.springframework.data.domain.Sort) BadgeDetailsV2(org.pmiops.workbench.moodle.model.BadgeDetailsV2) DbVerifiedInstitutionalAffiliation(org.pmiops.workbench.db.model.DbVerifiedInstitutionalAffiliation) GaugeDataCollector(org.pmiops.workbench.monitoring.GaugeDataCollector) Timestamp(java.sql.Timestamp) Collection(java.util.Collection) Set(java.util.Set) ConflictException(org.pmiops.workbench.exceptions.ConflictException) Instant(java.time.Instant) Logger(java.util.logging.Logger) Collectors(java.util.stream.Collectors) List(java.util.List) DbAddress(org.pmiops.workbench.db.model.DbAddress) DbDemographicSurvey(org.pmiops.workbench.db.model.DbDemographicSurvey) DbAccessTier(org.pmiops.workbench.db.model.DbAccessTier) Optional(java.util.Optional) AccessTierService(org.pmiops.workbench.access.AccessTierService) DbUser(org.pmiops.workbench.db.model.DbUser) REQUIRED_MODULES_FOR_CONTROLLED_TIER(org.pmiops.workbench.access.AccessUtils.REQUIRED_MODULES_FOR_CONTROLLED_TIER) ApiException(org.pmiops.workbench.firecloud.ApiException) ComplianceService(org.pmiops.workbench.compliance.ComplianceService) BadgeName(org.pmiops.workbench.compliance.ComplianceService.BadgeName) DbAccessModuleName(org.pmiops.workbench.db.model.DbAccessModule.DbAccessModuleName) Function(java.util.function.Function) Level(java.util.logging.Level) JpaSystemException(org.springframework.orm.jpa.JpaSystemException) ImmutableList(com.google.common.collect.ImmutableList) REGISTERED_TIER_SHORT_NAME(org.pmiops.workbench.access.AccessTierService.REGISTERED_TIER_SHORT_NAME) Service(org.springframework.stereotype.Service) Nonnull(javax.annotation.Nonnull) BadRequestException(org.pmiops.workbench.exceptions.BadRequestException) Institution(org.pmiops.workbench.model.Institution) MeasurementBundle(org.pmiops.workbench.monitoring.MeasurementBundle) Lists(org.javers.common.collections.Lists) AccessModuleNameMapper(org.pmiops.workbench.access.AccessModuleNameMapper) MetricLabel(org.pmiops.workbench.monitoring.labels.MetricLabel) GaugeMetric(org.pmiops.workbench.monitoring.views.GaugeMetric) TimeUnit(java.util.concurrent.TimeUnit) AccessModuleService(org.pmiops.workbench.access.AccessModuleService) DbUserCodeOfConductAgreement(org.pmiops.workbench.db.model.DbUserCodeOfConductAgreement) HttpStatus(org.springframework.http.HttpStatus) ServerErrorException(org.pmiops.workbench.exceptions.ServerErrorException) WorkbenchConfig(org.pmiops.workbench.config.WorkbenchConfig) MailService(org.pmiops.workbench.mail.MailService) REQUIRED_MODULES_FOR_REGISTERED_TIER(org.pmiops.workbench.access.AccessUtils.REQUIRED_MODULES_FOR_REGISTERED_TIER) NotFoundException(org.pmiops.workbench.exceptions.NotFoundException) Clock(java.time.Clock) Userinfo(com.google.api.services.oauth2.model.Userinfo) Collections(java.util.Collections) Transactional(org.springframework.transaction.annotation.Transactional) DbAccessModuleName(org.pmiops.workbench.db.model.DbAccessModule.DbAccessModuleName) Optional(java.util.Optional) Instant(java.time.Instant) NotFoundException(org.pmiops.workbench.exceptions.NotFoundException) BadgeName(org.pmiops.workbench.compliance.ComplianceService.BadgeName) BadgeDetailsV2(org.pmiops.workbench.moodle.model.BadgeDetailsV2)

Example 5 with AccessModuleStatus

use of org.pmiops.workbench.model.AccessModuleStatus in project workbench by all-of-us.

the class UserServiceImpl method setEraCommonsStatus.

/**
 * Updates the given user's eraCommons-related fields with the NihStatus object returned from FC.
 *
 * <p>This method saves the updated user object to the database and returns it.
 */
private DbUser setEraCommonsStatus(DbUser targetUser, FirecloudNihStatus nihStatus, Agent agent) {
    Timestamp now = clockNow();
    return updateUserWithRetries(user -> {
        if (nihStatus != null) {
            Timestamp eraCommonsCompletionTime = accessModuleService.getAccessModuleStatus(user, DbAccessModuleName.ERA_COMMONS).map(AccessModuleStatus::getCompletionEpochMillis).map(Timestamp::new).orElse(null);
            Timestamp nihLinkExpireTime = Timestamp.from(Instant.ofEpochSecond(nihStatus.getLinkExpireTime()));
            if (nihStatus.getLinkedNihUsername() == null) {
                // If FireCloud says we have no NIH link, always clear the completion time.
                eraCommonsCompletionTime = null;
            } else if (!nihLinkExpireTime.equals(user.getEraCommonsLinkExpireTime())) {
                // If the link expiration time has changed, we treat this as a "new" completion of the
                // access requirement.
                eraCommonsCompletionTime = now;
            } else if (nihStatus.getLinkedNihUsername() != null && !nihStatus.getLinkedNihUsername().equals(user.getEraCommonsLinkedNihUsername())) {
                // If the linked username has changed, we treat this as a new completion time.
                eraCommonsCompletionTime = now;
            } else if (eraCommonsCompletionTime == null) {
                // If the user hasn't yet completed this access requirement, set the time to now.
                eraCommonsCompletionTime = now;
            }
            user.setEraCommonsLinkedNihUsername(nihStatus.getLinkedNihUsername());
            user.setEraCommonsLinkExpireTime(nihLinkExpireTime);
            accessModuleService.updateCompletionTime(user, DbAccessModuleName.ERA_COMMONS, eraCommonsCompletionTime);
        } else {
            user.setEraCommonsLinkedNihUsername(null);
            user.setEraCommonsLinkExpireTime(null);
            accessModuleService.updateCompletionTime(user, DbAccessModuleName.ERA_COMMONS, null);
        }
        return user;
    }, targetUser, agent);
}
Also used : AccessModuleStatus(org.pmiops.workbench.model.AccessModuleStatus) Timestamp(java.sql.Timestamp)

Aggregations

AccessModuleStatus (org.pmiops.workbench.model.AccessModuleStatus)10 Timestamp (java.sql.Timestamp)7 Test (org.junit.jupiter.api.Test)5 DbUser (org.pmiops.workbench.db.model.DbUser)4 Instant (java.time.Instant)3 Optional (java.util.Optional)3 Logger (java.util.logging.Logger)3 Provider (javax.inject.Provider)3 AccessModuleService (org.pmiops.workbench.access.AccessModuleService)3 DbAccessModule (org.pmiops.workbench.db.model.DbAccessModule)3 DbUserAccessModule (org.pmiops.workbench.db.model.DbUserAccessModule)3 Clock (java.time.Clock)2 List (java.util.List)2 Level (java.util.logging.Level)2 Nonnull (javax.annotation.Nonnull)2 AccessModuleServiceImpl.deriveExpirationTimestamp (org.pmiops.workbench.access.AccessModuleServiceImpl.deriveExpirationTimestamp)2 AccessTierService (org.pmiops.workbench.access.AccessTierService)2 Agent (org.pmiops.workbench.actionaudit.Agent)2 UserService (org.pmiops.workbench.db.dao.UserService)2 Autowired (org.springframework.beans.factory.annotation.Autowired)2