{"record":{"id":"9995188f3d2876cd","repo":"spring-projects/spring-security","slug":"ctrl-geterrorstatus","errorCode":null,"errorMessage":"${ctrl.getErrorStatus()}","messagePattern":"\\$\\{ctrl\\.getErrorStatus\\(\\)\\}","errorType":"exception","errorClass":"PasswordPolicyException","httpStatus":null,"severity":"error","filePath":"ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyAwareContextSource.java","lineNumber":69,"sourceCode":"\t\t\treturn super.getContext(principal, credentials);\n\t\t}\n\t\tthis.logger.trace(LogMessage.format(\"Binding as %s, prior to reconnect as user %s\", getUserDn(), principal));\n\t\t// First bind as manager user before rebinding as the specific principal.\n\t\tLdapContext ctx = (LdapContext) super.getContext(getUserDn(), getPassword());\n\t\tControl[] rctls = { new PasswordPolicyControl(false) };\n\t\ttry {\n\t\t\tctx.addToEnvironment(Context.SECURITY_PRINCIPAL, principal);\n\t\t\tctx.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);\n\t\t\tctx.reconnect(rctls);\n\t\t}\n\t\tcatch (javax.naming.NamingException ex) {\n\t\t\tPasswordPolicyResponseControl ctrl = PasswordPolicyControlExtractor.extractControl(ctx);\n\t\t\tif (this.logger.isDebugEnabled()) {\n\t\t\t\tthis.logger.debug(LogMessage.format(\"Failed to bind with %s\", ctrl), ex);\n\t\t\t}\n\t\t\tLdapUtils.closeContext(ctx);\n\t\t\tif (ctrl != null && ctrl.isLocked() && ctrl.getErrorStatus() != null) {\n\t\t\t\tthrow new PasswordPolicyException(ctrl.getErrorStatus());\n\t\t\t}\n\t\t\tthrow LdapUtils.convertLdapException(ex);\n\t\t}\n\t\tthis.logger.debug(LogMessage.of(() -> \"Bound with \" + PasswordPolicyControlExtractor.extractControl(ctx)));\n\t\treturn ctx;\n\t}\n\n\t@Override\n\t@SuppressWarnings(\"unchecked\")\n\tprotected Hashtable getAuthenticatedEnv(String principal, String credentials) {\n\t\tHashtable<String, Object> env = super.getAuthenticatedEnv(principal, credentials);\n\t\tenv.put(LdapContext.CONTROL_FACTORIES, PasswordPolicyControlFactory.class.getName());\n\t\treturn env;\n\t}\n\n}\n","sourceCodeStart":51,"sourceCodeEnd":86,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyAwareContextSource.java#L51-L86","documentation":"PasswordPolicyAwareContextSource.getContext() consults the LDAP password-policy response control after a failed bind. If the control indicates the account is locked and carries an error status string, it throws PasswordPolicyException with the server's error status as the message; otherwise the original NamingException is converted normally.","triggerScenarios":"Calling getContext(username, password) (directly or via LDAP authentication using this ContextSource) where the bind fails, the server returns a PasswordPolicyResponseControl with isLocked()==true and non-null error status, e.g. 'account is locked' or 'password expired'.","commonSituations":"OpenLDAP/Oracle directory with ppolicy overlay locking accounts after failed attempts; authenticating a user whose password is expired per password policy; password must-change state preventing bind.","solutions":["Have the user's account unlocked / password changed according to the directory's password policy.","Read PasswordPolicyException.getMessage() — it contains the server's error status describing the exact policy violation.","Catch PasswordPolicyException to route users to unlock/reset flows instead of showing 'wrong password'.","Check ppolicy overlay configuration (lockout duration, pwdMaxAge) if lockouts occur unexpectedly."],"exampleFix":"// before\ncatch (BadCredentialsException e) { return \"login?error\"; }\n// after\ncatch (PasswordPolicyException e) { model.addAttribute(\"msg\", e.getMessage()); return \"redirect:/account/unlock\"; }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    DirContext ctx = passwordPolicyAwareContextSource.getContext(userDn, password);\n} catch (PasswordPolicyException e) {\n    // e.getMessage() contains the server error status (locked/expired)\n    model.addAttribute(\"policyMessage\", e.getMessage());\n}","preventionTips":["Read PasswordPolicyException.getMessage() for the exact policy violation.","Configure ppolicy lockout duration so lockouts self-clear.","Distinguish PasswordPolicyException from generic AuthenticationException in handlers.","Verify the directory returns the ppolicy response control (ppolicy overlay enabled)."],"tags":["ldap","password-policy","account-locked","spring-security"],"backgroundTag":"permission-denied","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}