You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ro...@apache.org on 2010/05/31 18:04:46 UTC

svn commit: r949783 - in /qpid/trunk/qpid/java/broker-plugins/access-control: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/qpid/ src/main/java/org/apache/qpid/server/ src/main/java/org/apache/qp...

Author: robbie
Date: Mon May 31 16:04:45 2010
New Revision: 949783

URL: http://svn.apache.org/viewvc?rev=949783&view=rev
Log:
QPID-2542: Implement ACL checking as OSGi plugin

Applied patch from Andrew Kennedy <an...@gmail.com>

Added:
    qpid/trunk/qpid/java/broker-plugins/access-control/
    qpid/trunk/qpid/java/broker-plugins/access-control/MANIFEST.MF
    qpid/trunk/qpid/java/broker-plugins/access-control/build.xml
    qpid/trunk/qpid/java/broker-plugins/access-control/src/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Action.java
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/RuleSet.java
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/XMLConfiguration.java
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControl.java
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlActivator.java
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlConfiguration.java
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/resources/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/main/resources/acl.xsd
    qpid/trunk/qpid/java/broker-plugins/access-control/src/test/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/
    qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/AccessControlTest.java
    qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java

Added: qpid/trunk/qpid/java/broker-plugins/access-control/MANIFEST.MF
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/MANIFEST.MF?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/MANIFEST.MF (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/MANIFEST.MF Mon May 31 16:04:45 2010
@@ -0,0 +1,37 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Qpid Broker-Plugins Access Control
+Bundle-SymbolicName: broker-plugins-access-control
+Bundle-Description: Access control plugin for Qpid.
+Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt
+Bundle-DocURL: http://www.apache.org/
+Bundle-Version: 1.0.0
+Bundle-Activator: org.apache.qpid.server.security.access.plugins.AccessControlActivator
+Bundle-RequiredExecutionEnvironment: JavaSE-1.5
+Bundle-ClassPath: .
+Bundle-ActivationPolicy: lazy
+Import-Package:  org.apache.qpid,
+ org.apache.qpid.exchange,
+ org.apache.qpid.framing,
+ org.apache.qpid.junit.extensions.util,
+ org.apache.qpid.protocol,
+ org.apache.qpid.server.configuration,
+ org.apache.qpid.server.configuration.plugins,
+ org.apache.qpid.server.exchange,
+ org.apache.qpid.server.management,
+ org.apache.qpid.server.plugins,
+ org.apache.qpid.server.queue,
+ org.apache.qpid.server.security,
+ org.apache.qpid.server.security.access,
+ org.apache.qpid.server.virtualhost,
+ org.apache.qpid.util,
+ org.apache.commons.configuration;version=1.0.0,
+ org.apache.commons.lang;version=1.0.0,
+ org.apache.commons.lang.builder;version=1.0.0,
+ org.apache.log4j;version=1.0.0,
+ javax.management;version=1.0.0,
+ javax.management.openmbean;version=1.0.0,
+ org.osgi.util.tracker;version=1.0.0,
+ org.osgi.framework;version=1.3
+Private-Package: org.apache.qpid.server.security.access.config
+Export-Package: org.apache.qpid.server.security.access.plugins

Added: qpid/trunk/qpid/java/broker-plugins/access-control/build.xml
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/build.xml?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/build.xml (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/build.xml Mon May 31 16:04:45 2010
@@ -0,0 +1,29 @@
+<!--
+ - 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.
+ -->
+<project name="Qpid Broker-Plugins Access Control" default="build">
+    <property name="module.depends" value="common management/common broker broker-plugins" />
+    <property name="module.test.depends" value="junit-toolkit broker/test" />
+    
+    <property name="module.manifest" value="MANIFEST.MF" />
+    <property name="module.plugin" value="true" />
+
+    <import file="../../module.xml" />
+
+    <target name="bundle" depends="bundle-tasks" />
+</project>
\ No newline at end of file

Added: qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java Mon May 31 16:04:45 2010
@@ -0,0 +1,57 @@
+package org.apache.qpid.server.security.access.config;
+
+import java.io.File;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.log4j.Logger;
+
+public abstract class AbstractConfiguration implements ConfigurationFile
+{
+    protected static final Logger _logger = Logger.getLogger(ConfigurationFile.class);
+    
+    protected File _file;
+    protected RuleSet _config;
+    
+    public AbstractConfiguration(File file)
+    {
+        _file = file;
+    }
+    
+    public File getFile()
+    {
+        return _file;
+    }
+    
+    public RuleSet load() throws ConfigurationException
+    {
+        _config = new RuleSet();
+        return _config;
+    }
+    
+    public RuleSet getConfiguration()
+    {
+        return _config;
+    }
+        
+    public boolean save(RuleSet configuration)
+    {
+        return true;
+    }
+    
+    public RuleSet reload()
+    {
+        RuleSet oldRules = _config;
+        
+        try
+        {
+            RuleSet newRules = load();
+            _config = newRules;
+        }
+        catch (Exception e)
+        {
+            _config = oldRules;
+        }
+        
+        return _config;
+    }
+}

Added: qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Action.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Action.java?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Action.java (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Action.java Mon May 31 16:04:45 2010
@@ -0,0 +1,192 @@
+/*
+ *  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 org.apache.qpid.server.security.access.config;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.apache.qpid.server.security.access.ObjectProperties;
+import org.apache.qpid.server.security.access.ObjectType;
+import org.apache.qpid.server.security.access.Operation;
+
+/**
+ * An access control v2 rule action.
+ * 
+ * An action consists of an {@link Operation} on an {@link ObjectType} with certain properties, stored in a {@link Map}.
+ * The operation and object should be an allowable combination, based on the {@link ObjectType#isAllowed(Operation)}
+ * method of the object, which is exposed as the {@link #isAllowed()} method here. The internal {@link #propertiesMatch(Map)}
+ * and {@link #valueMatches(String, String)} methods are used to determine wildcarded matching of properties, with
+ * the empty string or "*" matching all values, and "*" at the end of a rule value indicating prefix matching.
+ * <p>
+ * The {@link #matches(Action)} method is intended to be used when determining precedence of rules, and
+ * {@link #equals(Object)} and {@link #hashCode()} are intended for use in maps. This is due to the wildcard matching
+ * described above.
+ */
+public class Action
+{
+    private Operation _operation;
+    private ObjectType _object;
+    private ObjectProperties _properties;
+    
+    public Action(Operation operation)
+    {
+        this(operation, ObjectType.ALL);
+    }
+    
+    public Action(Operation operation, ObjectType object, String name)
+    {
+        this(operation, object, new ObjectProperties(name));
+    }
+    
+    public Action(Operation operation, ObjectType object)
+    {
+        this(operation, object, ObjectProperties.EMPTY);
+    }
+    
+    public Action(Operation operation, ObjectType object, ObjectProperties properties)
+    {
+        setOperation(operation);
+        setObjectType(object);
+        setProperties(properties);
+    }
+    
+    public Operation getOperation()
+    {
+        return _operation;
+    }
+
+    public void setOperation(Operation operation)
+    {
+        _operation = operation;
+    }
+
+    public ObjectType getObjectType()
+    {
+        return _object;
+    }
+
+    public void setObjectType(ObjectType object)
+    {
+        _object = object;
+    }
+
+    public ObjectProperties getProperties()
+    {
+        return _properties;
+    }
+    
+    public void setProperties(ObjectProperties properties)
+    {
+        _properties = properties;
+    }
+    
+    public boolean isAllowed()
+    {
+        return _object.isAllowed(_operation);
+    }
+
+    /** @see Comparable#compareTo(Object) */
+    public boolean matches(Action a)
+    {
+        return (Operation.ALL == a.getOperation()
+                || (getOperation() == a.getOperation()
+                    && getObjectType() == a.getObjectType()
+                    && _properties.matches(a.getProperties())));
+    }
+
+    /**
+     * An ordering based on specificity
+     * 
+     * @see Comparator#compare(Object, Object)
+     */
+    public class Specificity implements Comparator<Action>
+    {
+        public int compare(Action a, Action b)
+        {
+            if (a.getOperation() == Operation.ALL && b.getOperation() != Operation.ALL)
+            {
+                return 1; // B is more specific
+            }
+            else if (b.getOperation() == Operation.ALL && a.getOperation() != Operation.ALL)
+            {
+                return 1; // A is more specific
+            }
+            else if (a.getOperation() == b.getOperation())
+            {
+                // Same operator, compare rest of action
+                
+//                    || (getOperation() == a.getOperation()
+//                        && getObjectType() == a.getObjectType()
+//                        && _properties.matches(a.getProperties())));
+
+                return 1; // b is more specific
+            }
+            else // Different operations
+            {
+                return a.getOperation().compareTo(b.getOperation()); // Arbitrary
+            }
+        }
+    }
+
+    /** @see Object#equals(Object) */
+    @Override
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof Action))
+        {
+            return false;
+        }
+        Action a = (Action) o;
+        
+        return new EqualsBuilder()
+                .append(_operation, a.getOperation())
+                .append(_object, a.getObjectType())
+                .appendSuper(_properties.equals(a.getProperties()))
+                .isEquals();
+    }
+
+    /** @see Object#hashCode() */
+    @Override
+    public int hashCode()
+    {
+        return new HashCodeBuilder()
+                .append(_operation)
+                .append(_operation)
+                .append(_properties)
+                .toHashCode();
+    }
+
+    /** @see Object#toString() */
+    @Override
+    public String toString()
+    {
+        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+                .append("operation", _operation)
+                .append("objectType", _object)
+                .append("properties", _properties)
+                .toString();
+    }
+}

