/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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.apache.openejb.core.security;

import junit.framework.TestCase;
import org.apache.openejb.OpenEJB;
import org.apache.openejb.assembler.classic.Assembler;
import org.apache.openejb.assembler.classic.EjbJarInfo;
import org.apache.openejb.assembler.classic.ProxyFactoryInfo;
import org.apache.openejb.assembler.classic.SecurityServiceInfo;
import org.apache.openejb.assembler.classic.StatelessSessionContainerInfo;
import org.apache.openejb.assembler.classic.TransactionServiceInfo;
import org.apache.openejb.config.ConfigurationFactory;
import org.apache.openejb.core.LocalInitialContextFactory;
import org.apache.openejb.jee.EjbJar;
import org.apache.openejb.jee.StatelessBean;
import org.junit.AfterClass;

import javax.annotation.Resource;
import javax.annotation.security.DeclareRoles;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.annotation.security.RunAs;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.util.Properties;

/**
 * @version $Rev$ $Date$
 */
public class SecurityTest extends TestCase {

    @AfterClass
    public static void afterClass() throws Exception {
        OpenEJB.destroy();
    }

    private Assembler configureAssembler(final String defaultUser) throws Exception {
        System.setProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY, LocalInitialContextFactory.class.getName());

        final ConfigurationFactory config = new ConfigurationFactory();
        final Assembler assembler = new Assembler();

        assembler.createProxyFactory(config.configureService(ProxyFactoryInfo.class));
        assembler.createTransactionManager(config.configureService(TransactionServiceInfo.class));

        final SecurityServiceInfo serviceInfo = new SecurityServiceInfo();
        serviceInfo.service = "SecurityService";
        serviceInfo.className = SecurityServiceImpl.class.getName();
        serviceInfo.id = "New Security Service";
        serviceInfo.properties = new Properties();
        if (defaultUser != null) {
            // override the default user
            serviceInfo.properties.setProperty("DefaultUser", defaultUser);

        }

        assembler.createSecurityService(serviceInfo);

        // containers
        assembler.createContainer(config.configureService(StatelessSessionContainerInfo.class));

        final EjbJar ejbJar = new EjbJar("SecurityTest");

        ejbJar.addEnterpriseBean(new StatelessBean(FooBean.class));
        ejbJar.addEnterpriseBean(new StatelessBean(BarBean.class));

        final EjbJarInfo ejbJarInfo = config.configureApplication(ejbJar);

        assembler.createApplication(ejbJarInfo);

