/**
 * 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;

import org.apache.openejb.assembler.classic.Assembler;
import org.apache.openejb.client.JNDIContext;
import org.apache.openejb.client.RemoteInitialContextFactory;
import org.apache.openejb.config.ConfigurationFactory;
import org.apache.openejb.config.DeploymentsResolver;
import org.apache.openejb.core.ServerFederation;
import org.apache.openejb.jee.EjbJar;
import org.apache.openejb.jee.StatelessBean;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.server.ServiceDaemon;
import org.apache.openejb.server.ejbd.EjbServer;
import org.apache.openejb.spi.SecurityService;
import org.apache.openejb.testng.PropertiesBuilder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.ejb.EJBException;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import java.util.Map;
import java.util.Properties;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

public class AuthentWithRequestTest {

    @BeforeClass
    public static void initJAAS() {
        System.setProperty("java.security.auth.login.config", AuthentWithRequestTest.class.getResource("/login.config").getFile());
    }

    @AfterClass
    public static void resetJAAS() {
        System.clearProperty("java.security.auth.login.config");
    }

    @Test
    public void invoke() throws Exception {
        final EjbServer ejbServer = new EjbServer();

        OpenEJB.init(new PropertiesBuilder().p(DeploymentsResolver.DEPLOYMENTS_CLASSPATH_PROPERTY, "false").build(), new ServerFederation());
        ejbServer.init(new Properties());

        final ServiceDaemon serviceDaemon = new ServiceDaemon(ejbServer, 0, "localhost");
        serviceDaemon.start();

        final int port = serviceDaemon.getPort();

        final Assembler assembler = SystemInstance.get().getComponent(Assembler.class);
        final ConfigurationFactory config = new ConfigurationFactory();

        final EjbJar ejbJar = new EjbJar();
        ejbJar.addEnterpriseBean(new StatelessBean(RemoteWithSecurity.class));
        assembler.createApplication(config.configureApplication(ejbJar));

        try {
            { // ok case
                final Context context = new InitialContext(new PropertiesBuilder()
                        .p(Context.INITIAL_CONTEXT_FACTORY, RemoteInitialContextFactory.class.getName())
                        .p(Context.PROVIDER_URL, "ejbd://127.0.0.1:" + port)
                        .p(JNDIContext.AUTHENTICATE_WITH_THE_REQUEST, "true")
                        .p("java.naming.security.principal", "foo")
                        .p("java.naming.security.credentials", "bar")
                        .p("openejb.authentication.realmName", "LM")
                        .build());
                final AnInterfaceRemote client = AnInterfaceRemote.class.cast(context.lookup("RemoteWithSecurityRemote"));
                assertNotNull(client);

                assertEquals("foo", client.call());
            }

            {// now the failing case
                final Context context = new InitialContext(new PropertiesBuilder()
                        .p(Context.INITIAL_CONTEXT_FACTORY, RemoteInitialContextFactory.class.getName())
                        .p(Context.PROVIDER_URL, "ejbd://127.0.0.1:" + port)
                        .p(JNDIContext.AUTHENTICATE_WITH_THE_REQUEST, "true")
                        .p("java.naming.security.principal", "wrong")
                        .p("java.naming.security.credentials", "wrong")
                        .p("openejb.authentication.realmName", "LM")
                        .build());
                final AnInterfaceRemote client = AnInterfaceRemote.class.cast(context.lookup("RemoteWithSecurityRemote"));
                try {
                    client.call();
                } catch (final EJBException e) {
                    if (!LoginException.class.isInstance(e.getCause())) {
                        e.printStackTrace();
                    }
                    assertTrue(LoginException.class.isInstance(e.getCause()));
                }
            }
        } finally {
            serviceDaemon.stop();
            OpenEJB.destroy();
        }
    }

    @Remote
    public static interface AnInterfaceRemote {

        String call();
    }

    @Stateless
    public static class RemoteWithSecurity implements AnInterfaceRemote {

        private static ThreadLocal<String> name = new ThreadLocal<String>();

        @Override
        public String call() {
            return name.get();
        }
    }

    public static class MyLoginModule implements LoginModule {

        private CallbackHandler callbackHandler;

        @Override
        public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
            this.callbackHandler = callbackHandler;
        }

        @Override
        public boolean login() throws LoginException {
            assertNull(SystemInstance.get().getComponent(SecurityService.class).currentState()); // check the user was not logged at lookup()

            final NameCallback nameCallback = new NameCallback("name?", "dummy");
            try {
                callbackHandler.handle(new Callback[]{nameCallback});
            } catch (final Exception e) {
                throw new LoginException(e.getMessage());
            }
            if (!"foo".equals(nameCallback.getName())) {
                throw new IllegalArgumentException("Not an Error/assert cause in java 9 jaas doesnt capture it anymore");
            }
            RemoteWithSecurity.name.set(nameCallback.getName());
            return true;
        }

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

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

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