/*
* 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 net.jini.security;
import java.rmi.RemoteException;
import java.security.Permission;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collections;
import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
/**
* A <code>ProxyPreparer</code> for verifying that proxies are trusted,
* dynamically granting permissions to trusted proxies, and optionally
* setting the client constraints on trusted proxies.
*
* @author Sun Microsystems, Inc.
* @since 2.1
*/
public final class VerifyingProxyPreparer implements ProxyPreparer {
/** Set constraints on proxy from context. */
private static final int SET_CONSTRAINTS = 1;
/** No change to proxy or context. */
private static final int AS_IS = 2;
/** Add proxy's constraints to context. */
private static final int ADD_CONSTRAINTS = 3;
/** SET_CONSTRAINTS, AS_IS, or ADD_CONSTRAINTS. */
private final int type;
/** Class loader to pass to verifyObjectTrust. */
private final ClassLoader loader;
/** Trust verifier context elements. */
private final Object[] contextElements;
/** Principals to scope the permission grant, if any. */
private final Principal[] principals;
/** Permissions to dynamically grant. */
private final Permission[] permissions;
/**
* Creates a proxy preparer that verifies proxies using the context
* class loader and specified trust verifier context elements, dynamically
* grants the specified permissions to trusted proxies for the principals
* of the preparing thread's subject, and returns trusted proxies with
* their client constraints set to the constraints specified as a trust
* verifier context element. The arrays passed to this constructor are
* neither modified nor retained; subsequent changes to the arrays have no
* effect on the instance created.
*
* @param contextElements the trust verifier context elements
* @param permissions the permissions to dynamically grant, or
* <code>null</code> if no permissions should be granted
* @throws NullPointerException if <code>contextElements</code> is
* <code>null</code> or any element of <code>permissions</code> is
* <code>null</code>
* @throws IllegalArgumentException if no element of
* <code>contextElements</code> is an instance of {@link MethodConstraints}
*/
public VerifyingProxyPreparer(Object[] contextElements,
Permission[] permissions)
{
this(null, contextElements, null, permissions);
}
/**
* Creates a proxy preparer that verifies proxies using the specified
* class loader and trust verifier context elements, dynamically grants
* the specified permissions to trusted proxies for the specified
* principals, and returns trusted proxies with their client constraints
* set to the constraints specified as a trust verifier context element.
* The arrays passed to this constructor are neither modified nor
* retained; subsequent changes to the arrays have no effect on the
* instance created.
*
* @param loader the class loader for finding trust verifiers, or
* <code>null</code> to use the context class loader
* @param contextElements the trust verifier context elements
* @param principals minimum set of principals to which grants apply, or
* <code>null</code> to use the principals of the preparing thread's
* subject
* @param permissions the permissions to dynamically grant, or
* <code>null</code> if no permissions should be granted
* @throws NullPointerException if <code>contextElements</code> is
* <code>null</code> or any element of <code>principals</code> or
* <code>permissions</code> is <code>null</code>
* @throws IllegalArgumentException if no element of
* <code>contextElements</code> is an instance of {@link MethodConstraints}
*/
public VerifyingProxyPreparer(ClassLoader loader,
Object[] contextElements,
Principal[] principals,
Permission[] permissions)
{
type = SET_CONSTRAINTS;
this.loader = loader;
this.contextElements = (Object[]) contextElements.clone();
this.principals = checkPrincipals(principals);
this.permissions = checkPermissions(permissions);
for (int i = this.contextElements.length; --i >= 0; ) {
if (this.contextElements[i] instanceof MethodConstraints) {
return;
}
}
throw new IllegalArgumentException("no MethodConstraints in context");
}
/**
* Creates a proxy preparer that verifies proxies using the specified
* class loader and trust verifier context elements (optionally with
* the proxy's client constraints as an additional context element),
* dynamically grants the specified permissions to trusted proxies for the
* specified principals, and returns trusted proxies with their original
* client constraints intact. The arrays passed to this constructor are
* neither modified nor retained; subsequent changes to the arrays have no
* effect on the instance created.
*
* @param addProxyConstraints <code>true</code> if the proxy's client
* constraints should be included as a trust verifier context element,
* <code>false</code> otherwise
* @param loader the class loader for finding trust verifiers, or
* <code>null</code> to use the context class loader
* @param contextElements the trust verifier context elements, or
* <code>null</code> if no elements need to be supplied
* @param principals minimum set of principals to which grants apply, or
* <code>null</code> to use the principals of the preparing thread's
* subject
* @param permissions the permissions to dynamically grant, or
* <code>null</code> if no permissions should be granted
* @throws NullPointerException if any element of <code>principals</code>
* or <code>permissions</code> is <code>null</code>
*/
public VerifyingProxyPreparer(boolean addProxyConstraints,
ClassLoader loader,
Object[] contextElements,
Principal[] principals,
Permission[] permissions)
{
type = addProxyConstraints ? ADD_CONSTRAINTS : AS_IS;
this.loader = loader;
this.contextElements = contextElements == null ?
new Object[0] : (Object[]) contextElements.clone();
this.principals = checkPrincipals(principals);
this.permissions = checkPermissions(permissions);
}
/** Clones the argument, checks for null elements, returns non-null. */
private static Permission[] checkPermissions(Permission[] permissions) {
if (permissions == null) {
return new Permission[0];
}
permissions = (Permission[]) permissions.clone();
for (int i = permissions.length; --i >= 0; ) {
if (permissions[i] == null) {
throw new NullPointerException("permission cannot be null");
}
}
return permissions;
}
/** Clones the argument, checks for null elements. */
private static Principal[] checkPrincipals(Principal[] principals) {
if (principals == null) {
return null;
}
principals = (Principal[]) principals.clone();
for (int i = principals.length; --i >= 0; ) {
if (principals[i] == null) {
throw new NullPointerException("principal cannot be null");
}
}
return principals;
}
/**
* Performs operations on a proxy to prepare it for use, returning the
* prepared proxy, which may or may not be the argument itself.
* <p>
* If this preparer was created using the two-argument or four-argument
* constructor, or using the five-argument constructor
* with <code>addProxyConstraints</code> set to <code>true</code>, and if
* the specified proxy is not an instance of {@link RemoteMethodControl},
* then a <code>SecurityException</code> is thrown. Otherwise,
* {@link Security#verifyObjectTrust Security.verifyObjectTrust} is
* invoked with the specified proxy, the class loader that was passed to
* the constructor of this preparer (or <code>null</code> if the
* two-argument constructor was used), and a trust verifier context
* collection containing all of the context elements that were passed to
* the constructor of this preparer. If this preparer was created using
* the five-arguent constructor with <code>addProxyConstraints</code>
* set to <code>true</code>, then the proxy's client constraints (obtained
* by calling {@link RemoteMethodControl#getConstraints getConstraints} on
* the proxy) are included as an additional context element. Any exception
* thrown by <code>verifyObjectTrust</code> is thrown by this method. If
* this preparer was created with a non-<code>null</code> array of
* principals and one or more permissions, then
* {@link Security#grant(Class,Principal[],Permission[]) Security.grant}
* is invoked with the proxy's class and those principals and permissions.
* If this preparer was created with no array of principals (either
* <code>null</code> was specified or the two-argument constructor was
* used) but one or more permissions, then
* {@link Security#grant(Class,Permission[]) Security.grant} is invoked
* with the proxy's class and those permissions. In either case, if
* <code>grant</code>
* throws an {@link UnsupportedOperationException}, this method throws
* a <code>SecurityException</code>. Finally, if this preparer was
* created using the five-argument constructor, then the original proxy
* is returned, otherwise what is returned is the result of calling
* {@link RemoteMethodControl#setConstraints
* RemoteMethodControl.setConstraints} on the proxy, passing the first
* trust verifier context element that is an instance of
* {@link MethodConstraints}.
*
* @param proxy the proxy to prepare
* @return the prepared proxy
* @throws NullPointerException if <code>proxy</code> is <code>null</code>
* @throws RemoteException if a communication-related exception occurs
* @throws SecurityException if a security exception occurs
*/
public Object prepareProxy(Object proxy) throws RemoteException {
if (proxy == null) {
throw new NullPointerException("proxy cannot be null");
} else if (type != AS_IS && !(proxy instanceof RemoteMethodControl)) {
throw new SecurityException(
"proxy must implement RemoteMethodControl");
}
Object[] elts = contextElements;
if (type == ADD_CONSTRAINTS) {
elts = new Object[contextElements.length + 1];
elts[0] = ((RemoteMethodControl) proxy).getConstraints();
System.arraycopy(contextElements, 0, elts, 1,
contextElements.length);
}
Security.verifyObjectTrust(proxy, loader,
Collections.unmodifiableCollection(
Arrays.asList(elts)));
if (permissions.length > 0) {
try {
if (principals == null) {
Security.grant(proxy.getClass(), permissions);
} else {
Security.grant(proxy.getClass(), principals, permissions);
}
} catch (UnsupportedOperationException e) {
SecurityException se = new SecurityException(
"dynamic permission grants are not supported");
se.initCause(e);
throw se;
}
}
if (type == SET_CONSTRAINTS) {
for (int i = 0; i < contextElements.length; i++) {
Object elt = contextElements[i];
if (elt instanceof MethodConstraints) {
return ((RemoteMethodControl) proxy).setConstraints(
(MethodConstraints) elt);
}
}
}
return proxy;
}
/** Returns a string representation of this object. */
public String toString() {
StringBuffer sb = new StringBuffer("VerifyingProxyPreparer[");
if (type != SET_CONSTRAINTS) {
sb.append(type == AS_IS ? "false, " : "true, ");
}
sb.append(loader);
sb.append(", {");
for (int i = 0; i < contextElements.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(contextElements[i]);
}
if (principals == null) {
sb.append("}, null, {");
} else {
sb.append("}, {");
for (int i = 0; i < principals.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(principals[i]);
}
sb.append("}, {");
}
for (int i = 0; i < permissions.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(permissions[i]);
}
sb.append("}]");
return sb.toString();
}
/**
* Returns <code>true</code> if the specified object and this object
* are both instances of this class that were constructed with equivalent
* arguments. The order of trust verifier context elements, principals,
* and permissions in the arrays that were passed to the constructor is
* not significant.
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof VerifyingProxyPreparer)) {
return false;
}
VerifyingProxyPreparer other = (VerifyingProxyPreparer) obj;
if (type != other.type ||
loader != other.loader ||
contextElements.length != other.contextElements.length ||
(principals == null) != (other.principals == null) ||
(principals != null &&
principals.length != other.principals.length) ||
permissions.length != other.permissions.length)
{
return false;
}
Object[] otherElts = (Object[]) other.contextElements.clone();
context:
for (int i = contextElements.length; --i >= 0; ) {
Object elt = contextElements[i];
for (int j = i; j >= 0; j--) {
if (elt.equals(otherElts[j])) {
otherElts[j] = otherElts[i];
continue context;
}
}
return false;
}
if (principals != null) {
Principal[] otherPrins = (Principal[]) other.principals.clone();
prins:
for (int i = principals.length; --i >= 0; ) {
Principal p = principals[i];
for (int j = i; j >= 0; j--) {
if (p.equals(otherPrins[j])) {
otherPrins[j] = otherPrins[i];
continue prins;
}
}
return false;
}
}
Permission[] otherPerms = (Permission[]) other.permissions.clone();
perms:
for (int i = permissions.length; --i >= 0; ) {
Permission p = permissions[i];
for (int j = i; j >= 0; j--) {
if (p.equals(otherPerms[j])) {
otherPerms[j] = otherPerms[i];
continue perms;
}
}
return false;
}
return true;
}
/** Returns a hash code value for this object. */
public int hashCode() {
int hash = type;
if (loader != null) {
hash += loader.hashCode();
}
for (int i = contextElements.length; --i >= 0; ) {
hash += contextElements[i].hashCode();
}
if (principals != null) {
for (int i = principals.length; --i >= 0; ) {
hash += principals[i].hashCode();
}
}
for (int i = permissions.length; --i >= 0; ) {
hash += permissions[i].hashCode();
}
return hash;
}
}