[BUG-277551] promote to trunk so we can review in full context if this implemenation is the way to go

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@365 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Jesse McConnell 2009-06-11 16:20:07 +00:00
parent b9b14e438e
commit eea741beb8
15 changed files with 1521 additions and 0 deletions

40
jetty-policy/pom.xml Normal file
View File

@ -0,0 +1,40 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>7.0.0.M3-SNAPSHOT</version>
</parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-policy</artifactId>
<name>Jetty :: Policy Tool</name>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>config.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,107 @@
// basic policy file for jetty
// TODO update with greg's latest property changes and set better reasonable defaults for various jetty codeBases
grant codeBase "file:${jetty.home}${/}-" {
permission java.io.FilePermission "${jetty.home}${/}-", "read";
permission java.io.FilePermission "${jetty.home}${/}logs${/}-", "read, write";
permission java.lang.RuntimePermission "createClassLoader";
permission java.lang.RuntimePermission "setContextClassLoader";
permission java.security.SecurityPermission "getPolicy";
permission java.lang.RuntimePermission "accessDeclaredMembers";
// makes everything work as a crutch to work on startup
permission java.security.AllPermission;
};
// default permissions granted to all domains
grant {
// Allows any thread to stop itself using the java.lang.Thread.stop()
// method that takes no argument.
// Note that this permission is granted by default only to remain
// backwards compatible.
// It is strongly recommended that you either remove this permission
// from this policy file or further restrict it to code sources
// that you specify, because Thread.stop() is potentially unsafe.
// See "http://java.sun.com/notes" for more information.
permission java.lang.RuntimePermission "stopThread";
// allows anyone to listen on un-privileged ports
permission java.net.SocketPermission "localhost:1024-", "listen";
// "standard" properties that can be read by anyone
permission java.util.PropertyPermission "java.version", "read";
permission java.util.PropertyPermission "java.vendor", "read";
permission java.util.PropertyPermission "java.vendor.url", "read";
permission java.util.PropertyPermission "java.class.version", "read";
permission java.util.PropertyPermission "os.name", "read";
permission java.util.PropertyPermission "os.version", "read";
permission java.util.PropertyPermission "os.arch", "read";
permission java.util.PropertyPermission "file.separator", "read";
permission java.util.PropertyPermission "path.separator", "read";
permission java.util.PropertyPermission "line.separator", "read";
permission java.util.PropertyPermission "java.specification.version", "read";
permission java.util.PropertyPermission "java.specification.vendor", "read";
permission java.util.PropertyPermission "java.specification.name", "read";
permission java.util.PropertyPermission "java.vm.specification.version", "read";
permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
permission java.util.PropertyPermission "java.vm.specification.name", "read";
permission java.util.PropertyPermission "java.vm.version", "read";
permission java.util.PropertyPermission "java.vm.vendor", "read";
permission java.util.PropertyPermission "java.vm.name", "read";
// jetty specific properties
permission java.util.PropertyPermission "org.eclipse.jetty.util.log.DEBUG", "read";
permission java.util.PropertyPermission "START", "read";
permission java.util.PropertyPermission "org.eclipse.jetty.util.log.VERBOSE", "read";
permission java.util.PropertyPermission "STOP.PORT", "read";
permission java.util.PropertyPermission "STOP.KEY", "read";
permission java.util.PropertyPermission "org.eclipse.jetty.util.log.IGNORED", "read";
permission java.util.PropertyPermission "CLASSPATH", "read";
permission java.util.PropertyPermission "OPTIONS", "read";
permission java.util.PropertyPermission "JETTY_NO_SHUTDOWN_HOOK", "read";
permission java.util.PropertyPermission "ISO_8859_1", "read";
permission java.util.PropertyPermission "jetty.home", "read, write";
permission java.util.PropertyPermission "user.home", "read";
permission java.util.PropertyPermission "jetty.class.path", "read, write";
permission java.util.PropertyPermission "java.class.path", "read, write";
permission java.util.PropertyPermission "repository", "read, write";
permission java.util.PropertyPermission "jetty.lib", "read";
permission java.util.PropertyPermission "jetty.server", "read";
permission java.util.PropertyPermission "jetty.host", "read";
permission java.util.PropertyPermission "jetty.port", "read";
permission java.util.PropertyPermission "start.class", "read";
permission java.util.PropertyPermission "main.class", "read";
permission java.util.PropertyPermission "org.eclipse.jetty.util.log.class", "read";
permission java.util.PropertyPermission "org.eclipse.jetty.util.URI.charset", "read";
permission java.util.PropertyPermission "org.eclipse.jetty.util.FileResource.checkAliases", "read";
permission java.util.PropertyPermission "org.eclipse.jetty.xml.XmlParser.Validating", "read";
permission java.util.PropertyPermission "org.eclipse.jetty.io.nio.JVMBUG_THRESHHOLD", "read, write";
permission java.util.PropertyPermission "org.eclipse.jetty.util.TypeUtil.IntegerCacheSize", "read, write";
permission java.util.PropertyPermission "org.eclipse.jetty.util.TypeUtil.LongCacheSize", "read";
// provides access to webapps
permission java.io.FilePermission "${jetty.home}${/}webapps${/}-", "read"; // Ought to go up a specific codebase
};

