/*
 * 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.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

/**
 * Represents permission to use the private credentials of subjects for the
 * purpose of authenticating as any subset of the local principals specified
 * in the target name, during secure remote calls with any peer that
 * authenticates as at least the set of peer principals specified in the
 * target name. In general, security providers check for this permission
 * instead of checking for
 * {@link javax.security.auth.PrivateCredentialPermission}. This
 * permission does not need to be granted for anonymous communication;
 * it only needs to be granted if an entity needs to authenticate itself.
 * <p>
 * An instance of this class contains a name (also referred to as a "target
 * name") and a set of actions. The target name specifies both the maximum
 * set of principals that an entity can authenticate as, and the minimum
 * set of principals that the peer must authenticate as. The actions specify
 * whether the permission is granted for making outbound remote calls with or
 * without delegation, listening for incoming remote calls, receiving
 * incoming remote calls, or some combination.
 * <p>
 * The syntax of the target name is either:
 * <pre><i>LocalPrincipals</i></pre>
 * or:
 * <pre><i>LocalPrincipals</i> <code>peer</code> <i>PeerPrincipals</i></pre>
 * where <i>LocalPrincipals</i> specifies the maximum set of principals that
 * an entity can authenticate as (that is, the entity can authenticate as any
 * subset of these principals), and <i>PeerPrincipals</i> specifies the
 * minimum set of principals that the peer must authenticate as (that is,
 * the peer must authenticate as at least all of these principals). If the
 * first syntactic form is used, the peer can authenticate as anyone (and can
 * be anonymous). The syntax of both <i>LocalPrincipals</i> and
 * <i>PeerPrincipals</i> is:
 * <pre><i>PrincipalClass</i> "<i>PrincipalName</i>" ...</pre>
 * That is, alternating principal classes and principal names, separated by
 * spaces, with each principal name surrounded by quotes. The order in which
 * principals are specified does not matter, but both class names and
 * principal names are case sensitive. For <i>LocalPrincipals</i>, in any
 * given principal specification, a wildcard value of "*" can be used for
 * both <i>PrincipalClass</i> and <i>PrincipalName</i> or for just
 * <i>PrincipalName</i>, but it is illegal to use a wildcard value for just
 * <i>PrincipalClass</i>. Explicit wildcard values cannot be used in
 * <i>PeerPrincipals</i>; only complete wildcarding of the peer is supported,
 * and is expressed by using the first syntactic form instead.
 * <p>
 * The syntax of the actions is a comma-separated list of any of the following
 * (case-insensitive) action names: <code>listen</code>, <code>accept</code>,
 * <code>connect</code>, <code>delegate</code>. The <code>listen</code> action
 * grants permission to authenticate as the server when listening for
 * incoming remote calls; in this case, the peer principals are ignored
 * (because it is assumed that in general servers authenticate themselves
 * before clients do). The <code>accept</code> action grants permission to
 * receive authenticated incoming remote calls; in this case, the entity has
 * authenticated as the server, and the peer has authenticated as the client.
 * If the <code>accept</code> action is specified, the <code>listen</code>
 * action is implied and need not be specified explicitly. The
 * <code>connect</code> action grants permission to authenticate when
 * making outgoing remote calls; in this case, the entity authenticates as
 * the client, and the peer authenticates as the server. The
 * <code>delegate</code> action grants permission to authenticate with
 * (or without) delegation when making outgoing remote calls. If the
 * <code>delegate</code> action is specified, the <code>connect</code>
 * action is implied and need not be specified explicitly.
 * <p>
 * A principal <code>p</code> matches <i>LocalPrincipals</i> if
 * <i>LocalPrincipals</i> has any of the following principal specifications:
 * <ul>
 * <li>"*" for both <i>PrincipalClass</i> and <i>PrincipalName</i>
 * <li>a <i>PrincipalClass</i> equal to the value of
 * <code>p.getClass().getName()</code> and a <i>PrincipalName</i> equal to "*"
 * <li>a <i>PrincipalClass</i> equal to the value of
 * <code>p.getClass().getName()</code> and a <i>PrincipalName</i> equal to
 * the value of <code>p.getName()</code>
 * </ul>
 * A principal <code>p</code> matches <i>PeerPrincipals</i> if
 * <i>PeerPrincipals</i> has a <i>PrincipalClass</i> equal to the value of
 * <code>p.getClass().getName()</code> and a <i>PrincipalName</i> equal to
 * the value of <code>p.getName()</code>.
 * <p>
 * Some example policy file permissions:
 * <pre>
 * // client authenticate as jack, with or without delegation, to any server
 * permission net.jini.security.AuthenticationPermission
 *     "javax.security.auth.x500.X500Principal \"CN=jack\"", "delegate";
 *
 * // client authenticate as joe and/or sue, without delegation, to any server
 * permission net.jini.security.AuthenticationPermission
 *     "javax.security.auth.x500.X500Principal \"CN=joe\" javax.security.auth.x500.X500Principal \"CN=sue\"", "connect";
 *
 * // client authenticate as any X500 principals, without delegation, to jack
 * permission net.jini.security.AuthenticationPermission
 *     "javax.security.auth.x500.X500Principal \"*\" peer javax.security.auth.x500.X500Principal \"CN=jack\"", "connect";
 *
 * // authenticate as jack to jack, bi-directional, with or without delegation
 * permission net.jini.security.AuthenticationPermission
 *     "javax.security.auth.x500.X500Principal \"CN=jack\" peer javax.security.auth.x500.X500Principal \"CN=jack\"", "accept,delegate";
 *
 * // authenticate as anyone to jack, bi-directional, without delegation
 * permission net.jini.security.AuthenticationPermission
 *     "* \"*\" peer javax.security.auth.x500.X500Principal \"CN=jack\"", "accept,connect";
 * </pre>
 *
 * 
 * 
 * @since 2.0
 */
