View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.server.ldap.handlers.extended;
21  
22  
23  import java.util.Collections;
24  import java.util.HashSet;
25  import java.util.Set;
26  
27  import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyRequest;
28  import org.apache.directory.api.ldap.extras.extended.pwdModify.PasswordModifyRequest;
29  import org.apache.directory.api.ldap.extras.extended.pwdModify.PasswordModifyResponse;
30  import org.apache.directory.api.ldap.extras.extended.pwdModify.PasswordModifyResponseImpl;
31  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
32  import org.apache.directory.api.ldap.model.entry.Attribute;
33  import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
34  import org.apache.directory.api.ldap.model.entry.DefaultModification;
35  import org.apache.directory.api.ldap.model.entry.Entry;
36  import org.apache.directory.api.ldap.model.entry.Modification;
37  import org.apache.directory.api.ldap.model.entry.ModificationOperation;
38  import org.apache.directory.api.ldap.model.entry.Value;
39  import org.apache.directory.api.ldap.model.exception.LdapException;
40  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
41  import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
42  import org.apache.directory.api.ldap.model.exception.LdapOperationException;
43  import org.apache.directory.api.ldap.model.message.Control;
44  import org.apache.directory.api.ldap.model.message.ModifyRequest;
45  import org.apache.directory.api.ldap.model.message.ModifyRequestImpl;
46  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
47  import org.apache.directory.api.ldap.model.name.Dn;
48  import org.apache.directory.api.ldap.model.password.PasswordUtil;
49  import org.apache.directory.api.util.Strings;
50  import org.apache.directory.server.core.api.CoreSession;
51  import org.apache.directory.server.core.api.DirectoryService;
52  import org.apache.directory.server.core.api.interceptor.context.BindOperationContext;
53  import org.apache.directory.server.core.shared.DefaultCoreSession;
54  import org.apache.directory.server.ldap.ExtendedOperationHandler;
55  import org.apache.directory.server.ldap.LdapServer;
56  import org.apache.directory.server.ldap.LdapSession;
57  import org.apache.mina.core.session.IoSession;
58  import org.slf4j.Logger;
59  import org.slf4j.LoggerFactory;
60  
61  
62  /**
63   * An handler to manage PwdModifyRequest. Users can send a pwdModify request
64   * for their own passwords, or for another people password. Only admin can
65   * change someone else password without having to provide the original password.
66   *
67   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
68   */
69  public class PwdModifyHandler implements ExtendedOperationHandler<PasswordModifyRequest, PasswordModifyResponse>
70  {
71      private static final Logger LOG = LoggerFactory.getLogger( PwdModifyHandler.class );
72      public static final Set<String> EXTENSION_OIDS;
73  
74      static
75      {
76          Set<String> set = new HashSet<>( 2 );
77          set.add( PasswordModifyRequest.EXTENSION_OID );
78          set.add( PasswordModifyResponse.EXTENSION_OID );
79          EXTENSION_OIDS = Collections.unmodifiableSet( set );
80      }
81  
82  
83      /**
84       * {@inheritDoc}
85       */
86      public String getOid()
87      {
88          return PasswordModifyRequest.EXTENSION_OID;
89      }
90  
91  
92      /**
93       * Modify the user's credentials.
94       * 
95       * We will need to modify the userPassword attribute, accordingly to a few rules:
96       * - if the old password is present, we should verify it's valid. if not, we return an error
97       * - if the old password is absent, we are modifying the password of the current used.
98       * - if the new password is absent, we will return an error. The RFC says that we could
99       * generate a random password, but that would be dangerous to do so.
100      * - if the new password already exists, we simply return not changing anything 
101      * - otherwise, we just remove the old password from the list of passwords (we may have 
102      * more than one) and add the new password. This is done with a REPLACE operation (Modify)
103      */
104     private void modifyUserPassword( CoreSession userSession, Entry userEntry, Dn userDn, 
105         byte[] oldPassword, byte[] newPassword, PasswordModifyRequest req )
106     {
107         IoSession ioSession = ( ( DefaultCoreSession ) userSession ).getIoSession();
108 
109         if ( newPassword == null )
110         {
111             // We don't support password generation on ApacheDS
112             writeResult( ioSession, req, ResultCodeEnum.UNWILLING_TO_PERFORM, 
113                 "Cannot change a password for user " + userDn + ", exception : null new password" );
114 
115             return;
116         }
117         
118         // Get the user password attribute
119         Attribute userPassword = userEntry.get( SchemaConstants.USER_PASSWORD_AT );
120         
121         if ( userPassword == null )
122         {
123             // We can't modify the password
124             writeResult( ioSession, req, ResultCodeEnum.UNWILLING_TO_PERFORM, 
125                 "Cannot change a password for user " + userDn + ", the user has no existing password" );
126 
127             return;
128         }
129         
130         if ( userPassword.contains( newPassword ) )
131         {
132            // Ok, we are done : just return success
133             PasswordModifyResponseImpl pmrl = new PasswordModifyResponseImpl(
134                 req.getMessageId(), ResultCodeEnum.SUCCESS );
135 
136             Control ppolicyControl = req.getControl( PasswordPolicyRequest.OID );
137 
138             if ( ppolicyControl != null )
139             {
140                 pmrl.addControl( ppolicyControl );
141             }
142 
143             ioSession.write( pmrl );
144 
145             return;
146         }
147         
148         if ( oldPassword == null )
149         {
150             // We are modifying the password on behalf of another user. ACI will
151             // protect such modification if it's not allowed. In any case, we just 
152             // modify the existing userPassword attribute, adding the password
153             ModifyRequest modifyRequest = new ModifyRequestImpl();
154             modifyRequest.setName( userDn );
155 
156             Control ppolicyControl = req.getControl( PasswordPolicyRequest.OID );
157             
158             if ( ppolicyControl != null )
159             {
160                 modifyRequest.addControl( ppolicyControl );
161             }
162             
163             try
164             {
165                 Modification modification = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE,
166                     userPassword.getAttributeType(), newPassword );
167     
168                 modifyRequest.addModification( modification );
169                 ResultCodeEnum errorCode = null;
170                 String errorMessage = null;
171 
172                 try
173                 {
174                     userSession.modify( modifyRequest );
175 
176                     if ( LOG.isDebugEnabled() )
177                     {
178                         LOG.debug( "Password modified for user {}", userDn );
179                     }
180 
181                     // Ok, all done
182                     PasswordModifyResponseImpl pmrl = new PasswordModifyResponseImpl(
183                         req.getMessageId(), ResultCodeEnum.SUCCESS );
184 
185                     ppolicyControl = modifyRequest.getResultResponse().getControl( PasswordPolicyRequest.OID );
186 
187                     if ( ppolicyControl != null )
188                     {
189                         pmrl.addControl( ppolicyControl );
190                     }
191 
192                     ioSession.write( pmrl );
193 
194                     return;
195                 }
196                 catch ( LdapOperationException loe )
197                 {
198                     errorCode = loe.getResultCode();
199                     errorMessage = loe.getMessage();
200                 }
201                 catch ( LdapException le )
202                 {
203                     // this exception means something else must be wrong
204                     errorCode = ResultCodeEnum.OTHER;
205                     errorMessage = le.getMessage();
206                 }
207 
208                 // We can't modify the password
209                 LOG.error( "Cannot modify the password for user {}, exception : {}", userDn, errorMessage );
210                 PasswordModifyResponseImpl errorPmrl = new PasswordModifyResponseImpl(
211                     req.getMessageId(), errorCode, "Cannot modify the password for user "
212                         + userDn + ", exception : " + errorMessage );
213 
214                 ppolicyControl = modifyRequest.getResultResponse().getControl( PasswordPolicyRequest.OID );
215 
216                 if ( ppolicyControl != null )
217                 {
218                     errorPmrl.addControl( ppolicyControl );
219                 }
220 
221                 ioSession.write( errorPmrl );
222                 
223                 return;
224             }
225             catch ( LdapInvalidAttributeValueException liave )
226             {
227                 // Nothing to do, this will never be a problem
228             }
229         }
230         else
231         {
232             // We are changing the password of the current user, check the password
233             boolean valid = false;
234             Attribute modifiedPassword = new DefaultAttribute( userPassword.getAttributeType() );
235             
236             for ( Value value : userPassword )
237             {
238                 if ( !valid )
239                 {
240                     valid = PasswordUtil.compareCredentials( oldPassword, value.getBytes() );
241                 }
242                 
243                 try
244                 {
245                     if ( valid )
246                     {
247                         modifiedPassword.add( newPassword );
248                     }
249                     else
250                     { 
251                         modifiedPassword.add( value );
252                     }
253                 }
254                 catch ( LdapInvalidAttributeValueException e )
255                 {
256                     // Nothing to do, this will never be a problem
257                 }
258             }
259             
260             // At this point, we have what is needed to modify the password, if the oldPassword
261             // was valid
262             if ( valid )
263             {
264                 ModifyRequest modifyRequest = new ModifyRequestImpl();
265                 modifyRequest.setName( userDn );
266 
267                 Control ppolicyControl = req.getControl( PasswordPolicyRequest.OID );
268                 
269                 if ( ppolicyControl != null )
270                 {
271                     modifyRequest.addControl( ppolicyControl );
272                 }
273                 
274                 Modification modification = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE,
275                     modifiedPassword );
276 
277                 modifyRequest.addModification( modification );
278 
279                 ResultCodeEnum errorCode = null;
280                 String errorMessage = null;
281 
282                 try
283                 {
284                     userSession.modify( modifyRequest );
285 
286                     if ( LOG.isDebugEnabled() )
287                     {
288                         LOG.debug( "Password modified for user {}", userDn );
289                     }
290 
291                     // Ok, all done
292                     PasswordModifyResponseImpl pmrl = new PasswordModifyResponseImpl(
293                         req.getMessageId(), ResultCodeEnum.SUCCESS );
294 
295                     ppolicyControl = modifyRequest.getResultResponse().getControl( PasswordPolicyRequest.OID );
296 
297                     if ( ppolicyControl != null )
298                     {
299                         pmrl.addControl( ppolicyControl );
300                     }
301 
302                     ioSession.write( pmrl );
303 
304                     return;
305                 }
306                 catch ( LdapOperationException loe )
307                 {
308                     errorCode = loe.getResultCode();
309                     errorMessage = loe.getMessage();
310                 }
311                 catch ( LdapException le )
312                 {
313                     // this exception means something else must be wrong
314                     errorCode = ResultCodeEnum.OTHER;
315                     errorMessage = le.getMessage();
316                 }
317 
318                 // We can't modify the password
319                 LOG.error( "Cannot modify the password for user {}, exception : {}", userDn, errorMessage );
320                 PasswordModifyResponseImpl errorPmrl = new PasswordModifyResponseImpl(
321                     req.getMessageId(), errorCode, "Cannot modify the password for user "
322                         + userDn + ", exception : " + errorMessage );
323 
324                 ppolicyControl = modifyRequest.getResultResponse().getControl( PasswordPolicyRequest.OID );
325 
326                 if ( ppolicyControl != null )
327                 {
328                     errorPmrl.addControl( ppolicyControl );
329                 }
330 
331                 ioSession.write( errorPmrl );
332                 
333                 return;
334             }
335             else
336             {
337                 // Too bad, the old password is invalid
338                 writeResult( ioSession, req, ResultCodeEnum.INVALID_CREDENTIALS, 
339                     "Cannot change a password for user " + userDn + ", invalid credentials" );
340 
341                 return;
342             }
343         }
344     }
345 
346     
347     private void writeResult( LdapSession requestor, PasswordModifyRequest req, ResultCodeEnum error, String errorMessage )
348     {
349         writeResult( requestor.getIoSession(), req, error, errorMessage );
350 
351     }
352 
353     
354     private void writeResult( IoSession ioSession, PasswordModifyRequest req, ResultCodeEnum error, String errorMessage )
355     {
356         LOG.error( errorMessage );
357         ioSession.write( new PasswordModifyResponseImpl(
358             req.getMessageId(), error, errorMessage ) );
359 
360     }
361     
362     
363     private Entry getModifiedEntry( LdapSession requestor, PasswordModifyRequest req, Dn entryDn )
364     {
365         try
366         {
367             Entry modifiedEntry = requestor.getCoreSession().lookup( entryDn, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
368             
369             if ( modifiedEntry == null )
370             {
371                 // The entry does not exist, we can't modify its password
372                 writeResult( requestor, req, ResultCodeEnum.NO_SUCH_OBJECT, 
373                     "The entry does not exist, we can't modify its password" );
374                 return null;
375             }
376             else
377             {
378                 return modifiedEntry;
379             }
380         }
381         catch ( Exception le )
382         {
383             // The entry does not exist, we can't modify its password
384             writeResult( requestor, req, ResultCodeEnum.NO_SUCH_OBJECT, 
385                 "The entry does not exist, we can't modify its password" );
386             return null;
387         }
388     }
389     
390     
391     private void processAuthenticatedPasswordModify( LdapSession requestor, PasswordModifyRequest req,
392         Dn userDn )
393     {
394         byte[] oldPassword = req.getOldPassword();
395         byte[] newPassword = req.getNewPassword();
396 
397         // We are already bound. Fetch the entry which we want to modify
398         Entry modifiedEntry = null;
399         
400         Dn principalDn = requestor.getCoreSession().getEffectivePrincipal().getDn();
401 
402         LOG.debug( "User {} trying to modify password of user {}", principalDn, userDn );
403         
404         
405         // First, check that the userDn is null : we can't change the password of someone else
406         // except if we are admin
407         if ( ( userDn != null ) && ( !userDn.equals( principalDn ) ) )
408         {
409             // Are we admin ?
410             if ( requestor.getCoreSession().isAdministrator() )
411             {
412                 modifiedEntry = getModifiedEntry( requestor, req, userDn );
413                 
414                 if ( modifiedEntry == null )
415                 {
416                     return;
417                 }
418                 
419                 // We are administrator, we can try to modify the user's credentials
420                 modifyUserPassword( requestor.getCoreSession(), modifiedEntry, userDn, oldPassword, newPassword, req );
421             }
422             else
423             {
424                 // No : error
425                 writeResult( requestor, req, ResultCodeEnum.INSUFFICIENT_ACCESS_RIGHTS, 
426                     "Non-admin user cannot access another user's password to modify it" );
427             }
428         }
429         else
430         {
431             // We are trying to modify our own password
432             modifiedEntry = getModifiedEntry( requestor, req, principalDn );
433 
434             if ( modifiedEntry == null )
435             {
436                 return;
437             }
438 
439             modifyUserPassword( requestor.getCoreSession(), modifiedEntry, principalDn, oldPassword, newPassword, req );
440         }
441     }
442     
443 
444     /**
445      * {@inheritDoc}
446      */
447     public void handleExtendedOperation( LdapSession requestor, PasswordModifyRequest req ) throws Exception
448     {
449         LOG.debug( "Password modification requested" );
450 
451         // Grab the adminSession, we might need it later
452         DirectoryService service = requestor.getLdapServer().getDirectoryService();
453         CoreSession adminSession = service.getAdminSession();
454         String userIdentity = Strings.utf8ToString( req.getUserIdentity() );
455         Dn userDn = null;
456 
457         if ( !Strings.isEmpty( userIdentity ) )
458         {
459             try
460             {
461                 userDn = service.getDnFactory().create( userIdentity );
462             }
463             catch ( LdapInvalidDnException lide )
464             {
465                 // The userIdentity is not a DN : return with an error code.
466                 writeResult( requestor, req, ResultCodeEnum.INVALID_DN_SYNTAX, 
467                     "The user DN is invalid : " + userDn );
468 
469                 return;
470             }
471         }
472 
473         byte[] oldPassword = req.getOldPassword();
474         byte[] newPassword = req.getNewPassword();
475 
476         // First check if the user is bound or not
477         if ( requestor.isAuthenticated() )
478         {
479             processAuthenticatedPasswordModify( requestor, req, userDn );
480         }
481         else
482         {
483             // The user is not authenticated : we have to use the provided userIdentity
484             // and the oldPassword to check if the user is present
485             BindOperationContextnterceptor/context/BindOperationContext.html#BindOperationContext">BindOperationContext bindContext = new BindOperationContext( adminSession );
486             bindContext.setDn( userDn );
487             bindContext.setCredentials( oldPassword );
488 
489             try
490             {
491                 service.getOperationManager().bind( bindContext );
492             }
493             catch ( LdapException le )
494             {
495                 // We can't bind with the provided information : we thus can't
496                 // change the password...
497                 requestor.getIoSession().write( new PasswordModifyResponseImpl(
498                     req.getMessageId(), ResultCodeEnum.INVALID_CREDENTIALS ) );
499 
500                 return;
501             }
502 
503             // Ok, we were able to bind using the userIdentity and the password. Let's
504             // modify the password now
505             modifyUserPassword( requestor.getCoreSession(), bindContext.getEntry(), userDn, oldPassword, newPassword, req );
506         }
507     }
508 
509 
510     /**
511      * {@inheritDoc}
512      */
513     public Set<String> getExtensionOids()
514     {
515         return EXTENSION_OIDS;
516     }
517 
518 
519     /**
520      * {@inheritDoc}
521      */
522     public void setLdapServer( LdapServer ldapServer )
523     {
524     }
525 }