/*
 * 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;
    }
}