public final class AuthenticationPermission extends Permission {
    private static final long serialVersionUID = -4733723479228998183L;

    /**
     * The listen action.
     */
    private final static int LISTEN = 0x1;
    /**
     * The connect action.
     */
    private final static int CONNECT = 0x2;
    /**
     * The accept action (includes the listen action).
     */
    private final static int ACCEPT = 0x4 | LISTEN;
    /**
     * The delegate action (includes the connect action).
     */
    private final static int DELEGATE = 0x8 | CONNECT;

    /**
     * The actions.
     *
     * @serial
     */
    private String actions;
    /**
     * The parsed elements of the local principals with wildcard principal
     * names replaced by null, or null if there is a principal with both a
     * wildcard class and a wildcard name. If there is an element with a
     * null principal name, no other element with the same class name will
     * exist.
     */
    private transient String[] me;
    /**
     * The parsed elements of the peer principals, or null if no peer
     * principals were specified.
     */
    private transient String[] peer;
    /**
     * The parsed actions as a bitmask.
     */
    private transient int mask;

    /**
     * Creates an instance with the specified target name and actions.
     *
     * @param name the target name
     * @param actions the actions
     * @throws NullPointerException if the target name or actions string is
     * <code>null</code>
     * @throws IllegalArgumentException if the target name or actions string
     * does not match the syntax specified in the comments at the beginning
     * of this class
     */
    public AuthenticationPermission(String name, String actions) {
	super(name);
	this.actions = actions;
	init();
    }

    /**
     * Creates an instance with the specified actions and a target name
     * constructed from the specified local and peer principals.
     *
     * @param local the local principals
     * @param peer the peer principals, or <code>null</code>
     * @param actions the actions
     * @throws NullPointerException if the local principals set or the
     * actions string is <code>null</code>
     * @throws IllegalArgumentException if the local principals set is
     * empty, or either set contains objects that are not
     * <code>java.security.Principal</code> instances, or the actions string
     * does not match the syntax specified in the comments at the beginning
     * of this class
     */
    public AuthenticationPermission(Set local, Set peer, String actions) {
	this(parseName(local, peer), actions);
    }

    /**
     * Internal structure to work around the fact that you can't do
     * computation on this prior to calling super() in a constructor.
     */
    private static final class Data {
	/**
	 * The target name.
	 */
	String name;
	/**
	 * The parsed elements of the local principals.
	 */
	String[] me;
	/**
	 * The parsed elements of the peer principals.
	 */
	String[] peer;

	/**
	 * Simple constructor.
	 */
	Data() {}
    }

    /**
     * Creates an instance with the specified data and actions.
     */
    private AuthenticationPermission(Data data, String actions) {
	super(data.name);
	this.me = data.me;
	this.peer = data.peer;
	this.actions = actions;
	parseActions();
    }

