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