/*
 * 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.jackrabbit.oak.run.osgi

import org.apache.felix.connect.launch.PojoServiceRegistry
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters
import org.apache.jackrabbit.oak.spi.security.Context
import org.apache.jackrabbit.oak.spi.security.SecurityProvider
import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenConfiguration
import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider
import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration
import org.apache.jackrabbit.oak.spi.security.user.AuthorizableNodeName
import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory
import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration
import org.apache.jackrabbit.oak.spi.security.user.action.AccessControlAction
import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableAction
import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableActionProvider
import org.junit.Before
import org.junit.Test
import org.osgi.framework.ServiceEvent
import org.osgi.framework.ServiceReference
import org.osgi.service.cm.ConfigurationAdmin


import static org.mockito.Mockito.mock
import static org.mockito.Mockito.when
import static org.osgi.service.component.runtime.ServiceComponentRuntime.*

class SecurityProviderRegistrationTest extends AbstractRepositoryFactoryTest {

    private PojoServiceRegistry registry;

    @Before
    public void initializeRegistry() {
        registry = repositoryFactory.initializeServiceRegistry(config)
    }

    @Override
    protected PojoServiceRegistry getRegistry() {
        return registry
    }
    /**
     * Test that, without any additional configuration, a SecurityProvider
     * service is registered by default.
     */
    @Test
    public void testDefaultSetup() {
        assert securityProviderServiceReferences != null
    }

    /**
     * A SecurityProvider shouldn't start without a required
     * AuthorizationConfiguration service.
     */
    @Test
    public void testRequiredAuthorizationConfigurationNotAvailable() {
        def m = mock(AuthorizationConfiguration)
        when(m.getParameters()).thenReturn(ConfigurationParameters.EMPTY)
        when(m.getContext()).thenReturn(Context.DEFAULT)

        testRequiredService(AuthorizationConfiguration, m)
    }

    /**
     * A SecurityProvider shouldn't start without a required
     * PrincipalConfiguration service.
     */
    @Test
    public void testRequiredPrincipalConfigurationNotAvailable() {
        def m = mock(PrincipalConfiguration)
        when(m.getParameters()).thenReturn(ConfigurationParameters.EMPTY)
        when(m.getContext()).thenReturn(Context.DEFAULT)

        testRequiredService(PrincipalConfiguration, m)
    }

    /**
     * A SecurityProvider shouldn't start without a required TokenConfiguration
     * service.
     */
    @Test
    public void testRequiredTokenConfigurationNotAvailable() {
        def m = mock(TokenConfiguration)
        when(m.getParameters()).thenReturn(ConfigurationParameters.EMPTY)
        when(m.getContext()).thenReturn(Context.DEFAULT)

        testRequiredService(TokenConfiguration, m)
    }

    /**
     * A SecurityProvider shouldn't start without a required
     * AuthorizableNodeName service.
     */
    @Test
    public void testRequiredAuthorizableNodeNameNotAvailable() {
        testRequiredService(AuthorizableNodeName, mock(AuthorizableNodeName))
    }

    /**
     * A SecurityProvider shouldn't start without a required
     * AuthorizableActionProvider service.
     */
    @Test
    public void testRequiredAuthorizableActionProviderNotAvailable() {
        testRequiredService(AuthorizableActionProvider, mock(AuthorizableActionProvider))
    }

    /**
     * A SecurityProvider shouldn't start without a required RestrictionProvider
     * service.
     */
    @Test
    public void testRequiredRestrictionProviderNotAvailable() {
        testRequiredService(RestrictionProvider, mock(RestrictionProvider))
    }

    /**
     * A SecurityProvider shouldn't start without a required
     * UserAuthenticationFactory service.
     */
    @Test
    public void testRequiredUserAuthenticationFactoryNotAvailable() {
        testRequiredService(UserAuthenticationFactory, mock(UserAuthenticationFactory))
    }

    /**
     * A SecurityProvider should be registered only if every every prerequisite
     * is satisfied.
     */
    @Test
    public void testMultipleRequiredServices() {

        // Set up the SecurityProvider to require 4 services
        awaitServiceEvent({
                    setRequiredServicePids(
                            "test.RequiredAuthorizationConfiguration",
                            "test.RequiredPrincipalConfiguration",
                            "test.RequiredTokenConfiguration",
                            "test.RestrictionProvider")
                },
                "(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)",
                ServiceEvent.UNREGISTERING
        )
        assert securityProviderServiceReferences == null

        // Start the services and verify that only at the end the
        // SecurityProvider registers itself
        def ac = mock(AuthorizationConfiguration)
        when(ac.getParameters()).thenReturn(ConfigurationParameters.EMPTY)
        when(ac.getContext()).thenReturn(Context.DEFAULT)

        registry.registerService(AuthorizationConfiguration.class.name, ac, dict("service.pid": "test.RequiredAuthorizationConfiguration"))
        assert securityProviderServiceReferences == null

        def pc = mock(PrincipalConfiguration)
        when(pc.getParameters()).thenReturn(ConfigurationParameters.EMPTY)
        when(pc.getContext()).thenReturn(Context.DEFAULT)

        registry.registerService(PrincipalConfiguration.class.name, pc, dict("service.pid": "test.RequiredPrincipalConfiguration"))
        assert securityProviderServiceReferences == null

        def tc = mock(TokenConfiguration)
        when(tc.getParameters()).thenReturn(ConfigurationParameters.EMPTY)
        when(tc.getContext()).thenReturn(Context.DEFAULT)

        registry.registerService(TokenConfiguration.class.name, tc, dict("service.pid": "test.RequiredTokenConfiguration"))
        assert securityProviderServiceReferences == null

        registry.registerService(RestrictionProvider.class.name, mock(RestrictionProvider), dict("service.pid": "test.RestrictionProvider"))
        assert securityProviderServiceReferences != null
    }

    @Test
    public void testSecurityConfigurations() {
        SecurityProvider securityProvider = registry.getService(registry.getServiceReference(SecurityProvider.class.name))
        assertAuthorizationConfig(securityProvider)
        assertUserConfig(securityProvider)

        //Keep a dummy reference to UserConfiguration such that SCR does not deactivate it
        //once SecurityProviderRegistration gets deactivated. Otherwise if SecurityProviderRegistration is
        //the only component that refers it then upon its deactivation UserConfiguration would also be
        //deactivate and its internal state would be reset
        UserConfiguration userConfiguration = getServiceWithWait(UserConfiguration.class)

        def authComponentName = 'org.apache.jackrabbit.oak.security.authentication.AuthenticationConfigurationImpl'

        // 1. Disable AuthenticationConfiguration such that SecurityProvider is unregistered
        awaitServiceEvent({
                    disableComponent(authComponentName)
                },
                '(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)',
                ServiceEvent.UNREGISTERING,
                5000
        )

        assert securityProviderServiceReferences == null

        // 2. Modify the config for AuthorizableActionProvider. It's expected that this config change is picked up
        awaitServiceEvent({
                    setConfiguration([
                            "org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider": [
                                    "groupPrivilegeNames":"jcr:read"
                            ]
                    ])
                },
                classNameFilter('org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider'),
                ServiceEvent.MODIFIED | ServiceEvent.REGISTERED
        )

        // 3. Enable component again such that SecurityProvider gets reactivated
        awaitServiceEvent({
                   enableComponent(authComponentName)
                },
                '(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)',
                ServiceEvent.REGISTERED
        )

        securityProvider = getServiceWithWait(SecurityProvider.class)
        assertAuthorizationConfig(securityProvider)
        assertUserConfig(securityProvider, "jcr:read")
    }

    @Test
    public void testSecurityConfigurations2() {
        SecurityProvider securityProvider = registry.getService(registry.getServiceReference(SecurityProvider.class.name))
        assertAuthorizationConfig(securityProvider)
        assertUserConfig(securityProvider)

        //Keep a dummy reference to UserConfiguration such that SCR does not deactivate it
        //once SecurityProviderRegistration gets deactivated. Otherwise if SecurityProviderRegistration is
        //the only component that refers it then upon its deactivation UserConfiguration would also be
        //deactivate and its internal state would be reset
        UserConfiguration userConfiguration = getServiceWithWait(UserConfiguration.class)

        //1. Modify the config for AuthorizableActionProvider. It's expected that this config change is picked up
        def servicePid = "org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider"
        awaitServiceEvent({
            setConfiguration([
                        (servicePid): [
                                "groupPrivilegeNames":"jcr:read"
                        ]
                ]
            )},
            "(service.pid=${servicePid})",
            ServiceEvent.MODIFIED | ServiceEvent.REGISTERED
        )

        securityProvider = getServiceWithWait(SecurityProvider.class)
        assertAuthorizationConfig(securityProvider)
        assertUserConfig(securityProvider, "jcr:read")

        String authComponentName = 'org.apache.jackrabbit.oak.security.authentication.AuthenticationConfigurationImpl'

        // 2. Disable AuthenticationConfiguration such that SecurityProvider is unregistered
        awaitServiceEvent({
                    disableComponent(authComponentName)
                },
                "(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)",
                ServiceEvent.UNREGISTERING
        )

        assert securityProviderServiceReferences == null

        // 3. Enable component again such that SecurityProvider gets reactivated
        awaitServiceEvent({
                    enableComponent(authComponentName)
                },
                "(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)",
                ServiceEvent.REGISTERED
        )

        securityProvider = getServiceWithWait(SecurityProvider.class)
        assertAuthorizationConfig(securityProvider)
        assertUserConfig(securityProvider, "jcr:read")
    }

    private <T> void testRequiredService(Class<T> serviceClass, T service) {

        // Adding a new precondition on a missing service PID forces the
        // SecurityProvider to unregister.

        awaitServiceEvent({
                    setRequiredServicePids("test.Required" + serviceClass.simpleName)
                },
                "(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)",
                ServiceEvent.UNREGISTERING
        )
        assert securityProviderServiceReferences == null

        // If a service is registered, and if the PID of the service matches the
        // precondition, the SecurityProvider is registered again.

        def registration;
        awaitServiceEvent({
                    registration = registry.registerService(serviceClass.name, service, dict("service.pid": "test.Required" + serviceClass.simpleName))
                },
                "(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)",
                ServiceEvent.REGISTERED
        )
        assert securityProviderServiceReferences != null

        // If the service is unregistered, but the precondition is still in
        // place, the SecurityProvider unregisters again.

        awaitServiceEvent({
                    registration.unregister()
                },
                "(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)",
                ServiceEvent.UNREGISTERING
        )
        assert securityProviderServiceReferences == null

        // Removing the precondition allows the SecurityProvider to register.
        awaitServiceEvent({
                    setRequiredServicePids()
                },
                "(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)",
                ServiceEvent.REGISTERED
        )
        assert securityProviderServiceReferences != null
    }

    private ServiceReference<?>[] getSecurityProviderServiceReferences() {
        return registry.getServiceReferences(SecurityProvider.class.name, "(type=default)")
    }

    private void setRequiredServicePids(String... pids) {
        setConfiguration([
                "org.apache.jackrabbit.oak.security.internal.SecurityProviderRegistration": [
                        "requiredServicePids": pids
                ]
        ])
    }

    private void setConfiguration(Map<String, Map<String, Object>> configuration) {
        getConfigurationInstaller().installConfigs(configuration)
    }

    private ConfigInstaller getConfigurationInstaller() {
        return new ConfigInstaller(getConfigurationAdmin(), registry.bundleContext)
    }

    private ConfigurationAdmin getConfigurationAdmin() {
        return registry.getService(registry.getServiceReference(ConfigurationAdmin.class.name)) as ConfigurationAdmin
    }

    private static <K, V> Dictionary<K, V> dict(Map<K, V> map) {
        return new Hashtable<K, V>(map);
    }

    private static void assertAuthorizationConfig(SecurityProvider securityProvider, String... adminPrincipals) {
        AuthorizationConfiguration ac = securityProvider.getConfiguration(AuthorizationConfiguration.class)

        assert ac.getParameters().containsKey("restrictionProvider")
        assert ac.getRestrictionProvider() != null
        assert ac.getParameters().get("restrictionProvider") == ac.getRestrictionProvider()

        String[] found = ac.getParameters().getConfigValue("administrativePrincipals", new String[0])
        assert Arrays.equals(adminPrincipals, found)
    }

    private static void assertUserConfig(SecurityProvider securityProvider, String... groupPrivilegeNames) {
        UserConfiguration uc = securityProvider.getConfiguration(UserConfiguration.class)

        assert uc.getParameters().containsKey("authorizableActionProvider")
        AuthorizableActionProvider ap = uc.getParameters().get("authorizableActionProvider")
        assert ap != null;

        List<AuthorizableAction> actionList = ap.getAuthorizableActions(securityProvider)
        assert actionList.size() == 1
        AuthorizableAction action = actionList.get(0)
        assert Arrays.equals(groupPrivilegeNames, ((AccessControlAction) action).groupPrivilegeNames)
    }

}
