| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.server.core.authn; |
| |
| |
| import static org.apache.directory.shared.ldap.extras.controls.PasswordPolicyErrorEnum.INSUFFICIENT_PASSWORD_QUALITY; |
| import static org.apache.directory.shared.ldap.extras.controls.PasswordPolicyErrorEnum.PASSWORD_TOO_SHORT; |
| import static org.apache.directory.shared.ldap.model.constants.PasswordPolicySchemaConstants.PWD_ACCOUNT_LOCKED_TIME_AT; |
| import static org.apache.directory.shared.ldap.model.constants.PasswordPolicySchemaConstants.PWD_CHANGED_TIME_AT; |
| import static org.apache.directory.shared.ldap.model.constants.PasswordPolicySchemaConstants.PWD_EXPIRE_WARNING_AT; |
| import static org.apache.directory.shared.ldap.model.constants.PasswordPolicySchemaConstants.PWD_FAILURE_TIME_AT; |
| import static org.apache.directory.shared.ldap.model.constants.PasswordPolicySchemaConstants.PWD_GRACE_USE_TIME_AT; |
| import static org.apache.directory.shared.ldap.model.constants.PasswordPolicySchemaConstants.PWD_HISTORY_AT; |
| import static org.apache.directory.shared.ldap.model.constants.PasswordPolicySchemaConstants.PWD_LAST_SUCCESS_AT; |
| import static org.apache.directory.shared.ldap.model.constants.PasswordPolicySchemaConstants.PWD_RESET_AT; |
| import static org.apache.directory.shared.ldap.model.entry.ModificationOperation.ADD_ATTRIBUTE; |
| import static org.apache.directory.shared.ldap.model.entry.ModificationOperation.REMOVE_ATTRIBUTE; |
| import static org.apache.directory.shared.ldap.model.entry.ModificationOperation.REPLACE_ATTRIBUTE; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import org.apache.directory.server.core.CoreSession; |
| import org.apache.directory.server.core.DefaultCoreSession; |
| import org.apache.directory.server.core.DirectoryService; |
| import org.apache.directory.server.core.LdapPrincipal; |
| import org.apache.directory.server.core.PasswordPolicyConfiguration; |
| import org.apache.directory.server.core.admin.AdministrativePointInterceptor; |
| import org.apache.directory.server.core.authz.AciAuthorizationInterceptor; |
| import org.apache.directory.server.core.authz.DefaultAuthorizationInterceptor; |
| import org.apache.directory.server.core.collective.CollectiveAttributeInterceptor; |
| import org.apache.directory.server.core.event.EventInterceptor; |
| import org.apache.directory.server.core.exception.ExceptionInterceptor; |
| import org.apache.directory.server.core.filtering.EntryFilteringCursor; |
| import org.apache.directory.server.core.interceptor.BaseInterceptor; |
| import org.apache.directory.server.core.interceptor.Interceptor; |
| import org.apache.directory.server.core.interceptor.NextInterceptor; |
| import org.apache.directory.server.core.interceptor.context.AddOperationContext; |
| import org.apache.directory.server.core.interceptor.context.BindOperationContext; |
| import org.apache.directory.server.core.interceptor.context.CompareOperationContext; |
| import org.apache.directory.server.core.interceptor.context.DeleteOperationContext; |
| import org.apache.directory.server.core.interceptor.context.EntryOperationContext; |
| import org.apache.directory.server.core.interceptor.context.GetRootDSEOperationContext; |
| import org.apache.directory.server.core.interceptor.context.ListOperationContext; |
| import org.apache.directory.server.core.interceptor.context.LookupOperationContext; |
| import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; |
| import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext; |
| import org.apache.directory.server.core.interceptor.context.MoveOperationContext; |
| import org.apache.directory.server.core.interceptor.context.OperationContext; |
| import org.apache.directory.server.core.interceptor.context.RenameOperationContext; |
| import org.apache.directory.server.core.interceptor.context.SearchOperationContext; |
| import org.apache.directory.server.core.interceptor.context.UnbindOperationContext; |
| import org.apache.directory.server.core.normalization.NormalizationInterceptor; |
| import org.apache.directory.server.core.operational.OperationalAttributeInterceptor; |
| import org.apache.directory.server.core.schema.SchemaInterceptor; |
| import org.apache.directory.server.core.subtree.SubentryInterceptor; |
| import org.apache.directory.server.core.trigger.TriggerInterceptor; |
| import org.apache.directory.server.i18n.I18n; |
| import org.apache.directory.shared.ldap.extras.controls.PasswordPolicy; |
| import org.apache.directory.shared.ldap.extras.controls.PasswordPolicyErrorEnum; |
| import org.apache.directory.shared.ldap.extras.controls.ppolicy_impl.PasswordPolicyDecorator; |
| import org.apache.directory.shared.ldap.model.constants.AuthenticationLevel; |
| import org.apache.directory.shared.ldap.model.constants.LdapSecurityConstants; |
| import org.apache.directory.shared.ldap.model.constants.SchemaConstants; |
| import org.apache.directory.shared.ldap.model.entry.BinaryValue; |
| import org.apache.directory.shared.ldap.model.entry.DefaultEntryAttribute; |
| import org.apache.directory.shared.ldap.model.entry.DefaultModification; |
| import org.apache.directory.shared.ldap.model.entry.Entry; |
| import org.apache.directory.shared.ldap.model.entry.EntryAttribute; |
| import org.apache.directory.shared.ldap.model.entry.Modification; |
| import org.apache.directory.shared.ldap.model.entry.ModificationOperation; |
| import org.apache.directory.shared.ldap.model.entry.StringValue; |
| import org.apache.directory.shared.ldap.model.entry.Value; |
| import org.apache.directory.shared.ldap.model.exception.LdapAuthenticationException; |
| import org.apache.directory.shared.ldap.model.exception.LdapException; |
| import org.apache.directory.shared.ldap.model.exception.LdapNoPermissionException; |
| import org.apache.directory.shared.ldap.model.exception.LdapOperationException; |
| import org.apache.directory.shared.ldap.model.exception.LdapUnwillingToPerformException; |
| import org.apache.directory.shared.ldap.model.message.ResultCodeEnum; |
| import org.apache.directory.shared.ldap.model.name.Dn; |
| import org.apache.directory.shared.ldap.model.schema.MutableAttributeTypeImpl; |
| import org.apache.directory.shared.util.DateUtils; |
| import org.apache.directory.shared.util.StringConstants; |
| import org.apache.directory.shared.util.Strings; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * An {@link Interceptor} that authenticates users. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class AuthenticationInterceptor extends BaseInterceptor |
| { |
| private static final Logger LOG = LoggerFactory.getLogger( AuthenticationInterceptor.class ); |
| |
| /** |
| * Speedup for logs |
| */ |
| private static final boolean IS_DEBUG = LOG.isDebugEnabled(); |
| |
| /** A Set of all the existing Authenticator to be used by the bind operation */ |
| private Set<Authenticator> authenticators = new HashSet<Authenticator>(); |
| |
| /** A map of authenticators associated with the authentication level required */ |
| private final Map<AuthenticationLevel, Collection<Authenticator>> authenticatorsMapByType = new HashMap<AuthenticationLevel, Collection<Authenticator>>(); |
| |
| private CoreSession adminSession; |
| |
| private Set<Dn> pwdResetSet = new HashSet<Dn>(); |
| |
| // pwdpolicy state attribute types |
| private MutableAttributeTypeImpl AT_PWD_RESET; |
| |
| private MutableAttributeTypeImpl AT_PWD_CHANGED_TIME; |
| |
| private MutableAttributeTypeImpl AT_PWD_HISTORY; |
| |
| private MutableAttributeTypeImpl AT_PWD_FAILURE_TIME; |
| |
| private MutableAttributeTypeImpl AT_PWD_ACCOUNT_LOCKED_TIME; |
| |
| private MutableAttributeTypeImpl AT_PWD_LAST_SUCCESS; |
| |
| private MutableAttributeTypeImpl AT_PWD_GRACE_USE_TIME; |
| |
| |
| /** |
| * the set of interceptors we should *not* go through when pwdpolicy state information is being updated |
| */ |
| private static final Collection<String> BYPASS_INTERCEPTORS; |
| |
| static |
| { |
| Set<String> c = new HashSet<String>(); |
| c.add( NormalizationInterceptor.class.getName() ); |
| c.add( AuthenticationInterceptor.class.getName() ); |
| c.add( AciAuthorizationInterceptor.class.getName() ); |
| c.add( AdministrativePointInterceptor.class.getName() ); |
| c.add( DefaultAuthorizationInterceptor.class.getName() ); |
| c.add( AdministrativePointInterceptor.class.getName() ); |
| c.add( ExceptionInterceptor.class.getName() ); |
| c.add( OperationalAttributeInterceptor.class.getName() ); |
| c.add( SchemaInterceptor.class.getName() ); |
| c.add( CollectiveAttributeInterceptor.class.getName() ); |
| c.add( SubentryInterceptor.class.getName() ); |
| c.add( EventInterceptor.class.getName() ); |
| c.add( TriggerInterceptor.class.getName() ); |
| BYPASS_INTERCEPTORS = Collections.unmodifiableCollection( c ); |
| } |
| |
| |
| /** |
| * Creates an authentication service interceptor. |
| */ |
| public AuthenticationInterceptor() |
| { |
| } |
| |
| |
| /** |
| * Registers and initializes all {@link Authenticator}s to this service. |
| */ |
| public void init( DirectoryService directoryService ) throws LdapException |
| { |
| super.init( directoryService ); |
| |
| adminSession = directoryService.getAdminSession(); |
| |
| loadPwdPolicyStateAtributeTypes(); |
| |
| if ( ( authenticators == null ) || ( authenticators.size() == 0 ) ) |
| { |
| setDefaultAuthenticators(); |
| } |
| |
| // Register all authenticators |
| for ( Authenticator authenticator : authenticators ) |
| { |
| register( authenticator, directoryService ); |
| } |
| } |
| |
| |
| /** |
| * Initialize the set of authenticators with some default values |
| */ |
| private void setDefaultAuthenticators() |
| { |
| if ( authenticators == null ) |
| { |
| authenticators = new HashSet<Authenticator>(); |
| } |
| |
| authenticators.clear(); |
| authenticators.add( new AnonymousAuthenticator() ); |
| authenticators.add( new SimpleAuthenticator() ); |
| authenticators.add( new StrongAuthenticator() ); |
| } |
| |
| |
| public Set<Authenticator> getAuthenticators() |
| { |
| return authenticators; |
| } |
| |
| |
| /** |
| * @param authenticators authenticators to be used by this AuthenticationInterceptor |
| */ |
| public void setAuthenticators( Set<Authenticator> authenticators ) |
| { |
| if ( authenticators == null ) |
| { |
| this.authenticators.clear(); |
| } |
| else |
| { |
| this.authenticators = authenticators; |
| } |
| } |
| |
| |
| /** |
| * @param authenticators authenticators to be used by this AuthenticationInterceptor |
| */ |
| public void setAuthenticators( Authenticator[] authenticators ) |
| { |
| if ( authenticators == null ) |
| { |
| throw new IllegalArgumentException( "The given authenticators set is null" ); |
| } |
| |
| this.authenticators.clear(); |
| |
| for (Authenticator authenticator : authenticators) |
| { |
| this.authenticators.add( authenticator ); |
| } |
| } |
| |
| |
| /** |
| * Deinitializes and deregisters all {@link Authenticator}s from this service. |
| */ |
| public void destroy() |
| { |
| authenticatorsMapByType.clear(); |
| Set<Authenticator> copy = new HashSet<Authenticator>( authenticators ); |
| authenticators = null; |
| for ( Authenticator authenticator : copy ) |
| { |
| authenticator.destroy(); |
| } |
| } |
| |
| |
| /** |
| * Initializes the specified {@link Authenticator} and registers it to |
| * this service. |
| * |
| * @param authenticator Authenticator to initialize and register by type |
| * @param directoryService configuration info to supply to the Authenticator during initialization |
| * @throws javax.naming.Exception if initialization fails. |
| */ |
| private void register( Authenticator authenticator, DirectoryService directoryService ) throws LdapException |
| { |
| authenticator.init( directoryService ); |
| |
| Collection<Authenticator> authenticatorList = getAuthenticators( authenticator.getAuthenticatorType() ); |
| |
| if ( authenticatorList == null ) |
| { |
| authenticatorList = new ArrayList<Authenticator>(); |
| authenticatorsMapByType.put( authenticator.getAuthenticatorType(), authenticatorList ); |
| } |
| |
| authenticatorList.add( authenticator ); |
| } |
| |
| |
| /** |
| * Returns the list of {@link Authenticator}s with the specified type. |
| * |
| * @param type type of Authenticator sought |
| * @return A list of Authenticators of the requested type or <tt>null</tt> if no authenticator is found. |
| */ |
| private Collection<Authenticator> getAuthenticators( AuthenticationLevel type ) |
| { |
| Collection<Authenticator> result = authenticatorsMapByType.get( type ); |
| |
| if ( ( result != null ) && ( result.size() > 0 ) ) |
| { |
| return result; |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| |
| public void add( NextInterceptor next, AddOperationContext addContext ) throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", addContext ); |
| } |
| |
| checkAuthenticated( addContext ); |
| |
| Entry entry = addContext.getEntry(); |
| |
| |
| if ( !directoryService.isPwdPolicyEnabled() ) |
| { |
| next.add( addContext ); |
| return; |
| } |
| |
| PasswordPolicyConfiguration policyConfig = directoryService.getPwdPolicy( entry ); |
| |
| boolean isPPolicyReqCtrlPresent = addContext.hasRequestControl( PasswordPolicy.OID ); |
| |
| checkPwdReset( addContext ); |
| |
| if ( entry.get( SchemaConstants.USER_PASSWORD_AT ) != null ) |
| { |
| String username = null; |
| |
| BinaryValue userPassword = ( BinaryValue ) entry.get( SchemaConstants.USER_PASSWORD_AT ).get(); |
| |
| if ( entry.get( SchemaConstants.CN_AT ) != null ) |
| { |
| StringValue attr = ( StringValue ) entry.get( SchemaConstants.CN_AT ).get(); |
| username = attr.getString(); |
| } |
| |
| try |
| { |
| check( username, userPassword.get(), policyConfig ); |
| } |
| catch ( PasswordPolicyException e ) |
| { |
| if ( isPPolicyReqCtrlPresent ) |
| { |
| PasswordPolicyDecorator responseControl = |
| new PasswordPolicyDecorator( directoryService.getLdapCodecService() ); |
| responseControl.getResponse().setPasswordPolicyError( e.getErrorCode() ); |
| addContext.addResponseControl( responseControl ); |
| } |
| |
| // throw exception if userPassword quality checks fail |
| throw new LdapOperationException( ResultCodeEnum.CONSTRAINT_VIOLATION, e.getMessage() ); |
| } |
| |
| String pwdChangedTime = DateUtils.getGeneralizedTime(); |
| if ( ( policyConfig.getPwdMinAge() > 0 ) || ( policyConfig.getPwdMaxAge() > 0 ) ) |
| { |
| EntryAttribute pwdChangedTimeAt = new DefaultEntryAttribute( AT_PWD_CHANGED_TIME ); |
| pwdChangedTimeAt.add( pwdChangedTime ); |
| entry.add( pwdChangedTimeAt ); |
| } |
| |
| if ( policyConfig.isPwdMustChange() && addContext.getSession().isAnAdministrator() ) |
| { |
| EntryAttribute pwdResetAt = new DefaultEntryAttribute( AT_PWD_RESET ); |
| pwdResetAt.add( "TRUE" ); |
| entry.add( pwdResetAt ); |
| } |
| |
| if ( policyConfig.getPwdInHistory() > 0 ) |
| { |
| EntryAttribute pwdHistoryAt = new DefaultEntryAttribute( AT_PWD_HISTORY ); |
| byte[] pwdHistoryVal = new PasswordHistory( pwdChangedTime, userPassword.get() ).getHistoryValue(); |
| pwdHistoryAt.add( pwdHistoryVal ); |
| entry.add( pwdHistoryAt ); |
| } |
| } |
| |
| next.add( addContext ); |
| } |
| |
| |
| public void delete( NextInterceptor next, DeleteOperationContext deleteContext ) throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", deleteContext ); |
| } |
| |
| checkAuthenticated( deleteContext ); |
| checkPwdReset( deleteContext ); |
| next.delete( deleteContext ); |
| invalidateAuthenticatorCaches( deleteContext.getDn() ); |
| } |
| |
| |
| public Entry getRootDSE( NextInterceptor next, GetRootDSEOperationContext getRootDseContext ) throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", getRootDseContext ); |
| } |
| |
| checkAuthenticated( getRootDseContext ); |
| checkPwdReset( getRootDseContext ); |
| return next.getRootDSE( getRootDseContext ); |
| } |
| |
| |
| public boolean hasEntry( NextInterceptor next, EntryOperationContext hasEntryContext ) throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", hasEntryContext ); |
| } |
| |
| checkAuthenticated( hasEntryContext ); |
| checkPwdReset( hasEntryContext ); |
| return next.hasEntry( hasEntryContext ); |
| } |
| |
| |
| public EntryFilteringCursor list( NextInterceptor next, ListOperationContext listContext ) throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", listContext ); |
| } |
| |
| checkAuthenticated( listContext ); |
| checkPwdReset( listContext ); |
| return next.list( listContext ); |
| } |
| |
| |
| public Entry lookup( NextInterceptor next, LookupOperationContext lookupContext ) throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", lookupContext ); |
| } |
| |
| checkAuthenticated( lookupContext ); |
| checkPwdReset( lookupContext ); |
| |
| return next.lookup( lookupContext ); |
| } |
| |
| |
| private void invalidateAuthenticatorCaches( Dn principalDn ) |
| { |
| for ( AuthenticationLevel authMech : authenticatorsMapByType.keySet() ) |
| { |
| Collection<Authenticator> authenticators = getAuthenticators( authMech ); |
| |
| // try each authenticator |
| for ( Authenticator authenticator : authenticators ) |
| { |
| authenticator.invalidateCache( principalDn ); |
| } |
| } |
| } |
| |
| |
| public void modify( NextInterceptor next, ModifyOperationContext modifyContext ) throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", modifyContext ); |
| } |
| |
| checkAuthenticated( modifyContext ); |
| |
| |
| if ( ! directoryService.isPwdPolicyEnabled() ) |
| { |
| next.modify( modifyContext ); |
| invalidateAuthenticatorCaches( modifyContext.getDn() ); |
| return; |
| } |
| |
| // handle the case where pwdPolicySubentry AT is about to be deleted in thid modify() |
| PasswordPolicyConfiguration policyConfig = directoryService.getPwdPolicy( modifyContext.getOriginalEntry() ); |
| |
| boolean isPPolicyReqCtrlPresent = modifyContext.hasRequestControl( PasswordPolicy.OID ); |
| Dn userDn = modifyContext.getSession().getAuthenticatedPrincipal().getDn(); |
| |
| PwdModDetailsHolder pwdModDetails = null; |
| if ( policyConfig.isPwdSafeModify() || pwdResetSet.contains( userDn ) || ( policyConfig.getPwdMinAge() > 0 ) ) |
| { |
| pwdModDetails = getPwdModDetails( modifyContext, policyConfig ); |
| } |
| |
| if ( ( pwdModDetails != null ) && pwdModDetails.isPwdModPresent() ) |
| { |
| if ( pwdResetSet.contains( userDn ) ) |
| { |
| if ( pwdModDetails.isOtherModExists() ) |
| { |
| if ( isPPolicyReqCtrlPresent ) |
| { |
| PasswordPolicyDecorator responseControl = |
| new PasswordPolicyDecorator( directoryService.getLdapCodecService() ); |
| responseControl.getResponse().setPasswordPolicyError( PasswordPolicyErrorEnum.CHANGE_AFTER_RESET ); |
| modifyContext.addResponseControl( responseControl ); |
| } |
| |
| throw new LdapNoPermissionException(); |
| } |
| } |
| |
| if ( policyConfig.isPwdSafeModify() ) |
| { |
| if ( pwdModDetails.isAddOrReplace() && !pwdModDetails.isDelete() ) |
| { |
| LOG.debug( "trying to update password attribute without the supplying the old password" ); |
| if ( isPPolicyReqCtrlPresent ) |
| { |
| PasswordPolicyDecorator responseControl = |
| new PasswordPolicyDecorator( directoryService.getLdapCodecService() ); |
| responseControl.getResponse().setPasswordPolicyError( PasswordPolicyErrorEnum.MUST_SUPPLY_OLD_PASSWORD ); |
| modifyContext.addResponseControl( responseControl ); |
| } |
| |
| throw new LdapNoPermissionException(); |
| } |
| } |
| |
| if ( !policyConfig.isPwdAllowUserChange() && !modifyContext.getSession().isAnAdministrator() ) |
| { |
| |
| if ( isPPolicyReqCtrlPresent ) |
| { |
| PasswordPolicyDecorator responseControl = |
| new PasswordPolicyDecorator( directoryService.getLdapCodecService() ); |
| responseControl.getResponse().setPasswordPolicyError( PasswordPolicyErrorEnum.PASSWORD_MOD_NOT_ALLOWED ); |
| modifyContext.addResponseControl( responseControl ); |
| } |
| |
| throw new LdapNoPermissionException(); |
| } |
| |
| Entry entry = modifyContext.getEntry(); |
| |
| if ( isPwdTooYoung( entry, policyConfig ) ) |
| { |
| if ( isPPolicyReqCtrlPresent ) |
| { |
| PasswordPolicyDecorator responseControl = |
| new PasswordPolicyDecorator( directoryService.getLdapCodecService() ); |
| responseControl.getResponse().setPasswordPolicyError( PasswordPolicyErrorEnum.PASSWORD_TOO_YOUNG ); |
| modifyContext.addResponseControl( responseControl ); |
| } |
| |
| throw new LdapOperationException( ResultCodeEnum.CONSTRAINT_VIOLATION, |
| "password is too young to update" ); |
| } |
| |
| byte[] newPassword = null; |
| if ( ( pwdModDetails != null ) ) |
| { |
| String userName = null; |
| if ( entry.get( SchemaConstants.CN_AT ) != null ) |
| { |
| StringValue attr = ( StringValue ) entry.get( SchemaConstants.CN_AT ).get(); |
| userName = attr.getString(); |
| } |
| |
| newPassword = pwdModDetails.getNewPwd(); |
| try |
| { |
| check( userName, newPassword, policyConfig ); |
| } |
| catch ( PasswordPolicyException e ) |
| { |
| if ( isPPolicyReqCtrlPresent ) |
| { |
| PasswordPolicyDecorator responseControl = |
| new PasswordPolicyDecorator( directoryService.getLdapCodecService() ); |
| responseControl.getResponse().setPasswordPolicyError( e.getErrorCode() ); |
| modifyContext.addResponseControl( responseControl ); |
| } |
| |
| // throw exception if userPassword quality checks fail |
| throw new LdapOperationException( ResultCodeEnum.CONSTRAINT_VIOLATION, e.getMessage() ); |
| } |
| } |
| |
| int histSize = policyConfig.getPwdInHistory(); |
| Modification pwdRemHistMod = null; |
| Modification pwdAddHistMod = null; |
| String pwdChangedTime = DateUtils.getGeneralizedTime(); |
| |
| if ( histSize > 0 ) |
| { |
| EntryAttribute pwdHistoryAt = entry.get( PWD_HISTORY_AT ); |
| Set<PasswordHistory> pwdHistSet = new TreeSet<PasswordHistory>(); |
| |
| Iterator<Value<?>> itr = pwdHistoryAt.getAll(); |
| while ( itr.hasNext() ) |
| { |
| Value<?> val = itr.next(); |
| PasswordHistory pwdh = new PasswordHistory( Strings.utf8ToString(val.getBytes()) ); |
| |
| boolean matched = Arrays.equals( newPassword, pwdh.getPassword() ); |
| |
| if ( matched ) |
| { |
| if ( isPPolicyReqCtrlPresent ) |
| { |
| PasswordPolicyDecorator responseControl = |
| new PasswordPolicyDecorator( directoryService.getLdapCodecService() ); |
| responseControl.getResponse().setPasswordPolicyError( PasswordPolicyErrorEnum.PASSWORD_IN_HISTORY ); |
| modifyContext.addResponseControl( responseControl ); |
| } |
| |
| throw new LdapOperationException( ResultCodeEnum.CONSTRAINT_VIOLATION, |
| "invalid reuse of password present in password history" ); |
| } |
| |
| pwdHistSet.add( pwdh ); |
| } |
| |
| PasswordHistory newPwdHist = new PasswordHistory( pwdChangedTime, newPassword ); |
| pwdHistSet.add( newPwdHist ); |
| |
| pwdHistoryAt = new DefaultEntryAttribute( pwdHistoryAt.getAttributeType() ); |
| pwdHistoryAt.add( newPwdHist.getHistoryValue() ); |
| pwdAddHistMod = new DefaultModification( ADD_ATTRIBUTE, pwdHistoryAt ); |
| |
| if ( pwdHistSet.size() > histSize ) |
| { |
| pwdHistoryAt = new DefaultEntryAttribute( pwdHistoryAt.getAttributeType() ); |
| PasswordHistory remPwdHist = ( PasswordHistory ) pwdHistSet.toArray()[histSize - 1]; |
| pwdHistoryAt.add( remPwdHist.getHistoryValue() ); |
| pwdRemHistMod = new DefaultModification( REMOVE_ATTRIBUTE, pwdHistoryAt ); |
| } |
| } |
| |
| next.modify( modifyContext ); |
| invalidateAuthenticatorCaches( modifyContext.getDn() ); |
| |
| List<Modification> mods = new ArrayList<Modification>(); |
| if ( ( policyConfig.getPwdMinAge() > 0 ) || ( policyConfig.getPwdMaxAge() > 0 ) ) |
| { |
| EntryAttribute pwdChangedTimeAt = new DefaultEntryAttribute( AT_PWD_CHANGED_TIME ); |
| pwdChangedTimeAt.add( pwdChangedTime ); |
| Modification pwdChangedTimeMod = new DefaultModification( REPLACE_ATTRIBUTE, pwdChangedTimeAt ); |
| mods.add( pwdChangedTimeMod ); |
| } |
| |
| if ( pwdAddHistMod != null ) |
| { |
| mods.add( pwdAddHistMod ); |
| } |
| |
| if ( pwdRemHistMod != null ) |
| { |
| mods.add( pwdRemHistMod ); |
| } |
| |
| boolean removeFromPwdResetSet = false; |
| if ( policyConfig.isPwdMustChange() ) |
| { |
| EntryAttribute pwdMustChangeAt = new DefaultEntryAttribute( AT_PWD_RESET ); |
| Modification pwdMustChangeMod = null; |
| |
| if ( modifyContext.getSession().isAnAdministrator() ) |
| { |
| pwdMustChangeAt.add( "TRUE" ); |
| pwdMustChangeMod = new DefaultModification( REPLACE_ATTRIBUTE, pwdMustChangeAt ); |
| } |
| else |
| { |
| pwdMustChangeMod = new DefaultModification( REMOVE_ATTRIBUTE, pwdMustChangeAt ); |
| removeFromPwdResetSet = true; |
| } |
| |
| mods.add( pwdMustChangeMod ); |
| } |
| |
| EntryAttribute pwdFailureTimeAt = entry.get( PWD_FAILURE_TIME_AT ); |
| if ( pwdFailureTimeAt != null ) |
| { |
| mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdFailureTimeAt ) ); |
| } |
| |
| EntryAttribute pwdGraceUseTimeAt = entry.get( PWD_GRACE_USE_TIME_AT ); |
| if ( pwdGraceUseTimeAt != null ) |
| { |
| mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdGraceUseTimeAt ) ); |
| } |
| |
| directoryService.getAdminSession().modify( modifyContext.getDn(), mods ); |
| |
| if ( removeFromPwdResetSet ) |
| { |
| pwdResetSet.remove( userDn ); |
| } |
| } |
| else |
| { |
| next.modify( modifyContext ); |
| invalidateAuthenticatorCaches( modifyContext.getDn() ); |
| } |
| } |
| |
| |
| public void rename( NextInterceptor next, RenameOperationContext renameContext ) throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", renameContext ); |
| } |
| |
| checkAuthenticated( renameContext ); |
| checkPwdReset( renameContext ); |
| next.rename( renameContext ); |
| invalidateAuthenticatorCaches( renameContext.getDn() ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean compare( NextInterceptor next, CompareOperationContext compareContext ) throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", compareContext ); |
| } |
| |
| checkAuthenticated( compareContext ); |
| checkPwdReset( compareContext ); |
| boolean result = next.compare( compareContext ); |
| invalidateAuthenticatorCaches( compareContext.getDn() ); |
| |
| return result; |
| } |
| |
| |
| public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext moveAndRenameContext ) |
| throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", moveAndRenameContext ); |
| } |
| |
| checkAuthenticated( moveAndRenameContext ); |
| checkPwdReset( moveAndRenameContext ); |
| next.moveAndRename( moveAndRenameContext ); |
| invalidateAuthenticatorCaches( moveAndRenameContext.getDn() ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void move( NextInterceptor next, MoveOperationContext moveContext ) throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", moveContext ); |
| } |
| |
| checkAuthenticated( moveContext ); |
| checkPwdReset( moveContext ); |
| next.move( moveContext ); |
| invalidateAuthenticatorCaches( moveContext.getDn() ); |
| } |
| |
| |
| public EntryFilteringCursor search( NextInterceptor next, SearchOperationContext searchContext ) |
| throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", searchContext ); |
| } |
| |
| checkAuthenticated( searchContext ); |
| checkPwdReset( searchContext ); |
| return next.search( searchContext ); |
| } |
| |
| |
| /** |
| * Check if the current operation has a valid PrincipalDN or not. |
| * |
| * @param operation the operation type |
| * @throws Exception |
| */ |
| private void checkAuthenticated( OperationContext operation ) throws LdapException |
| { |
| if ( operation.getSession().isAnonymous() && !directoryService.isAllowAnonymousAccess() |
| && !operation.getDn().isEmpty() ) |
| { |
| String msg = I18n.err( I18n.ERR_5, operation.getName() ); |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| } |
| |
| |
| public void bind( NextInterceptor next, BindOperationContext bindContext ) throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", bindContext ); |
| } |
| |
| if ( ( bindContext.getSession() != null ) && ( bindContext.getSession().getEffectivePrincipal() != null ) ) |
| { |
| // null out the credentials |
| bindContext.setCredentials( null ); |
| } |
| |
| // pick the first matching authenticator type |
| AuthenticationLevel level = bindContext.getAuthenticationLevel(); |
| |
| if ( level == AuthenticationLevel.UNAUTHENT ) |
| { |
| // This is a case where the Bind request contains a Dn, but no password. |
| // We don't check the Dn, we just return a UnwillingToPerform error |
| // Cf RFC 4513, chap. 5.1.2 |
| throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, "Cannot Bind for Dn " |
| + bindContext.getDn().getName() ); |
| } |
| |
| Collection<Authenticator> authenticators = getAuthenticators( level ); |
| |
| if ( authenticators == null ) |
| { |
| LOG.debug( "No authenticators found, delegating bind to the nexus." ); |
| |
| // as a last resort try binding via the nexus |
| next.bind( bindContext ); |
| |
| LOG.debug( "Nexus succeeded on bind operation." ); |
| |
| // bind succeeded if we got this far |
| // TODO - authentication level not being set |
| LdapPrincipal principal = new LdapPrincipal( schemaManager, bindContext.getDn(), AuthenticationLevel.SIMPLE ); |
| CoreSession session = new DefaultCoreSession( principal, directoryService ); |
| bindContext.setSession( session ); |
| |
| // remove creds so there is no security risk |
| bindContext.setCredentials( null ); |
| return; |
| } |
| |
| boolean isPPolicyReqCtrlPresent = bindContext.hasRequestControl( PasswordPolicy.OID ); |
| PasswordPolicyDecorator pwdRespCtrl = |
| new PasswordPolicyDecorator( directoryService.getLdapCodecService() ); |
| |
| boolean authenticated = false; |
| PasswordPolicyException ppe = null; |
| |
| // TODO : we should refactor that. |
| // try each authenticator |
| for ( Authenticator authenticator : authenticators ) |
| { |
| try |
| { |
| // perform the authentication |
| LdapPrincipal principal = authenticator.authenticate( bindContext ); |
| |
| LdapPrincipal clonedPrincipal = ( LdapPrincipal ) ( principal.clone() ); |
| |
| // remove creds so there is no security risk |
| bindContext.setCredentials( null ); |
| clonedPrincipal.setUserPassword( StringConstants.EMPTY_BYTES ); |
| |
| // authentication was successful |
| CoreSession session = new DefaultCoreSession( clonedPrincipal, directoryService ); |
| bindContext.setSession( session ); |
| |
| authenticated = true; |
| } |
| catch ( PasswordPolicyException e ) |
| { |
| ppe = e; |
| break; |
| } |
| catch ( LdapAuthenticationException e ) |
| { |
| // authentication failed, try the next authenticator |
| if ( LOG.isInfoEnabled() ) |
| { |
| LOG.info( "Authenticator {} failed to authenticate: {}", authenticator, bindContext ); |
| } |
| } |
| catch ( Exception e ) |
| { |
| // Log other exceptions than LdapAuthenticationException |
| if ( LOG.isWarnEnabled() ) |
| { |
| LOG.info( "Unexpected failure for Authenticator {} : {}", authenticator, bindContext ); |
| } |
| } |
| } |
| |
| if ( ppe != null ) |
| { |
| if ( isPPolicyReqCtrlPresent ) |
| { |
| pwdRespCtrl.getResponse().setPasswordPolicyError( ppe.getErrorCode() ); |
| bindContext.addResponseControl( pwdRespCtrl ); |
| } |
| |
| throw ppe; |
| } |
| |
| Dn dn = bindContext.getDn(); |
| Entry userEntry = bindContext.getEntry(); |
| |
| PasswordPolicyConfiguration policyConfig = directoryService.getPwdPolicy( userEntry ); |
| |
| // check if the user entry is null, it will be null |
| // in cases of anonymous bind |
| if ( authenticated && ( userEntry == null ) && directoryService.isAllowAnonymousAccess() ) |
| { |
| return; |
| } |
| |
| if ( !authenticated ) |
| { |
| if ( LOG.isInfoEnabled() ) |
| { |
| LOG.info( "Cannot bind to the server " ); |
| } |
| |
| if ( ( policyConfig != null ) && ( userEntry != null ) ) |
| { |
| EntryAttribute pwdFailTimeAt = userEntry.get( PWD_FAILURE_TIME_AT ); |
| if ( pwdFailTimeAt == null ) |
| { |
| pwdFailTimeAt = new DefaultEntryAttribute( AT_PWD_FAILURE_TIME ); |
| } |
| else |
| { |
| PasswordUtil.purgeFailureTimes( policyConfig, pwdFailTimeAt ); |
| } |
| |
| String failureTime = DateUtils.getGeneralizedTime(); |
| pwdFailTimeAt.add( failureTime ); |
| Modification pwdFailTimeMod = new DefaultModification( ADD_ATTRIBUTE, pwdFailTimeAt ); |
| |
| List<Modification> mods = new ArrayList<Modification>(); |
| mods.add( pwdFailTimeMod ); |
| |
| int numFailures = pwdFailTimeAt.size(); |
| |
| if ( policyConfig.isPwdLockout() && ( numFailures >= policyConfig.getPwdMaxFailure() ) ) |
| { |
| EntryAttribute pwdAccountLockedTimeAt = new DefaultEntryAttribute( AT_PWD_ACCOUNT_LOCKED_TIME ); |
| pwdAccountLockedTimeAt.add( failureTime ); |
| Modification pwdAccountLockedMod = new DefaultModification( ADD_ATTRIBUTE, pwdAccountLockedTimeAt ); |
| mods.add( pwdAccountLockedMod ); |
| |
| pwdRespCtrl.getResponse().setPasswordPolicyError( PasswordPolicyErrorEnum.ACCOUNT_LOCKED ); |
| } |
| else if ( policyConfig.getPwdMinDelay() > 0 ) |
| { |
| int numDelay = numFailures * policyConfig.getPwdMinDelay(); |
| int maxDelay = policyConfig.getPwdMaxDelay(); |
| if ( numDelay > maxDelay ) |
| { |
| numDelay = maxDelay; |
| } |
| |
| try |
| { |
| Thread.sleep( numDelay * 1000 ); |
| } |
| catch ( InterruptedException e ) |
| { |
| LOG.warn( |
| "Interrupted while delaying to send the failed authentication response for the user {}", |
| dn, e ); |
| } |
| } |
| |
| //adminSession.modify( dn, Collections.singletonList( pwdFailTimeMod ) ); |
| ModifyOperationContext bindModCtx = new ModifyOperationContext( adminSession ); |
| bindModCtx.setByPassed( BYPASS_INTERCEPTORS ); |
| bindModCtx.setDn( dn ); |
| bindModCtx.setModItems( Collections.singletonList( pwdFailTimeMod ) ); |
| directoryService.getOperationManager().modify( bindModCtx ); |
| } |
| |
| String upDn = ( dn == null ? "" : dn.getName() ); |
| throw new LdapAuthenticationException( I18n.err( I18n.ERR_229, upDn ) ); |
| } |
| else if ( policyConfig != null ) |
| { |
| List<Modification> mods = new ArrayList<Modification>(); |
| |
| if ( policyConfig.getPwdMaxIdle() > 0 ) |
| { |
| EntryAttribute pwdLastSuccesTimeAt = new DefaultEntryAttribute( AT_PWD_LAST_SUCCESS ); |
| pwdLastSuccesTimeAt.add( DateUtils.getGeneralizedTime() ); |
| Modification pwdLastSuccesTimeMod = new DefaultModification( REPLACE_ATTRIBUTE, pwdLastSuccesTimeAt ); |
| mods.add( pwdLastSuccesTimeMod ); |
| } |
| |
| EntryAttribute pwdFailTimeAt = userEntry.get( AT_PWD_FAILURE_TIME ); |
| if ( pwdFailTimeAt != null ) |
| { |
| Modification pwdFailTimeMod = new DefaultModification( REMOVE_ATTRIBUTE, pwdFailTimeAt ); |
| mods.add( pwdFailTimeMod ); |
| } |
| |
| EntryAttribute pwdAccLockedTimeAt = userEntry.get( AT_PWD_ACCOUNT_LOCKED_TIME ); |
| if ( pwdAccLockedTimeAt != null ) |
| { |
| Modification pwdAccLockedTimeMod = new DefaultModification( REMOVE_ATTRIBUTE, pwdAccLockedTimeAt ); |
| mods.add( pwdAccLockedTimeMod ); |
| } |
| |
| // checking the expiration time *after* performing authentication, do we need to care about millisecond precision? |
| if ( ( policyConfig.getPwdMaxAge() > 0 ) && ( policyConfig.getPwdGraceAuthNLimit() > 0 ) ) |
| { |
| EntryAttribute pwdChangeTimeAttr = userEntry.get( PWD_CHANGED_TIME_AT ); |
| if ( pwdChangeTimeAttr != null ) |
| { |
| boolean expired = PasswordUtil.isPwdExpired( pwdChangeTimeAttr.getString(), |
| policyConfig.getPwdMaxAge() ); |
| if ( expired ) |
| { |
| EntryAttribute pwdGraceUseAttr = userEntry.get( PWD_GRACE_USE_TIME_AT ); |
| if ( pwdGraceUseAttr != null ) |
| { |
| pwdRespCtrl.getResponse().setGraceAuthNsRemaining( policyConfig.getPwdGraceAuthNLimit() |
| - ( pwdGraceUseAttr.size() + 1 ) ); |
| } |
| else |
| { |
| pwdGraceUseAttr = new DefaultEntryAttribute( AT_PWD_GRACE_USE_TIME ); |
| } |
| |
| pwdGraceUseAttr.add( DateUtils.getGeneralizedTime() ); |
| Modification pwdGraceUseMod = new DefaultModification( ADD_ATTRIBUTE, pwdGraceUseAttr ); |
| mods.add( pwdGraceUseMod ); |
| } |
| } |
| } |
| |
| if ( !mods.isEmpty() ) |
| { |
| //adminSession.modify( dn, mods ); |
| ModifyOperationContext bindModCtx = new ModifyOperationContext( adminSession ); |
| bindModCtx.setByPassed( BYPASS_INTERCEPTORS ); |
| bindModCtx.setDn( dn ); |
| bindModCtx.setModItems( mods ); |
| directoryService.getOperationManager().modify( bindModCtx ); |
| } |
| |
| if ( isPPolicyReqCtrlPresent ) |
| { |
| int expiryWarnTime = getPwdTimeBeforeExpiry( userEntry, policyConfig ); |
| if ( expiryWarnTime > 0 ) |
| { |
| pwdRespCtrl.getResponse().setTimeBeforeExpiration( expiryWarnTime ); |
| } |
| |
| if ( isPwdMustReset( userEntry ) ) |
| { |
| pwdRespCtrl.getResponse().setPasswordPolicyError( PasswordPolicyErrorEnum.CHANGE_AFTER_RESET ); |
| pwdResetSet.add( dn ); |
| } |
| |
| bindContext.addResponseControl( pwdRespCtrl ); |
| } |
| } |
| } |
| |
| |
| @Override |
| public void unbind( NextInterceptor next, UnbindOperationContext unbindContext ) throws LdapException |
| { |
| super.unbind( next, unbindContext ); |
| |
| // remove the Dn from the password reset Set |
| // we do not perform a check to see if the reset flag in the associated ppolicy is enabled |
| // cause that requires fetching the ppolicy first, which requires a lookup for user entry |
| if ( !directoryService.isPwdPolicyEnabled() ) |
| { |
| pwdResetSet.remove( unbindContext.getDn() ); |
| } |
| } |
| |
| |
| /** |
| * Initialize the PasswordPolicy attributeTypes |
| * |
| * @throws LdapException If the initialization failed |
| */ |
| public void loadPwdPolicyStateAtributeTypes() throws LdapException |
| { |
| if ( directoryService.isPwdPolicyEnabled() ) |
| { |
| AT_PWD_RESET = schemaManager.lookupAttributeTypeRegistry( PWD_RESET_AT ); |
| PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( AT_PWD_RESET ); |
| |
| AT_PWD_CHANGED_TIME = schemaManager.lookupAttributeTypeRegistry( PWD_CHANGED_TIME_AT ); |
| PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( AT_PWD_CHANGED_TIME ); |
| |
| AT_PWD_HISTORY = schemaManager.lookupAttributeTypeRegistry( PWD_HISTORY_AT ); |
| PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( AT_PWD_HISTORY ); |
| |
| AT_PWD_FAILURE_TIME = schemaManager.lookupAttributeTypeRegistry( PWD_FAILURE_TIME_AT ); |
| PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( AT_PWD_FAILURE_TIME ); |
| |
| AT_PWD_ACCOUNT_LOCKED_TIME = schemaManager.lookupAttributeTypeRegistry( PWD_ACCOUNT_LOCKED_TIME_AT ); |
| PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( AT_PWD_ACCOUNT_LOCKED_TIME ); |
| |
| AT_PWD_LAST_SUCCESS = schemaManager.lookupAttributeTypeRegistry( PWD_LAST_SUCCESS_AT ); |
| PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( AT_PWD_LAST_SUCCESS ); |
| |
| AT_PWD_GRACE_USE_TIME = schemaManager.lookupAttributeTypeRegistry( PWD_GRACE_USE_TIME_AT ); |
| PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( AT_PWD_GRACE_USE_TIME ); |
| } |
| } |
| |
| |
| // ---------- private methods ---------------- |
| |
| private void check( String username, byte[] password, PasswordPolicyConfiguration policyConfig ) throws LdapException |
| { |
| final int qualityVal = policyConfig.getPwdCheckQuality(); |
| |
| if ( qualityVal == 0 ) |
| { |
| return; |
| } |
| |
| LdapSecurityConstants secConst = PasswordUtil.findAlgorithm( password ); |
| |
| // do not perform quality check if the password is not plain text and |
| // pwdCheckQuality value is set to 1 |
| if ( secConst != null ) |
| { |
| if ( qualityVal == 1 ) |
| { |
| return; |
| } |
| else |
| { |
| throw new PasswordPolicyException( "cannot verify the quality of the non-cleartext passwords", |
| INSUFFICIENT_PASSWORD_QUALITY ); |
| } |
| } |
| |
| String strPassword = Strings.utf8ToString(password); |
| validatePasswordLength( strPassword, policyConfig ); |
| checkUsernameSubstring( username, strPassword, policyConfig ); |
| // checkPasswordChars( strPassword ); |
| } |
| |
| |
| /** |
| * validates the length of the password |
| */ |
| private void validatePasswordLength( String password, PasswordPolicyConfiguration policyConfig ) throws PasswordPolicyException |
| { |
| int maxLen = policyConfig.getPwdMaxLength(); |
| int minLen = policyConfig.getPwdMinLength(); |
| |
| int pwdLen = password.length(); |
| |
| if ( maxLen > 0 ) |
| { |
| if ( pwdLen > maxLen ) |
| { |
| throw new PasswordPolicyException( "Password should not have more than " + maxLen + " characters", |
| INSUFFICIENT_PASSWORD_QUALITY ); |
| } |
| } |
| |
| if ( minLen > 0 ) |
| { |
| if ( pwdLen < minLen ) |
| { |
| throw new PasswordPolicyException( "Password should have a minmum of " + minLen + " characters", |
| PASSWORD_TOO_SHORT ); |
| } |
| } |
| } |
| |
| |
| /** |
| * The password does not contain three letter (or more) tokens from the user's account name. |
| * |
| * If the account name is less than three characters long, this check is not performed |
| * because the rate at which passwords would be rejected is too high. For each token that is |
| * three or more characters long, that token is searched for in the password; if it is present, |
| * the password change is rejected. For example, the name "First M. Last" would be split into |
| * three tokens: "First", "M", and "Last". Because the second token is only one character long, |
| * it would be ignored. Therefore, this user could not have a password that included either |
| * "first" or "last" as a substring anywhere in the password. All of these checks are |
| * case-insensitive. |
| */ |
| private void checkUsernameSubstring( String username, String password, PasswordPolicyConfiguration policyConfig ) throws PasswordPolicyException |
| { |
| if ( username == null || username.trim().length() == 0 ) |
| { |
| return; |
| } |
| |
| String[] tokens = username.split( "[^a-zA-Z]" ); |
| |
| for ( int ii = 0; ii < tokens.length; ii++ ) |
| { |
| if ( password.matches( "(?i).*" + tokens[ii] + ".*" ) ) |
| { |
| throw new PasswordPolicyException( "Password shouldn't contain parts of the username", |
| INSUFFICIENT_PASSWORD_QUALITY ); |
| } |
| } |
| } |
| |
| |
| private int getPwdTimeBeforeExpiry( Entry userEntry, PasswordPolicyConfiguration policyConfig ) throws LdapException |
| { |
| if ( policyConfig.getPwdMaxAge() == 0 ) |
| { |
| return 0; |
| } |
| |
| EntryAttribute pwdExpireWarningAt = userEntry.get( PWD_EXPIRE_WARNING_AT ); |
| if ( pwdExpireWarningAt == null ) |
| { |
| return 0; |
| } |
| |
| EntryAttribute pwdChangedTimeAt = userEntry.get( PWD_CHANGED_TIME_AT ); |
| long changedTime = DateUtils.getDate(pwdChangedTimeAt.getString()).getTime(); |
| |
| int pwdAge = ( int ) ( System.currentTimeMillis() - changedTime ) / 1000; |
| |
| if ( pwdAge > policyConfig.getPwdMaxAge() ) |
| { |
| return 0; |
| } |
| |
| int warningAge = ( int ) ( DateUtils.getDate( pwdExpireWarningAt.getString() ).getTime() ) / 1000; |
| |
| if ( pwdAge >= warningAge ) |
| { |
| return policyConfig.getPwdMaxAge() - pwdAge; |
| } |
| |
| return 0; |
| } |
| |
| |
| /** |
| * checks if the password is too young |
| * |
| * @param userEntry the user's entry |
| * @return true if the password is young, false otherwise |
| * @throws LdapException |
| */ |
| private boolean isPwdTooYoung( Entry userEntry, PasswordPolicyConfiguration policyConfig ) throws LdapException |
| { |
| if ( policyConfig.getPwdMinAge() == 0 ) |
| { |
| return false; |
| } |
| |
| EntryAttribute pwdChangedTimeAt = userEntry.get( PWD_CHANGED_TIME_AT ); |
| long changedTime = DateUtils.getDate( pwdChangedTimeAt.getString() ).getTime(); |
| changedTime += policyConfig.getPwdMinAge() * 1000; |
| |
| if ( changedTime > System.currentTimeMillis() ) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| |
| /** |
| * checks if the password must be changed after the initial bind |
| * |
| * @param userEntry the user's entry |
| * @return true if must be changed, false otherwise |
| * @throws LdapException |
| */ |
| private boolean isPwdMustReset( Entry userEntry ) throws LdapException |
| { |
| boolean mustChange = false; |
| |
| EntryAttribute pwdResetAt = userEntry.get( PWD_RESET_AT ); |
| if ( pwdResetAt != null ) |
| { |
| mustChange = Boolean.parseBoolean( pwdResetAt.getString() ); |
| } |
| |
| return mustChange; |
| } |
| |
| |
| private PwdModDetailsHolder getPwdModDetails( ModifyOperationContext modifyContext, PasswordPolicyConfiguration policyConfig ) throws LdapException |
| { |
| PwdModDetailsHolder pwdModDetails = new PwdModDetailsHolder(); |
| |
| List<Modification> mods = modifyContext.getModItems(); |
| for ( Modification m : mods ) |
| { |
| EntryAttribute at = m.getAttribute(); |
| |
| if ( at.getUpId().equalsIgnoreCase( policyConfig.getPwdAttribute() ) ) |
| { |
| pwdModDetails.setPwdModPresent( true ); |
| ModificationOperation op = m.getOperation(); |
| |
| if ( op == REMOVE_ATTRIBUTE ) |
| { |
| pwdModDetails.setDelete( true ); |
| } |
| else if ( op == REPLACE_ATTRIBUTE || op == ADD_ATTRIBUTE ) |
| { |
| pwdModDetails.setAddOrReplace( true ); |
| pwdModDetails.setNewPwd( at.getBytes() ); |
| } |
| } |
| else |
| { |
| pwdModDetails.setOtherModExists( true ); |
| } |
| } |
| |
| return pwdModDetails; |
| } |
| |
| |
| /** |
| * checks to see if the user's password should be changed before performing any operations |
| * other than bind, password update, unbind, abandon or StartTLS |
| * |
| * @param opContext the operation's context |
| * @throws LdapException |
| */ |
| private void checkPwdReset( OperationContext opContext ) throws LdapException |
| { |
| if ( ! directoryService.isPwdPolicyEnabled() ) |
| { |
| CoreSession session = opContext.getSession(); |
| |
| Dn userDn = session.getAuthenticatedPrincipal().getDn(); |
| |
| if ( pwdResetSet.contains( userDn ) ) |
| { |
| boolean isPPolicyReqCtrlPresent = opContext |
| .hasRequestControl( PasswordPolicy.OID ); |
| if ( isPPolicyReqCtrlPresent ) |
| { |
| PasswordPolicyDecorator pwdRespCtrl = |
| new PasswordPolicyDecorator( directoryService.getLdapCodecService() ); |
| pwdRespCtrl.getResponse().setPasswordPolicyError( PasswordPolicyErrorEnum.CHANGE_AFTER_RESET ); |
| opContext.addResponseControl( pwdRespCtrl ); |
| } |
| |
| throw new LdapNoPermissionException( "password needs to be reset before performing this operation" ); |
| } |
| } |
| } |
| |
| |
| private class PwdModDetailsHolder |
| { |
| private boolean pwdModPresent = false; |
| |
| private boolean isDelete = false; |
| |
| private boolean isAddOrReplace = false; |
| |
| private boolean otherModExists = false; |
| |
| private byte[] newPwd; |
| |
| |
| public boolean isPwdModPresent() |
| { |
| return pwdModPresent; |
| } |
| |
| |
| public void setPwdModPresent( boolean pwdModPresent ) |
| { |
| this.pwdModPresent = pwdModPresent; |
| } |
| |
| |
| public boolean isDelete() |
| { |
| return isDelete; |
| } |
| |
| |
| public void setDelete( boolean isDelete ) |
| { |
| this.isDelete = isDelete; |
| } |
| |
| |
| public boolean isAddOrReplace() |
| { |
| return isAddOrReplace; |
| } |
| |
| |
| public void setAddOrReplace( boolean isAddOrReplace ) |
| { |
| this.isAddOrReplace = isAddOrReplace; |
| } |
| |
| |
| public boolean isOtherModExists() |
| { |
| return otherModExists; |
| } |
| |
| |
| public void setOtherModExists( boolean otherModExists ) |
| { |
| this.otherModExists = otherModExists; |
| } |
| |
| |
| public byte[] getNewPwd() |
| { |
| return newPwd; |
| } |
| |
| |
| public void setNewPwd( byte[] newPwd ) |
| { |
| this.newPwd = newPwd; |
| } |
| } |
| } |