/*
 * Copyright (C) 2003-2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.exoplatform.web.login;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.web.AbstractHttpServlet;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.web.security.AuthenticationRegistry;
import org.exoplatform.web.security.security.AbstractTokenService;
import org.exoplatform.web.security.security.CookieTokenService;
import org.exoplatform.web.security.sso.SSOHelper;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;
import org.gatein.wci.ServletContainer;
import org.gatein.wci.ServletContainerFactory;
import org.gatein.wci.authentication.AuthenticationEvent;
import org.gatein.wci.authentication.AuthenticationEventType;
import org.gatein.wci.authentication.AuthenticationException;
import org.gatein.wci.authentication.AuthenticationListener;
import org.gatein.wci.security.Credentials;

/**
 * The login servlet which proceeds as.
 *
 * 0. If user is already authenticated : nothing happens 1. When username and password are provided, a login is attempted 1.1 if
 * login is successful the user is authenticated 1.2 if login fails the user is not authenticated and the login form is
 * displayed with the user name filled 2. When username is provided, the login form is displayed with the user name filled 3.
 * Finally if nothing is provided the login form is displayed
 *
 * @author <a href="mailto:alain.defrance@exoplatform.com">Alain Defrance</a>
 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
 * @version $Revision$
 */
public class LoginServlet extends AbstractHttpServlet {

    /**
     *
     */
    private static final long serialVersionUID = -1330051083735349589L;

    /** . */
    private static final int UNAUTHENTICATED = 0;

    /** . */
    private static final int AUTHENTICATED = 1;

    /** . */
    private static final int FAILED = 2;

    /** . */
    private static final Logger log = LoggerFactory.getLogger(LoginServlet.class);

    /** . */
    public static final String COOKIE_NAME = "rememberme";

    public static final String OAUTH_COOKIE_NAME = "oauth_rememberme";

    //value of this field need equals with org.gatein.security.oauth.common.OAuthConstants.ATTRIBUTE_REMEMBER_ME
    public static final String SESSION_ATTRIBUTE_REMEMBER_ME = "_rememberme";

    /**
     * Register WCI authentication listener, which is used to bind credentials to temporary authentication registry after each
     * successful login
     *
     * @param config
     * @throws ServletException
     */
    @Override
    protected void afterInit(ServletConfig config) throws ServletException {
        ServletContainerFactory.getServletContainer().addAuthenticationListener(new AuthenticationListener() {
            @Override
            public void onEvent(AuthenticationEvent event) {
                if (event.getType() == AuthenticationEventType.LOGIN) {
                    bindCredentialsToAuthenticationRegistry(getContainer(), event.getRequest(), event.getCredentials());
                }
            }
        });
    }

