// Copyright 2004 The Bazel Authors. All Rights Reserved.
//
// Licensed 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 com.google.testing.junit.runner.util;

import java.security.Permission;

/**
 * A security manager that prevents things that are dangerous or
 * bad in a testing environment. Currently prevents System.exit() and
 * System.setSecurityManager().
 *
 * <p>For simplicity this is a Java 1.1 style security manager, ignoring
 * the whole Permissions framework. This should be fine unless you
 * are testing code that itself manipulates SecurityManagers.
 */
public final class GoogleTestSecurityManager extends SecurityManager {
  private volatile boolean enabled = true;

  /** Prevent System.exit() from ever being called. */
  @Override public void checkExit(int code) {
    if (enabled) {
      throw new SecurityException("Test code should never call System.exit()");
    }
  }

  //
  // The code below overrides the Java2 security mechanism to allow
  // (almost) all requests. This is OK vis-a-vis the Java default
  // (which is to run with no security policy at all).
  //
  // The default Java security policy is to pass through to the
  // Permissions check mechanism, which in turn by default denies
  // things. We override all of that (in essence, disabling Java2
  // Permissions) and just allow everything.
  //

  /**
   * Cache a copy of the permission that System.setSecurityManager() checks.
   */
  private final RuntimePermission securityManagerPermission =
      new RuntimePermission("setSecurityManager");

  /** Allow everything but creation of security managers. */
  @Override public void checkPermission(Permission p) {
    if (enabled && securityManagerPermission.equals(p)) {
      throw new SecurityException("GoogleTestSecurityManager is not designed to handle other " +
          "security managers.");
    }
  }

  /** Allow everything. */
  @Override public void checkPermission(Permission p, java.lang.Object o) {
    return;
  }

  public boolean isEnabled() { return enabled; }

  /**
   * If {@code GoogleTestSecurityManager} is the current security manager,
   * uninstall it.
   */
  public static void uninstallIfInstalled() {
    SecurityManager securityManager = System.getSecurityManager();
    if (securityManager instanceof GoogleTestSecurityManager) {
      GoogleTestSecurityManager testSecurityManager = (GoogleTestSecurityManager) securityManager;
      boolean wasEnabled = testSecurityManager.isEnabled();

      try {
        testSecurityManager.setEnabled(false);
        System.setSecurityManager(null);
      } finally {
        testSecurityManager.setEnabled(wasEnabled);
      }
    }
  }

  /**
   * The security manager can be disabled by the test runner (or any other
   * class in the same package) to allow it to exit with a meaningful result
   * code.
   *
   * VisibleForTesting
   */
  synchronized void setEnabled(boolean enabled) { this.enabled = enabled; }
}
