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-2013 UnboundID Corp.
026     */
027    package com.unboundid.directory.sdk.ds.scripting;
028    
029    
030    
031    import java.util.List;
032    import java.util.Set;
033    
034    import com.unboundid.directory.sdk.common.internal.Reconfigurable;
035    import com.unboundid.directory.sdk.common.types.Entry;
036    import com.unboundid.directory.sdk.common.types.OperationContext;
037    import com.unboundid.directory.sdk.ds.config.PasswordValidatorConfig;
038    import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension;
039    import com.unboundid.directory.sdk.ds.types.DirectoryServerContext;
040    import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension;
041    import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension;
042    import com.unboundid.ldap.sdk.LDAPException;
043    import com.unboundid.ldap.sdk.ResultCode;
044    import com.unboundid.util.ByteString;
045    import com.unboundid.util.Extensible;
046    import com.unboundid.util.ThreadSafety;
047    import com.unboundid.util.ThreadSafetyLevel;
048    import com.unboundid.util.args.ArgumentException;
049    import 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)
093    public 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    }