View File

@ -0,0 +1,155 @@
package org.eclipse.jetty.policy;
//========================================================================
//Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//========================================================================
import java.io.File;
import java.io.FileInputStream;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.jetty.policy.loader.DefaultPolicyLoader;
/**
* Policy implementation that will load a set of policy files and manage the mapping of permissions and protection domains
*
* The reason I created this class and added this mechanism are:
*
* 1) I wanted a way to be able to follow the startup mechanic that jetty uses with jetty-start using OPTIONS=policy,default
* to be able to startup a security manager and policy implementation without have to rely on the existing JVM cli options
* 2) establish a starting point to add on further functionality to permissions based security with jetty like jmx enabled
* permission tweaking or runtime creation and specification of policies for specific webapps
* 3) I wanted to have support for specifying multiple policy files to source permissions from
*
* Possible additions are:
* - jmx enabled a la #2 above
* - proxying of system security policy where we can proxy access to the system policy should the jvm have been started with
* one, I had support for this but ripped it out to add in again later
* - merging of protection domains if process multiple policy files that declare permissions for the same codebase
* - an xml policy file parser, had originally added this using modello but tore it out since it would have been
* a nightmare to get its dependencies through IP validation, could do this with jvm xml parser instead sometime
* - check performance of the synch'd map I am using for the protection domain mapping
*/
public class JettyPolicy extends Policy
{
// Policy files that are actively managed by the aggregate policy mechanism
private Set<String> _policies;
private PropertyEvaluator _evaluator;
private Map<ProtectionDomain, PermissionCollection> pdMapping =
Collections.synchronizedMap( new HashMap<ProtectionDomain, PermissionCollection>() );
public JettyPolicy( Set<String> policies, Map<String,String> properties )
{
_policies = policies;
_evaluator = new PropertyEvaluator( properties );
// we have the policies we need and an evaluator to reference, lets refresh and save the user a call.
refresh();
}
public PermissionCollection getPermissions( ProtectionDomain domain )
{
PermissionCollection perms = new Permissions();
for ( Iterator<ProtectionDomain> i = pdMapping.keySet().iterator(); i.hasNext(); )
{
ProtectionDomain pd = (ProtectionDomain) i.next();
if ( pd.getCodeSource() == null || pd.getCodeSource().implies( domain.getCodeSource() ) )
{
// gather dynamic permissions
if ( pdMapping.get( pd ) != null )
{
for ( Enumeration<Permission> e = pdMapping.get( pd ).elements(); e.hasMoreElements(); )
{
perms.add( e.nextElement() );
}
}
// gather static permissions
if ( pd.getPermissions() != null )
{
for ( Enumeration<Permission> e = pd.getPermissions().elements(); e.hasMoreElements(); )
{
perms.add( e.nextElement() );
}
}
}
}
return perms;
}
public PermissionCollection getPermissions( CodeSource codesource )
{
PermissionCollection perms = new Permissions();
for ( Iterator<ProtectionDomain> i = pdMapping.keySet().iterator(); i.hasNext(); )
{
ProtectionDomain pd = (ProtectionDomain) i.next();
if ( pd.getCodeSource() == null || pd.getCodeSource().implies( codesource ) )
{
// gather dynamic permissions
if ( pdMapping.get( pd ) != null )
{
for ( Enumeration<Permission> e = pdMapping.get( pd ).elements(); e.hasMoreElements(); )
{
perms.add( e.nextElement() );
}
}
// gather static permissions
if ( pd.getPermissions() != null )
{
for ( Enumeration<Permission> e = pd.getPermissions().elements(); e.hasMoreElements(); )
{
perms.add( e.nextElement() );
}
}
}
}
return perms;
}
public void refresh()
{
try
{
pdMapping.clear();
for ( Iterator<String> i = _policies.iterator(); i.hasNext(); )
{
File policyFile = new File( i.next() );
pdMapping.putAll( DefaultPolicyLoader.load( new FileInputStream( policyFile ), _evaluator ) );
}
}
catch ( Exception e )
{
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,40 @@
package org.eclipse.jetty.policy;
//========================================================================
//Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//========================================================================
public class PolicyException extends Exception
{
public PolicyException()
{
super();
}
public PolicyException( final String message, final Throwable cause)
{
super( message, cause );
}
public PolicyException( final String message )
{
super( message );
}
public PolicyException( final Throwable cause )
{
super( cause );
}
}

View File

@ -0,0 +1,92 @@
package org.eclipse.jetty.policy;
//========================================================================
//Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//========================================================================
import java.io.File;
import java.util.HashMap;
import java.util.Map;
/*
* Property evaluator to provide common reference object for property access and evaluation
*
* The origin of this class was in the Main.class of the jetty-start package where is where
* it picks up the convention of $() properties and ${} system properties
*
* Not exactly sun convention but a jetty convention none the less
*/
public class PropertyEvaluator extends HashMap<String,String>
{
private static final long serialVersionUID = -7745629868268683553L;
public PropertyEvaluator( Map<String,String> properties )
{
putAll( properties );
put("/", File.separator ); // '/' is a special case when evaluated itself, resolves to File.separator as per policy parsing convention
}
/**
* returns the value of it exists in this map, otherwise consults the System properties
*
* @param name
* @return
*/
public String getSystemProperty(String name)
{
if (containsKey(name))
return get(name);
return System.getProperty(name);
}
public String getProperty(String name)
{
return get(name);
}
/* ------------------------------------------------------------ */
public String evaluate(String s)
{
int i1=0;
int i2=0;
while (s!=null)
{
i1=s.indexOf("$(",i2);
if (i1<0)
break;
i2=s.indexOf(")",i1+2);
if (i2<0)
break;
String name=s.substring(i1+2,i2);
String property=getSystemProperty(name);
s=s.substring(0,i1)+property+s.substring(i2+1);
}
i1=0;
i2=0;
while (s!=null)
{
i1=s.indexOf("${",i2);
if (i1<0)
break;
i2=s.indexOf("}",i1+2);
if (i2<0)
break;
String name=s.substring(i1+2,i2);
String property=getProperty(name);
s=s.substring(0,i1)+property+s.substring(i2+1);
}
return s;
}
}

View File

@ -0,0 +1,267 @@
package org.eclipse.jetty.policy.loader;
//========================================================================
//Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//
//Portions of this file adapted for use from Apache Harmony code by written
//and contributed to that project by Alexey V. Varlamov under the ASL
//========================================================================
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.jetty.policy.PolicyException;
import org.eclipse.jetty.policy.PropertyEvaluator;
/**
* Load the policies within the stream and resolve into protection domains and permission collections
*
* TODO: currently this loading does not support keystores or certificates
*/
public class DefaultPolicyLoader
{
public static Map<ProtectionDomain, PermissionCollection> load( InputStream policyStream, PropertyEvaluator evaluator ) throws PolicyException
{
Map<ProtectionDomain, PermissionCollection> pdMappings = new HashMap<ProtectionDomain, PermissionCollection>();
try
{
PolicyFileScanner loader = new PolicyFileScanner();
Collection<GrantEntry> grantEntries = new ArrayList<GrantEntry>();
List<KeystoreEntry> keystoreEntries = new ArrayList<KeystoreEntry>();
loader.scanStream( new InputStreamReader(policyStream), grantEntries, keystoreEntries );
for ( Iterator<GrantEntry> i = grantEntries.iterator(); i.hasNext(); )
{
GrantEntry grant = i.next();
Permissions permissions = processPermissions( grant.permissions, evaluator );
ProtectionDomain pd;
if ( grant.codebase == null ) // these are hereby known as global permissions (no codebase associated)
{
pd = new ProtectionDomain( null, permissions );
}
else
{
CodeSource codeSource = resolveToCodeSource( grant.codebase, evaluator );
pd = new ProtectionDomain( codeSource, permissions );
}
pdMappings.put( pd, null );
}
return pdMappings;
}
catch ( Exception e )
{
throw new PolicyException( e );
}
}
private static Permissions processPermissions( Collection<PermissionEntry> collection, PropertyEvaluator evaluator ) throws PolicyException
{
Permissions permissions = new Permissions();
for ( Iterator<PermissionEntry> i = collection.iterator(); i.hasNext(); )
{
PermissionEntry perm = i.next();
Class clazz;
try
{
clazz = Class.forName( perm.klass );
if ( perm.name == null && perm.actions == null )
{
permissions.add( (Permission)clazz.newInstance() );
}
else if ( perm.name != null && perm.actions == null )
{
Constructor c = clazz.getConstructor(new Class[] { String.class });
permissions.add( (Permission)c.newInstance( evaluator.evaluate( perm.name ) ) );
}
else if ( perm.name != null && perm.actions != null )
{
Constructor c = clazz.getConstructor(new Class[] { String.class, String.class });
permissions.add( (Permission)c.newInstance( evaluator.evaluate( perm.name ), perm.actions ) );
}
}
catch ( Exception e )
{
throw new PolicyException( e );
}
}
return permissions;
}
private static CodeSource resolveToCodeSource( String codeBase, PropertyEvaluator evaluator ) throws PolicyException
{
try
{
URL url = new URL( evaluator.evaluate(codeBase) );
Certificate[] cert = null;
return new CodeSource( url, cert); //TODO support certificates
}
catch ( Exception e )
{
throw new PolicyException( e );
}
}
/**
* Compound token representing <i>keystore </i> clause. See policy format
* {@link org.apache.harmony.security.DefaultPolicy description}for details.
*
* @see org.apache.harmony.security.fortress.DefaultPolicyParser
* @see org.apache.harmony.security.DefaultPolicyScanner
*/
public static class KeystoreEntry
{
/**
* The URL part of keystore clause.
*/
public String url;
/**
* The typename part of keystore clause.
*/
public String type;
}
/**
* Compound token representing <i>grant </i> clause. See policy format
* {@link org.apache.harmony.security.DefaultPolicy description}for details.
*
* @see org.apache.harmony.security.fortress.DefaultPolicyParser
* @see org.apache.harmony.security.DefaultPolicyScanner
*/
public static class GrantEntry
{
/**
* The signers part of grant clause. This is a comma-separated list of certificate aliases.
*/
public String signers;
/**
* The codebase part of grant clause. This is an URL from which code originates.
*/
public String codebase;
/**
* Collection of PrincipalEntries of grant clause.
*/
public Collection<PrincipalEntry> principals;
/**
* Collection of PermissionEntries of grant clause.
*/
public Collection<PermissionEntry> permissions;
/**
* Adds specified element to the <code>principals</code> collection. If collection does not exist yet, creates a
* new one.
*/
public void addPrincipal( PrincipalEntry pe )
{
if ( principals == null )
{
principals = new HashSet<PrincipalEntry>();
}
principals.add( pe );
}
}
/**
* Compound token representing <i>principal </i> entry of a <i>grant </i> clause. See policy format
* {@link org.apache.harmony.security.DefaultPolicy description}for details.
*
* @see org.apache.harmony.security.fortress.DefaultPolicyParser
* @see org.apache.harmony.security.DefaultPolicyScanner
*/
public static class PrincipalEntry
{
/**
* Wildcard value denotes any class and/or any name. Must be asterisk, for proper general expansion and
* PrivateCredentialsPermission wildcarding
*/
public static final String WILDCARD = "*"; //$NON-NLS-1$
/**
* The classname part of principal clause.
*/
public String klass;
/**
* The name part of principal clause.
*/
public String name;
}
/**
* Compound token representing <i>permission </i> entry of a <i>grant </i> clause. See policy format
* {@link org.apache.harmony.security.DefaultPolicy description}for details.
*
* @see org.apache.harmony.security.fortress.DefaultPolicyParser
* @see org.apache.harmony.security.DefaultPolicyScanner
*/
public static class PermissionEntry
{
/**
* The classname part of permission clause.
*/
public String klass;
/**
* The name part of permission clause.
*/
public String name;
/**
* The actions part of permission clause.
*/
public String actions;
/**
* The signers part of permission clause. This is a comma-separated list of certificate aliases.
*/
public String signers;
}
}

View File

@ -0,0 +1,469 @@
package org.eclipse.jetty.policy.loader;
/*
* 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.
*/
//========================================================================
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//
// This file adapted for use from Apache Harmony code by written and contributed
// to that project by Alexey V. Varlamov under the ASL-2.0
//
// See CQ3380
//========================================================================
import java.io.IOException;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import org.eclipse.jetty.policy.loader.DefaultPolicyLoader.GrantEntry;
import org.eclipse.jetty.policy.loader.DefaultPolicyLoader.KeystoreEntry;
import org.eclipse.jetty.policy.loader.DefaultPolicyLoader.PermissionEntry;
import org.eclipse.jetty.policy.loader.DefaultPolicyLoader.PrincipalEntry;
/**
* This is a basic high-level tokenizer of policy files. It takes in a stream, analyzes data read from it and returns a
* set of structured tokens. <br>
* This implementation recognizes text files, consisting of clauses with the following syntax:
*
* <pre>
*
* keystore &quot;some_keystore_url&quot;, &quot;keystore_type&quot;;
*
* </pre>
*
* <pre>
*
* grant [SignedBy &quot;signer_names&quot;] [, CodeBase &quot;URL&quot;]
* [, Principal [principal_class_name] &quot;principal_name&quot;]
* [, Principal [principal_class_name] &quot;principal_name&quot;] ... {
* permission permission_class_name [ &quot;target_name&quot; ] [, &quot;action&quot;]
* [, SignedBy &quot;signer_names&quot;];
* permission ...
* };
*
* </pre>
*
* For semantical details of this format, see the {@link org.apache.harmony.security.DefaultPolicy default policy
* description}. <br>
* Keywords are case-insensitive in contrast to quoted string literals. Comma-separation rule is quite forgiving, most
* commas may be just omitted. Whitespaces, line- and block comments are ignored. Symbol-level tokenization is delegated
* to java.io.StreamTokenizer. <br>
* <br>
* This implementation is effectively thread-safe, as it has no field references to data being processed (that is,
* passes all the data as method parameters).
*
*/
public class PolicyFileScanner
{
/**
* Specific exception class to signal policy file syntax error.
*/
public static class InvalidFormatException
extends Exception
{
/**
* @serial
*/
private static final long serialVersionUID = 5789786270390222184L;
/**
* Constructor with detailed message parameter.
*/
public InvalidFormatException( String arg0 )
{
super( arg0 );
}
}
/**
* Configures passed tokenizer accordingly to supported syntax.
*/
protected StreamTokenizer configure( StreamTokenizer st )
{
st.slashSlashComments( true );
st.slashStarComments( true );
st.wordChars( '_', '_' );
st.wordChars( '$', '$' );
return st;
}
/**
* Performs the main parsing loop. Starts with creating and configuring a StreamTokenizer instance; then tries to
* recognize <i>keystore </i> or <i>grant </i> keyword. When found, invokes read method corresponding to the clause
* and collects result to the passed collection.
*
* @param r policy stream reader
* @param grantEntries a collection to accumulate parsed GrantEntries
* @param keystoreEntries a collection to accumulate parsed KeystoreEntries
* @throws IOException if stream reading failed
* @throws InvalidFormatException if unexpected or unknown token encountered
*/
public void scanStream( Reader r, Collection<GrantEntry> grantEntries, List<KeystoreEntry> keystoreEntries )
throws IOException, InvalidFormatException
{
StreamTokenizer st = configure( new StreamTokenizer( r ) );
// main parsing loop
parsing: while ( true )
{
switch ( st.nextToken() )
{
case StreamTokenizer.TT_EOF: // we've done the job
break parsing;
case StreamTokenizer.TT_WORD:
if ( Util.equalsIgnoreCase( "keystore", st.sval ) ) { //$NON-NLS-1$
keystoreEntries.add( readKeystoreEntry( st ) );
}
else if ( Util.equalsIgnoreCase( "grant", st.sval ) ) { //$NON-NLS-1$
grantEntries.add( readGrantEntry( st ) );
}
else
{
handleUnexpectedToken( st, "Expected entries are : \"grant\" or \"keystore\"" ); //$NON-NLS-1$
}
break;
case ';': // just delimiter of entries
break;
default:
handleUnexpectedToken( st );
break;
}
}
}
/**
* Tries to read <i>keystore </i> clause fields. The expected syntax is
*
* <pre>
*
* &quot;some_keystore_url&quot;[, &quot;keystore_type&quot;];
*
* </pre>
*
* @return successfully parsed KeystoreEntry
* @throws IOException if stream reading failed
* @throws InvalidFormatException if unexpected or unknown token encountered
*/
protected KeystoreEntry readKeystoreEntry( StreamTokenizer st )
throws IOException, InvalidFormatException
{
KeystoreEntry ke = new KeystoreEntry();
if ( st.nextToken() == '"' )
{
ke.url = st.sval;
if ( ( st.nextToken() == '"' ) || ( ( st.ttype == ',' ) && ( st.nextToken() == '"' ) ) )
{
ke.type = st.sval;
}
else
{ // handle token in the main loop
st.pushBack();
}
}
else
{
handleUnexpectedToken( st, "Expected syntax is : keystore \"url\"[, \"type\"]" ); //$NON-NLS-1$
}
return ke;
}
/**
* Tries to read <i>grant </i> clause. <br>
* First, it reads <i>codebase </i>, <i>signedby </i>, <i>principal </i> entries till the '{' (opening curly brace)
* symbol. Then it calls readPermissionEntries() method to read the permissions of this clause. <br>
* Principal entries (if any) are read by invoking readPrincipalEntry() method, obtained PrincipalEntries are
* accumulated. <br>
* The expected syntax is
*
* <pre>
*
* [ [codebase &quot;url&quot;] | [signedby &quot;name1,...,nameN&quot;] |
* principal ...] ]* { ... }
*
* </pre>
*
* @return successfully parsed GrantEntry
* @throws IOException if stream reading failed
* @throws InvalidFormatException if unexpected or unknown token encountered
*/
protected GrantEntry readGrantEntry( StreamTokenizer st )
throws IOException, InvalidFormatException
{
GrantEntry ge = new GrantEntry();
parsing: while ( true )
{
switch ( st.nextToken() )
{
case StreamTokenizer.TT_WORD:
if ( Util.equalsIgnoreCase( "signedby", st.sval ) ) { //$NON-NLS-1$
if ( st.nextToken() == '"' )
{
ge.signers = st.sval;
}
else
{
handleUnexpectedToken( st, "Expected syntax is : signedby \"name1,...,nameN\"" ); //$NON-NLS-1$
}
}
else if ( Util.equalsIgnoreCase( "codebase", st.sval ) ) { //$NON-NLS-1$
if ( st.nextToken() == '"' )
{
ge.codebase = st.sval;
}
else
{
handleUnexpectedToken( st, "Expected syntax is : codebase \"url\"" ); //$NON-NLS-1$
}
}
else if ( Util.equalsIgnoreCase( "principal", st.sval ) ) { //$NON-NLS-1$
ge.addPrincipal( readPrincipalEntry( st ) );
}
else
{
handleUnexpectedToken( st );
}
break;
case ',': // just delimiter of entries
break;
case '{':
ge.permissions = readPermissionEntries( st );
break parsing;
default: // handle token in the main loop
st.pushBack();
break parsing;
}
}
return ge;
}
/**
* Tries to read <i>Principal </i> entry fields. The expected syntax is
*
* <pre>
*
* [ principal_class_name ] &quot;principal_name&quot;
*
* </pre>
*
* Both class and name may be wildcards, wildcard names should not surrounded by quotes.
*
* @return successfully parsed PrincipalEntry
* @throws IOException if stream reading failed
* @throws InvalidFormatException if unexpected or unknown token encountered
*/
protected PrincipalEntry readPrincipalEntry( StreamTokenizer st )
throws IOException, InvalidFormatException
{
PrincipalEntry pe = new PrincipalEntry();
if ( st.nextToken() == StreamTokenizer.TT_WORD )
{
pe.klass = st.sval;
st.nextToken();
}
else if ( st.ttype == '*' )
{
pe.klass = PrincipalEntry.WILDCARD;
st.nextToken();
}
if ( st.ttype == '"' )
{
pe.name = st.sval;
}
else if ( st.ttype == '*' )
{
pe.name = PrincipalEntry.WILDCARD;
}
else
{
handleUnexpectedToken( st, "Expected syntax is : principal [class_name] \"principal_name\"" ); //$NON-NLS-1$
}
return pe;
}
/**
* Tries to read a list of <i>permission </i> entries. The expected syntax is
*
* <pre>
*
* permission permission_class_name
* [ &quot;target_name&quot; ] [, &quot;action_list&quot;]
* [, signedby &quot;name1,name2,...&quot;];
*
* </pre>
*
* List is terminated by '}' (closing curly brace) symbol.
*
* @return collection of successfully parsed PermissionEntries
* @throws IOException if stream reading failed
* @throws InvalidFormatException if unexpected or unknown token encountered
*/
protected Collection<PermissionEntry> readPermissionEntries( StreamTokenizer st )
throws IOException, InvalidFormatException
{
Collection<PermissionEntry> permissions = new HashSet<PermissionEntry>();
parsing: while ( true )
{
switch ( st.nextToken() )
{
case StreamTokenizer.TT_WORD:
if ( Util.equalsIgnoreCase( "permission", st.sval ) ) { //$NON-NLS-1$
PermissionEntry pe = new PermissionEntry();
if ( st.nextToken() == StreamTokenizer.TT_WORD )
{
pe.klass = st.sval;
if ( st.nextToken() == '"' )
{
pe.name = st.sval;
st.nextToken();
}
if ( st.ttype == ',' )
{
st.nextToken();
}
if ( st.ttype == '"' )
{
pe.actions = st.sval;
if ( st.nextToken() == ',' )
{
st.nextToken();
}
}
if ( st.ttype == StreamTokenizer.TT_WORD && Util.equalsIgnoreCase( "signedby", st.sval ) ) { //$NON-NLS-1$
if ( st.nextToken() == '"' )
{
pe.signers = st.sval;
}
else
{
handleUnexpectedToken( st );
}
}
else
{ // handle token in the next iteration
st.pushBack();
}
permissions.add( pe );
continue parsing;
}
}
handleUnexpectedToken(
st,
"Expected syntax is : permission permission_class_name [\"target_name\"] [, \"action_list\"] [, signedby \"name1,...,nameN\"]" ); //$NON-NLS-1$
break;
case ';': // just delimiter of entries
break;
case '}': // end of list
break parsing;
default: // invalid token
handleUnexpectedToken( st );
break;
}
}
return permissions;
}
/**
* Formats a detailed description of tokenizer status: current token, current line number, etc.
*/
protected String composeStatus( StreamTokenizer st )
{
return st.toString();
}
/**
* Throws InvalidFormatException with detailed diagnostics.
*
* @param st a tokenizer holding the erroneous token
* @param message a user-friendly comment, probably explaining expected syntax. Should not be <code>null</code>- use
* the overloaded single-parameter method instead.
*/
protected final void handleUnexpectedToken( StreamTokenizer st, String message )
throws InvalidFormatException
{
throw new InvalidFormatException( "Unexpected token encountered: " + composeStatus( st ) + ". " + message );
}
/**
* Throws InvalidFormatException with error status: which token is unexpected on which line.
*
* @param st a tokenizer holding the erroneous token
*/
protected final void handleUnexpectedToken( StreamTokenizer st )
throws InvalidFormatException
{
throw new InvalidFormatException( "Unexpected token encountered: " + composeStatus( st ) );
}
private static class Util
{
public static String toUpperCase( String s )
{
int len = s.length();
StringBuilder buffer = new StringBuilder( len );
for ( int i = 0; i < len; i++ )
{
char c = s.charAt( i );
if ( 'a' <= c && c <= 'z' )
{
buffer.append( (char) ( c - ( 'a' - 'A' ) ) );
}
else
{
buffer.append( c );
}
}
return buffer.toString();
}
public static boolean equalsIgnoreCase( String s1, String s2 )
{
s1 = toUpperCase( s1 );
s2 = toUpperCase( s2 );
return s1.equals( s2 );
}
}
}

View File

@ -0,0 +1,163 @@
package org.eclipse.jetty.policy;
//========================================================================
//Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//========================================================================
import java.io.FilePermission;
import java.net.URL;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jetty.policy.JettyPolicy;
import org.eclipse.jetty.policy.PropertyEvaluator;
import junit.framework.TestCase;
public class TestJettyPolicy extends TestCase
{
PropertyEvaluator evaluator = new PropertyEvaluator( new HashMap<String,String>());
@Override
protected void setUp()
throws Exception
{
super.setUp();
evaluator.put( "jetty.home", getWorkingDirectory() );
}
public void testGlobalAllPermissionLoader()
throws Exception
{
JettyPolicy ap =
new JettyPolicy( Collections.singleton( getWorkingDirectory() + "/src/test/resources/global-all-permission.policy" ), evaluator );
ap.refresh();
PermissionCollection pc = ap.getPermissions( new ProtectionDomain( null, null ) );
assertNotNull( pc );
Permission testPerm = new FilePermission( "/tmp", "read" );
assertTrue( pc.implies( testPerm ) );
for ( Enumeration<Permission> e = pc.elements(); e.hasMoreElements(); )
{
System.out.println( "Permission: " + e.nextElement().getClass().getName() );
}
}
public void testSingleCodebaseFilePermissionLoader()
throws Exception
{
JettyPolicy ap =
new JettyPolicy( Collections.singleton( getWorkingDirectory()
+ "/src/test/resources/single-codebase-file-permission.policy" ), evaluator );
ap.refresh();
URL url = new URL( "file:///foo.jar" );
CodeSource cs = new CodeSource( url, new Certificate[0]);
PermissionCollection pc = ap.getPermissions( cs );
assertNotNull( pc );
Permission testPerm = new FilePermission( "/tmp/*", "read" );
assertTrue( pc.implies( testPerm ) );
}
public void testMultipleCodebaseFilePermissionLoader()
throws Exception
{
JettyPolicy ap =
new JettyPolicy( Collections.singleton( getWorkingDirectory()
+ "/src/test/resources/multiple-codebase-file-permission.policy" ), evaluator );
ap.refresh();
URL url = new URL( "file:///bar.jar" );
CodeSource cs = new CodeSource( url, new Certificate[0]);
PermissionCollection pc = ap.getPermissions( cs );
assertNotNull( pc );
Permission testPerm = new FilePermission( "/tmp/*", "read,write" );
Permission testPerm2 = new FilePermission( "/usr/*", "write" ); // only read was granted
assertTrue( pc.implies( testPerm ) );
assertFalse( pc.implies( testPerm2 ) );
}
public void testMultipleCodebaseMixedPermissionLoader()
throws Exception
{
JettyPolicy ap =
new JettyPolicy( Collections.singleton( getWorkingDirectory()
+ "/src/test/resources/multiple-codebase-mixed-permission.policy" ), evaluator );
ap.refresh();
}
public void testMultipleFilePermissionLoader()
throws Exception
{
Set<String> files = new HashSet<String>();
files.add( getWorkingDirectory() + "/src/test/resources/single-codebase-file-permission.policy" );
files.add( getWorkingDirectory() + "/src/test/resources/single-codebase-file-permission-2.policy" );
JettyPolicy ap = new JettyPolicy( files, evaluator );
ap.refresh();
URL url = new URL( "file:///bar.jar" );
CodeSource cs = new CodeSource( url, new Certificate[0]);
PermissionCollection pc = ap.getPermissions( cs );
assertNotNull( pc );
Permission testPerm = new FilePermission( "/tmp/*", "read" );
Permission testPerm2 = new FilePermission( "/usr/*", "write" ); //
assertTrue( pc.implies( testPerm ) );
assertFalse( pc.implies( testPerm2 ) );
}
private String getWorkingDirectory()
{
return System.getProperty( "basedir" ); // TODO work in eclipse
}
}

View File

@ -0,0 +1,149 @@
package org.eclipse.jetty.policy;
//========================================================================
//Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//========================================================================
import java.io.File;
import java.security.AccessControlException;
import java.security.Policy;
import java.util.Collections;
import java.util.HashMap;
import org.eclipse.jetty.policy.JettyPolicy;
import org.eclipse.jetty.policy.PropertyEvaluator;
import junit.framework.TestCase;
public class TestJettyPolicyRuntime extends TestCase
{
PropertyEvaluator evaluator = new PropertyEvaluator( new HashMap<String,String>());
@Override
protected void setUp()
throws Exception
{
super.setUp();
evaluator.put( "jetty.home", getWorkingDirectory() );
}
public void testSimplePolicyReplacement() throws Exception
{
JettyPolicy ap =
new JettyPolicy( Collections.singleton( getWorkingDirectory() + "/src/test/resources/global-all-permission.policy" ), evaluator );
ap.refresh();
Policy.setPolicy( ap );
System.setSecurityManager( new SecurityManager() );
File test = new File( "/tmp" );
assertTrue ( test.canRead() );
// Policy nulling must occur after Security Manager null
System.setSecurityManager( null );
Policy.setPolicy( null );
}
public void testRepeatedPolicyReplacement() throws Exception
{
JettyPolicy ap =
new JettyPolicy( Collections.singleton( getWorkingDirectory() + "/src/test/resources/global-all-permission.policy" ), evaluator );
ap.refresh();
Policy.setPolicy( ap );
System.setSecurityManager( new SecurityManager() );
// Test that the all permission policy allows us to do this
try
{
File test3 = new File( "/tmp/foo/bar/do" );
test3.mkdirs();
test3.delete();
assertTrue( "Under AllPermission we are allowed", true );
}
catch ( AccessControlException ace )
{
//ace.printStackTrace();
assertFalse( "Exception was thrown which it shouldn't have been", true );
}
JettyPolicy ap2 =
new JettyPolicy( Collections.singleton( getWorkingDirectory() + "/src/test/resources/global-file-read-only-tmp-permission.policy" ), evaluator );
ap2.refresh();
Policy.setPolicy( ap2 );
// Test that the new policy does replace the old one and we are now now allowed
try
{
File test3 = new File( "/tmp/foo/bar/do" );
test3.mkdirs();
assertFalse( "We should be restricted and not get here.", true );
}
catch ( AccessControlException ace )
{
//ace.printStackTrace();
assertTrue( "Exception was thrown as it should be.", true );
}
System.setSecurityManager( null );
Policy.setPolicy( null );
}
public void testPolicyRestrictive() throws Exception
{
JettyPolicy ap =
new JettyPolicy( Collections.singleton( getWorkingDirectory() + "/src/test/resources/global-file-read-only-tmp-permission.policy" ), evaluator );
ap.refresh();
Policy.setPolicy( ap );
System.setSecurityManager( new SecurityManager() );
File test = new File( "/tmp" );
assertTrue ( test.canRead() );
File test2 = new File( "/tmp/foo" );
assertTrue ( test2.canRead() );
try
{
File test3 = new File( "/tmp/foo/bar/do" );
test3.mkdirs();
assertTrue( "we should not get here", false );
}
catch ( AccessControlException ace )
{
//ace.printStackTrace();
assertTrue( "Exception was thrown", true );
}
System.setSecurityManager( null );
Policy.setPolicy( null );
}
private String getWorkingDirectory()
{
return System.getProperty( "basedir" ); // TODO work in eclipse
}
}

View File

@ -0,0 +1,3 @@
grant {
permission java.security.AllPermission;
}

View File

@ -0,0 +1,10 @@
grant {
permission java.lang.RuntimePermission "setContextClassLoader";
permission java.lang.RuntimePermission "setIO";
permission java.lang.RuntimePermission "setSecurityManager";
permission java.security.SecurityPermission "getPolicy";
permission java.io.FilePermission "/tmp", "read,write";
permission java.io.FilePermission "/tmp/*", "read,write";
permission java.io.FilePermission "/tmp/foo/bar/*", "read";
}

View File

@ -0,0 +1,8 @@
grant codeBase "file:///foo.jar" {
permission java.io.FilePermission "/tmp/*", "read,write";
}
grant codeBase "file:///bar.jar" {
permission java.io.FilePermission "/tmp/*", "read,write";
permission java.io.FilePermission "/usr/*", "read";
}

View File

@ -0,0 +1,12 @@
grant codeBase "file:///foo.jar" {
permission java.io.FilePermission "/tmp/*", "read,write";
}
grant codeBase "file:///bar.jar" {
permission java.security.AllPermission;
}
grant codeBase "file:///snap.jar" {
permission java.io.FilePermission "/tmp/*", "read,write";
permission java.io.FilePermission "/usr/*", "read,write";
}

View File

@ -0,0 +1,3 @@
grant codeBase "file:///bar.jar" {
permission java.io.FilePermission "/tmp/*", "read";
}

View File

@ -0,0 +1,3 @@
grant codeBase "file:///foo.jar" {
permission java.io.FilePermission "/tmp/*", "read,write";
}