Added: qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java Mon May 31 16:04:45 2010
@@ -0,0 +1,36 @@
+package org.apache.qpid.server.security.access.config;
+
+import java.io.File;
+
+import org.apache.commons.configuration.ConfigurationException;
+
+public interface ConfigurationFile
+{
+    /**
+     * Return the actual {@link File}  object containing the configuration.
+     */
+    File getFile();
+    
+    /**
+     * Load this configuration file's contents into a {@link RuleSet}.
+     * 
+     * @throws ConfigurationException if the configuration file has errors.
+     * @throws IllegalArgumentException if individual tokens cannot be parsed.
+     */
+    RuleSet load() throws ConfigurationException;
+    
+    /**
+     * Reload this configuration file's contents.
+     * 
+     * @throws ConfigurationException if the configuration file has errors.
+     * @throws IllegalArgumentException if individual tokens cannot be parsed.
+     */
+    RuleSet reload() throws ConfigurationException;
+        
+    RuleSet getConfiguration();
+    
+    /**
+     * TODO document me.
+     */
+    boolean save(RuleSet configuration);
+}

Added: qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java Mon May 31 16:04:45 2010
@@ -0,0 +1,303 @@
+package org.apache.qpid.server.security.access.config;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.StreamTokenizer;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.lang.StringUtils;
+import org.apache.qpid.server.security.access.ObjectProperties;
+import org.apache.qpid.server.security.access.ObjectType;
+import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.security.access.Permission;
+
+public class PlainConfiguration extends AbstractConfiguration
+{
+    public static final Character COMMENT = '#';
+    public static final Character CONTINUATION = '\\';
+
+    public static final String GROUP = "group";
+    public static final String ACL = "acl";
+    public static final String CONFIG = "config";
+
+    public static final String UNRECOGNISED_INITIAL_MSG = "Unrecognised initial token '%s' at line %d";
+    public static final String NOT_ENOUGH_TOKENS_MSG = "Not enough tokens at line %d";
+    public static final String NUMBER_NOT_ALLOWED_MSG = "Number not allowed before '%s' at line %d";    
+    public static final String CANNOT_LOAD_MSG = "Cannot load config file %s";
+    public static final String PREMATURE_CONTINUATION_MSG = "Premature continuation character at line %d";
+    public static final String PREMATURE_EOF_MSG = "Premature end of file reached at line %d";
+    public static final String PARSE_TOKEN_FAILED_MSG = "Failed to parse token at line %d";
+    public static final String CONFIG_NOT_FOUND_MSG = "Cannot find config file %s";
+    public static final String NOT_ENOUGH_GROUP_MSG = "Not enough data for a group at line %d";
+    public static final String NOT_ENOUGH_ACL_MSG = "Not enough data for an acl at line %d";
+    public static final String NOT_ENOUGH_CONFIG_MSG = "Not enough data for config at line %d";
+    public static final String BAD_ACL_RULE_NUMBER_MSG = "Invalid rule number at line %d";
+    public static final String PROPERTY_KEY_ONLY_MSG = "Incomplete property (key only) at line %d";
+    public static final String PROPERTY_NO_EQUALS_MSG = "Incomplete property (no equals) at line %d";
+    public static final String PROPERTY_NO_VALUE_MSG = "Incomplete property (no value) at line %d";
+    
+    private StreamTokenizer _st;
+
+    public PlainConfiguration(File file)
+    {
+        super(file);
+    }
+    
+    @Override
+    public RuleSet load() throws ConfigurationException
+    {
+        RuleSet ruleSet = super.load();
+        
+        try
+        {
+            _st = new StreamTokenizer(new BufferedReader(new FileReader(_file)));
+            _st.resetSyntax(); // setup the tokenizer
+                
+            _st.commentChar(COMMENT); // single line comments
+            _st.eolIsSignificant(true); // return EOL as a token
+            _st.lowerCaseMode(true); // case insensitive tokens
+            _st.ordinaryChar('='); // equals is a token
+            _st.ordinaryChar(CONTINUATION); // continuation character (when followed by EOL)
+            _st.quoteChar('"'); // double quote
+            _st.quoteChar('\''); // single quote
+            _st.whitespaceChars('\u0000', '\u0020'); // whitespace (to be ignored) TODO properly
+            _st.wordChars('a', 'z'); // unquoted token characters [a-z]
+            _st.wordChars('A', 'Z'); // [A-Z]
+            _st.wordChars('0', '9'); // [0-9]
+            _st.wordChars('_', '_'); // underscore
+            _st.wordChars('-', '-'); // dash
+            _st.wordChars('.', '.'); // dot
+            _st.wordChars('*', '*'); // star
+            _st.wordChars('@', '@'); // at
+            _st.wordChars(':', ':'); // colon
+            
+            // parse the acl file lines
+            Stack<String> stack = new Stack<String>();
+            int current;
+            do {
+                current = _st.nextToken();
+                switch (current)
+                {
+                    case StreamTokenizer.TT_EOF:
+                    case StreamTokenizer.TT_EOL:
+                        if (stack.isEmpty())
+                        {
+                            break; // blank line
+                        }
+                        
+                        // pull out the first token from the bottom of the stack and check arguments exist
+                        String first = stack.firstElement();
+                        stack.removeElementAt(0);
+                        if (stack.isEmpty())
+                        {
+                            throw new ConfigurationException(String.format(NOT_ENOUGH_TOKENS_MSG, getLine()));
+                        }
+                        
+                        // check for and parse optional initial number for ACL lines
+                        Integer number = null;
+                        if (StringUtils.isNumeric(first))
+                        {
+                            // set the acl number and get the next element
+                            number = Integer.valueOf(first);                            
+                            first = stack.firstElement();
+                            stack.removeElementAt(0);
+                        }
+
+                        if (StringUtils.equalsIgnoreCase(ACL, first))
+                        {
+                            parseAcl(number, stack);
+                        }
+                        else if (number == null)
+                        {
+                            if (StringUtils.equalsIgnoreCase(GROUP, first))
+                            {
+                                parseGroup(stack);
+                            }
+                            else if (StringUtils.equalsIgnoreCase(CONFIG, first))
+                            {
+                                parseConfig(stack);
+                            }
+                            else
+                            {
+                                throw new ConfigurationException(String.format(UNRECOGNISED_INITIAL_MSG, first, getLine()));
+                            }
+                        }
+                        else
+                        {
+                            throw new ConfigurationException(String.format(NUMBER_NOT_ALLOWED_MSG, first, getLine()));
+                        }
+                        
+                        // reset stack, start next line
+                        stack.clear();
+                        break;
+                    case StreamTokenizer.TT_NUMBER:
+                        stack.push(Integer.toString(Double.valueOf(_st.nval).intValue()));
+                        break;
+                    case StreamTokenizer.TT_WORD:
+                        stack.push(_st.sval); // token
+                        break;
+                    default:
+                        if (_st.ttype == CONTINUATION)
+                        {
+                            int next = _st.nextToken();
+                            if (next == StreamTokenizer.TT_EOL)
+                            {
+	                            break; // continue reading next line
+                            }
+                            
+                            // invalid location for continuation character (add one to line beacuse we ate the EOL)
+                            throw new ConfigurationException(String.format(PREMATURE_CONTINUATION_MSG, getLine() + 1));
+                        }
+                        else if (_st.ttype == '\'' || _st.ttype == '"')
+                        {
+                            stack.push(_st.sval); // quoted token
+                        }
+                        else
+                        {
+                            stack.push(Character.toString((char) _st.ttype)); // single character
+                        }
+                }
+            } while (current != StreamTokenizer.TT_EOF);
+        
+            if (!stack.isEmpty())
+            {
+                throw new ConfigurationException(String.format(PREMATURE_EOF_MSG, getLine()));
+            }
+        }
+        catch (IllegalArgumentException iae)
+        {
+            throw new ConfigurationException(String.format(PARSE_TOKEN_FAILED_MSG, getLine()), iae);
+        }
+        catch (FileNotFoundException fnfe)
+        {
+            throw new ConfigurationException(String.format(CONFIG_NOT_FOUND_MSG, getFile().getName()), fnfe);
+        }
+        catch (IOException ioe)
+        {
+            throw new ConfigurationException(String.format(CANNOT_LOAD_MSG, getFile().getName()), ioe);
+        }
+        
+        return ruleSet;
+    }
+    
+    private void parseGroup(List<String> args) throws ConfigurationException
+    {
+        if (args.size() < 2)
+        {
+            throw new ConfigurationException(String.format(NOT_ENOUGH_GROUP_MSG, getLine()));
+        }
+        
+        getConfiguration().addGroup(args.get(0), args.subList(1, args.size()));
+    }
+    
+    private void parseAcl(Integer number, List<String> args) throws ConfigurationException
+    {
+        if (args.size() < 3)
+        {
+            throw new ConfigurationException(String.format(NOT_ENOUGH_ACL_MSG, getLine()));
+        }
+
+        Permission permission = Permission.parse(args.get(0));
+        String identity = args.get(1);
+        Operation operation = Operation.parse(args.get(2));
+        
+        if (number != null && !getConfiguration().isValidNumber(number))
+        {
+            throw new ConfigurationException(String.format(BAD_ACL_RULE_NUMBER_MSG, getLine()));
+        }
+        
+        if (args.size() == 3)
+        {
+            getConfiguration().grant(number, identity, permission, operation);
+        }
+        else
+        {
+            ObjectType object = ObjectType.parse(args.get(3));
+            ObjectProperties properties = toObjectProperties(args.subList(4, args.size()));
+
+            getConfiguration().grant(number, identity, permission, operation, object, properties);
+        }
+    }
+    
+    private void parseConfig(List<String> args) throws ConfigurationException
+    {
+        if (args.size() < 3)
+        {
+            throw new ConfigurationException(String.format(NOT_ENOUGH_CONFIG_MSG, getLine()));
+        }
+
+        Map<String, Boolean> properties = toPluginProperties(args);
+        
+        getConfiguration().configure(properties);
+    }
+    
+    /** Converts a {@link List} of "name", "=", "value" tokens into a {@link Map}. */
+    protected ObjectProperties toObjectProperties(List<String> args) throws ConfigurationException
+    {
+        ObjectProperties properties = new ObjectProperties();
+        Iterator<String> i = args.iterator();
+        while (i.hasNext())
+        {
+            String key = i.next();
+            if (!i.hasNext())
+            {
+                throw new ConfigurationException(String.format(PROPERTY_KEY_ONLY_MSG, getLine()));
+            }
+            if (!"=".equals(i.next()))
+            {
+                throw new ConfigurationException(String.format(PROPERTY_NO_EQUALS_MSG, getLine()));
+            }
+            if (!i.hasNext())
+            {
+                throw new ConfigurationException(String.format(PROPERTY_NO_VALUE_MSG, getLine()));
+            }
+            String value = i.next();
+            
+            // parse property key
+            ObjectProperties.Property property = ObjectProperties.Property.parse(key);
+            properties.put(property, value);
+        }
+        return properties;
+    }
+    
+    /** Converts a {@link List} of "name", "=", "value" tokens into a {@link Map}. */
+    protected Map<String, Boolean> toPluginProperties(List<String> args) throws ConfigurationException
+    {
+        Map<String, Boolean> properties = new HashMap<String, Boolean>();
+        Iterator<String> i = args.iterator();
+        while (i.hasNext())
+        {
+            String key = i.next().toLowerCase();
+            if (!i.hasNext())
+            {
+                throw new ConfigurationException(String.format(PROPERTY_KEY_ONLY_MSG, getLine()));
+            }
+            if (!"=".equals(i.next()))
+            {
+                throw new ConfigurationException(String.format(PROPERTY_NO_EQUALS_MSG, getLine()));
+            }
+            if (!i.hasNext())
+            {
+                throw new ConfigurationException(String.format(PROPERTY_NO_VALUE_MSG, getLine()));
+            }
+            
+            // parse property value and save
+            Boolean value = Boolean.valueOf(i.next());
+            properties.put(key, value);
+        }
+        return properties;
+    }
+    
+    protected int getLine()
+    {
+        return _st.lineno() - 1;
+    }
+}

