blob: 5c5bc262dd468b064064fa84ad72f7b5d0e860c8 [file] [log] [blame]
/*
* 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.
*/
/*
* Copyright (C) 2008 The Android Open Source Project
*
* 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 java.security;
import java.util.ArrayList;
import java.util.WeakHashMap;
import org.apache.harmony.security.fortress.SecurityUtils;
/**
* {@code AccessController} provides static methods to perform access control
* checks and privileged operations.
*/
public final class AccessController {
private AccessController() {
throw new Error("statics only.");
}
/**
* A map used to store a mapping between a given Thread and
* AccessControllerContext-s used in successive calls of doPrivileged(). A
* WeakHashMap is used to allow automagical wiping of the dead threads from
* the map. The thread (normally Thread.currentThread()) is used as a key
* for the map, and a value is ArrayList where all AccessControlContext-s
* are stored.
* ((ArrayList)contexts.get(Thread.currentThread())).lastElement() - is
* reference to the latest context passed to the doPrivileged() call.
*/
private static final WeakHashMap<Thread, ArrayList<AccessControlContext>> contexts = new WeakHashMap<Thread, ArrayList<AccessControlContext>>();
/**
* Returns the result of executing the specified privileged action. Only the
* {@code ProtectionDomain} of the direct caller of this method and the
* {@code ProtectionDomain}s of all subsequent classes in the call chain are
* checked to be granted the necessary permission if access checks are
* performed.
* <p>
* If an instance of {@code RuntimeException} is thrown during the execution
* of the {@code PrivilegedAction#run()} method of the given action, it will
* be propagated through this method.
*
* @param action
* the action to be executed with privileges
* @return the result of executing the privileged action
* @throws NullPointerException
* if the specified action is {@code null}
* @since Android 1.0
*/
public static <T> T doPrivileged(PrivilegedAction<T> action) {
if (action == null) {
throw new NullPointerException("action can not be null");
}
return doPrivilegedImpl(action, null);
}
/**
* Returns the result of executing the specified privileged action. The
* {@code ProtectionDomain} of the direct caller of this method, the {@code
* ProtectionDomain}s of all subsequent classes in the call chain and all
* {@code ProtectionDomain}s of the given context are checked to be granted
* the necessary permission if access checks are performed.
* <p>
* If an instance of {@code RuntimeException} is thrown during the execution
* of the {@code PrivilegedAction#run()} method of the given action, it will
* be propagated through this method.
*
* @param action
* the action to be executed with privileges
* @param context
* the {@code AccessControlContext} whose protection domains are
* checked additionally
* @return the result of executing the privileged action
* @throws NullPointerException
* if the specified action is {@code null}
* @since Android 1.0
*/
public static <T> T doPrivileged(PrivilegedAction<T> action,
AccessControlContext context) {
if (action == null) {
throw new NullPointerException("action can not be null");
}
return doPrivilegedImpl(action, context);
}
/**
* Returns the result of executing the specified privileged action. Only the
* {@code ProtectionDomain} of the direct caller of this method and the
* {@code ProtectionDomain}s of all subsequent classes in the call chain are
* checked to be granted the necessary permission if access checks are
* performed.
* <p>
* If a checked exception is thrown by the action's run method, it will be
* wrapped and propagated through this method.
* <p>
* If an instance of {@code RuntimeException} is thrown during the execution
* of the {@code PrivilegedAction#run()} method of the given action, it will
* be propagated through this method.
*
* @param action
* the action to be executed with privileges
* @return the result of executing the privileged action
* @throws PrivilegedActionException
* if the action's run method throws any checked exception
* @throws NullPointerException
* if the specified action is {@code null}
* @since Android 1.0
*/
public static <T> T doPrivileged(PrivilegedExceptionAction<T> action)
throws PrivilegedActionException {
if (action == null) {
throw new NullPointerException("action can not be null");
}
return doPrivilegedImpl(action, null);
}
/**
* Returns the result of executing the specified privileged action. The
* {@code ProtectionDomain} of the direct caller of this method, the {@code
* ProtectionDomain}s of all subsequent classes in the call chain and all
* {@code ProtectionDomain}s of the given context are checked to be granted
* the necessary permission if access checks are performed.
* <p>
* If a checked exception is thrown by the action's run method, it will be
* wrapped and propagated through this method.
* <p>
* If an instance of {@code RuntimeException} is thrown during the execution
* of the {@code PrivilegedAction#run()} method of the given action, it will
* be propagated through this method.
*
* @param action
* the action to be executed with privileges
* @param context
* the {@code AccessControlContext} whose protection domains are
* checked additionally
* @return the result of executing the privileged action
* @throws PrivilegedActionException
* if the action's run method throws any checked exception
* @throws NullPointerException
* if the specified action is {@code null}
* @since Android 1.0
*/
public static <T> T doPrivileged(PrivilegedExceptionAction<T> action,
AccessControlContext context) throws PrivilegedActionException {
if (action == null) {
throw new NullPointerException("action can not be null");
}
return doPrivilegedImpl(action, context);
}
/**
* The real implementation of doPrivileged() method. It pushes the passed
* context into this thread's contexts stack, and then invokes
* <code>action.run()</code>. The pushed context is then investigated in the
* {@link #getContext()} which is called in the {@link #checkPermission}.
*/
private static <T> T doPrivilegedImpl(PrivilegedExceptionAction<T> action,
AccessControlContext context) throws PrivilegedActionException {
Thread currThread = Thread.currentThread();
ArrayList<AccessControlContext> a = null;
try {
// currThread==null means that VM warm up is in progress
if (currThread != null && contexts != null) {
synchronized (contexts) {
a = contexts.get(currThread);
if (a == null) {
a = new ArrayList<AccessControlContext>();
contexts.put(currThread, a);
}
}
a.add(context);
}
return action.run();
} catch (Exception ex) {
// Errors automagically go through - they are not catched by this
// block
// Unchecked exceptions must pass through without modification
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
// All other (==checked) exceptions get wrapped
throw new PrivilegedActionException(ex);
} finally {
if (currThread != null) {
// No need to sync() here, as each given 'a' will be accessed
// only from one Thread. 'contexts' still need sync() however,
// as it's accessed from different threads simultaneously
if (a != null) {
// it seems I will never have here [v.size() == 0]
a.remove(a.size() - 1);
}
}
}
}
/**
* The real implementation of appropriate doPrivileged() method.<br>
* It pushes the passed context into this thread's stack of contexts and
* then invokes <code>action.run()</code>.<br>
* The pushed context is then investigated in the {@link #getContext()}
* which is called in the {@link #checkPermission}.
*/
private static <T> T doPrivilegedImpl(PrivilegedAction<T> action,
AccessControlContext context) {
Thread currThread = Thread.currentThread();
if (currThread == null || contexts == null) {
// Big boom time - VM is starting... No need to check permissions:
// 1st, I do believe there is no malicious code available here for
// this moment
// 2d, I cant use currentThread() as a key anyway - when it will
// turn into the real Thread, I'll be unable to retrieve the value
// stored with 'currThread==null' as a key.
return action.run();
}
ArrayList<AccessControlContext> a = null;
try {
synchronized (contexts) {
a = contexts.get(currThread);
if (a == null) {
a = new ArrayList<AccessControlContext>();
contexts.put(currThread, a);
}
}
a.add(context);
return action.run();
} finally {
// No need to sync() here, as each given 'a' will be accessed
// only from one Thread. 'contexts' still need sync() however,
// as it's accessed from different threads simultaneously
if (a != null) {
a.remove(a.size() - 1);
}
}
}
/**
* Checks the specified permission against the vm's current security policy.
* The check is performed in the context of the current thread. This method
* returns silently if the permission is granted, otherwise an {@code
* AccessControlException} is thrown.
* <p>
* A permission is considered granted if every {@link ProtectionDomain} in
* the current execution context has been granted the specified permission.
* If privileged operations are on the execution context, only the {@code
* ProtectionDomain}s from the last privileged operation are taken into
* account.
* <p>
* This method delegates the permission check to
* {@link AccessControlContext#checkPermission(Permission)} on the current
* callers' context obtained by {@link #getContext()}.
*
* @param perm
* the permission to check against the policy
* @throws AccessControlException
* if the specified permission is not granted
* @throws NullPointerException
* if the specified permission is {@code null}
* @see AccessControlContext#checkPermission(Permission)
*
* @since Android 1.0
*/
public static void checkPermission(Permission perm)
throws AccessControlException {
if (perm == null) {
throw new NullPointerException("permission can not be null");
}
getContext().checkPermission(perm);
}
/**
* Returns array of ProtectionDomains from the classes residing on the stack
* of the current thread, up to and including the caller of the nearest
* privileged frame. Reflection frames are skipped. The returned array is
* never null and never contains null elements, meaning that bootstrap
* classes are effectively ignored.
*/
private static native ProtectionDomain[] getStackDomains();
/**
* Returns the {@code AccessControlContext} for the current {@code Thread}
* including the inherited access control context of the thread that spawned
* the current thread (recursively).
* <p>
* The returned context may be used to perform access checks at a later
* point in time, possibly by another thread.
*
* @return the {@code AccessControlContext} for the current {@code Thread}
* @see Thread#currentThread
* @since Android 1.0
*/
public static AccessControlContext getContext() {
// duplicates (if any) will be removed in ACC constructor
ProtectionDomain[] stack = getStackDomains();
Thread currThread = Thread.currentThread();
if (currThread == null || contexts == null) {
// Big boo time. No need to check anything ?
return new AccessControlContext(stack);
}
ArrayList<AccessControlContext> threadContexts;
synchronized (contexts) {
threadContexts = contexts.get(currThread);
}
AccessControlContext that;
if ((threadContexts == null) || (threadContexts.size() == 0)) {
// We were not in doPrivileged method, so
// have inherited context here
that = SecurityUtils.getContext(currThread);
} else {
// We were in doPrivileged method, so
// Use context passed to the doPrivileged()
that = threadContexts.get(threadContexts.size() - 1);
}
if (that != null && that.combiner != null) {
ProtectionDomain[] assigned = null;
if (that.context != null && that.context.length != 0) {
assigned = new ProtectionDomain[that.context.length];
System.arraycopy(that.context, 0, assigned, 0, assigned.length);
}
ProtectionDomain[] allpds = that.combiner.combine(stack, assigned);
if (allpds == null) {
allpds = new ProtectionDomain[0];
}
return new AccessControlContext(allpds, that.combiner);
}
return new AccessControlContext(stack, that);
}
}