/*
 * Copyright 2002 Tryllian BV and Otto Moerbeek
 * http://www.tryllian.com
 * otto@tryllian.com
 */

package net.drijf.javaone;

import java.security.Permission;

/**
 * This class implements a more strict Security manager than the
 * standard <code>SecurityManager</code>. In particular, it:
 * <ul>
 * <li>does not allow creation of threads without proper permissions,</li>
 * <li>it does not allow execution of <code>System.exit()</code> for code
 * that is of unknown origin,</li>
 * <li>
 * <li>it does not allow setting of a security manager for code that
 * is of unknown origin.</li>
 * </ul>
 * The last two checks even fail if the protection domain implies
 * <code>AllPermissions</code>. While this is not according to
 * specification of Sun, it gives some hints on how a policy could be
 * defined that allows for subtractive permissions.
 *
 * @author Otto Moerbeek
 */
public class StrictSecurityManager extends SecurityManager {

    /** A permission used by checkAccess */
    private static RuntimePermission threadGroupPermission;
    /** A permission used by checkPermission */
    private static RuntimePermission setSecurityManagerPermission;

    /**
     * Check access to the ThreadGroup.
     *
     * The standard security manager allows creation of threads
     * outside the system thread group. This method disallows thread
     * creation in any group unless <code>modifyThreadGroup</code>
     * permission has been granted.
     *
     * @param g the thread group to check access to.
     */
    public void checkAccess(ThreadGroup g) {
        // First do the standard security check
        super.checkAccess(g);
        
        if (threadGroupPermission == null) {
            threadGroupPermission = new RuntimePermission("modifyThreadGroup");
        }

        // Now check if we have the proper permission
        checkPermission(threadGroupPermission);
    }

    /**
     * Prevent access to <code>System.exit()</code>, even if
     * <code>AllPermission has been granted. The only case that is
     * allowed shutdown: all code on the stack is found to be OK by
     * <code>checkTrusted</code>
     *
     * @see #checkTrusted
     */
    public void checkExit(int code) {
        // First do the standard security check
        super.checkExit(code);

        // Now do the more strict check
        Class[] stack = getClassContext();
        for (int i = 0; i < stack.length; i++) {
            String currentClassName = stack[i].getName();
            checkTrusted(currentClassName);
        }
    }

    /**
     * Check is we trust the class with the given name.
     *
     * @param className the name of the class to check.
     */
    protected void checkTrusted(String className) {
        if (className.startsWith("java.")
            || className.startsWith("net.drijf.")) {
            return;
        }
        throw new SecurityException("You may not shutdown the VM");  
    }

    /**
     * Check if setting the security manager is allowed.
     *
     * The check is implemented by this general method, because there is no
     * separate method for checking
     * <code>"setSecurityManagerPermission"</code>.
     *
     * @perm the permisison to check.
     */
    public void checkPermission(Permission perm) {

        super.checkPermission(perm);

        if (setSecurityManagerPermission == null) {
            setSecurityManagerPermission
                = new RuntimePermission("setSecurityManager");
        }
        if (setSecurityManagerPermission.equals(perm)) {
            throw new SecurityException("You may not set a security manager");
        }
    }


}
