Description: Demonstrates how to add custom password requirements which are not covered by out of the box Oracle Identity Manager password policy. Implementation is handled by creating a custom validation event handler on change password operations. The example given here validates that the new password does not contain the user's middle name and email.
![]() |
Validation on First Login Password Change |
![]() |
Validation on Forgot Password |
![]() |
Validation on Admin Changing User Password |
References: https://docs.oracle.com/cd/E52734_01/oim/OMDEV/oper.htm#OMDEV3085
http://docs.oracle.com/cd/E52734_01/oim/OMUSG/pwdpolicy.htm#OMUSG5478
http://docs.oracle.com/cd/E27559_01/apirefs.1112/e28159/oracle/iam/platform/Platform.html#getServiceForEventHandlers_java_lang_Class__java_lang_String__java_lang_String__java_lang_String__java_util_HashMap_
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.blogspot.oraclestack.eventhandlers; | |
import java.io.Serializable; | |
import java.util.HashMap; | |
import oracle.core.ojdl.logging.ODLLevel; | |
import oracle.core.ojdl.logging.ODLLogger; | |
import oracle.iam.platform.context.ContextAware; | |
import oracle.iam.platform.kernel.ValidationFailedException; | |
import oracle.iam.platform.kernel.spi.ValidationHandler; | |
import oracle.iam.platform.kernel.vo.BulkOrchestration; | |
import oracle.iam.platform.kernel.vo.Orchestration; | |
import com.thortech.xl.crypto.tcCryptoUtil; | |
import java.sql.Connection; | |
import java.sql.PreparedStatement; | |
import java.sql.ResultSet; | |
import java.sql.SQLException; | |
import java.util.HashSet; | |
import javax.sql.DataSource; | |
import oracle.iam.identity.usermgmt.api.UserManager; | |
import oracle.iam.identity.usermgmt.api.UserManagerConstants; | |
import oracle.iam.identity.usermgmt.vo.User; | |
import oracle.iam.platform.Platform; | |
import oracle.iam.platform.context.ContextManager; | |
import oracle.iam.platform.kernel.ValidationException; | |
/** | |
* Additional password rules which are not handled by the OOTB Password Policy. | |
* Validate if the new password meets the custom password rules. | |
* @author rayedchan | |
*/ | |
public class ChangePasswordValidationEH implements ValidationHandler | |
{ | |
// Logger | |
private static final ODLLogger LOGGER = ODLLogger.getODLLogger(ChangePasswordValidationEH.class.getName()); | |
// OIM API Services | |
// private static final UserManager USRMGR = Platform.getService(UserManager.class); | |
private static final UserManager USRMGR = Platform.getServiceForEventHandlers(UserManager.class, null, "ADMIN","ChangePasswordValidationEH", null); | |
// SQL Query | |
private static final String USER_ATTRS_SQL_QUERY = "SELECT usr_login, usr_middle_name, usr_email FROM usr where usr_key=?"; | |
@Override | |
public void validate(long processId, long eventId, Orchestration orchestration) | |
{ | |
LOGGER.log(ODLLevel.NOTIFICATION, "Version 1.0"); | |
LOGGER.log(ODLLevel.NOTIFICATION, "Enter validate() with parameters: Process Id = [{0}], Event Id = [{1}], Orchestration = [{2}]", new Object[]{processId, eventId, orchestration}); | |
Connection conn = null; | |
PreparedStatement ps = null; | |
User user = null; | |
try | |
{ | |
// Get usr_key of target user | |
String usrKey = orchestration.getTarget().getEntityId(); | |
LOGGER.log(ODLLevel.NOTIFICATION, "Target User USR_KEY: {0}", new Object[]{usrKey}); | |
// Get actor | |
String actorLogin = ContextManager.getOIMUser(); | |
LOGGER.log(ODLLevel.NOTIFICATION, "Actor Login: {0}", new Object[]{actorLogin}); | |
// Contains only the new values | |
HashMap<String, Serializable> newParameters = orchestration.getParameters(); | |
LOGGER.log(ODLLevel.TRACE, "Parameters: {0}", new Object[]{newParameters}); | |
LOGGER.log(ODLLevel.TRACE, "InterEventData: {0}", new Object[]{orchestration.getInterEventData()}); // password policy info | |
LOGGER.log(ODLLevel.TRACE, "Context: {0}", new Object[]{orchestration.getContextVal()}); | |
// Decrypt new password using the default secret key | |
String newPasswordEncrypted = getParamaterValue(newParameters, "usr_password"); | |
String newPasswordDecrypted = tcCryptoUtil.decrypt(newPasswordEncrypted, "DBSecretKey"); | |
LOGGER.log(ODLLevel.TRACE, "New Password: {0}", new Object[]{newPasswordDecrypted}); // TODO: Remove | |
// Anonymous user case E.g. Scenario Forget Password? | |
/*if("<anonymous>".equalsIgnoreCase(actorLogin)) | |
{ | |
// Get OIM database connection from data source | |
LOGGER.log(ODLLevel.NOTIFICATION, "Anonymous User"); | |
DataSource ds = Platform.getOperationalDS(); // Get OIM datasource | |
conn = ds.getConnection(); // Get connection | |
LOGGER.log(ODLLevel.TRACE, "Got database connection."); | |
// Construct Prepared Statement | |
ps = conn.prepareStatement(USER_ATTRS_SQL_QUERY); | |
ps.setString(1, usrKey); // Set parametized value usr_key | |
// Execute query | |
ResultSet rs = ps.executeQuery(); | |
// Iterate record; should be only one since usr_key is a primary key | |
while(rs.next()) | |
{ | |
// Get data from record | |
String middleName = rs.getString("usr_middle_name"); | |
String email = rs.getString("usr_email"); | |
String userLogin = rs.getString("usr_login"); | |
// Construct user object | |
user = new User(usrKey); | |
user.setEmail(email); | |
user.setLogin(userLogin); | |
user.setMiddleName(middleName); | |
} | |
} | |
// All other cases (E.g. Administrator, Self) | |
else | |
{*/ | |
// Get OIM User | |
HashSet<String> attrs = new HashSet<String>(); | |
attrs.add(UserManagerConstants.AttributeName.MIDDLENAME.getId()); // Middle Name | |
attrs.add(UserManagerConstants.AttributeName.EMAIL.getId()); // Email | |
boolean useUserLogin = false; | |
user = USRMGR.getDetails(usrKey, attrs, useUserLogin); | |
//} | |
LOGGER.log(ODLLevel.NOTIFICATION, "User: {0}", new Object[]{user}); | |
// Check password against custom rules | |
boolean validatePassword = this.customPasswordPolicy(newPasswordDecrypted, user); | |
LOGGER.log(ODLLevel.NOTIFICATION, "Is new password validate? {0}", new Object[]{validatePassword}); | |
// Validation failed | |
if(!validatePassword) | |
{ | |
throw new ValidationException("The following requirements have not been met. " + "(1) Must not contain middle name. (2) Must not contain email. "); | |
} | |
} | |
catch(Exception e) | |
{ | |
LOGGER.log(ODLLevel.ERROR, "", e); | |
throw new ValidationFailedException(e); | |
} | |
finally | |
{ | |
// Close statement | |
if(ps != null) | |
{ | |
try | |
{ | |
ps.close(); | |
} | |
catch (SQLException ex) | |
{ | |
LOGGER.log(ODLLevel.ERROR, "", ex); | |
} | |
} | |
// Close database connection | |
if(conn != null) | |
{ | |
try | |
{ | |
conn.close(); | |
} | |
catch (SQLException ex) | |
{ | |
LOGGER.log(ODLLevel.ERROR, "", ex); | |
} | |
} | |
} | |
} | |
@Override | |
public void validate(long processId, long eventId, BulkOrchestration bulkOrchestration) | |
{ | |
LOGGER.log(ODLLevel.NOTIFICATION, "[NOT SUPPORTED] Enter validate() with parameters: Process Id = [{0}], Event Id = [{1}], Bulk Orchestration = [{2}]", new Object[]{processId, eventId, bulkOrchestration}); | |
} | |
@Override | |
public void initialize(HashMap<String, String> hm) | |
{ | |
LOGGER.log(ODLLevel.NOTIFICATION, "Enter initialize: {0}", new Object[]{hm}); | |
} | |
/** | |
* ContextAware object is obtained when the actor is a regular user. | |
* If the actor is an administrator, the exact value of the attribute is obtained. | |
* @param parameters parameters from the orchestration object | |
* @param key name of User Attribute in OIM Profile or column in USR table | |
* @return value of the corresponding key in parameters | |
*/ | |
private String getParamaterValue(HashMap<String, Serializable> parameters, String key) | |
{ | |
String value = (parameters.get(key) instanceof ContextAware) | |
? (String) ((ContextAware) parameters.get(key)).getObjectValue() | |
: (String) parameters.get(key); | |
return value; | |
} | |
/** | |
* Custom Password Policy | |
* - Does not contain middle name | |
* - Does not contain email | |
* @param password Plain text password to validate | |
* @param user OIM User | |
* @return true if password requirements are met; false otherwise | |
*/ | |
private boolean customPasswordPolicy(String password, User user) | |
{ | |
// Fetch user's attributes | |
String middleName = user.getMiddleName(); // Get user's middle name | |
String email = user.getEmail(); // Get user's email | |
// Construct Regular Expression | |
String middleNameRegex = (middleName == null || "".equalsIgnoreCase(middleName)) ? "" : ".*(?i)" + middleName + ".*"; // contains, ignore case | |
String emailRegex = (email == null || "".equalsIgnoreCase(email)) ? "" : ".*(?i)" + email + ".*"; // contains, ignore case | |
// Check if password valid | |
boolean containsMiddleName = password.matches(middleNameRegex); | |
boolean containsEmail = password.matches(emailRegex); | |
boolean isValidatePassword = (!containsMiddleName) && (!containsEmail); | |
LOGGER.log(ODLLevel.TRACE, "Password contains middle name? {0}", new Object[]{containsMiddleName}); | |
LOGGER.log(ODLLevel.TRACE, "Password contains email? {0}", new Object[]{containsEmail}); | |
return isValidatePassword; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<eventhandlers xmlns="http://www.oracle.com/schema/oim/platform/kernel" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.oracle.com/schema/oim/platform/kernel orchestration-handlers.xsd"> | |
<validation-handler | |
class="com.blogspot.oraclestack.eventhandlers.ChangePasswordValidationEH" | |
entity-type="User" | |
operation="CHANGE_PASSWORD" | |
name="ChangePasswordValidationEH" | |
order="1000"/> | |
</eventhandlers> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<oimplugins xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |
<plugins pluginpoint="oracle.iam.platform.kernel.spi.EventHandler"> | |
<plugin name="ChangePasswordValidationEH" pluginclass="com.blogspot.oraclestack.eventhandlers.ChangePasswordValidationEH" version="1.0"> | |
</plugin></plugins> | |
</oimplugins> |
Troubleshooting
Anonymous User IssueError: <oracle.iam.platform.authopss.impl> <BEA-000000> <Unable to populate the self-capabilities for User :null
Issue: When trying to change the user's password through the Forgot Password? link, the custom validation event handler fails when trying to use User Manager API.
Cause: Since the actor (the internal user performing the change) is anonymous, it fails when trying to call oracle.iam.identity.usermgmt.api.UserManager.getDetails method in the custom code when service is obtained by "Platform.getService(UserManager.class)".
Workaround: The custom code has a check for <anonymous> user and performs a SQL query to get the target user's attributes or a much better approach is to use "Platform.getServiceForEventHandlers(UserManager.class, null, "ADMIN","ChangePasswordValidationEH", null)" to obtain the service.
![]() |
IAM-3040027 : An error occurred while changing the user password. java.lang.RuntimeException: Unable to populate the self-capabilities for User :null |
Null Validation Message
Issue: When trying to change password through My Information section, the validation message thrown in the custom validation event handler is not shown instead null is displayed. Looking at the logs this error may be the culprit:
<Error> <oracle.iam.platform.utils> <BEA-000000> <An error occurred while loading the parent resource bundle oracle.iam.selfservice.resources.Logging
i get the following error when event handler is triggered..
ReplyDeletejava.lang.NullPointerException
at com.thortech.xl.crypto.tcDefaultDBEncryptionImpl.getCipher(tcDefaultDBEncryptionImpl.java:121)
at com.thortech.xl.crypto.tcDefaultDBEncryptionImpl.decrypt(tcDefaultDBEncryptionImpl.java:215)
at com.thortech.xl.crypto.tcCryptoUtil.decrypt(tcCryptoUtil.java:122)
at com.thortech.xl.crypto.tcCryptoUtil.decrypt(tcCryptoUtil.java:163)
at com.wa.wahbe.oim.eventhandlers.PasswordValidate.validate(Unknown Source)
at oracle.iam.platform.kernel.impl.OrchProcessData.validate(OrchProcessData.java:258)
Any help please ?
what is the solution for Null being displayed instead of error message when the password changed from my profile section?
ReplyDeleteThe null message on the My Information page is a bug in OIM 12.2.1.3 also. I reported it. So hopefully it will get fixed.
ReplyDelete