/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2015, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * 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.jboss.as.test.integration.security.jaspi;

import java.net.URL;
import javax.servlet.http.HttpServletResponse;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.as.arquillian.api.ServerSetup;
import org.jboss.as.security.Constants;
import org.jboss.as.test.integration.security.common.AbstractSecurityDomainsServerSetupTask;
import org.jboss.as.test.integration.security.common.SecurityTestConstants;
import org.jboss.as.test.integration.security.common.Utils;
import org.jboss.as.test.integration.security.common.config.AuthnModule;
import org.jboss.as.test.integration.security.common.config.JaspiAuthn;
import org.jboss.as.test.integration.security.common.config.LoginModuleStack;
import org.jboss.as.test.integration.security.common.config.SecurityDomain;
import org.jboss.as.test.integration.security.common.config.SecurityModule;
import org.jboss.as.web.security.jaspi.WebJASPIAuthenticator;
import org.jboss.logging.Logger;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Test case for JASPI security context propagation. It tests whether security context is correctly created as well as whether
 * expected roles are added for authenticated user.
 *
 * @author olukas
 */
@RunWith(Arquillian.class)
@ServerSetup({JaspiSecurityContextPropagationTestCase.SecurityDomainsSetup.class})
@RunAsClient
public class JaspiSecurityContextPropagationTestCase {

    private static final Logger LOGGER = Logger.getLogger(JaspiSecurityContextPropagationTestCase.class);

    private static final String ADMIN_SECURITY_DOMAIN = "AdminSecurityDomain";
    private static final String USER_SECURITY_DOMAIN = "UserSecurityDomain";
    private static final String ADMIN_USER_SECURITY_DOMAIN = "AdminUserSecurityDomain";

    /**
     * Test which using JASPI Auth module which passing authentication and adding admin role for user. That means that only
     * access to EJB is allowed. For that reason HttpClient should ends with status code 403 since access to servlet is denied.
     *
     * @param url
     * @throws Exception
     */
    @OperateOnDeployment(ADMIN_SECURITY_DOMAIN)
    @Test
    public void testAccessOnlyForEjbIsAllowed(@ArquillianResource URL url) throws Exception {
        URL servletUrl = new URL(url.toExternalForm() + CallEjbSecuredServlet.SERVLET_PATH.substring(1));
        Utils.makeCall(servletUrl.toURI(), HttpServletResponse.SC_FORBIDDEN);
    }

    /**
     * Test which using JASPI Auth module which passing authentication and adding user role for user. That means that only
     * access to Servlet is allowed. For that reason invocation of EJB should thrown exception since access to EJB is denied.
     *
     * @param url
     * @throws Exception
     */
    @OperateOnDeployment(USER_SECURITY_DOMAIN)
    @Test
    public void testAccessOnlyForServletIsAllowed(@ArquillianResource URL url) throws Exception {
        URL servletUrl = new URL(url.toExternalForm() + CallEjbSecuredServlet.SERVLET_PATH.substring(1));
        String servletOutput = Utils.makeCall(servletUrl.toURI(), 200);
        Assert.assertEquals("Access to EJB was not denied for user without needed role.", CallEjbSecuredServlet.EXCEPTION_CAUGHT,
                servletOutput);
    }

    /**
     * Test which using JASPI Auth module which passing authentication and adding user and admin roles for user. That means that
     * access to both Servlet and EJB is allowed. For that reason HttpClient should ends with Servlet page which correctly
     * displays output of EJB method.
     *
     * @param url
     * @throws Exception
     */
    @Ignore("https://bugzilla.redhat.com/show_bug.cgi?id=1245353")
    @OperateOnDeployment(ADMIN_USER_SECURITY_DOMAIN)
    @Test
    public void testAccessForServletAndEjbIsAllowed(@ArquillianResource URL url) throws Exception {

        URL servletUrl = new URL(url.toExternalForm() + CallEjbSecuredServlet.SERVLET_PATH.substring(1));
        String servletOutput = Utils.makeCall(servletUrl.toURI(), 200);
        Assert.assertEquals("Access to EJB was denied. Needed role was not added to user.", HelloBeanImpl.SAY_HELLO,
                servletOutput);
    }

