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));
}
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);
}
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();
}
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;
}
}
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);
}
Aggregations