001/*
002 * Copyright 2011-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2011-2017 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.listener;
022
023
024
025import java.security.SecureRandom;
026import java.util.Arrays;
027import java.util.List;
028
029import com.unboundid.asn1.ASN1OctetString;
030import com.unboundid.ldap.matchingrules.OctetStringMatchingRule;
031import com.unboundid.ldap.sdk.Control;
032import com.unboundid.ldap.sdk.DN;
033import com.unboundid.ldap.sdk.Entry;
034import com.unboundid.ldap.sdk.ExtendedRequest;
035import com.unboundid.ldap.sdk.ExtendedResult;
036import com.unboundid.ldap.sdk.LDAPException;
037import com.unboundid.ldap.sdk.Modification;
038import com.unboundid.ldap.sdk.ModificationType;
039import com.unboundid.ldap.sdk.ResultCode;
040import com.unboundid.ldap.sdk.extensions.PasswordModifyExtendedRequest;
041import com.unboundid.ldap.sdk.extensions.PasswordModifyExtendedResult;
042import com.unboundid.util.Debug;
043import com.unboundid.util.NotMutable;
044import com.unboundid.util.StaticUtils ;
045import com.unboundid.util.ThreadSafety;
046import com.unboundid.util.ThreadSafetyLevel;
047
048import static com.unboundid.ldap.listener.ListenerMessages.*;
049
050
051
052/**
053 * This class provides an implementation of an extended operation handler for
054 * the in-memory directory server that can be used to process the password
055 * modify extended operation as defined in
056 * <A HREF="http://www.ietf.org/rfc/rfc3062.txt">RFC 3062</A>.
057 */
058@NotMutable()
059@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
060public final class PasswordModifyExtendedOperationHandler
061       extends InMemoryExtendedOperationHandler
062{
063  /**
064   * Creates a new instance of this extended operation handler.
065   */
066  public PasswordModifyExtendedOperationHandler()
067  {
068    // No initialization is required.
069  }
070
071
072
073  /**
074   * {@inheritDoc}
075   */
076  @Override()
077  public String getExtendedOperationHandlerName()
078  {
079    return "Password Modify";
080  }
081
082
083
084  /**
085   * {@inheritDoc}
086   */
087  @Override()
088  public List<String> getSupportedExtendedRequestOIDs()
089  {
090    return Arrays.asList(
091         PasswordModifyExtendedRequest.PASSWORD_MODIFY_REQUEST_OID);
092  }
093
094
095
096  /**
097   * {@inheritDoc}
098   */
099  @Override()
100  public ExtendedResult processExtendedOperation(
101                             final InMemoryRequestHandler handler,
102                             final int messageID, final ExtendedRequest request)
103  {
104    // This extended operation handler does not support any controls.  If the
105    // request has any critical controls, then reject it.
106    for (final Control c : request.getControls())
107    {
108      if (c.isCritical())
109      {
110        return new ExtendedResult(messageID,
111             ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
112             ERR_PW_MOD_EXTOP_UNSUPPORTED_CONTROL.get(c.getOID()),
113             null, null, null, null, null);
114      }
115    }
116
117
118    // Decode the request.
119    final PasswordModifyExtendedRequest pwModRequest;
120    try
121    {
122      pwModRequest = new PasswordModifyExtendedRequest(request);
123    }
124    catch (final LDAPException le)
125    {
126      Debug.debugException(le);
127      return new ExtendedResult(messageID, le.getResultCode(),
128           le.getDiagnosticMessage(), le.getMatchedDN(), le.getReferralURLs(),
129           null, null, null);
130    }
131
132
133    // Get the elements of the request.
134    final String userIdentity = pwModRequest.getUserIdentity();
135    final byte[] oldPWBytes = pwModRequest.getOldPasswordBytes();
136    final byte[] newPWBytes = pwModRequest.getNewPasswordBytes();
137
138
139    // Determine the DN of the target user.
140    final DN targetDN;
141    if (userIdentity == null)
142    {
143      targetDN = handler.getAuthenticatedDN();
144    }
145    else
146    {
147      // The user identity should generally be a DN, but we'll also allow an
148      // authorization ID.
149      DN authDN;
150      try
151      {
152        authDN = new DN(userIdentity, handler.getSchema());
153      }
154      catch (final LDAPException le)
155      {
156        Debug.debugException(le);
157        try
158        {
159          authDN = handler.getDNForAuthzID(userIdentity);
160        }
161        catch (final LDAPException le2)
162        {
163          Debug.debugException(le2);
164          return new PasswordModifyExtendedResult(messageID,
165               ResultCode.INVALID_DN_SYNTAX,
166               ERR_PW_MOD_EXTOP_CANNOT_PARSE_USER_IDENTITY.get(userIdentity),
167               null, null, null, null);
168        }
169      }
170      targetDN = authDN;
171    }
172
173    if ((targetDN == null) || targetDN.isNullDN())
174    {
175      return new PasswordModifyExtendedResult(messageID,
176           ResultCode.UNWILLING_TO_PERFORM, ERR_PW_MOD_NO_IDENTITY.get(),
177           null, null, null, null);
178    }
179
180    final Entry userEntry = handler.getEntry(targetDN);
181    if (userEntry == null)
182    {
183      return new PasswordModifyExtendedResult(messageID,
184           ResultCode.UNWILLING_TO_PERFORM,
185           ERR_PW_MOD_EXTOP_CANNOT_GET_USER_ENTRY.get(targetDN.toString()),
186           null, null, null, null);
187    }
188
189
190    // If an old password was provided, then validate it.  If not, then
191    // determine whether it is acceptable for no password to have been given.
192    if (oldPWBytes == null)
193    {
194      if (handler.getAuthenticatedDN().isNullDN())
195      {
196        return new PasswordModifyExtendedResult(messageID,
197             ResultCode.UNWILLING_TO_PERFORM,
198             ERR_PW_MOD_EXTOP_NO_AUTHENTICATION.get(), null, null, null, null);
199      }
200    }
201    else
202    {
203      if (! userEntry.hasAttributeValue("userPassword", oldPWBytes,
204           OctetStringMatchingRule.getInstance()))
205      {
206        return new PasswordModifyExtendedResult(messageID,
207             ResultCode.INVALID_CREDENTIALS, null, null, null, null, null);
208      }
209    }
210
211
212    // If no new password was provided, then generate a random password to use.
213    final byte[] pwBytes;
214    final ASN1OctetString genPW;
215    if (newPWBytes == null)
216    {
217      final SecureRandom random = new SecureRandom();
218      final byte[] pwAlphabet = StaticUtils.getBytes(
219           "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
220      pwBytes = new byte[8];
221      for (int i=0; i < pwBytes.length; i++)
222      {
223        pwBytes[i] = pwAlphabet[random.nextInt(pwAlphabet.length)];
224      }
225      genPW = new ASN1OctetString(pwBytes);
226    }
227    else
228    {
229      genPW   = null;
230      pwBytes = newPWBytes;
231    }
232
233
234    // Attempt to modify the user password.
235    try
236    {
237      handler.modifyEntry(userEntry.getDN(), Arrays.asList(new Modification(
238           ModificationType.REPLACE, "userPassword", pwBytes)));
239      return new PasswordModifyExtendedResult(messageID, ResultCode.SUCCESS,
240           null, null, null, genPW, null);
241    }
242    catch (final LDAPException le)
243    {
244      Debug.debugException(le);
245      return new PasswordModifyExtendedResult(messageID, le.getResultCode(),
246           ERR_PW_MOD_EXTOP_CANNOT_CHANGE_PW.get(userEntry.getDN(),
247                le.getMessage()),
248           null, null, null, null);
249    }
250  }
251}