001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 * 
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 * 
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 * 
019 */
020package org.apache.directory.server.core.authn;
021
022
023import static org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyErrorEnum.ACCOUNT_LOCKED;
024import static org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyErrorEnum.PASSWORD_EXPIRED;
025import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_ACCOUNT_LOCKED_TIME_AT;
026import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_CHANGED_TIME_AT;
027import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_END_TIME_AT;
028import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_GRACE_USE_TIME_AT;
029import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_LAST_SUCCESS_AT;
030import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_START_TIME_AT;
031
032import java.io.IOException;
033import java.util.Collections;
034import java.util.Date;
035
036import org.apache.directory.api.ldap.model.constants.AuthenticationLevel;
037import org.apache.directory.api.ldap.model.entry.Attribute;
038import org.apache.directory.api.ldap.model.entry.DefaultModification;
039import org.apache.directory.api.ldap.model.entry.Entry;
040import org.apache.directory.api.ldap.model.entry.Modification;
041import org.apache.directory.api.ldap.model.entry.ModificationOperation;
042import org.apache.directory.api.ldap.model.exception.LdapException;
043import org.apache.directory.api.ldap.model.exception.LdapOtherException;
044import org.apache.directory.api.ldap.model.name.Dn;
045import org.apache.directory.api.ldap.model.password.PasswordUtil;
046import org.apache.directory.api.util.DateUtils;
047import org.apache.directory.server.core.api.DirectoryService;
048import org.apache.directory.server.core.api.InterceptorEnum;
049import org.apache.directory.server.core.api.authn.ppolicy.PasswordPolicyConfiguration;
050import org.apache.directory.server.core.api.authn.ppolicy.PasswordPolicyException;
051import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
052import org.apache.directory.server.core.api.partition.Partition;
053import org.apache.directory.server.core.api.partition.PartitionTxn;
054import org.slf4j.Logger;
055import org.slf4j.LoggerFactory;
056
057
058/**
059 * Base class for all Authenticators.
060 *
061 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
062 */
063public abstract class AbstractAuthenticator implements Authenticator
064{
065    /** A logger for the extending classes */
066    protected static final Logger LOG = LoggerFactory.getLogger( AbstractAuthenticator.class );
067
068    /** The associated DirectoryService */
069    private DirectoryService directoryService;
070
071    /** authenticator type */
072    private final AuthenticationLevel authenticatorType;
073
074    /** The base DN which will be the starting point from which we use the authenticator */
075    private Dn baseDn;
076
077
078    /**
079     * Creates a new instance.
080     *
081     * @param type the type of this authenticator (e.g. <tt>'simple'</tt>, <tt>'none'</tt>...)
082     */
083    protected AbstractAuthenticator( AuthenticationLevel type )
084    {
085        this.authenticatorType = type;
086        this.baseDn = Dn.ROOT_DSE;
087    }
088
089
090    /**
091     * Creates a new instance.
092     *
093     * @param type the type of this authenticator (e.g. <tt>'simple'</tt>, <tt>'none'</tt>...)
094     * @param baseDn The base DN for this authenticator
095     */
096    protected AbstractAuthenticator( AuthenticationLevel type, Dn baseDn )
097    {
098        this.authenticatorType = type;
099        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        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                        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}