    @Deployment(name = ADMIN_SECURITY_DOMAIN)
    public static WebArchive adminDeployment() {
        return createWar(ADMIN_SECURITY_DOMAIN, AdminSimpleJaspiAuthModule.class);
    }

    @Deployment(name = USER_SECURITY_DOMAIN)
    public static WebArchive userDeployment() {
        return createWar(USER_SECURITY_DOMAIN, UserSimpleJaspiAuthModule.class);
    }

    @Deployment(name = ADMIN_USER_SECURITY_DOMAIN)
    public static WebArchive adminUserDeployment() {
        return createWar(ADMIN_USER_SECURITY_DOMAIN, AdminUserSimpleJaspiAuthModule.class);
    }

    /**
     * Creates WAR which has configured given security domain and it's name is the same name as the domain name (with ".war"
     * suffix).
     *
     * @param securityDomainName
     * @return a new WebArchive
     */
    private static WebArchive createWar(final String securityDomainName, Class jaspiAuthModule) {
        final String archiveName = securityDomainName + ".war";
        LOGGER.info("Creating " + archiveName);
        final WebArchive war = ShrinkWrap.create(WebArchive.class, archiveName);
        war.addClasses(jaspiAuthModule,
                AbstractSimpleJaspiAuthModule.class,
                HelloBean.class,
                HelloBeanImpl.class,
                CallEjbSecuredServlet.class
        );
        war.addAsWebInfResource(new StringAsset(SecurityTestConstants.WEB_XML_BASIC_AUTHN), "web.xml");
        war.addAsWebInfResource(new StringAsset("<jboss-web>" + //
                "<security-domain>" + securityDomainName + "</security-domain>" + //
                "<valve><class-name>" + WebJASPIAuthenticator.class.getName() + "</class-name></valve>" + //
                "</jboss-web>"), "jboss-web.xml");
        war.addAsWebInfResource(Utils.getJBossEjb3XmlAsset(securityDomainName), "jboss-ejb3.xml");
        return war;
    }

    static class SecurityDomainsSetup extends AbstractSecurityDomainsServerSetupTask {

        @Override
        protected SecurityDomain[] getSecurityDomains() throws Exception {

            final LoginModuleStack lmStack = new LoginModuleStack.Builder().name("lmStack")
                    .loginModules(new SecurityModule.Builder().name("UsersRoles").build()).build();

            final SecurityDomain sd1 = new SecurityDomain.Builder()
                    .name(ADMIN_SECURITY_DOMAIN)
                    .jaspiAuthn(
                            new JaspiAuthn.Builder()
                            .loginModuleStacks(lmStack)
                            .authnModules(new AuthnModule.Builder()
                                    .name(AdminSimpleJaspiAuthModule.class.getName())
                                    .flag(Constants.REQUIRED)
                                    .build())
                            .build())
                    .build();

            final SecurityDomain sd2 = new SecurityDomain.Builder()
                    .name(USER_SECURITY_DOMAIN)
                    .jaspiAuthn(
                            new JaspiAuthn.Builder()
                            .loginModuleStacks(lmStack)
                            .authnModules(new AuthnModule.Builder()
                                    .name(UserSimpleJaspiAuthModule.class.getName())
                                    .flag(Constants.REQUIRED)
                                    .build())
                            .build())
                    .build();

            final SecurityDomain sd3 = new SecurityDomain.Builder()
                    .name(ADMIN_USER_SECURITY_DOMAIN)
                    .jaspiAuthn(
                            new JaspiAuthn.Builder()
                            .loginModuleStacks(lmStack)
                            .authnModules(new AuthnModule.Builder()
                                    .name(AdminUserSimpleJaspiAuthModule.class.getName())
                                    .flag(Constants.REQUIRED)
                                    .build())
                            .build())
                    .build();

            return new SecurityDomain[]{sd1, sd2, sd3};

        }

    }
}
