001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * docs/licenses/cddl.txt 011 * or http://www.opensource.org/licenses/cddl1.php. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * docs/licenses/cddl.txt. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Portions Copyright 2010-2024 Ping Identity Corporation 026 */ 027package com.unboundid.directory.sdk.ds.scripting; 028 029 030 031import java.util.List; 032import java.util.Set; 033 034import com.unboundid.directory.sdk.common.internal.Reconfigurable; 035import com.unboundid.directory.sdk.common.types.Entry; 036import com.unboundid.directory.sdk.common.types.OperationContext; 037import com.unboundid.directory.sdk.ds.config.PasswordValidatorConfig; 038import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension; 039import com.unboundid.directory.sdk.ds.types.DirectoryServerContext; 040import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension; 041import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension; 042import com.unboundid.ldap.sdk.LDAPException; 043import com.unboundid.ldap.sdk.ResultCode; 044import com.unboundid.ldap.sdk.unboundidds.extensions.PasswordQualityRequirement; 045import com.unboundid.util.ByteString; 046import com.unboundid.util.Extensible; 047import com.unboundid.util.ThreadSafety; 048import com.unboundid.util.ThreadSafetyLevel; 049import com.unboundid.util.args.ArgumentException; 050import com.unboundid.util.args.ArgumentParser; 051 052 053 054/** 055 * This class defines an API that must be implemented by scripted extensions 056 * which attempt to determine whether a proposed user password is acceptable. 057 * Each server password policy may be configured with zero or more password 058 * validators, and whenever a user changes his or her password (and optionally 059 * whenever an administrator resets the password for another user), then each of 060 * the password validators configured in the password policy for the target user 061 * will be given access to the clear-text password in order to determine whether 062 * that password will be allowed. Password validators will also have access to 063 * the rest of the user entry, and may also have access to a clear-text version 064 * of the user's current password(s) if they were provided in the request. 065 * <BR> 066 * <H2>Configuring Groovy-Scripted Password Validators</H2> 067 * In order to configure a scripted password validator based on this API and 068 * written in the Groovy scripting language, use a command like: 069 * <PRE> 070 * dsconfig create-password-validator \ 071 * --validator-name "<I>{validator-name}</I>" \ 072 * --type groovy-scripted \ 073 * --set enabled:true \ 074 * --set "script-class:<I>{class-name}</I>" \ 075 * --set "script-argument:<I>{name=value}</I>" 076 * </PRE> 077 * where "<I>{validator-name}</I>" is the name to use for the password validator 078 * instance, "<I>{class-name}</I>" is the fully-qualified name of the Groovy 079 * class written using this API, and "<I>{name=value}</I>" represents name-value 080 * pairs for any arguments to provide to the password validator. If multiple 081 * arguments should be provided to the password validator, then the 082 * "<CODE>--set script-argument:<I>{name=value}</I></CODE>" option should be 083 * provided multiple times. 084 * 085 * @see com.unboundid.directory.sdk.ds.api.PasswordValidator 086 */ 087@Extensible() 088@DirectoryServerExtension() 089@DirectoryProxyServerExtension(appliesToLocalContent=true, 090 appliesToRemoteContent=false) 091@SynchronizationServerExtension(appliesToLocalContent=true, 092 appliesToSynchronizedContent=false) 093@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE) 094public abstract class ScriptedPasswordValidator 095 implements Reconfigurable<PasswordValidatorConfig> 096{ 097 /** 098 * Creates a new instance of this password validator. All password validator 099 * implementations must include a default constructor, but any initialization 100 * should generally be done in the {@code initializePasswordValidator} method. 101 */ 102 public ScriptedPasswordValidator() 103 { 104 // No implementation is required. 105 } 106 107 108 109 /** 110 * {@inheritDoc} 111 */ 112 public void defineConfigArguments(final ArgumentParser parser) 113 throws ArgumentException 114 { 115 // No arguments will be allowed by default. 116 } 117 118 119 120 /** 121 * Initializes this password validator. 122 * 123 * @param serverContext A handle to the server context for the server in 124 * which this extension is running. 125 * @param config The general configuration for this password 126 * validator. 127 * @param parser The argument parser which has been initialized from 128 * the configuration for this password validator. 129 * 130 * @throws LDAPException If a problem occurs while initializing this 131 * password validator. 132 */ 133 public void initializePasswordValidator( 134 final DirectoryServerContext serverContext, 135 final PasswordValidatorConfig config, 136 final ArgumentParser parser) 137 throws LDAPException 138 { 139 // No initialization will be performed by default. 140 } 141 142 143 144 /** 145 * Performs any cleanup which may be necessary when this password validator is 146 * to be taken out of service. 147 */ 148 public void finalizePasswordValidator() 149 { 150 // No implementation is required. 151 } 152 153 154 155 /** 156 * {@inheritDoc} 157 */ 158 public boolean isConfigurationAcceptable( 159 final PasswordValidatorConfig config, 160 final ArgumentParser parser, 161 final List<String> unacceptableReasons) 162 { 163 // No extended validation will be performed. 164 return true; 165 } 166 167 168 169 /** 170 * {@inheritDoc} 171 */ 172 public ResultCode applyConfiguration(final PasswordValidatorConfig config, 173 final ArgumentParser parser, 174 final List<String> adminActionsRequired, 175 final List<String> messages) 176 { 177 // By default, no configuration changes will be applied. If there are any 178 // arguments, then add an admin action message indicating that the extension 179 // needs to be restarted for any changes to take effect. 180 if (! parser.getNamedArguments().isEmpty()) 181 { 182 adminActionsRequired.add( 183 "No configuration change has actually been applied. The new " + 184 "configuration will not take effect until this password " + 185 "validator is disabled and re-enabled or until the server is " + 186 "restarted."); 187 } 188 189 return ResultCode.SUCCESS; 190 } 191 192 193 194 /** 195 * Indicates whether this password validator should be invoked for add 196 * operations that attempt to create an entry containing one or more 197 * password values. 198 * 199 * @return {@code true} if this password validator should be invoked for 200 * add operations that include one or more passwords, or 201 * {@code false} if not. 202 */ 203 public boolean invokeForAdd() 204 { 205 return true; 206 } 207 208 209 210 /** 211 * Indicates whether this password validator should be invoked for modify or 212 * password modify operations that represent a user's attempt to change 213 * his/her own password. 214 * 215 * @return {@code true} if this password validator should be invoked for 216 * self password change operations, or {@code false} if not. 217 */ 218 public boolean invokeForSelfChange() 219 { 220 return true; 221 } 222 223 224 225 /** 226 * Indicates whether this password validator should be invoked for modify or 227 * password modify operations that represent one user's attempt to change the 228 * password for another user. 229 * 230 * @return {@code true} if this password validator should be invoked for 231 * administrative password reset operations, or {@code false} if not. 232 */ 233 public boolean invokeForAdministrativeReset() 234 { 235 return true; 236 } 237 238 239 240 /** 241 * Retrieves the password quality requirement for this password validator, if 242 * available. 243 * 244 * @return The password quality requirement for this password validator, or 245 * {@code null} if no requirement information is available. 246 */ 247 public PasswordQualityRequirement getPasswordQualityRequirement() 248 { 249 return null; 250 } 251 252 253 254 /** 255 * Indicates whether the proposed password is acceptable for the specified 256 * user. 257 * 258 * @param operationContext The operation context for the associated request. 259 * It may be associated with an add, modify, or 260 * password modify operation. 261 * @param newPassword The proposed new password for the user that 262 * should be validated. It will not be encoded or 263 * obscured in any way. 264 * @param currentPasswords The current set of passwords for the user, if 265 * available. It may be {@code null} if this is 266 * not available. Note that even if one or more 267 * current passwords are available, it may not 268 * constitute the complete set of passwords for the 269 * user. 270 * @param userEntry The entry for the user whose password is being 271 * changed. 272 * @param invalidReason A buffer to which a message may be appended to 273 * indicate why the proposed password is not 274 * acceptable. 275 * 276 * @return {@code true} if the proposed new password is acceptable, or 277 * {@code false} if not. 278 */ 279 public abstract boolean isPasswordAcceptable( 280 final OperationContext operationContext, 281 final ByteString newPassword, 282 final Set<ByteString> currentPasswords, 283 final Entry userEntry, 284 final StringBuilder invalidReason); 285}