/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * The OpenSearch Contributors require contributions made to
 * this file be licensed under the Apache-2.0 license or a
 * compatible open source license.
 */

/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.
 */

/*
 * Modifications Copyright OpenSearch Contributors. See
 * GitHub history for details.
 */

package org.opensearch.secure_sm;

import junit.framework.TestCase;

import java.security.Permission;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;

/** Simple tests for SecureSM */
public class SecureSMTests extends TestCase {
    static {
        // install a mock security policy:
        // AllPermission to source code
        // ThreadPermission not granted anywhere else
        final ProtectionDomain sourceCode = SecureSM.class.getProtectionDomain();
        Policy.setPolicy(new Policy() {
            @Override
            public boolean implies(ProtectionDomain domain, Permission permission) {
                if (domain == sourceCode) {
                    return true;
                } else if (permission instanceof ThreadPermission) {
                    return false;
                }
                return true;
            }
        });
        System.setSecurityManager(SecureSM.createTestSecureSM(Collections.emptySet()));
    }

    @SuppressForbidden(reason = "testing that System#exit is blocked")
    public void testTryToExit() {
        try {
            System.exit(1);
            fail("did not hit expected exception");
        } catch (SecurityException expected) {}
    }

    public void testClassCanExit() {
        assertTrue(SecureSM.classCanExit("org.apache.maven.surefire.booter.CommandReader", SecureSM.TEST_RUNNER_PACKAGES));
        assertTrue(SecureSM.classCanExit("com.carrotsearch.ant.tasks.junit4.slave.JvmExit", SecureSM.TEST_RUNNER_PACKAGES));
        assertTrue(SecureSM.classCanExit("org.eclipse.jdt.internal.junit.runner.RemoteTestRunner", SecureSM.TEST_RUNNER_PACKAGES));
        assertTrue(SecureSM.classCanExit("com.intellij.rt.execution.junit.JUnitStarter", SecureSM.TEST_RUNNER_PACKAGES));
        assertTrue(SecureSM.classCanExit("org.opensearch.Foo", new String[] { "org.opensearch.Foo" }));
        assertFalse(SecureSM.classCanExit("org.opensearch.Foo", new String[] { "org.opensearch.Bar" }));
    }

    public void testCreateThread() throws Exception {
        Thread t = new Thread();
        t.start();
        t.join();
        // no exception
    }

    public void testCreateThreadGroup() throws Exception {
        Thread t = new Thread(new ThreadGroup("childgroup"), "child");
        t.start();
        t.join();
        // no exception
    }

    public void testModifyChild() throws Exception {
        final AtomicBoolean interrupted = new AtomicBoolean(false);
        Thread t = new Thread(new ThreadGroup("childgroup"), "child") {
            @Override
            public void run() {
                try {
                    Thread.sleep(Long.MAX_VALUE);
                } catch (InterruptedException expected) {
                    interrupted.set(true);
                }
            }
        };
        t.start();
        t.interrupt();
        t.join();
        // no exception
        assertTrue(interrupted.get());
    }

    public void testNoModifySibling() throws Exception {
        final AtomicBoolean interrupted1 = new AtomicBoolean(false);
        final AtomicBoolean interrupted2 = new AtomicBoolean(false);

        final Thread t1 = new Thread(new ThreadGroup("childgroup"), "child") {
            @Override
            public void run() {
                try {
                    Thread.sleep(Long.MAX_VALUE);
                } catch (InterruptedException expected) {
                    interrupted1.set(true);
                }
            }
        };
        t1.start();

        Thread t2 = new Thread(new ThreadGroup("anothergroup"), "another child") {
            @Override
            public void run() {
                try {
                    Thread.sleep(Long.MAX_VALUE);
                } catch (InterruptedException expected) {
                    interrupted2.set(true);
                    try {
                        t1.interrupt(); // try to bogusly interrupt our sibling
                        fail("did not hit expected exception");
                    } catch (SecurityException expected2) {}
                }
            }
        };
        t2.start();
        t2.interrupt();
        t2.join();
        // sibling attempted to but was not able to muck with its other sibling
        assertTrue(interrupted2.get());
        assertFalse(interrupted1.get());
        // but we are the parent and can terminate
        t1.interrupt();
        t1.join();
        assertTrue(interrupted1.get());
    }
}
