001/*
002 * Copyright 2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.unboundidds.extensions;
037
038
039
040import com.unboundid.asn1.ASN1OctetString;
041import com.unboundid.ldap.sdk.Control;
042import com.unboundid.ldap.sdk.ExtendedRequest;
043import com.unboundid.ldap.sdk.LDAPException;
044import com.unboundid.ldap.sdk.ResultCode;
045import com.unboundid.util.Debug;
046import com.unboundid.util.NotMutable;
047import com.unboundid.util.NotNull;
048import com.unboundid.util.Nullable;
049import com.unboundid.util.ThreadSafety;
050import com.unboundid.util.ThreadSafetyLevel;
051import com.unboundid.util.Validator;
052import com.unboundid.util.json.JSONField;
053import com.unboundid.util.json.JSONObject;
054
055import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
056
057
058
059/**
060 * This class provides an implementation of an extended request that may be sent
061 * to the Ping Identity Directory Server to determine whether a provided
062 * password is correct for a user without performing any other password policy
063 * processing for that user.  The server will not make any attempt to determine
064 * whether the target user's account is in a usable state, nor will it update
065 * the user's password policy state information in any way as a result of the
066 * verification attempt.
067 * <BR>
068 * <BLOCKQUOTE>
069 *   <B>NOTE:</B>  This class, and other classes within the
070 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
071 *   supported for use against Ping Identity, UnboundID, and
072 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
073 *   for proprietary functionality or for external specifications that are not
074 *   considered stable or mature enough to be guaranteed to work in an
075 *   interoperable way with other types of LDAP servers.
076 * </BLOCKQUOTE>
077 * <BR>
078 * The extended request has an OID of 1.3.6.1.4.1.30221.2.6.72. The request must
079 * have a value, which will be encoded as a JSON object with the following
080 * fields:
081 * <UL>
082 *   <LI>
083 *     {@code dn} -- The DN of the user for whom to make the determination.
084 *     This field is required to be present.
085 *   </LI>
086 *   <LI>
087 *     {@code password} -- The password to verify for the user.  This field is
088 *     required to be present.
089 * </UL>
090 * <BR>
091 * For security purposes, the server will only allow this request to be issued
092 * by a client with the necessary access control permission to do so, and who
093 * also has the {@code permit-verify-password-request} privilege.  And by
094 * default, the server will only permit clients to issue verify password
095 * requests over a secure connection.
096 * <BR><BR>
097 * In response to a verify password extended request, the server will return a
098 * generic extended response with no OID or value.  The result code included in
099 * that response should provide a suitable indication of the outcome, and in
100 * some cases, a diagnostic message may provide additional details about any
101 * issue that the server encountered.  Some of the result codes that may be
102 * returned in response to a verify password extended request include:
103 * <BR>
104 * <UL>
105 *   <LI>
106 *     {@link ResultCode#COMPARE_TRUE} -- All processing completed successfully,
107 *     and the provided password was correct for the target user.
108 *   </LI>
109 *   <LI>
110 *     {@link ResultCode#COMPARE_FALSE} -- All processing completed
111 *     successfully, but the provided password was not correct for the target
112 *     user.
113 *   </LI>
114 *   <LI>
115 *     {@link ResultCode#NO_SUCH_OBJECT} -- If the entry for the target user
116 *     does not exist.
117 *   </LI>
118 *   <LI>
119 *     {@link ResultCode#INVALID_DN_SYNTAX} -- If the target user DN cannot be
120 *     parsed as a valid DN.
121 *   </LI>
122 *   <LI>
123 *     {@link ResultCode#INAPPROPRIATE_AUTHENTICATION} -- If the target user
124 *     does not have a password.
125 *   </LI>
126 *   <LI>
127 *     {@link ResultCode#INSUFFICIENT_ACCESS_RIGHTS} -- If the requester does
128 *     not have the necessary access control permission to issue the request,
129 *     or if they do not have the {@code permit-verify-password-request}
130 *     privilege.
131 *   </LI>
132 *   <LI>
133 *     {@link ResultCode#CONFIDENTIALITY_REQUIRED} -- If the client is using an
134 *     insecure connection, but the server requires secure communication for the
135 *     request.
136 *   </LI>
137 *   <LI>
138 *     {@link ResultCode#OTHER} -- If an internal error occurred while
139 *     attempting to process the request.
140 *   </LI>
141 * </UL>
142 * <BR>
143 * <H2>Example</H2>
144 * The following example demonstrates how to use the verify password extended
145 * request to determine whether a password is correct for a user without
146 * performing any password policy processing that would normally occur for a
147 * bind operation:
148 * <BR><BR>
149 * <PRE>
150 *   public static boolean isPasswordValidForUser(
151 *               final LDAPConnection connection,
152 *               final String targetUserDN,
153 *               final String passwordToVerify)
154 *          throws LDAPException
155 *   {
156 *     final VerifyPasswordExtendedRequest verifyPasswordRequest =
157 *          new VerifyPasswordExtendedRequest(targetUserDN, passwordToVerify);
158 *
159 *     LDAPResult verifyPasswordResult;
160 *     try
161 *     {
162 *       verifyPasswordResult =
163 *            connection.processExtendedOperation(verifyPasswordRequest);
164 *     }
165 *     catch (final LDAPException e)
166 *     {
167 *       verifyPasswordResult = e.toLDAPResult();
168 *     }
169 *
170 *     final ResultCode resultCode = verifyPasswordResult.getResultCode();
171 *     if (resultCode == ResultCode.COMPARE_TRUE)
172 *     {
173 *       // The provided password is correct for the target user.
174 *       return true;
175 *     }
176 *     else if (resultCode == ResultCode.COMPARE_FALSE)
177 *     {
178 *       // The provided password is not correct for the target user.
179 *       return false;
180 *     }
181 *     else
182 *     {
183 *       // An error occurred while trying to verify the password.
184 *       throw new LDAPException(verifyPasswordResult);
185 *     }
186 *   }
187 * </PRE>
188 */
189@NotMutable()
190@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
191public final class VerifyPasswordExtendedRequest
192       extends ExtendedRequest
193{
194  /**
195   * The OID (1.3.6.1.4.1.30221.2.6.72) for the verify password extended
196   * request.
197   */
198  @NotNull public static final String VERIFY_PASSWORD_REQUEST_OID =
199       "1.3.6.1.4.1.30221.2.6.72";
200
201
202
203  /**
204   * The name of the JSON field used to specify the DN of the user for whom
205   * to make the determination.
206   */
207  @NotNull public static final String REQUEST_FIELD_DN = "dn";
208
209
210
211  /**
212   * The name of the JSON field used to specify the password for which to make
213   * the determination.
214   */
215  @NotNull public static final String REQUEST_FIELD_PASSWORD = "password";
216
217
218
219  /**
220   * The serial version UID for this serializable class.
221   */
222  private static final long serialVersionUID = 4632159563446607461L;
223
224
225
226  // The DN of the user for whom to make the determination.
227  @NotNull private final String dn;
228
229  // The password of the user for whom to make the determination.
230  @NotNull private final String password;
231
232
233
234  /**
235   * Creates a new verify password extended request with the provided
236   * information.
237   *
238   * @param  dn        The DN of the user for whom to make the determination.
239   *                   It must not be {@code null} or empty.
240   * @param  password  The password for which to make the determination.  It
241   *                   must not be {@code null} or empty.
242   * @param  controls  An optional set of controls to include in the extended
243   *                   request.  It may be {@code null} or empty if no controls
244   *                   are needed.
245   */
246  public VerifyPasswordExtendedRequest(@NotNull final String dn,
247                                       @NotNull final String password,
248                                       @Nullable final Control... controls)
249  {
250    super(VERIFY_PASSWORD_REQUEST_OID, encodeValue(dn, password), controls);
251
252    this.dn = dn;
253    this.password = password;
254  }
255
256
257
258  /**
259   * Encodes the provided information into a form sufficient for use as the
260   * value of this extended request.
261   *
262   * @param  dn        The DN of the user for whom to make the determination.
263   *                   It must not be {@code null} or empty.
264   * @param  password  The password for which to make the determination.  It
265   *                   must not be {@code null} or empty.
266   *
267   * @return  An ASN.1 octet string containing the encoded value.
268   */
269  @NotNull()
270  private static ASN1OctetString encodeValue(@NotNull final String dn,
271                                             @NotNull final String password)
272  {
273    Validator.ensureNotNullOrEmpty(dn,
274         "VerifyPasswordExtendedRequest.dn must not be null or empty");
275    Validator.ensureNotNullOrEmpty(password,
276         "VerifyPasswordExtendedRequest.password must not be null or empty");
277
278    final JSONObject requestObject = new JSONObject(
279         new JSONField(REQUEST_FIELD_DN, dn),
280         new JSONField(REQUEST_FIELD_PASSWORD, password));
281
282    return new ASN1OctetString(requestObject.toSingleLineString());
283  }
284
285
286
287  /**
288   * Attempts to decode the provided generic extended request as a verify
289   * password extended request.
290   *
291   * @param  extendedRequest  The generic extended request to decode as a verify
292   *                          password request.  It must not be {@code null}.
293   *
294   * @throws  LDAPException  If the provided request cannot be decoded as a
295   *                         verify password request.
296   */
297  public VerifyPasswordExtendedRequest(
298              @NotNull final ExtendedRequest extendedRequest)
299         throws LDAPException
300  {
301    super(extendedRequest);
302
303    final ASN1OctetString value = extendedRequest.getValue();
304    if (value == null)
305    {
306      throw new LDAPException(ResultCode.DECODING_ERROR,
307           ERR_VERIFY_PASSWORD_REQUEST_NO_VALUE.get());
308    }
309
310    final JSONObject requestObject;
311    try
312    {
313      requestObject = new JSONObject(value.stringValue());
314    }
315    catch (final Exception e)
316    {
317      Debug.debugException(e);
318
319      throw new LDAPException(ResultCode.DECODING_ERROR,
320           ERR_VERIFY_PASSWORD_REQUEST_CANNOT_DECODE_VALUE.get());
321    }
322
323    dn = requestObject.getFieldAsString(REQUEST_FIELD_DN);
324    if (dn == null)
325    {
326      throw new LDAPException(ResultCode.DECODING_ERROR,
327           ERR_VERIFY_PASSWORD_REQUEST_MISSING_FIELD.get(REQUEST_FIELD_DN));
328    }
329    else if (dn.isEmpty())
330    {
331      throw new LDAPException(ResultCode.DECODING_ERROR,
332           ERR_VERIFY_PASSWORD_REQUEST_EMPTY_FIELD.get(REQUEST_FIELD_DN));
333    }
334
335    password = requestObject.getFieldAsString(REQUEST_FIELD_PASSWORD);
336    if (password == null)
337    {
338      throw new LDAPException(ResultCode.DECODING_ERROR,
339           ERR_VERIFY_PASSWORD_REQUEST_MISSING_FIELD.get(
340                REQUEST_FIELD_PASSWORD));
341    }
342    else if (password.isEmpty())
343    {
344      throw new LDAPException(ResultCode.DECODING_ERROR,
345           ERR_VERIFY_PASSWORD_REQUEST_EMPTY_FIELD.get(REQUEST_FIELD_PASSWORD));
346    }
347  }
348
349
350
351  /**
352   * Retrieves the DN of the user for whom to verify the password.
353   *
354   * @return  The DN of the user for whom to verify the password.
355   */
356  @NotNull()
357  public String getDN()
358  {
359    return dn;
360  }
361
362
363
364  /**
365   * Retrieves the password to attempt to verify for the user.
366   *
367   * @return  The password to attempt to verify for the user.
368   */
369  @NotNull()
370  public String getPassword()
371  {
372    return password;
373  }
374
375
376
377  /**
378   * {@inheritDoc}
379   */
380  @Override()
381  @NotNull()
382  public VerifyPasswordExtendedRequest duplicate()
383  {
384    return duplicate(getControls());
385  }
386
387
388
389  /**
390   * {@inheritDoc}
391   */
392  @Override()
393  @NotNull()
394  public VerifyPasswordExtendedRequest duplicate(
395              @Nullable final Control[] controls)
396  {
397    final VerifyPasswordExtendedRequest r =
398         new VerifyPasswordExtendedRequest(dn, password, controls);
399    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
400    r.setIntermediateResponseListener(getIntermediateResponseListener());
401    r.setReferralDepth(getReferralDepth());
402    r.setReferralConnector(getReferralConnectorInternal());
403    return r;
404  }
405
406
407
408  /**
409   * {@inheritDoc}
410   */
411  @Override()
412  @NotNull()
413  public String getExtendedRequestName()
414  {
415    return INFO_EXTENDED_REQUEST_NAME_VERIFY_PASSWORD.get();
416  }
417
418
419
420  /**
421   * {@inheritDoc}
422   */
423  @Override()
424  public void toString(@NotNull final StringBuilder buffer)
425  {
426    buffer.append("VerifyPasswordExtendedRequest(dn='");
427    buffer.append(dn);
428    buffer.append('\'');
429
430    final Control[] controls = getControls();
431    if (controls.length > 0)
432    {
433      buffer.append(", controls={");
434      for (int i=0; i < controls.length; i++)
435      {
436        if (i > 0)
437        {
438          buffer.append(", ");
439        }
440
441        buffer.append(controls[i]);
442      }
443      buffer.append('}');
444    }
445
446    buffer.append(')');
447  }
448}