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 2013-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;
034
035import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider;
036import com.unboundid.directory.sdk.common.internal.Reconfigurable;
037import com.unboundid.directory.sdk.common.internal.UnboundIDExtension;
038import com.unboundid.directory.sdk.common.types.Entry;
039import com.unboundid.directory.sdk.ds.config.PasswordStorageSchemeConfig;
040import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension;
041import com.unboundid.directory.sdk.ds.types.DirectoryServerContext;
042import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension;
043import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension;
044import com.unboundid.ldap.sdk.LDAPException;
045import com.unboundid.ldap.sdk.Modification;
046import com.unboundid.ldap.sdk.ResultCode;
047import com.unboundid.util.ByteString;
048import com.unboundid.util.Extensible;
049import com.unboundid.util.ThreadSafety;
050import com.unboundid.util.ThreadSafetyLevel;
051import com.unboundid.util.args.ArgumentException;
052import com.unboundid.util.args.ArgumentParser;
053
054
055
056/**
057 * This class defines an API that may be implemented by extensions which provide
058 * enhanced password storage scheme support.  Although the older
059 * {@link PasswordStorageScheme} API is still supported, this enhanced version
060 * is both simpler and provides additional functionality, including providing
061 * access to the user entry (which may be useful, for example, if salt or
062 * encryption information is to be derived from other content in the entry, or
063 * from another repository that can be accessed based on content in the entry).
064 * However, this API is generally simpler than the {@code PasswordStorageScheme}
065 * API, so it may be desirable for all new implementations to implement this API
066 * instead.
067 * <BR><BR>
068 * Encoded passwords may take one of two forms.  The first is the "user
069 * password" syntax, in which the encoded password is represented by the name of
070 * the storage scheme in curly braces followed by the transformed password
071 * (e.g., "{scheme}encoded").  This format isn't based on any defined standard,
072 * but is commonly used by a number of directory server implementations.  The
073 * second format is the authentication password syntax as described in RFC 3112,
074 * in which the encoded representation is broken into scheme, authInfo, and
075 * authValue segments separated by dollar signs (e.g.,
076 * "scheme$authInfo$authValue").  All password storage schemes are required to
077 * support the "user password" syntax and may optionally also support the
078 * authentication password syntax.
079 * <BR>
080 * <H2>Configuring Enhanced Password Storage Schemes</H2>
081 * In order to configure a password storage scheme created using this API, use
082 * a command like:
083 * <PRE>
084 *      dsconfig create-password-storage-scheme \
085 *           --scheme-name "<I>{scheme-name}</I>" \
086 *           --type third-party-enhanced \
087 *           --set enabled:true \
088 *           --set "extension-class:<I>{class-name}</I>" \
089 *           --set "extension-argument:<I>{name=value}</I>"
090 * </PRE>
091 * where "<I>{scheme-name}</I>" is the name to use for the password storage
092 * scheme instance, "<I>{class-name}</I>" is the fully-qualified name of the
093 * Java class that extends
094 * {@code com.unboundid.directory.sdk.ds.api.PasswordStorageScheme}, and
095 * "<I>{name=value}</I>" represents name-value pairs for any arguments to
096 * provide to the password storage scheme.  If multiple arguments should be
097 * provided to the password storage scheme, then the
098 * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" option should be
099 * provided multiple times.
100 *
101 * @see PasswordStorageScheme
102 */
103@Extensible()
104@DirectoryServerExtension()
105@DirectoryProxyServerExtension(appliesToLocalContent=true,
106     appliesToRemoteContent=false)
107@SynchronizationServerExtension(appliesToLocalContent=true,
108     appliesToSynchronizedContent=false)
109@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE)
110public abstract class EnhancedPasswordStorageScheme
111       implements UnboundIDExtension,
112                  Reconfigurable<PasswordStorageSchemeConfig>,
113                  ExampleUsageProvider
114{
115  /**
116   * Creates a new instance of this password storage scheme.  All password
117   * storage scheme implementations must include a default constructor, but any
118   * initialization should generally be done in the
119   * {@code initializePasswordStorageScheme} method.
120   */
121  public EnhancedPasswordStorageScheme()
122  {
123    // No implementation is required.
124  }
125
126
127
128  /**
129   * {@inheritDoc}
130   */
131  public abstract String getExtensionName();
132
133
134
135  /**
136   * {@inheritDoc}
137   */
138  public abstract String[] getExtensionDescription();
139
140
141
142  /**
143   * {@inheritDoc}
144   */
145  public void defineConfigArguments(final ArgumentParser parser)
146         throws ArgumentException
147  {
148    // No arguments will be allowed by default.
149  }
150
151
152
153  /**
154   * Initializes this password storage scheme.
155   *
156   * @param  serverContext  A handle to the server context for the server in
157   *                        which this extension is running.
158   * @param  config         The general configuration for this password storage
159   *                        scheme.
160   * @param  parser         The argument parser which has been initialized from
161   *                        the configuration for this password storage scheme.
162   *
163   * @throws  LDAPException  If a problem occurs while initializing this
164   *                         password storage scheme.
165   */
166  public void initializePasswordStorageScheme(
167                   final DirectoryServerContext serverContext,
168                   final PasswordStorageSchemeConfig config,
169                   final ArgumentParser parser)
170         throws LDAPException
171  {
172    // No initialization will be performed by default.
173  }
174
175
176
177  /**
178   * {@inheritDoc}
179   */
180  public boolean isConfigurationAcceptable(
181                      final PasswordStorageSchemeConfig config,
182                      final ArgumentParser parser,
183                      final List<String> unacceptableReasons)
184  {
185    // No extended validation will be performed by default.
186    return true;
187  }
188
189
190
191  /**
192   * {@inheritDoc}
193   */
194  public ResultCode applyConfiguration(final PasswordStorageSchemeConfig config,
195                                       final ArgumentParser parser,
196                                       final List<String> adminActionsRequired,
197                                       final List<String> messages)
198  {
199    // By default, no configuration changes will be applied.  If there are any
200    // arguments, then add an admin action message indicating that the extension
201    // needs to be restarted for any changes to take effect.
202    if (! parser.getNamedArguments().isEmpty())
203    {
204      adminActionsRequired.add(
205           "No configuration change has actually been applied.  The new " +
206                "configuration will not take effect until this password " +
207                "storage scheme is disabled and re-enabled or until the " +
208                "server is restarted.");
209    }
210
211    return ResultCode.SUCCESS;
212  }
213
214
215
216  /**
217   * Performs any cleanup which may be necessary when this password storage
218   * scheme is to be taken out of service.
219   */
220  public void finalizePasswordStorageScheme()
221  {
222    // No implementation is required.
223  }
224
225
226
227  /**
228   * Retrieves the name for this password storage scheme.  This will be the
229   * identifier which appears in curly braces at the beginning of the encoded
230   * password.  The name should not include curly braces.
231   *
232   * @return  The name for this password storage scheme.
233   */
234  public abstract String getStorageSchemeName();
235
236
237
238  /**
239   * Indicates whether this password storage scheme encodes passwords in a form
240   * that allows the original plaintext value to be obtained from the encoded
241   * representation.
242   *
243   * @return  {@code true} if the original plaintext password may be obtained
244   *          from the encoded password, or {@code false} if not.
245   */
246  public abstract boolean isReversible();
247
248
249
250  /**
251   * Indicates whether this password storage scheme encodes passwords in a form
252   * that may be considered secure.  A storage scheme should only be considered
253   * secure if it is not possible to trivially determine a clear-text value
254   * which may be used to generate a given encoded representation.
255   *
256   * @return  {@code true} if this password storage scheme may be considered
257   *          secure, or {@code false} if not.
258   */
259  public abstract boolean isSecure();
260
261
262
263  /**
264   * Indicates whether this password storage scheme requires access to the user
265   * entry in order to perform processing.  If the storage scheme does require
266   * access to the user entry (e.g., in order to obtain key or salt information
267   * from another attribute, or to use information in the entry to access
268   * information in another data store), then some features (e.g., the ability
269   * to perform extensible matching with password values, or the ability to use
270   * the encode-password tool to encode and compare passwords) may not be
271   * available.
272   * <BR><BR>
273   * Note that if this method returns {@code true}, then the server will never
274   * provide a {@code null} value for the {@code userEntry} parameter to methods
275   * that accept it.  However, if this method returns {@code false}, then there
276   * may be some instances in which the server may invoke those methods with a
277   * {@code null} value for the {@code userEntry} parameter (e.g., when invoked
278   * by the encode-password tool or when targeting a password with an extensible
279   * match search filter).
280   *
281   * @return  {@code true} if this password storage scheme requires access to
282   *          the user entry, or {@code false} if not.
283   */
284  public boolean requiresUserEntry()
285  {
286    return false;
287  }
288
289
290
291  /**
292   * Encodes the provided plaintext password for this storage scheme,
293   * without the name of the associated scheme.  Note that the
294   * provided plaintext password should not be altered in any way.
295   *
296   * @param  plaintext      The plaintext password to be encoded.  It must not
297   *                        be {@code null}.  Note that there is no guarantee
298   *                        that password validators have yet been invoked for
299   *                        this password, so this password storage scheme
300   *                        implementation should not make any assumptions about
301   *                        the format of the plaintext password or whether it
302   *                        will actually be allowed for use in the entry.
303   * @param  userEntry      The complete entry for the user for whom the
304   *                        password is to be encoded.  This will not be
305   *                        {@code null} for schemes in which
306   *                        {@link #requiresUserEntry} returns {@code true}.
307   * @param  modifications  The set of modifications to be applied to the user
308   *                        entry.  This will generally be non-{@code null} only
309   *                        for operations that use a modify to change the user
310   *                        password.  If a modification list is provided, then
311   *                        it will contain the complete set of modifications
312   *                        for the operation, some of which may have no
313   *                        impact on password encoding.
314   * @param  deterministic  Indicates whether the password encoding should be
315   *                        deterministic.  If this is {@code true}, then the
316   *                        scheme should attempt to generate a consistent
317   *                        encoding for the password (e.g., by determining the
318   *                        salt from a normalized representation of the user's
319   *                        DN).  This will be used during LDIF import
320   *                        processing (and potentially at other times) to help
321   *                        ensure that if the LDIF file is imported into
322   *                        multiple servers, any clear-text password
323   *                        encountered in the LDIF file will have the same
324   *                        encoded representation on all servers.  Some
325   *                        password storage schemes may choose to ignore this
326   *                        property if it does not apply or is not possible to
327   *                        achieve.
328   * @param  includeScheme  Indicates whether to include the name of the scheme
329   *                        in curly braces before the encoded password.
330   *
331   * @return  The password that has been encoded using this storage
332   *          scheme.
333   *
334   * @throws  LDAPException  If a problem occurs while attempting to encode the
335   *                         password.
336   */
337  public abstract ByteString encodePassword(final ByteString plaintext,
338                                  final Entry userEntry,
339                                  final List<Modification> modifications,
340                                  final boolean deterministic,
341                                  final boolean includeScheme)
342         throws LDAPException;
343
344
345
346  /**
347   * Indicates whether the provided plaintext password could have been used to
348   * generate the given encoded password.
349   *
350   * @param  plaintextPassword  The plaintext password provided by the user as
351   *                            part of a simple bind attempt.
352   * @param  storedPassword     The stored password to compare against the
353   *                            provided plaintext password.  It will not
354   *                            include the scheme name in curly braces.
355   * @param  userEntry          The complete entry for the user for whom the
356   *                            password is to be validated.  This will not be
357   *                            {@code null} for schemes in which
358   *                            {@link #requiresUserEntry} returns {@code true}.
359   *
360   * @return  {@code true} if the provided clear-text password could have been
361   *          used to generate the encoded password, or {@code false} if not.
362   */
363  public abstract boolean passwordMatches(final ByteString plaintextPassword,
364                                          final ByteString storedPassword,
365                                          final Entry userEntry);
366
367
368
369  /**
370   * Indicates whether the provided password is encoded with the current
371   * settings for this password storage scheme.
372   *
373   * @param  encodedPassword  The encoded password for which to make the
374   *                          determination.  This will not be {@code null}, and
375   *                          it will not include the scheme name in curly
376   *                          braces.
377   * @param  userEntry        The complete entry for the user with whom the
378   *                          password is associated.  This will not be
379   *                          {@code null} for schemes in which
380   *                          {@link #requiresUserEntry()} returns {@code true}.
381   *                          The entry should not be altered in any way by this
382   *                          password storage scheme.
383   * @param  explanations     A list that may be updated with messages that may
384   *                          help explain the result that this method returns.
385   *                          This may include messages that explain why the
386   *                          provided password is not encoded with the current
387   *                          settings or why the determination could not be
388   *                          made.  This will not be {@code null}, and it will
389   *                          be updatable.
390   *
391   * @return  {@code true} if the provided password is encoded wit the current
392   *          settings for this password storage scheme, {@code false} if it is
393   *          not encoded with the current settings for this password storage
394   *          scheme, or {@code null} if the determination cannot be made (e.g.,
395   *          because this method is not implemented for this password storage
396   *          scheme, or because an error occurred while attempting to make
397   *          the determination).
398   */
399  public Boolean isPasswordEncodedWithCurrentSettings(
400              final ByteString encodedPassword,
401              final Entry userEntry,
402              final List<String> explanations)
403  {
404    explanations.add("The " + getStorageSchemeName() + " password storage " +
405         "scheme does not support determining whether a stored password is " +
406         "encoded with the current settings");
407    return null;
408  }
409
410
411
412  /**
413   * Attempts to determine the plaintext password used to generate the provided
414   * encoded password.  This method should only be called if the
415   * {@link #isReversible} method returns {@code true}.
416   *
417   * @param  storedPassword  The password for which to obtain the plaintext
418   *                         value.  It should not include the scheme name in
419   *                         curly braces.
420   * @param  userEntry       The complete entry for the user for whom the
421   *                         password is to be validated.  This will not be
422   *                         {@code null} for schemes in which
423   *                         {@link #requiresUserEntry} returns {@code true}.
424   *
425   * @return  The plaintext password obtained from the given encoded password.
426   *
427   * @throws  LDAPException  If this password storage scheme is not reversible,
428   *                         or if the provided value could not be decoded to
429   *                         its plaintext representation.
430   */
431  public abstract ByteString getPlaintextValue(final ByteString storedPassword,
432                                               final Entry userEntry)
433         throws LDAPException;
434
435
436
437  /**
438   * Indicates whether this password storage scheme provides the ability to
439   * encode passwords in the authentication password syntax as described in RFC
440   * 3112.
441   *
442   * @return  {@code true} if this password storage scheme supports the
443   *          authentication password syntax, or {@code false} if not.
444   */
445  public boolean supportsAuthPasswordSyntax()
446  {
447    return false;
448  }
449
450
451
452  /**
453   * Retrieves the name that should be used to identify this password storage
454   * scheme when encoding passwords using the authentication password syntax as
455   * described in RFC 3112.  This should only be used if the
456   * {@link #supportsAuthPasswordSyntax} method returns {@code true}.
457   *
458   * @return  The name that should be used to identify this password storage
459   *          scheme when encoding passwords using the authentication password
460   *          syntax.
461   */
462  public String getAuthPasswordSchemeName()
463  {
464    return getStorageSchemeName();
465  }
466
467
468
469  /**
470   * Encodes the provided plaintext password using the authentication password
471   * syntax as defined in RFC 3112.  This should only be used if the
472   * {@link #supportsAuthPasswordSyntax} method returns {@code true}.
473   *
474   * @param  plaintext      The plaintext password to be encoded.  It must not
475   *                        be {@code null}.  Note that there is no guarantee
476   *                        that password validators have yet been invoked for
477   *                        this password, so this password storage scheme
478   *                        implementation should not make any assumptions about
479   *                        the format of the plaintext password or whether it
480   *                        will actually be allowed for use in the entry.
481   * @param  userEntry      The complete entry for the user for whom the
482   *                        password is to be encoded.  This will not be
483   *                        {@code null} for schemes in which
484   *                        {@link #requiresUserEntry} returns {@code true}.
485   * @param  modifications  The set of modifications to be applied to the user
486   *                        entry.  This will generally be non-{@code null} only
487   *                        for operations that use a modify to change the user
488   *                        password.  If a modification list is provided, then
489   *                        it will contain the complete set of modifications
490   *                        for the operation, some of which may have no
491   *                        impact on password encoding.
492   * @param  deterministic  Indicates whether the password encoding should be
493   *                        deterministic.  If this is {@code true}, then the
494   *                        scheme should attempt to generate a consistent
495   *                        encoding for the password (e.g., by determining the
496   *                        salt from a normalized representation of the user's
497   *                        DN).  This will be used during LDIF import
498   *                        processing (and potentially at other times) to help
499   *                        ensure that if the LDIF file is imported into
500   *                        multiple servers, any clear-text password
501   *                        encountered in the LDIF file will have the same
502   *                        encoded representation on all servers.  Some
503   *                        password storage schemes may choose to ignore this
504   *                        property if it does not apply or is not possible to
505   *                        achieve.
506   *
507   * @return  The encoded representation of the provided password.
508   *
509   * @throws  LDAPException  If a problem occurs while encoding the provided
510   *                         password, or if this password storage scheme does
511   *                         not support the authentication password syntax.
512   */
513  public ByteString encodeAuthPassword(final ByteString plaintext,
514                                       final Entry userEntry,
515                                       final List<Modification> modifications,
516                                       final boolean deterministic)
517         throws LDAPException
518  {
519    throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
520         "This password storage scheme does not support the use of the " +
521         "authentication password syntax.");
522  }
523
524
525
526  /**
527   * Indicates whether the provided plaintext password may be used to generate
528   * an encoded password with the given authInfo and authValue elements when
529   * using the authentication password syntax as defined in RFC 3112.  This
530   * should only be used if the {@link #supportsAuthPasswordSyntax} method
531   * returns {@code true}.
532   *
533   * @param  plaintextPassword  The plaintext password provided by the user.
534   * @param  authInfo           The authInfo component of the password encoded
535   *                            in the authentication password syntax.
536   * @param  authValue          The authValue component of the password encoded
537   *                            in the authentication password syntax.
538   * @param  userEntry          The complete entry for the user for whom the
539   *                            password is to be validated.  This will not be
540   *                            {@code null} for schemes in which
541   *                            {@link #requiresUserEntry} returns {@code true}.
542   *
543   * @return  {@code true} if the provided plaintext password could be used to
544   *          generate an encoded password with the given authInfo and authValue
545   *          portions, or {@code false} if not.
546   */
547  public boolean authPasswordMatches(final ByteString plaintextPassword,
548                                     final String authInfo,
549                                     final String authValue,
550                                     final Entry userEntry)
551  {
552    return false;
553  }
554
555
556
557  /**
558   * Indicates whether the provided password is encoded with the current
559   * settings for this password storage scheme using the authentication password
560   * syntax.
561   *
562   * @param  authInfo      The authInfo component of the password encoded in the
563   *                       authentication password syntax.
564   * @param  authValue     The authValue component of the password encoded in
565   *                       the authentication password syntax.
566   * @param  userEntry     The complete entry for the user with whom the
567   *                       password is associated.  This will not be
568   *                       {@code null} for wchemes in which
569   *                       {@link #requiresUserEntry()} returns {@code true}.
570   *                       The entry should not be altered in any way by this
571   *                       password storage scheme.
572   * @param  explanations  A list that may be updated with messages that may
573   *                       help explain the result that this method returns.
574   *                       This may include messages that explain why the
575   *                       provided password is not encoded with the current
576   *                       settings or why the determination could not be made.
577   *                       This will not be {@code null}, and it will be
578   *                       updatable.
579   *
580   * @return  {@code true} if the provided password is encoded wit the current
581   *          settings for this password storage scheme, {@code false} if it is
582   *          not encoded with the current settings for this password storage
583   *          scheme, or {@code null} if the determination cannot be made (e.g.,
584   *          because this method is not implemented for this password storage
585   *          scheme, or because an error occurred while attempting to make
586   *          the determination).
587   */
588  public Boolean isAuthPasswordEncodedWithCurrentSettings(
589              final String authInfo, final String authValue,
590              final Entry userEntry,
591              final List<String> explanations)
592  {
593    explanations.add("The " + getAuthPasswordSchemeName() + " password " +
594         "storage scheme does not support determining whether a stored " +
595         "password is encoded with the current settings");
596    return null;
597  }
598
599
600
601  /**
602   * Obtains the plaintext password that was used to generate an encoded
603   * password with the given authInfo and authValue elements when using the
604   * authentication password syntax as described in RFC 3112.  This should only
605   * be used if both the {@link #supportsAuthPasswordSyntax} and
606   * {@link #isReversible} methods return {@code true}.
607   *
608   * @param  authInfo   The authInfo component of the password encoded in the
609   *                    authentication password syntax.
610   * @param  authValue  The authValue component of the password encoded in the
611   *                    authentication password syntax.
612   * @param  userEntry  The complete entry for the user for whom the password is
613   *                    to be validated.  This will not be {@code null} for
614   *                    schemes in which {@link #requiresUserEntry} returns
615   *                    {@code true}.  The entry should not be altered in any
616   *                    way by this password storage scheme.
617   *
618   * @return  The plaintext password that was used to generate the encoded
619   *          password.
620   *
621   * @throws  LDAPException  If this password storage scheme is not reversible,
622   *                         if this password storage scheme does not support
623   *                         the authentication password syntax, or if some
624   *                         other problem is encountered while attempting to
625   *                         determine the plaintext password.
626   */
627  public ByteString getAuthPasswordPlaintextValue(final String authInfo,
628                                                  final String authValue,
629                                                  final Entry userEntry)
630         throws LDAPException
631  {
632    throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
633         "This password storage scheme does not support the use of the " +
634         "authentication password syntax.");
635  }
636
637
638
639  /**
640   * {@inheritDoc}
641   */
642  public Map<List<String>,String> getExamplesArgumentSets()
643  {
644    return Collections.emptyMap();
645  }
646}