Added: qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java Mon May 31 16:04:45 2010
@@ -0,0 +1,170 @@
+/*
+ *  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 org.apache.qpid.server.security.access.config;
+
+import org.apache.commons.lang.builder.CompareToBuilder;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.apache.qpid.server.security.access.Permission;
+
+/**
+ * An access control v2 rule.
+ * 
+ * A rule consists of {@link Permission} for a particular identity to perform an {@link Action}. The identity
+ * may be either a user or a group.
+ */
+public class Rule implements Comparable<Rule>
+{
+	/** String indicating all identitied. */
+	public static final String ALL = "all";
+	
+    private Integer _number;
+    private Boolean _enabled = Boolean.TRUE;
+    private String _identity;
+    private Action _action;
+    private Permission _permission;
+    
+    public Rule(Integer number, String identity, Action action, Permission permission)
+    {
+        setNumber(number);
+        setIdentity(identity);
+        setAction(action);
+        setPermission(permission);
+    }
+    
+    public Rule(String identity, Action action, Permission permission)
+    {
+        this(null, identity, action, permission);
+    }
+    
+    public boolean isEnabled()
+    {
+        return _enabled;
+    }
+    
+    public void setEnabled(boolean enabled)
+    {
+        _enabled = enabled;
+    }
+    
+    public void enable()
+    {
+        _enabled = Boolean.TRUE;
+    }
+    
+    public void disable()
+    {
+        _enabled = Boolean.FALSE;
+    }
+
+    public Integer getNumber()
+    {
+        return _number;
+    }
+
+    public void setNumber(Integer number)
+    {
+        _number = number;
+    }
+
+    public String getIdentity()
+    {
+        return _identity;
+    }
+
+    public void setIdentity(String identity)
+    {
+        _identity = identity;
+    }
+    
+    public Action getAction()
+    {
+        return _action;
+    }
+
+    public void setAction(Action action)
+    {
+        _action = action;
+    }
+
+    public Permission getPermission()
+    {
+        return _permission;
+    }
+
+    public void setPermission(Permission permission)
+    {
+        _permission = permission;
+    }
+
+    /** @see Comparable#compareTo(Object) */
+    public int compareTo(Rule r)
+    {
+        return new CompareToBuilder()
+                .append(getAction(), r.getAction())
+                .append(getIdentity(), r.getIdentity())
+                .append(getPermission(), r.getPermission())
+                .toComparison();
+    }
+
+    /** @see Object#equals(Object) */
+    @Override
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof Rule))
+        {
+            return false;
+        }
+        Rule r = (Rule) o;
+        
+        return new EqualsBuilder()
+                .append(getIdentity(), r.getIdentity())
+                .append(getAction(), r.getAction())
+                .append(getPermission(), r.getPermission())
+                .isEquals();
+    }
+
+    /** @see Object#hashCode() */
+    @Override
+    public int hashCode()
+    {
+        return new HashCodeBuilder()
+                .append(getIdentity())
+                .append(getAction())
+                .append(getPermission())
+                .toHashCode();
+    }
+
+    /** @see Object#toString() */
+    @Override
+    public String toString()
+    {
+        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+                .append("#", getNumber())
+                .append("identity", getIdentity())
+                .append("action", getAction())
+                .append("permission", getPermission())
+                .append("enabled", isEnabled())
+                .toString();
+    }
+}

