/*
 * 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.jeri;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.security.PrivilegedAction;
import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.security.Security;
import net.jini.security.TrustVerifier;
import net.jini.security.proxytrust.TrustEquivalence;

/**
 * Trust verifier for dynamic proxies and object endpoints used in Jini
 * extensible remote invocation (Jini ERI). This class is intended to be
 * specified in a resource to configure the operation of {@link
 * Security#verifyObjectTrust Security.verifyObjectTrust}.
 *
 * 
 * @since 2.0
 **/
public class BasicJeriTrustVerifier implements TrustVerifier {

    /**
     * Creates an instance.
     */
    public BasicJeriTrustVerifier() {
    }

    /**
     * Returns <code>true</code> if the specified object is a trusted
     * Jini ERI dynamic proxy or a trusted Jini ERI object endpoint;
     * returns <code>false</code> otherwise.
     * 
     * <p>For the purposes of this verifier, the specified object is a trusted
     * Jini ERI dynamic proxy if all of the following are true:
     * <ul>
     * <li>The object is an instance of {@link RemoteMethodControl}.
     * <li>The object is an instance of a dynamic proxy class; that is, a
     * class generated by {@link Proxy}.
     * <li>Invoking the protected method {@link #hasTrustedProxyClass
     * hasTrustedProxyClass} with the specified object and the specified
     * trust verifier context returns <code>true</code>.
     * <li>Invoking the protected method {@link #isTrustedInvocationHandler
     * isTrustedInvocationHandler} with the {@link
     * java.lang.reflect.InvocationHandler} contained in the dynamic proxy and
     * the specified trust verifier context returns <code>true</code>. 
     * </ul>
     * 
     * <p>For the purposes of this verifier, the specified object is a trusted
     * Jini ERI object endpoint if the object is an instance of
     * {@link BasicObjectEndpoint} and its transport endpoint is
     * trusted; that is, calling the specified context's
     * <code>isTrustedObject</code> method with the transport endpoint returns
     * <code>true</code>.
     *
     * @throws SecurityException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     **/
    public boolean isTrustedObject(Object obj, final TrustVerifier.Context ctx)
	throws RemoteException
    {
	if (obj == null || ctx == null) {
	    throw new NullPointerException();
	} else if (obj instanceof RemoteMethodControl) {
	    final Class c = obj.getClass();
	    if (Proxy.isProxyClass(c)) {
		if (!hasTrustedProxyClass(obj, ctx)) {
		    return false;
		}
		
		InvocationHandler handler = Proxy.getInvocationHandler(obj);
		return isTrustedInvocationHandler(handler, ctx);

	    }
	} else if (obj.getClass() == BasicObjectEndpoint.class) {
	    BasicObjectEndpoint oe = (BasicObjectEndpoint) obj;
	    return ctx.isTrustedObject(oe.getEndpoint());
	}
	return false;
    }

    /**
     * Returns <code>true</code> if the specified dynamic proxy's class is
     * trusted by this verifier; returns <code>false</code> otherwise.  The
     * <code>isTrustedObject</code> method calls this method to verify that
     * the proxy's class is trusted.
     *
     * <p><code>BasicJeriTrustVerifier</code> implements this method to
     * return <code>true</code> if all of the following are true:
     * <ul>
     * <li>The proxy is an instance of {@link Remote}.
     * <li>Invoking the protected method {@link #hasTrustedClassLoader
     * hasTrustedClassLoader} with the specified proxy and the specified
     * trust verifier context returns <code>true</code>.
     * <li>For each direct superinterface of the proxy's class, invoking
     * the protected method {@link #isTrustedProxyInterface
     * isTrustedProxyInterface} with the interface and the specified trust
     * verifier context returns <code>true</code>. 
     * </ul>
     *
     * <p>A subclass can override this method to augment the set of trusted 
     * proxy classes for Jini ERI proxies.
     *
     * @param proxy the dynamic proxy
     * @param ctx the trust verifier context, to aid in verification of
     * the specified proxy's class
     * @return <code>true</code> if the specified dynamic proxy's class is
     * trusted by this verifier; returns <code>false</code> otherwise
     * @throws RemoteException if a communication-related exception occurs
     * @throws SecurityException if a security exception occurs
     * @throws NullPointerException if any argument is <code>null</code>
     * @since 2.1
     **/
    protected boolean hasTrustedProxyClass(Object proxy,
					   TrustVerifier.Context ctx)
	throws RemoteException
    {
	Class c = proxy.getClass();
	if (ctx == null) {
	    throw new NullPointerException();
	} else if (!(proxy instanceof Remote &&
		     hasTrustedClassLoader(proxy, ctx)))
	{
	    return false;
	}
	Class[] interfaces = c.getInterfaces();
	for (int i = interfaces.length; --i >= 0; ) {
	    if (!isTrustedProxyInterface(interfaces[i], ctx)) {
		return false;
	    }
	}
	return true;
    }