    /**
     * Parses the target name and actions, and initializes the transient
     * fields.
     */
    private void init() {
	parseActions();
	parseName(new StringTokenizer(getName(), " ", true), false);
    }

    /**
     * Parses the actions field and initializes the transient mask field.
     */
    private void parseActions() {
	StringTokenizer st = new StringTokenizer(actions, " ,", true);
	boolean comma = false;
	while (st.hasMoreTokens()) {
	    String act = st.nextToken();
	    if (act.equals(" ")) {
		continue;
	    } else if (comma) {
		if (!act.equals(",")) {
		    comma = false;
		    break;
		}
	    } else if (act.equalsIgnoreCase("connect")) {
		mask |= CONNECT;
	    } else if (act.equalsIgnoreCase("accept")) {
		mask |= ACCEPT;
	    } else if (act.equalsIgnoreCase("delegate")) {
		mask |= DELEGATE;
	    } else if (act.equalsIgnoreCase("listen")) {
		mask |= LISTEN;
	    } else {
		break;
	    }
	    comma = !comma;
	}
	if (!comma) {
	    throw new IllegalArgumentException("invalid actions");
	}
    }

    /**
     * Parses what's left of the target name in the specified tokenizer,
     * and initializes the transient fields. Peer is false when parsing
     * the local principals, true when parsing the peer principals.
     */
    private void parseName(StringTokenizer st, boolean peer) {
	List vals = new ArrayList(2);
    outer:
	while (true) {
	    String cls;
	    do {
		if (!st.hasMoreTokens()) {
		    break outer;
		}
		cls = st.nextToken();
	    } while (cls.equals(" "));
	    if (!peer && cls.equalsIgnoreCase("peer")) {
		parseName(st, true);
		break;
	    }
	    if (cls.equals("*")) {
		if (peer) {
		    throw new IllegalArgumentException(
						   "peer class cannot be *");
		}
		cls = null;
		vals = null;
	    }
	    String nm;
	    do {
		if (!st.hasMoreTokens()) {
		    throw new IllegalArgumentException(
						 "missing name after class");
		}
		nm = st.nextToken();
	    } while (nm.equals(" "));
	    if (!nm.startsWith("\"")) {
		throw new IllegalArgumentException("name must be in quotes");
	    }
	    while (!nm.endsWith("\"")) {
		if (!st.hasMoreTokens()) {
		    throw new IllegalArgumentException(
						   "name must be in quotes");
		}
		nm = nm + st.nextToken();
	    }
	    if (nm.equals("\"*\"")) {
		if (peer) {
		    throw new IllegalArgumentException(
						"peer name cannot be \"*\"");
		}
		if (cls == null) {
		    continue;
		}
		nm = null;
	    } else if (cls == null) {
		throw new IllegalArgumentException(
				   "class cannot be * unless name is \"*\"");
	    } else {
		nm = nm.substring(1, nm.length() - 1);
	    }
	    if (vals != null) {
		for (int i = vals.size(); i > 0; ) {
		    String onm = (String) vals.get(--i);
		    String ocls = (String) vals.get(--i);
		    if (cls.equals(ocls)) {
			if (onm == null || (onm != null && onm.equals(nm))) {
			    continue outer;
			} else if (nm == null) {
			    vals.remove(i);
			    vals.remove(i);
			}
		    }
		}
		vals.add(cls);
		vals.add(nm);
	    }
	}
	String[] res = null;
	if (vals != null) {
	    if (vals.isEmpty()) {
		throw new IllegalArgumentException(
					  "target name is missing elements");
	    }
	    res = (String[]) vals.toArray(new String[vals.size()]);
	}
	if (peer) {
	    this.peer = res;
	} else {
	    this.me = res;
	}
    }

    /**
     * Returns an array of alternating class and principal names for the
     * specified set of principals, and appends all of those strings to
     * the specified buffer, separated by spaces, with the principal
     * names in quotes.
     */
    private static String[] cons(Set s, StringBuffer b) {
	String[] vals = new String[s.size() * 2];
	int i = 0;
	for (Iterator iter = s.iterator(); iter.hasNext(); ) {
	    Principal p;
	    try {
		p = (Principal) iter.next();
	    } catch (ClassCastException e) {
		throw new IllegalArgumentException(
					    "sets must contain Principals");
	    }
	    String v = p.getClass().getName();
	    if (i > 0) {
		b.append(' ');
	    }
	    b.append(v);
	    vals[i++] = v;
	    v = p.getName();
	    b.append(" \"");
	    b.append(v);
	    b.append('"');
	    vals[i++] = v;
	}
	return vals;
    }