Added: qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/RuleSet.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/RuleSet.java?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/RuleSet.java (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/RuleSet.java Mon May 31 16:04:45 2010
@@ -0,0 +1,471 @@
+/*
+ *  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 org.apache.qpid.server.security.access.config;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.WeakHashMap;
+
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.server.security.Result;
+import org.apache.qpid.server.security.access.ObjectProperties;
+import org.apache.qpid.server.security.access.ObjectType;
+import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.security.access.Permission;
+
+/**
+ * Models the rule configuration for the access control plugin.
+ *
+ * The access control rule definitions are loaded from an external configuration file, passed in as the
+ * target to the {@link load(ConfigurationFile)} method. The file specified 
+ */
+public class RuleSet
+{
+    private static final Logger _logger = Logger.getLogger(RuleSet.class);
+    
+    private static final String AT = "@";
+	private static final String SLASH = "/";
+
+	public static final String DEFAULT_ALLOW = "defaultallow";
+	public static final String DEFAULT_DENY = "defaultdeny";
+	public static final String TRANSITIVE = "transitive";
+	public static final String EXPAND = "expand";
+    public static final String AUTONUMBER = "autonumber";
+    public static final String CONTROLLED = "controlled";
+    public static final String VALIDATE = "validate";
+    
+    public static final List<String> CONFIG_PROPERTIES = Arrays.asList(
+            DEFAULT_ALLOW, DEFAULT_DENY, TRANSITIVE, EXPAND, AUTONUMBER, CONTROLLED
+        );
+    
+    private static final Integer _increment = 10;
+	
+    private final Map<String, List<String>> _groups = new HashMap<String, List<String>>();
+    private final SortedMap<Integer, Rule> _rules = new TreeMap<Integer, Rule>();
+    private final Map<String, Map<Operation, Map<ObjectType, List<Rule>>>> _cache =
+                        new WeakHashMap<String, Map<Operation, Map<ObjectType, List<Rule>>>>();
+    private final Map<String, Boolean> _config = new HashMap<String, Boolean>();
+    
+    public RuleSet()
+    {
+        // set some default configuration properties
+        configure(DEFAULT_DENY, Boolean.TRUE);
+        configure(TRANSITIVE, Boolean.TRUE);
+    }
+    
+    /**
+     * Clear the contents, invluding groups, rules and configuration.
+     */
+    public void clear()
+    {
+        _rules.clear();
+        _cache.clear();
+        _config.clear();
+        _groups.clear();
+    }
+    
+    public int getRuleCount()
+    {
+        return _rules.size();
+    }
+	
+	/**
+	 * Filtered rules list based on an identity and operation.
+	 * 
+	 * Allows only enabled rules with identity equal to all, the same, or a group with identity as a member,
+	 * and operation is either all or the same operation.
+	 */		
+	public List<Rule> getRules(String identity, Operation operation, ObjectType objectType)
+	{
+        // Lookup identity in cache and create empty operation map if required
+		Map<Operation, Map<ObjectType, List<Rule>>> operations = _cache.get(identity);		
+		if (operations == null)
+		{	
+			operations = new EnumMap<Operation, Map<ObjectType, List<Rule>>>(Operation.class);
+			_cache.put(identity, operations);
+		}
+		
+        // Lookup operation and create empty object type map if required        
+        Map<ObjectType, List<Rule>> objects = operations.get(operation);
+		if (objects == null)
+		{
+            objects = new EnumMap<ObjectType, List<Rule>>(ObjectType.class);
+            operations.put(operation, objects);
+        }
+
+        // Lookup object type rules for the operation
+        if (!objects.containsKey(objectType))
+        {
+            boolean controlled = false;
+            List<Rule> filtered = new LinkedList<Rule>();
+            for (Rule rule : _rules.values())
+            {
+                if (rule.isEnabled()
+                    && (rule.getAction().getOperation() == Operation.ALL || rule.getAction().getOperation() == operation)
+                    && (rule.getAction().getObjectType() == ObjectType.ALL || rule.getAction().getObjectType() == objectType))
+                {
+                    controlled = true;
+
+                    if (rule.getIdentity().equalsIgnoreCase(Rule.ALL)
+                        || rule.getIdentity().equalsIgnoreCase(identity)
+                        || (_groups.containsKey(rule.getIdentity()) && _groups.get(rule.getIdentity()).contains(identity)))
+                    {
+                        filtered.add(rule);
+                    }
+                }
+            }
+            
+            // Return null if there are no rules at all for this operation and object type
+            if (filtered.isEmpty() && controlled == false)
+            {
+                filtered = null;
+            }
+            
+            // Save the rules we selected
+            objects.put(objectType, filtered);
+        }
+		
+        // Return the cached rules
+		return objects.get(objectType);
+	}
+    
+    public boolean isValidNumber(Integer number)
+    {
+        return !_rules.containsKey(number);
+    }
+	
+    public void grant(Integer number, String identity, Permission permission, Operation operation)
+    {
+        Action action = new Action(operation);
+        addRule(number, identity, permission, action);
+    }
+    
+    public void grant(Integer number, String identity, Permission permission, Operation operation, ObjectType object, ObjectProperties properties)
+    {
+        Action action = new Action(operation, object, properties);
+        addRule(number, identity, permission, action);
+    }
+    
+    public boolean ruleExists(String identity, Action action)
+    {
+		for (Rule rule : _rules.values())
+		{
+		    if (rule.getIdentity().equals(identity) && rule.getAction().equals(action))
+		    {
+		        return true;
+		    }
+		}
+		return false;
+    }
+
+    // TODO make this work when group membership is not known at file parse time
+    public void addRule(Integer number, String identity, Permission permission, Action action)
+    {
+		if (!action.isAllowed())
+		{
+			throw new IllegalArgumentException("Action is not allowd: " + action);
+		}
+        if (ruleExists(identity, action))
+        {
+            return;
+        }
+        
+        // expand actions - possibly multiply number by
+        if (isSet(EXPAND))
+        {
+            if (action.getOperation() == Operation.CREATE && action.getObjectType() == ObjectType.TOPIC)
+            {
+                addRule(null, identity, permission, new Action(Operation.BIND, ObjectType.EXCHANGE,
+                        new ObjectProperties("amq.topic", action.getProperties().get(ObjectProperties.Property.NAME))));
+                ObjectProperties topicProperties = new ObjectProperties();
+                topicProperties.put(ObjectProperties.Property.DURABLE, true);
+                addRule(null, identity, permission, new Action(Operation.CREATE, ObjectType.QUEUE, topicProperties));
+                return;
+            }
+            if (action.getOperation() == Operation.DELETE && action.getObjectType() == ObjectType.TOPIC)
+            {
+                addRule(null, identity, permission, new Action(Operation.UNBIND, ObjectType.EXCHANGE,
+                        new ObjectProperties("amq.topic", action.getProperties().get(ObjectProperties.Property.NAME))));
+                ObjectProperties topicProperties = new ObjectProperties();
+                topicProperties.put(ObjectProperties.Property.DURABLE, true);
+                addRule(null, identity, permission, new Action(Operation.DELETE, ObjectType.QUEUE, topicProperties));
+                return;
+            }
+        }
+        
+		// transitive action dependencies
+        if (isSet(TRANSITIVE))
+        {
+            if (action.getOperation() == Operation.CREATE && action.getObjectType() == ObjectType.QUEUE)
+            {
+                ObjectProperties exchProperties = new ObjectProperties(action.getProperties());
+                exchProperties.setName(ExchangeDefaults.DEFAULT_EXCHANGE_NAME);
+                exchProperties.put(ObjectProperties.Property.ROUTING_KEY, action.getProperties().get(ObjectProperties.Property.NAME));
+                addRule(null, identity, permission, new Action(Operation.BIND, ObjectType.EXCHANGE, exchProperties));
+				if (action.getProperties().isSet(ObjectProperties.Property.AUTO_DELETE))
+				{
+					addRule(null, identity, permission, new Action(Operation.DELETE, ObjectType.QUEUE, action.getProperties()));
+				}
+            }
+            else if (action.getOperation() == Operation.DELETE && action.getObjectType() == ObjectType.QUEUE)
+            {
+                ObjectProperties exchProperties = new ObjectProperties(action.getProperties());
+                exchProperties.setName(ExchangeDefaults.DEFAULT_EXCHANGE_NAME);
+                exchProperties.put(ObjectProperties.Property.ROUTING_KEY, action.getProperties().get(ObjectProperties.Property.NAME));
+                addRule(null, identity, permission, new Action(Operation.UNBIND, ObjectType.EXCHANGE, exchProperties));
+            }
+            else if (action.getOperation() != Operation.ACCESS && action.getObjectType() != ObjectType.VIRTUALHOST)
+            {
+                addRule(null, identity, permission, new Action(Operation.ACCESS, ObjectType.VIRTUALHOST));
+            }
+        }
+        
+        // set rule number if needed
+        Rule rule = new Rule(number, identity, action, permission);        
+        if (rule.getNumber() == null)
+        {
+            if (_rules.isEmpty())
+            {
+                rule.setNumber(0);
+            }
+            else
+            {
+                rule.setNumber(_rules.lastKey() + _increment);
+            }
+        }
+        
+        // save rule
+        _cache.remove(identity);
+        _rules.put(rule.getNumber(), rule);
+	}
+    
+    public void enableRule(int ruleNumber)
+    {
+        _rules.get(Integer.valueOf(ruleNumber)).enable();
+    }
+    
+    public void disableRule(int ruleNumber)
+    {
+        _rules.get(Integer.valueOf(ruleNumber)).disable();
+    }
+    
+    public boolean addGroup(String group, List<String> constituents)
+    {
+        if (_groups.containsKey(group))
+        {
+            // cannot redefine
+            return false;
+        }
+        else
+        {
+            _groups.put(group, new ArrayList<String>());
+        }
+        
+        for (String name : constituents)
+        {
+            if (name.equalsIgnoreCase(group))
+            {
+                // recursive definition
+                return false;
+            }
+            
+            if (!checkName(name))
+            {
+                // invalid name
+                return false;
+            }
+            
+            if (_groups.containsKey(name))
+            {
+                // is a group
+                _groups.get(group).addAll(_groups.get(name));
+            }
+            else
+            {
+                // is a user
+                if (!isvalidUserName(name))
+                {
+                    // invalid username
+                    return false;
+                }
+                _groups.get(group).add(name);
+            }
+        }
+        return true;
+    }
+    
+    /** Return true if the name is well-formed (contains legal characters). */
+    protected boolean checkName(String name)
+    {
+        for (int i = 0; i < name.length(); i++)
+        {
+            Character c = name.charAt(i);
+            if (!Character.isLetterOrDigit(c) && c != '-' && c != '_' && c != '@' && c != '.' && c != '/')
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    /** Returns true if a username has the name[@domain][/realm] format  */
+    protected boolean isvalidUserName(String name)
+    {	
+		// check for '@' and '/' in namne
+		int atPos = name.indexOf(AT);
+		int slashPos = name.indexOf(SLASH);
+		boolean atFound = atPos != StringUtils.INDEX_NOT_FOUND && atPos == name.lastIndexOf(AT);
+		boolean slashFound = slashPos != StringUtils.INDEX_NOT_FOUND && slashPos == name.lastIndexOf(SLASH);
+				
+		// must be at least one character after '@' or '/'
+		if (atFound && atPos > name.length() - 2)
+		{
+			return false;
+		}
+		if (slashFound && slashPos > name.length() - 2)
+		{
+			return false;
+		}
+		
+		// must be at least one character between '@' and '/'
+		if (atFound && slashFound)
+		{
+			return (atPos < (slashPos - 1));
+		}
+		
+		// otherwise all good
+		return true; 
+    }
+
+	// C++ broker authorise function prototype
+    // virtual bool authorise(const std::string& id, const Action& action, const ObjectType& objType,
+	//		const std::string& name, std::map<Property, std::string>* params=0);
+	
+	// Possibly add a String name paramater?
+
+    /**
+     * Check the authorisation granted to a particular identity for an operation on an object type with
+     * specific properties.
+     *
+     * Looks up the entire ruleset, whcih may be cached, for the user and operation and goes through the rules
+     * in order to find the first one that matches. Either defers if there are no rules, returns the result of
+     * the first match found, or denies access if there are no matching rules. Normally, it would be expected
+     * to have a default deny or allow rule at the end of an access configuration however.
+     */
+    public Result check(String identity, Operation operation, ObjectType objectType, ObjectProperties properties)
+    {
+        // Create the action to check
+        Action action = new Action(operation, objectType, properties);
+
+		// get the list of rules relevant for this request
+		List<Rule> rules = getRules(identity, operation, objectType);
+		if (rules == null)
+		{
+		    if (isSet(CONTROLLED))
+		    {
+    		    // Abstain if there are no rules for this operation
+                return Result.ABSTAIN;
+		    }
+		    else
+		    {
+		        return getDefault();
+		    }
+		}
+		
+		// Iterate through a filtered set of rules dealing with this identity and operation
+        for (Rule current : rules)
+		{
+			// Check if action matches
+            if (action.matches(current.getAction()))
+            {
+                Permission permission = current.getPermission();
+                
+                switch (permission)
+                {
+                    case ALLOW_LOG:
+                        _logger.info("ALLOWED " + action);
+                    case ALLOW:
+                        return Result.ALLOWED;
+                    case DENY_LOG:
+                        _logger.info("DENIED " + action);
+                    case DENY:
+                        return Result.DENIED;
+                }
+
+                return Result.DENIED;
+            }
+        }
+        
+        // Defer to the next plugin of this type, if it exists
+		return Result.DEFER;
+    }
+	
+	/** Default deny. */
+	public Result getDefault()
+	{
+	    if (isSet(DEFAULT_ALLOW))
+        {
+            return Result.ALLOWED;
+        }
+        if (isSet(DEFAULT_DENY))
+        {
+            return Result.DENIED;
+        }
+        return Result.ABSTAIN;
+	}
+	
+	/**
+	 * Check if a configuration property is set.
+	 */
+	protected boolean isSet(String key)
+	{
+	    return BooleanUtils.isTrue(_config.get(key));
+	}
+
+    /**
+     * Configure properties for the plugin instance.
+     * 
+     * @param properties
+     */
+    public void configure(Map<String, Boolean> properties)
+    {
+        _config.putAll(properties);
+    }
+
+    /**
+     * Configure a single property for the plugin instance.
+     * 
+     * @param key
+     * @param value
+     */
+    public void configure(String key, Boolean value)
+    {
+        _config.put(key, value);
+    }
+}

Added: qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/XMLConfiguration.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/XMLConfiguration.java?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/XMLConfiguration.java (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/XMLConfiguration.java Mon May 31 16:04:45 2010
@@ -0,0 +1,11 @@
+package org.apache.qpid.server.security.access.config;
+
+import java.io.File;
+
+public class XMLConfiguration extends AbstractConfiguration
+{
+    public XMLConfiguration(File file)
+    {
+        super(file);
+    }
+}

Added: qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControl.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControl.java?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControl.java (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControl.java Mon May 31 16:04:45 2010
@@ -0,0 +1,127 @@
+/*
+ *
+ * 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 org.apache.qpid.server.security.access.plugins;
+
+import java.io.File;
+import java.security.Principal;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.log4j.Logger;
+import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin;
+import org.apache.qpid.server.security.AbstractPlugin;
+import org.apache.qpid.server.security.Result;
+import org.apache.qpid.server.security.SecurityManager;
+import org.apache.qpid.server.security.SecurityPluginFactory;
+import org.apache.qpid.server.security.access.ObjectProperties;
+import org.apache.qpid.server.security.access.ObjectType;
+import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.security.access.config.ConfigurationFile;
+import org.apache.qpid.server.security.access.config.PlainConfiguration;
+import org.apache.qpid.server.security.access.config.RuleSet;
+
+/**
+ * This access control plugin implements version two plain text access control.
+ */
+public class AccessControl extends AbstractPlugin
+{
+    public static final Logger _logger = Logger.getLogger(AccessControl.class);
+    
+    private RuleSet _ruleSet;
+    
+    public static final SecurityPluginFactory<AccessControl> FACTORY = new SecurityPluginFactory<AccessControl>()
+    {
+        public Class<AccessControl> getPluginClass()
+        {
+            return AccessControl.class;
+        }
+
+        public String getPluginName()
+        {
+            return AccessControl.class.getName();
+        }
+
+        public AccessControl newInstance(ConfigurationPlugin config) throws ConfigurationException
+        {
+            AccessControl plugin = new AccessControl(config);
+            plugin.configure();
+            return plugin;
+        }
+    };
+    
+    public AccessControl(ConfigurationPlugin config)
+    {
+        _config = config.getConfiguration(AccessControlConfiguration.class);
+    }
+    
+    public Result getDefault()
+    {
+        return _ruleSet.getDefault();
+    }
+
+    /** Parse a version two access control file. */
+    private void parseFile(File aclFile) throws ConfigurationException
+    {
+        ConfigurationFile configFile = new PlainConfiguration(aclFile);
+        _ruleSet = configFile.load();
+    }
+    
+    /**
+     * Object instance access authorisation.
+     *
+	 * Delegate to the {@link #authorise(Operation, ObjectType, ObjectProperties)} method, with
+     * the operation set to ACCESS and no object properties.
+	 */
+    public Result access(ObjectType objectType, Object instance)
+    {
+        return authorise(Operation.ACCESS, objectType, ObjectProperties.EMPTY);
+    }
+    
+    /**
+     * Check if an operation is authorised by asking the  configuration object about the access
+     * control rules granted to the current thread's {@link Principal}. If there is no current
+     * user the plugin will abstain.
+     */
+    public Result authorise(Operation operation, ObjectType objectType, ObjectProperties properties)
+    {
+        Principal principal = SecurityManager.getThreadPrincipal();
+        
+        // Abstain if there is no user associated with this thread
+        if (principal == null)
+        {
+            return Result.ABSTAIN;
+        }
+        
+        return _ruleSet.check(principal.getName(), operation, objectType, properties);
+    }
+
+    @Override
+    public void configure() throws ConfigurationException
+    {
+        AccessControlConfiguration accessConfig = (AccessControlConfiguration) _config;
+            
+        if (isConfigured())
+        {
+            String fileName = accessConfig.getFileName();
+            File aclFile = new File(fileName);
+            parseFile(aclFile);
+        }
+    }
+}

Added: qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlActivator.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlActivator.java?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlActivator.java (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlActivator.java Mon May 31 16:04:45 2010
@@ -0,0 +1,22 @@
+package org.apache.qpid.server.security.access.plugins;
+
+import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory;
+import org.apache.qpid.server.security.SecurityPluginActivator;
+import org.apache.qpid.server.security.SecurityPluginFactory;
+import org.osgi.framework.BundleActivator;
+
+/**
+ * The OSGi {@link BundleActivator} for {@link AccessControl}.
+ */
+public class AccessControlActivator extends SecurityPluginActivator
+{
+	public SecurityPluginFactory getFactory()
+	{
+	    return AccessControl.FACTORY;
+	}
+
+    public ConfigurationPluginFactory getConfigurationFactory()
+    {
+        return AccessControlConfiguration.FACTORY;
+    }
+}

Added: qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlConfiguration.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlConfiguration.java?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlConfiguration.java (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlConfiguration.java Mon May 31 16:04:45 2010
@@ -0,0 +1,57 @@
+/*
+ *
+ * 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 org.apache.qpid.server.security.access.plugins;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin;
+import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory;
+
+public class AccessControlConfiguration extends ConfigurationPlugin
+{
+    public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory() 
+    {
+        public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException
+        {
+            ConfigurationPlugin instance = new AccessControlConfiguration();
+            instance.setConfiguration(path, config);
+            return instance;
+        }
+
+        public List<String> getParentPaths()
+        {
+            return Arrays.asList("security", "virtualhosts.virtualhost.security");
+        }
+    };
+
+    public String[] getElementsProcessed()
+    {
+        return new String[] { "aclv2" };
+    }
+
+    public String getFileName()
+    {
+        return _configuration.getString("aclv2");
+    }
+}

Added: qpid/trunk/qpid/java/broker-plugins/access-control/src/main/resources/acl.xsd
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/src/main/resources/acl.xsd?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/src/main/resources/acl.xsd (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/src/main/resources/acl.xsd Mon May 31 16:04:45 2010
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema
+        xmlns="http://qpid.apache.org/schema/qpid/broker/security/acl.xsd"
+        xmlns:xs="http://www.w3.org/2001/XMLSchema"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        targetNamespace="http://qpid.apache.org/schema/qpid/broker/security/acl.xsd"
+        elementFormDefault="qualified">
+  <xs:element name="aclv2" type="xs:string" />
+</xs:schema>
\ No newline at end of file

Added: qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/AccessControlTest.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/AccessControlTest.java?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/AccessControlTest.java (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/AccessControlTest.java Mon May 31 16:04:45 2010
@@ -0,0 +1,195 @@
+/*
+ *  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 org.apache.qpid.server.security.access.plugins;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.security.access.config.ConfigurationFile;
+import org.apache.qpid.server.security.access.config.PlainConfiguration;
+import org.apache.qpid.server.security.access.config.RuleSet;
+
+/**
+ * These tests check that the ACL file parsing works correctly.
+ * 
+ * For each message that can be returned in a {@link ConfigurationException}, an ACL file is created that should trigger this
+ * particular message.
+ */
+public class AccessControlTest extends TestCase
+{
+    public void writeACLConfig(String...aclData) throws Exception
+    {
+        File acl = File.createTempFile(getClass().getName() + getName(), "acl");
+        acl.deleteOnExit();
+        
+        // Write ACL file
+        PrintWriter aclWriter = new PrintWriter(new FileWriter(acl));
+        for (String line : aclData)
+        {
+            aclWriter.println(line);
+        }
+        aclWriter.close();
+
+        // Load ruleset
+        ConfigurationFile configFile = new PlainConfiguration(acl);
+        RuleSet ruleSet = configFile.load();
+    }
+
+    public void testMissingACLConfig() throws Exception
+    {
+        try
+        {
+            // Load ruleset
+	        ConfigurationFile configFile = new PlainConfiguration(new File("doesnotexist"));
+	        RuleSet ruleSet = configFile.load();
+            
+            fail("fail");
+        }
+        catch (ConfigurationException ce)
+        {
+            assertEquals(String.format(PlainConfiguration.CONFIG_NOT_FOUND_MSG, "doesnotexist"), ce.getMessage());
+            assertTrue(ce.getCause() instanceof FileNotFoundException);
+            assertEquals("doesnotexist (No such file or directory)", ce.getCause().getMessage());
+        }
+    }
+
+    public void testACLFileSyntaxContinuation() throws Exception
+    {
+        try
+        {
+            writeACLConfig("ACL ALLOW ALL \\ ALL");
+            fail("fail");
+        }
+        catch (ConfigurationException ce)
+        {
+            assertEquals(String.format(PlainConfiguration.PREMATURE_CONTINUATION_MSG, 1), ce.getMessage());
+        }
+    }
+
+    public void testACLFileSyntaxTokens() throws Exception
+    {
+        try
+        {
+            writeACLConfig("ACL unparsed ALL ALL");
+            fail("fail");
+        }
+        catch (ConfigurationException ce)
+        {
+            assertEquals(String.format(PlainConfiguration.PARSE_TOKEN_FAILED_MSG, 1), ce.getMessage());
+            assertTrue(ce.getCause() instanceof IllegalArgumentException);
+            assertEquals("Not a valid permission: unparsed", ce.getCause().getMessage());
+        }
+    }
+
+    public void testACLFileSyntaxNotEnoughGroup() throws Exception
+    {
+        try
+        {
+            writeACLConfig("GROUP blah");
+            fail("fail");
+        }
+        catch (ConfigurationException ce)
+        {
+            assertEquals(String.format(PlainConfiguration.NOT_ENOUGH_GROUP_MSG, 1), ce.getMessage());
+        }
+    }
+
+    public void testACLFileSyntaxNotEnoughACL() throws Exception
+    {
+        try
+        {
+            writeACLConfig("ACL ALLOW");
+            fail("fail");
+        }
+        catch (ConfigurationException ce)
+        {
+            assertEquals(String.format(PlainConfiguration.NOT_ENOUGH_ACL_MSG, 1), ce.getMessage());
+        }
+    }
+
+    public void testACLFileSyntaxNotEnoughConfig() throws Exception
+    {
+        try
+        {
+            writeACLConfig("CONFIG");
+            fail("fail");
+        }
+        catch (ConfigurationException ce)
+        {
+            assertEquals(String.format(PlainConfiguration.NOT_ENOUGH_TOKENS_MSG, 1), ce.getMessage());
+        }
+    }
+
+    public void testACLFileSyntaxNotEnough() throws Exception
+    {
+        try
+        {
+            writeACLConfig("INVALID");
+            fail("fail");
+        }
+        catch (ConfigurationException ce)
+        {
+            assertEquals(String.format(PlainConfiguration.NOT_ENOUGH_TOKENS_MSG, 1), ce.getMessage());
+        }
+    }
+
+    public void testACLFileSyntaxPropertyKeyOnly() throws Exception
+    {
+        try
+        {
+            writeACLConfig("ACL ALLOW adk CREATE QUEUE name");
+            fail("fail");
+        }
+        catch (ConfigurationException ce)
+        {
+            assertEquals(String.format(PlainConfiguration.PROPERTY_KEY_ONLY_MSG, 1), ce.getMessage());
+        }
+    }
+
+    public void testACLFileSyntaxPropertyNoEquals() throws Exception
+    {
+        try
+        {
+            writeACLConfig("ACL ALLOW adk CREATE QUEUE name test");
+            fail("fail");
+        }
+        catch (ConfigurationException ce)
+        {
+            assertEquals(String.format(PlainConfiguration.PROPERTY_NO_EQUALS_MSG, 1), ce.getMessage());
+        }
+    }
+
+    public void testACLFileSyntaxPropertyNoValue() throws Exception
+    {
+        try
+        {
+            writeACLConfig("ACL ALLOW adk CREATE QUEUE name =");
+            fail("fail");
+        }
+        catch (ConfigurationException ce)
+        {
+            assertEquals(String.format(PlainConfiguration.PROPERTY_NO_VALUE_MSG, 1), ce.getMessage());
+        }
+    }
+}

Added: qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java?rev=949783&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java (added)
+++ qpid/trunk/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java Mon May 31 16:04:45 2010
@@ -0,0 +1,292 @@
+/*
+ *
+ * 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 org.apache.qpid.server.security.access.plugins;
+
+import junit.framework.TestCase;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.security.Result;
+import org.apache.qpid.server.security.access.ObjectProperties;
+import org.apache.qpid.server.security.access.ObjectType;
+import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.qpid.server.security.access.config.RuleSet;
+
+/**
+ * This test checks that the {@link RuleSet} object which forms the core of the access control plugin performs correctly.
+ * 
+ * The ruleset is configured directly rather than using an external file by adding rules individually, calling the
+ * {@link RuleSet#grant(Integer, String, Permission, Operation, ObjectType, ObjectProperties)} method. Then, the
+ * access control mechanism is validated by checking whether operations would be authorised by calling the
+ * {@link RuleSet#check(String, Operation, ObjectType, ObjectProperties)} method.
+ */
+public class RuleSetTest extends TestCase
+{
+    private RuleSet _ruleSet;
+
+    // Common things that are passed to frame constructors
+    private AMQShortString _queueName = new AMQShortString(this.getClass().getName() + "queue");
+    private AMQShortString _exchangeName = new AMQShortString("amq.direct");
+    private AMQShortString _exchangeType = new AMQShortString("direct");
+
+    @Override
+    public void setUp()
+    {
+        // Each test will cause a new application registry and non-transitive rule set to be created
+        ApplicationRegistry.getInstance();
+        _ruleSet = new RuleSet();
+        _ruleSet.configure(RuleSet.TRANSITIVE, Boolean.FALSE);
+    }
+
+    @Override
+    protected void tearDown() throws Exception
+    {
+        // Ensure we close the opened Registry
+        ApplicationRegistry.remove();
+        _ruleSet.clear();
+    }
+
+    public void assertDenyGrantAllow(String identity, Operation operation, ObjectType objectType)
+    {
+        assertDenyGrantAllow(identity, operation, objectType, ObjectProperties.EMPTY);
+    }
+    
+    public void assertDenyGrantAllow(String identity, Operation operation, ObjectType objectType, ObjectProperties properties)
+    {
+        assertEquals(Result.DENIED, _ruleSet.check(identity, operation, objectType, properties));
+        _ruleSet.grant(0, identity, Permission.ALLOW, operation, objectType, properties);
+        assertEquals(1, _ruleSet.getRuleCount());
+        assertEquals(Result.ALLOWED, _ruleSet.check(identity, operation, objectType, properties));
+    }
+
+    public void testEmptyRuleSet()
+    {
+        assertNotNull(_ruleSet);
+        assertEquals(_ruleSet.getRuleCount(), 0);
+        assertEquals(_ruleSet.getDefault(), _ruleSet.check("user", Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY));
+    }
+    
+    public void testVirtualHostAccess() throws Exception
+    {
+        assertDenyGrantAllow("user", Operation.ACCESS, ObjectType.VIRTUALHOST);
+    }
+
+    public void testQueueCreateNamed() throws Exception
+    {
+        assertDenyGrantAllow("user", Operation.CREATE, ObjectType.QUEUE, new ObjectProperties(_queueName));
+    }
+
+    public void testQueueCreatenamedNullRoutingKey()
+    {
+        ObjectProperties properties = new ObjectProperties(_queueName);
+        properties.put(ObjectProperties.Property.ROUTING_KEY, (String) null);
+        
+        assertDenyGrantAllow("user", Operation.CREATE, ObjectType.QUEUE, properties);
+    }
+
+    public void testExchangeCreate()
+    {
+        ObjectProperties properties = new ObjectProperties(_exchangeName);
+        properties.put(ObjectProperties.Property.TYPE, _exchangeType.asString());
+        
+        assertDenyGrantAllow("user", Operation.CREATE, ObjectType.EXCHANGE, properties);
+    }
+
+    public void testConsume()
+    {
+        assertDenyGrantAllow("user", Operation.CONSUME, ObjectType.QUEUE);
+    }
+
+    public void testPublish()
+    {
+        assertDenyGrantAllow("user", Operation.PUBLISH, ObjectType.EXCHANGE);
+    }
+
+    /**
+    * If the consume permission for temporary queues is for an unnamed queue then it should
+    * be global for any temporary queue but not for any non-temporary queue
+    */
+    public void testTemporaryUnnamedQueueConsume()
+    {
+        ObjectProperties temporary = new ObjectProperties();
+        temporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+        
+        ObjectProperties normal = new ObjectProperties();
+        normal.put(ObjectProperties.Property.AUTO_DELETE, Boolean.FALSE);
+        
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, temporary));
+        _ruleSet.grant(0, "user", Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, temporary);
+        assertEquals(1, _ruleSet.getRuleCount());
+        assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, temporary));
+        
+        // defer to global if exists, otherwise default answer - this is handled by the security manager
+        assertEquals(Result.DEFER, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, normal));
+    }
+
+    /**
+     * Test that temporary queue permissions before queue perms in the ACL config work correctly
+     */
+    public void testTemporaryQueueFirstConsume()
+    {
+        ObjectProperties temporary = new ObjectProperties(_queueName);
+        temporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+        
+        ObjectProperties normal = new ObjectProperties(_queueName);
+        normal.put(ObjectProperties.Property.AUTO_DELETE, Boolean.FALSE);
+        
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, temporary));
+
+        // should not matter if the temporary permission is processed first or last
+        _ruleSet.grant(1, "user", Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, normal);
+        _ruleSet.grant(2, "user", Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, temporary);
+        assertEquals(2, _ruleSet.getRuleCount());
+        
+        assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, normal));
+        assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, temporary));
+    }
+
+    /**
+     * Test that temporary queue permissions after queue perms in the ACL config work correctly
+     */
+    public void testTemporaryQueueLastConsume()
+    {
+        ObjectProperties temporary = new ObjectProperties(_queueName);
+        temporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+        
+        ObjectProperties normal = new ObjectProperties(_queueName);
+        normal.put(ObjectProperties.Property.AUTO_DELETE, Boolean.FALSE);
+        
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, temporary));
+
+        // should not matter if the temporary permission is processed first or last
+        _ruleSet.grant(1, "user", Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, temporary);
+        _ruleSet.grant(2, "user", Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, normal);
+        assertEquals(2, _ruleSet.getRuleCount());
+        
+        assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, normal));
+        assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, temporary));
+    }
+
+    /*
+     * Test different rules for temporary queues.
+     */
+    
+    /**
+     * The more generic rule first is used, so both requests are allowed.
+     */
+    public void testFirstNamedSecondTemporaryQueueDenied()
+    {
+        ObjectProperties named = new ObjectProperties(_queueName);
+        ObjectProperties namedTemporary = new ObjectProperties(_queueName);
+        namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+        
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+
+        _ruleSet.grant(1, "user", Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, named);
+        _ruleSet.grant(2, "user", Permission.DENY, Operation.CREATE, ObjectType.QUEUE, namedTemporary);
+        assertEquals(2, _ruleSet.getRuleCount());
+        
+        assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+        assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+    }
+    
+    /**
+     * The more specific rule is first, so those requests are denied.
+     */
+    public void testFirstTemporarySecondNamedQueueDenied()
+    {
+        ObjectProperties named = new ObjectProperties(_queueName);
+        ObjectProperties namedTemporary = new ObjectProperties(_queueName);
+        namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+        
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+
+        _ruleSet.grant(1, "user", Permission.DENY, Operation.CREATE, ObjectType.QUEUE, namedTemporary);
+        _ruleSet.grant(2, "user", Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, named);
+        assertEquals(2, _ruleSet.getRuleCount());
+        
+        assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+    }
+    
+    /**
+     * The more specific rules are first, so those requests are denied.
+     */
+    public void testFirstTemporarySecondDurableThirdNamedQueueDenied()
+    {
+        ObjectProperties named = new ObjectProperties(_queueName);
+        ObjectProperties namedTemporary = new ObjectProperties(_queueName);
+        namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+        ObjectProperties namedDurable = new ObjectProperties(_queueName);
+        namedDurable.put(ObjectProperties.Property.DURABLE, Boolean.TRUE);
+        
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedDurable));
+
+        _ruleSet.grant(1, "user", Permission.DENY, Operation.CREATE, ObjectType.QUEUE, namedTemporary);
+        _ruleSet.grant(2, "user", Permission.DENY, Operation.CREATE, ObjectType.QUEUE, namedDurable);
+        _ruleSet.grant(3, "user", Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, named);
+        assertEquals(3, _ruleSet.getRuleCount());
+        
+        assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedDurable));
+    }
+    
+    public void testNamedTemporaryQueueAllowed()
+    {
+        ObjectProperties named = new ObjectProperties(_queueName);
+        ObjectProperties namedTemporary = new ObjectProperties(_queueName);
+        namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+        
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+
+        _ruleSet.grant(1, "user", Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, namedTemporary);
+        _ruleSet.grant(2, "user", Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, named);
+        assertEquals(2, _ruleSet.getRuleCount());
+        
+        assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+        assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+    }
+    
+    public void testNamedTemporaryQueueDeniedAllowed()
+    {
+        ObjectProperties named = new ObjectProperties(_queueName);
+        ObjectProperties namedTemporary = new ObjectProperties(_queueName);
+        namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+        
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+
+        _ruleSet.grant(1, "user", Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, namedTemporary);
+        _ruleSet.grant(2, "user", Permission.DENY, Operation.CREATE, ObjectType.QUEUE, named);
+        assertEquals(2, _ruleSet.getRuleCount());
+        
+        assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+        assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+    }
+}



---------------------------------------------------------------------
Apache Qpid - AMQP Messaging Implementation
Project:      http://qpid.apache.org
Use/Interact: mailto:commits-subscribe@qpid.apache.org