org.jasig.cas.authentication.support.DefaultAccountStateHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.cas.authentication.support.DefaultAccountStateHandler.java

Source

/*
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo 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.authentication.support;

import org.jasig.cas.DefaultMessageDescriptor;
import org.jasig.cas.MessageDescriptor;
import org.jasig.cas.authentication.AccountDisabledException;
import org.jasig.cas.authentication.AccountPasswordMustChangeException;
import org.jasig.cas.authentication.InvalidLoginLocationException;
import org.jasig.cas.authentication.InvalidLoginTimeException;
import org.joda.time.Days;
import org.joda.time.Instant;
import org.ldaptive.auth.AccountState;
import org.ldaptive.auth.AuthenticationResponse;
import org.ldaptive.auth.ext.ActiveDirectoryAccountState;
import org.ldaptive.auth.ext.EDirectoryAccountState;
import org.ldaptive.auth.ext.PasswordExpirationAccountState;
import org.ldaptive.control.PasswordPolicyControl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.security.auth.login.AccountExpiredException;
import javax.security.auth.login.AccountLockedException;
import javax.security.auth.login.CredentialExpiredException;
import javax.security.auth.login.LoginException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Default account state handler.
 *
 * @author Marvin S. Addison
 * @since 4.0.0
 */
@Component("accountStateHandler")
public class DefaultAccountStateHandler implements AccountStateHandler {
    /** Map of account state error to CAS authentication exception. */
    protected final Map<AccountState.Error, LoginException> errorMap;

    /** Logger instance. */
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * Instantiates a new account state handler, that populates
     * the error map with LDAP error codes and corresponding exceptions.
     */
    public DefaultAccountStateHandler() {
        this.errorMap = new HashMap<>();
        this.errorMap.put(ActiveDirectoryAccountState.Error.ACCOUNT_DISABLED, new AccountDisabledException());
        this.errorMap.put(ActiveDirectoryAccountState.Error.ACCOUNT_LOCKED_OUT, new AccountLockedException());
        this.errorMap.put(ActiveDirectoryAccountState.Error.INVALID_LOGON_HOURS, new InvalidLoginTimeException());
        this.errorMap.put(ActiveDirectoryAccountState.Error.INVALID_WORKSTATION,
                new InvalidLoginLocationException());
        this.errorMap.put(ActiveDirectoryAccountState.Error.PASSWORD_MUST_CHANGE,
                new AccountPasswordMustChangeException());
        this.errorMap.put(ActiveDirectoryAccountState.Error.PASSWORD_EXPIRED, new CredentialExpiredException());
        this.errorMap.put(EDirectoryAccountState.Error.ACCOUNT_EXPIRED, new AccountExpiredException());
        this.errorMap.put(EDirectoryAccountState.Error.LOGIN_LOCKOUT, new AccountLockedException());
        this.errorMap.put(EDirectoryAccountState.Error.LOGIN_TIME_LIMITED, new InvalidLoginTimeException());
        this.errorMap.put(EDirectoryAccountState.Error.PASSWORD_EXPIRED, new CredentialExpiredException());
        this.errorMap.put(PasswordExpirationAccountState.Error.PASSWORD_EXPIRED, new CredentialExpiredException());
        this.errorMap.put(PasswordPolicyControl.Error.ACCOUNT_LOCKED, new AccountLockedException());
        this.errorMap.put(PasswordPolicyControl.Error.PASSWORD_EXPIRED, new CredentialExpiredException());
        this.errorMap.put(PasswordPolicyControl.Error.CHANGE_AFTER_RESET, new CredentialExpiredException());
    }

    @Override
    public List<MessageDescriptor> handle(final AuthenticationResponse response,
            final LdapPasswordPolicyConfiguration configuration) throws LoginException {

        final AccountState state = response.getAccountState();
        if (state == null) {
            logger.debug("Account state not defined. Returning empty list of messages.");
            return Collections.emptyList();
        }
        final List<MessageDescriptor> messages = new ArrayList<>();
        handleError(state.getError(), response, configuration, messages);
        handleWarning(state.getWarning(), response, configuration, messages);

        return messages;
    }

    /**
     * Handle an account state error produced by ldaptive account state machinery.
     * <p>
     * Override this method to provide custom error handling.
     *
     * @param error Account state error.
     * @param response Ldaptive authentication response.
     * @param configuration Password policy configuration.
     * @param messages Container for messages produced by account state error handling.
     *
     * @throws LoginException On errors that should be communicated as login exceptions.
     */
    protected void handleError(final AccountState.Error error, final AuthenticationResponse response,
            final LdapPasswordPolicyConfiguration configuration, final List<MessageDescriptor> messages)
            throws LoginException {

        logger.debug("Handling error {}", error);
        final LoginException ex = this.errorMap.get(error);
        if (ex != null) {
            throw ex;
        }
        logger.debug("No LDAP error mapping defined for {}", error);
    }

    /**
     * Handle an account state warning produced by ldaptive account state machinery.
     * <p>
     * Override this method to provide custom warning message handling.
     *
     * @param warning the account state warning messages.
     * @param response Ldaptive authentication response.
     * @param configuration Password policy configuration.
     * @param messages Container for messages produced by account state warning handling.
     */
    protected void handleWarning(final AccountState.Warning warning, final AuthenticationResponse response,
            final LdapPasswordPolicyConfiguration configuration, final List<MessageDescriptor> messages) {

        logger.debug("Handling warning {}", warning);
        if (warning == null) {
            logger.debug("Account state warning not defined");
            return;
        }

        final Calendar expDate = warning.getExpiration();
        final Days ttl = Days.daysBetween(Instant.now(), new Instant(expDate));
        logger.debug("Password expires in {} days. Expiration warning threshold is {} days.", ttl.getDays(),
                configuration.getPasswordWarningNumberOfDays());
        if (configuration.isAlwaysDisplayPasswordExpirationWarning()
                || ttl.getDays() < configuration.getPasswordWarningNumberOfDays()) {
            messages.add(new PasswordExpiringWarningMessageDescriptor(
                    "Password expires in {0} days. Please change your password at <href=\"{1}\">{1}</a>",
                    ttl.getDays(), configuration.getPasswordPolicyUrl()));
        }
        if (warning.getLoginsRemaining() > 0) {
            messages.add(new DefaultMessageDescriptor("password.expiration.loginsRemaining",
                    "You have {0} logins remaining before you MUST change your password.",
                    warning.getLoginsRemaining()));

        }
    }
}