    /**
     * Constructs the target name and transient field data for the
     * specified principal sets.
     */
    private static Data parseName(Set me, Set peer) {
	if (me == null) {
	    throw new NullPointerException(
					"local principals must be non-empty");
	} else if (me.isEmpty()) {
	    throw new IllegalArgumentException(
					"local principals must be non-empty");
	}
	Data data = new Data();
	StringBuffer b = new StringBuffer();
	data.me = cons(me, b);
	if (peer != null && !peer.isEmpty()) {
	    b.append(" peer ");
	    data.peer = cons(peer, b);
	}
	data.name = b.toString();
	return data;
    }

    /**
     * Returns <code>true</code> if the specified permission is an instance
     * of <code>AuthenticationPermission</code>, and every action included in
     * the specified permission is included as an action of this permission,
     * and every principal that matches the local principals of the specified
     * permission also matches the local principals of this permission, and
     * (if the specified permission has any action besides
     * <code>listen</code>) every principal that matches the peer principals
     * of this permission also matches the peer principals of the specified
     * permission; returns <code>false</code> otherwise.
     *
     * @param perm the permission to check
     * @return <code>true</code> if the specified permission is an instance
     * of <code>AuthenticationPermission</code>, and every action included in
     * the specified permission is included as an action of this permission,
     * and every principal that matches the local principals of the specified
     * permission also matches the local principals of this permission, and
     * (if the specified permission has any action besides
     * <code>listen</code>) every principal that matches the peer principals
     * of this permission also matches the peer principals of the specified
     * permission; <code>false</code> otherwise
     */
    public boolean implies(Permission perm) {
	if (!(perm instanceof AuthenticationPermission)) {
	    return false;
	}
	AuthenticationPermission ap = (AuthenticationPermission) perm;
	return (mask & ap.mask) == ap.mask && implies0(ap);
    }

    private boolean implies0(AuthenticationPermission ap) {
	return ((me == null || (ap.me != null && covers(me, ap.me))) &&
		(ap.mask == LISTEN || peer == null ||
		 (ap.peer != null && covers(ap.peer, peer))));
    }

    /**
     * Returns true if every principal that matches sub also matches sup.
     */
    private static boolean covers(String[] sup, String[] sub) {
    outer:
	for (int i = sub.length; i > 0; ) {
	    String onm = sub[--i];
	    String ocls = sub[--i];
	    for (int j = sup.length; j > 0; ) {
		String nm = sup[--j];
		String cls = sup[--j];
		if (cls.equals(ocls) &&
		    (nm == null || (onm != null && nm.equals(onm))))
		{
		    continue outer;
		}
	    }
	    return false;
	}
	return true;
    }

    /**
     * Returns the actions.
     */
    public String getActions() {
	return actions;
    }

    /**
     * Returns an empty <code>PermissionCollection</code> for storing
     * <code>AuthenticationPermission</code> instances.
     *
     * @return an empty <code>PermissionCollection</code> for storing
     * <code>AuthenticationPermission</code> instances
     */
    public PermissionCollection newPermissionCollection() {
	return new AuthenticationPermissionCollection();
    }

    /**
     * Two instances of this class are equal if each implies the other;
     * that is, both instances have the same actions, every principal that
     * matches the local principals of one instance matches the local
     * principals of the other instance, and (if the instances have any
     * action besides <code>listen</code>) every principal that matches the
     * peer principals of one instance matches the peer principals of the
     * other instance.
     */
    public boolean equals(Object obj) {
	if (!(obj instanceof AuthenticationPermission)) {
	    return false;
	}
	AuthenticationPermission ap = (AuthenticationPermission) obj;
	return (mask == ap.mask && same(me, ap.me) &&
		(mask == LISTEN || same(peer, ap.peer)));
    }

