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