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-2015 UnboundID Corp.
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.util.ByteString;
045import com.unboundid.util.Extensible;
046import com.unboundid.util.ThreadSafety;
047import com.unboundid.util.ThreadSafetyLevel;
048import com.unboundid.util.args.ArgumentException;
049import com.unboundid.util.args.ArgumentParser;
050
051
052
053/**
054 * This class defines an API that must be implemented by scripted extensions
055 * which attempt to determine whether a proposed user password is acceptable.
056 * Each server password policy may be configured with zero or more password
057 * validators, and whenever a user changes his or her password (and optionally
058 * whenever an administrator resets the password for another user), then each of
059 * the password validators configured in the password policy for the target user
060 * will be given access to the clear-text password in order to determine whether
061 * that password will be allowed.  Password validators will also have access to
062 * the rest of the user entry, and may also have access to a clear-text version
063 * of the user's current password(s) if they were provided in the request.
064 * <BR>
065 * <H2>Configuring Groovy-Scripted Password Validators</H2>
066 * In order to configure a scripted password validator based on this API and
067 * written in the Groovy scripting language, use a command like:
068 * <PRE>
069 *      dsconfig create-password-validator \
070 *           --validator-name "<I>{validator-name}</I>" \
071 *           --type groovy-scripted \
072 *           --set enabled:true \
073 *           --set "script-class:<I>{class-name}</I>" \
074 *           --set "script-argument:<I>{name=value}</I>"
075 * </PRE>
076 * where "<I>{validator-name}</I>" is the name to use for the password validator
077 * instance, "<I>{class-name}</I>" is the fully-qualified name of the Groovy
078 * class written using this API, and "<I>{name=value}</I>" represents name-value
079 * pairs for any arguments to provide to the password validator.  If multiple
080 * arguments should be provided to the password validator, then the
081 * "<CODE>--set script-argument:<I>{name=value}</I></CODE>" option should be
082 * provided multiple times.
083 *
084 * @see  com.unboundid.directory.sdk.ds.api.PasswordValidator
085 */
086@Extensible()
087@DirectoryServerExtension()
088@DirectoryProxyServerExtension(appliesToLocalContent=true,
089     appliesToRemoteContent=false)
090@SynchronizationServerExtension(appliesToLocalContent=true,
091     appliesToSynchronizedContent=false)
092@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE)
093public abstract class ScriptedPasswordValidator
094       implements Reconfigurable<PasswordValidatorConfig>
095{
096  /**
097   * Creates a new instance of this password validator.  All password validator
098   * implementations must include a default constructor, but any initialization
099   * should generally be done in the {@code initializePasswordValidator} method.
100   */
101  public ScriptedPasswordValidator()
102  {
103    // No implementation is required.
104  }
105
106
107
108  /**
109   * {@inheritDoc}
110   */
111  public void defineConfigArguments(final ArgumentParser parser)
112         throws ArgumentException
113  {
114    // No arguments will be allowed by default.
115  }
116
117
118
119  /**
120   * Initializes this password validator.
121   *
122   * @param  serverContext  A handle to the server context for the server in
123   *                        which this extension is running.
124   * @param  config         The general configuration for this password
125   *                        validator.
126   * @param  parser         The argument parser which has been initialized from
127   *                        the configuration for this password validator.
128   *
129   * @throws  LDAPException  If a problem occurs while initializing this
130   *                         password validator.
131   */
132  public void initializePasswordValidator(
133                   final DirectoryServerContext serverContext,
134                   final PasswordValidatorConfig config,
135                   final ArgumentParser parser)
136         throws LDAPException
137  {
138    // No initialization will be performed by default.
139  }
140
141
142
143  /**
144   * Performs any cleanup which may be necessary when this password validator is
145   * to be taken out of service.
146   */
147  public void finalizePasswordValidator()
148  {
149    // No implementation is required.
150  }
151
152
153
154  /**
155   * {@inheritDoc}
156   */
157  public boolean isConfigurationAcceptable(
158                      final PasswordValidatorConfig config,
159                      final ArgumentParser parser,
160                      final List<String> unacceptableReasons)
161  {
162    // No extended validation will be performed.
163    return true;
164  }
165
166
167
168  /**
169   * {@inheritDoc}
170   */
171  public ResultCode applyConfiguration(final PasswordValidatorConfig config,
172                                       final ArgumentParser parser,
173                                       final List<String> adminActionsRequired,
174                                       final List<String> messages)
175  {
176    // By default, no configuration changes will be applied.  If there are any
177    // arguments, then add an admin action message indicating that the extension
178    // needs to be restarted for any changes to take effect.
179    if (! parser.getNamedArguments().isEmpty())
180    {
181      adminActionsRequired.add(
182           "No configuration change has actually been applied.  The new " +
183                "configuration will not take effect until this password " +
184                "validator is disabled and re-enabled or until the server is " +
185                "restarted.");
186    }
187
188    return ResultCode.SUCCESS;
189  }
190
191
192
193  /**
194   * Indicates whether the proposed password is acceptable for the specified
195   * user.
196   *
197   * @param  operationContext  The operation context for the associated request.
198   *                           It may be associated with an add, modify, or
199   *                           password modify operation.
200   * @param  newPassword       The proposed new password for the user that
201   *                           should be validated.  It will not be encoded or
202   *                           obscured in any way.
203   * @param  currentPasswords  The current set of passwords for the user, if
204   *                           available.  It may be {@code null} if this is
205   *                           not available.  Note that even if one or more
206   *                           current passwords are available, it may not
207   *                           constitute the complete set of passwords for the
208   *                           user.
209   * @param  userEntry         The entry for the user whose password is being
210   *                           changed.
211   * @param  invalidReason     A buffer to which a message may be appended to
212   *                           indicate why the proposed password is not
213   *                           acceptable.
214   *
215   * @return  {@code true} if the proposed new password is acceptable, or
216   *          {@code false} if not.
217   */
218  public abstract boolean isPasswordAcceptable(
219                               final OperationContext operationContext,
220                               final ByteString newPassword,
221                               final Set<ByteString> currentPasswords,
222                               final Entry userEntry,
223                               final StringBuilder invalidReason);
224}