    /**
     * Returns <code>true</code> if the class loader of the specified dynamic
     * proxy's class is trusted by this verifier; returns <code>false</code>
     * otherwise.  The default implementation of
     * <code>hasTrustedProxyClass</code> calls this method to verify
     * that the proxy's class loader is trusted.
     *
     * <p><code>BasicJeriTrustVerifier</code> implements this method to
     * return <code>true</code> if the specified proxy is an instance of a
     * dynamic proxy class and the class loader of the specified proxy's
     * class is either equal to or an ancestor of the class loader returned
     * by the trust verifier context's {@link
     * net.jini.security.TrustVerifier.Context#getClassLoader
     * getClassLoader} method (or the current context class loader, if that
     * method returns <code>null</code>).
     *
     * <p>A subclass can override this method to augment the set of trusted 
     * loaders for Jini ERI proxies.
     *
     * @param proxy the dynamic proxy
     * @param ctx the trust verifier context, to aid in verification of
     * the class loader of the specified proxy's class
     * @return <code>true</code> if the class loader of the specified dynamic
     * proxy's class is trusted by this verifier; returns <code>false</code>
     * otherwise
     * @throws RemoteException if a communication-related exception occurs
     * @throws SecurityException if a security exception occurs
     * @throws IllegalArgumentException if the proxy is not an
     * instance of a dynamic proxy class
     * @throws NullPointerException if any argument is <code>null</code>
     * @since 2.1
     **/
    protected boolean hasTrustedClassLoader(Object proxy,
					    final TrustVerifier.Context ctx)
	throws RemoteException
    {
	final Class c = proxy.getClass();
	if (!Proxy.isProxyClass(c)) {
	    throw new IllegalArgumentException(
		"Proxy must be an instance of a dynamic proxy class");
	}
	Boolean b = (Boolean)
	    Security.doPrivileged(new PrivilegedAction() {
		public Object run() {
		    ClassLoader cl = ctx.getClassLoader();
		    if (cl == null) {
			cl = Thread.currentThread().getContextClassLoader();
		    }
		    return Boolean.valueOf(covers(cl, c.getClassLoader()));
		}
	    });
	return b.booleanValue();
    }

    /**
     * Returns <code>true</code> if the specified dynamic proxy interface is
     * trusted by this verifier; returns <code>false</code> otherwise.  The
     * default implementation of <code>hasTrustedProxyClass</code> calls
     * this method to verify that the dynamic proxy's interfaces are trusted.
     *
     * <p><code>BasicJeriTrustVerifier</code> implements this method to
     * return <code>true</code> if the specified class is
     * {@link RemoteMethodControl}, {@link TrustEquivalence}, or a
     * well-formed remote interface (one that extends {@link Remote} and for
     * which all methods declare {@link RemoteException} or a superclass in
     * their <code>throws</code> clause).
     *
     * <p>A subclass can override this method to augment the set of trusted 
     * proxy interfaces for Jini ERI proxies.
     *
     * @param intf the dynamic proxy interface
     * @param ctx the trust verifier context, to aid in verification of
     * the specified proxy interface
     * @return <code>true</code> if the specified dynamic proxy interface is
     * trusted by this verifier; returns <code>false</code> otherwise
     * @throws RemoteException if a communication-related exception occurs
     * @throws SecurityException if a security exception occurs
     * @throws NullPointerException if any argument is <code>null</code>
     * @since 2.1
     **/
    protected boolean isTrustedProxyInterface(Class intf,
					      TrustVerifier.Context ctx)
	throws RemoteException
    {
	if (ctx == null) {
	    throw new NullPointerException();
	} else if (intf == RemoteMethodControl.class ||
		   intf == TrustEquivalence.class)
	{
	    return true;
	} else if (!Remote.class.isAssignableFrom(intf) || !intf.isInterface())
	{
	    return false;
	}
	Method[] methods = intf.getMethods();
    methods:
	for (int i = methods.length; --i >= 0; ) {
	    Class[] exceptions = methods[i].getExceptionTypes();
	    for (int j = exceptions.length; --j >= 0; ) {
		if (exceptions[j].isAssignableFrom(RemoteException.class))
		{
		    continue methods;
		}
	    }
	    return false;
	}
	return true;
    }