        return assembler;
    }

    public void test() throws Exception {
        final Assembler assembler = configureAssembler(null);

        final Properties props = new Properties();
        props.setProperty(Context.SECURITY_PRINCIPAL, "jonathan");
        props.setProperty(Context.SECURITY_CREDENTIALS, "secret");

        final InitialContext ctx = new InitialContext(props);

        final Project foo = (Project) ctx.lookup("FooBeanLocal");

        foo.svnCheckout("");
        foo.svnCommit("");

        try {
            foo.deleteProject("");
            fail("Should not be allowed");
        } catch (final Exception e) {
            // good.
        }

        assertTrue("not in role committer", foo.isCallerInRole("committer"));
        assertTrue("not in role community", foo.isCallerInRole("community"));
        assertFalse("in role contributor", foo.isCallerInRole("contributor"));
        assertTrue("Caller is not jonathan", foo.isCaller("jonathan"));

        ctx.close();
        assembler.destroy();

        //        Project bar = (Project) ctx.lookup("BarBeanLocal");
        //
        //        bar.svnCheckout("");
        //
        //        try {
        //            bar.svnCommit("");
        //            fail("Should not be allowed");
        //        } catch (Exception e) {
        //            // good
        //        }
        //
        //        try {
        //            bar.deleteProject("");
        //            fail("Should not be allowed");
        //        } catch (Exception e) {
        //            // good.
        //        }
        //
        //        assertFalse("in role committer", bar.isCallerInRole("committer"));
        //        assertFalse("in role community", bar.isCallerInRole("community"));
        //        assertTrue("not in role contributor", bar.isCallerInRole("contributor"));
    }

    // When no credentials are provided, the default user/role should be "guest"
    public void testUnauthenticatedUser() throws Exception {
        final Assembler assembler = configureAssembler(null);

        // no credentials provided, the default user should be "guest"
        final Properties props = new Properties();

        final InitialContext ctx = new InitialContext(props);

        final Project foo = (Project) ctx.lookup("FooBeanLocal");

        foo.svnCheckout("");
        try {
            foo.svnCommit("");
            fail("Should not be allowed");
        } catch (final Exception e) {
            // good.
        }

        assertFalse("in role committer", foo.isCallerInRole("committer"));
        assertFalse("in role community", foo.isCallerInRole("community"));
        assertFalse("in role contributor", foo.isCallerInRole("contributor"));
        assertTrue("not in role guest", foo.isCallerInRole("guest"));
        assertTrue("Caller is not guest", foo.isCaller("guest"));

        ctx.close();
        assembler.destroy();
    }

    // Just to be sure we can override the default user (ie. guest)
    public void testDefaultUser() throws Exception {
        final Assembler assembler = configureAssembler("public");

        // no credentials provided, the default user should be "guest"
        final Properties props = new Properties();

        final InitialContext ctx = new InitialContext(props);

        final Project foo = (Project) ctx.lookup("FooBeanLocal");

        foo.svnCheckout("");
        try {
            foo.svnCommit("");
            fail("Should not be allowed");
        } catch (final Exception e) {
            // good.
        }

        assertFalse("in role committer", foo.isCallerInRole("committer"));
        assertFalse("in role community", foo.isCallerInRole("community"));
        assertFalse("in role contributor", foo.isCallerInRole("contributor"));
        assertFalse("in role guest", foo.isCallerInRole("guest"));
        assertTrue("Caller is not public", foo.isCaller("public"));

        ctx.close();
        assembler.destroy();
    }

    @Stateless
    @DeclareRoles({"committer", "contributor", "community", "guest", "public"})
    public static class FooBean implements Project {

        @Resource
        private SessionContext context;

        @Override
        @RolesAllowed({"committer"})
        public String svnCommit(final String s) {
            return s;
        }

        @Override
        @RolesAllowed({"committer", "contributor"})
        public String submitPatch(final String s) {
            return s;
        }

        @Override
        @PermitAll
        public String svnCheckout(final String s) {
            return s;
        }

        @Override
        @DenyAll
        public String deleteProject(final String s) {
            return s;
        }

        @Override
        public boolean isCallerInRole(final String role) {
            return context.isCallerInRole(role);
        }

        @Override
        public boolean isCaller(final String user) {
            return context.getCallerPrincipal().getName().equals(user);
        }
    }

    @Stateless
    @RunAs("contributor")
    @DeclareRoles({"committer", "contributor", "community"})
    public static class BarBean implements Project {

        @Resource
        private SessionContext context;

        @Override
        @RolesAllowed({"committer"})
        public String svnCommit(final String s) {
            return s;
        }

        @Override
        @RolesAllowed({"committer", "contributor"})
        public String submitPatch(final String s) {
            return s;
        }

        @Override
        @PermitAll
        public String svnCheckout(final String s) {
            return s;
        }

        @Override
        @DenyAll
        public String deleteProject(final String s) {
            return s;
        }

        @Override
        @PermitAll
        public boolean isCallerInRole(final String role) {
            return context.isCallerInRole(role);
        }

        @Override
        @PermitAll
        public boolean isCaller(final String user) {
            return context.getCallerPrincipal().getName().equals(user);
        }
    }

    public static interface Project {

        public String svnCommit(String s);

        public String submitPatch(String s);

        public String svnCheckout(String s);

        public String deleteProject(String s);

        public boolean isCaller(String s);

        public boolean isCallerInRole(String s);
    }
}
