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_END_TIME_AT; 028import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_FAILURE_TIME_AT; 029import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_GRACE_USE_TIME_AT; 030import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_HISTORY_AT; 031import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_LAST_SUCCESS_AT; 032import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_POLICY_SUBENTRY_AT; 033import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_RESET_AT; 034import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_START_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.PasswordPolicyErrorEnum; 051import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyRequest; 052import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyResponse; 053import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyResponseImpl; 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 // Check that we can execute this operation 860 checkAuthenticated( deleteContext ); 861 862 // propagate the call to the next interceptor 863 next( deleteContext ); 864 865 // if the deleted entry contains a password, then invalidate the associated caches 866 invalidateAuthenticatorCaches( deleteContext.getDn() ); 867 } 868 869 870 /** 871 * {@inheritDoc} 872 */ 873 @Override 874 public Entry getRootDse( GetRootDseOperationContext getRootDseContext ) throws LdapException 875 { 876 if ( IS_DEBUG ) 877 { 878 LOG.debug( "Operation Context: {}", getRootDseContext ); 879 } 880 881 checkAuthenticated( getRootDseContext ); 882 checkPwdReset( getRootDseContext ); 883 884 return next( getRootDseContext ); 885 } 886 887 888 /** 889 * {@inheritDoc} 890 */ 891 @Override 892 public boolean hasEntry( HasEntryOperationContext hasEntryContext ) throws LdapException 893 { 894 if ( IS_DEBUG ) 895 { 896 LOG.debug( "Operation Context: {}", hasEntryContext ); 897 } 898 899 checkAuthenticated( hasEntryContext ); 900 checkPwdReset( hasEntryContext ); 901 902 return next( hasEntryContext ); 903 } 904 905 906 /** 907 * {@inheritDoc} 908 */ 909 @Override 910 public Entry lookup( LookupOperationContext lookupContext ) throws LdapException 911 { 912 if ( IS_DEBUG ) 913 { 914 LOG.debug( "Operation Context: {}", lookupContext ); 915 } 916 917 checkAuthenticated( lookupContext ); 918 checkPwdReset( lookupContext ); 919 920 return next( lookupContext ); 921 } 922 923 924 private void invalidateAuthenticatorCaches( Dn principalDn ) 925 { 926 for ( AuthenticationLevel authMech : authenticatorsMapByType.keySet() ) 927 { 928 // try each authenticator 929 for ( Authenticator authenticator : getAuthenticators( authMech ) ) 930 { 931 authenticator.invalidateCache( principalDn ); 932 } 933 } 934 } 935 936 937 /** 938 * {@inheritDoc} 939 */ 940 @Override 941 public void modify( ModifyOperationContext modifyContext ) throws LdapException 942 { 943 if ( IS_DEBUG ) 944 { 945 LOG.debug( "Operation Context: {}", modifyContext ); 946 } 947 948 checkAuthenticated( modifyContext ); 949 950 if ( !directoryService.isPwdPolicyEnabled() || modifyContext.isReplEvent() ) 951 { 952 processStandardModify( modifyContext ); 953 } 954 else 955 { 956 processPasswordPolicydModify( modifyContext ); 957 } 958 } 959 960 961 /** 962 * Proceed with the Modification operation when the PasswordPolicy is not activated. 963 */ 964 private void processStandardModify( ModifyOperationContext modifyContext ) throws LdapException 965 { 966 next( modifyContext ); 967 968 List<Modification> modifications = modifyContext.getModItems(); 969 970 for ( Modification modification : modifications ) 971 { 972 if ( directoryService.getAtProvider().getUserPassword() 973 .equals( modification.getAttribute().getAttributeType() ) ) 974 { 975 invalidateAuthenticatorCaches( modifyContext.getDn() ); 976 break; 977 } 978 } 979 } 980 981 982 /** 983 * Proceed with the Modification operation when the PasswordPolicy is activated. 984 */ 985 private void processPasswordPolicydModify( ModifyOperationContext modifyContext ) throws LdapException 986 { 987 // handle the case where pwdPolicySubentry AT is about to be deleted in this modify() 988 PasswordPolicyConfiguration policyConfig = getPwdPolicy( modifyContext.getEntry() ); 989 990 PwdModDetailsHolder pwdModDetails = getPwdModDetails( modifyContext, policyConfig ); 991 992 if ( !pwdModDetails.isPwdModPresent() ) 993 { 994 // We can going on, the password attribute is not present in the Modifications. 995 next( modifyContext ); 996 } 997 else 998 { 999 // The password is present in the modifications. Deal with the various use cases. 1000 CoreSession userSession = modifyContext.getSession(); 1001 boolean isPPolicyReqCtrlPresent = modifyContext.hasRequestControl( PasswordPolicyRequest.OID ); 1002 1003 // First, check if the password must be changed, and if the operation allows it 1004 checkPwdMustChange( modifyContext, userSession, pwdModDetails, isPPolicyReqCtrlPresent ); 1005 1006 // Check the the old password is present if it's required by the PP config 1007 checkOldPwdRequired( modifyContext, policyConfig, pwdModDetails, isPPolicyReqCtrlPresent ); 1008 1009 // Check that we can't update the password if it's not allowed 1010 checkChangePwdAllowed( modifyContext, policyConfig, isPPolicyReqCtrlPresent ); 1011 1012 Entry entry = modifyContext.getEntry(); 1013 1014 boolean removePwdReset = false; 1015 1016 List<Modification> mods = new ArrayList<>(); 1017 1018 if ( pwdModDetails.isAddOrReplace() ) 1019 { 1020 if ( isPwdTooYoung( modifyContext, entry, policyConfig ) ) 1021 { 1022 if ( isPPolicyReqCtrlPresent ) 1023 { 1024 PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl(); 1025 responseControl.setPasswordPolicyError( 1026 PasswordPolicyErrorEnum.PASSWORD_TOO_YOUNG ); 1027 modifyContext.addResponseControl( responseControl ); 1028 } 1029 1030 throw new LdapOperationException( ResultCodeEnum.CONSTRAINT_VIOLATION, 1031 "password is too young to update" ); 1032 } 1033 1034 byte[] newPassword = pwdModDetails.getNewPwd(); 1035 1036 try 1037 { 1038 check( modifyContext, entry, newPassword, policyConfig ); 1039 } 1040 catch ( PasswordPolicyException e ) 1041 { 1042 if ( isPPolicyReqCtrlPresent ) 1043 { 1044 PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl(); 1045 responseControl.setPasswordPolicyError( 1046 PasswordPolicyErrorEnum.get( e.getErrorCode() ) ); 1047 modifyContext.addResponseControl( responseControl ); 1048 } 1049 1050 // throw exception if userPassword quality checks fail 1051 throw new LdapOperationException( ResultCodeEnum.CONSTRAINT_VIOLATION, e.getMessage(), e ); 1052 } 1053 1054 int histSize = policyConfig.getPwdInHistory(); 1055 Modification pwdRemHistMod = null; 1056 Modification pwdAddHistMod = null; 1057 String pwdChangedTime = DateUtils.getGeneralizedTime( directoryService.getTimeProvider() ); 1058 1059 if ( histSize > 0 ) 1060 { 1061 Attribute pwdHistoryAt = entry.get( pwdHistoryAT ); 1062 1063 if ( pwdHistoryAt == null ) 1064 { 1065 pwdHistoryAt = new DefaultAttribute( pwdHistoryAT ); 1066 } 1067 1068 // Build the Modification containing the password history 1069 pwdRemHistMod = buildPwdHistory( modifyContext, pwdHistoryAt, histSize, 1070 newPassword, isPPolicyReqCtrlPresent ); 1071 1072 PasswordHistory newPwdHist = new PasswordHistory( pwdChangedTime, newPassword ); 1073 pwdHistoryAt.add( newPwdHist.getHistoryValue() ); 1074 pwdAddHistMod = new DefaultModification( REPLACE_ATTRIBUTE, pwdHistoryAt ); 1075 } 1076 1077 next( modifyContext ); 1078 1079 invalidateAuthenticatorCaches( modifyContext.getDn() ); 1080 1081 LookupOperationContext lookupContext = new LookupOperationContext( adminSession, modifyContext.getDn(), 1082 SchemaConstants.ALL_ATTRIBUTES_ARRAY ); 1083 lookupContext.setPartition( modifyContext.getPartition() ); 1084 lookupContext.setTransaction( modifyContext.getTransaction() ); 1085 1086 entry = directoryService.getPartitionNexus().lookup( lookupContext ); 1087 1088 if ( ( policyConfig.getPwdMinAge() > 0 ) || ( policyConfig.getPwdMaxAge() > 0 ) ) 1089 { 1090 Attribute pwdChangedTimeAt = new DefaultAttribute( pwdChangedTimeAT ); 1091 pwdChangedTimeAt.add( pwdChangedTime ); 1092 Modification pwdChangedTimeMod = new DefaultModification( REPLACE_ATTRIBUTE, pwdChangedTimeAt ); 1093 mods.add( pwdChangedTimeMod ); 1094 } 1095 1096 if ( pwdAddHistMod != null ) 1097 { 1098 mods.add( pwdAddHistMod ); 1099 } 1100 1101 if ( pwdRemHistMod != null ) 1102 { 1103 mods.add( pwdRemHistMod ); 1104 } 1105 1106 if ( policyConfig.isPwdMustChange() ) 1107 { 1108 Attribute pwdMustChangeAt = new DefaultAttribute( pwdResetAT ); 1109 Modification pwdMustChangeMod; 1110 1111 if ( modifyContext.getSession().isAnAdministrator() ) 1112 { 1113 pwdMustChangeAt.add( "TRUE" ); 1114 pwdMustChangeMod = new DefaultModification( REPLACE_ATTRIBUTE, pwdMustChangeAt ); 1115 } 1116 else 1117 { 1118 pwdMustChangeMod = new DefaultModification( REMOVE_ATTRIBUTE, pwdMustChangeAt ); 1119 removePwdReset = true; 1120 } 1121 1122 mods.add( pwdMustChangeMod ); 1123 } 1124 } 1125 1126 // Add the attributes that have been modified following a Add/Replace password 1127 processModifyAddPwdAttributes( entry, mods, pwdModDetails ); 1128 1129 String csnVal = directoryService.getCSN().toString(); 1130 Modification csnMod = new DefaultModification( REPLACE_ATTRIBUTE, directoryService.getAtProvider() 1131 .getEntryCSN(), csnVal ); 1132 mods.add( csnMod ); 1133 1134 ModifyOperationContext internalModifyCtx = new ModifyOperationContext( adminSession ); 1135 internalModifyCtx.setPushToEvtInterceptor( true ); 1136 internalModifyCtx.setDn( modifyContext.getDn() ); 1137 internalModifyCtx.setEntry( entry ); 1138 internalModifyCtx.setModItems( mods ); 1139 1140 internalModify( modifyContext, internalModifyCtx ); 1141 1142 if ( removePwdReset || pwdModDetails.isDelete() ) 1143 { 1144 userSession.setPwdMustChange( false ); 1145 } 1146 } 1147 } 1148 1149 1150 /** 1151 * Build the list of passwordHistory 1152 */ 1153 Modification buildPwdHistory( ModifyOperationContext modifyContext, Attribute pwdHistoryAt, 1154 int histSize, byte[] newPassword, boolean isPPolicyReqCtrlPresent ) throws LdapOperationException 1155 { 1156 List<PasswordHistory> pwdHistLst = new ArrayList<>(); 1157 1158 for ( Value value : pwdHistoryAt ) 1159 { 1160 PasswordHistory pwdh = new PasswordHistory( Strings.utf8ToString( value.getBytes() ) ); 1161 1162 // Admin user is exempt from history check 1163 // https://issues.apache.org/jira/browse/DIRSERVER-2084 1164 if ( !modifyContext.getSession().isAnAdministrator() ) 1165 { 1166 boolean matched = MessageDigest.isEqual( newPassword, pwdh.getPassword() ); 1167 1168 if ( matched ) 1169 { 1170 if ( isPPolicyReqCtrlPresent ) 1171 { 1172 PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl(); 1173 responseControl.setPasswordPolicyError( 1174 PasswordPolicyErrorEnum.PASSWORD_IN_HISTORY ); 1175 modifyContext.addResponseControl( responseControl ); 1176 } 1177 1178 throw new LdapOperationException( ResultCodeEnum.CONSTRAINT_VIOLATION, 1179 "invalid reuse of password present in password history" ); 1180 } 1181 } 1182 1183 pwdHistLst.add( pwdh ); 1184 } 1185 1186 Modification pwdRemHistMod = null; 1187 1188 if ( pwdHistLst.size() >= histSize ) 1189 { 1190 // see the javadoc of PasswordHistory 1191 Collections.sort( pwdHistLst ); 1192 1193 // remove the oldest value 1194 PasswordHistory remPwdHist = ( PasswordHistory ) pwdHistLst.toArray()[histSize - 1]; 1195 Attribute tempAt = new DefaultAttribute( pwdHistoryAT ); 1196 tempAt.add( remPwdHist.getHistoryValue() ); 1197 pwdRemHistMod = new DefaultModification( REMOVE_ATTRIBUTE, tempAt ); 1198 } 1199 1200 return pwdRemHistMod; 1201 } 1202 1203 1204 /** 1205 * Add the passwordPolicy related Attributes from the modified entry 1206 */ 1207 private void processModifyAddPwdAttributes( Entry entry, List<Modification> mods, PwdModDetailsHolder pwdModDetails ) 1208 { 1209 Attribute pwdFailureTimeAt = entry.get( pwdFailurTimeAT ); 1210 1211 if ( pwdFailureTimeAt != null ) 1212 { 1213 mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdFailureTimeAt ) ); 1214 } 1215 1216 Attribute pwdGraceUseTimeAt = entry.get( pwdGraceUseTimeAT ); 1217 1218 if ( pwdGraceUseTimeAt != null ) 1219 { 1220 mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdGraceUseTimeAt ) ); 1221 } 1222 1223 if ( pwdModDetails.isDelete() ) 1224 { 1225 Attribute pwdHistory = entry.get( pwdHistoryAT ); 1226 1227 if ( pwdHistory != null ) 1228 { 1229 mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdHistory ) ); 1230 } 1231 1232 Attribute pwdChangedTimeAt = entry.get( pwdChangedTimeAT ); 1233 1234 if ( pwdChangedTimeAt != null ) 1235 { 1236 mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdChangedTimeAt ) ); 1237 } 1238 1239 Attribute pwdMustChangeAt = entry.get( pwdResetAT ); 1240 1241 if ( pwdMustChangeAt != null ) 1242 { 1243 mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdMustChangeAt ) ); 1244 } 1245 1246 Attribute pwdAccountLockedTimeAt = entry.get( pwdAccountLockedTimeAT ); 1247 1248 if ( pwdAccountLockedTimeAt != null ) 1249 { 1250 mods.add( new DefaultModification( REMOVE_ATTRIBUTE, pwdAccountLockedTimeAt ) ); 1251 } 1252 } 1253 } 1254 1255 1256 /** 1257 * Check if the password has to be changed, but can't. 1258 */ 1259 private void checkPwdMustChange( ModifyOperationContext modifyContext, CoreSession userSession, 1260 PwdModDetailsHolder pwdModDetails, boolean isPPolicyReqCtrlPresent ) throws LdapNoPermissionException 1261 { 1262 if ( userSession.isPwdMustChange() && !pwdModDetails.isDelete() && pwdModDetails.isOtherModExists() ) 1263 { 1264 if ( isPPolicyReqCtrlPresent ) 1265 { 1266 PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl(); 1267 responseControl.setPasswordPolicyError( 1268 PasswordPolicyErrorEnum.CHANGE_AFTER_RESET ); 1269 modifyContext.addResponseControl( responseControl ); 1270 } 1271 1272 throw new LdapNoPermissionException( 1273 "Password should be reset before making any changes to this entry" ); 1274 } 1275 } 1276 1277 1278 /** 1279 * If the PP config request it, the old password must be supplied in the modifications. Check that it 1280 * is present. 1281 */ 1282 private void checkOldPwdRequired( ModifyOperationContext modifyContext, PasswordPolicyConfiguration policyConfig, 1283 PwdModDetailsHolder pwdModDetails, boolean isPPolicyReqCtrlPresent ) throws LdapNoPermissionException 1284 { 1285 if ( policyConfig.isPwdSafeModify() && !pwdModDetails.isDelete() && pwdModDetails.isAddOrReplace() ) 1286 { 1287 String msg = "trying to update password attribute without the supplying the old password"; 1288 LOG.debug( msg ); 1289 1290 if ( isPPolicyReqCtrlPresent ) 1291 { 1292 PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl(); 1293 responseControl.setPasswordPolicyError( 1294 PasswordPolicyErrorEnum.MUST_SUPPLY_OLD_PASSWORD ); 1295 modifyContext.addResponseControl( responseControl ); 1296 } 1297 1298 throw new LdapNoPermissionException( msg ); 1299 } 1300 } 1301 1302 1303 /** 1304 * check that if the password modification is allowed by the PP config, or if the session is 1305 * the admin. 1306 */ 1307 private void checkChangePwdAllowed( ModifyOperationContext modifyContext, PasswordPolicyConfiguration policyConfig, 1308 boolean isPPolicyReqCtrlPresent ) throws LdapNoPermissionException 1309 { 1310 if ( !policyConfig.isPwdAllowUserChange() && !modifyContext.getSession().isAnAdministrator() ) 1311 1312 { 1313 if ( isPPolicyReqCtrlPresent ) 1314 { 1315 PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl(); 1316 responseControl.setPasswordPolicyError( 1317 PasswordPolicyErrorEnum.PASSWORD_MOD_NOT_ALLOWED ); 1318 modifyContext.addResponseControl( responseControl ); 1319 } 1320 1321 throw new LdapNoPermissionException(); 1322 } 1323 } 1324 1325 1326 /** 1327 * {@inheritDoc} 1328 */ 1329 @Override 1330 public void move( MoveOperationContext moveContext ) throws LdapException 1331 { 1332 if ( IS_DEBUG ) 1333 { 1334 LOG.debug( "Operation Context: {}", moveContext ); 1335 } 1336 1337 checkAuthenticated( moveContext ); 1338 checkPwdReset( moveContext ); 1339 next( moveContext ); 1340 invalidateAuthenticatorCaches( moveContext.getDn() ); 1341 } 1342 1343 1344 /** 1345 * {@inheritDoc} 1346 */ 1347 @Override 1348 public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException 1349 { 1350 if ( IS_DEBUG ) 1351 { 1352 LOG.debug( "Operation Context: {}", moveAndRenameContext ); 1353 } 1354 1355 checkAuthenticated( moveAndRenameContext ); 1356 checkPwdReset( moveAndRenameContext ); 1357 next( moveAndRenameContext ); 1358 invalidateAuthenticatorCaches( moveAndRenameContext.getDn() ); 1359 } 1360 1361 1362 /** 1363 * {@inheritDoc} 1364 */ 1365 @Override 1366 public void rename( RenameOperationContext renameContext ) throws LdapException 1367 { 1368 if ( IS_DEBUG ) 1369 { 1370 LOG.debug( "Operation Context: {}", renameContext ); 1371 } 1372 1373 checkAuthenticated( renameContext ); 1374 checkPwdReset( renameContext ); 1375 next( renameContext ); 1376 invalidateAuthenticatorCaches( renameContext.getDn() ); 1377 } 1378 1379 1380 /** 1381 * {@inheritDoc} 1382 */ 1383 @Override 1384 public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException 1385 { 1386 if ( IS_DEBUG ) 1387 { 1388 LOG.debug( "Operation Context: {}", searchContext ); 1389 } 1390 1391 checkAuthenticated( searchContext ); 1392 checkPwdReset( searchContext ); 1393 1394 return next( searchContext ); 1395 } 1396 1397 1398 /** 1399 * {@inheritDoc} 1400 */ 1401 @Override 1402 public void unbind( UnbindOperationContext unbindContext ) throws LdapException 1403 { 1404 next( unbindContext ); 1405 } 1406 1407 1408 /** 1409 * Check if the current operation has a valid PrincipalDN or not. 1410 * 1411 * @param operation the operation type 1412 * @throws Exception 1413 */ 1414 private void checkAuthenticated( OperationContext operation ) throws LdapException 1415 { 1416 if ( operation.getSession().isAnonymous() && !directoryService.isAllowAnonymousAccess() 1417 && !operation.getDn().isEmpty() ) 1418 { 1419 String msg = I18n.err( I18n.ERR_5, operation.getName() ); 1420 LOG.error( msg ); 1421 throw new LdapNoPermissionException( msg ); 1422 } 1423 } 1424 1425 1426 /** 1427 * Initialize the PasswordPolicy attributeTypes 1428 * 1429 * @throws LdapException If the initialization failed 1430 */ 1431 public void loadPwdPolicyStateAttributeTypes() throws LdapException 1432 { 1433 pwdResetAT = schemaManager.lookupAttributeTypeRegistry( PWD_RESET_AT ); 1434 PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdResetAT ); 1435 1436 pwdChangedTimeAT = schemaManager.lookupAttributeTypeRegistry( PWD_CHANGED_TIME_AT ); 1437 PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdChangedTimeAT ); 1438 1439 pwdHistoryAT = schemaManager.lookupAttributeTypeRegistry( PWD_HISTORY_AT ); 1440 PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdHistoryAT ); 1441 1442 pwdFailurTimeAT = schemaManager.lookupAttributeTypeRegistry( PWD_FAILURE_TIME_AT ); 1443 PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdFailurTimeAT ); 1444 1445 pwdAccountLockedTimeAT = schemaManager.lookupAttributeTypeRegistry( PWD_ACCOUNT_LOCKED_TIME_AT ); 1446 PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdAccountLockedTimeAT ); 1447 1448 pwdLastSuccessAT = schemaManager.lookupAttributeTypeRegistry( PWD_LAST_SUCCESS_AT ); 1449 PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdLastSuccessAT ); 1450 1451 pwdGraceUseTimeAT = schemaManager.lookupAttributeTypeRegistry( PWD_GRACE_USE_TIME_AT ); 1452 PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdGraceUseTimeAT ); 1453 1454 pwdPolicySubentryAT = schemaManager.lookupAttributeTypeRegistry( PWD_POLICY_SUBENTRY_AT ); 1455 PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdPolicySubentryAT ); 1456 1457 pwdStartTimeAT = schemaManager.lookupAttributeTypeRegistry( PWD_START_TIME_AT ); 1458 PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdStartTimeAT ); 1459 1460 pwdEndTimeAT = schemaManager.lookupAttributeTypeRegistry( PWD_END_TIME_AT ); 1461 PWD_POLICY_STATE_ATTRIBUTE_TYPES.add( pwdEndTimeAT ); 1462 } 1463 1464 1465 // ---------- private methods ---------------- 1466 private void check( OperationContext operationContext, Entry entry, 1467 byte[] password, PasswordPolicyConfiguration policyConfig ) 1468 throws LdapException 1469 { 1470 // https://issues.apache.org/jira/browse/DIRSERVER-1928 1471 if ( operationContext.getSession().isAnAdministrator() ) 1472 { 1473 return; 1474 } 1475 1476 CheckQualityEnum qualityVal = policyConfig.getPwdCheckQuality(); 1477 1478 if ( qualityVal == CheckQualityEnum.NO_CHECK ) 1479 { 1480 return; 1481 } 1482 1483 LdapSecurityConstants secConst = PasswordUtil.findAlgorithm( password ); 1484 1485 // do not perform quality check if the password is not plain text and 1486 // pwdCheckQuality value is set to 1 1487 if ( secConst != null ) 1488 { 1489 if ( qualityVal == CheckQualityEnum.CHECK_ACCEPT ) 1490 { 1491 return; 1492 } 1493 else 1494 { 1495 throw new PasswordPolicyException( "cannot verify the quality of the non-cleartext passwords", 1496 INSUFFICIENT_PASSWORD_QUALITY.getValue() ); 1497 } 1498 } 1499 1500 String strPassword = Strings.utf8ToString( password ); 1501 1502 // perform the length validation 1503 validatePasswordLength( strPassword, policyConfig ); 1504 1505 PasswordValidator passwordValidator = policyConfig.getPwdValidator(); 1506 1507 if ( passwordValidator == null ) 1508 { 1509 // Use the default one 1510 passwordValidator = new DefaultPasswordValidator(); 1511 } 1512 1513 passwordValidator.validate( strPassword, entry ); 1514 } 1515 1516 1517 /** 1518 * validates the length of the password 1519 */ 1520 private void validatePasswordLength( String password, PasswordPolicyConfiguration policyConfig ) 1521 throws PasswordPolicyException 1522 { 1523 int maxLen = policyConfig.getPwdMaxLength(); 1524 int minLen = policyConfig.getPwdMinLength(); 1525 1526 int pwdLen = password.length(); 1527 1528 if ( ( maxLen > 0 ) && ( pwdLen > maxLen ) ) 1529 { 1530 throw new PasswordPolicyException( "Password should not have more than " + maxLen + " characters", 1531 INSUFFICIENT_PASSWORD_QUALITY.getValue() ); 1532 } 1533 1534 if ( ( minLen > 0 ) && ( pwdLen < minLen ) ) 1535 { 1536 throw new PasswordPolicyException( "Password should have a minimum of " + minLen + " characters", 1537 PASSWORD_TOO_SHORT.getValue() ); 1538 } 1539 } 1540 1541 1542 private int getPwdTimeBeforeExpiry( Entry userEntry, PasswordPolicyConfiguration policyConfig ) 1543 throws LdapException 1544 { 1545 if ( policyConfig.getPwdMaxAge() == 0 ) 1546 { 1547 return 0; 1548 } 1549 1550 int warningAge = policyConfig.getPwdExpireWarning(); 1551 1552 if ( warningAge <= 0 ) 1553 { 1554 return 0; 1555 } 1556 1557 Attribute pwdChangedTimeAt = userEntry.get( pwdChangedTimeAT ); 1558 if ( pwdChangedTimeAt == null ) 1559 { 1560 pwdChangedTimeAt = userEntry.get( directoryService.getAtProvider().getCreateTimestamp() ); 1561 } 1562 long changedTime = DateUtils.getDate( pwdChangedTimeAt.getString() ).getTime(); 1563 1564 long currentTime = directoryService.getTimeProvider().currentIimeMillis(); 1565 long pwdAge = ( currentTime - changedTime ) / 1000; 1566 1567 if ( pwdAge > policyConfig.getPwdMaxAge() ) 1568 { 1569 return 0; 1570 } 1571 1572 warningAge = policyConfig.getPwdMaxAge() - warningAge; 1573 1574 if ( pwdAge >= warningAge ) 1575 { 1576 long timeBeforeExpiration = ( ( long ) policyConfig.getPwdMaxAge() ) - pwdAge; 1577 1578 if ( timeBeforeExpiration > Integer.MAX_VALUE ) 1579 { 1580 timeBeforeExpiration = Integer.MAX_VALUE; 1581 } 1582 1583 return ( int ) timeBeforeExpiration; 1584 } 1585 1586 return 0; 1587 } 1588 1589 1590 /** 1591 * checks if the password is too young 1592 * 1593 * @param userEntry the user's entry 1594 * @return true if the password is young, false otherwise 1595 * @throws LdapException 1596 */ 1597 private boolean isPwdTooYoung( OperationContext operationContext, 1598 Entry userEntry, PasswordPolicyConfiguration policyConfig ) throws LdapException 1599 { 1600 // https://issues.apache.org/jira/browse/DIRSERVER-1928 1601 if ( operationContext.getSession().isAnAdministrator() ) 1602 { 1603 return false; 1604 } 1605 if ( policyConfig.getPwdMinAge() == 0 ) 1606 { 1607 return false; 1608 } 1609 1610 CoreSession userSession = operationContext.getSession(); 1611 1612 // see sections 7.8 and 7.2 of the ppolicy draft 1613 if ( policyConfig.isPwdMustChange() && userSession.isPwdMustChange() ) 1614 { 1615 return false; 1616 } 1617 1618 Attribute pwdChangedTimeAt = userEntry.get( pwdChangedTimeAT ); 1619 1620 if ( pwdChangedTimeAt != null ) 1621 { 1622 long changedTime = DateUtils.getDate( pwdChangedTimeAt.getString() ).getTime(); 1623 changedTime += policyConfig.getPwdMinAge() * 1000L; 1624 1625 long currentTime = directoryService.getTimeProvider().currentIimeMillis(); 1626 1627 if ( changedTime > currentTime ) 1628 { 1629 return true; 1630 } 1631 } 1632 1633 return false; 1634 } 1635 1636 1637 /** 1638 * checks if the password must be changed after the initial bind 1639 * 1640 * @param userEntry the user's entry 1641 * @return true if must be changed, false otherwise 1642 * @throws LdapException 1643 */ 1644 private boolean isPwdMustReset( Entry userEntry ) throws LdapException 1645 { 1646 boolean mustChange = false; 1647 1648 Attribute pwdResetAt = userEntry.get( pwdResetAT ); 1649 1650 if ( pwdResetAt != null ) 1651 { 1652 mustChange = Boolean.parseBoolean( pwdResetAt.getString() ); 1653 } 1654 1655 return mustChange; 1656 } 1657 1658 1659 private PwdModDetailsHolder getPwdModDetails( ModifyOperationContext modifyContext, 1660 PasswordPolicyConfiguration policyConfig ) throws LdapException 1661 { 1662 PwdModDetailsHolder pwdModDetails = new PwdModDetailsHolder(); 1663 1664 List<Modification> mods = modifyContext.getModItems(); 1665 1666 for ( Modification mod : mods ) 1667 { 1668 Attribute at = mod.getAttribute(); 1669 AttributeType passwordAttribute = schemaManager.lookupAttributeTypeRegistry( policyConfig.getPwdAttribute() ); 1670 1671 if ( at.getAttributeType().equals( passwordAttribute ) ) 1672 { 1673 pwdModDetails.setPwdModPresent( true ); 1674 ModificationOperation op = mod.getOperation(); 1675 1676 if ( op == REMOVE_ATTRIBUTE ) 1677 { 1678 pwdModDetails.setDelete( true ); 1679 } 1680 else if ( op == REPLACE_ATTRIBUTE || op == ADD_ATTRIBUTE ) 1681 { 1682 pwdModDetails.setAddOrReplace( true ); 1683 pwdModDetails.setNewPwd( at.getBytes() ); 1684 } 1685 1686 switch ( op ) 1687 { 1688 case REMOVE_ATTRIBUTE: 1689 pwdModDetails.setDelete( true ); 1690 break; 1691 1692 case REPLACE_ATTRIBUTE: 1693 case ADD_ATTRIBUTE: 1694 pwdModDetails.setAddOrReplace( true ); 1695 pwdModDetails.setNewPwd( at.getBytes() ); 1696 break; 1697 1698 default: 1699 // nothing to do 1700 } 1701 } 1702 else 1703 { 1704 pwdModDetails.setOtherModExists( true ); 1705 } 1706 } 1707 1708 return pwdModDetails; 1709 } 1710 1711 1712 /** 1713 * checks to see if the user's password should be changed before performing any operations 1714 * other than bind, password update, unbind, abandon or StartTLS 1715 * 1716 * @param opContext the operation's context 1717 * @throws LdapException 1718 */ 1719 private void checkPwdReset( OperationContext opContext ) throws LdapException 1720 { 1721 if ( directoryService.isPwdPolicyEnabled() ) 1722 { 1723 CoreSession session = opContext.getSession(); 1724 1725 if ( session.isPwdMustChange() ) 1726 { 1727 boolean isPPolicyReqCtrlPresent = opContext 1728 .hasRequestControl( PasswordPolicyRequest.OID ); 1729 1730 if ( isPPolicyReqCtrlPresent ) 1731 { 1732 PasswordPolicyResponse responseControl = new PasswordPolicyResponseImpl(); 1733 responseControl.setPasswordPolicyError( PasswordPolicyErrorEnum.CHANGE_AFTER_RESET ); 1734 opContext.addResponseControl( responseControl ); 1735 } 1736 1737 throw new LdapNoPermissionException( "password needs to be reset before performing this operation" ); 1738 } 1739 } 1740 } 1741 1742 private static class PwdModDetailsHolder 1743 { 1744 private boolean pwdModPresent = false; 1745 1746 private boolean isDelete = false; 1747 1748 private boolean isAddOrReplace = false; 1749 1750 private boolean otherModExists = false; 1751 1752 private byte[] newPwd; 1753 1754 1755 public boolean isPwdModPresent() 1756 { 1757 return pwdModPresent; 1758 } 1759 1760 1761 public void setPwdModPresent( boolean pwdModPresent ) 1762 { 1763 this.pwdModPresent = pwdModPresent; 1764 } 1765 1766 1767 public boolean isDelete() 1768 { 1769 return isDelete; 1770 } 1771 1772 1773 public void setDelete( boolean isDelete ) 1774 { 1775 this.isDelete = isDelete; 1776 } 1777 1778 1779 public boolean isAddOrReplace() 1780 { 1781 return isAddOrReplace; 1782 } 1783 1784 1785 public void setAddOrReplace( boolean isAddOrReplace ) 1786 { 1787 this.isAddOrReplace = isAddOrReplace; 1788 } 1789 1790 1791 public boolean isOtherModExists() 1792 { 1793 return otherModExists; 1794 } 1795 1796 1797 public void setOtherModExists( boolean otherModExists ) 1798 { 1799 this.otherModExists = otherModExists; 1800 } 1801 1802 1803 public byte[] getNewPwd() 1804 { 1805 return newPwd; 1806 } 1807 1808 1809 public void setNewPwd( byte[] newPwd ) 1810 { 1811 this.newPwd = newPwd; 1812 } 1813 } 1814 1815 1816 /** 1817 * Gets the effective password policy of the given entry. 1818 * If the entry has defined a custom password policy by setting "pwdPolicySubentry" attribute 1819 * then the password policy associated with the Dn specified at the above attribute's value will be returned. 1820 * Otherwise the default password policy will be returned (if present) 1821 * 1822 * @param userEntry the user's entry 1823 * @return the associated password policy 1824 * @throws LdapException If we weren't able to ftech the password policy 1825 */ 1826 public PasswordPolicyConfiguration getPwdPolicy( Entry userEntry ) throws LdapException 1827 { 1828 if ( pwdPolicyContainer == null ) 1829 { 1830 return null; 1831 } 1832 1833 if ( userEntry == null ) 1834 { 1835 return pwdPolicyContainer.getDefaultPolicy(); 1836 } 1837 1838 if ( pwdPolicyContainer.hasCustomConfigs() ) 1839 { 1840 Attribute pwdPolicySubentry = userEntry.get( pwdPolicySubentryAT ); 1841 1842 if ( pwdPolicySubentry != null ) 1843 { 1844 Dn configDn = dnFactory.create( pwdPolicySubentry.getString() ); 1845 1846 PasswordPolicyConfiguration custom = pwdPolicyContainer.getPolicyConfig( configDn ); 1847 1848 if ( custom != null ) 1849 { 1850 return custom; 1851 } 1852 else 1853 { 1854 LOG.warn( 1855 "The custom password policy for the user entry {} is not found, returning default policy configuration", 1856 userEntry.getDn() ); 1857 } 1858 } 1859 } 1860 1861 return pwdPolicyContainer.getDefaultPolicy(); 1862 } 1863 1864 1865 /** 1866 * set all the password policies to be used by the server. 1867 * This includes a default(i.e applicable to all entries) and custom(a.k.a per user) password policies 1868 * 1869 * @param policyContainer the container holding all the password policies 1870 */ 1871 public void setPwdPolicies( PpolicyConfigContainer policyContainer ) 1872 { 1873 this.pwdPolicyContainer = policyContainer; 1874 } 1875 1876 1877 /** 1878 * {@inheritDoc} 1879 */ 1880 public boolean isPwdPolicyEnabled() 1881 { 1882 return ( pwdPolicyContainer != null ) 1883 && ( ( pwdPolicyContainer.getDefaultPolicy() != null ) 1884 || ( pwdPolicyContainer.hasCustomConfigs() ) ); 1885 } 1886 1887 1888 /** 1889 * @return the pwdPolicyContainer 1890 */ 1891 public PpolicyConfigContainer getPwdPolicyContainer() 1892 { 1893 return pwdPolicyContainer; 1894 } 1895 1896 1897 /** 1898 * purges failure timestamps which are older than the configured interval 1899 * (section 7.6 in the draft) 1900 */ 1901 private void purgeFailureTimes( PasswordPolicyConfiguration config, Attribute pwdFailTimeAt ) 1902 { 1903 long interval = config.getPwdFailureCountInterval(); 1904 1905 if ( interval == 0 ) 1906 { 1907 return; 1908 } 1909 1910 interval *= 1000; 1911 1912 long currentTime = directoryService.getTimeProvider().currentIimeMillis(); 1913 1914 Iterator<Value> itr = pwdFailTimeAt.iterator(); 1915 1916 while ( itr.hasNext() ) 1917 { 1918 Value value = itr.next(); 1919 String failureTime = value.getString(); 1920 long time = DateUtils.getDate( failureTime ).getTime(); 1921 time += interval; 1922 1923 if ( currentTime >= time ) 1924 { 1925 itr.remove(); 1926 } 1927 } 1928 } 1929}