From 3cfd8de4ec22cb6a9a6479cde24c0d3d9e3546d6 Mon Sep 17 00:00:00 2001 From: Shan Jayathilaka Date: Mon, 11 Sep 2023 01:57:02 +0530 Subject: [PATCH] Improve organization user invitation and user sharing --- .../management/InvitationCoreService.java | 12 + .../management/InvitationCoreServiceImpl.java | 217 ++++++++++++------ .../management/constant/SQLConstants.java | 4 +- .../constant/UserInvitationMgtConstants.java | 8 +- .../management/dao/UserInvitationDAO.java | 12 + .../management/dao/UserInvitationDAOImpl.java | 31 ++- .../OrgSharedUserOperationEventListener.java | 19 +- .../models/SharedUserAssociation.java | 30 +++ .../InvitationCoreServiceImplTest.java | 6 +- 9 files changed, 258 insertions(+), 81 deletions(-) create mode 100644 components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/models/SharedUserAssociation.java diff --git a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/InvitationCoreService.java b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/InvitationCoreService.java index 30d59dfb4..b5f93f48c 100644 --- a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/InvitationCoreService.java +++ b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/InvitationCoreService.java @@ -93,4 +93,16 @@ public interface InvitationCoreService { */ boolean deleteInvitedUserAssociation(String userId, UserStoreManager userStoreManager) throws UserInvitationMgtException; + + /** + * Checks whether the user claim values can be updated for the user. + * + * @param userID The ID of the user. + * @param profileName The profile name of the user. + * @param userStoreManager The user store manager of the user. + * @return True if the user claim values can be updated. + * @throws UserInvitationMgtException If an error occurs while checking the user claim values can be updated. + */ + boolean isUpdateUserClaimValuesAllowed(String userID, String profileName, UserStoreManager userStoreManager) + throws UserInvitationMgtException; } diff --git a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/InvitationCoreServiceImpl.java b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/InvitationCoreServiceImpl.java index 847bfa090..ba2f05627 100644 --- a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/InvitationCoreServiceImpl.java +++ b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/InvitationCoreServiceImpl.java @@ -22,7 +22,10 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.core.ServiceURLBuilder; +import org.wso2.carbon.identity.core.URLBuilderException; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.event.IdentityEventException; import org.wso2.carbon.identity.event.event.Event; import org.wso2.carbon.identity.organization.management.service.OrganizationManager; @@ -36,6 +39,7 @@ import org.wso2.carbon.identity.organization.user.invitation.management.internal.UserInvitationMgtDataHolder; import org.wso2.carbon.identity.organization.user.invitation.management.models.Invitation; import org.wso2.carbon.identity.organization.user.invitation.management.models.RoleAssignments; +import org.wso2.carbon.identity.organization.user.invitation.management.models.SharedUserAssociation; import org.wso2.carbon.user.api.UserRealm; import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.api.UserStoreManager; @@ -70,12 +74,12 @@ import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_ACTIVE_INVITATION_EXISTS; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_CREATE_INVITATION; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_EVENT_HANDLE; +import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_GET_MANAGED_CLAIM; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_GET_PARENT_ORG; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_GET_USER_CLAIMS; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_INVALID_CONFIRMATION_CODE; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_INVALID_FILTER; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_INVALID_INVITATION_ID; -import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_INVALID_USER; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_INVITATION_EXPIRED; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_NO_INVITATION_FOR_USER; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_UNABLE_TO_RESEND_INVITATION; @@ -100,6 +104,7 @@ public class InvitationCoreServiceImpl implements InvitationCoreService { @Override public Invitation createInvitation(Invitation invitation) throws UserInvitationMgtException { + validateInvitationPayload(invitation); String organizationId = Utils.getOrganizationId(); OrganizationManager organizationManager = UserInvitationMgtDataHolder.getInstance() .getOrganizationManagerService(); @@ -108,8 +113,7 @@ public Invitation createInvitation(Invitation invitation) throws UserInvitationM // Checking the parent organization id String parentOrgId = organizationManager.getParentOrganizationId(organizationId); // Picking the parent organization's tenant domain - String parentTenantDomain = organizationManager - .resolveTenantDomain(parentOrgId); + String parentTenantDomain = organizationManager.resolveTenantDomain(parentOrgId); boolean isActiveInvitationAvailable = isActiveInvitationAvailable(invitation.getUsername(), invitation.getUserDomain(), parentOrgId, organizationId); if (isActiveInvitationAvailable) { @@ -155,81 +159,95 @@ public Invitation createInvitation(Invitation invitation) throws UserInvitationM return createdInvitation; } + private void validateInvitationPayload(Invitation invitation) { + + if (StringUtils.isEmpty(invitation.getUserDomain())) { + invitation.setUserDomain(IdentityUtil.getProperty("OrganizationUserInvitation.PrimaryUserDomain")); + } + if (StringUtils.isEmpty(invitation.getUserRedirectUrl())) { + String defaultInvitationAcceptanceURL = IdentityUtil.getProperty( + "OrganizationUserInvitation.DefaultRedirectURL"); + try { + String invitationAcceptanceURL = ServiceURLBuilder.create() + .addPath(defaultInvitationAcceptanceURL) + .build() + .getAbsolutePublicURL(); + invitation.setUserRedirectUrl(invitationAcceptanceURL); + } catch (URLBuilderException e) { + throw new RuntimeException(e); + } + } + } + @Override public boolean acceptInvitation(String confirmationCode) throws UserInvitationMgtException { - String authenticatedUser = Utils.getAuthenticatedUsername(); Invitation invitation = userInvitationDAO.getInvitationWithRolesByConfirmationCode(confirmationCode); if (invitation != null) { - String userDomainQualifiedUserName = UserCoreUtil - .addDomainToName(invitation.getUsername(), invitation.getUserDomain()); - if (userDomainQualifiedUserName.equals(authenticatedUser)) { - if (invitation.getExpiredAt().getTime() > Instant.now().toEpochMilli()) { - try { - OrganizationManager organizationManager = UserInvitationMgtDataHolder.getInstance() - .getOrganizationManagerService(); - String invitedOrganizationId = invitation.getInvitedOrganizationId(); - String invitedTenantDomain = organizationManager.resolveTenantDomain(invitedOrganizationId); - int invitedTenantId = IdentityTenantUtil.getTenantId(invitedTenantDomain); - AbstractUserStoreManager userStoreManager = getAbstractUserStoreManager(invitedTenantId); - String userNameInInvitedOrg = UserCoreUtil - .addDomainToName(invitation.getUsername(), DEFAULT_USER_STORE_DOMAIN); - if (!userStoreManager.isExistingUser(userNameInInvitedOrg)) { - HashMap userClaims = new HashMap<>(); - userClaims.put(CLAIM_MANAGED_ORGANIZATION, invitation.getUserOrganizationId()); - userClaims.put(ID_CLAIM_READ_ONLY, "true"); - UserCoreUtil.setSkipPasswordPatternValidationThreadLocal(true); - userStoreManager.addUser(userNameInInvitedOrg, generatePassword(), null, - userClaims, DEFAULT_PROFILE); - // Adding the association with the organization. - String sharedUserId = userStoreManager.getUserIDFromUserName(userNameInInvitedOrg); + if (invitation.getExpiredAt().getTime() > Instant.now().toEpochMilli()) { + try { + OrganizationManager organizationManager = UserInvitationMgtDataHolder.getInstance() + .getOrganizationManagerService(); + String invitedOrganizationId = invitation.getInvitedOrganizationId(); + String invitedTenantDomain = organizationManager.resolveTenantDomain(invitedOrganizationId); + int invitedTenantId = IdentityTenantUtil.getTenantId(invitedTenantDomain); + AbstractUserStoreManager userStoreManager = getAbstractUserStoreManager(invitedTenantId); + String userNameInInvitedOrg = UserCoreUtil + .addDomainToName(invitation.getUsername(), DEFAULT_USER_STORE_DOMAIN); + if (!userStoreManager.isExistingUser(userNameInInvitedOrg)) { + HashMap userClaims = new HashMap<>(); + userClaims.put(CLAIM_MANAGED_ORGANIZATION, invitation.getUserOrganizationId()); + userClaims.put(ID_CLAIM_READ_ONLY, "true"); + UserCoreUtil.setSkipPasswordPatternValidationThreadLocal(true); + userStoreManager.addUser(userNameInInvitedOrg, generatePassword(), null, + userClaims, DEFAULT_PROFILE); + // Adding the association with the organization. + String sharedUserId = userStoreManager.getUserIDFromUserName(userNameInInvitedOrg); + String realUserName = UserCoreUtil + .addDomainToName(invitation.getUsername(), invitation.getUserDomain()); + String realUserId = getRealUserId(invitation.getUserOrganizationId(), realUserName); + userInvitationDAO.createOrganizationAssociation(realUserId, + invitation.getUserOrganizationId(), sharedUserId, invitedOrganizationId); + // Trigger event to add the role assignments if any available in the invitation. + if (ArrayUtils.isNotEmpty(invitation.getRoleAssignments())) { // Get the available group name for the group in the invited organization. String availableGroupName = getAvailableGroupName(); userStoreManager.addRole(availableGroupName, new String[]{userNameInInvitedOrg}, null); - String realUserId = Utils.getUserId(); - userInvitationDAO.createOrganizationAssociation(realUserId, - invitation.getUserOrganizationId(), sharedUserId, invitedOrganizationId); - // Trigger event to add the role assignments if any available in the invitation. - if (ArrayUtils.isNotEmpty(invitation.getRoleAssignments())) { - triggerRoleAssignmentEvent(invitedOrganizationId, availableGroupName, - Arrays.asList(invitation.getRoleAssignments())); - } - // Removing the invitation since the user is added to the organization. - if (LOG.isDebugEnabled()) { - LOG.debug("User: " + invitation.getUsername() + " is added to the organization: " - + invitedOrganizationId + ". Hence deleting the invitation with the " + - "confirmation code: " + confirmationCode); - } - userInvitationDAO.deleteInvitation(invitation.getInvitationId()); - return true; + triggerRoleAssignmentEvent(invitedOrganizationId, availableGroupName, + Arrays.asList(invitation.getRoleAssignments())); + } + // Removing the invitation since the user is added to the organization. + if (LOG.isDebugEnabled()) { + LOG.debug("User: " + invitation.getUsername() + " is added to the organization: " + + invitedOrganizationId + ". Hence deleting the invitation with the " + + "confirmation code: " + confirmationCode); } - LOG.error("User: " + invitation.getUsername() + " exists in the organization: " - + invitedOrganizationId + ". Hence deleting the invitation with the " + - "confirmation code: " + confirmationCode); userInvitationDAO.deleteInvitation(invitation.getInvitationId()); - throw new UserInvitationMgtClientException(ERROR_CODE_USER_ALREADY_EXISTS.getCode(), - ERROR_CODE_USER_ALREADY_EXISTS.getMessage(), - String.format(ERROR_CODE_USER_ALREADY_EXISTS.getDescription(), - invitation.getUsername(), invitedOrganizationId)); - - } catch (UserStoreException | OrganizationManagementException e) { - UserCoreUtil.removeSkipPasswordPatternValidationThreadLocal(); - throw new UserInvitationMgtServerException(ERROR_CODE_ACCEPT_INVITATION.getCode(), - ERROR_CODE_ACCEPT_INVITATION.getMessage(), - String.format(ERROR_CODE_ACCEPT_INVITATION.getDescription(), confirmationCode), e); - } - } else { - // Removing the invitation since the invitation is expired. - if (LOG.isDebugEnabled()) { - LOG.error("Invitation with the confirmation code: " + confirmationCode + " is expired. Hence " + - "deleting the invitation."); + return true; } + LOG.error("User: " + invitation.getUsername() + " exists in the organization: " + + invitedOrganizationId + ". Hence deleting the invitation with the " + + "confirmation code: " + confirmationCode); userInvitationDAO.deleteInvitation(invitation.getInvitationId()); + throw new UserInvitationMgtClientException(ERROR_CODE_USER_ALREADY_EXISTS.getCode(), + ERROR_CODE_USER_ALREADY_EXISTS.getMessage(), + String.format(ERROR_CODE_USER_ALREADY_EXISTS.getDescription(), + invitation.getUsername(), invitedOrganizationId)); + + } catch (UserStoreException | OrganizationManagementException e) { + UserCoreUtil.removeSkipPasswordPatternValidationThreadLocal(); + throw new UserInvitationMgtServerException(ERROR_CODE_ACCEPT_INVITATION.getCode(), + ERROR_CODE_ACCEPT_INVITATION.getMessage(), + String.format(ERROR_CODE_ACCEPT_INVITATION.getDescription(), confirmationCode), e); } + } else { + // Removing the invitation since the invitation is expired. + if (LOG.isDebugEnabled()) { + LOG.error("Invitation with the confirmation code: " + confirmationCode + " is expired. Hence " + + "deleting the invitation."); + } + userInvitationDAO.deleteInvitation(invitation.getInvitationId()); } - throw new UserInvitationMgtClientException(ERROR_CODE_INVALID_USER.getCode(), - ERROR_CODE_INVALID_USER.getMessage(), String.format(ERROR_CODE_INVALID_USER.getDescription(), - authenticatedUser)); } throw new UserInvitationMgtException(ERROR_CODE_INVALID_CONFIRMATION_CODE.getCode(), ERROR_CODE_INVALID_CONFIRMATION_CODE.getMessage(), @@ -341,21 +359,44 @@ public Invitation resendInvitation(String username, String domain) throws UserIn public boolean deleteInvitedUserAssociation(String userId, UserStoreManager userStoreManager) throws UserInvitationMgtException { - String authenticatedOrganizationId = Utils.getOrganizationId(); + String authenticatedTenant = Utils.getTenantDomain(); + OrganizationManager organizationManager = UserInvitationMgtDataHolder.getInstance() + .getOrganizationManagerService(); + String authenticatedOrganizationId; + try { + authenticatedOrganizationId = organizationManager.resolveOrganizationId(authenticatedTenant); + } catch (OrganizationManagementException e) { + throw new RuntimeException(e); + } + try { String managedOrgClaimValue = ((AbstractUserStoreManager) userStoreManager).getUserClaimValueWithID(userId, CLAIM_MANAGED_ORGANIZATION, DEFAULT_PROFILE); if (StringUtils.isEmpty(managedOrgClaimValue)) { - userInvitationDAO.deleteAllAssociationsOfOrgUserToSharedOrgs(userId, authenticatedOrganizationId); - return true; - } - String parentOrgOfAuthenticatedOrg = UserInvitationMgtDataHolder.getInstance() - .getOrganizationManagerService() - .getParentOrganizationId(authenticatedOrganizationId); - if (managedOrgClaimValue.equals(parentOrgOfAuthenticatedOrg)) { - userInvitationDAO.deleteOrgUserAssociationToSharedOrg(userId, authenticatedOrganizationId); + // ToDo need to remove the users shared to the sub organizations + // Get the associations from the user id. + List sharedUserAssociationList = userInvitationDAO + .getAllAssociationsOfOrgUserToSharedOrgs(userId, authenticatedOrganizationId); + // Loop them. + // Get the associated organizations. + // Get userstore managers for the associated organizations. + // Delete the user from the associated organizations by using the userstore managers. + for (SharedUserAssociation sharedUserAssociation : sharedUserAssociationList) { + String organizationId = sharedUserAssociation.getSharedUserOrganizationId(); + String tenantDomain = organizationManager.resolveTenantDomain(organizationId); + int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); + AbstractUserStoreManager sharedOrgUserStoreManager = getAbstractUserStoreManager(tenantId); + sharedOrgUserStoreManager.deleteUserWithID(sharedUserAssociation.getSharedUserId()); + } return true; } +// String parentOrgOfAuthenticatedOrg = UserInvitationMgtDataHolder.getInstance() +// .getOrganizationManagerService() +// .getParentOrganizationId(authenticatedOrganizationId); + userInvitationDAO.deleteOrgUserAssociationToSharedOrg(userId, managedOrgClaimValue); + return true; +// if (StringUtils.equals(managedOrgClaimValue, authenticatedOrganizationId)) { +// } } catch (UserStoreException e) { throw new UserInvitationMgtServerException(ERROR_CODE_GET_USER_CLAIMS.getCode(), ERROR_CODE_GET_USER_CLAIMS.getMessage(), String.format(ERROR_CODE_GET_USER_CLAIMS @@ -365,7 +406,21 @@ public boolean deleteInvitedUserAssociation(String userId, UserStoreManager user .getMessage(), String.format(ERROR_CODE_GET_PARENT_ORG.getDescription(), authenticatedOrganizationId), e); } - return false; + } + + @Override + public boolean isUpdateUserClaimValuesAllowed(String userId, String profileName, UserStoreManager userStoreManager) + throws UserInvitationMgtException { + + try { + String managedOrgClaimValue = ((AbstractUserStoreManager) userStoreManager).getUserClaimValueWithID(userId, + CLAIM_MANAGED_ORGANIZATION, profileName); + return StringUtils.isEmpty(managedOrgClaimValue); + } catch (UserStoreException e) { + throw new UserInvitationMgtServerException(ERROR_CODE_GET_MANAGED_CLAIM.getCode(), + ERROR_CODE_GET_MANAGED_CLAIM.getMessage(), String.format(ERROR_CODE_GET_MANAGED_CLAIM. + getDescription(), userId), e); + } } private AbstractUserStoreManager getAbstractUserStoreManager(int tenantId) throws UserStoreException { @@ -375,6 +430,20 @@ private AbstractUserStoreManager getAbstractUserStoreManager(int tenantId) throw return (AbstractUserStoreManager) tenantUserRealm.getUserStoreManager(); } + private String getRealUserId(String userOrgId, String username) { + + OrganizationManager organizationManager = UserInvitationMgtDataHolder.getInstance() + .getOrganizationManagerService(); + try { + String userTenantDomain = organizationManager.resolveTenantDomain(userOrgId); + int userTenantId = IdentityTenantUtil.getTenantId(userTenantDomain); + AbstractUserStoreManager userStoreManager = getAbstractUserStoreManager(userTenantId); + return userStoreManager.getUserIDFromUserName(username); + } catch (UserStoreException | OrganizationManagementException e) { + throw new RuntimeException(e); + } + } + private void triggerInvitationAddNotification(Invitation invitation) throws UserInvitationMgtServerException { diff --git a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/constant/SQLConstants.java b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/constant/SQLConstants.java index ba45bf745..f6815bc9a 100644 --- a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/constant/SQLConstants.java +++ b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/constant/SQLConstants.java @@ -65,9 +65,11 @@ public static final class SQLQueries { public static final String CREATE_USER_ASSOCIATION_TO_ORG = "INSERT INTO IDN_ORG_USER_ASSOCIATION(" + "SHARED_USER_ID, SUB_ORG_ID, REAL_USER_ID, USER_RESIDENT_ORG_ID) VALUES(?, ?, ?, ?)"; public static final String DELETE_ORG_ASSOCIATION_FOR_SHARED_USER = "DELETE FROM " + - "IDN_ORG_USER_ASSOCIATION WHERE SHARED_USER_ID = ? AND SUB_ORG_ID = ?"; + "IDN_ORG_USER_ASSOCIATION WHERE SHARED_USER_ID = ? AND USER_RESIDENT_ORG_ID = ?"; public static final String DELETE_ALL_ORG_ASSOCIATIONS_FOR_SHARED_USER = "DELETE FROM " + "IDN_ORG_USER_ASSOCIATION WHERE REAL_USER_ID = ? AND USER_RESIDENT_ORG_ID = ?"; + public static final String GET_ALL_ORG_ASSOCIATIONS_FOR_SHARED_USER = "SELECT SHARED_USER_ID, SUB_ORG_ID " + + "FROM IDN_ORG_USER_ASSOCIATION WHERE REAL_USER_ID = ? AND USER_RESIDENT_ORG_ID = ?"; } /** diff --git a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/constant/UserInvitationMgtConstants.java b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/constant/UserInvitationMgtConstants.java index 929096a07..42e6f9e9a 100644 --- a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/constant/UserInvitationMgtConstants.java +++ b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/constant/UserInvitationMgtConstants.java @@ -112,6 +112,9 @@ public enum ErrorMessage { ERROR_CODE_INVALID_USER("10027", "Invalid user identification provided.", "Authenticated user %s is not entitled for the invitation."), + ERROR_CODE_GET_MANAGED_CLAIM("10028", + "Unable to get the claim.", + "Unable to get the managed org claim for user %s."), // DAO layer errors ERROR_CODE_STORE_INVITATION("10501", @@ -172,7 +175,10 @@ public enum ErrorMessage { // Event listener errors ERROR_CODE_DELETE_INVITED_USER_ASSOCIATION("10400", "Unable to delete invited user association.", - "Could not delete invited user association for user %s."); + "Could not delete invited user association for user %s."), + ERROR_CODE_CHECK_USER_CLAIM_UPDATE_ALLOWED("10401", + "Unable to check whether the update is allowed.", + "Could not check whether the user claim update is allowed for user %s."); private final String code; private final String message; diff --git a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/dao/UserInvitationDAO.java b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/dao/UserInvitationDAO.java index 8e8f28d24..33c260a91 100644 --- a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/dao/UserInvitationDAO.java +++ b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/dao/UserInvitationDAO.java @@ -21,6 +21,7 @@ import org.wso2.carbon.identity.organization.user.invitation.management.exception.UserInvitationMgtException; import org.wso2.carbon.identity.organization.user.invitation.management.exception.UserInvitationMgtServerException; import org.wso2.carbon.identity.organization.user.invitation.management.models.Invitation; +import org.wso2.carbon.identity.organization.user.invitation.management.models.SharedUserAssociation; import java.util.List; @@ -132,4 +133,15 @@ boolean deleteOrgUserAssociationToSharedOrg(String userId, String organizationId */ boolean deleteAllAssociationsOfOrgUserToSharedOrgs(String userId, String organizationId) throws UserInvitationMgtException; + + /** + * Get all the associations for the child organizations when the user is deleting from the parent organization. + * + * @param userId Actual user id of the user in the parent organization. + * @param organizationId The organization id of the parent organization. + * @return True if the associations are deleted successfully. + * @throws UserInvitationMgtException If an error occurs while deleting the associations. + */ + List getAllAssociationsOfOrgUserToSharedOrgs(String userId, String organizationId) + throws UserInvitationMgtException; } diff --git a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/dao/UserInvitationDAOImpl.java b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/dao/UserInvitationDAOImpl.java index 68fed40b4..1d6dc6f56 100644 --- a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/dao/UserInvitationDAOImpl.java +++ b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/dao/UserInvitationDAOImpl.java @@ -20,12 +20,14 @@ import org.apache.commons.lang.StringUtils; import org.wso2.carbon.identity.core.util.IdentityDatabaseUtil; +import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants; import org.wso2.carbon.identity.organization.user.invitation.management.exception.UserInvitationMgtClientException; import org.wso2.carbon.identity.organization.user.invitation.management.exception.UserInvitationMgtException; import org.wso2.carbon.identity.organization.user.invitation.management.exception.UserInvitationMgtServerException; import org.wso2.carbon.identity.organization.user.invitation.management.models.Invitation; import org.wso2.carbon.identity.organization.user.invitation.management.models.RoleAssignments; +import org.wso2.carbon.identity.organization.user.invitation.management.models.SharedUserAssociation; import java.sql.Connection; import java.sql.PreparedStatement; @@ -59,6 +61,7 @@ import static org.wso2.carbon.identity.organization.user.invitation.management.constant.SQLConstants.SQLQueries.DELETE_ORG_ASSOCIATION_FOR_SHARED_USER; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.SQLConstants.SQLQueries.DELETE_ROLE_ASSIGNMENTS_BY_INVITATION_ID; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.SQLConstants.SQLQueries.GET_ACTIVE_INVITATION_BY_USER; +import static org.wso2.carbon.identity.organization.user.invitation.management.constant.SQLConstants.SQLQueries.GET_ALL_ORG_ASSOCIATIONS_FOR_SHARED_USER; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.SQLConstants.SQLQueries.GET_INVITATIONS_BY_INVITED_ORG_ID; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.SQLConstants.SQLQueries.GET_INVITATIONS_BY_INVITED_ORG_ID_WITH_STATUS_FILTER_EXPIRED; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.SQLConstants.SQLQueries.GET_INVITATIONS_BY_INVITED_ORG_ID_WITH_STATUS_FILTER_PENDING; @@ -124,8 +127,10 @@ public void createInvitation(Invitation invitation) throws UserInvitationMgtExce Timestamp currentTimestamp = new Timestamp(new Date().getTime()); invitationCreatePrepStat.setTimestamp(9, currentTimestamp, Calendar.getInstance(TimeZone.getTimeZone(UTC))); + int expiryTime = Integer.parseInt(IdentityUtil.getProperty( + "OrganizationUserInvitation.DefaultExpiryTime")); Timestamp expiryTimestamp = new Timestamp( - new Timestamp(currentTimestamp.getTime() + TimeUnit.MINUTES.toMillis(1440)).getTime()); + new Timestamp(currentTimestamp.getTime() + TimeUnit.MINUTES.toMillis(expiryTime)).getTime()); invitationCreatePrepStat.setTimestamp(10, expiryTimestamp, Calendar.getInstance(TimeZone.getTimeZone(UTC))); invitationCreatePrepStat.setString(11, invitation.getUserRedirectUrl()); @@ -436,6 +441,30 @@ public boolean deleteAllAssociationsOfOrgUserToSharedOrgs(String userId, String } } + @Override + public List getAllAssociationsOfOrgUserToSharedOrgs(String userId, String organizationId) + throws UserInvitationMgtException { + + try (Connection connection = IdentityDatabaseUtil.getDBConnection(false); + PreparedStatement getUserSharedOrgsPrepStat = + connection.prepareStatement(GET_ALL_ORG_ASSOCIATIONS_FOR_SHARED_USER)) { + getUserSharedOrgsPrepStat.setString(1, userId); + getUserSharedOrgsPrepStat.setString(2, organizationId); + List sharedUserAssociationList = new ArrayList<>(); + try (ResultSet resultSet = getUserSharedOrgsPrepStat.executeQuery()) { + if (resultSet.next()) { + SharedUserAssociation sharedUserAssociation = new SharedUserAssociation(); + sharedUserAssociation.setSharedUserId(resultSet.getString("SHARED_USER_ID")); + sharedUserAssociation.setSharedUserOrganizationId(resultSet.getString("SUB_ORG_ID")); + sharedUserAssociationList.add(sharedUserAssociation); + } + } + return sharedUserAssociationList; + } catch (SQLException e) { + throw handleServerException(ERROR_CODE_GET_INVITATION_BY_USER, userId, e); + } + } + private List processRoleAssignments(List roleAssignmentsResultList) { // Processing the role assignments diff --git a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/listener/OrgSharedUserOperationEventListener.java b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/listener/OrgSharedUserOperationEventListener.java index 70ae505f4..cdb84112c 100644 --- a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/listener/OrgSharedUserOperationEventListener.java +++ b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/listener/OrgSharedUserOperationEventListener.java @@ -25,6 +25,9 @@ import org.wso2.carbon.user.core.UserStoreException; import org.wso2.carbon.user.core.UserStoreManager; +import java.util.Map; + +import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_CHECK_USER_CLAIM_UPDATE_ALLOWED; import static org.wso2.carbon.identity.organization.user.invitation.management.constant.UserInvitationMgtConstants.ErrorMessage.ERROR_CODE_DELETE_INVITED_USER_ASSOCIATION; /** @@ -35,7 +38,7 @@ public class OrgSharedUserOperationEventListener extends AbstractIdentityUserOpe @Override public int getExecutionOrderId() { - return 160; + return 8; } @Override @@ -55,8 +58,18 @@ public boolean doPreDeleteUserWithID(String userID, UserStoreManager userStoreMa } @Override - public boolean isEnable() { + public boolean doPreSetUserClaimValuesWithID(String userID, Map claims, String profileName, + UserStoreManager userStoreManager) throws UserStoreException { - return false; + if (!isEnable() || userStoreManager == null) { + return true; + } + InvitationCoreService invitationCoreService = new InvitationCoreServiceImpl(); + try { + return invitationCoreService.isUpdateUserClaimValuesAllowed(userID, profileName, userStoreManager); + } catch (UserInvitationMgtException e) { + throw new UserStoreException(String.format(ERROR_CODE_CHECK_USER_CLAIM_UPDATE_ALLOWED.getDescription(), + userID), ERROR_CODE_CHECK_USER_CLAIM_UPDATE_ALLOWED.getCode(), e); + } } } diff --git a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/models/SharedUserAssociation.java b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/models/SharedUserAssociation.java new file mode 100644 index 000000000..2b3ee0211 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/main/java/org/wso2/carbon/identity/organization/user/invitation/management/models/SharedUserAssociation.java @@ -0,0 +1,30 @@ +package org.wso2.carbon.identity.organization.user.invitation.management.models; + +/** + * Model class to represent the shared user association. + */ +public class SharedUserAssociation { + + private String sharedUserId; + private String sharedUserOrganizationId; + + public String getSharedUserId() { + + return sharedUserId; + } + + public void setSharedUserId(String sharedUserId) { + + this.sharedUserId = sharedUserId; + } + + public String getSharedUserOrganizationId() { + + return sharedUserOrganizationId; + } + + public void setSharedUserOrganizationId(String sharedUserOrganizationId) { + + this.sharedUserOrganizationId = sharedUserOrganizationId; + } +} diff --git a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/test/java/org/wso2/carbon/identity/organization/user/invitation/management/InvitationCoreServiceImplTest.java b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/test/java/org/wso2/carbon/identity/organization/user/invitation/management/InvitationCoreServiceImplTest.java index 5f6728ada..18783bd45 100644 --- a/components/org.wso2.carbon.identity.organization.user.invitation.management/src/test/java/org/wso2/carbon/identity/organization/user/invitation/management/InvitationCoreServiceImplTest.java +++ b/components/org.wso2.carbon.identity.organization.user.invitation.management/src/test/java/org/wso2/carbon/identity/organization/user/invitation/management/InvitationCoreServiceImplTest.java @@ -28,6 +28,7 @@ import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.core.util.IdentityDatabaseUtil; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.organization.management.service.OrganizationManager; import org.wso2.carbon.identity.organization.user.invitation.management.dao.UserInvitationDAO; import org.wso2.carbon.identity.organization.user.invitation.management.dao.UserInvitationDAOImpl; @@ -81,7 +82,8 @@ IdentityDatabaseUtil.class, UserInvitationMgtDataHolder.class, IdentityTenantUtil.class, - UserInvitationMgtDataHolder.class}) + UserInvitationMgtDataHolder.class, + IdentityUtil.class}) public class InvitationCoreServiceImplTest extends PowerMockTestCase { private final UserInvitationDAO userInvitationDAO = new UserInvitationDAOImpl(); @@ -96,6 +98,7 @@ public void setUp() throws Exception { mockCarbonContextForTenant(); mockStatic(IdentityTenantUtil.class); mockStatic(IdentityDatabaseUtil.class); + mockStatic(IdentityUtil.class); Connection connection1 = getConnection(); Connection connection2 = getConnection(); @@ -274,6 +277,7 @@ private static void mockIdentityTenantUtil() { private void populateH2Base(Connection connection, Invitation invitation) throws Exception { when(IdentityDatabaseUtil.getDBConnection(anyBoolean())).thenReturn(connection); + when(IdentityUtil.getProperty(anyString())).thenReturn("1440"); userInvitationDAO.createInvitation(invitation); }