/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.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://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */

package net.jini.jeri;


import java.lang.reflect.Proxy;
import java.rmi.RemoteException;
import java.rmi.server.RMIClassLoader;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.security.Security;
import net.jini.security.TrustVerifier;

/**
 * Trust verifier for dynamic proxies and object endpoints used in Jini(TM)
 * 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}.
 *
 * @author Sun Microsystems, Inc.
 * @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>The class loader of the dynamic proxy 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>), or its codebase (as given by
     * {@link RMIClassLoader#getClassAnnotation
     * RMIClassLoader.getClassAnnotation}) provides integrity. The codebase
     * provides integrity if calling {@link Security#verifyCodebaseIntegrity
     * Security.verifyCodebaseIntegrity} with that codebase and the
     * trust verifier context's class loader returns normally. (If that
     * method throws an exception, the exception is caught and this method
     * returns <code>false</code>.)
     * <li>The {@link java.lang.reflect.InvocationHandler} contained in the
     * dynamic proxy is an instance of {@link BasicInvocationHandler}
     * (not a subclass).
     * <li>The object endpoint in that 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 that invocation handler are trusted; that
     * is, calling the specified context's <code>isTrustedObject</code> method
     * with the server constraints 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 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)) {
		try {
		    Security.doPrivileged(new PrivilegedExceptionAction() {
			public Object run() throws Exception {
			    ClassLoader cl = ctx.getClassLoader();
			    if (cl == null) {
				cl = Thread.currentThread().
				        getContextClassLoader();
			    }
			    if (!covers(cl, c.getClassLoader())) {
				Security.verifyCodebaseIntegrity(
				    RMIClassLoader.getClassAnnotation(c), cl);
			    }
			    return null;
			}
		    });
		} catch (PrivilegedActionException e) {
		    return false;
		}
		
		Object handler = Proxy.getInvocationHandler(obj);
		if (handler.getClass() == BasicInvocationHandler.class)
		{
		    BasicInvocationHandler h =
			(BasicInvocationHandler) handler;
		    MethodConstraints serverConstraints =
			h.getServerConstraints();
		    return (ctx.isTrustedObject(h.getObjectEndpoint()) &&
			    ctx.isTrustedObject(serverConstraints));
		}
	    }
	} else if (obj.getClass() == BasicObjectEndpoint.class) {
	    BasicObjectEndpoint oe = (BasicObjectEndpoint) obj;
	    return ctx.isTrustedObject(oe.getEndpoint());
	}
	return false;
    }
    
    /**
     * 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;
    }
}
