001package com.identityworksllc.iiq.common.password; 002 003import com.identityworksllc.iiq.common.logging.SLogger; 004import org.apache.commons.logging.Log; 005import sailpoint.api.PasswordPolice; 006import sailpoint.api.PasswordPolicyException; 007import sailpoint.api.SailPointContext; 008import sailpoint.api.passwordConstraints.PasswordConstraint; 009import sailpoint.object.Identity; 010import sailpoint.object.PasswordPolicy; 011import sailpoint.tools.GeneralException; 012 013import java.lang.reflect.Field; 014import java.util.ArrayList; 015import java.util.List; 016import java.util.Locale; 017import java.util.TimeZone; 018 019/** 020 * An extension of the OOTB PasswordPolice that allows adding custom constraints. Note that you 021 * should NOT reuse an instance of this class to check two different users' passwords, as the 022 * superclass does some odd stuff. 023 */ 024public class ExtendedPasswordPolice extends PasswordPolice { 025 /** 026 * Logger 027 */ 028 private static final SLogger log = new SLogger(ExtendedPasswordPolice.class); 029 030 /** 031 * The extra rules that we've added 032 */ 033 private final List<ExtendedPasswordConstraint> extraRules; 034 035 /** 036 * Constructs a new ExtendedPasswordPolice with an empty list of extra rules. 037 * 038 * @param con the SailPointContext 039 * @throws GeneralException if there is an error initializing the PasswordPolice 040 */ 041 public ExtendedPasswordPolice(SailPointContext con) throws GeneralException { 042 super(con); 043 this.extraRules = new ArrayList<>(); 044 } 045 046 /** 047 * Constructs a new ExtendedPasswordPolice with the given PasswordPolicy and an empty list of extra rules. 048 * 049 * @param con the SailPointContext 050 * @param policy the PasswordPolicy to use 051 * @throws GeneralException if there is an error initializing the PasswordPolice 052 */ 053 public ExtendedPasswordPolice(SailPointContext con, PasswordPolicy policy) throws GeneralException { 054 super(con, policy); 055 056 this.extraRules = new ArrayList<>(); 057 } 058 059 /** 060 * Adds a new password constraint to the list of rules. Since the _rules field is private, 061 * we use reflection to access it and add the new constraint. 062 * 063 * There is no uniqueness check, so if you call this method twice with the same 064 * constraint, you will end up wasting your time. 065 * 066 * @param constraint the ExtendedPasswordConstraint to add 067 * @throws GeneralException if there is an error accessing the _rules field 068 */ 069 @SuppressWarnings("unchecked") 070 public void addConstraint(ExtendedPasswordConstraint constraint) throws GeneralException { 071 try { 072 Field rulesField = PasswordPolice.class.getDeclaredField("_rules"); 073 rulesField.setAccessible(true); 074 075 List<PasswordConstraint> rules = (List<PasswordConstraint>) rulesField.get(this); 076 rules.add(constraint); 077 078 if (log.isDebugEnabled()) { 079 log.debug("Added new password constraint: " + constraint.getDescription()); 080 } 081 } catch(Exception e) { 082 throw new GeneralException("Failed to access _rules field in PasswordPolice", e); 083 } 084 } 085 086 @Override 087 public void checkPassword(Identity identity, String password, boolean isSystemAdmin) throws GeneralException { 088 if (log.isDebugEnabled()) { 089 log.debug("Checking password for identity: " + identity.getName() + ", isSystemAdmin: " + isSystemAdmin); 090 } 091 super.checkPassword(identity, password, isSystemAdmin); 092 } 093 094 /** 095 * Generates the descriptions of the various configured constraints, including any of our 096 * extra rules. 097 * 098 * @param locale the locale to use for formatting 099 * @param timeZone the time zone to use for formatting 100 * @param showNoConstraintMessage whether to show the "no constraints" message 101 * @return a list of constraint descriptions 102 */ 103 @Override 104 public List<String> getIIQPasswordConstraints(Locale locale, TimeZone timeZone, boolean showNoConstraintMessage) throws GeneralException { 105 // If we have extra rules, we never want to show the "no constraints" message 106 boolean reallyShowNoConstraintMessage = showNoConstraintMessage && this.extraRules.isEmpty(); 107 108 List<String> constraints = super.getIIQPasswordConstraints(locale, timeZone, reallyShowNoConstraintMessage); 109 for (ExtendedPasswordConstraint constraint : this.extraRules) { 110 constraints.add(constraint.getDescription()); 111 } 112 return constraints; 113 } 114 115 /** 116 * Validates the current state of the PasswordPolice, ensuring that any extra rules 117 * have been set to admin mode if the _admin field is true. 118 * 119 * Note that this will re-add all of the OOTB constraints to _rules, which suggests that 120 * SP doesn't particularly care about efficiency here. 121 * 122 * @throws GeneralException if there is an error accessing the _admin field 123 * @throws sailpoint.api.PasswordPolicyException if there are validation errors in the password policy 124 */ 125 @Override 126 public void validate() throws GeneralException { 127 try { 128 Field adminField = PasswordPolice.class.getDeclaredField("_admin"); 129 adminField.setAccessible(true); 130 131 boolean isAdmin = (boolean) adminField.get(this); 132 133 if (isAdmin) { 134 if (log.isDebugEnabled()) { 135 log.debug("Setting admin mode for all extra password constraints"); 136 } 137 for(ExtendedPasswordConstraint constraint : this.extraRules) { 138 constraint.setAdmin(true); 139 } 140 } 141 } catch(Exception e) { 142 throw new GeneralException("Failed to access _admin field in PasswordPolice", e); 143 } 144 try { 145 super.validate(); 146 } catch(PasswordPolicyException e) { 147 if (log.isDebugEnabled()) { 148 log.debug("Password policy validation failed: {0}", e); 149 } 150 } 151 } 152}