/*
* 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.security.Permission;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
/**
* Represents permission to call a method. An instance of this class
* contains a name (also referred to as a "target name") but no actions list;
* you either have the named permission or you don't. The target name can be
* any of the following forms:
* <pre>
* *
* <i>Identifier</i>
* *<i>Suffix</i>
* <i>Identifier</i>*
* <i>QualifiedIdentifier</i>.*
* <i>QualifiedIdentifier</i>.<i>Identifier</i>
* <i>QualifiedIdentifier</i>.*<i>Suffix</i>
* <i>QualifiedIdentifier</i>.<i>Identifier</i>*
* </pre>
* where <i>QualifiedIdentifier</i> and <i>Identifier</i> are as defined in
* <i>The Java(TM) Language Specification</i> except that whitespace is not
* permitted, and <i>Suffix</i> is defined to be one or more characters
* that may be part of an <i>Identifier</i>. These forms are defined to
* match fully qualified names of the form
* <i>QualifiedIdentifier</i>.<i>Identifier</i> as follows:
* <table border=1 cellpadding=5
* summary="Describes target name forms and matching semantics">
* <tr>
* <th>Target Name</th>
* <th><i>QualifiedIdentifier</i> Match</th>
* <th><i>Identifier</i> Match</th>
* </tr>
* <tr>
* <td>*</td>
* <td>any</td>
* <td>any</td>
* </tr>
* <tr>
* <td><b>method</b></td>
* <td>any</td>
* <td><b>method</b></td>
* </tr>
* <tr>
* <td>*<b>suffix</b></td>
* <td>any</td>
* <td>any ending with <b>suffix</b></td>
* </tr>
* <tr>
* <td><b>prefix</b>*</td>
* <td>any</td>
* <td>any starting with <b>prefix</b></td>
* </tr>
* <tr>
* <td><b>type</b>.*</td>
* <td><b>type</b></td>
* <td>any</td>
* </tr>
* <tr>
* <td><b>type</b>.<b>method</b></td>
* <td><b>type</b></td>
* <td><b>method</b></td>
* </tr>
* <tr>
* <td><b>type</b>.*<b>suffix</b></td>
* <td><b>type</b></td>
* <td>any ending with <b>suffix</b></td>
* </tr>
* <tr>
* <td><b>type</b>.<b>prefix</b>*</td>
* <td><b>type</b></td>
* <td>any starting with <b>prefix</b></td>
* </tr>
* </table>
* <p>
* This class, and simple subclasses of it, can be used (for example) with
* {@link net.jini.jeri.BasicInvocationDispatcher}. It is
* recommended that a simple subclass of this class be defined for each
* remote object implementation class that can be exported using an
* {@link net.jini.export.Exporter}, to allow separation of
* grants in policy files.
*
* @author Sun Microsystems, Inc.
* @since 2.0
*/
public class AccessPermission extends Permission {
private static final long serialVersionUID = 7269818741475881138L;
/**
* The interface name, or null if wildcarded.
*/
private transient String iface;
/**
* The name of the method, with prefix or suffix '*' permitted,
* or null if wildcarded.
*/
private transient String method;
/**
* Creates an instance with the specified target name.
*
* @param name the target name
* @throws NullPointerException if the target name is <code>null</code>
* @throws IllegalArgumentException if the target name does not match
* the syntax specified in the comments at the beginning of this class
*/
public AccessPermission(String name) {
super(name);
init(name);
}
/**
* Parses the target name and initializes the transient fields.
*/
private void init(String name) {
if (name == null) {
throw new NullPointerException("name cannot be null");
} else if (name.length() == 0) {
throw new IllegalArgumentException("name cannot be empty");
}
int i = name.lastIndexOf('.');
if (i >= 0) {
iface = name.substring(0, i);
name = name.substring(i + 1);
}
if (!name.equals("*")) {
method = name;
}
if (iface != null && !validClass(iface)) {
throw new IllegalArgumentException("invalid interface name");
} else if (method != null && !validMethod(method)) {
throw new IllegalArgumentException("invalid method name");
}
}
/**
* Returns true if the name is a syntactically valid fully qualified
* class name (no whitespace permitted), and returns false otherwise.
*/
private static boolean validClass(String name) {
int len = name.length();
outer:
for (int i = 0;
i < len && Character.isJavaIdentifierStart(name.charAt(i));
i++)
{
while (++i < len) {
char c = name.charAt(i);
if (c == '.') {
continue outer;
}
if (!Character.isJavaIdentifierPart(c)) {
return false;
}
}
return true;
}
return false;
}
/**
* Returns true if the name is a syntactically valid method name, or
* if the name is a syntactically valid method name with a '*' appended
* or could be constructed from some syntactically valid method name
* containing more than two characters by replacing the first character
* of that name with '*', and returns false otherwise.
*/
private static boolean validMethod(String name) {
int len = name.length();
if (len == 0) {
return false;
}
char c = name.charAt(0);
if (!Character.isJavaIdentifierStart(c) && !(c == '*' && len > 1)) {
return false;
}
if (c != '*' && name.charAt(len - 1) == '*') {
len--;
}
while (--len >= 1) {
if (!Character.isJavaIdentifierPart(name.charAt(len))) {
return false;
}
}
return true;
}
/**
* Returns <code>true</code> if every fully qualified name that
* matches the specified permission's name also matches this
* permission's name; returns <code>false</code> otherwise.
*
* @param perm the permission to check
* @return <code>true</code> if every fully qualified name that
* matches the specified permission's name also matches this
* permission's name; <code>false</code> otherwise
*/
public boolean implies(Permission perm) {
if (perm == null || perm.getClass() != getClass()) {
return false;
}
AccessPermission ap = (AccessPermission) perm;
if (iface != null && !iface.equals(ap.iface)) {
return false;
} else if (method == null) {
return true;
} else if (ap.method == null) {
return false;
}
int len = method.length() - 1;
if (method.charAt(0) == '*') {
return ap.method.regionMatches(ap.method.length() - len,
method, 1, len);
} else if (method.charAt(len) == '*') {
return ap.method.regionMatches(0, method, 0, len);
}
return method.equals(ap.method);
}
/**
* Returns <code>true</code> if the specified object is an instance
* of the same class as this permission and has the same target name
* as this permission; returns <code>false</code> otherwise.
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj == null || obj.getClass() != getClass()) {
return false;
}
return getName().equals(((Permission) obj).getName());
}
/**
* Returns a hash code value for this object.
*/
public int hashCode() {
return getName().hashCode();
}
/**
* Returns the empty string.
*/
public String getActions() {
return "";
}
/**
* Verifies the syntax of the target name and recreates any transient
* state.
*
* @throws InvalidObjectException if the target name is <code>null</code>,
* or if the target name 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();
Exception cause;
try {
init(getName());
return;
} catch (NullPointerException e) {
cause = e;
} catch (IllegalArgumentException e) {
cause = e;
}
InvalidObjectException e =
new InvalidObjectException(cause.getMessage());
e.initCause(cause);
throw e;
}
}