summaryrefslogtreecommitdiff
path: root/qpid/java/broker-plugins/access-control/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'qpid/java/broker-plugins/access-control/src/main')
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java77
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Action.java192
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java56
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java323
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java170
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/RuleSet.java489
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/XMLConfiguration.java31
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/logging/AccessControl_logmessages.properties28
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControl.java116
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlActivator.java42
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlConfiguration.java83
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/resources/acl.xsd29
12 files changed, 1636 insertions, 0 deletions
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java
new file mode 100644
index 0000000000..a684e52ce4
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java
@@ -0,0 +1,77 @@
+/*
+ *
+ * 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.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;
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Action.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Action.java
new file mode 100644
index 0000000000..fdbd96e63e
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Action.java
@@ -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();
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java
new file mode 100644
index 0000000000..8b1a00259b
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java
@@ -0,0 +1,56 @@
+/*
+ *
+ * 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.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);
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java
new file mode 100644
index 0000000000..9f2168a31c
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java
@@ -0,0 +1,323 @@
+/*
+ *
+ * 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.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;
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java
new file mode 100644
index 0000000000..15d6b67192
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java
@@ -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();
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/RuleSet.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/RuleSet.java
new file mode 100644
index 0000000000..ebc73440ed
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/RuleSet.java
@@ -0,0 +1,489 @@
+/*
+ * 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.logging.actors.CurrentActor;
+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.logging.AccessControlMessages;
+
+/**
+ * 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;
+ }
+
+ private Permission noLog(Permission permission)
+ {
+ switch (permission)
+ {
+ case ALLOW:
+ case ALLOW_LOG:
+ return Permission.ALLOW;
+ case DENY:
+ case DENY_LOG:
+ default:
+ return Permission.DENY;
+ }
+ }
+
+ // 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, noLog(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, noLog(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, noLog(permission), new Action(Operation.BIND, ObjectType.EXCHANGE, exchProperties));
+ if (action.getProperties().isSet(ObjectProperties.Property.AUTO_DELETE))
+ {
+ addRule(null, identity, noLog(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, noLog(permission), new Action(Operation.UNBIND, ObjectType.EXCHANGE, exchProperties));
+ }
+ else if (action.getOperation() != Operation.ACCESS && action.getObjectType() != ObjectType.VIRTUALHOST)
+ {
+ addRule(null, identity, noLog(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:
+ CurrentActor.get().message(AccessControlMessages.ALLOWED(
+ action.getOperation().toString(), action.getObjectType().toString(), action.getProperties().toString()));
+ case ALLOW:
+ return Result.ALLOWED;
+ case DENY_LOG:
+ CurrentActor.get().message(AccessControlMessages.DENIED(
+ action.getOperation().toString(), action.getObjectType().toString(), action.getProperties().toString()));
+ 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);
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/XMLConfiguration.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/XMLConfiguration.java
new file mode 100644
index 0000000000..a4f6f8b65a
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/XMLConfiguration.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * 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.io.File;
+
+public class XMLConfiguration extends AbstractConfiguration
+{
+ public XMLConfiguration(File file)
+ {
+ super(file);
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/logging/AccessControl_logmessages.properties b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/logging/AccessControl_logmessages.properties
new file mode 100644
index 0000000000..bf80df3722
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/logging/AccessControl_logmessages.properties
@@ -0,0 +1,28 @@
+#
+# 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.
+#
+
+# org.apache.qpid.server.security.access.logging.AccessControl
+
+# Access Control logging message i18n strings.
+
+# 'accept-log' rule message
+ALLOWED = ACL-1001 : Allowed : {0} {1} {2}
+
+# 'deny-log' rule message
+DENIED = ACL-1002 : Denied : {0} {1} {2} \ No newline at end of file
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControl.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControl.java
new file mode 100644
index 0000000000..69cfa173bd
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControl.java
@@ -0,0 +1,116 @@
+/*
+ *
+ * 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.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.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
+ {
+ AccessControlConfiguration configuration = config.getConfiguration(AccessControlConfiguration.class.getName());
+
+ // If there is no configuration for this plugin then don't load it.
+ if (configuration == null)
+ {
+ return null;
+ }
+
+ AccessControl plugin = new AccessControl();
+ plugin.configure(configuration);
+ return plugin;
+ }
+ };
+
+ public Result getDefault()
+ {
+ return _ruleSet.getDefault();
+ }
+
+ /**
+ * 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);
+ }
+
+ public void configure(ConfigurationPlugin config)
+ {
+ super.configure(config);
+
+ AccessControlConfiguration accessConfig = (AccessControlConfiguration) _config;
+
+ _ruleSet = accessConfig.getRuleSet();
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlActivator.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlActivator.java
new file mode 100644
index 0000000000..72eac7dbe6
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlActivator.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * 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 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;
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlConfiguration.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlConfiguration.java
new file mode 100644
index 0000000000..f7db740ebc
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlConfiguration.java
@@ -0,0 +1,83 @@
+/*
+ *
+ * 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.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;
+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;
+
+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.aclv2", "virtualhosts.virtualhost.security.aclv2");
+ }
+ };
+
+ private RuleSet _ruleSet;
+
+ public String[] getElementsProcessed()
+ {
+ return new String[] { "" };
+ }
+
+ public String getFileName()
+ {
+ return _configuration.getString("");
+ }
+
+ public void validateConfiguration() throws ConfigurationException
+ {
+ String filename = getFileName();
+ if (filename == null)
+ {
+ throw new ConfigurationException("No ACL file name specified");
+ }
+
+ File aclFile = new File(filename);
+
+ ConfigurationFile configFile = new PlainConfiguration(aclFile);
+ _ruleSet = configFile.load();
+ }
+
+ public RuleSet getRuleSet()
+ {
+ return _ruleSet;
+ }
+
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/resources/acl.xsd b/qpid/java/broker-plugins/access-control/src/main/resources/acl.xsd
new file mode 100644
index 0000000000..9a165b50b8
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/resources/acl.xsd
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ -
+ - 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.
+ -
+ -->
+<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>