001/* 002 * Copyright 2011-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2011-2014 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}