/*
 * 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.pulsar.broker.authorization;

import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import java.util.HashSet;
import org.apache.pulsar.broker.PulsarServerException;
import org.apache.pulsar.broker.ServiceConfiguration;
import org.apache.pulsar.common.naming.NamespaceName;
import org.apache.pulsar.common.naming.TopicName;
import org.apache.pulsar.common.policies.data.NamespaceOperation;
import org.apache.pulsar.common.policies.data.PolicyName;
import org.apache.pulsar.common.policies.data.PolicyOperation;
import org.apache.pulsar.common.policies.data.TenantOperation;
import org.apache.pulsar.common.policies.data.TopicOperation;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class AuthorizationServiceTest {

    AuthorizationService authorizationService;

    @BeforeClass
    void beforeClass() throws PulsarServerException {
        ServiceConfiguration conf = new ServiceConfiguration();
        conf.setAuthorizationEnabled(true);
        // Consider both of these proxy roles to make testing more comprehensive
        HashSet<String> proxyRoles = new HashSet<>();
        proxyRoles.add("pass.proxy");
        proxyRoles.add("fail.proxy");
        conf.setProxyRoles(proxyRoles);
        conf.setAuthorizationProvider(MockAuthorizationProvider.class.getName());
        authorizationService = new AuthorizationService(conf, null);
    }

    /**
     * See {@link MockAuthorizationProvider} for the implementation of the mock authorization provider.
     */
    @DataProvider(name = "roles")
    public Object[][] encryptionProvider() {
        return new Object[][]{
                // Schema: role, originalRole, whether authorization should pass

                // Client conditions where original role isn't passed or is blank
                {"pass.client", null, Boolean.TRUE},
                {"pass.client", " ", Boolean.TRUE},
                {"fail.client", null, Boolean.FALSE},
                {"fail.client", " ", Boolean.FALSE},

                // Proxy conditions where original role isn't passed or is blank
                {"pass.proxy", null, Boolean.FALSE},
                {"pass.proxy", " ", Boolean.FALSE},
                {"fail.proxy", null, Boolean.FALSE},
                {"fail.proxy", " ", Boolean.FALSE},

                // Normal proxy and client conditions
                {"pass.proxy", "pass.client", Boolean.TRUE},
                {"pass.proxy", "fail.client", Boolean.FALSE},
                {"fail.proxy", "pass.client", Boolean.FALSE},
                {"fail.proxy", "fail.client", Boolean.FALSE},

                // Not proxy with original principal
                {"pass.not-proxy", "pass.client", Boolean.FALSE}, // non proxy role can't pass original role
                {"pass.not-proxy", "fail.client", Boolean.FALSE},
                {"fail.not-proxy", "pass.client", Boolean.FALSE},
                {"fail.not-proxy", "fail.client", Boolean.FALSE},

                // Covers an unlikely scenario, but valid in the context of this test
                {null, "pass.proxy", Boolean.FALSE},
        };
    }

    private void checkResult(boolean expected, boolean actual) {
        if (expected) {
            assertTrue(actual);
        } else {
            assertFalse(actual);
        }
    }

    @Test(dataProvider = "roles")
    public void testAllowTenantOperationAsync(String role, String originalRole, boolean shouldPass) throws Exception {
        boolean isAuthorized = authorizationService.allowTenantOperationAsync("tenant",
                TenantOperation.DELETE_NAMESPACE, originalRole, role, null).get();
        checkResult(shouldPass, isAuthorized);
    }

    @Test(dataProvider = "roles")
    public void testNamespaceOperationAsync(String role, String originalRole, boolean shouldPass) throws Exception {
        boolean isAuthorized = authorizationService.allowNamespaceOperationAsync(NamespaceName.get("public/default"),
                NamespaceOperation.PACKAGES, originalRole, role, null).get();
        checkResult(shouldPass, isAuthorized);
    }

    @Test(dataProvider = "roles")
    public void testTopicOperationAsync(String role, String originalRole, boolean shouldPass) throws Exception {
        boolean isAuthorized = authorizationService.allowTopicOperationAsync(TopicName.get("topic"),
                TopicOperation.PRODUCE, originalRole, role, null).get();
        checkResult(shouldPass, isAuthorized);
    }

    @Test(dataProvider = "roles")
    public void testNamespacePolicyOperationAsync(String role, String originalRole, boolean shouldPass)
            throws Exception {
        boolean isAuthorized = authorizationService.allowNamespacePolicyOperationAsync(
                NamespaceName.get("public/default"), PolicyName.ALL, PolicyOperation.READ, originalRole, role, null)
                .get();
        checkResult(shouldPass, isAuthorized);
    }

    @Test(dataProvider = "roles")
    public void testTopicPolicyOperationAsync(String role, String originalRole, boolean shouldPass) throws Exception {
        boolean isAuthorized = authorizationService.allowTopicPolicyOperationAsync(TopicName.get("topic"),
                PolicyName.ALL, PolicyOperation.READ, originalRole, role, null).get();
        checkResult(shouldPass, isAuthorized);
    }
}
