org.wso2.carbon.identity.application.authentication.framework.AbstractLocalApplicationAuthenticator.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.identity.application.authentication.framework.AbstractLocalApplicationAuthenticator.java

Source

/*
 *  Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. 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
 *
 *  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.wso2.carbon.identity.application.authentication.framework;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.application.authentication.framework.config.ConfigurationFacade;
import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.AuthenticationFailedException;
import org.wso2.carbon.identity.application.authentication.framework.exception.LogoutFailedException;
import org.wso2.carbon.identity.application.authentication.framework.internal.FrameworkServiceDataHolder;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants;
import org.wso2.carbon.identity.core.model.IdentityErrorMsgContext;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.identity.event.IdentityEventConstants;
import org.wso2.carbon.identity.event.IdentityEventException;
import org.wso2.carbon.identity.event.event.Event;
import org.wso2.carbon.identity.event.services.IdentityEventService;
import org.wso2.carbon.user.api.UserRealm;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.core.UserCoreConstants;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * This is the super class to fire the account lock event for all authenticators which are not federated
 * authenticator and are not use user-store to authenticate the user.
 */
public abstract class AbstractLocalApplicationAuthenticator extends AbstractApplicationAuthenticator
        implements ApplicationAuthenticator {

    private static final long serialVersionUID = -4406878411547612129L;
    private static final Log log = LogFactory.getLog(AbstractLocalApplicationAuthenticator.class);

    @Override
    public AuthenticatorFlowStatus process(HttpServletRequest request, HttpServletResponse response,
            AuthenticationContext context) throws AuthenticationFailedException, LogoutFailedException {

        // if an authentication flow
        if (!context.isLogoutRequest()) {
            if (!canHandle(request)
                    || Boolean.TRUE.equals(request.getAttribute(FrameworkConstants.REQ_ATTR_HANDLED))) {
                context.setRetrying(false);
                return initiateAuthenticationFlow(request, response, context);
            } else {
                try {
                    fireEvent(context, IdentityEventConstants.Event.PRE_AUTHENTICATION, false);
                    processAuthenticationResponse(request, response, context);
                    if (this instanceof LocalApplicationAuthenticator
                            && !context.getSequenceConfig().getApplicationConfig().isSaaSApp()) {
                        validateNonSaasAppLogin(context);
                    }
                    request.setAttribute(FrameworkConstants.REQ_ATTR_HANDLED, true);
                    context.setProperty(FrameworkConstants.LAST_FAILED_AUTHENTICATOR, null);
                    fireEvent(context, IdentityEventConstants.Event.POST_AUTHENTICATION, true);
                    return AuthenticatorFlowStatus.SUCCESS_COMPLETED;
                } catch (AuthenticationFailedException e) {
                    if (isAccountLocked(context)) {
                        try {
                            String redirectUrl = getRedirectUrlOnAccountLock(context, response);
                            response.sendRedirect(redirectUrl);
                        } catch (IOException e1) {
                            throw new AuthenticationFailedException(" Error while redirecting to the retry page ",
                                    e1);
                        }
                        return AuthenticatorFlowStatus.INCOMPLETE;
                    }
                    fireEvent(context, IdentityEventConstants.Event.POST_AUTHENTICATION, false);
                    request.setAttribute(FrameworkConstants.REQ_ATTR_HANDLED, true);
                    // Decide whether we need to redirect to the login page to retry authentication.
                    return handleRetryOnFailure(request, response, context, e);
                }
            }
            // else a logout flow
        } else {
            return processLogoutFlow(request, response, context);
        }
    }

    /**
     * To decide whether need to redirect the user to login page to retry authentication.
     *
     * @param request  the httpServletRequest
     * @param response the httpServletResponse
     * @param context  the authentication context
     * @param e        the authentication failed exception
     * @return authentication flow status
     * @throws AuthenticationFailedException the exception in the authentication flow
     */
    protected AuthenticatorFlowStatus handleRetryOnFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationContext context, AuthenticationFailedException e) throws AuthenticationFailedException {

        boolean sendToMultiOptionPage = isStepHasMultiOption(context) && isRedirectToMultiOptionPageOnFailure();
        if (retryAuthenticationEnabled(context) && !sendToMultiOptionPage) {
            // The Authenticator will re-initiate the authentication and retry.
            context.setRetrying(true);
            return initiateAuthenticationFlow(request, response, context);
        } else {
            context.setProperty(FrameworkConstants.LAST_FAILED_AUTHENTICATOR, getName());
            /*
            By throwing this exception step handler will redirect to multi options page if
            multi-option are available in the step.
             */
            throw e;
        }
    }

    /**
     * To check whether user domain and tenant domain equal for non SaaS application.
     *
     * @param context the authentication context
     * @throws AuthenticationFailedException the exception in the authentication flow
     */
    protected void validateNonSaasAppLogin(AuthenticationContext context) throws AuthenticationFailedException {

        String userTenantDomain = context.getSubject().getTenantDomain();
        String spTenantDomain = context.getTenantDomain();
        if (!StringUtils.equals(userTenantDomain, spTenantDomain)) {
            context.setProperty(FrameworkConstants.USER_TENANT_DOMAIN_MISMATCH, true);
            throw new AuthenticationFailedException("Service Provider tenant domain must be "
                    + "equal to user tenant domain for non-SaaS applications", context.getSubject());
        }
    }

    /**
     * To process the authentication failed flow
     *
     * @param request  the httpServletRequest
     * @param response the httpServletResponse
     * @param context  the authentication context
     * @return authentication flow status
     * @throws AuthenticationFailedException the exception in the authentication flow
     */
    protected AuthenticatorFlowStatus initiateAuthenticationFlow(HttpServletRequest request,
            HttpServletResponse response, AuthenticationContext context) throws AuthenticationFailedException {

        if (getName().equals(context.getProperty(FrameworkConstants.LAST_FAILED_AUTHENTICATOR))) {
            context.setRetrying(true);
        }
        initiateAuthenticationRequest(request, response, context);
        context.setCurrentAuthenticator(getName());
        return AuthenticatorFlowStatus.INCOMPLETE;
    }

    /**
     * To check whether the user's account is being already locked or not.
     *
     * @param context  the authentication context
     * @return true or false
     * @throws AuthenticationFailedException the exception in the authentication flow
     */
    protected boolean isAccountLocked(AuthenticationContext context) throws AuthenticationFailedException {

        String errorCode = getErrorCode();
        if (StringUtils.isNotEmpty(errorCode) && errorCode.equals(UserCoreConstants.ErrorCode.USER_IS_LOCKED)) {
            context.setRetrying(true);
            context.setCurrentAuthenticator(getName());
            return true;
        }
        return false;
    }

    /**
     * To get the redirect url when the user's account gets locked.
     *
     * @param context the authentication context
     * @param response the the httpServletResponse
     * @return redirect_url
     */

    protected String getRedirectUrlOnAccountLock(AuthenticationContext context, HttpServletResponse response) {

        String retryPage = ConfigurationFacade.getInstance().getAuthenticationEndpointRetryURL();
        String queryParams = context.getContextIdIncludedQueryParams();
        return response.encodeRedirectURL(retryPage + ("?" + queryParams)) + FrameworkConstants.STATUS_MSG
                + FrameworkConstants.ERROR_MSG + FrameworkConstants.STATUS + FrameworkConstants.ACCOUNT_LOCKED_MSG;
    }

    /**
     * To process the logout flow.
     *
     * @param request  the httpServletRequest
     * @param response the httpServletResponse
     * @param context  the authentication context
     * @return the authentication flow status
     * @throws LogoutFailedException the exception in logout flow
     */
    protected AuthenticatorFlowStatus processLogoutFlow(HttpServletRequest request, HttpServletResponse response,
            AuthenticationContext context) throws LogoutFailedException {

        try {
            if (!canHandle(request)) {
                context.setCurrentAuthenticator(getName());
                initiateLogoutRequest(request, response, context);
                return AuthenticatorFlowStatus.INCOMPLETE;
            } else {
                processLogoutResponse(request, response, context);
                return AuthenticatorFlowStatus.SUCCESS_COMPLETED;
            }
        } catch (UnsupportedOperationException e) {
            if (log.isDebugEnabled()) {
                log.debug("Ignoring UnsupportedOperationException.", e);
            }
            return AuthenticatorFlowStatus.SUCCESS_COMPLETED;
        }
    }

    /**
     * To fire the events for account locking.
     *
     * @param context         the authentication context
     * @param eventName       the event name
     * @param operationStatus the success or failure status
     * @throws AuthenticationFailedException the exception in the authentication flow
     */
    private void fireEvent(AuthenticationContext context, String eventName, boolean operationStatus)
            throws AuthenticationFailedException {

        if (eventFiringEnabledForAccountLocking()) {
            IdentityEventService eventService = FrameworkServiceDataHolder.getInstance().getIdentityEventService();
            try {
                Map<String, Object> eventProperties = new HashMap<>();
                String userName = (String) context.getProperty(FrameworkConstants.USERNAME);
                String tenantAwareUsername = MultitenantUtils.getTenantAwareUsername(userName);
                String tenantDomain = context.getTenantDomain();
                int tenantID = IdentityTenantUtil.getTenantId(tenantDomain);
                RealmService realmService = FrameworkServiceDataHolder.getInstance().getRealmService();
                UserRealm userRealm = realmService.getTenantUserRealm(tenantID);
                eventProperties.put(IdentityEventConstants.EventProperty.USER_NAME, tenantAwareUsername);
                eventProperties.put(IdentityEventConstants.EventProperty.USER_STORE_MANAGER,
                        userRealm.getUserStoreManager());
                eventProperties.put(IdentityEventConstants.EventProperty.TENANT_DOMAIN, tenantDomain);
                eventProperties.put(IdentityEventConstants.EventProperty.OPERATION_STATUS, operationStatus);
                Event event = new Event(eventName, eventProperties);
                eventService.handleEvent(event);

            } catch (UserStoreException e) {
                throw new AuthenticationFailedException(" Error in accessing user store ", e);
            } catch (IdentityEventException e) {
                throw new AuthenticationFailedException(" Error while firing the events ", e);
            }
        }
    }

    /**
     * To get account lock error code from identity error message context.
     *
     * @return the error code
     */
    private String getErrorCode() {

        String errorCode = null;
        IdentityErrorMsgContext errorContext = IdentityUtil.getIdentityErrorMsg();
        IdentityUtil.clearIdentityErrorMsg();
        if (errorContext != null && errorContext.getErrorCode() != null) {
            if (log.isDebugEnabled()) {
                log.debug("Retrieving error code " + errorContext.getErrorCode() + " from identity error message "
                        + "context ");
            }
            errorCode = errorContext.getErrorCode();
        }
        return errorCode;
    }

    /**
     * To check whether the event need to fire for account locking or not.
     *
     * @return true or false
     */
    protected boolean eventFiringEnabledForAccountLocking() {

        return false;
    }
}