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.opends.server.admin.std.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 generateAccountStatusNotificationForLockedBindAccount(userEntry, 477 pwPolicyState); 478 } 479 } 480 } 481 else 482 { 483 // Check to see if the user is administratively disabled or locked. 484 if (authPolicyState.isDisabled()) 485 { 486 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 487 ERR_BIND_OPERATION_ACCOUNT_DISABLED.get()); 488 } 489 490 // Invoke pre-operation plugins. 491 if (!invokePreOpPlugins()) 492 { 493 return false; 494 } 495 496 if (authPolicyState.passwordMatches(simplePassword)) 497 { 498 setResultCode(ResultCode.SUCCESS); 499 500 if (DirectoryServer.lockdownMode() 501 && !ClientConnection.hasPrivilege(userEntry, BYPASS_LOCKDOWN)) 502 { 503 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 504 ERR_BIND_REJECTED_LOCKDOWN_MODE.get()); 505 } 506 setAuthenticationInfo(new AuthenticationInfo(userEntry, getBindDN(), 507 DirectoryServer.isRootDN(userEntry.getName()))); 508 509 // Set resource limits for the authenticated user. 510 setResourceLimits(userEntry); 511 } 512 else 513 { 514 setResultCode(ResultCode.INVALID_CREDENTIALS); 515 setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get()); 516 } 517 } 518 519 return true; 520 } 521 522 /** 523 * Performs the processing necessary for an anonymous simple bind. 524 * 525 * @return {@code true} if processing should continue for the operation, or 526 * {@code false} if not. 527 * @throws DirectoryException If a problem occurs that should cause the bind 528 * operation to fail. 529 */ 530 private boolean processAnonymousSimpleBind() throws DirectoryException 531 { 532 // If the server is in lockdown mode, then fail. 533 if (DirectoryServer.lockdownMode()) 534 { 535 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 536 ERR_BIND_REJECTED_LOCKDOWN_MODE.get()); 537 } 538 539 // If there is a bind DN, then see whether that is acceptable. 540 if (DirectoryServer.bindWithDNRequiresPassword() 541 && bindDN != null && !bindDN.isRootDN()) 542 { 543 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 544 ERR_BIND_DN_BUT_NO_PASSWORD.get()); 545 } 546 547 // Invoke pre-operation plugins. 548 if (!invokePreOpPlugins()) 549 { 550 return false; 551 } 552 553 setResultCode(ResultCode.SUCCESS); 554 setAuthenticationInfo(new AuthenticationInfo()); 555 return true; 556 } 557 558 /** 559 * Performs the processing necessary for a SASL bind operation. 560 * 561 * @return {@code true} if processing should continue for the operation, or 562 * {@code false} if not. 563 * 564 * @throws DirectoryException If a problem occurs that should cause the bind 565 * operation to fail. 566 */ 567 private boolean processSASLBind() throws DirectoryException 568 { 569 // Get the appropriate authentication handler for this request based 570 // on the SASL mechanism. If there is none, then fail. 571 SASLMechanismHandler<?> saslHandler = 572 DirectoryServer.getSASLMechanismHandler(saslMechanism); 573 if (saslHandler == null) 574 { 575 throw new DirectoryException(ResultCode.AUTH_METHOD_NOT_SUPPORTED, 576 ERR_BIND_OPERATION_UNKNOWN_SASL_MECHANISM.get( 577 saslMechanism)); 578 } 579 580 // Check to see if the client has sufficient permission to perform the bind. 581 // NYI 582 583 // Invoke pre-operation plugins. 584 if (!invokePreOpPlugins()) 585 { 586 return false; 587 } 588 589 // Actually process the SASL bind. 590 saslHandler.processSASLBind(this); 591 592 // If the server is operating in lockdown mode, then we will need to 593 // ensure that the authentication was successful and performed as a 594 // root user to continue. 595 Entry saslAuthUserEntry = getSASLAuthUserEntry(); 596 if (DirectoryServer.lockdownMode()) 597 { 598 ResultCode resultCode = getResultCode(); 599 if (resultCode != ResultCode.SASL_BIND_IN_PROGRESS 600 && (resultCode != ResultCode.SUCCESS 601 || saslAuthUserEntry == null 602 || !ClientConnection.hasPrivilege(saslAuthUserEntry, BYPASS_LOCKDOWN))) 603 { 604 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 605 ERR_BIND_REJECTED_LOCKDOWN_MODE.get()); 606 } 607 } 608 609 // Create the password policy state object. 610 if (saslAuthUserEntry != null) 611 { 612 setUserEntryDN(saslAuthUserEntry.getName()); 613 614 // FIXME -- Need to have a way to enable debugging. 615 authPolicyState = AuthenticationPolicyState.forUser( 616 saslAuthUserEntry, false); 617 if (authPolicyState.isPasswordPolicy()) 618 { 619 // Account is managed locally: perform password policy checks that can 620 // be completed before we have checked authentication was successful. 621 checkUnverifiedPasswordPolicyState(saslAuthUserEntry, saslHandler); 622 } 623 } 624 625 // Determine whether the authentication was successful and perform 626 // any remaining password policy processing accordingly. 627 ResultCode resultCode = getResultCode(); 628 if (resultCode == ResultCode.SUCCESS) 629 { 630 if (authPolicyState != null && authPolicyState.isPasswordPolicy()) 631 { 632 checkVerifiedPasswordPolicyState(saslAuthUserEntry, saslHandler); 633 634 PasswordPolicyState pwPolicyState = 635 (PasswordPolicyState) authPolicyState; 636 637 if (saslHandler.isPasswordBased(saslMechanism) && 638 pwPolicyState.mustChangePassword()) 639 { 640 mustChangePassword = true; 641 } 642 643 if (isFirstWarning) 644 { 645 pwPolicyState.setWarnedTime(); 646 647 int numSeconds = pwPolicyState.getSecondsUntilExpiration(); 648 LocalizableMessage m = WARN_BIND_PASSWORD_EXPIRING.get( 649 secondsToTimeString(numSeconds)); 650 651 pwPolicyState.generateAccountStatusNotification( 652 AccountStatusNotificationType.PASSWORD_EXPIRING, 653 saslAuthUserEntry, m, 654 AccountStatusNotification.createProperties(pwPolicyState, 655 false, numSeconds, null, null)); 656 } 657 658 if (isGraceLogin) 659 { 660 pwPolicyState.updateGraceLoginTimes(); 661 } 662 663 pwPolicyState.setLastLoginTime(); 664 } 665 666 // Set appropriate resource limits for the user (note that SASL ANONYMOUS 667 // does not have a user). 668 if (saslAuthUserEntry != null) 669 { 670 setResourceLimits(saslAuthUserEntry); 671 } 672 } 673 else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS) 674 { 675 // FIXME -- Is any special processing needed here? 676 return false; 677 } 678 else 679 { 680 if (authPolicyState != null && authPolicyState.isPasswordPolicy()) 681 { 682 PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState; 683 684 if (saslHandler.isPasswordBased(saslMechanism) 685 && pwPolicyState.getAuthenticationPolicy().getLockoutFailureCount() > 0) 686 { 687 generateAccountStatusNotificationForLockedBindAccount( 688 saslAuthUserEntry, pwPolicyState); 689 } 690 } 691 } 692 693 return true; 694 } 695 696 private void generateAccountStatusNotificationForLockedBindAccount( 697 Entry userEntry, PasswordPolicyState pwPolicyState) 698 { 699 pwPolicyState.updateAuthFailureTimes(); 700 if (pwPolicyState.lockedDueToFailures()) 701 { 702 AccountStatusNotificationType notificationType; 703 boolean tempLocked; 704 LocalizableMessage m; 705 706 int lockoutDuration = pwPolicyState.getSecondsUntilUnlock(); 707 if (lockoutDuration > -1) 708 { 709 notificationType = AccountStatusNotificationType.ACCOUNT_TEMPORARILY_LOCKED; 710 tempLocked = true; 711 m = 712 ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED 713 .get(secondsToTimeString(lockoutDuration)); 714 } 715 else 716 { 717 notificationType = AccountStatusNotificationType.ACCOUNT_PERMANENTLY_LOCKED; 718 tempLocked = false; 719 m = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get(); 720 } 721 722 pwPolicyState.generateAccountStatusNotification(notificationType, 723 userEntry, m, AccountStatusNotification.createProperties( 724 pwPolicyState, tempLocked, -1, null, null)); 725 } 726 } 727 728 private boolean invokePreOpPlugins() 729 { 730 executePostOpPlugins = true; 731 return processOperationResult(this, pluginConfigManager.invokePreOperationBindPlugins(this)); 732 } 733 734 /** 735 * Validates a number of password policy state constraints for the user. This 736 * will be called before the offered credentials are checked. 737 * 738 * @param userEntry 739 * The entry for the user that is authenticating. 740 * @param saslHandler 741 * The SASL mechanism handler if this is a SASL bind, or {@code null} 742 * for a simple bind. 743 * @throws DirectoryException 744 * If a problem occurs that should cause the bind to fail. 745 */ 746 private void checkUnverifiedPasswordPolicyState( 747 Entry userEntry, SASLMechanismHandler<?> saslHandler) 748 throws DirectoryException 749 { 750 PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState; 751 PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy(); 752 753 754 // If the password policy is configured to track authentication failures or 755 // keep the last login time and the associated backend is disabled, then we 756 // may need to reject the bind immediately. 757 if ((policy.getStateUpdateFailurePolicy() == 758 PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) && 759 ((policy.getLockoutFailureCount() > 0) || 760 ((policy.getLastLoginTimeAttribute() != null) && 761 (policy.getLastLoginTimeFormat() != null))) && 762 ((DirectoryServer.getWritabilityMode() == WritabilityMode.DISABLED) || 763 (backend.getWritabilityMode() == WritabilityMode.DISABLED))) 764 { 765 // This policy isn't applicable to root users, so if it's a root 766 // user then ignore it. 767 if (! DirectoryServer.isRootDN(userEntry.getName())) 768 { 769 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 770 ERR_BIND_OPERATION_WRITABILITY_DISABLED.get(userEntry.getName())); 771 } 772 } 773 774 // Check to see if the authentication must be done in a secure 775 // manner. If so, then the client connection must be secure. 776 if (policy.isRequireSecureAuthentication() 777 && !clientConnection.isSecure()) 778 { 779 boolean isSASLBind = saslHandler != null; 780 if (isSASLBind) 781 { 782 if (! saslHandler.isSecure(saslMechanism)) 783 { 784 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 785 ERR_BIND_OPERATION_INSECURE_SASL_BIND.get(saslMechanism, userEntry.getName())); 786 } 787 } 788 else 789 { 790 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 791 ERR_BIND_OPERATION_INSECURE_SIMPLE_BIND.get()); 792 } 793 } 794 } 795 796 /** 797 * Perform policy checks for accounts when the credentials are correct. 798 * 799 * @param userEntry 800 * The entry for the user that is authenticating. 801 * @param saslHandler 802 * The SASL mechanism handler if this is a SASL bind, or {@code null} 803 * for a simple bind. 804 * @throws DirectoryException 805 * If a problem occurs that should cause the bind to fail. 806 */ 807 private void checkVerifiedPasswordPolicyState( 808 Entry userEntry, SASLMechanismHandler<?> saslHandler) 809 throws DirectoryException 810 { 811 PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState; 812 PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy(); 813 814 // Check to see if the user is administratively disabled or locked. 815 if (pwPolicyState.isDisabled()) 816 { 817 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 818 ERR_BIND_OPERATION_ACCOUNT_DISABLED.get()); 819 } 820 else if (pwPolicyState.isAccountExpired()) 821 { 822 LocalizableMessage m = ERR_BIND_OPERATION_ACCOUNT_EXPIRED.get(); 823 pwPolicyState.generateAccountStatusNotification( 824 AccountStatusNotificationType.ACCOUNT_EXPIRED, userEntry, m, 825 AccountStatusNotification.createProperties(pwPolicyState, 826 false, -1, null, null)); 827 828 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 829 } 830 else if (pwPolicyState.lockedDueToFailures()) 831 { 832 if (pwPolicyErrorType == null) 833 { 834 pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; 835 } 836 837 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 838 ERR_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED.get()); 839 } 840 else if (pwPolicyState.lockedDueToIdleInterval()) 841 { 842 if (pwPolicyErrorType == null) 843 { 844 pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; 845 } 846 847 LocalizableMessage m = ERR_BIND_OPERATION_ACCOUNT_IDLE_LOCKED.get(); 848 pwPolicyState.generateAccountStatusNotification( 849 AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, userEntry, m, 850 AccountStatusNotification.createProperties(pwPolicyState, false, -1, 851 null, null)); 852 853 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 854 } 855 856 // If it's a simple bind, or if it's a password-based SASL bind, then 857 // perform a number of password-based checks. 858 boolean isSASLBind = saslHandler != null; 859 if (!isSASLBind || saslHandler.isPasswordBased(saslMechanism)) 860 { 861 // Check to see if the account is locked due to the maximum reset age. 862 if (pwPolicyState.lockedDueToMaximumResetAge()) 863 { 864 if (pwPolicyErrorType == null) 865 { 866 pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; 867 } 868 869 LocalizableMessage m = ERR_BIND_OPERATION_ACCOUNT_RESET_LOCKED.get(); 870 pwPolicyState.generateAccountStatusNotification( 871 AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, userEntry, m, 872 AccountStatusNotification.createProperties(pwPolicyState, false, 873 -1, null, null)); 874 875 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 876 } 877 878 // Determine whether the password is expired, or whether the user 879 // should be warned about an upcoming expiration. 880 if (pwPolicyState.isPasswordExpired()) 881 { 882 if (pwPolicyErrorType == null) 883 { 884 pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED; 885 } 886 887 int maxGraceLogins = policy.getGraceLoginCount(); 888 if (maxGraceLogins > 0 && pwPolicyState.mayUseGraceLogin()) 889 { 890 List<Long> graceLoginTimes = pwPolicyState.getGraceLoginTimes(); 891 if (graceLoginTimes == null || 892 graceLoginTimes.size() < maxGraceLogins) 893 { 894 isGraceLogin = true; 895 mustChangePassword = true; 896 897 if (pwPolicyWarningType == null) 898 { 899 pwPolicyWarningType = 900 PasswordPolicyWarningType.GRACE_LOGINS_REMAINING; 901 pwPolicyWarningValue = maxGraceLogins - 902 (graceLoginTimes.size() + 1); 903 } 904 } 905 else 906 { 907 LocalizableMessage m = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get(); 908 909 pwPolicyState.generateAccountStatusNotification( 910 AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry, m, 911 AccountStatusNotification.createProperties(pwPolicyState, 912 false, -1, null, 913 null)); 914 915 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 916 } 917 } 918 else 919 { 920 LocalizableMessage m = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get(); 921 922 pwPolicyState.generateAccountStatusNotification( 923 AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry, m, 924 AccountStatusNotification.createProperties(pwPolicyState, false, 925 -1, null, null)); 926 927 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 928 } 929 } 930 else if (pwPolicyState.shouldWarn()) 931 { 932 int numSeconds = pwPolicyState.getSecondsUntilExpiration(); 933 934 if (pwPolicyWarningType == null) 935 { 936 pwPolicyWarningType = PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION; 937 pwPolicyWarningValue = numSeconds; 938 } 939 940 isFirstWarning = pwPolicyState.isFirstWarning(); 941 } 942 943 // Check to see if the user's password has been reset. 944 if (pwPolicyState.mustChangePassword()) 945 { 946 mustChangePassword = true; 947 948 if (pwPolicyErrorType == null) 949 { 950 pwPolicyErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET; 951 } 952 } 953 } 954 } 955 956 /** 957 * Sets resource limits for the authenticated user. 958 * 959 * @param userEntry The entry for the authenticated user. 960 */ 961 private void setResourceLimits(Entry userEntry) 962 { 963 // See if the user's entry contains a custom size limit. 964 Integer customSizeLimit = 965 getIntegerUserAttribute(userEntry, OP_ATTR_USER_SIZE_LIMIT, 966 WARN_BIND_MULTIPLE_USER_SIZE_LIMITS, 967 WARN_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT); 968 if (customSizeLimit != null) 969 { 970 sizeLimit = customSizeLimit; 971 } 972 973 // See if the user's entry contains a custom time limit. 974 Integer customTimeLimit = 975 getIntegerUserAttribute(userEntry, OP_ATTR_USER_TIME_LIMIT, 976 WARN_BIND_MULTIPLE_USER_TIME_LIMITS, 977 WARN_BIND_CANNOT_PROCESS_USER_TIME_LIMIT); 978 if (customTimeLimit != null) 979 { 980 timeLimit = customTimeLimit; 981 } 982 983 // See if the user's entry contains a custom idle time limit. 984 // idleTimeLimit = 1000L * Long.parseLong(v.toString()); 985 Integer customIdleTimeLimitInSec = 986 getIntegerUserAttribute(userEntry, OP_ATTR_USER_IDLE_TIME_LIMIT, 987 WARN_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS, 988 WARN_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT); 989 if (customIdleTimeLimitInSec != null) 990 { 991 idleTimeLimit = 1000L * customIdleTimeLimitInSec; 992 } 993 994 // See if the user's entry contains a custom lookthrough limit. 995 Integer customLookthroughLimit = 996 getIntegerUserAttribute(userEntry, OP_ATTR_USER_LOOKTHROUGH_LIMIT, 997 WARN_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS, 998 WARN_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT); 999 if (customLookthroughLimit != null) 1000 { 1001 lookthroughLimit = customLookthroughLimit; 1002 } 1003 } 1004 1005 private Integer getIntegerUserAttribute(Entry userEntry, 1006 String attributeTypeName, 1007 Arg1<Object> nonUniqueAttributeMessage, 1008 Arg2<Object, Object> cannotProcessAttributeMessage) 1009 { 1010 AttributeType attrType = DirectoryServer.getAttributeType(attributeTypeName); 1011 List<Attribute> attrList = userEntry.getAttribute(attrType); 1012 if (attrList.size() == 1) 1013 { 1014 Attribute a = attrList.get(0); 1015 if (a.size() == 1) 1016 { 1017 ByteString v = a.iterator().next(); 1018 try 1019 { 1020 return Integer.valueOf(v.toString()); 1021 } 1022 catch (Exception e) 1023 { 1024 logger.traceException(e); 1025 logger.error(cannotProcessAttributeMessage.get(v, userEntry.getName())); 1026 } 1027 } 1028 else if (a.size() > 1) 1029 { 1030 logger.error(nonUniqueAttributeMessage.get(userEntry.getName())); 1031 } 1032 } 1033 return null; 1034 } 1035}