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.core.authn;
21  
22  
23  import static org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyErrorEnum.INSUFFICIENT_PASSWORD_QUALITY;
24  import static org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyErrorEnum.PASSWORD_TOO_SHORT;
25  import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_ACCOUNT_LOCKED_TIME_AT;
26  import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_CHANGED_TIME_AT;
27  import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_FAILURE_TIME_AT;
28  import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_GRACE_USE_TIME_AT;
29  import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_HISTORY_AT;
30  import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_LAST_SUCCESS_AT;
31  import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_POLICY_SUBENTRY_AT;
32  import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_RESET_AT;
33  import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_START_TIME_AT;
34  import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_END_TIME_AT;
35  import static org.apache.directory.api.ldap.model.entry.ModificationOperation.ADD_ATTRIBUTE;
36  import static org.apache.directory.api.ldap.model.entry.ModificationOperation.REMOVE_ATTRIBUTE;
37  import static org.apache.directory.api.ldap.model.entry.ModificationOperation.REPLACE_ATTRIBUTE;
38  
39  import java.io.IOException;
40  import java.security.MessageDigest;
41  import java.util.ArrayList;
42  import java.util.Collection;
43  import java.util.Collections;
44  import java.util.EnumMap;
45  import java.util.HashSet;
46  import java.util.Iterator;
47  import java.util.List;
48  import java.util.Set;
49  
50  import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyRequest;
51  import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyResponse;
52  import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyResponseImpl;
53  import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyErrorEnum;
54  import org.apache.directory.api.ldap.model.constants.AuthenticationLevel;
55  import org.apache.directory.api.ldap.model.constants.LdapSecurityConstants;
56  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
57  import org.apache.directory.api.ldap.model.entry.Attribute;
58  import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
59  import org.apache.directory.api.ldap.model.entry.DefaultModification;
60  import org.apache.directory.api.ldap.model.entry.Entry;
61  import org.apache.directory.api.ldap.model.entry.Modification;
62  import org.apache.directory.api.ldap.model.entry.ModificationOperation;
63  import org.apache.directory.api.ldap.model.entry.Value;
64  import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
65  import org.apache.directory.api.ldap.model.exception.LdapException;
66  import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException;
67  import org.apache.directory.api.ldap.model.exception.LdapOperationException;
68  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
69  import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException;
70  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
71  import org.apache.directory.api.ldap.model.name.Dn;
72  import org.apache.directory.api.ldap.model.password.PasswordUtil;
73  import org.apache.directory.api.ldap.model.schema.AttributeType;
74  import org.apache.directory.api.util.DateUtils;
75  import org.apache.directory.api.util.Strings;
76  import org.apache.directory.server.constants.ServerDNConstants;
77  import org.apache.directory.server.core.api.CoreSession;
78  import org.apache.directory.server.core.api.DirectoryService;
79  import org.apache.directory.server.core.api.InterceptorEnum;
80  import org.apache.directory.server.core.api.LdapPrincipal;
81  import org.apache.directory.server.core.api.authn.ppolicy.CheckQualityEnum;
82  import org.apache.directory.server.core.api.authn.ppolicy.DefaultPasswordValidator;
83  import org.apache.directory.server.core.api.authn.ppolicy.PasswordPolicyConfiguration;
84  import org.apache.directory.server.core.api.authn.ppolicy.PasswordPolicyException;
85  import org.apache.directory.server.core.api.authn.ppolicy.PasswordValidator;
86  import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
87  import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
88  import org.apache.directory.server.core.api.interceptor.Interceptor;
89  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
90  import org.apache.directory.server.core.api.interceptor.context.BindOperationContext;
91  import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext;
92  import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
93  import org.apache.directory.server.core.api.interceptor.context.GetRootDseOperationContext;
94  import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext;
95  import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
96  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
97  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
98  import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
99  import org.apache.directory.server.core.api.interceptor.context.OperationContext;
100 import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
101 import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
102 import org.apache.directory.server.core.api.interceptor.context.UnbindOperationContext;
103 import org.apache.directory.server.core.api.partition.Partition;
104 import org.apache.directory.server.core.api.partition.PartitionTxn;
105 import org.apache.directory.server.core.authn.ppolicy.PpolicyConfigContainer;
106 import org.apache.directory.server.core.shared.DefaultCoreSession;
107 import org.apache.directory.server.i18n.I18n;
108 import org.slf4j.Logger;
109 import org.slf4j.LoggerFactory;
110 
111 
112 /**
113  * An {@link Interceptor} that authenticates users.
114  *
115  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
116  */
117 public class AuthenticationInterceptor extends BaseInterceptor
118 {
119     private static final Logger LOG = LoggerFactory.getLogger( AuthenticationInterceptor.class );
120 
121     /**
122      * Speedup for logs
123      */
124     private static final boolean IS_DEBUG = LOG.isDebugEnabled();
125 
126     /** A Set of all the existing Authenticator to be used by the bind operation */
127     private Set<Authenticator> authenticators = new HashSet<>();
128 
129     /** A map of authenticators associated with the authentication level required */
130     private final EnumMap<AuthenticationLevel, Collection<Authenticator>> authenticatorsMapByType = new EnumMap<>( AuthenticationLevel.class );
131 
132     private CoreSession adminSession;
133 
134     // pwdpolicy state attribute types
135     private AttributeType pwdResetAT;
136 
137     private AttributeType pwdChangedTimeAT;
138 
139     private AttributeType pwdHistoryAT;
140 
141     private AttributeType pwdFailurTimeAT;
142 
143     private AttributeType pwdAccountLockedTimeAT;
144 
145     private AttributeType pwdLastSuccessAT;
146 
147     private AttributeType pwdGraceUseTimeAT;
148 
149     private AttributeType pwdPolicySubentryAT;
150 
151     private AttributeType pwdStartTimeAT;
152 
153     private AttributeType pwdEndTimeAT;
154 
155     /** a container to hold all the ppolicies */
156     private PpolicyConfigContainer pwdPolicyContainer;
157 
158 
159     /**
160      * Creates an authentication service interceptor.
161      */
162     public AuthenticationInterceptor()
163     {
164         super( InterceptorEnum.AUTHENTICATION_INTERCEPTOR );
165     }
166 
167 
168     /**
169      * Registers and initializes all {@link Authenticator}s to this service.
170      */
171     @Override
172     public void init( DirectoryService directoryService ) throws LdapException
173     {
174         super.init( directoryService );
175 
176         adminSession = directoryService.getAdminSession();
177 
178         if ( ( authenticators == null ) || authenticators.isEmpty() )
179         {
180             setDefaultAuthenticators();
181         }
182 
183         // Register all authenticators
184         for ( Authenticator authenticator : authenticators )
185         {
186             register( authenticator, directoryService );
187         }
188 
189         loadPwdPolicyStateAttributeTypes();
190     }
191 
192 
193     /**
194      * Initialize the set of authenticators with some default values
195      */
196     private void setDefaultAuthenticators()
197     {
198         if ( authenticators == null )
199         {
200             authenticators = new HashSet<>();
201         }
202 
203         authenticators.clear();
204         authenticators.add( new AnonymousAuthenticator( Dn.ROOT_DSE ) );
205         authenticators.add( new SimpleAuthenticator( Dn.ROOT_DSE ) );
206         authenticators.add( new StrongAuthenticator( Dn.ROOT_DSE ) );
207     }
208 
209 
210     public Set<Authenticator> getAuthenticators()
211     {
212         return authenticators;
213     }
214 
215 
216     /**
217      * @param authenticators authenticators to be used by this AuthenticationInterceptor
218      */
219     public void setAuthenticators( Set<Authenticator> authenticators )
220     {
221         if ( authenticators == null )
222         {
223             this.authenticators.clear();
224         }
225         else
226         {
227             this.authenticators = authenticators;
228         }
229     }
230 
231 
232     /**
233      * @param authenticators authenticators to be used by this AuthenticationInterceptor
234      */
235     public void setAuthenticators( Authenticator[] authenticators )
236     {
237         if ( authenticators == null )
238         {
239             throw new IllegalArgumentException( "The given authenticators set is null" );
240         }
241 
242         this.authenticators.clear();
243         this.authenticatorsMapByType.clear();
244 
245         for ( Authenticator authenticator : authenticators )
246         {
247             try
248             {
249                 register( authenticator, directoryService );
250             }
251             catch ( LdapException le )
252             {
253                 LOG.error( "Cannot register authenticator {}", authenticator );
254             }
255         }
256     }
257 
258 
259     /**
260      * Deinitializes and deregisters all {@link Authenticator}s from this service.
261      */
262     @Override
263     public void destroy()
264     {
265         authenticatorsMapByType.clear();
266         Set<Authenticator> copy = new HashSet<>( authenticators );
267         authenticators = new HashSet<>();
268 
269         for ( Authenticator authenticator : copy )
270         {
271             authenticator.destroy();
272         }
273     }
274 
275 
276     /**
277      * Initializes the specified {@link Authenticator} and registers it to
278      * this service.
279      *
280      * @param authenticator Authenticator to initialize and register by type
281      * @param directoryService configuration info to supply to the Authenticator during initialization
282      * @throws javax.naming.Exception if initialization fails.
283      */
284     private void register( Authenticator authenticator, DirectoryService directoryService ) throws LdapException
285     {
286         authenticator.init( directoryService );
287         authenticators.add( authenticator );
288 
289         Collection<Authenticator> authenticatorList = getAuthenticators( authenticator.getAuthenticatorType() );
290 
291         if ( authenticatorList == null )
292         {
293             authenticatorList = new ArrayList<>();
294             authenticatorsMapByType.put( authenticator.getAuthenticatorType(), authenticatorList );
295         }
296 
297         if ( !authenticatorList.contains( authenticator ) )
298         {
299             authenticatorList.add( authenticator );
300         }
301     }
302 
303 
304     /**
305      * Returns the list of {@link Authenticator}s with the specified type.
306      *
307      * @param type type of Authenticator sought
308      * @return A list of Authenticators of the requested type or <tt>null</tt> if no authenticator is found.
309      */
310     private Collection<Authenticator> getAuthenticators( AuthenticationLevel type )
311     {
312         Collection<Authenticator> result = authenticatorsMapByType.get( type );
313 
314         if ( ( result != null ) && ( !result.isEmpty() ) )
315         {
316             return result;
317         }
318         else
319         {
320             return null;
321         }
322     }
323 
324 
325     /**
326      * {@inheritDoc}
327      */
328     @Override
329     public void add( AddOperationContext addContext ) throws LdapException
330     {
331         if ( IS_DEBUG )
332         {
333             LOG.debug( "Operation Context: {}", addContext );
334         }
335 
336         checkAuthenticated( addContext );
337 
338         Entry entry = addContext.getEntry();
339 
340         if ( !directoryService.isPwdPolicyEnabled() || addContext.isReplEvent() )
341         {
342             next( addContext );
343             return;
344         }
345 
346         PasswordPolicyConfiguration policyConfig = getPwdPolicy( entry );
347 
348         boolean isPPolicyReqCtrlPresent = addContext.hasRequestControl( PasswordPolicyRequest.OID );
349 
350         checkPwdReset( addContext );
351 
352         // Get the password depending on the configuration
353         String passwordAttribute = SchemaConstants.USER_PASSWORD_AT;
354 
355         if ( isPPolicyReqCtrlPresent )
356         {
357             passwordAttribute = policyConfig.getPwdAttribute();
358         }
359 
360         Attribute userPasswordAttribute = entry.get( passwordAttribute );
361 
362         if ( userPasswordAttribute != null )
363         {
364             Value userPassword = userPasswordAttribute.get();
365 
366             try
367             {
368                 check( addContext, entry, userPassword.getBytes(), policyConfig );
369             }
370             catch ( PasswordPolicyException e )
371             {
372                 if ( isPPolicyReqCtrlPresent )
373                 {
374                     PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl();
375                     responseControl.setPasswordPolicyError(
376                         PasswordPolicyErrorEnum.get( e.getErrorCode() ) );
377                     addContext.addResponseControl( responseControl );
378                 }
379 
380                 // throw exception if userPassword quality checks fail
381                 throw new LdapOperationException( ResultCodeEnum.CONSTRAINT_VIOLATION, e.getMessage(), e );
382             }
383 
384             String pwdChangedTime = DateUtils.getGeneralizedTime( directoryService.getTimeProvider() );
385 
386             if ( ( policyConfig.getPwdMinAge() > 0 ) || ( policyConfig.getPwdMaxAge() > 0 ) )
387             {
388                 // https://issues.apache.org/jira/browse/DIRSERVER-1978
389                 if ( !addContext.getSession().isAnAdministrator()
390                     || entry.get( pwdChangedTimeAT ) == null )
391                 {
392                     Attribute pwdChangedTimeAt = new DefaultAttribute( pwdChangedTimeAT );
393                     pwdChangedTimeAt.add( pwdChangedTime );
394                     entry.add( pwdChangedTimeAt );
395                 }
396             }
397 
398             if ( policyConfig.isPwdMustChange() && addContext.getSession().isAnAdministrator() )
399             {
400                 Attribute pwdResetAt = new DefaultAttribute( pwdResetAT );
401                 pwdResetAt.add( "TRUE" );
402                 entry.add( pwdResetAt );
403             }
404 
405             if ( policyConfig.getPwdInHistory() > 0 )
406             {
407                 Attribute pwdHistoryAt = new DefaultAttribute( pwdHistoryAT );
408                 byte[] pwdHistoryVal = new PasswordHistory( pwdChangedTime, userPassword.getBytes() ).getHistoryValue();
409                 pwdHistoryAt.add( pwdHistoryVal );
410                 entry.add( pwdHistoryAt );
411             }
412         }
413 
414         next( addContext );
415     }
416 
417 
418     /**
419      * Return the selected authenticator given the DN and the level required.
420      */
421     private Authenticator selectAuthenticator( Dn bindDn, AuthenticationLevel level )
422         throws LdapUnwillingToPerformException, LdapAuthenticationException
423     {
424         Authenticator selectedAuthenticator = null;
425         Collection<Authenticator> levelAuthenticators = authenticatorsMapByType.get( level );
426 
427         if ( ( levelAuthenticators == null ) || levelAuthenticators.isEmpty() )
428         {
429             // No authenticators associated with this level : get out
430             throw new LdapAuthenticationException( "Cannot Bind for Dn "
431                 + bindDn.getName() + ", no authenticator for the requested level " + level );
432         }
433 
434         if ( levelAuthenticators.size() == 1 )
435         {
436             // Just pick the existing one
437             for ( Authenticator authenticator : levelAuthenticators )
438             {
439                 // Check that the bindDN fits
440                 if ( authenticator.isValid( bindDn ) )
441                 {
442                     return authenticator;
443                 }
444                 else
445                 {
446                     throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
447                         "Cannot Bind for Dn " + bindDn.getName() 
448                         + ", its not a descendant of the authenticator base DN '" + authenticator.getBaseDn() + "'" );
449                 }
450             }
451         }
452 
453         // We have more than one authenticator. Let's loop on all of them and
454         // select the one that fits the bindDN
455         Dn innerDn = Dn.ROOT_DSE;
456 
457         for ( Authenticator authenticator : levelAuthenticators )
458         {
459             if ( authenticator.isValid( bindDn ) )
460             {
461                 // We have found a valid authenticator, let's check if it's the inner one
462                 if ( innerDn.isAncestorOf( authenticator.getBaseDn() ) )
463                 {
464                     innerDn = authenticator.getBaseDn();
465                     selectedAuthenticator = authenticator;
466                 }
467             }
468         }
469 
470         if ( selectedAuthenticator == null )
471         {
472             throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
473                     "Cannot Bind for Dn " + bindDn.getName() + ", there is no authenticator for it" );
474         }
475         
476         return selectedAuthenticator;
477     }
478     
479     
480     private void internalModify( OperationContext opContext, ModifyOperationContext bindModCtx ) throws LdapException
481     {
482         Partition partition = opContext.getPartition();
483         bindModCtx.setPartition( partition );
484         PartitionTxn partitionTxn = null;
485 
486         try
487         {
488             partitionTxn = partition.beginWriteTransaction();
489             bindModCtx.setTransaction( partitionTxn );
490 
491             directoryService.getPartitionNexus().modify( bindModCtx );
492 
493             partitionTxn.commit();
494         }
495         catch ( LdapException le )
496         {
497             try 
498             {
499                 if ( partitionTxn != null )
500                 {
501                     partitionTxn.abort();
502                 }
503                 
504                 throw le;
505             }
506             catch ( IOException ioe )
507             {
508                 throw new LdapOtherException( ioe.getMessage(), ioe );
509             }
510         }
511         catch ( IOException ioe )
512         {
513             try 
514             {
515                 partitionTxn.abort();
516                 
517                 throw new LdapOtherException( ioe.getMessage(), ioe );
518             }
519             catch ( IOException ioe2 )
520             {
521                 throw new LdapOtherException( ioe2.getMessage(), ioe2 );
522             }
523         }
524     }
525 
526 
527     /**
528      * {@inheritDoc}
529      */
530     @Override
531     public void bind( BindOperationContext bindContext ) throws LdapException
532     {
533         if ( IS_DEBUG )
534         {
535             LOG.debug( "Operation Context: {}", bindContext );
536         }
537 
538         CoreSession session = bindContext.getSession();
539         Dn bindDn = bindContext.getDn();
540 
541         if ( ( session != null )
542             && ( session.getEffectivePrincipal() != null )
543             && ( !session.isAnonymous() )
544             && ( !session.isAdministrator() ) )
545         {
546             // null out the credentials
547             bindContext.setCredentials( null );
548         }
549 
550         // pick the first matching authenticator type
551         AuthenticationLevel level = bindContext.getAuthenticationLevel();
552 
553         if ( level == AuthenticationLevel.UNAUTHENT )
554         {
555             // This is a case where the Bind request contains a Dn, but no password.
556             // We don't check the Dn, we just return a UnwillingToPerform error
557             // Cf RFC 4513, chap. 5.1.2
558             throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, "Cannot Bind for Dn "
559                 + bindDn.getName() );
560         }
561 
562         PasswordPolicyException ppe = null;
563         boolean isPPolicyReqCtrlPresent = bindContext.hasRequestControl( PasswordPolicyRequest.OID );
564         PasswordPolicyResponse pwdRespCtrl = new PasswordPolicyResponseImpl();
565         boolean authenticated = false;
566 
567         Authenticator authenticator = selectAuthenticator( bindDn, level );
568 
569         try
570         {
571             // perform the authentication
572             LdapPrincipal principal = authenticator.authenticate( bindContext );
573 
574             if ( principal != null )
575             {
576                 LdapPrincipal/org/apache/directory/server/core/api/LdapPrincipal.html#LdapPrincipal">LdapPrincipal clonedPrincipal = ( LdapPrincipal ) ( principal.clone() );
577 
578                 // remove creds so there is no security risk
579                 bindContext.setCredentials( null );
580                 clonedPrincipal.setUserPassword( Strings.EMPTY_BYTES );
581 
582                 // authentication was successful
583                 CoreSession newSession = new DefaultCoreSession( clonedPrincipal, directoryService );
584                 bindContext.setSession( newSession );
585 
586                 authenticated = true;
587             }
588         }
589         catch ( PasswordPolicyException e )
590         {
591             ppe = e;
592         }
593         catch ( LdapAuthenticationException e )
594         {
595             // authentication failed, try the next authenticator
596             LOG.info( "Authenticator {} failed to authenticate: {}", authenticator, bindContext.getDn() );
597         }
598         catch ( Exception e )
599         {
600             // Log other exceptions than LdapAuthenticationException
601             LOG.info( "Unexpected failure for Authenticator {} : {}", authenticator, bindContext.getDn() );
602         }
603 
604         if ( ppe != null )
605         {
606             if ( isPPolicyReqCtrlPresent )
607             {
608                 pwdRespCtrl.setPasswordPolicyError( PasswordPolicyErrorEnum.get( ppe.getErrorCode() ) );
609                 bindContext.addResponseControl( pwdRespCtrl );
610             }
611 
612             throw ppe;
613         }
614 
615         Entry userEntry = bindContext.getEntry();
616 
617         PasswordPolicyConfiguration policyConfig = getPwdPolicy( userEntry );
618 
619         // load the user entry again if ppolicy is enabled, cause the authenticator might have modified the entry
620         if ( policyConfig != null )
621         {
622             LookupOperationContexttor/context/LookupOperationContext.html#LookupOperationContext">LookupOperationContext lookupContext = new LookupOperationContext( adminSession, bindDn,
623                 SchemaConstants.ALL_ATTRIBUTES_ARRAY );
624             lookupContext.setPartition( bindContext.getPartition() );
625             lookupContext.setTransaction( bindContext.getTransaction() );
626             
627             userEntry = directoryService.getPartitionNexus().lookup( lookupContext );
628         }
629 
630         // check if the user entry is null, it will be null
631         // in cases of anonymous bind
632         if ( authenticated && ( userEntry == null ) && directoryService.isAllowAnonymousAccess() )
633         {
634             return;
635         }
636 
637         if ( !authenticated )
638         {
639             if ( LOG.isInfoEnabled() )
640             {
641                 LOG.info( "Cannot bind to the server " );
642             }
643 
644             if ( ( policyConfig != null ) && ( userEntry != null ) )
645             {
646                 Attribute pwdFailTimeAt = userEntry.get( pwdFailurTimeAT );
647 
648                 if ( pwdFailTimeAt == null )
649                 {
650                     pwdFailTimeAt = new DefaultAttribute( pwdFailurTimeAT );
651                 }
652                 else
653                 {
654                     purgeFailureTimes( policyConfig, pwdFailTimeAt );
655                 }
656 
657                 String failureTime = DateUtils.getGeneralizedTime( directoryService.getTimeProvider() );
658                 pwdFailTimeAt.add( failureTime );
659                 Modification pwdFailTimeMod = new DefaultModification( REPLACE_ATTRIBUTE, pwdFailTimeAt );
660 
661                 List<Modification> mods = new ArrayList<>();
662                 mods.add( pwdFailTimeMod );
663 
664                 int numFailures = pwdFailTimeAt.size();
665 
666                 if ( policyConfig.isPwdLockout() && ( numFailures >= policyConfig.getPwdMaxFailure() ) )
667                 {
668                     // Checking that we're not locking the admin user of the system partition
669                     // See DIRSERVER-1812 (The default admin account should never get locked forever)
670                     if ( !userEntry.getDn().equals( new Dn( schemaManager, ServerDNConstants.ADMIN_SYSTEM_DN ) ) )
671                     {
672                         Attribute pwdAccountLockedTimeAt = new DefaultAttribute( pwdAccountLockedTimeAT );
673 
674                         // if zero, lockout permanently, only admin can unlock it
675                         if ( policyConfig.getPwdLockoutDuration() == 0 )
676                         {
677                             pwdAccountLockedTimeAt.add( "000001010000Z" );
678                         }
679                         else
680                         {
681                             pwdAccountLockedTimeAt.add( failureTime );
682                         }
683 
684                         Modification pwdAccountLockedMod = new DefaultModification( REPLACE_ATTRIBUTE,
685                             pwdAccountLockedTimeAt );
686                         mods.add( pwdAccountLockedMod );
687 
688                         pwdRespCtrl.setPasswordPolicyError( PasswordPolicyErrorEnum.ACCOUNT_LOCKED );
689                     }
690                 }
691                 else if ( policyConfig.getPwdMinDelay() > 0 )
692                 {
693                     int numDelay = numFailures * policyConfig.getPwdMinDelay();
694                     int maxDelay = policyConfig.getPwdMaxDelay();
695 
696                     if ( numDelay > maxDelay )
697                     {
698                         numDelay = maxDelay;
699                     }
700 
701                     try
702                     {
703                         Thread.sleep( numDelay * 1000L );
704                     }
705                     catch ( InterruptedException e )
706                     {
707                         LOG.warn(
708                             "Interrupted while delaying to send the failed authentication response for the user {}",
709                             bindDn, e );
710                     }
711                 }
712 
713                 if ( !mods.isEmpty() )
714                 {
715                     String csnVal = directoryService.getCSN().toString();
716                     Modification csnMod = new DefaultModification( REPLACE_ATTRIBUTE, directoryService.getAtProvider()
717                         .getEntryCSN(), csnVal );
718                     mods.add( csnMod );
719                     ModifyOperationContextceptor/context/ModifyOperationContext.html#ModifyOperationContext">ModifyOperationContext bindModCtx = new ModifyOperationContext( adminSession );
720                     bindModCtx.setDn( bindDn );
721                     bindModCtx.setEntry( userEntry );
722                     bindModCtx.setModItems( mods );
723                     bindModCtx.setPushToEvtInterceptor( true );
724 
725                     internalModify( bindContext, bindModCtx );
726                 }
727             }
728 
729             String upDn = bindDn == null ? "" : bindDn.getName();
730             throw new LdapAuthenticationException( I18n.err( I18n.ERR_229, upDn ) );
731         }
732         else if ( policyConfig != null )
733         {
734             List<Modification> mods = new ArrayList<>();
735 
736             if ( policyConfig.getPwdMaxIdle() > 0 )
737             {
738                 Attribute pwdLastSuccesTimeAt = new DefaultAttribute( pwdLastSuccessAT );
739                 pwdLastSuccesTimeAt.add( DateUtils.getGeneralizedTime( directoryService.getTimeProvider() ) );
740                 Modification pwdLastSuccesTimeMod = new DefaultModification( REPLACE_ATTRIBUTE, pwdLastSuccesTimeAt );
741                 mods.add( pwdLastSuccesTimeMod );
742             }
743 
744             Attribute pwdFailTimeAt = userEntry.get( pwdFailurTimeAT );
745 
746             if ( pwdFailTimeAt != null )
747             {
748                 Modification pwdFailTimeMod = new DefaultModification( REMOVE_ATTRIBUTE, pwdFailTimeAt );
749                 mods.add( pwdFailTimeMod );
750             }
751 
752             Attribute pwdAccLockedTimeAt = userEntry.get( pwdAccountLockedTimeAT );
753 
754             if ( pwdAccLockedTimeAt != null )
755             {
756                 Modification pwdAccLockedTimeMod = new DefaultModification( REMOVE_ATTRIBUTE, pwdAccLockedTimeAt );
757                 mods.add( pwdAccLockedTimeMod );
758             }
759 
760             // checking the expiration time *after* performing authentication, do we need to care about millisecond precision?
761             if ( ( policyConfig.getPwdMaxAge() > 0 ) && ( policyConfig.getPwdGraceAuthNLimit() > 0 ) )
762             {
763                 Attribute pwdChangeTimeAttr = userEntry.get( pwdChangedTimeAT );
764 
765                 if ( pwdChangeTimeAttr != null )
766                 {
767                     boolean expired = PasswordUtil.isPwdExpired( pwdChangeTimeAttr.getString(),
768                         policyConfig.getPwdMaxAge(), directoryService.getTimeProvider() );
769 
770                     if ( expired )
771                     {
772                         Attribute pwdGraceUseAttr = userEntry.get( pwdGraceUseTimeAT );
773                         int numGraceAuth;
774 
775                         if ( pwdGraceUseAttr != null )
776                         {
777                             numGraceAuth = policyConfig.getPwdGraceAuthNLimit() - ( pwdGraceUseAttr.size() + 1 );
778                         }
779                         else
780                         {
781                             pwdGraceUseAttr = new DefaultAttribute( pwdGraceUseTimeAT );
782                             numGraceAuth = policyConfig.getPwdGraceAuthNLimit() - 1;
783                         }
784 
785                         pwdRespCtrl.setGraceAuthNRemaining( numGraceAuth );
786 
787                         pwdGraceUseAttr.add( DateUtils.getGeneralizedTime( directoryService.getTimeProvider() ) );
788                         Modification pwdGraceUseMod = new DefaultModification( ADD_ATTRIBUTE, pwdGraceUseAttr );
789                         mods.add( pwdGraceUseMod );
790                     }
791                 }
792             }
793 
794             if ( !mods.isEmpty() )
795             {
796                 String csnVal = directoryService.getCSN().toString();
797                 Modification csnMod = new DefaultModification( REPLACE_ATTRIBUTE, directoryService.getAtProvider()
798                     .getEntryCSN(), csnVal );
799                 mods.add( csnMod );
800 
801                 ModifyOperationContextceptor/context/ModifyOperationContext.html#ModifyOperationContext">ModifyOperationContext bindModCtx = new ModifyOperationContext( adminSession );
802                 bindModCtx.setDn( bindDn );
803                 bindModCtx.setEntry( userEntry );
804                 bindModCtx.setModItems( mods );
805                 bindModCtx.setPushToEvtInterceptor( true );
806                 
807                 internalModify( bindContext, bindModCtx );
808             }
809 
810             if ( isPPolicyReqCtrlPresent )
811             {
812                 int expiryWarnTime = getPwdTimeBeforeExpiry( userEntry, policyConfig );
813 
814                 if ( expiryWarnTime > 0 )
815                 {
816                     pwdRespCtrl.setTimeBeforeExpiration( expiryWarnTime );
817                 }
818 
819                 if ( isPwdMustReset( userEntry ) )
820                 {
821                     pwdRespCtrl.setPasswordPolicyError( PasswordPolicyErrorEnum.CHANGE_AFTER_RESET );
822                     bindContext.getSession().setPwdMustChange( true );
823                 }
824 
825                 bindContext.addResponseControl( pwdRespCtrl );
826             }
827         }
828     }
829 
830 
831     /**
832      * {@inheritDoc}
833      */
834     @Override
835     public boolean compare( CompareOperationContext compareContext ) throws LdapException
836     {
837         if ( IS_DEBUG )
838         {
839             LOG.debug( "Operation Context: {}", compareContext );
840         }
841 
842         checkAuthenticated( compareContext );
843         checkPwdReset( compareContext );
844         return next( compareContext );
845     }
846 
847 
848     /**
849      * {@inheritDoc}
850      */
851     @Override
852     public void delete( DeleteOperationContext deleteContext ) throws LdapException
853     {
854         if ( IS_DEBUG )
855         {
856             LOG.debug( "Operation Context: {}", deleteContext );
857         }
858 
859         checkAuthenticated( deleteContext );
860         checkPwdReset( deleteContext );
861         next( deleteContext );
862         invalidateAuthenticatorCaches( deleteContext.getDn() );
863     }
864 
865 
866     /**
867      * {@inheritDoc}
868      */
869     @Override
870     public Entry getRootDse( GetRootDseOperationContext getRootDseContext ) throws LdapException
871     {
872         if ( IS_DEBUG )
873         {
874             LOG.debug( "Operation Context: {}", getRootDseContext );
875         }
876 
877         checkAuthenticated( getRootDseContext );
878         checkPwdReset( getRootDseContext );
879 
880         return next( getRootDseContext );
881     }
882 
883 
884     /**
885      * {@inheritDoc}
886      */
887     @Override
888     public boolean hasEntry( HasEntryOperationContext hasEntryContext ) throws LdapException
889     {
890         if ( IS_DEBUG )
891         {
892             LOG.debug( "Operation Context: {}", hasEntryContext );
893         }
894 
895         checkAuthenticated( hasEntryContext );
896         checkPwdReset( hasEntryContext );
897 
898         return next( hasEntryContext );
899     }
900 
901 
902     /**
903      * {@inheritDoc}
904      */
905     @Override
906     public Entry lookup( LookupOperationContext lookupContext ) throws LdapException
907     {
908         if ( IS_DEBUG )
909         {
910             LOG.debug( "Operation Context: {}", lookupContext );
911         }
912 
913         checkAuthenticated( lookupContext );
914         checkPwdReset( lookupContext );
915 
916         return next( lookupContext );
917     }
918 
919 
920     private void invalidateAuthenticatorCaches( Dn principalDn )
921     {
922         for ( AuthenticationLevel authMech : authenticatorsMapByType.keySet() )
923         {
924             // try each authenticator
925             for ( Authenticator authenticator : getAuthenticators( authMech ) )
926             {
927                 authenticator.invalidateCache( principalDn );
928             }
929         }
930     }
931 
932     
933     /**
934      * {@inheritDoc}
935      */
936     @Override
937     public void modify( ModifyOperationContext modifyContext ) throws LdapException
938     {
939         if ( IS_DEBUG )
940         {
941             LOG.debug( "Operation Context: {}", modifyContext );
942         }
943 
944         checkAuthenticated( modifyContext );
945 
946         if ( !directoryService.isPwdPolicyEnabled() || modifyContext.isReplEvent() )
947         {
948             processStandardModify( modifyContext );
949         }
950         else
951         {
952             processPasswordPolicydModify( modifyContext );
953         }
954     }
955 
956     
957     /**
958      * Proceed with the Modification operation when the PasswordPolicy is not activated.
959      */
960     private void processStandardModify( ModifyOperationContext modifyContext ) throws LdapException
961     {
962         next( modifyContext );
963 
964         List<Modification> modifications = modifyContext.getModItems();
965 
966         for ( Modification modification : modifications )
967         {
968             if ( directoryService.getAtProvider().getUserPassword()
969                 .equals( modification.getAttribute().getAttributeType() ) )
970             {
971                 invalidateAuthenticatorCaches( modifyContext.getDn() );
972                 break;
973             }
974         }
975     }
976 
977     
978     /**
979      * Proceed with the Modification operation when the PasswordPolicy is activated.
980      */
981     private void processPasswordPolicydModify( ModifyOperationContext modifyContext ) throws LdapException
982     {
983         // handle the case where pwdPolicySubentry AT is about to be deleted in this modify()
984         PasswordPolicyConfiguration policyConfig = getPwdPolicy( modifyContext.getEntry() );
985 
986         PwdModDetailsHolder pwdModDetails = getPwdModDetails( modifyContext, policyConfig );
987 
988         if ( !pwdModDetails.isPwdModPresent() )
989         {
990             // We can going on, the password attribute is not present in the Modifications.
991             next( modifyContext );
992         }
993         else
994         {
995             // The password is present in the modifications. Deal with the various use cases.
996             CoreSession userSession = modifyContext.getSession();
997             boolean isPPolicyReqCtrlPresent = modifyContext.hasRequestControl( PasswordPolicyRequest.OID );
998             
999             // First, check if the password must be changed, and if the operation allows it
1000             checkPwdMustChange( modifyContext, userSession, pwdModDetails, isPPolicyReqCtrlPresent );
1001 
1002             // Check the the old password is present if it's required by the PP config
1003             checkOldPwdRequired( modifyContext, policyConfig, pwdModDetails, isPPolicyReqCtrlPresent );
1004 
1005             // Check that we can't update the password if it's not allowed
1006             checkChangePwdAllowed( modifyContext, policyConfig, isPPolicyReqCtrlPresent );
1007 
1008             Entry entry = modifyContext.getEntry();
1009 
1010             boolean removePwdReset = false;
1011 
1012             List<Modification> mods = new ArrayList<>();
1013 
1014             if ( pwdModDetails.isAddOrReplace() )
1015             {
1016                 if ( isPwdTooYoung( modifyContext, entry, policyConfig ) )
1017                 {
1018                     if ( isPPolicyReqCtrlPresent )
1019                     {
1020                         PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl();
1021                         responseControl.setPasswordPolicyError(
1022                             PasswordPolicyErrorEnum.PASSWORD_TOO_YOUNG );
1023                         modifyContext.addResponseControl( responseControl );
1024                     }
1025 
1026                     throw new LdapOperationException( ResultCodeEnum.CONSTRAINT_VIOLATION,
1027                         "password is too young to update" );
1028                 }
1029 
1030                 byte[] newPassword = pwdModDetails.getNewPwd();
1031 
1032                 try
1033                 {
1034                     check( modifyContext, entry, newPassword, policyConfig );
1035                 }
1036                 catch ( PasswordPolicyException e )
1037                 {
1038                     if ( isPPolicyReqCtrlPresent )
1039                     {
1040                         PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl();
1041                         responseControl.setPasswordPolicyError(
1042                             PasswordPolicyErrorEnum.get( e.getErrorCode() ) );
1043                         modifyContext.addResponseControl( responseControl );
1044                     }
1045 
1046                     // throw exception if userPassword quality checks fail
1047                     throw new LdapOperationException( ResultCodeEnum.CONSTRAINT_VIOLATION, e.getMessage(), e );
1048                 }
1049 
1050                 int histSize = policyConfig.getPwdInHistory();
1051                 Modification pwdRemHistMod = null;
1052                 Modification pwdAddHistMod = null;
1053                 String pwdChangedTime = DateUtils.getGeneralizedTime( directoryService.getTimeProvider() );
1054 
1055                 if ( histSize > 0 )
1056                 {
1057                     Attribute pwdHistoryAt = entry.get( pwdHistoryAT );
1058 
1059                     if ( pwdHistoryAt == null )
1060                     {
1061                         pwdHistoryAt = new DefaultAttribute( pwdHistoryAT );
1062                     }
1063 
1064                     // Build the Modification containing the password history
1065                     pwdRemHistMod = buildPwdHistory( modifyContext, pwdHistoryAt, histSize, 
1066                         newPassword, isPPolicyReqCtrlPresent );
1067 
1068                     PasswordHistorythn/PasswordHistory.html#PasswordHistory">PasswordHistory newPwdHist = new PasswordHistory( pwdChangedTime, newPassword );
1069                     pwdHistoryAt.add( newPwdHist.getHistoryValue() );
1070                     pwdAddHistMod = new DefaultModification( REPLACE_ATTRIBUTE, pwdHistoryAt );
1071                 }
1072 
1073                 next( modifyContext );
1074 
1075                 invalidateAuthenticatorCaches( modifyContext.getDn() );
1076 
1077                 LookupOperationContexttor/context/LookupOperationContext.html#LookupOperationContext">LookupOperationContext lookupContext = new LookupOperationContext( adminSession, modifyContext.getDn(),
1078                     SchemaConstants.ALL_ATTRIBUTES_ARRAY );
1079                 lookupContext.setPartition( modifyContext.getPartition() );
1080                 lookupContext.setTransaction( modifyContext.getTransaction() );
1081                 
1082                 entry = directoryService.getPartitionNexus().lookup( lookupContext );
1083 
1084                 if ( ( policyConfig.getPwdMinAge() > 0 ) || ( policyConfig.getPwdMaxAge() > 0 ) )
1085                 {
1086                     Attribute pwdChangedTimeAt = new DefaultAttribute( pwdChangedTimeAT );
1087                     pwdChangedTimeAt.add( pwdChangedTime );
1088                     Modification pwdChangedTimeMod = new DefaultModification( REPLACE_ATTRIBUTE, pwdChangedTimeAt );
1089                     mods.add( pwdChangedTimeMod );
1090                 }
1091 
1092                 if ( pwdAddHistMod != null )
1093                 {
1094                     mods.add( pwdAddHistMod );
1095                 }
1096 
1097                 if ( pwdRemHistMod != null )
1098                 {
1099                     mods.add( pwdRemHistMod );
1100                 }
1101 
1102                 if ( policyConfig.isPwdMustChange() )
1103                 {
1104                     Attribute pwdMustChangeAt = new DefaultAttribute( pwdResetAT );
1105                     Modification pwdMustChangeMod;
1106 
1107                     if ( modifyContext.getSession().isAnAdministrator() )
1108                     {
1109                         pwdMustChangeAt.add( "TRUE" );
1110                         pwdMustChangeMod = new DefaultModification( REPLACE_ATTRIBUTE, pwdMustChangeAt );
1111                     }
1112                     else
1113                     {
1114                         pwdMustChangeMod = new DefaultModification( REMOVE_ATTRIBUTE, pwdMustChangeAt );
1115                         removePwdReset = true;
1116                     }
1117 
1118                     mods.add( pwdMustChangeMod );
1119                 }
1120             }
1121 
1122             // Add the attributes that have been modified following a Add/Replace password
1123             processModifyAddPwdAttributes( entry, mods, pwdModDetails );
1124 
1125             String csnVal = directoryService.getCSN().toString();
1126             Modification csnMod = new DefaultModification( REPLACE_ATTRIBUTE, directoryService.getAtProvider()
1127                 .getEntryCSN(), csnVal );
1128             mods.add( csnMod );
1129 
1130             ModifyOperationContextcontext/ModifyOperationContext.html#ModifyOperationContext">ModifyOperationContext internalModifyCtx = new ModifyOperationContext( adminSession );
1131             internalModifyCtx.setPushToEvtInterceptor( true );
1132             internalModifyCtx.setDn( modifyContext.getDn() );
1133             internalModifyCtx.setEntry( entry );
1134             internalModifyCtx.setModItems( mods );
1135 
1136             internalModify( modifyContext, internalModifyCtx );
1137 
1138             if ( removePwdReset || pwdModDetails.isDelete() )
1139             {
1140                 userSession.setPwdMustChange( false );
1141             }
1142         }
1143     }
1144     
1145     
1146     /**
1147      * Build the list of passwordHistory
1148      */
1149     Modification buildPwdHistory( ModifyOperationContext modifyContext, Attribute pwdHistoryAt, 
1150         int histSize, byte[] newPassword, boolean isPPolicyReqCtrlPresent ) throws LdapOperationException
1151     {
1152         List<PasswordHistory> pwdHistLst = new ArrayList<>();
1153 
1154         for ( Value value : pwdHistoryAt )
1155         {
1156             PasswordHistoryore/authn/PasswordHistory.html#PasswordHistory">PasswordHistory pwdh = new PasswordHistory( Strings.utf8ToString( value.getBytes() ) );
1157 
1158             // Admin user is exempt from history check
1159             // https://issues.apache.org/jira/browse/DIRSERVER-2084 
1160             if ( !modifyContext.getSession().isAnAdministrator() )
1161             {
1162                 boolean matched = MessageDigest.isEqual( newPassword, pwdh.getPassword() );
1163 
1164                 if ( matched )
1165                 {
1166                     if ( isPPolicyReqCtrlPresent )
1167                     {
1168                         PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl();
1169                         responseControl.setPasswordPolicyError(
1170                             PasswordPolicyErrorEnum.PASSWORD_IN_HISTORY );
1171                         modifyContext.addResponseControl( responseControl );
1172                     }
1173 
1174                     throw new LdapOperationException( ResultCodeEnum.CONSTRAINT_VIOLATION,
1175                         "invalid reuse of password present in password history" );
1176                 }
1177             }
1178 
1179             pwdHistLst.add( pwdh );
1180         }
1181  
1182         Modification pwdRemHistMod = null;
1183         
1184         if ( pwdHistLst.size() >= histSize )
1185         {
1186             // see the javadoc of PasswordHistory
1187             Collections.sort( pwdHistLst );
1188 
1189             // remove the oldest value
1190             PasswordHistory/../org/apache/directory/server/core/authn/PasswordHistory.html#PasswordHistory">PasswordHistory remPwdHist = ( PasswordHistory ) pwdHistLst.toArray()[histSize - 1];
1191             Attribute tempAt = new DefaultAttribute( pwdHistoryAT );
1192             tempAt.add( remPwdHist.getHistoryValue() );
1193             pwdRemHistMod = new DefaultModification( REMOVE_ATTRIBUTE, tempAt );
1194         }
1195 
1196         return pwdRemHistMod;
1197     }
1198     
1199     
1200     /**
1201      * Add the passwordPolicy related Attributes from the modified entry
1202      */
1203     private void processModifyAddPwdAttributes( Entry entry, List<Modification> mods, PwdModDetailsHolder pwdModDetails )
1204     {
1205         Attribute pwdFailureTimeAt = entry.get( pwdFailurTimeAT );
1206     
1207         if ( pwdFailureTimeAt != null )
1208         {
1209             mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdFailureTimeAt ) );
1210         }
1211     
1212         Attribute pwdGraceUseTimeAt = entry.get( pwdGraceUseTimeAT );
1213     
1214         if ( pwdGraceUseTimeAt != null )
1215         {
1216             mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdGraceUseTimeAt ) );
1217         }
1218     
1219         if ( pwdModDetails.isDelete() )
1220         {
1221             Attribute pwdHistory = entry.get( pwdHistoryAT );
1222             
1223             if ( pwdHistory != null )
1224             {
1225                 mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdHistory ) );
1226             }
1227     
1228             Attribute pwdChangedTimeAt = entry.get( pwdChangedTimeAT );
1229             
1230             if ( pwdChangedTimeAt != null )
1231             {
1232                 mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdChangedTimeAt ) );
1233             }
1234     
1235             Attribute pwdMustChangeAt = entry.get( pwdResetAT );
1236             
1237             if ( pwdMustChangeAt != null )
1238             {
1239                 mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdMustChangeAt ) );
1240             }
1241     
1242             Attribute pwdAccountLockedTimeAt = entry.get( pwdAccountLockedTimeAT );
1243             
1244             if ( pwdAccountLockedTimeAt != null )
1245             {
1246                 mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdAccountLockedTimeAt ) );
1247             }
1248         }
1249     }
1250 
1251     
1252     /**
1253      * Check if the password has to be changed, but can't.
1254      */
1255     private void checkPwdMustChange( ModifyOperationContext modifyContext, CoreSession userSession, 
1256         PwdModDetailsHolder pwdModDetails, boolean isPPolicyReqCtrlPresent ) throws LdapNoPermissionException
1257     {
1258         if ( userSession.isPwdMustChange() && !pwdModDetails.isDelete() && pwdModDetails.isOtherModExists() )
1259        {
1260            if ( isPPolicyReqCtrlPresent )
1261            {
1262                PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl();
1263                responseControl.setPasswordPolicyError(
1264                    PasswordPolicyErrorEnum.CHANGE_AFTER_RESET );
1265                modifyContext.addResponseControl( responseControl );
1266            }
1267 
1268            throw new LdapNoPermissionException(
1269                "Password should be reset before making any changes to this entry" );
1270        }
1271     }
1272     
1273     
1274     /**
1275      * If the PP config request it, the old password must be supplied in the modifications. Check that it 
1276      * is present.
1277      */
1278     private void checkOldPwdRequired( ModifyOperationContext modifyContext, PasswordPolicyConfiguration policyConfig,
1279         PwdModDetailsHolder pwdModDetails, boolean isPPolicyReqCtrlPresent ) throws LdapNoPermissionException
1280     {
1281         if ( policyConfig.isPwdSafeModify() && !pwdModDetails.isDelete() && pwdModDetails.isAddOrReplace() )
1282         {
1283             String msg = "trying to update password attribute without the supplying the old password";
1284             LOG.debug( msg );
1285 
1286             if ( isPPolicyReqCtrlPresent )
1287             {
1288                 PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl();
1289                 responseControl.setPasswordPolicyError(
1290                     PasswordPolicyErrorEnum.MUST_SUPPLY_OLD_PASSWORD );
1291                 modifyContext.addResponseControl( responseControl );
1292             }
1293 
1294             throw new LdapNoPermissionException( msg );
1295         }
1296     }
1297     
1298     
1299     /**
1300      * check that if the password modification is allowed by the PP config, or if the session is 
1301      * the admin. 
1302      */
1303     private void checkChangePwdAllowed( ModifyOperationContext modifyContext, PasswordPolicyConfiguration policyConfig,
1304         boolean isPPolicyReqCtrlPresent ) throws LdapNoPermissionException
1305     {
1306         if ( !policyConfig.isPwdAllowUserChange() && !modifyContext.getSession().isAnAdministrator() )
1307              
1308         {
1309             if ( isPPolicyReqCtrlPresent )
1310             {
1311                 PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl();
1312                 responseControl.setPasswordPolicyError(
1313                     PasswordPolicyErrorEnum.PASSWORD_MOD_NOT_ALLOWED );
1314                 modifyContext.addResponseControl( responseControl );
1315             }
1316 
1317             throw new LdapNoPermissionException();
1318         }
1319     }
1320 
1321     
1322     /**
1323      * {@inheritDoc}
1324      */
1325     @Override
1326     public void move( MoveOperationContext moveContext ) throws LdapException
1327     {
1328         if ( IS_DEBUG )
1329         {
1330             LOG.debug( "Operation Context: {}", moveContext );
1331         }
1332 
1333         checkAuthenticated( moveContext );
1334         checkPwdReset( moveContext );
1335         next( moveContext );
1336         invalidateAuthenticatorCaches( moveContext.getDn() );
1337     }
1338 
1339 
1340     /**
1341      * {@inheritDoc}
1342      */
1343     @Override
1344     public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
1345     {
1346         if ( IS_DEBUG )
1347         {
1348             LOG.debug( "Operation Context: {}", moveAndRenameContext );
1349         }
1350 
1351         checkAuthenticated( moveAndRenameContext );
1352         checkPwdReset( moveAndRenameContext );
1353         next( moveAndRenameContext );
1354         invalidateAuthenticatorCaches( moveAndRenameContext.getDn() );
1355     }
1356 
1357 
1358     /**
1359      * {@inheritDoc}
1360      */
1361     @Override
1362     public void rename( RenameOperationContext renameContext ) throws LdapException
1363     {
1364         if ( IS_DEBUG )
1365         {
1366             LOG.debug( "Operation Context: {}", renameContext );
1367         }
1368 
1369         checkAuthenticated( renameContext );
1370         checkPwdReset( renameContext );
1371         next( renameContext );
1372         invalidateAuthenticatorCaches( renameContext.getDn() );
1373     }
1374 
1375 
1376     /**
1377      * {@inheritDoc}
1378      */
1379     @Override
1380     public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException
1381     {
1382         if ( IS_DEBUG )
1383         {
1384             LOG.debug( "Operation Context: {}", searchContext );
1385         }
1386 
1387         checkAuthenticated( searchContext );
1388         checkPwdReset( searchContext );
1389 
1390         return next( searchContext );
1391     }
1392 
1393 
1394     /**
1395      * {@inheritDoc}
1396      */
1397     @Override
1398     public void unbind( UnbindOperationContext unbindContext ) throws LdapException
1399     {
1400         next( unbindContext );
1401     }
1402 
1403 
1404     /**
1405      * Check if the current operation has a valid PrincipalDN or not.
1406      *
1407      * @param operation the operation type
1408      * @throws Exception
1409      */
1410     private void checkAuthenticated( OperationContext operation ) throws LdapException
1411     {
1412         if ( operation.getSession().isAnonymous() && !directoryService.isAllowAnonymousAccess()
1413             && !operation.getDn().isEmpty() )
1414         {
1415             String msg = I18n.err( I18n.ERR_5, operation.getName() );
1416             LOG.error( msg );
1417             throw new LdapNoPermissionException( msg );
1418         }
1419     }
1420 
1421 
1422     /**
1423      * Initialize the PasswordPolicy attributeTypes
1424      * 
1425      * @throws LdapException If the initialization failed
1426      */
1427     public void loadPwdPolicyStateAttributeTypes() throws LdapException
1428     {
1429         pwdResetAT = schemaManager.lookupAttributeTypeRegistry( PWD_RESET_AT );
1430         PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdResetAT );
1431 
1432         pwdChangedTimeAT = schemaManager.lookupAttributeTypeRegistry( PWD_CHANGED_TIME_AT );
1433         PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdChangedTimeAT );
1434 
1435         pwdHistoryAT = schemaManager.lookupAttributeTypeRegistry( PWD_HISTORY_AT );
1436         PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdHistoryAT );
1437 
1438         pwdFailurTimeAT = schemaManager.lookupAttributeTypeRegistry( PWD_FAILURE_TIME_AT );
1439         PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdFailurTimeAT );
1440 
1441         pwdAccountLockedTimeAT = schemaManager.lookupAttributeTypeRegistry( PWD_ACCOUNT_LOCKED_TIME_AT );
1442         PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdAccountLockedTimeAT );
1443 
1444         pwdLastSuccessAT = schemaManager.lookupAttributeTypeRegistry( PWD_LAST_SUCCESS_AT );
1445         PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdLastSuccessAT );
1446 
1447         pwdGraceUseTimeAT = schemaManager.lookupAttributeTypeRegistry( PWD_GRACE_USE_TIME_AT );
1448         PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdGraceUseTimeAT );
1449 
1450         pwdPolicySubentryAT = schemaManager.lookupAttributeTypeRegistry( PWD_POLICY_SUBENTRY_AT );
1451         PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdPolicySubentryAT );
1452 
1453         pwdStartTimeAT = schemaManager.lookupAttributeTypeRegistry( PWD_START_TIME_AT );
1454         PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdStartTimeAT );
1455 
1456         pwdEndTimeAT = schemaManager.lookupAttributeTypeRegistry( PWD_END_TIME_AT );
1457         PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdEndTimeAT );
1458     }
1459 
1460 
1461     // ---------- private methods ----------------
1462     private void check( OperationContext operationContext, Entry entry,
1463         byte[] password, PasswordPolicyConfiguration policyConfig )
1464         throws LdapException
1465     {
1466         // https://issues.apache.org/jira/browse/DIRSERVER-1928
1467         if ( operationContext.getSession().isAnAdministrator() )
1468         {
1469             return;
1470         }
1471         final CheckQualityEnum qualityVal = policyConfig.getPwdCheckQuality();
1472 
1473         if ( qualityVal == CheckQualityEnum.NO_CHECK )
1474         {
1475             return;
1476         }
1477 
1478         LdapSecurityConstants secConst = PasswordUtil.findAlgorithm( password );
1479 
1480         // do not perform quality check if the password is not plain text and
1481         // pwdCheckQuality value is set to 1
1482         if ( secConst != null )
1483         {
1484             if ( qualityVal == CheckQualityEnum.CHECK_ACCEPT )
1485             {
1486                 return;
1487             }
1488             else
1489             {
1490                 throw new PasswordPolicyException( "cannot verify the quality of the non-cleartext passwords",
1491                     INSUFFICIENT_PASSWORD_QUALITY.getValue() );
1492             }
1493         }
1494 
1495         String strPassword = Strings.utf8ToString( password );
1496 
1497         // perform the length validation
1498         validatePasswordLength( strPassword, policyConfig );
1499 
1500         PasswordValidator passwordValidator = policyConfig.getPwdValidator();
1501         
1502         if ( passwordValidator == null )
1503         {
1504             // Use the default one
1505             passwordValidator = new DefaultPasswordValidator();
1506         }
1507         
1508         passwordValidator.validate( strPassword, entry );
1509     }
1510 
1511 
1512     /**
1513      * validates the length of the password
1514      */
1515     private void validatePasswordLength( String password, PasswordPolicyConfiguration policyConfig )
1516         throws PasswordPolicyException
1517     {
1518         int maxLen = policyConfig.getPwdMaxLength();
1519         int minLen = policyConfig.getPwdMinLength();
1520 
1521         int pwdLen = password.length();
1522 
1523         if ( ( maxLen > 0 ) && ( pwdLen > maxLen ) )
1524         {
1525             throw new PasswordPolicyException( "Password should not have more than " + maxLen + " characters",
1526                 INSUFFICIENT_PASSWORD_QUALITY.getValue() );
1527         }
1528 
1529         if ( ( minLen > 0 ) && ( pwdLen < minLen ) )
1530         {
1531             throw new PasswordPolicyException( "Password should have a minimum of " + minLen + " characters",
1532                 PASSWORD_TOO_SHORT.getValue() );
1533         }
1534     }
1535 
1536 
1537     private int getPwdTimeBeforeExpiry( Entry userEntry, PasswordPolicyConfiguration policyConfig )
1538         throws LdapException
1539     {
1540         if ( policyConfig.getPwdMaxAge() == 0 )
1541         {
1542             return 0;
1543         }
1544 
1545         int warningAge = policyConfig.getPwdExpireWarning();
1546 
1547         if ( warningAge <= 0 )
1548         {
1549             return 0;
1550         }
1551 
1552         Attribute pwdChangedTimeAt = userEntry.get( pwdChangedTimeAT );
1553         if ( pwdChangedTimeAt == null )
1554         {
1555             pwdChangedTimeAt = userEntry.get( directoryService.getAtProvider().getCreateTimestamp() );
1556         }
1557         long changedTime = DateUtils.getDate( pwdChangedTimeAt.getString() ).getTime();
1558 
1559         long currentTime = directoryService.getTimeProvider().currentIimeMillis();
1560         long pwdAge = ( currentTime - changedTime ) / 1000;
1561 
1562         if ( pwdAge > policyConfig.getPwdMaxAge() )
1563         {
1564             return 0;
1565         }
1566 
1567         warningAge = policyConfig.getPwdMaxAge() - warningAge;
1568 
1569         if ( pwdAge >= warningAge )
1570         {
1571             long timeBeforeExpiration = ( ( long ) policyConfig.getPwdMaxAge() ) - pwdAge;
1572 
1573             if ( timeBeforeExpiration > Integer.MAX_VALUE )
1574             {
1575                 timeBeforeExpiration = Integer.MAX_VALUE;
1576             }
1577 
1578             return ( int ) timeBeforeExpiration;
1579         }
1580 
1581         return 0;
1582     }
1583 
1584 
1585     /**
1586      * checks if the password is too young
1587      *
1588      * @param userEntry the user's entry
1589      * @return true if the password is young, false otherwise
1590      * @throws LdapException
1591      */
1592     private boolean isPwdTooYoung( OperationContext operationContext,
1593         Entry userEntry, PasswordPolicyConfiguration policyConfig ) throws LdapException
1594     {
1595         // https://issues.apache.org/jira/browse/DIRSERVER-1928
1596         if ( operationContext.getSession().isAnAdministrator() )
1597         {
1598             return false;
1599         }
1600         if ( policyConfig.getPwdMinAge() == 0 )
1601         {
1602             return false;
1603         }
1604 
1605         CoreSession userSession = operationContext.getSession();
1606         
1607         // see sections 7.8 and 7.2 of the ppolicy draft
1608         if ( policyConfig.isPwdMustChange() && userSession.isPwdMustChange() )
1609         {
1610             return false;
1611         }
1612 
1613         Attribute pwdChangedTimeAt = userEntry.get( pwdChangedTimeAT );
1614 
1615         if ( pwdChangedTimeAt != null )
1616         {
1617             long changedTime = DateUtils.getDate( pwdChangedTimeAt.getString() ).getTime();
1618             changedTime += policyConfig.getPwdMinAge() * 1000L;
1619 
1620             long currentTime = directoryService.getTimeProvider().currentIimeMillis();
1621 
1622             if ( changedTime > currentTime )
1623             {
1624                 return true;
1625             }
1626         }
1627 
1628         return false;
1629     }
1630 
1631 
1632     /**
1633      * checks if the password must be changed after the initial bind
1634      *
1635      * @param userEntry the user's entry
1636      * @return true if must be changed, false otherwise
1637      * @throws LdapException
1638      */
1639     private boolean isPwdMustReset( Entry userEntry ) throws LdapException
1640     {
1641         boolean mustChange = false;
1642 
1643         Attribute pwdResetAt = userEntry.get( pwdResetAT );
1644 
1645         if ( pwdResetAt != null )
1646         {
1647             mustChange = Boolean.parseBoolean( pwdResetAt.getString() );
1648         }
1649 
1650         return mustChange;
1651     }
1652 
1653 
1654     private PwdModDetailsHolder getPwdModDetails( ModifyOperationContext modifyContext,
1655         PasswordPolicyConfiguration policyConfig ) throws LdapException
1656     {
1657         PwdModDetailsHolder pwdModDetails = new PwdModDetailsHolder();
1658 
1659         List<Modification> mods = modifyContext.getModItems();
1660 
1661         for ( Modification m : mods )
1662         {
1663             Attribute at = m.getAttribute();
1664             AttributeType passwordAttribute = schemaManager.lookupAttributeTypeRegistry( policyConfig.getPwdAttribute() );
1665 
1666             if ( at.getAttributeType().equals( passwordAttribute ) )
1667             {
1668                 pwdModDetails.setPwdModPresent( true );
1669                 ModificationOperation op = m.getOperation();
1670 
1671                 if ( op == REMOVE_ATTRIBUTE )
1672                 {
1673                     pwdModDetails.setDelete( true );
1674                 }
1675                 else if ( op == REPLACE_ATTRIBUTE || op == ADD_ATTRIBUTE )
1676                 {
1677                     pwdModDetails.setAddOrReplace( true );
1678                     pwdModDetails.setNewPwd( at.getBytes() );
1679                 }
1680             }
1681             else
1682             {
1683                 pwdModDetails.setOtherModExists( true );
1684             }
1685         }
1686 
1687         return pwdModDetails;
1688     }
1689 
1690 
1691     /**
1692      * checks to see if the user's password should be changed before performing any operations
1693      * other than bind, password update, unbind, abandon or StartTLS
1694      *
1695      * @param opContext the operation's context
1696      * @throws LdapException
1697      */
1698     private void checkPwdReset( OperationContext opContext ) throws LdapException
1699     {
1700         if ( directoryService.isPwdPolicyEnabled() )
1701         {
1702             CoreSession session = opContext.getSession();
1703 
1704             if ( session.isPwdMustChange() )
1705             {
1706                 boolean isPPolicyReqCtrlPresent = opContext
1707                     .hasRequestControl( PasswordPolicyRequest.OID );
1708 
1709                 if ( isPPolicyReqCtrlPresent )
1710                 {
1711                     PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl();
1712                     responseControl.setPasswordPolicyError( PasswordPolicyErrorEnum.CHANGE_AFTER_RESET );
1713                     opContext.addResponseControl( responseControl );
1714                 }
1715 
1716                 throw new LdapNoPermissionException( "password needs to be reset before performing this operation" );
1717             }
1718         }
1719     }
1720 
1721     private static class PwdModDetailsHolder
1722     {
1723         private boolean pwdModPresent = false;
1724 
1725         private boolean isDelete = false;
1726 
1727         private boolean isAddOrReplace = false;
1728 
1729         private boolean otherModExists = false;
1730 
1731         private byte[] newPwd;
1732 
1733 
1734         public boolean isPwdModPresent()
1735         {
1736             return pwdModPresent;
1737         }
1738 
1739 
1740         public void setPwdModPresent( boolean pwdModPresent )
1741         {
1742             this.pwdModPresent = pwdModPresent;
1743         }
1744 
1745 
1746         public boolean isDelete()
1747         {
1748             return isDelete;
1749         }
1750 
1751 
1752         public void setDelete( boolean isDelete )
1753         {
1754             this.isDelete = isDelete;
1755         }
1756 
1757 
1758         public boolean isAddOrReplace()
1759         {
1760             return isAddOrReplace;
1761         }
1762 
1763 
1764         public void setAddOrReplace( boolean isAddOrReplace )
1765         {
1766             this.isAddOrReplace = isAddOrReplace;
1767         }
1768 
1769 
1770         public boolean isOtherModExists()
1771         {
1772             return otherModExists;
1773         }
1774 
1775 
1776         public void setOtherModExists( boolean otherModExists )
1777         {
1778             this.otherModExists = otherModExists;
1779         }
1780 
1781 
1782         public byte[] getNewPwd()
1783         {
1784             return newPwd;
1785         }
1786 
1787 
1788         public void setNewPwd( byte[] newPwd )
1789         {
1790             this.newPwd = newPwd;
1791         }
1792     }
1793 
1794 
1795     /**
1796      * Gets the effective password policy of the given entry.
1797      * If the entry has defined a custom password policy by setting "pwdPolicySubentry" attribute
1798      * then the password policy associated with the Dn specified at the above attribute's value will be returned.
1799      * Otherwise the default password policy will be returned (if present)
1800      * 
1801      * @param userEntry the user's entry
1802      * @return the associated password policy
1803      * @throws LdapException If we weren't able to ftech the password policy
1804      */
1805     public PasswordPolicyConfiguration getPwdPolicy( Entry userEntry ) throws LdapException
1806     {
1807         if ( pwdPolicyContainer == null )
1808         {
1809             return null;
1810         }
1811 
1812         if ( userEntry == null )
1813         {
1814             return pwdPolicyContainer.getDefaultPolicy();
1815         }
1816 
1817         if ( pwdPolicyContainer.hasCustomConfigs() )
1818         {
1819             Attribute pwdPolicySubentry = userEntry.get( pwdPolicySubentryAT );
1820 
1821             if ( pwdPolicySubentry != null )
1822             {
1823                 Dn configDn = dnFactory.create( pwdPolicySubentry.getString() );
1824 
1825                 PasswordPolicyConfiguration custom = pwdPolicyContainer.getPolicyConfig( configDn );
1826                 
1827                 if ( custom != null )
1828                 {
1829                     return custom;
1830                 }
1831                 else
1832                 {
1833                     LOG.warn(
1834                         "The custom password policy for the user entry {} is not found, returning default policy configuration",
1835                         userEntry.getDn() );
1836                 }
1837             }
1838         }
1839 
1840         return pwdPolicyContainer.getDefaultPolicy();
1841     }
1842 
1843 
1844     /**
1845      * set all the password policies to be used by the server.
1846      * This includes a default(i.e applicable to all entries) and custom(a.k.a per user) password policies
1847      * 
1848      * @param policyContainer the container holding all the password policies
1849      */
1850     public void setPwdPolicies( PpolicyConfigContainer policyContainer )
1851     {
1852         this.pwdPolicyContainer = policyContainer;
1853     }
1854 
1855 
1856     /**
1857      * {@inheritDoc}
1858      */
1859     public boolean isPwdPolicyEnabled()
1860     {
1861         return ( pwdPolicyContainer != null )
1862         && ( ( pwdPolicyContainer.getDefaultPolicy() != null )
1863         || ( pwdPolicyContainer.hasCustomConfigs() ) );
1864     }
1865 
1866 
1867     /**
1868      * @return the pwdPolicyContainer
1869      */
1870     public PpolicyConfigContainer getPwdPolicyContainer()
1871     {
1872         return pwdPolicyContainer;
1873     }
1874 
1875 
1876     /**
1877      * @param pwdPolicyContainer the pwdPolicyContainer to set
1878      */
1879     public void setPwdPolicyContainer( PpolicyConfigContainer pwdPolicyContainer )
1880     {
1881         this.pwdPolicyContainer = pwdPolicyContainer;
1882     }
1883 
1884 
1885     /**
1886      * purges failure timestamps which are older than the configured interval
1887      * (section 7.6 in the draft)
1888      */
1889     private void purgeFailureTimes( PasswordPolicyConfiguration config, Attribute pwdFailTimeAt )
1890     {
1891         long interval = config.getPwdFailureCountInterval();
1892 
1893         if ( interval == 0 )
1894         {
1895             return;
1896         }
1897 
1898         interval *= 1000;
1899 
1900         long currentTime = directoryService.getTimeProvider().currentIimeMillis();
1901 
1902         Iterator<Value> itr = pwdFailTimeAt.iterator();
1903 
1904         while ( itr.hasNext() )
1905         {
1906             Value value = itr.next();
1907             String failureTime = value.getString();
1908             long time = DateUtils.getDate( failureTime ).getTime();
1909             time += interval;
1910 
1911             if ( currentTime >= time )
1912             {
1913                 itr.remove();
1914             }
1915         }
1916     }
1917 }