    /**
     * Returns <code>true</code> if the specified invocation
     * handler is trusted by this trust verifier; returns
     * <code>false</code> otherwise.  The <code>isTrustedObject</code>
     * method calls this method to verify trust in the invocation handler.
     *
     * <p><code>BasicJeriTrustVerifier</code> implements this method to
     * return <code>true</code> if all of the following are true:
     * <ul>
     * <li>The specified invocation handler's class is equal to
     * <code>BasicInvocationHandler</code>. 
     * <li>Invoking the {@link #checkInvocationHandlerContent
     * checkInvocationHandlerContent} method with the specified handler
     * and the specified context returns <code>true</code>. 
     * </ul>
     *
     * <p>A subclass can override this method to verify that the invocation
     * handler's class is a trusted subclass of
     * <code>BasicInvocationHandler</code> and to verify the contents of
     * the invocation handler.  A subclass implementation of this method
     * can invoke the <code>checkInvocationHandlerContent</code> static
     * utility method to verify that the invocation handler's constraints
     * and object endpoint are trusted.
     *
     * @param handler the invocation handler
     * @param ctx the trust verifier context, to aid in verification of
     * the specified handler
     * @return <code>true</code> if the specified invocation
     * handler is trusted by this trust verifier; returns
     * <code>false</code> otherwise
     * @throws RemoteException if a communication-related exception occurs
     * @throws SecurityException if a security exception occurs
     * @throws NullPointerException if any argument is <code>null</code>
     * @since 2.1
     **/
    protected boolean isTrustedInvocationHandler(
				InvocationHandler handler,
				TrustVerifier.Context ctx)
	throws RemoteException
    {
	if (handler.getClass() != BasicInvocationHandler.class) {
	    return false;
	}
	
	return checkInvocationHandlerContent(
		    (BasicInvocationHandler) handler, ctx);
    }

    /**
     * Returns <code>true</code> if the specified invocation handler's
     * constraints and object endpoint are trusted; returns
     * <code>false</code> otherwise.  The
     * <code>isTrustedInvocationHandler</code> method calls this utility
     * method to check that the invocation handler's constraints and object
     * endpoint are trusted.
     *
     * <p>The specified invocation handler's constraints and object
     * endpoint are trusted if all of the following are true:
     * <ul>
     * <li>The object endpoint in the specified invocation handler is
     * trusted; that is, calling the specified context's
     * {@link net.jini.security.TrustVerifier.Context#isTrustedObject
     * isTrustedObject} method with the object endpoint returns
     * <code>true</code>. 
     * <li>The server constraints in the specified invocation handler are
     * trusted; that is, calling the specified context's
     * <code>isTrustedObject</code> method with the server constraints
     * returns <code>true</code>. 
     * </ul>
     *
     * @param handler the invocation handler
     * @param ctx the trust verifier context, to aid in verification of
     * the specified handler
     * @return <code>true</code> if the specified invocation
     * handler's constraints and object endpoint are trusted by this method;
     * returns <code>false</code> otherwise
     * @throws RemoteException if a communication-related exception occurs
     * @throws SecurityException if a security exception occurs
     * @throws NullPointerException if any argument is <code>null</code>
     * @since 2.1
     **/
    protected static boolean checkInvocationHandlerContent(
				BasicInvocationHandler handler,
				TrustVerifier.Context ctx)
	throws RemoteException
    {
	if (handler == null) {
	    throw new NullPointerException("handler is null");
	}

	MethodConstraints serverConstraints = handler.getServerConstraints();
	return (ctx.isTrustedObject(handler.getObjectEndpoint()) &&
		ctx.isTrustedObject(serverConstraints));
    }
    
    /**
     * Returns true if the first argument is either equal to, or is a
     * descendant of, the second argument.  Null is treated as the root of
     * the tree.
     */
    private static boolean covers(ClassLoader sub, ClassLoader sup) {
	if (sup == null) {
	    return true;
	} else if (sub == null) {
	    return false;
	}
	do {
	    if (sub == sup) {
		return true;
	    }
	    sub = sub.getParent();
	} while (sub != null);
	return false;
    }
}
