Skip to content

Commit

Permalink
Merge pull request #1766 from kekey1/OCD-4712
Browse files Browse the repository at this point in the history
OCD-4712: Adjust Cognito user management during developer split/join
  • Loading branch information
kekey1 authored Jan 23, 2025
2 parents 8fe7a1b + 1830ade commit 7410a9c
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 20 deletions.
4 changes: 2 additions & 2 deletions chpl/chpl-resources/src/main/resources/jobs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@
<durability>true</durability>
<recover>false</recover>
</job>

<job>
<name>cognitoMassRequirePasswordChangeJob</name>
<group>systemJobs</group>
Expand All @@ -912,7 +912,7 @@
<durability>true</durability>
<recover>false</recover>
</job>

<job>
<name>updateSedFriendlyIdsJob</name>
<group>systemJobs</group>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import java.io.Serializable;

import gov.healthit.chpl.dto.OrganizationDTO;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@Builder
@Data
public class Organization implements Serializable {
private static final long serialVersionUID = -5910873076481736684L;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,8 @@ public class User extends Person implements Serializable {
private String hash;
private String status;

public boolean hasRole(String authority) {
return role.equals(authority);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.ff4j.FF4j;
import org.quartz.JobDataMap;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -25,6 +26,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;

import gov.healthit.chpl.FeatureList;
import gov.healthit.chpl.caching.CacheNames;
import gov.healthit.chpl.caching.ListingSearchCacheRefresh;
import gov.healthit.chpl.dao.CertifiedProductDAO;
Expand All @@ -39,6 +41,7 @@
import gov.healthit.chpl.domain.Developer;
import gov.healthit.chpl.domain.DeveloperStatusEvent;
import gov.healthit.chpl.domain.IdNamePair;
import gov.healthit.chpl.domain.Organization;
import gov.healthit.chpl.domain.Product;
import gov.healthit.chpl.domain.activity.ActivityConcept;
import gov.healthit.chpl.domain.auth.User;
Expand Down Expand Up @@ -70,6 +73,7 @@
import gov.healthit.chpl.scheduler.job.developer.messaging.MessageDevelopersJob;
import gov.healthit.chpl.sharedstore.listing.ListingStoreRemove;
import gov.healthit.chpl.sharedstore.listing.RemoveBy;
import gov.healthit.chpl.user.cognito.CognitoUserManager;
import gov.healthit.chpl.util.AuthUtil;
import gov.healthit.chpl.util.ChplProductNumberUtil;
import gov.healthit.chpl.util.ErrorMessageUtil;
Expand All @@ -94,7 +98,9 @@ public class DeveloperManager extends SecuredManager {
private DeveloperValidationFactory developerValidationFactory;
private SearchRequestValidator developerSearchRequestValidator;
private SearchRequestNormalizer developerSearchRequestNormalizer;
private CognitoUserManager cognitoUserManager;
private SchedulerManager schedulerManager;
private FF4j ff4j;

@Autowired
@SuppressWarnings("checkstyle:parameternumber")
Expand All @@ -104,7 +110,9 @@ public DeveloperManager(DeveloperDAO developerDao, ProductManager productManager
ActivityManager activityManager, ErrorMessageUtil msgUtil, ResourcePermissionsFactory resourcePermissionsFactory,
DeveloperValidationFactory developerValidationFactory,
@Qualifier("developerSearchRequestValidator") SearchRequestValidator developerSearchRequestValidator,
SchedulerManager schedulerManager) {
CognitoUserManager cognitoUserManager,
SchedulerManager schedulerManager,
FF4j ff4j) {
this.developerDao = developerDao;
this.productManager = productManager;
this.versionManager = versionManager;
Expand All @@ -118,7 +126,9 @@ public DeveloperManager(DeveloperDAO developerDao, ProductManager productManager
this.developerValidationFactory = developerValidationFactory;
this.developerSearchRequestValidator = developerSearchRequestValidator;
this.developerSearchRequestNormalizer = new SearchRequestNormalizer();
this.cognitoUserManager = cognitoUserManager;
this.schedulerManager = schedulerManager;
this.ff4j = ff4j;
}

@Transactional(readOnly = true)
Expand Down Expand Up @@ -340,6 +350,54 @@ public Long create(Developer developer) throws ValidationException, EntityCreati
return developerId;
}

@PreAuthorize("@permissions.hasAccess(T(gov.healthit.chpl.permissions.Permissions).DEVELOPER, "
+ "T(gov.healthit.chpl.permissions.domains.DeveloperDomainPermissions).DELETE)")
@Transactional(readOnly = false)
@CacheEvict(value = {
CacheNames.ALL_DEVELOPERS,
CacheNames.ALL_DEVELOPERS_INCLUDING_DELETED,
CacheNames.COLLECTIONS_DEVELOPERS,
CacheNames.GET_DECERTIFIED_DEVELOPERS,
CacheNames.QUESTIONABLE_ACTIVITIES,
CacheNames.COLLECTIONS_LISTINGS,
CacheNames.COGNITO_USERS_BY_EMAIL,
CacheNames.COGNITO_USERS_BY_UUID
}, allEntries = true)
public void deleteDeveloperForJoin(Long developerIdToDelete, Developer developerToJoin) throws EntityRetrievalException {
//The below code is to remove permissions to the developer from any users who might have had them in Cognito
//and add permissions for the users to belong to the joined developer
if (ff4j.check(FeatureList.SSO)) {
List<User> usersOnDeveloper = resourcePermissionsFactory.get().getAllUsersOnDeveloper(
Developer.builder().id(developerIdToDelete).build());

usersOnDeveloper.stream()
.forEach(user -> {
Organization developerOrgToDelete = user.getOrganizations().stream()
.filter(org -> org.getId().equals(developerIdToDelete))
.findAny().orElse(null);
if (developerOrgToDelete != null) {
user.getOrganizations().remove(developerOrgToDelete);
} else {
LOGGER.error("User " + user.getEmail() + " did not have permissions to developer organization " + developerIdToDelete);
}
Organization developerOrgToJoin = Organization.builder()
.id(developerToJoin.getId())
.name(developerToJoin.getName())
.build();
user.getOrganizations().add(developerOrgToJoin);
try {
cognitoUserManager.updateUser(user);
} catch (Exception ex) {
LOGGER.error("Error removing user's permissions on developer organization ID " + developerIdToDelete + " in Cognito", ex);
}
});
}

//The delete is last because if the developer is marked as deleted
//we have trouble finding it's users.
developerDao.delete(developerIdToDelete);
}

@PreAuthorize("@permissions.hasAccess(T(gov.healthit.chpl.permissions.Permissions).DEVELOPER, "
+ "T(gov.healthit.chpl.permissions.domains.DeveloperDomainPermissions).JOIN)")
@Transactional(readOnly = false)
Expand All @@ -349,7 +407,9 @@ public Long create(Developer developer) throws ValidationException, EntityCreati
CacheNames.COLLECTIONS_DEVELOPERS,
CacheNames.GET_DECERTIFIED_DEVELOPERS,
CacheNames.QUESTIONABLE_ACTIVITIES,
CacheNames.COLLECTIONS_LISTINGS
CacheNames.COLLECTIONS_LISTINGS,
CacheNames.COGNITO_USERS_BY_EMAIL,
CacheNames.COGNITO_USERS_BY_UUID
}, allEntries = true)
public ChplOneTimeTrigger join(Long owningDeveloperId, List<Long> joiningDeveloperIds)
throws EntityRetrievalException, JsonProcessingException, EntityCreationException,
Expand Down Expand Up @@ -382,6 +442,44 @@ public ChplOneTimeTrigger join(Long owningDeveloperId, List<Long> joiningDevelop
return joinDevelopersTrigger;
}

@PreAuthorize("@permissions.hasAccess(T(gov.healthit.chpl.permissions.Permissions).DEVELOPER, "
+ "T(gov.healthit.chpl.permissions.domains.DeveloperDomainPermissions).SPLIT, #oldDeveloper)")
@Transactional(readOnly = false)
@CacheEvict(value = {
CacheNames.ALL_DEVELOPERS,
CacheNames.ALL_DEVELOPERS_INCLUDING_DELETED,
CacheNames.COLLECTIONS_DEVELOPERS,
CacheNames.GET_DECERTIFIED_DEVELOPERS,
CacheNames.QUESTIONABLE_ACTIVITIES,
CacheNames.COLLECTIONS_LISTINGS,
CacheNames.COGNITO_USERS_BY_EMAIL,
CacheNames.COGNITO_USERS_BY_UUID
}, allEntries = true)
public void removeUsersForDeveloperSplit(Developer oldDeveloper) throws EntityRetrievalException {
//The below code is to remove permissions to the developer from any users who might have had them in Cognito
if (ff4j.check(FeatureList.SSO)) {
List<User> usersOnDeveloper = resourcePermissionsFactory.get().getAllUsersOnDeveloper(
Developer.builder().id(oldDeveloper.getId()).build());

usersOnDeveloper.stream()
.forEach(user -> {
Organization developerOrgToDelete = user.getOrganizations().stream()
.filter(org -> org.getId().equals(oldDeveloper.getId()))
.findAny().orElse(null);
if (developerOrgToDelete != null) {
user.getOrganizations().remove(developerOrgToDelete);
} else {
LOGGER.error("User " + user.getEmail() + " did not have permissions to developer organization " + oldDeveloper.getId());
}
try {
cognitoUserManager.updateUser(user);
} catch (Exception ex) {
LOGGER.error("Error removing user's permissions on developer organization ID " + oldDeveloper.getId() + " in Cognito", ex);
}
});
}
}

@PreAuthorize("@permissions.hasAccess(T(gov.healthit.chpl.permissions.Permissions).DEVELOPER, "
+ "T(gov.healthit.chpl.permissions.domains.DeveloperDomainPermissions).SPLIT, #oldDeveloper)")
@CacheEvict(value = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ private CertificationBody getCertifcationBody(Long certificationBodyId) {
try {
return certificationBodyDAO.getById(certificationBodyId);
} catch (EntityRetrievalException e) {
LOGGER.error("Could not retrieve Certification Body: {}", certificationBodyId);
LOGGER.error("Could not retrieve Certification Body: {}", certificationBodyId, e);
return null;
}
}
Expand All @@ -263,7 +263,7 @@ private Developer getDeveloper(Long developerId) {
try {
return developerDAO.getById(developerId);
} catch (EntityRetrievalException e) {
LOGGER.error("Could not retrieve Developer: {}", developerId);
LOGGER.error("Could not retrieve Developer: {}", developerId, e);
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.stereotype.Component;

import gov.healthit.chpl.permissions.domains.developer.CreateActionPermissions;
import gov.healthit.chpl.permissions.domains.developer.DeleteActionPermissions;
import gov.healthit.chpl.permissions.domains.developer.GetAllUsersActionPermissions;
import gov.healthit.chpl.permissions.domains.developer.JoinActionPermissions;
import gov.healthit.chpl.permissions.domains.developer.MessageActionPermissions;
Expand All @@ -15,6 +16,7 @@
public class DeveloperDomainPermissions extends DomainPermissions {
public static final String UPDATE = "UPDATE";
public static final String CREATE = "CREATE";
public static final String DELETE = "DELETE";
public static final String JOIN = "JOIN";
public static final String SPLIT = "SPLIT";
public static final String GET_ALL_USERS = "GET_ALL_USERS";
Expand All @@ -24,13 +26,15 @@ public class DeveloperDomainPermissions extends DomainPermissions {
public DeveloperDomainPermissions(
@Qualifier("developerUpdateActionPermissions") UpdateActionPermissions updateActionPermissions,
@Qualifier("developerCreateActionPermissions") CreateActionPermissions createActionPermissions,
@Qualifier("developerDeleteActionPermissions") DeleteActionPermissions deleteActionPermissions,
@Qualifier("developerJoinActionPermissions") JoinActionPermissions joinActionPermissions,
@Qualifier("developerSplitActionPermissions") SplitActionPermissions splitActionPermissions,
@Qualifier("developerGetAllUsersActionPermissions") GetAllUsersActionPermissions getUsersActionPermissions,
@Qualifier("developerMessagingActionPermissions") MessageActionPermissions messageActionPermissions) {

getActionPermissions().put(UPDATE, updateActionPermissions);
getActionPermissions().put(CREATE, createActionPermissions);
getActionPermissions().put(DELETE, deleteActionPermissions);
getActionPermissions().put(JOIN, joinActionPermissions);
getActionPermissions().put(SPLIT, splitActionPermissions);
getActionPermissions().put(GET_ALL_USERS, getUsersActionPermissions);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package gov.healthit.chpl.permissions.domains.developer;

import org.springframework.stereotype.Component;

import gov.healthit.chpl.permissions.domains.ActionPermissions;

@Component("developerDeleteActionPermissions")
public class DeleteActionPermissions extends ActionPermissions {

@Override
public boolean hasAccess() {
return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc();
}

@Override
public boolean hasAccess(Object obj) {
return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.ff4j.FF4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
Expand All @@ -27,6 +28,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;

import gov.healthit.chpl.FeatureList;
import gov.healthit.chpl.auth.user.JWTAuthenticatedUser;
import gov.healthit.chpl.caching.CacheNames;
import gov.healthit.chpl.caching.ListingSearchCacheRefresh;
Expand Down Expand Up @@ -90,6 +92,9 @@ public class SplitDeveloperJob extends QuartzJob {
@Autowired
private ChplHtmlEmailBuilder emailBuilder;

@Autowired
private FF4j ff4j;

@Value("${internalErrorEmailRecipients}")
private String internalErrorEmailRecipients;

Expand Down Expand Up @@ -117,6 +122,7 @@ public void execute(JobExecutionContext jobContext) throws JobExecutionException
Exception splitException = null;
try {
postSplitDeveloper = splitDeveloper(newDeveloper, productIdsToMove);
devManager.removeUsersForDeveloperSplit(preSplitDeveloper);
} catch (Exception e) {
LOGGER.error("Error completing split of old developer '" + preSplitDeveloper.getName() + "' to new developer '"
+ newDeveloper.getName() + "'.", e);
Expand Down Expand Up @@ -251,8 +257,11 @@ private void clearCachesRelatedToDevelopers() {
cacheManager.getCache(CacheNames.ALL_DEVELOPERS).invalidate();
cacheManager.getCache(CacheNames.ALL_DEVELOPERS_INCLUDING_DELETED).invalidate();
cacheManager.getCache(CacheNames.COLLECTIONS_DEVELOPERS).invalidate();
cacheManager.getCache(CacheNames.COLLECTIONS_LISTINGS).invalidate();
cacheManager.getCache(CacheNames.GET_DECERTIFIED_DEVELOPERS).invalidate();
cacheManager.getCache(CacheNames.QUESTIONABLE_ACTIVITIES).invalidate();
cacheManager.getCache(CacheNames.COLLECTIONS_LISTINGS).invalidate();
cacheManager.getCache(CacheNames.COGNITO_USERS_BY_EMAIL).invalidate();
cacheManager.getCache(CacheNames.COGNITO_USERS_BY_UUID).invalidate();
}

private void sendJobCompletionEmails(Developer newDeveloper, List<Long> productIds,
Expand Down Expand Up @@ -325,10 +334,23 @@ private String createHtmlEmailBodySuccess(String title, Developer createdDevelop
}
productList += "</ul>";

String userReminder = "";
if (ff4j.check(FeatureList.SSO)) {
userReminder = String.format("All users associated with %s have had their access removed "
+ "and may have been disabled. Users will need to be manually re-invited with the "
+ "correct organization access.",
preSplitDeveloper.getName());
} else {
userReminder = String.format("User access to the developers %s and %s must be manually updated.",
preSplitDeveloper.getName(),
createdDeveloper.getName());
}

String htmlMessage = emailBuilder.initialize()
.heading(title)
.paragraph(null, summaryText)
.paragraph(null, productList)
.paragraph(null, userReminder)
.footer(AdminFooter.class)
.build();
return htmlMessage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,11 @@ private void clearCachesRelatedToDevelopers() {
cacheManager.getCache(CacheNames.ALL_DEVELOPERS).invalidate();
cacheManager.getCache(CacheNames.ALL_DEVELOPERS_INCLUDING_DELETED).invalidate();
cacheManager.getCache(CacheNames.COLLECTIONS_DEVELOPERS).invalidate();
cacheManager.getCache(CacheNames.COLLECTIONS_LISTINGS).invalidate();
cacheManager.getCache(CacheNames.GET_DECERTIFIED_DEVELOPERS).invalidate();
cacheManager.getCache(CacheNames.QUESTIONABLE_ACTIVITIES).invalidate();
cacheManager.getCache(CacheNames.COLLECTIONS_LISTINGS).invalidate();
cacheManager.getCache(CacheNames.COGNITO_USERS_BY_EMAIL).invalidate();
cacheManager.getCache(CacheNames.COGNITO_USERS_BY_UUID).invalidate();
}

private void sendJobCompletionEmails(Developer developerJoined, List<Developer> developersJoining,
Expand Down
Loading

0 comments on commit 7410a9c

Please sign in to comment.