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.ACCOUNT_LOCKED;
24  import static org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyErrorEnum.PASSWORD_EXPIRED;
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_END_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_LAST_SUCCESS_AT;
30  import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_START_TIME_AT;
31  
32  import java.io.IOException;
33  import java.util.Collections;
34  import java.util.Date;
35  
36  import org.apache.directory.api.ldap.model.constants.AuthenticationLevel;
37  import org.apache.directory.api.ldap.model.entry.Attribute;
38  import org.apache.directory.api.ldap.model.entry.DefaultModification;
39  import org.apache.directory.api.ldap.model.entry.Entry;
40  import org.apache.directory.api.ldap.model.entry.Modification;
41  import org.apache.directory.api.ldap.model.entry.ModificationOperation;
42  import org.apache.directory.api.ldap.model.exception.LdapException;
43  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
44  import org.apache.directory.api.ldap.model.name.Dn;
45  import org.apache.directory.api.ldap.model.password.PasswordUtil;
46  import org.apache.directory.api.util.DateUtils;
47  import org.apache.directory.server.core.api.DirectoryService;
48  import org.apache.directory.server.core.api.InterceptorEnum;
49  import org.apache.directory.server.core.api.authn.ppolicy.PasswordPolicyConfiguration;
50  import org.apache.directory.server.core.api.authn.ppolicy.PasswordPolicyException;
51  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
52  import org.apache.directory.server.core.api.partition.Partition;
53  import org.apache.directory.server.core.api.partition.PartitionTxn;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
57  
58  /**
59   * Base class for all Authenticators.
60   *
61   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
62   */
63  public abstract class AbstractAuthenticator implements Authenticator
64  {
65      /** A logger for the extending classes */
66      protected static final Logger LOG = LoggerFactory.getLogger( AbstractAuthenticator.class );
67  
68      /** The associated DirectoryService */
69      private DirectoryService directoryService;
70  
71      /** authenticator type */
72      private final AuthenticationLevel authenticatorType;
73  
74      /** The base DN which will be the starting point from which we use the authenticator */
75      private Dn baseDn;
76  
77  
78      /**
79       * Creates a new instance.
80       *
81       * @param type the type of this authenticator (e.g. <tt>'simple'</tt>, <tt>'none'</tt>...)
82       */
83      protected AbstractAuthenticator( AuthenticationLevel type )
84      {
85          this.authenticatorType = type;
86          this.baseDn = Dn.ROOT_DSE;
87      }
88  
89  
90      /**
91       * Creates a new instance.
92       *
93       * @param type the type of this authenticator (e.g. <tt>'simple'</tt>, <tt>'none'</tt>...)
94       * @param baseDn The base DN for this authenticator
95       */
96      protected AbstractAuthenticator( AuthenticationLevel type, Dn baseDn )
97      {
98          this.authenticatorType = type;
99          this.baseDn = baseDn;
100     }
101 
102 
103     /**
104      * Returns {@link DirectoryService} for this authenticator.
105      * @return the directory service core
106      */
107     public DirectoryService getDirectoryService()
108     {
109         return directoryService;
110     }
111 
112 
113     /**
114      * {@inheritDoc}
115      */
116     @Override
117     public AuthenticationLevel getAuthenticatorType()
118     {
119         return authenticatorType;
120     }
121 
122 
123     /**
124      * Initializes (<tt>directoryService</tt> and and calls {@link #doInit()} method.
125      * Please put your initialization code into {@link #doInit()}.
126      * @param directoryService the directory core for this authenticator
127      * @throws LdapException if there is a problem starting up the authenticator
128      */
129     @Override
130     public final void init( DirectoryService directoryService ) throws LdapException
131     {
132         this.directoryService = directoryService;
133         doInit();
134     }
135 
136 
137     /**
138      * Implement your initialization code here.
139      */
140     protected void doInit()
141     {
142     }
143 
144 
145     /**
146      * Calls {@link #doDestroy()} method, and clears default properties
147      * (<tt>factoryConfiguration</tt> and <tt>configuration</tt>).
148      * Please put your deinitialization code into {@link #doDestroy()}.
149      */
150     @Override
151     public final void destroy()
152     {
153         try
154         {
155             doDestroy();
156         }
157         finally
158         {
159             this.directoryService = null;
160         }
161     }
162 
163 
164     /**
165      * Implement your deinitialization code here.
166      */
167     protected void doDestroy()
168     {
169     }
170 
171 
172     /**
173      * Does nothing leaving it so subclasses can override.
174      */
175     @Override
176     public void invalidateCache( Dn bindDn )
177     {
178     }
179 
180 
181     /**
182      * {@inheritDoc}
183      */
184     @Override
185     public boolean isValid( Dn bindDn )
186     {
187         // The authenticator is valid if the baseDn is null or if it's a parent of the bindDn
188         return ( baseDn == null ) || ( baseDn.isAncestorOf( bindDn ) );
189     }
190 
191 
192     /**
193      * {@inheritDoc}
194      */
195     @Override
196     public Dn getBaseDn()
197     {
198         return baseDn;
199     }
200 
201 
202     /**
203      * {@inheritDoc}
204      */
205     @Override
206     public void setBaseDn( Dn baseDn )
207     {
208         this.baseDn = baseDn;
209     }
210     
211     
212     private void internalModify( ModifyOperationContext modContext ) throws LdapException
213     {
214         Partition partition = directoryService.getPartitionNexus().getPartition( modContext.getDn() );
215         modContext.setPartition( partition );
216         PartitionTxn partitionTxn = null;
217 
218         try
219         {
220             partitionTxn = partition.beginWriteTransaction();
221             modContext.setTransaction( partitionTxn );
222 
223             directoryService.getPartitionNexus().modify( modContext );
224 
225             partitionTxn.commit();
226         }
227         catch ( LdapException le )
228         {
229             try 
230             {
231                 if ( partitionTxn != null )
232                 {
233                     partitionTxn.abort();
234                 }
235                 
236                 throw le;
237             }
238             catch ( IOException ioe )
239             {
240                 throw new LdapOtherException( ioe.getMessage(), ioe );
241             }
242         }
243         catch ( IOException ioe )
244         {
245             try 
246             {
247                 partitionTxn.abort();
248                 
249                 throw new LdapOtherException( ioe.getMessage(), ioe );
250             }
251             catch ( IOException ioe2 )
252             {
253                 throw new LdapOtherException( ioe2.getMessage(), ioe2 );
254             }
255         }
256     }
257 
258 
259     /**
260      * {@inheritDoc}
261      */
262     @Override
263     public void checkPwdPolicy( Entry userEntry ) throws LdapException
264     {
265         if ( !directoryService.isPwdPolicyEnabled() )
266         {
267             return;
268         }
269 
270         AuthenticationInterceptorserver/core/authn/AuthenticationInterceptor.html#AuthenticationInterceptor">AuthenticationInterceptor authenticationInterceptor = ( AuthenticationInterceptor ) directoryService
271             .getInterceptor(
272             InterceptorEnum.AUTHENTICATION_INTERCEPTOR.getName() );
273         PasswordPolicyConfiguration pPolicyConfig = authenticationInterceptor.getPwdPolicy( userEntry );
274 
275         // check for locked out account
276         if ( pPolicyConfig.isPwdLockout() )
277         {
278             LOG.debug( "checking if account with the Dn {} is locked", userEntry.getDn() );
279 
280             Attribute accountLockAttr = userEntry.get( PWD_ACCOUNT_LOCKED_TIME_AT );
281 
282             if ( accountLockAttr != null )
283             {
284                 String lockedTime = accountLockAttr.getString();
285                 
286                 if ( "000001010000Z".equals( lockedTime ) )
287                 {
288                     throw new PasswordPolicyException( "account was permanently locked", ACCOUNT_LOCKED.getValue() );
289                 }
290                 else
291                 {
292                     Date lockedDate = DateUtils.getDate( lockedTime );
293                     long unlockTime = pPolicyConfig.getPwdLockoutDuration() * 1000L;
294                     unlockTime += lockedDate.getTime();
295 
296                     Date unlockDate = new Date( unlockTime );
297                     Date now = new Date( directoryService.getTimeProvider().currentIimeMillis() );
298 
299                     if ( unlockDate.after( now ) )
300                     {
301                         throw new PasswordPolicyException( "account will remain locked till " + unlockDate,
302                             ACCOUNT_LOCKED.getValue() );
303                     }
304                     else
305                     {
306                         // remove pwdAccountLockedTime attribute
307                         Modification pwdAccountLockMod = new DefaultModification(
308                             ModificationOperation.REMOVE_ATTRIBUTE, accountLockAttr );
309                         ModifyOperationContextceptor/context/ModifyOperationContext.html#ModifyOperationContext">ModifyOperationContext modContext = new ModifyOperationContext(
310                             directoryService.getAdminSession() );
311                         modContext.setDn( userEntry.getDn() );
312                         modContext.setModItems( Collections.singletonList( pwdAccountLockMod ) );
313 
314                         internalModify( modContext );
315                     }
316                 }
317             }
318         }
319 
320         Attribute pwdStartTimeAttr = userEntry.get( PWD_START_TIME_AT );
321 
322         if ( pwdStartTimeAttr != null )
323         {
324             Date pwdStartTime = DateUtils.getDate( pwdStartTimeAttr.getString() );
325 
326             if ( System.currentTimeMillis() < pwdStartTime.getTime() )
327             {
328                 throw new PasswordPolicyException( "account is locked, will be activated after " + pwdStartTime,
329                     ACCOUNT_LOCKED.getValue() );
330             }
331         }
332 
333         Attribute pwdEndTimeAttr = userEntry.get( PWD_END_TIME_AT );
334 
335         if ( pwdEndTimeAttr != null )
336         {
337             Date pwdEndTime = DateUtils.getDate( pwdEndTimeAttr.getString() );
338 
339             if ( System.currentTimeMillis() >= pwdEndTime.getTime() )
340             {
341                 throw new PasswordPolicyException(
342                     "password end time reached, will be locked till administrator activates it",
343                     ACCOUNT_LOCKED.getValue() );
344             }
345         }
346 
347         if ( pPolicyConfig.getPwdMaxIdle() > 0 )
348         {
349             Attribute pwdLastSuccessTimeAttr = userEntry.get( PWD_LAST_SUCCESS_AT );
350 
351             // Let's be sure that the user has already logged in
352             if ( pwdLastSuccessTimeAttr != null )
353             {
354                 long time = pPolicyConfig.getPwdMaxIdle() * 1000L;
355                 time += DateUtils.getDate( pwdLastSuccessTimeAttr.getString() ).getTime();
356 
357                 if ( directoryService.getTimeProvider().currentIimeMillis() >= time )
358                 {
359                     throw new PasswordPolicyException(
360                         "account locked due to the max idle time of the password was exceeded",
361                         ACCOUNT_LOCKED.getValue() );
362                 }
363             }
364         }
365 
366         // Check that the password is not too old and need to be disabled
367         if ( pPolicyConfig.getPwdMaxAge() > 0 )
368         {
369             // In case we have a grace number of attempts
370             if ( pPolicyConfig.getPwdGraceAuthNLimit() > 0 )
371             {
372                 Attribute pwdGraceUseAttr = userEntry.get( PWD_GRACE_USE_TIME_AT );
373 
374                 // check for grace authentication count
375                 if ( ( pwdGraceUseAttr != null ) && ( pwdGraceUseAttr.size() >= pPolicyConfig.getPwdGraceAuthNLimit() ) )
376                 {
377                     throw new PasswordPolicyException( "password expired and max grace logins were used",
378                         PASSWORD_EXPIRED.getValue() );
379                 }
380             }
381             else
382             {
383                 // No grace attempt : check if the password has expired or not
384                 Attribute pwdChangeTimeAttr = userEntry.get( PWD_CHANGED_TIME_AT );
385 
386                 // If the attr is null, this is the admin user. We don't block it
387                 if ( pwdChangeTimeAttr != null )
388                 {
389                     boolean expired = PasswordUtil.isPwdExpired( pwdChangeTimeAttr.getString(),
390                         pPolicyConfig.getPwdMaxAge(), directoryService.getTimeProvider() );
391 
392                     if ( expired )
393                     {
394                         throw new PasswordPolicyException( "password expired", PASSWORD_EXPIRED.getValue() );
395                     }
396                 }
397             }
398         }
399     }
400 }