Java tutorial
/* * Licensed to Jasig under one or more contributor license * agreements. See the NOTICE file distributed with this work * for additional information regarding copyright ownership. * Jasig licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a * copy of the License at the following location: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jasig.cas.adaptors.ldap.lppe; import java.util.Collections; import java.util.LinkedList; import java.util.List; import javax.security.auth.login.AccountLockedException; import javax.security.auth.login.CredentialExpiredException; import javax.security.auth.login.FailedLoginException; import javax.security.auth.login.LoginException; import javax.validation.constraints.NotNull; import org.jasig.cas.Message; import org.jasig.cas.authentication.AccountDisabledException; import org.jasig.cas.authentication.AccountPasswordMustChangeException; import org.jasig.cas.authentication.BasicCredentialMetaData; import org.jasig.cas.authentication.HandlerResult; import org.jasig.cas.authentication.LdapAuthenticationHandler; import org.jasig.cas.authentication.UsernamePasswordCredential; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Days; import org.ldaptive.LdapEntry; import org.ldaptive.auth.AuthenticationResponse; import org.ldaptive.auth.Authenticator; /** * An extension of the {@link LdapAuthenticationHandler} that is aware of * the underlying password policy and can translate errors and warnings * back to the CAS flow. * @author Misagh Moayyed * @since 4.0 */ public class LPPEAuthenticationHandler extends LdapAuthenticationHandler { /** The ldap configuration constructed based on given the policy. **/ private final PasswordPolicyConfiguration configuration; public LPPEAuthenticationHandler(@NotNull final Authenticator authenticator, @NotNull final PasswordPolicyConfiguration configuration) { super(authenticator); this.configuration = configuration; } /** * {@inheritDoc} * <p>Builds the {@link PasswordPolicyConfiguration} defined, examines the account status * for locked, disabled, expired, etc accounts and validates the password expiration policy. * If the policy cannot be built, account status matches one of the defined failure conditions * or the password policy expiration fails and the configuration is set as critical, * the authentication will fail. otherwise a warning is issued and the flow. * is resumed. * @see PasswordPolicyConfiguration#setCritical(boolean) * @see #examineAccountStatus(AuthenticationResponse) * @see #validateAccountPasswordExpirationPolicy() */ @Override protected final HandlerResult doPostAuthentication(final UsernamePasswordCredential credential, final AuthenticationResponse response) throws LoginException { final LdapEntry entry = response.getLdapEntry(); final PasswordPolicyResult result = configuration.build(entry); if (result == null) { throw new FailedLoginException("LPPE authentication failed. Configuration cannot be determined."); } this.examineAccountStatus(response, result); final List<Message> warnings = this.validateAccountPasswordExpirationPolicy(result); return new HandlerResult(this, new BasicCredentialMetaData(credential), super.createPrincipal(credential.getUsername(), entry), warnings); } /** * Examine the account status based on custom attributes defined (if any), * to determine whether the account status matches the following cases. * <ul> * <li>Disabled: {@link AccountDisabledException}</li> * <li>Locked: {@link AccountLockedException}</li> * <li>Expired: {@link CredentialExpiredException}</li> * <li>Password Must Change: {@link AccountPasswordMustChangeException}</li> * </ul> * @param response the ldaptive authentication response. * @throws LoginException if the above conditions match, an instance of LoginException * mapped to the error is thrown. */ protected void examineAccountStatus(final AuthenticationResponse response, final PasswordPolicyResult result) throws LoginException { final String uid = result.getDn(); if (result.isAccountExpired()) { final String msg = String.format("Account %s has expired", uid); throw new CredentialExpiredException(msg); } if (result.isAccountDisabled()) { final String msg = String.format("Account %s is disabled", uid); throw new AccountDisabledException(msg); } if (result.isAccountLocked()) { final String msg = String.format("Account %s is locked", uid); throw new AccountLockedException(msg); } if (result.isAccountPasswordMustChange()) { final String msg = String.format("Account %s must change it password", uid); throw new AccountPasswordMustChangeException(msg); } } @Override protected void initializeInternal() { populatePrincipalAttributeMap(); } /** * Populate configured custom attributes automatically to be returned * as part of the authentication. This is a facility to provide easier and reduced * configuration. */ private void populatePrincipalAttributeMap() { principalAttributeMap.putAll(configuration.getPasswordPolicyAttributesMap()); } /** * Calculates the number of days left to the expiration date. * @return Number of days left to the expiration date or -1 if the no expiration warning is * calculated based on the defined policy. */ protected int getDaysToExpirationDate(final DateTime expireDate, final PasswordPolicyResult result) throws LoginException { final DateTimeZone timezone = configuration.getDateConverter().getTimeZone(); final DateTime currentTime = new DateTime(timezone); logger.debug("Current date is {}. Expiration date is {}", currentTime, expireDate); final Days d = Days.daysBetween(currentTime, expireDate); int daysToExpirationDate = d.getDays(); logger.debug("[{}] days left to the expiration date.", daysToExpirationDate); if (expireDate.equals(currentTime) || expireDate.isBefore(currentTime)) { final String msgToLog = String.format("Password expiration date %s is on/before the current time %s.", daysToExpirationDate, currentTime); logger.debug(msgToLog); throw new CredentialExpiredException(msgToLog); } // Warning period begins from X number of days before the expiration date final DateTime warnPeriod = new DateTime(DateTime.parse(expireDate.toString()), timezone) .minusDays(result.getPasswordWarningNumberOfDays()); logger.debug("Warning period begins on [{}]", warnPeriod); if (configuration.isAlwaysDisplayPasswordExpirationWarning()) { logger.debug("Warning all. The password for [{}] will expire in [{}] day(s).", result.getDn(), daysToExpirationDate); } else if (currentTime.equals(warnPeriod) || currentTime.isAfter(warnPeriod)) { logger.debug("Password will expire in [{}] day(s)", daysToExpirationDate); } else { logger.debug("Password is not expiring. [{}] day(s) left to the warning.", daysToExpirationDate); daysToExpirationDate = -1; } return daysToExpirationDate; } /** * Determines the expiration date to use based on the password policy configuration. * Converts the password expiration date based on the * {@link PasswordPolicyConfiguration#setDateConverter(LdapDateConverter)} and returns * that value is the policy is set to evaluate against a static password expiration date. * Otherwise, adds {@link PasswordPolicyConfiguration#getValidPasswordNumberOfDays()} days * and returns the expiration date. * @return the configured expiration date to use. */ private DateTime getExpirationDateToUse(final PasswordPolicyResult result) { final DateTime dateValue = result.getPasswordExpirationDateTime(); if (configuration.getStaticPasswordExpirationDate() == null) { final DateTime expireDate = dateValue.plusDays(result.getValidPasswordNumberOfDays()); logger.debug( "Retrieved date value [{}] for date attribute [{}] and added {} valid days. " + "The final expiration date is [{}]", dateValue, configuration.getPasswordExpirationDateAttributeName(), result.getValidPasswordNumberOfDays(), expireDate); return expireDate; } return dateValue; } /** * Validate the account password expiration policy based on results collected. * @param result the policy result object * @return List of warnings * @throws LoginException if the account has already expired. */ private List<Message> validateAccountPasswordExpirationPolicy(final PasswordPolicyResult result) throws LoginException { if (result.isAccountPasswordSetToNeverExpire()) { logger.debug("Account password will never expire. Skipping password policy..."); return Collections.emptyList(); } final DateTime expireTime = getExpirationDateToUse(result); final List<Message> warnings = new LinkedList<Message>(); if (configuration.getStaticPasswordExpirationDate() != null && (expireTime.equals(configuration.getStaticPasswordExpirationDate()) || expireTime.isAfter(configuration.getStaticPasswordExpirationDate()))) { final String msg = String.format("Account password has expired beyond the static expiration date [{}]", configuration.getStaticPasswordExpirationDate()); logger.debug(msg); throw new CredentialExpiredException(msg); } final int days = getDaysToExpirationDate(expireTime, result); if (days != -1) { final String msg = String.format("Password expires in [%d] days", days); logger.debug(msg); warnings.add(new AccountPasswordExpiringMessage(msg, days)); } return warnings; } }