    /**
     * Extract the remember me token from the request or returns null.
     *
     * @param req the incoming request
     * @return the token
     */
    public static String getRememberMeTokenCookie(HttpServletRequest req) {
        Cookie[] cookies = req.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (COOKIE_NAME.equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    public static String getOauthRememberMeTokenCookie (HttpServletRequest req) {
        Cookie[] cookies = req.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (OAUTH_COOKIE_NAME.equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            // We set the character encoding now to UTF-8 before obtaining parameters
            req.setCharacterEncoding("UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("Encoding not supported", e);
        }

        String username = req.getParameter("username");
        String password = req.getParameter("password");

        //
        int status;
        if (req.getRemoteUser() == null) {
            if (username != null && password != null) {
                Credentials credentials = new Credentials(username, password);
                ServletContainer container = ServletContainerFactory.getServletContainer();

                // This will login or send an AuthenticationException
                try {
                    container.login(req, resp, credentials);
                } catch (AuthenticationException e) {
                    log.debug("User authentication failed");
                    if (log.isTraceEnabled()) {
                        log.trace(e.getMessage(), e);
                    }
                }

                //
                status = req.getRemoteUser() != null ? AUTHENTICATED : FAILED;

                // If we are authenticated
                if (status == AUTHENTICATED) {
                    if (log.isTraceEnabled()) {
                        log.trace("User authenticated successfuly through WCI. Will redirect to initialURI");
                    }

                    // Handle remember me
                    String rememberme = req.getParameter("rememberme");
                    if ("true".equals(rememberme)) {
                        // Create token for credentials
                        CookieTokenService tokenService = AbstractTokenService.getInstance(CookieTokenService.class);
                        String cookieToken = tokenService.createToken(credentials);
                        if (log.isDebugEnabled()) {
                            log.debug("Found a remember me request parameter, created a persistent token " + cookieToken
                                    + " for it and set it up " + "in the next response");
                        }
                        Cookie cookie = new Cookie(COOKIE_NAME, cookieToken);
                        cookie.setPath(req.getContextPath());
                        cookie.setHttpOnly(true);
                        cookie.setMaxAge((int) tokenService.getValidityTime());
                        resp.addCookie(cookie);

                    } else {

                        //Handle oauth remember me
                        if("true".equals(req.getSession().getAttribute(SESSION_ATTRIBUTE_REMEMBER_ME))) {
                            CookieTokenService tokenService = AbstractTokenService.getInstance(CookieTokenService.class);
                            String cookieToken = tokenService.createToken(credentials);
                            Cookie cookie = new Cookie(OAUTH_COOKIE_NAME, cookieToken);
                            cookie.setPath(req.getContextPath());
                            cookie.setMaxAge((int) tokenService.getValidityTime());
                            resp.addCookie(cookie);

                            req.getSession().removeAttribute(SESSION_ATTRIBUTE_REMEMBER_ME);
                        }
                    }
                }
            } else {
                log.debug("username or password not provided. Changing status to UNAUTHENTICATED");
                status = UNAUTHENTICATED;
            }
        } else {
            log.debug("User already authenticated. Will redirect to initialURI");
            status = AUTHENTICATED;
        }

        // Obtain initial URI
        String initialURI = req.getParameter("initialURI");

        // Otherwise compute one
        if (initialURI == null || initialURI.length() == 0) {
            initialURI = req.getContextPath();
            log.debug("No initial URI found, will use default " + initialURI + " instead ");
        } else {
            log.debug("Found initial URI " + initialURI);
        }

        try {
            URI uri = new URI(initialURI);
            if (uri.isAbsolute() && !(uri.getHost().equals(req.getServerName()))) {
                log.warn("Cannot redirect to an URI outside of the current host when using a login redirect. Redirecting to the portal context path instead.");
                initialURI = req.getContextPath();
            }
        } catch (URISyntaxException e) {
            log.warn("Initial URI in login link is malformed. Redirecting to the portal context path instead.");
            initialURI = req.getContextPath();
        }

        // Redirect to initialURI
        if (status == AUTHENTICATED) {
            // Response may be already committed in case of SAML or other SSO providers
            if (!resp.isCommitted()) {
                resp.sendRedirect(resp.encodeRedirectURL(initialURI));
            }
        } else {
            if (status == FAILED) {
                req.setAttribute("org.gatein.portal.login.error", "whatever");
            }

            // Show login form or redirect to SSO url (/portal/sso) if SSO is enabled
            req.setAttribute("org.gatein.portal.login.initial_uri", initialURI);
            SSOHelper ssoHelper = getContainer().getComponentInstanceOfType(SSOHelper.class);

            String disabledUser = (String)req.getAttribute(FilterDisabledLoginModule.DISABLED_USER_NAME);
            boolean meetDisabledUser = disabledUser != null;
            if(ssoHelper.skipJSPRedirection() && meetDisabledUser) {
                resp.setContentType("text/html; charset=UTF-8");
                getServletContext().getRequestDispatcher("/login/jsp/disabled.jsp").include(req, resp);
            } else if (ssoHelper.skipJSPRedirection()) {
                String ssoRedirectUrl = req.getContextPath() + ssoHelper.getSSORedirectURLSuffix();
                ssoRedirectUrl = resp.encodeRedirectURL(ssoRedirectUrl);
                if (log.isTraceEnabled()) {
                    log.trace("Redirected to SSO login URL: " + ssoRedirectUrl);
                }
                resp.sendRedirect(ssoRedirectUrl);
            } else {
                StringBuilder loginPath = new StringBuilder("/login/jsp/login.jsp");
                if (meetDisabledUser) {
                    String errorData = meetDisabledUser ? new LoginError(LoginError.DISABLED_USER_ERROR, disabledUser).toString() : "";
                    loginPath.append("?").append(LoginError.ERROR_PARAM).append("=").append(URLEncoder.encode(errorData, "UTF-8"));
                }

                resp.setContentType("text/html; charset=UTF-8");
                getServletContext().getRequestDispatcher(loginPath.toString()).include(req, resp);
            }
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }

    /**
     * Add credentials to {@link ConversationState}.
     *
     * @param credentials the credentials
     */
    private static void bindCredentialsToAuthenticationRegistry(ExoContainer exoContainer, HttpServletRequest req,
            Credentials credentials) {
        AuthenticationRegistry authRegistry = (AuthenticationRegistry) exoContainer
                .getComponentInstanceOfType(AuthenticationRegistry.class);
        if (log.isTraceEnabled()) {
            log.trace("Binding credentials to temporary authentication registry for user " + credentials.getUsername());
        }
        authRegistry.setCredentials(req, credentials);
    }
}
