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.INSUFFICIENT_PASSWORD_QUALITY; 024import static org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyErrorEnum.PASSWORD_TOO_SHORT; 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_FAILURE_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_HISTORY_AT; 030import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_LAST_SUCCESS_AT; 031import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_POLICY_SUBENTRY_AT; 032import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_RESET_AT; 033import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_START_TIME_AT; 034import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_END_TIME_AT; 035import static org.apache.directory.api.ldap.model.entry.ModificationOperation.ADD_ATTRIBUTE; 036import static org.apache.directory.api.ldap.model.entry.ModificationOperation.REMOVE_ATTRIBUTE; 037import static org.apache.directory.api.ldap.model.entry.ModificationOperation.REPLACE_ATTRIBUTE; 038 039import java.io.IOException; 040import java.security.MessageDigest; 041import java.util.ArrayList; 042import java.util.Collection; 043import java.util.Collections; 044import java.util.EnumMap; 045import java.util.HashSet; 046import java.util.Iterator; 047import java.util.List; 048import java.util.Set; 049 050import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyRequest; 051import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyResponse; 052import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyResponseImpl; 053import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyErrorEnum; 054import org.apache.directory.api.ldap.model.constants.AuthenticationLevel; 055import org.apache.directory.api.ldap.model.constants.LdapSecurityConstants; 056import org.apache.directory.api.ldap.model.constants.SchemaConstants; 057import org.apache.directory.api.ldap.model.entry.Attribute; 058import org.apache.directory.api.ldap.model.entry.DefaultAttribute; 059import org.apache.directory.api.ldap.model.entry.DefaultModification; 060import org.apache.directory.api.ldap.model.entry.Entry; 061import org.apache.directory.api.ldap.model.entry.Modification; 062import org.apache.directory.api.ldap.model.entry.ModificationOperation; 063import org.apache.directory.api.ldap.model.entry.Value; 064import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException; 065import org.apache.directory.api.ldap.model.exception.LdapException; 066import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException; 067import org.apache.directory.api.ldap.model.exception.LdapOperationException; 068import org.apache.directory.api.ldap.model.exception.LdapOtherException; 069import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException; 070import org.apache.directory.api.ldap.model.message.ResultCodeEnum; 071import org.apache.directory.api.ldap.model.name.Dn; 072import org.apache.directory.api.ldap.model.password.PasswordUtil; 073import org.apache.directory.api.ldap.model.schema.AttributeType; 074import org.apache.directory.api.util.DateUtils; 075import org.apache.directory.api.util.Strings; 076import org.apache.directory.server.constants.ServerDNConstants; 077import org.apache.directory.server.core.api.CoreSession; 078import org.apache.directory.server.core.api.DirectoryService; 079import org.apache.directory.server.core.api.InterceptorEnum; 080import org.apache.directory.server.core.api.LdapPrincipal; 081import org.apache.directory.server.core.api.authn.ppolicy.CheckQualityEnum; 082import org.apache.directory.server.core.api.authn.ppolicy.DefaultPasswordValidator; 083import org.apache.directory.server.core.api.authn.ppolicy.PasswordPolicyConfiguration; 084import org.apache.directory.server.core.api.authn.ppolicy.PasswordPolicyException; 085import org.apache.directory.server.core.api.authn.ppolicy.PasswordValidator; 086import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; 087import org.apache.directory.server.core.api.interceptor.BaseInterceptor; 088import org.apache.directory.server.core.api.interceptor.Interceptor; 089import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 090import org.apache.directory.server.core.api.interceptor.context.BindOperationContext; 091import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext; 092import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 093import org.apache.directory.server.core.api.interceptor.context.GetRootDseOperationContext; 094import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext; 095import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 096import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 097import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 098import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 099import org.apache.directory.server.core.api.interceptor.context.OperationContext; 100import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 101import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; 102import org.apache.directory.server.core.api.interceptor.context.UnbindOperationContext; 103import org.apache.directory.server.core.api.partition.Partition; 104import org.apache.directory.server.core.api.partition.PartitionTxn; 105import org.apache.directory.server.core.authn.ppolicy.PpolicyConfigContainer; 106import org.apache.directory.server.core.shared.DefaultCoreSession; 107import org.apache.directory.server.i18n.I18n; 108import org.slf4j.Logger; 109import 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 */ 117public 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 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 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 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 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 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 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 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 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 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}