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.api;
028
029
030
031import java.util.Collections;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035
036import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider;
037import com.unboundid.directory.sdk.common.internal.Reconfigurable;
038import com.unboundid.directory.sdk.common.internal.UnboundIDExtension;
039import com.unboundid.directory.sdk.common.types.Entry;
040import com.unboundid.directory.sdk.common.types.OperationContext;
041import com.unboundid.directory.sdk.ds.config.PasswordValidatorConfig;
042import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension;
043import com.unboundid.directory.sdk.ds.types.DirectoryServerContext;
044import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension;
045import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension;
046import com.unboundid.ldap.sdk.LDAPException;
047import com.unboundid.ldap.sdk.ResultCode;
048import com.unboundid.ldap.sdk.unboundidds.extensions.PasswordQualityRequirement;
049import com.unboundid.util.ByteString;
050import com.unboundid.util.Extensible;
051import com.unboundid.util.ThreadSafety;
052import com.unboundid.util.ThreadSafetyLevel;
053import com.unboundid.util.args.ArgumentException;
054import com.unboundid.util.args.ArgumentParser;
055
056
057
058/**
059 * This class defines an API that must be implemented by extensions which
060 * attempt to determine whether a proposed user password is acceptable.  Each
061 * server password policy may be configured with zero or more password
062 * validators, and whenever a user changes his or her password (and optionally
063 * whenever an administrator resets the password for another user), then each of
064 * the password validators configured in the password policy for the target user
065 * will be given access to the clear-text password in order to determine whether
066 * that password will be allowed.  Password validators will also have access to
067 * the rest of the user entry, and may also have access to a clear-text version
068 * of the user's current password(s) if they were provided in the request.
069 * <BR>
070 * <H2>Configuring Password Validators</H2>
071 * In order to configure a password validator created using this API, use a
072 * command like:
073 * <PRE>
074 *      dsconfig create-password-validator \
075 *           --validator-name "<I>{validator-name}</I>" \
076 *           --type third-party \
077 *           --set enabled:true \
078 *           --set "extension-class:<I>{class-name}</I>" \
079 *           --set "extension-argument:<I>{name=value}</I>"
080 * </PRE>
081 * where "<I>{validator-name}</I>" is the name to use for the password validator
082 * instance, "<I>{class-name}</I>" is the fully-qualified name of the Java class
083 * that extends {@code com.unboundid.directory.sdk.ds.api.PasswordValidator},
084 * and "<I>{name=value}</I>" represents name-value pairs for any arguments to
085 * provide to the password validator.  If multiple arguments should be provided
086 * to the password validator, then the
087 * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" option should be
088 * provided multiple times.
089 *
090 * @see  com.unboundid.directory.sdk.ds.scripting.ScriptedPasswordValidator
091 */
092@Extensible()
093@DirectoryServerExtension()
094@DirectoryProxyServerExtension(appliesToLocalContent=true,
095     appliesToRemoteContent=false)
096@SynchronizationServerExtension(appliesToLocalContent=true,
097     appliesToSynchronizedContent=false)
098@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE)
099public abstract class PasswordValidator
100       implements UnboundIDExtension, Reconfigurable<PasswordValidatorConfig>,
101                  ExampleUsageProvider
102{
103  /**
104   * Creates a new instance of this password validator.  All password validator
105   * implementations must include a default constructor, but any initialization
106   * should generally be done in the {@code initializePasswordValidator} method.
107   */
108  public PasswordValidator()
109  {
110    // No implementation is required.
111  }
112
113
114
115  /**
116   * {@inheritDoc}
117   */
118  public abstract String getExtensionName();
119
120
121
122  /**
123   * {@inheritDoc}
124   */
125  public abstract String[] getExtensionDescription();
126
127
128
129  /**
130   * {@inheritDoc}
131   */
132  public void defineConfigArguments(final ArgumentParser parser)
133         throws ArgumentException
134  {
135    // No arguments will be allowed by default.
136  }
137
138
139
140  /**
141   * Initializes this password validator.
142   *
143   * @param  serverContext  A handle to the server context for the server in
144   *                        which this extension is running.
145   * @param  config         The general configuration for this password
146   *                        validator.
147   * @param  parser         The argument parser which has been initialized from
148   *                        the configuration for this password validator.
149   *
150   * @throws  LDAPException  If a problem occurs while initializing this
151   *                         password validator.
152   */
153  public void initializePasswordValidator(
154                   final DirectoryServerContext serverContext,
155                   final PasswordValidatorConfig config,
156                   final ArgumentParser parser)
157         throws LDAPException
158  {
159    // No initialization will be performed by default.
160  }
161
162
163
164  /**
165   * {@inheritDoc}
166   */
167  public boolean isConfigurationAcceptable(final PasswordValidatorConfig config,
168                      final ArgumentParser parser,
169                      final List<String> unacceptableReasons)
170  {
171    // No extended validation will be performed by default.
172    return true;
173  }
174
175
176
177  /**
178   * {@inheritDoc}
179   */
180  public ResultCode applyConfiguration(final PasswordValidatorConfig config,
181                                       final ArgumentParser parser,
182                                       final List<String> adminActionsRequired,
183                                       final List<String> messages)
184  {
185    // By default, no configuration changes will be applied.  If there are any
186    // arguments, then add an admin action message indicating that the extension
187    // needs to be restarted for any changes to take effect.
188    if (! parser.getNamedArguments().isEmpty())
189    {
190      adminActionsRequired.add(
191           "No configuration change has actually been applied.  The new " +
192                "configuration will not take effect until this password " +
193                "validator is disabled and re-enabled or until the server " +
194                "is restarted.");
195    }
196
197    return ResultCode.SUCCESS;
198  }
199
200
201
202  /**
203   * Performs any cleanup which may be necessary when this password validator is
204   * to be taken out of service.
205   */
206  public void finalizePasswordValidator()
207  {
208    // No implementation is required.
209  }
210
211
212
213  /**
214   * Indicates whether this password validator should be invoked for add
215   * operations that attempt to create an entry containing one or more
216   * password values.
217   *
218   * @return  {@code true} if this password validator should be invoked for
219   *          add operations that include one or more passwords, or
220   *          {@code false} if not.
221   */
222  public boolean invokeForAdd()
223  {
224    return true;
225  }
226
227
228
229  /**
230   * Indicates whether this password validator should be invoked for modify or
231   * password modify operations that represent a user's attempt to change
232   * his/her own password.
233   *
234   * @return  {@code true} if this password validator should be invoked for
235   *          self password change operations, or {@code false} if not.
236   */
237  public boolean invokeForSelfChange()
238  {
239    return true;
240  }
241
242
243
244  /**
245   * Indicates whether this password validator should be invoked for modify or
246   * password modify operations that represent one user's attempt to change the
247   * password for another user.
248   *
249   * @return  {@code true} if this password validator should be invoked for
250   *          administrative password reset operations, or {@code false} if not.
251   */
252  public boolean invokeForAdministrativeReset()
253  {
254    return true;
255  }
256
257
258
259  /**
260   * Retrieves the password quality requirement for this password validator, if
261   * available.
262   *
263   * @return  The password quality requirement for this password validator, or
264   *          {@code null} if no requirement information is available.
265   */
266  public PasswordQualityRequirement getPasswordQualityRequirement()
267  {
268    return null;
269  }
270
271
272
273  /**
274   * Indicates whether the proposed password is acceptable for the specified
275   * user.
276   *
277   * @param  operationContext  The operation context for the associated request.
278   *                           It may be associated with an add, modify, or
279   *                           password modify operation.
280   * @param  newPassword       The proposed new password for the user that
281   *                           should be validated.  It will not be encoded or
282   *                           obscured in any way.
283   * @param  currentPasswords  The current set of passwords for the user, if
284   *                           available.  It may be {@code null} if this is
285   *                           not available.  Note that even if one or more
286   *                           current passwords are available, it may not
287   *                           constitute the complete set of passwords for the
288   *                           user.
289   * @param  userEntry         The entry for the user whose password is being
290   *                           changed.
291   * @param  invalidReason     A buffer to which a message may be appended to
292   *                           indicate why the proposed password is not
293   *                           acceptable.
294   *
295   * @return  {@code true} if the proposed new password is acceptable, or
296   *          {@code false} if not.
297   */
298  public abstract boolean isPasswordAcceptable(
299                               final OperationContext operationContext,
300                               final ByteString newPassword,
301                               final Set<ByteString> currentPasswords,
302                               final Entry userEntry,
303                               final StringBuilder invalidReason);
304
305
306
307  /**
308   * {@inheritDoc}
309   */
310  public Map<List<String>,String> getExamplesArgumentSets()
311  {
312    return Collections.emptyMap();
313  }
314}