001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2008-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.workflowelement.localbackend; 018 019import java.util.List; 020 021import org.forgerock.i18n.LocalizableMessage; 022import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1; 023import org.forgerock.i18n.LocalizableMessageDescriptor.Arg2; 024import org.forgerock.i18n.slf4j.LocalizedLogger; 025import org.forgerock.opendj.ldap.ByteString; 026import org.forgerock.opendj.ldap.DN; 027import org.forgerock.opendj.ldap.ResultCode; 028import org.forgerock.opendj.ldap.schema.AttributeType; 029import org.forgerock.opendj.server.config.meta.PasswordPolicyCfgDefn; 030import org.opends.server.api.AuthenticationPolicyState; 031import org.opends.server.api.Backend; 032import org.opends.server.api.ClientConnection; 033import org.opends.server.api.SASLMechanismHandler; 034import org.opends.server.controls.*; 035import org.opends.server.core.*; 036import org.opends.server.types.*; 037import org.opends.server.types.operation.PostOperationBindOperation; 038import org.opends.server.types.operation.PostResponseBindOperation; 039import org.opends.server.types.operation.PreOperationBindOperation; 040 041import static org.opends.messages.CoreMessages.*; 042import static org.opends.server.config.ConfigConstants.*; 043import static org.opends.server.types.AbstractOperation.*; 044import static org.opends.server.types.Privilege.*; 045import static org.opends.server.util.ServerConstants.*; 046import static org.opends.server.util.StaticUtils.*; 047 048/** 049 * This class defines an operation used to bind against the Directory Server, 050 * with the bound user entry within a local backend. 051 */ 052public class LocalBackendBindOperation 053 extends BindOperationWrapper 054 implements PreOperationBindOperation, PostOperationBindOperation, 055 PostResponseBindOperation 056{ 057 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 058 059 /** The backend in which the bind operation should be processed. */ 060 private Backend<?> backend; 061 062 /** 063 * Indicates whether the bind response should include the first warning 064 * for an upcoming password expiration. 065 */ 066 private boolean isFirstWarning; 067 /** Indicates whether this bind is using a grace login for the user. */ 068 private boolean isGraceLogin; 069 070 /** Indicates whether the user must change his/her password before doing anything else. */ 071 private boolean mustChangePassword; 072 073 /** Indicates whether the user requested the password policy control. */ 074 private boolean pwPolicyControlRequested; 075 076 /** 077 * Indicates whether the server should return the authorization ID as a 078 * control in the bind response. 079 */ 080 private boolean returnAuthzID; 081 082 /** Indicates whether to execute post-operation plugins. */ 083 private boolean executePostOpPlugins; 084 085 /** The client connection associated with this bind operation. */ 086 private ClientConnection clientConnection; 087 088 /** The bind DN provided by the client. */ 089 private DN bindDN; 090 091 /** The value to use for the password policy warning. */ 092 private int pwPolicyWarningValue; 093 /** The lookthrough limit that should be enforced for the user. */ 094 private int lookthroughLimit; 095 /** The size limit that should be enforced for the user. */ 096 private int sizeLimit; 097 /** The time limit that should be enforced for the user. */ 098 private int timeLimit; 099 /** The idle time limit that should be enforced for the user. */ 100 private long idleTimeLimit; 101 102 /** Authentication policy state. */ 103 private AuthenticationPolicyState authPolicyState; 104 105 /** The password policy error type for this bind operation. */ 106 private PasswordPolicyErrorType pwPolicyErrorType; 107 /** The password policy warning type for this bind operation. */ 108 private PasswordPolicyWarningType pwPolicyWarningType; 109 110 /** The plugin config manager for the Directory Server. */ 111 private PluginConfigManager pluginConfigManager; 112 113 /** The SASL mechanism used for this bind operation. */ 114 private String saslMechanism; 115 116 /** 117 * Creates a new operation that may be used to bind where 118 * the bound user entry is stored in a local backend of the Directory Server. 119 * 120 * @param bind The operation to enhance. 121 */ 122 LocalBackendBindOperation(BindOperation bind) 123 { 124 super(bind); 125 LocalBackendWorkflowElement.attachLocalOperation (bind, this); 126 } 127 128 /** 129 * Process this bind operation in a local backend. 130 * 131 * @param wfe 132 * The local backend work-flow element. 133 */ 134 public void processLocalBind(LocalBackendWorkflowElement wfe) 135 { 136 this.backend = wfe.getBackend(); 137 138 // Initialize a number of variables for use during the bind processing. 139 clientConnection = getClientConnection(); 140 returnAuthzID = false; 141 executePostOpPlugins = false; 142 sizeLimit = DirectoryServer.getSizeLimit(); 143 timeLimit = DirectoryServer.getTimeLimit(); 144 lookthroughLimit = DirectoryServer.getLookthroughLimit(); 145 idleTimeLimit = DirectoryServer.getIdleTimeLimit(); 146 bindDN = getBindDN(); 147 saslMechanism = getSASLMechanism(); 148 authPolicyState = null; 149 pwPolicyErrorType = null; 150 pwPolicyControlRequested = false; 151 isGraceLogin = false; 152 isFirstWarning = false; 153 mustChangePassword = false; 154 pwPolicyWarningType = null; 155 pwPolicyWarningValue = -1 ; 156 pluginConfigManager = DirectoryServer.getPluginConfigManager(); 157 158 processBind(); 159 160 // Update the user's account with any password policy changes that may be 161 // required. 162 try 163 { 164 if (authPolicyState != null) 165 { 166 authPolicyState.finalizeStateAfterBind(); 167 } 168 } 169 catch (DirectoryException de) 170 { 171 logger.traceException(de); 172 173 setResponseData(de); 174 } 175 176 // Invoke the post-operation bind plugins. 177 if (executePostOpPlugins) 178 { 179 processOperationResult(this, pluginConfigManager.invokePostOperationBindPlugins(this)); 180 } 181 182 // Update the authentication information for the user. 183 AuthenticationInfo authInfo = getAuthenticationInfo(); 184 if (getResultCode() == ResultCode.SUCCESS && authInfo != null) 185 { 186 clientConnection.setAuthenticationInfo(authInfo); 187 clientConnection.setSizeLimit(sizeLimit); 188 clientConnection.setTimeLimit(timeLimit); 189 clientConnection.setIdleTimeLimit(idleTimeLimit); 190 clientConnection.setLookthroughLimit(lookthroughLimit); 191 clientConnection.setMustChangePassword(mustChangePassword); 192 193 if (returnAuthzID) 194 { 195 addResponseControl(new AuthorizationIdentityResponseControl( 196 authInfo.getAuthorizationDN())); 197 } 198 } 199 200 // See if we need to send a password policy control to the client. If so, 201 // then add it to the response. 202 if (pwPolicyControlRequested) 203 { 204 addResponseControl(new PasswordPolicyResponseControl( 205 pwPolicyWarningType, pwPolicyWarningValue, pwPolicyErrorType)); 206 } 207 else 208 { 209 if (getResultCode() == ResultCode.SUCCESS) 210 { 211 if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) 212 { 213 addResponseControl(new PasswordExpiredControl()); 214 } 215 else if (pwPolicyWarningType == PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION) 216 { 217 addResponseControl(new PasswordExpiringControl(pwPolicyWarningValue)); 218 } 219 else if (mustChangePassword) 220 { 221 addResponseControl(new PasswordExpiredControl()); 222 } 223 } 224 else 225 { 226 if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) 227 { 228 addResponseControl(new PasswordExpiredControl()); 229 } 230 } 231 } 232 } 233 234 /** 235 * Performs the checks and processing necessary for the current bind operation 236 * (simple or SASL). 237 */ 238 private void processBind() 239 { 240 // Check to see if the client has permission to perform the bind. 241 242 // FIXME: for now assume that this will check all permission 243 // pertinent to the operation. This includes any controls specified. 244 try 245 { 246 if (!AccessControlConfigManager.getInstance().getAccessControlHandler().isAllowed(this)) 247 { 248 setResultCode(ResultCode.INVALID_CREDENTIALS); 249 setAuthFailureReason(ERR_BIND_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get()); 250 return; 251 } 252 } 253 catch (DirectoryException e) 254 { 255 setResultCode(e.getResultCode()); 256 setAuthFailureReason(e.getMessageObject()); 257 return; 258 } 259 260 // Check to see if there are any controls in the request. If so, then see 261 // if there is any special processing required. 262 try 263 { 264 handleRequestControls(); 265 } 266 catch (DirectoryException de) 267 { 268 logger.traceException(de); 269 270 setResponseData(de); 271 return; 272 } 273 274 // Check to see if this is a simple bind or a SASL bind and process 275 // accordingly. 276 try 277 { 278 switch (getAuthenticationType()) 279 { 280 case SIMPLE: 281 processSimpleBind(); 282 break; 283 284 case SASL: 285 processSASLBind(); 286 break; 287 288 default: 289 // Send a protocol error response to the client and disconnect. 290 // We should never come here. 291 setResultCode(ResultCode.PROTOCOL_ERROR); 292 } 293 } 294 catch (DirectoryException de) 295 { 296 logger.traceException(de); 297 298 if (de.getResultCode() == ResultCode.INVALID_CREDENTIALS) 299 { 300 setResultCode(ResultCode.INVALID_CREDENTIALS); 301 setAuthFailureReason(de.getMessageObject()); 302 } 303 else 304 { 305 setResponseData(de); 306 } 307 } 308 } 309 310 /** 311 * Handles request control processing for this bind operation. 312 * 313 * @throws DirectoryException If there is a problem with any of the 314 * controls. 315 */ 316 private void handleRequestControls() throws DirectoryException 317 { 318 LocalBackendWorkflowElement.removeAllDisallowedControls(bindDN, this); 319 320 for (Control c : getRequestControls()) 321 { 322 final String oid = c.getOID(); 323 324 if (OID_AUTHZID_REQUEST.equals(oid)) 325 { 326 returnAuthzID = true; 327 } 328 else if (OID_PASSWORD_POLICY_CONTROL.equals(oid)) 329 { 330 pwPolicyControlRequested = true; 331 } 332 else if (c.isCritical()) 333 { 334 throw new DirectoryException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, 335 ERR_BIND_UNSUPPORTED_CRITICAL_CONTROL.get(oid)); 336 } 337 } 338 } 339 340 /** 341 * Performs the processing necessary for a simple bind operation. 342 * 343 * @return {@code true} if processing should continue for the operation, or 344 * {@code false} if not. 345 * 346 * @throws DirectoryException If a problem occurs that should cause the bind 347 * operation to fail. 348 */ 349 private boolean processSimpleBind() throws DirectoryException 350 { 351 // See if this is an anonymous bind. If so, then determine whether to allow it. 352 ByteString simplePassword = getSimplePassword(); 353 if (simplePassword == null || simplePassword.length() == 0) 354 { 355 return processAnonymousSimpleBind(); 356 } 357 358 // See if the bind DN is actually one of the alternate root DNs 359 // defined in the server. If so, then replace it with the actual DN 360 // for that user. 361 DN actualRootDN = DirectoryServer.getActualRootBindDN(bindDN); 362 if (actualRootDN != null) 363 { 364 bindDN = actualRootDN; 365 } 366 367 Entry userEntry; 368 try 369 { 370 userEntry = backend.getEntry(bindDN); 371 } 372 catch (DirectoryException de) 373 { 374 logger.traceException(de); 375 376 userEntry = null; 377 378 if (de.getResultCode() == ResultCode.REFERRAL) 379 { 380 // Re-throw referral exceptions - these should be passed back to the client. 381 throw de; 382 } 383 else 384 { 385 // Replace other exceptions in case they expose any sensitive information. 386 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, de.getMessageObject()); 387 } 388 } 389 390 if (userEntry == null) 391 { 392 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 393 ERR_BIND_OPERATION_UNKNOWN_USER.get()); 394 } 395 setUserEntryDN(userEntry.getName()); 396 397 // Check to see if the user has a password. If not, then fail. 398 // FIXME -- We need to have a way to enable/disable debugging. 399 authPolicyState = AuthenticationPolicyState.forUser(userEntry, false); 400 if (authPolicyState.isPasswordPolicy()) 401 { 402 // Account is managed locally. 403 PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState; 404 PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy(); 405 406 AttributeType pwType = policy.getPasswordAttribute(); 407 if (userEntry.getAttribute(pwType).isEmpty()) 408 { 409 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 410 ERR_BIND_OPERATION_NO_PASSWORD.get()); 411 } 412 413 // Perform a number of password policy state checks for the 414 // non-authenticated user. 415 checkUnverifiedPasswordPolicyState(userEntry, null); 416 417 // Invoke pre-operation plugins. 418 if (!invokePreOpPlugins()) 419 { 420 return false; 421 } 422 423 // Determine whether the provided password matches any of the stored 424 // passwords for the user. 425 if (pwPolicyState.passwordMatches(simplePassword)) 426 { 427 setResultCode(ResultCode.SUCCESS); 428 429 checkVerifiedPasswordPolicyState(userEntry, null); 430 431 if (DirectoryServer.lockdownMode() 432 && !ClientConnection.hasPrivilege(userEntry, BYPASS_LOCKDOWN)) 433 { 434 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 435 ERR_BIND_REJECTED_LOCKDOWN_MODE.get()); 436 } 437 setAuthenticationInfo(new AuthenticationInfo(userEntry, getBindDN(), 438 DirectoryServer.isRootDN(userEntry.getName()))); 439 440 // Set resource limits for the authenticated user. 441 setResourceLimits(userEntry); 442 443 // Perform any remaining processing for a successful simple 444 // authentication. 445 pwPolicyState.handleDeprecatedStorageSchemes(simplePassword); 446 pwPolicyState.clearFailureLockout(); 447 448 if (isFirstWarning) 449 { 450 pwPolicyState.setWarnedTime(); 451 452 int numSeconds = pwPolicyState.getSecondsUntilExpiration(); 453 LocalizableMessage m = WARN_BIND_PASSWORD_EXPIRING 454 .get(secondsToTimeString(numSeconds)); 455 456 pwPolicyState.generateAccountStatusNotification( 457 AccountStatusNotificationType.PASSWORD_EXPIRING, userEntry, m, 458 AccountStatusNotification.createProperties(pwPolicyState, 459 false, numSeconds, null, null)); 460 } 461 462 if (isGraceLogin) 463 { 464 pwPolicyState.updateGraceLoginTimes(); 465 } 466 467 pwPolicyState.setLastLoginTime(); 468 } 469 else 470 { 471 setResultCode(ResultCode.INVALID_CREDENTIALS); 472 setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get()); 473 474 if (policy.getLockoutFailureCount() > 0) 475 { 476 updateFailureCount(userEntry, pwPolicyState); 477 } 478 } 479 } 480 else 481 { 482 // Check to see if the user is administratively disabled or locked. 483 if (authPolicyState.isDisabled()) 484 { 485 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 486 ERR_BIND_OPERATION_ACCOUNT_DISABLED.get()); 487 } 488 489 // Invoke pre-operation plugins. 490 if (!invokePreOpPlugins()) 491 { 492 return false; 493 } 494 495 if (authPolicyState.passwordMatches(simplePassword)) 496 { 497 setResultCode(ResultCode.SUCCESS); 498 499 if (DirectoryServer.lockdownMode() 500 && !ClientConnection.hasPrivilege(userEntry, BYPASS_LOCKDOWN)) 501 { 502 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 503 ERR_BIND_REJECTED_LOCKDOWN_MODE.get()); 504 } 505 setAuthenticationInfo(new AuthenticationInfo(userEntry, getBindDN(), 506 DirectoryServer.isRootDN(userEntry.getName()))); 507 508 // Set resource limits for the authenticated user. 509 setResourceLimits(userEntry); 510 } 511 else 512 { 513 setResultCode(ResultCode.INVALID_CREDENTIALS); 514 setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get()); 515 } 516 } 517 518 return true; 519 } 520 521 /** 522 * Performs the processing necessary for an anonymous simple bind. 523 * 524 * @return {@code true} if processing should continue for the operation, or 525 * {@code false} if not. 526 * @throws DirectoryException If a problem occurs that should cause the bind 527 * operation to fail. 528 */ 529 private boolean processAnonymousSimpleBind() throws DirectoryException 530 { 531 // If the server is in lockdown mode, then fail. 532 if (DirectoryServer.lockdownMode()) 533 { 534 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 535 ERR_BIND_REJECTED_LOCKDOWN_MODE.get()); 536 } 537 538 // If there is a bind DN, then see whether that is acceptable. 539 if (DirectoryServer.bindWithDNRequiresPassword() 540 && bindDN != null && !bindDN.isRootDN()) 541 { 542 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 543 ERR_BIND_DN_BUT_NO_PASSWORD.get()); 544 } 545 546 // Invoke pre-operation plugins. 547 if (!invokePreOpPlugins()) 548 { 549 return false; 550 } 551 552 setResultCode(ResultCode.SUCCESS); 553 setAuthenticationInfo(new AuthenticationInfo()); 554 return true; 555 } 556 557 /** 558 * Performs the processing necessary for a SASL bind operation. 559 * 560 * @return {@code true} if processing should continue for the operation, or 561 * {@code false} if not. 562 * 563 * @throws DirectoryException If a problem occurs that should cause the bind 564 * operation to fail. 565 */ 566 private boolean processSASLBind() throws DirectoryException 567 { 568 // Get the appropriate authentication handler for this request based 569 // on the SASL mechanism. If there is none, then fail. 570 SASLMechanismHandler<?> saslHandler = 571 DirectoryServer.getSASLMechanismHandler(saslMechanism); 572 if (saslHandler == null) 573 { 574 throw new DirectoryException(ResultCode.AUTH_METHOD_NOT_SUPPORTED, 575 ERR_BIND_OPERATION_UNKNOWN_SASL_MECHANISM.get( 576 saslMechanism)); 577 } 578 579 // Check to see if the client has sufficient permission to perform the bind. 580 // NYI 581 582 // Invoke pre-operation plugins. 583 if (!invokePreOpPlugins()) 584 { 585 return false; 586 } 587 588 // Actually process the SASL bind. 589 saslHandler.processSASLBind(this); 590 591 // If the server is operating in lockdown mode, then we will need to 592 // ensure that the authentication was successful and performed as a 593 // root user to continue. 594 Entry saslAuthUserEntry = getSASLAuthUserEntry(); 595 if (DirectoryServer.lockdownMode()) 596 { 597 ResultCode resultCode = getResultCode(); 598 if (resultCode != ResultCode.SASL_BIND_IN_PROGRESS 599 && (resultCode != ResultCode.SUCCESS 600 || saslAuthUserEntry == null 601 || !ClientConnection.hasPrivilege(saslAuthUserEntry, BYPASS_LOCKDOWN))) 602 { 603 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 604 ERR_BIND_REJECTED_LOCKDOWN_MODE.get()); 605 } 606 } 607 608 // Create the password policy state object. 609 if (saslAuthUserEntry != null) 610 { 611 setUserEntryDN(saslAuthUserEntry.getName()); 612 613 // FIXME -- Need to have a way to enable debugging. 614 authPolicyState = AuthenticationPolicyState.forUser( 615 saslAuthUserEntry, false); 616 if (authPolicyState.isPasswordPolicy()) 617 { 618 // Account is managed locally: perform password policy checks that can 619 // be completed before we have checked authentication was successful. 620 checkUnverifiedPasswordPolicyState(saslAuthUserEntry, saslHandler); 621 } 622 } 623 624 // Determine whether the authentication was successful and perform 625 // any remaining password policy processing accordingly. 626 ResultCode resultCode = getResultCode(); 627 if (resultCode == ResultCode.SUCCESS) 628 { 629 if (authPolicyState != null && authPolicyState.isPasswordPolicy()) 630 { 631 checkVerifiedPasswordPolicyState(saslAuthUserEntry, saslHandler); 632 633 PasswordPolicyState pwPolicyState = 634 (PasswordPolicyState) authPolicyState; 635 636 if (saslHandler.isPasswordBased(saslMechanism) && 637 pwPolicyState.mustChangePassword()) 638 { 639 mustChangePassword = true; 640 } 641 642 if (isFirstWarning) 643 { 644 pwPolicyState.setWarnedTime(); 645 646 int numSeconds = pwPolicyState.getSecondsUntilExpiration(); 647 LocalizableMessage m = WARN_BIND_PASSWORD_EXPIRING.get( 648 secondsToTimeString(numSeconds)); 649 650 pwPolicyState.generateAccountStatusNotification( 651 AccountStatusNotificationType.PASSWORD_EXPIRING, 652 saslAuthUserEntry, m, 653 AccountStatusNotification.createProperties(pwPolicyState, 654 false, numSeconds, null, null)); 655 } 656 657 if (isGraceLogin) 658 { 659 pwPolicyState.updateGraceLoginTimes(); 660 } 661 662 pwPolicyState.setLastLoginTime(); 663 } 664 665 // Set appropriate resource limits for the user (note that SASL ANONYMOUS 666 // does not have a user). 667 if (saslAuthUserEntry != null) 668 { 669 setResourceLimits(saslAuthUserEntry); 670 } 671 } 672 else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS) 673 { 674 // FIXME -- Is any special processing needed here? 675 return false; 676 } 677 else 678 { 679 if (authPolicyState != null && authPolicyState.isPasswordPolicy()) 680 { 681 PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState; 682 683 if (saslHandler.isPasswordBased(saslMechanism) 684 && pwPolicyState.getAuthenticationPolicy().getLockoutFailureCount() > 0) 685 { 686 updateFailureCount(saslAuthUserEntry, pwPolicyState); 687 } 688 } 689 } 690 691 return true; 692 } 693 694 private void updateFailureCount(Entry userEntry, PasswordPolicyState pwPolicyState) 695 { 696 if (pwPolicyState.lockedDueToFailures()) 697 { 698 // Account is already locked, nothing to do 699 return; 700 } 701 pwPolicyState.updateAuthFailureTimes(); 702 if (pwPolicyState.lockedDueToFailures()) 703 { 704 AccountStatusNotificationType notificationType; 705 boolean tempLocked; 706 LocalizableMessage m; 707 708 int lockoutDuration = pwPolicyState.getSecondsUntilUnlock(); 709 if (lockoutDuration > -1) 710 { 711 notificationType = AccountStatusNotificationType.ACCOUNT_TEMPORARILY_LOCKED; 712 tempLocked = true; 713 m = 714 ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED 715 .get(secondsToTimeString(lockoutDuration)); 716 } 717 else 718 { 719 notificationType = AccountStatusNotificationType.ACCOUNT_PERMANENTLY_LOCKED; 720 tempLocked = false; 721 m = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get(); 722 } 723 724 pwPolicyState.generateAccountStatusNotification(notificationType, 725 userEntry, m, AccountStatusNotification.createProperties( 726 pwPolicyState, tempLocked, -1, null, null)); 727 } 728 } 729 730 private boolean invokePreOpPlugins() 731 { 732 executePostOpPlugins = true; 733 return processOperationResult(this, pluginConfigManager.invokePreOperationBindPlugins(this)); 734 } 735 736 /** 737 * Validates a number of password policy state constraints for the user. This 738 * will be called before the offered credentials are checked. 739 * 740 * @param userEntry 741 * The entry for the user that is authenticating. 742 * @param saslHandler 743 * The SASL mechanism handler if this is a SASL bind, or {@code null} 744 * for a simple bind. 745 * @throws DirectoryException 746 * If a problem occurs that should cause the bind to fail. 747 */ 748 private void checkUnverifiedPasswordPolicyState( 749 Entry userEntry, SASLMechanismHandler<?> saslHandler) 750 throws DirectoryException 751 { 752 PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState; 753 PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy(); 754 755 756 // If the password policy is configured to track authentication failures or 757 // keep the last login time and the associated backend is disabled, then we 758 // may need to reject the bind immediately. 759 if ((policy.getStateUpdateFailurePolicy() == 760 PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) && 761 ((policy.getLockoutFailureCount() > 0) || 762 ((policy.getLastLoginTimeAttribute() != null) && 763 (policy.getLastLoginTimeFormat() != null))) && 764 ((DirectoryServer.getWritabilityMode() == WritabilityMode.DISABLED) || 765 (backend.getWritabilityMode() == WritabilityMode.DISABLED))) 766 { 767 // This policy isn't applicable to root users, so if it's a root 768 // user then ignore it. 769 if (! DirectoryServer.isRootDN(userEntry.getName())) 770 { 771 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 772 ERR_BIND_OPERATION_WRITABILITY_DISABLED.get(userEntry.getName())); 773 } 774 } 775 776 // Check to see if the authentication must be done in a secure 777 // manner. If so, then the client connection must be secure. 778 if (policy.isRequireSecureAuthentication() 779 && !clientConnection.isSecure()) 780 { 781 boolean isSASLBind = saslHandler != null; 782 if (isSASLBind) 783 { 784 if (! saslHandler.isSecure(saslMechanism)) 785 { 786 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 787 ERR_BIND_OPERATION_INSECURE_SASL_BIND.get(saslMechanism, userEntry.getName())); 788 } 789 } 790 else 791 { 792 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 793 ERR_BIND_OPERATION_INSECURE_SIMPLE_BIND.get()); 794 } 795 } 796 } 797 798 /** 799 * Perform policy checks for accounts when the credentials are correct. 800 * 801 * @param userEntry 802 * The entry for the user that is authenticating. 803 * @param saslHandler 804 * The SASL mechanism handler if this is a SASL bind, or {@code null} 805 * for a simple bind. 806 * @throws DirectoryException 807 * If a problem occurs that should cause the bind to fail. 808 */ 809 private void checkVerifiedPasswordPolicyState( 810 Entry userEntry, SASLMechanismHandler<?> saslHandler) 811 throws DirectoryException 812 { 813 PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState; 814 PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy(); 815 816 // Check to see if the user is administratively disabled or locked. 817 if (pwPolicyState.isDisabled()) 818 { 819 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 820 ERR_BIND_OPERATION_ACCOUNT_DISABLED.get()); 821 } 822 else if (pwPolicyState.isAccountExpired()) 823 { 824 LocalizableMessage m = ERR_BIND_OPERATION_ACCOUNT_EXPIRED.get(); 825 pwPolicyState.generateAccountStatusNotification( 826 AccountStatusNotificationType.ACCOUNT_EXPIRED, userEntry, m, 827 AccountStatusNotification.createProperties(pwPolicyState, 828 false, -1, null, null)); 829 830 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 831 } 832 else if (pwPolicyState.lockedDueToFailures()) 833 { 834 if (pwPolicyErrorType == null) 835 { 836 pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; 837 } 838 839 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 840 ERR_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED.get()); 841 } 842 else if (pwPolicyState.lockedDueToIdleInterval()) 843 { 844 if (pwPolicyErrorType == null) 845 { 846 pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; 847 } 848 849 LocalizableMessage m = ERR_BIND_OPERATION_ACCOUNT_IDLE_LOCKED.get(); 850 pwPolicyState.generateAccountStatusNotification( 851 AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, userEntry, m, 852 AccountStatusNotification.createProperties(pwPolicyState, false, -1, 853 null, null)); 854 855 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 856 } 857 858 // If it's a simple bind, or if it's a password-based SASL bind, then 859 // perform a number of password-based checks. 860 boolean isSASLBind = saslHandler != null; 861 if (!isSASLBind || saslHandler.isPasswordBased(saslMechanism)) 862 { 863 // Check to see if the account is locked due to the maximum reset age. 864 if (pwPolicyState.lockedDueToMaximumResetAge()) 865 { 866 if (pwPolicyErrorType == null) 867 { 868 pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; 869 } 870 871 LocalizableMessage m = ERR_BIND_OPERATION_ACCOUNT_RESET_LOCKED.get(); 872 pwPolicyState.generateAccountStatusNotification( 873 AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, userEntry, m, 874 AccountStatusNotification.createProperties(pwPolicyState, false, 875 -1, null, null)); 876 877 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 878 } 879 880 // Determine whether the password is expired, or whether the user 881 // should be warned about an upcoming expiration. 882 if (pwPolicyState.isPasswordExpired()) 883 { 884 if (pwPolicyErrorType == null) 885 { 886 pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED; 887 } 888 889 int maxGraceLogins = policy.getGraceLoginCount(); 890 if (maxGraceLogins > 0 && pwPolicyState.mayUseGraceLogin()) 891 { 892 List<Long> graceLoginTimes = pwPolicyState.getGraceLoginTimes(); 893 if (graceLoginTimes == null || 894 graceLoginTimes.size() < maxGraceLogins) 895 { 896 isGraceLogin = true; 897 mustChangePassword = true; 898 899 if (pwPolicyWarningType == null) 900 { 901 pwPolicyWarningType = 902 PasswordPolicyWarningType.GRACE_LOGINS_REMAINING; 903 pwPolicyWarningValue = maxGraceLogins - 904 (graceLoginTimes.size() + 1); 905 } 906 } 907 else 908 { 909 LocalizableMessage m = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get(); 910 911 pwPolicyState.generateAccountStatusNotification( 912 AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry, m, 913 AccountStatusNotification.createProperties(pwPolicyState, 914 false, -1, null, 915 null)); 916 917 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 918 } 919 } 920 else 921 { 922 LocalizableMessage m = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get(); 923 924 pwPolicyState.generateAccountStatusNotification( 925 AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry, m, 926 AccountStatusNotification.createProperties(pwPolicyState, false, 927 -1, null, null)); 928 929 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 930 } 931 } 932 else if (pwPolicyState.shouldWarn()) 933 { 934 int numSeconds = pwPolicyState.getSecondsUntilExpiration(); 935 936 if (pwPolicyWarningType == null) 937 { 938 pwPolicyWarningType = PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION; 939 pwPolicyWarningValue = numSeconds; 940 } 941 942 isFirstWarning = pwPolicyState.isFirstWarning(); 943 } 944 945 // Check to see if the user's password has been reset. 946 if (pwPolicyState.mustChangePassword()) 947 { 948 mustChangePassword = true; 949 950 if (pwPolicyErrorType == null) 951 { 952 pwPolicyErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET; 953 } 954 } 955 } 956 } 957 958 /** 959 * Sets resource limits for the authenticated user. 960 * 961 * @param userEntry The entry for the authenticated user. 962 */ 963 private void setResourceLimits(Entry userEntry) 964 { 965 // See if the user's entry contains a custom size limit. 966 Integer customSizeLimit = 967 getIntegerUserAttribute(userEntry, OP_ATTR_USER_SIZE_LIMIT, 968 WARN_BIND_MULTIPLE_USER_SIZE_LIMITS, 969 WARN_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT); 970 if (customSizeLimit != null) 971 { 972 sizeLimit = customSizeLimit; 973 } 974 975 // See if the user's entry contains a custom time limit. 976 Integer customTimeLimit = 977 getIntegerUserAttribute(userEntry, OP_ATTR_USER_TIME_LIMIT, 978 WARN_BIND_MULTIPLE_USER_TIME_LIMITS, 979 WARN_BIND_CANNOT_PROCESS_USER_TIME_LIMIT); 980 if (customTimeLimit != null) 981 { 982 timeLimit = customTimeLimit; 983 } 984 985 // See if the user's entry contains a custom idle time limit. 986 // idleTimeLimit = 1000L * Long.parseLong(v.toString()); 987 Integer customIdleTimeLimitInSec = 988 getIntegerUserAttribute(userEntry, OP_ATTR_USER_IDLE_TIME_LIMIT, 989 WARN_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS, 990 WARN_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT); 991 if (customIdleTimeLimitInSec != null) 992 { 993 idleTimeLimit = 1000L * customIdleTimeLimitInSec; 994 } 995 996 // See if the user's entry contains a custom lookthrough limit. 997 Integer customLookthroughLimit = 998 getIntegerUserAttribute(userEntry, OP_ATTR_USER_LOOKTHROUGH_LIMIT, 999 WARN_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS, 1000 WARN_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT); 1001 if (customLookthroughLimit != null) 1002 { 1003 lookthroughLimit = customLookthroughLimit; 1004 } 1005 } 1006 1007 private Integer getIntegerUserAttribute(Entry userEntry, 1008 String attributeTypeName, 1009 Arg1<Object> nonUniqueAttributeMessage, 1010 Arg2<Object, Object> cannotProcessAttributeMessage) 1011 { 1012 AttributeType attrType = DirectoryServer.getSchema().getAttributeType(attributeTypeName); 1013 List<Attribute> attrList = userEntry.getAttribute(attrType); 1014 if (attrList.size() == 1) 1015 { 1016 Attribute a = attrList.get(0); 1017 if (a.size() == 1) 1018 { 1019 ByteString v = a.iterator().next(); 1020 try 1021 { 1022 return Integer.valueOf(v.toString()); 1023 } 1024 catch (Exception e) 1025 { 1026 logger.traceException(e); 1027 logger.error(cannotProcessAttributeMessage.get(v, userEntry.getName())); 1028 } 1029 } 1030 else if (a.size() > 1) 1031 { 1032 logger.error(nonUniqueAttributeMessage.get(userEntry.getName())); 1033 } 1034 } 1035 return null; 1036 } 1037}