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 *      Copyright 2010-2021 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}