diff --git a/components/org.wso2.carbon.identity.scim2.common/pom.xml b/components/org.wso2.carbon.identity.scim2.common/pom.xml index d999c1e7..4fc7dc8a 100644 --- a/components/org.wso2.carbon.identity.scim2.common/pom.xml +++ b/components/org.wso2.carbon.identity.scim2.common/pom.xml @@ -139,6 +139,10 @@ org.wso2.carbon.identity.governance org.wso2.carbon.identity.password.policy + + org.wso2.carbon.identity.governance + org.wso2.carbon.identity.password.expiry + org.wso2.carbon.identity.organization.management.core org.wso2.carbon.identity.organization.management.service diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java index 33b7c806..9ce95f7b 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java @@ -29,6 +29,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.http.HttpStatus; import org.wso2.carbon.CarbonConstants; +import org.wso2.carbon.identity.application.authentication.framework.exception.PostAuthenticationFailedException; import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils; import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; import org.wso2.carbon.identity.application.common.model.ServiceProvider; @@ -98,6 +99,7 @@ import org.wso2.charon3.core.objects.Role; import org.wso2.charon3.core.objects.RoleV2; import org.wso2.charon3.core.objects.User; +import org.wso2.charon3.core.objects.plainobjects.MultiValuedComplexType; import org.wso2.charon3.core.objects.plainobjects.UsersGetResponse; import org.wso2.charon3.core.objects.plainobjects.GroupsGetResponse; import org.wso2.charon3.core.protocol.ResponseCodeConstants; @@ -114,6 +116,8 @@ import org.wso2.charon3.core.utils.codeutils.PatchOperation; import org.wso2.charon3.core.utils.codeutils.SearchRequest; import org.wso2.carbon.identity.configuration.mgt.core.model.Resource; +import org.wso2.carbon.identity.password.expiry.util.PasswordPolicyUtils; +import org.wso2.carbon.utils.multitenancy.MultitenantUtils; import java.time.Instant; import java.util.AbstractMap; @@ -173,6 +177,9 @@ public class SCIMUserManager implements UserManager { private static final String ENABLE_PAGINATED_USER_STORE = "SCIM.EnablePaginatedUserStore"; private static final String SERVICE_PROVIDER = "serviceProvider"; private final String SERVICE_PROVIDER_TENANT_DOMAIN = "serviceProviderTenantDomain"; + private final String PASSWORD_EXPIRY_TIME_ATTRIBUTE_NAME = "passwordExpiryTime"; + private final String PASSWORD_EXPIRY_TIME_CLAIM_URI = + getCustomSchemaURI() + SCIMConstants.OperationalConstants.COLON + PASSWORD_EXPIRY_TIME_ATTRIBUTE_NAME; // Additional wso2 user schema properties. private static final String DISPLAY_NAME_PROPERTY = "displayName"; @@ -4188,6 +4195,10 @@ private User getSCIMUser(org.wso2.carbon.user.core.common.User coreUser, List getSCIMUsers(Set users, setRolesOfUser(rolesList, groupMetaAttributesCache, user, scimUser); } + if (requiredAttributes.containsKey(PASSWORD_EXPIRY_TIME_CLAIM_URI)) { + setUserPasswordExpiryTime(scimUser, user, tenantDomain); + } } catch (UserStoreException e) { throw resolveError(e, "Error in getting user information for user: " + maskIfRequired(user.getUsername())); @@ -4417,6 +4431,60 @@ private Set getSCIMUsers(Set users, return scimUserSet; } + /** + * Adds password expiry information to the SCIM user object. + * + * @param scimUser The SCIM user object to update. + * @param user The core user object. + * @param tenantDomain The tenant domain. + * @throws CharonException If there's an error during SCIM object construction. + */ + private void setUserPasswordExpiryTime(User scimUser, + org.wso2.carbon.user.core.common.User user, + String tenantDomain) throws CharonException { + + try { + String tenantAwareUsername = MultitenantUtils.getTenantAwareUsername( + user.getFullQualifiedUsername()); + + List roleIds = extractValueFromComplexType(scimUser.getRoles()); + List groupIds = extractValueFromComplexType(scimUser.getGroups()); + + Optional passwordExpiryTime = PasswordPolicyUtils.getUserPasswordExpiryTime( + tenantDomain, tenantAwareUsername, roleIds, groupIds); + + if (passwordExpiryTime.isPresent()) { + Map.Entry passwordExpiryTimeEntry = new AbstractMap.SimpleEntry<>( + PASSWORD_EXPIRY_TIME_CLAIM_URI, String.valueOf(passwordExpiryTime.get())); + AttributeMapper.constructSCIMObjectFromAttributesOfLevelTwo(this, passwordExpiryTimeEntry, + scimUser,new String[]{getCustomSchemaURI(), PASSWORD_EXPIRY_TIME_ATTRIBUTE_NAME}, + 1); + } + } catch (CharonException | NotFoundException | BadRequestException + | PostAuthenticationFailedException e) { + throw new CharonException("Error in getting user information for user: " + + maskIfRequired(user.getUsername()), e); + } + } + + /** + * Extracts values from MultiValuedComplexType list. + * + * @param complexTypes List of complex types. + * @return List of extracted values or null if no valid values exist. + */ + private List extractValueFromComplexType(List complexTypes) { + + if (CollectionUtils.isEmpty(complexTypes)) { + return null; + } + List values = complexTypes.stream() + .map(MultiValuedComplexType::getValue) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + return values.isEmpty() ? null : values; + } + private void setRolesOfUser(List rolesOfUser, Map groupMetaAttributesCache, org.wso2.carbon.user.core.common.User user, User scimUser) throws org.wso2.carbon.user.core.UserStoreException, CharonException, diff --git a/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java b/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java index 5c6cd4c4..d0cd8fd3 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java @@ -42,12 +42,14 @@ import org.wso2.carbon.identity.configuration.mgt.core.ConfigurationManager; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.identity.core.util.IdentityUtil; +import org.wso2.carbon.identity.password.expiry.util.PasswordPolicyUtils; import org.wso2.carbon.identity.scim2.common.DAO.GroupDAO; import org.wso2.carbon.identity.scim2.common.extenstion.SCIMUserStoreErrorResolver; import org.wso2.carbon.identity.scim2.common.group.SCIMGroupHandler; import org.wso2.carbon.identity.scim2.common.internal.SCIMCommonComponentHolder; import org.wso2.carbon.user.core.UserStoreClientException; import org.wso2.carbon.user.core.common.PaginatedUserResponse; +import org.wso2.carbon.utils.multitenancy.MultitenantUtils; import org.wso2.charon3.core.exceptions.NotImplementedException; import org.wso2.charon3.core.extensions.UserManager; import org.wso2.charon3.core.objects.plainobjects.UsersGetResponse; @@ -102,6 +104,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; import static org.mockito.ArgumentMatchers.any; @@ -206,6 +209,8 @@ public class SCIMUserManagerTest { private MockedStatic claimMetadataHandler; private MockedStatic carbonConstants; private MockedStatic identityTenantUtil; + private MockedStatic passwordPolicyUtils; + private MockedStatic muliTenantUtils; private MockedStatic applicationManagementServiceMockedStatic; private MockedStatic scimCommonComponentHolder; private MockedStatic resourceManagerUtil; @@ -218,6 +223,8 @@ public void setUpMethod() { scimCommonUtils = mockStatic(SCIMCommonUtils.class); carbonConstants = mockStatic(CarbonConstants.class); identityTenantUtil = mockStatic(IdentityTenantUtil.class); + muliTenantUtils = mockStatic(MultitenantUtils.class); + passwordPolicyUtils = mockStatic(PasswordPolicyUtils.class); applicationManagementServiceMockedStatic = mockStatic(ApplicationManagementService.class); scimCommonComponentHolder = mockStatic(SCIMCommonComponentHolder.class); scimUserSchemaExtensionBuilder = mockStatic(SCIMUserSchemaExtensionBuilder.class); @@ -234,6 +241,8 @@ public void tearDown() { scimCommonUtils.close(); carbonConstants.close(); identityTenantUtil.close(); + muliTenantUtils.close(); + passwordPolicyUtils.close(); applicationManagementServiceMockedStatic.close(); scimCommonComponentHolder.close(); scimUserSchemaExtensionBuilder.close(); @@ -744,6 +753,10 @@ public void testFilteringUsersWithGETWithPagination(List SCIMCommonUtils.isConsiderMaxLimitForTotalResultEnabled()) .thenReturn(isConsiderMaxLimitForTotalResultEnabled); + muliTenantUtils.when(() -> MultitenantUtils.getTenantAwareUsername(any())).thenReturn("testUser1"); + passwordPolicyUtils.when(() ->PasswordPolicyUtils.getUserPasswordExpiryTime( + anyString(), anyString(), any(), any())).thenReturn(Optional.empty()); + Map supportedByDefaultProperties = new HashMap() {{ put("SupportedByDefault", "true"); put("ReadOnly", "true"); diff --git a/pom.xml b/pom.xml index edd8f127..7bb871c7 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,11 @@ org.wso2.carbon.identity.password.policy ${identity.governance.version} + + org.wso2.carbon.identity.governance + org.wso2.carbon.identity.password.expiry + ${identity.governance.version} + org.wso2.carbon.identity.governance org.wso2.carbon.identity.recovery