/*
 * Copyright (C) 2013 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 javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletRequest;

import java.lang.reflect.Method;

import org.exoplatform.container.component.ComponentRequestLifecycle;
import org.exoplatform.container.component.RequestLifeCycle;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.services.organization.User;
import org.exoplatform.services.organization.UserHandler;
import org.exoplatform.services.organization.UserStatus;
import org.exoplatform.services.security.jaas.AbstractLoginModule;

public class FilterDisabledLoginModule extends AbstractLoginModule {

    private static final Log log = ExoLogger.getLogger(FilterDisabledLoginModule.class);

    /** JACC get context method. */
    private static Method getContextMethod;

    public static final String DISABLED_USER_NAME = "_disabledUserName";

    static {
        try {
            Class<?> policyContextClass = Thread.currentThread().getContextClassLoader()
                    .loadClass("javax.security.jacc.PolicyContext");
            getContextMethod = policyContextClass.getDeclaredMethod("getContext", String.class);
        } catch (ClassNotFoundException ignore) {
            log.debug("JACC not found ignoring it", ignore);
        } catch (Exception e) {
            log.error("Could not obtain JACC get context method", e);
        }
    }

    @Override
    public boolean login() throws LoginException {
        log.debug("In login of FilterDisabledLoginModule.");

        try {
            Callback[] callbacks = new Callback[] { new NameCallback("Username") };
            callbackHandler.handle(callbacks);

            String username = ((NameCallback) callbacks[0]).getName();
            if (username != null) {
                OrganizationService service = (OrganizationService) getContainer().getComponentInstanceOfType(
                        OrganizationService.class);

                try {
                    begin(service);
                    UserHandler uHandler = service.getUserHandler();
                    User user = uHandler.findUserByName(username, UserStatus.ANY);

                    if (user == null) {
                        log.debug("user {0} doesn't exists. FilterDisabledLoginModule will be ignored.", username);
                    } else if (!user.isEnabled()) {
                        HttpServletRequest request = getCurrentHttpServletRequest();
                        if (request != null) {
                            request.setAttribute(DISABLED_USER_NAME, username);
                        }

                        throw new LoginException("Can't authenticate. user " + username + " is disabled");
                    }
                } finally {
                    end(service);
                }
            } else {
                log.debug("No username has been committed. FilterDisabledLoginModule will be ignored.");
            }

            return true;
        } catch (final Exception e) {
            log.warn(e.getMessage());
            throw new LoginException(e.getMessage());
        }
    }

    protected HttpServletRequest getCurrentHttpServletRequest() {
        HttpServletRequest request = null;

        // JBoss way
        if (getContextMethod != null) {
            try {
                request = (HttpServletRequest) getContextMethod.invoke(null, "javax.servlet.http.HttpServletRequest");
            } catch (Throwable e) {
                log.error("LoginModule error. Turn off session credentials checking with proper configuration option of "
                        + "LoginModule set to false");
                log.error(this, e);
            }
        } else {
            // Tomcat way (Assumed that ServletAccessValve has been configured in context.xml)
            try {
                // TODO: improve this
                Class<?> clazz = Thread.currentThread().getContextClassLoader()
                        .loadClass("org.gatein.sso.agent.tomcat.ServletAccess");
                Method getRequestMethod = clazz.getDeclaredMethod("getRequest");
                request = (HttpServletRequest) getRequestMethod.invoke(null);
            } catch (Exception e) {
                log.error("Unexpected exception when trying to obtain HttpServletRequest from ServletAccess thread-local", e);
            }
        }

        log.trace("Returning HttpServletRequest {0}", request);
        return request;
    }

    @Override
    public boolean commit() throws LoginException {
        return true;
    }

    @Override
    public boolean abort() throws LoginException {
        return true;
    }

    @Override
    public boolean logout() throws LoginException {
        return true;
    }

    @Override
    protected Log getLogger() {
        return log;
    }

    private void begin(OrganizationService orgService) {
        if (orgService instanceof ComponentRequestLifecycle) {
            RequestLifeCycle.begin((ComponentRequestLifecycle) orgService);
        }
    }

    private void end(OrganizationService orgService) {
        if (orgService instanceof ComponentRequestLifecycle) {
            RequestLifeCycle.end();
        }
    }
}