    /**
     * Returns true if both arrays are null, or both arrays are the same
     * length and contain the same pairs (ignoring order).
     */
    private static boolean same(String[] s1, String[] s2) {
	if (s1 == null) {
	    return s2 == null;
	} else if (s2 == null || s1.length != s2.length) {
	    return false;
	}
    outer:
	for (int i = s2.length; i > 0; ) {
	    String onm = s2[--i];
	    String ocls = s2[--i];
	    for (int j = s1.length; j > 0; ) {
		String nm = s1[--j];
		String cls = s1[--j];
		if (cls.equals(ocls) &&
		    (nm == null ? onm == null : nm.equals(onm)))
		{
		    continue outer;
		}
	    }
	    return false;
	}
	return true;
    }

    /**
     * Returns a hash code value for this object.
     */
    public int hashCode() {
	int h = mask;
	if (me != null) {
	    for (int i = me.length; --i >= 0; ) {
		if (me[i] != null) {
		    h += me[i].hashCode();
		}
	    }
	}
	if (mask != LISTEN && peer != null) {
	    for (int i = peer.length; --i >= 0; ) {
		h += peer[i].hashCode();
	    }
	}
	return h;
    }

    /**
     * Verifies the syntax of the target name and recreates any transient
     * state.
     *
     * @throws java.io.InvalidObjectException if the target name or actions
     * string is <code>null</code>, or if the target name or actions string
     * does not match the syntax specified in the comments at the beginning
     * of this class
     */
    private void readObject(ObjectInputStream s)
	throws IOException, ClassNotFoundException
    {
	s.defaultReadObject();
	try {
	    init();
	} catch (RuntimeException e) {
	    if (e instanceof NullPointerException ||
		e instanceof IllegalArgumentException)
	    {
		InvalidObjectException ee =
		    new InvalidObjectException(e.getMessage());
		ee.initCause(e);
		throw ee;
	    }
	    throw e;
	}
    }

    /**
     * @serial include
     */
    static class AuthenticationPermissionCollection
				extends PermissionCollection
    {
	private static final long serialVersionUID = -2967578431368213049L;

	/**
	 * @serialField permissions List The permissions.
	 */
	private static final ObjectStreamField[] serialPersistentFields = {
	    new ObjectStreamField("permissions", List.class, true)
	};

	private List permissions = new ArrayList();

	AuthenticationPermissionCollection() {
	}

	public synchronized void add(Permission perm) {
	    if (!(perm instanceof AuthenticationPermission)) {
		throw new IllegalArgumentException(
			       "element must be an AuthenticationPermission");
	    } else if (isReadOnly()) {
		throw new SecurityException("collection is read-only");
	    }
	    permissions.add(perm);
	}

	public synchronized boolean implies(Permission perm) {
	    if (!(perm instanceof AuthenticationPermission)) {
		return false;
	    }
	    AuthenticationPermission ap = (AuthenticationPermission) perm;
	    int needed = ap.mask;
	    for (int i = permissions.size(); --i >= 0; ) {
		AuthenticationPermission cp =
		    (AuthenticationPermission) permissions.get(i);
		if ((needed & cp.mask) != 0 && cp.implies0(ap)) {
		    needed &= ~cp.mask;
		    if (needed == 0) {
			return true;
		    }
		}
	    }
	    return false;
	}

	public synchronized Enumeration elements() {
	    return Collections.enumeration(permissions);
	}

	public synchronized void setReadOnly() {
	    super.setReadOnly();
	}

	public synchronized boolean isReadOnly() {
	    return super.isReadOnly();
	}

	/**
	 * Verifies the permissions list.
	 *
	 * @throws java.io.InvalidObjectException if the list is
	 * <code>null</code> or any element is not an instance of
	 * <code>AuthenticationPermission</code>
	 */
	private void readObject(ObjectInputStream s)
	    throws IOException, ClassNotFoundException
	{
	    s.defaultReadObject();
	    if (permissions == null) {
		throw new InvalidObjectException("list cannot be null");
	    }
	    if (!permissions.getClass().equals(ArrayList.class)) {
		permissions = new ArrayList(permissions);
	    }
	    for (int i = permissions.size(); --i >= 0; ) {
		if (!(permissions.get(i) instanceof AuthenticationPermission))
		{
		    throw new InvalidObjectException(
			       "element must be an AuthenticationPermission");
		}
	    }
	}

	/**
	 * Writes the state to the stream.
	 */
	private synchronized void writeObject(ObjectOutputStream s)
	    throws IOException
	{
	    s.defaultWriteObject();
	}
    }
}
