Skip to content

Commit

Permalink
Merge pull request #841 from PasinduYeshan/feature/rule-based-passwor…
Browse files Browse the repository at this point in the history
…d-expiry

Feature - Rule-based password expiry
  • Loading branch information
PasinduYeshan authored Aug 20, 2024
2 parents 754a784 + 0e3a8f9 commit 0d6e57d
Show file tree
Hide file tree
Showing 12 changed files with 823 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Class which contains exposed identity governance services.
Expand Down Expand Up @@ -168,6 +170,7 @@ public List<ConnectorConfig> getConnectorListWithConfigs(String tenantDomain) th
Property[] properties = this.getConfiguration(tenantDomain);
List<ConnectorConfig> configs = new ArrayList<>(list.size());
String[] connectorProperties;
String connectorName;
for (int i = 0; i < list.size(); i++) {
ConnectorConfig config = new ConnectorConfig();
Map<String, String> propertyFriendlyNames = list.get(i).getPropertyNameMapping();
Expand All @@ -180,28 +183,38 @@ public List<ConnectorConfig> getConnectorListWithConfigs(String tenantDomain) th
config.setSubCategory(list.get(i).getSubCategory());
config.setOrder(list.get(i).getOrder());
connectorProperties = list.get(i).getPropertyNames();
Property[] configProperties = new Property[connectorProperties.length];
for (int j = 0; j < connectorProperties.length; j++) {
connectorName = list.get(i).getName();
List<Property> configProperties = new ArrayList<>();
Set<String> addedProperties = new HashSet<>();

for (String connectorProperty : connectorProperties) {
for (Property property : properties) {
if (connectorProperties[j].equals(property.getName())) {
configProperties[j] = property;
String resourceName = configProperties[j].getName();
configProperties[j].setDisplayName(propertyFriendlyNames.get(resourceName));
configProperties[j].setDescription(propertyDescriptions.get(resourceName));
if (metaData != null && metaData.containsKey(resourceName)) {
configProperties[j].setType(metaData.get(resourceName).getType());
configProperties[j].setRegex(metaData.get(resourceName).getRegex());
configProperties[j].setGroupId(metaData.get(resourceName).getGroupId());
if (StringUtils.isBlank(property.getName()) || addedProperties.contains(property.getName())) {
continue;
}
if (connectorProperty.equals(property.getName()) ||
(StringUtils.isNotBlank(connectorName) && property.getName().startsWith(connectorName))) {
Property configProperty = new Property();
configProperty.setName(property.getName());
configProperty.setValue(property.getValue());
configProperty.setDisplayName(propertyFriendlyNames.get(property.getName()));
configProperty.setDescription(propertyDescriptions.get(property.getName()));

if (metaData != null && metaData.containsKey(property.getName())) {
configProperty.setType(metaData.get(property.getName()).getType());
configProperty.setRegex(metaData.get(property.getName()).getRegex());
configProperty.setGroupId(metaData.get(property.getName()).getGroupId());
}
if (confidentialProperties != null &&
confidentialProperties.contains(configProperties[j].getName())) {
configProperties[j].setConfidential(true);
confidentialProperties.contains(configProperty.getName())) {
configProperty.setConfidential(true);
}
break;
configProperties.add(configProperty);
addedProperties.add(property.getName());
}
}
}
config.setProperties(configProperties);
config.setProperties(configProperties.toArray(new Property[0]));
configs.add(i, config);
}
return configs;
Expand Down
3 changes: 3 additions & 0 deletions components/org.wso2.carbon.identity.password.expiry/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
</Export-Package>
<Import-Package>
org.apache.commons.logging; version="${commons-logging.osgi.version.range}",
org.apache.commons.collections; version="${commons-collections.wso2.osgi.version.range}",
org.osgi.framework; version="${osgi.framework.imp.pkg.version.range}",
org.osgi.service.component; version="${osgi.service.component.imp.pkg.version.range}",
org.wso2.carbon.identity.event.handler;
Expand All @@ -197,6 +198,8 @@
org.wso2.carbon.user.api.*; version="${carbon.user.api.imp.pkg.version.range}",
org.wso2.carbon.identity.application.common.model.*;
version="${carbon.identity.framework.imp.pkg.version.range}",
org.wso2.carbon.identity.role.v2.mgt.core.*;
version="${carbon.identity.framework.imp.pkg.version.range}",
org.wso2.carbon.utils.multitenancy; version="${carbon.kernel.package.import.version.range}",
org.wso2.carbon.user.core.service; version="${carbon.kernel.package.import.version.range}",
org.wso2.carbon.identity.event.bean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public Map<String, String> getPropertyNameMapping() {
PasswordPolicyConstants.CONNECTOR_CONFIG_ENABLE_PASSWORD_EXPIRY_DISPLAYED_NAME);
nameMapping.put(PasswordPolicyConstants.CONNECTOR_CONFIG_PASSWORD_EXPIRY_IN_DAYS,
PasswordPolicyConstants.CONNECTOR_CONFIG_PASSWORD_EXPIRY_IN_DAYS_DISPLAYED_NAME);
nameMapping.put(PasswordPolicyConstants.CONNECTOR_CONFIG_SKIP_IF_NO_APPLICABLE_RULES,
PasswordPolicyConstants.CONNECTOR_CONFIG_SKIP_IF_NO_APPLICABLE_RULES_DISPLAYED_NAME);
return nameMapping;
}

Expand All @@ -90,6 +92,8 @@ public Map<String, String> getPropertyDescriptionMapping() {
PasswordPolicyConstants.CONNECTOR_CONFIG_ENABLE_PASSWORD_EXPIRY_DESCRIPTION);
nameMapping.put(PasswordPolicyConstants.CONNECTOR_CONFIG_PASSWORD_EXPIRY_IN_DAYS,
PasswordPolicyConstants.CONNECTOR_CONFIG_PASSWORD_EXPIRY_IN_DAYS_DESCRIPTION);
nameMapping.put(PasswordPolicyConstants.CONNECTOR_CONFIG_SKIP_IF_NO_APPLICABLE_RULES,
PasswordPolicyConstants.CONNECTOR_CONFIG_SKIP_IF_NO_APPLICABLE_RULES_DESCRIPTION);
return nameMapping;
}

Expand All @@ -107,21 +111,30 @@ public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityG
String enablePasswordExpiry = PasswordPolicyConstants.FALSE;
String passwordExpiryInDays =
String.valueOf(PasswordPolicyConstants.CONNECTOR_CONFIG_PASSWORD_EXPIRY_IN_DAYS_DEFAULT_VALUE);
String skipIfNoApplicableRules = PasswordPolicyConstants.FALSE;

String enablePasswordExpiryProperty = IdentityUtil.getProperty(
PasswordPolicyConstants.CONNECTOR_CONFIG_ENABLE_PASSWORD_EXPIRY);
String passwordExpiryInDaysProperty = IdentityUtil.getProperty(
PasswordPolicyConstants.CONNECTOR_CONFIG_PASSWORD_EXPIRY_IN_DAYS);
String skipIfNoApplicableRulesProperty = IdentityUtil.getProperty(
PasswordPolicyConstants.CONNECTOR_CONFIG_SKIP_IF_NO_APPLICABLE_RULES);

if (StringUtils.isNotBlank(enablePasswordExpiryProperty)) {
enablePasswordExpiry = enablePasswordExpiryProperty;
}
if (StringUtils.isNotBlank(passwordExpiryInDaysProperty)) {
passwordExpiryInDays = passwordExpiryInDaysProperty;
}
if (StringUtils.isNotBlank(skipIfNoApplicableRulesProperty)) {
skipIfNoApplicableRules = skipIfNoApplicableRulesProperty;
}

Map<String, String> defaultProperties = new HashMap<>();
defaultProperties.put(PasswordPolicyConstants.CONNECTOR_CONFIG_ENABLE_PASSWORD_EXPIRY, enablePasswordExpiry);
defaultProperties.put(PasswordPolicyConstants.CONNECTOR_CONFIG_PASSWORD_EXPIRY_IN_DAYS, passwordExpiryInDays);
defaultProperties.put(PasswordPolicyConstants.CONNECTOR_CONFIG_SKIP_IF_NO_APPLICABLE_RULES,
skipIfNoApplicableRules);

Properties properties = new Properties();
properties.putAll(defaultProperties);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com).
* Copyright (c) 2023-2024, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand Down Expand Up @@ -42,6 +42,12 @@ public class PasswordPolicyConstants {
"Enable Password Expiry";
public static final String CONNECTOR_CONFIG_ENABLE_PASSWORD_EXPIRY_DESCRIPTION =
"Allow users to reset the password after configured number of days";
public static final String CONNECTOR_CONFIG_SKIP_IF_NO_APPLICABLE_RULES =
"passwordExpiry.skipIfNoApplicableRules";
public static final String CONNECTOR_CONFIG_SKIP_IF_NO_APPLICABLE_RULES_DISPLAYED_NAME =
"Skip password expiry if no applicable rules";
public static final String CONNECTOR_CONFIG_SKIP_IF_NO_APPLICABLE_RULES_DESCRIPTION =
"Skip password expiry if no applicable rules are found for the user";
public static final String CONNECTOR_CONFIG_SUB_CATEGORY = "DEFAULT";
public static final String PASSWORD_EXPIRED_ERROR_MESSAGE = "Password has expired";
public static final String CONNECTOR_CONFIG_NAME = "passwordExpiry";
Expand All @@ -53,6 +59,7 @@ public class PasswordPolicyConstants {
public static final String FALSE = "false";
public static final String CONFIRMATION_QUERY_PARAM = "&confirmation=";
public static final String PASSWORD_EXPIRED_QUERY_PARAMS = "&passwordExpired=true";
public static final String PASSWORD_EXPIRY_RULES_PREFIX = "passwordExpiry.rule";

public enum ErrorMessages {
ERROR_WHILE_GETTING_USER_STORE_DOMAIN("80001",
Expand All @@ -70,7 +77,9 @@ public enum ErrorMessages {
ERROR_WHILE_UPDATING_PASSWORD("80011", "Error while updating the password"),
ERROR_RETRIEVE_PASSWORD_EXPIRED_USERS_FROM_DB("80012", "" +
"Error while retrieving password expired users from database."),
ERROR_RETRIEVE_USER_STORE_MANAGER("80013", "Error while retrieving user store manager.");
ERROR_RETRIEVE_USER_STORE_MANAGER("80013", "Error while retrieving user store manager."),
ERROR_WHILE_RETRIEVING_USER_ROLES("80014", "Error while retrieving user roles."),
ERROR_WHILE_RETRIEVING_USER_GROUPS("80015", "Error while retrieving user groups.");

private final String code;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com).
* Copyright (c) 2023-2024, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand Down Expand Up @@ -38,6 +38,7 @@
import org.wso2.carbon.identity.password.expiry.services.ExpiredPasswordIdentificationService;
import org.wso2.carbon.identity.password.expiry.services.impl.ExpiredPasswordIdentificationServiceImpl;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.identity.role.v2.mgt.core.RoleManagementService;

/**
* OSGi declarative services component which handles registration and un-registration of password enforce reset handler
Expand Down Expand Up @@ -124,4 +125,21 @@ protected void unsetIdentityDataStoreService(IdentityDataStoreService identityDa

EnforcePasswordResetComponentDataHolder.getInstance().setIdentityDataStoreService(null);
}

@Reference(
name = "role.management.service",
service = RoleManagementService.class,
cardinality = ReferenceCardinality.MANDATORY,
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetRoleManagementService"
)
protected void setRoleManagementService(RoleManagementService roleManagementService) {

EnforcePasswordResetComponentDataHolder.getInstance().setRoleManagementService(roleManagementService);
}

protected void unsetRoleManagementService(RoleManagementService roleManagementService) {

EnforcePasswordResetComponentDataHolder.getInstance().setRoleManagementService(null);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com).
* Copyright (c) 2023-2024, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand All @@ -21,6 +21,7 @@
import org.wso2.carbon.identity.governance.IdentityGovernanceService;
import org.wso2.carbon.identity.governance.service.IdentityDataStoreService;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.identity.role.v2.mgt.core.RoleManagementService;

/**
* A class to keep the data of the enforce password reset handler component.
Expand All @@ -32,6 +33,7 @@ public class EnforcePasswordResetComponentDataHolder {
private RealmService realmService = null;
private IdentityGovernanceService identityGovernanceService;
private IdentityDataStoreService identityDataStoreService;
private RoleManagementService roleManagementService;

private EnforcePasswordResetComponentDataHolder() {

Expand Down Expand Up @@ -70,4 +72,14 @@ public void setIdentityDataStoreService(IdentityDataStoreService identityDataSto

this.identityDataStoreService = identityDataStoreService;
}

public RoleManagementService getRoleManagementService() {

return roleManagementService;
}

public void setRoleManagementService(RoleManagementService roleManagementService) {

this.roleManagementService = roleManagementService;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.wso2.carbon.identity.password.expiry.models;

import org.apache.commons.lang.StringUtils;

import java.util.ArrayList;
import java.util.List;

/**
* Class to represent a password expiry rule.
*/
public class PasswordExpiryRule {

private int priority;
private int expiryDays;
private PasswordExpiryRuleAttributeEnum attribute;
private PasswordExpiryRuleOperatorEnum operator;
private List<String> values = new ArrayList<>();
private static final String RULE_SPLIT_REGEX = ",(?=(?:[^']*'[^']*')*[^']*$)";

public PasswordExpiryRule(String rule) throws IllegalArgumentException{

try {
// Rule format: "priority,expiryDays,attribute,operator,value1,value2, ...".
// At least 5 parts are required in the rule definition.
int ruleSectionLength = 4;

String[] ruleSections = rule.split(RULE_SPLIT_REGEX);
if (ruleSections.length < ruleSectionLength) {
throw new IllegalArgumentException("Invalid rule format: not enough parts in the rule definition.");
}

this.priority = Integer.parseInt(ruleSections[0].trim());
this.expiryDays = Integer.parseInt(ruleSections[1].trim());
this.attribute = PasswordExpiryRuleAttributeEnum.fromString(ruleSections[2].trim());
this.operator = PasswordExpiryRuleOperatorEnum.fromString(ruleSections[3].trim());

// Extract values from the rule removing quotes if present.
// Eg: "1,40,roles,eq,'12ec01e1-aa45,8a485d10c8fa',cc40ad49-8435-75fa1b627332".
for (int i = 4; i < ruleSections.length; i++) {
String value = ruleSections[i].trim();
if ((StringUtils.startsWith(value, "'") && StringUtils.endsWith(value, "'")) ||
(StringUtils.startsWith(value, "\"") && StringUtils.endsWith(value, "\""))) {
value = value.substring(1, value.length() - 1).trim();
}
this.values.add(value);
}

if (this.values.isEmpty()) {
throw new IllegalArgumentException("Invalid rule format: no valid values provided.");
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid rule format: " + e.getMessage());
}
}

public int getPriority() {

return priority;
}

public void setPriority(int priority) {

this.priority = priority;
}

public int getExpiryDays() {

return expiryDays;
}

public void setExpiryDays(int expiryDays) {

this.expiryDays = expiryDays;
}

public PasswordExpiryRuleAttributeEnum getAttribute() {

return attribute;
}

public void setAttribute(PasswordExpiryRuleAttributeEnum attribute) {

this.attribute = attribute;
}

public PasswordExpiryRuleOperatorEnum getOperator() {

return operator;
}

public void setOperator(PasswordExpiryRuleOperatorEnum operator) {

this.operator = operator;
}

public List<String> getValues() {

return values;
}

public void setValues(List<String> values) {

this.values = values;
}
}
Loading

0 comments on commit 0d6e57d

Please sign in to comment.