diff options
Diffstat (limited to 'qpid/java/broker-plugins')
68 files changed, 8345 insertions, 0 deletions
diff --git a/qpid/java/broker-plugins/access-control/MANIFEST.MF b/qpid/java/broker-plugins/access-control/MANIFEST.MF new file mode 100644 index 0000000000..1cd285ba20 --- /dev/null +++ b/qpid/java/broker-plugins/access-control/MANIFEST.MF @@ -0,0 +1,42 @@ +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://qpid.apache.org/acl.html +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.logging, + org.apache.qpid.server.logging.actors, + org.apache.qpid.server.logging.subjects, + org.apache.qpid.server.plugins, + org.apache.qpid.server.queue, + org.apache.qpid.server.registry, + 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, + org.apache.qpid.server.security.access.logging +Export-Package: org.apache.qpid.server.security.access.plugins diff --git a/qpid/java/broker-plugins/access-control/build.xml b/qpid/java/broker-plugins/access-control/build.xml new file mode 100644 index 0000000000..89f8240fd5 --- /dev/null +++ b/qpid/java/broker-plugins/access-control/build.xml @@ -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. + --> +<project name="Qpid Broker-Plugins Access Control" default="build"> + <property name="module.depends" value="common broker broker-plugins" /> + <property name="module.test.depends" value="test common/test broker/test management/common systests" /> + + <property name="module.manifest" value="MANIFEST.MF" /> + <property name="module.plugin" value="true" /> + + <import file="../../module.xml" /> + + <target name="bundle" depends="bundle-tasks"/> + + <target name="precompile" depends="gen_logging"/> +</project> 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> diff --git a/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/AccessControlTest.java b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/AccessControlTest.java new file mode 100644 index 0000000000..309a3aeb2c --- /dev/null +++ b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/AccessControlTest.java @@ -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()); + } + } +} diff --git a/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java new file mode 100644 index 0000000000..aad7290557 --- /dev/null +++ b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java @@ -0,0 +1,289 @@ +/* + * + * 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.framing.AMQShortString; +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; +import org.apache.qpid.test.utils.QpidTestCase; + +/** + * 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 QpidTestCase +{ + 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() throws Exception + { + super.setUp(); + + _ruleSet = new RuleSet(); + _ruleSet.configure(RuleSet.TRANSITIVE, Boolean.FALSE); + } + + @Override + public void tearDown() throws Exception + { + _ruleSet.clear(); + super.tearDown(); + } + + 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)); + } +} diff --git a/qpid/java/broker-plugins/experimental/info/MANIFEST.MF b/qpid/java/broker-plugins/experimental/info/MANIFEST.MF new file mode 100644 index 0000000000..f213104d8d --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/MANIFEST.MF @@ -0,0 +1,16 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: QpidPlugin +Bundle-SymbolicName: qpid_info_plugin;singleton:=true +Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt +Bundle-Version: 1.0.0 +Bundle-Activator: org.apache.qpid.info.Activator +Import-Package: org.apache.qpid.server.configuration, + org.osgi.framework, + org.apache.qpid.common, + org.apache.qpid.server.registry +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-ClassPath: . +Bundle-ActivationPolicy: lazy +Export-Package: org.apache.qpid.info;uses:="org.osgi.framework" + diff --git a/qpid/java/broker-plugins/experimental/info/build.properties b/qpid/java/broker-plugins/experimental/info/build.properties new file mode 100644 index 0000000000..bdbbe1c2af --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/build.properties @@ -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. +# +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.xml,\ + lib/eventTrackerClient-2.7.0.jar,\ + lib/commons-logging-1.0.4.jar +src.includes = src/,\ + plugin.xml,\ + lib/,\ + build.properties,\ + bin/,\ + META-INF/ diff --git a/qpid/java/broker-plugins/experimental/info/build.xml b/qpid/java/broker-plugins/experimental/info/build.xml new file mode 100644 index 0000000000..c5881aa839 --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/build.xml @@ -0,0 +1,32 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one +nn - or more contributor license agreements. See the NOTICE file + -n 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="AMQ Broker Info Plugin" default="build"> + + <property name="module.depends" value="common broker broker-plugins"/> + <property name="module.test.depends" value="test broker/test management/common client systests common/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> diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Activator.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Activator.java new file mode 100644 index 0000000000..c7d3fd38ff --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Activator.java @@ -0,0 +1,204 @@ +/* + * + * 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.info; + +import org.apache.qpid.info.util.HttpPoster; +import org.apache.qpid.info.util.IniFileReader; +import org.apache.qpid.info.util.SoapClient; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** The Activator class for the OSGI info service */ +public class Activator implements BundleActivator +{ + + private final List<String> _soapPropList = Arrays.asList("soap.hostname", + "soap.port", "soap.path", "soap.action", "soap.envelope"); + + private final List<String> _httpPropList = Arrays.asList("http.url", + "http.envelope"); + + InfoServiceImpl _service = null; + + BundleContext _ctx = null; + + /** + * Start bundle method + * + * @param ctx the bundle context + */ + public void start(BundleContext ctx) throws Exception + { + if (null != ctx) + { + _ctx = ctx; + _service = new InfoServiceImpl(); + ctx.registerService(InfoService.class.getName(), _service, null); + sendInfo("STARTUP"); + } + } + + /** + * Stop the bundle method + * + * @param ctx the bundle context + */ + public void stop(BundleContext ctx) throws Exception + { + sendInfo("SHUTDOWN"); + } + + /** + * Sends the information message + * + * @param action label that identifies if we are starting up or shutting down + */ + private void sendInfo(String action) + { + if ((null == _ctx) && (null == _service)) + { + // invalid state + return; + } + + IniFileReader ifr = new IniFileReader(); + try + { + String QPID_HOME = System.getProperty("QPID_HOME"); + String cfgFilePath = QPID_HOME + File.separator + "etc" + + File.separator + "qpidinfo.ini"; + ifr.load(cfgFilePath); + } + catch (Throwable ex) + { + // drop everything to be silent + return; + } + + // Only send Messages if we have some sections. + if (ifr.getSections().size() != 0) + { + Info<? extends Map<String, ?>> info = _service.invoke(action); + String protocol = ifr.getSections().get("").getProperty("protocol"); + sendMessages(protocol, ifr, info); + } + } + + /** + * Sends all the messages configured in the properties file + * + * @param protocol indicates what protocol to be used: http and soap implemented + * for now + * @param ifr an instance of IniFileReader class + * @param info an instance of an Info object, encapsulating the information + * we want to send + */ + private void sendMessages(String protocol, IniFileReader ifr, + Info<? extends Map<String, ?>> info) + { + if (null != protocol) + { + // Set the global properties first (as they are the defaults) + Properties defaultProps = ifr.getSections().get(""); + if (protocol.toLowerCase().startsWith("http")) + { + for (String section : ifr.getSections().keySet()) + { + // Skip the defaults + if (section.equals("")) + { + continue; + } + Properties props = new Properties(); + props.putAll(defaultProps); + props.putAll(ifr.getSections().get(section)); + if (isValid(protocol, props)) + { + new HttpPoster(props, info.toXML()).run(); + } + } + + } + else if (protocol.toLowerCase().startsWith("soap")) + { + for (String section : ifr.getSections().keySet()) + { + Properties props = new Properties(); + props.putAll(defaultProps); + props.putAll(ifr.getSections().get(section)); + if (isValid(protocol, props)) + { + new SoapClient(info.toMap(), props).sendSOAPMessage(); + } + } + } + } + } + + /** + * Checks if the properties for a specified protocol are valid + * + * @param protocol String representing the protocol + * @param props The properties associate with the specified protocol + * @return boolean + */ + private boolean isValid(String protocol, Properties props) + { + if (null == protocol) + { + return false; + } + String value = ""; + if (protocol.toLowerCase().startsWith("http")) + { + for (String prop : _httpPropList) + { + if (null == props.get(prop)) + { + return false; + } + } + return true; + } + + if (protocol.toLowerCase().startsWith("soap")) + { + for (String prop : _soapPropList) + { + value = props.getProperty(prop); + if (null == value) + { + return false; + } + } + return true; + } + return false; + } +} // end class + diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/AppInfo.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/AppInfo.java new file mode 100644 index 0000000000..a5d267282b --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/AppInfo.java @@ -0,0 +1,94 @@ +/* + * + * 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.info; + +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.TreeMap; + +/** AppInfo class is gathering application specific information */ +public class AppInfo +{ + + private static final List<String> appProps = Arrays.asList("QPID_HOME", + "QPID_WORK"); + + private static Map<String, String> appInfoMap = new TreeMap<String, String>(); + + /** + * getInfo method retrieves a key-value map for specific application properties + * + * @return Map<String,String> + */ + public static Map<String, String> getInfo() + { + + // Gather the selected app props + Properties sysprops = System.getProperties(); + String propName; + for (Iterator<Entry<Object, Object>> it = sysprops.entrySet() + .iterator(); it.hasNext();) + { + Entry<Object, Object> en = it.next(); + propName = en.getKey().toString(); + if (appProps.indexOf(propName) >= 0) + { + appInfoMap.put(propName, en.getValue().toString()); + } + } + + ServerConfiguration sc; + try + { + sc = ApplicationRegistry.getInstance().getConfiguration(); + if (null != sc) + { + appInfoMap.put("jmxport", sc.getJMXManagementPort() + ""); + appInfoMap.put("port", sc.getPorts().toString()); + appInfoMap.put("version", QpidProperties.getReleaseVersion()); + appInfoMap.put("vhosts", "standalone"); + appInfoMap.put("JMXPrincipalDatabase", sc + .getJMXPrincipalDatabase()); + appInfoMap.put("KeystorePath", sc.getKeystorePath()); + appInfoMap.put("PluginDirectory", sc.getPluginDirectory()); + appInfoMap.put("CertType", sc.getCertType()); + appInfoMap.put("QpidWork", sc.getQpidWork()); + appInfoMap.put("Bind", sc.getBind()); + } + } + catch (Exception e) + { + // drop everything to be silent + } + return appInfoMap; + + } + +} diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Info.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Info.java new file mode 100644 index 0000000000..2fb9382526 --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Info.java @@ -0,0 +1,143 @@ +/* + * + * 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. + * + */ + +/** + * + * @author sorin + * + * Info object + */ + +package org.apache.qpid.info; + +import org.apache.qpid.info.util.XMLWriter; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +/** + * The Info class encapsulates all the information we are collecting + * and it is able to render it in different data representations + */ +public class Info<T extends Map<String, ?>> +{ + private T _info; + + /** + * Constructor. + * + * @param info instantiates the object with a Map<String,?> + */ + public Info(T info) + { + _info = info; + } + + @Override + public String toString() + { + String result = ""; + for (Iterator<String> it = _info.keySet().iterator(); it.hasNext();) + { + String str = it.next(); + result += str + "=" + _info.get(str).toString() + "\n"; + } + return result; + } + + /** + * Renders Info map to a property object + * + * @return A Properties object representing the Info map + */ + public Properties toProps() + { + Properties props = new Properties(); + if (null == _info) + { + return null; + } + for (Iterator<String> it = _info.keySet().iterator(); it.hasNext();) + { + String key = it.next(); + props.put(key, _info.get(key)); + } + return props; + } + + /** + * Renders Info map to a StringBuffer + * + * @return A StringBuffer object representing the Info map + */ + public StringBuffer toStringBuffer() + { + StringBuffer sb = new StringBuffer(); + for (Iterator<String> it = _info.keySet().iterator(); it.hasNext();) + { + String str = it.next(); + sb.append(str + "=" + _info.get(str).toString() + "\n"); + } + return sb; + } + + /** + * Renders Info map to a StringBuffer containing an XML string + * + * @return A StringBuffer object containing an XML representation of the Info map + */ + public StringBuffer toXML() + { + XMLWriter xw = new XMLWriter(new StringBuffer()); + xw.writeXMLHeader(); + Map<String, String> attr = new HashMap<String, String>(); + xw.writeOpenTag("qpidinfo", attr); + String key; + for (Iterator<String> it = _info.keySet().iterator(); it.hasNext();) + { + attr.clear(); + key = it.next(); + xw.writeTag(key, attr, _info.get(key).toString()); + } + xw.writeCloseTag("qpidinfo"); + return xw.getXML(); + } + + /** + * Renders Info map to a HashMap + * + * @return A HashMap object representing the Info map + */ + public HashMap<String, String> toMap() + { + String key; + HashMap<String, String> infoMap = new HashMap<String, String>(); + for (Iterator<String> it = _info.keySet().iterator(); it.hasNext();) + { + key = it.next(); + infoMap.put(key, _info.get(key).toString()); + } + return infoMap; + } + +} diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoService.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoService.java new file mode 100644 index 0000000000..2804dfb1b4 --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoService.java @@ -0,0 +1,30 @@ +/* + * + * 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. + * + */ + + /** + * Interface exposing the service methods + */ + package org.apache.qpid.info; + + public interface InfoService + { + public Info<?> invoke(String action); + } diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoServiceImpl.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoServiceImpl.java new file mode 100644 index 0000000000..5522f2701e --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoServiceImpl.java @@ -0,0 +1,66 @@ +/* + * + * 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. + * + */ + +/** + * + * @author sorin + * + * Implementation for Info service + */ + +package org.apache.qpid.info; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + + +public class InfoServiceImpl implements InfoService +{ + + SortedMap<String, String> infoMap = new TreeMap<String, String>(); + + /** + * invoke method collects all the information from System and Application + * and encapsulates them in an Info object + * @return An instance of an Info object + */ + public Info<? extends Map<String,?>> invoke(String action) + { + // Record the action (STARTUP/SHUTDOWN) + infoMap.put("action",action); + + // Record the current time stamp + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ"); + infoMap.put("time", sdf.format(Calendar.getInstance().getTime())); + + // Add the system specific properties + infoMap.putAll(SystemInfo.getInfo()); + + // Add the application specific properties + infoMap.putAll(AppInfo.getInfo()); + + return new Info<SortedMap<String, String>>(infoMap); + } + +} diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/SystemInfo.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/SystemInfo.java new file mode 100644 index 0000000000..8bd94fe14d --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/SystemInfo.java @@ -0,0 +1,91 @@ +/* + * + * 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.info; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.TreeMap; +import java.util.Map.Entry; + +/** + * Collector for system specific information + */ +public class SystemInfo +{ + + private static Map<String, String> sysInfoMap = new TreeMap<String, String>(); + + private static final List<String> sysProps = Arrays.asList( + "java.class.path", "java.home", "java.vm.name", "java.vm.vendor", + "java.vm.version", "java.class.version", "java.runtime.version", + "os.arch", "os.name", "os.version", "sun.arch.data.model", + "user.home", "user.dir", "user.name", "user.timezone"); + + /** + * getInfo collects all the properties specified in sysprops list + * @return A Map<String,String> + */ + public static Map<String, String> getInfo() + { + + // Get the hostname + try + { + InetAddress addr = InetAddress.getLocalHost(); + String hostname = addr.getHostName(); + sysInfoMap.put("hostname", hostname); + sysInfoMap.put("ip", addr.getHostAddress()); + } + catch (UnknownHostException e) + { + // drop everything to be silent + } + // Get the runtime info + sysInfoMap.put("CPUCores", Runtime.getRuntime().availableProcessors() + + ""); + sysInfoMap.put("Maximum_Memory", Runtime.getRuntime().maxMemory() + ""); + sysInfoMap.put("Free_Memory", Runtime.getRuntime().freeMemory() + ""); + + // Gather the selected system props + Properties sysprops = System.getProperties(); + String propName; + for (Iterator<Entry<Object, Object>> it = sysprops.entrySet() + .iterator(); it.hasNext();) + { + Entry<Object, Object> en = it.next(); + propName = en.getKey().toString(); + if (sysProps.indexOf(propName) >= 0) + { + sysInfoMap.put(propName, en.getValue().toString()); + } + } + + return sysInfoMap; + + } + +} diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/HttpPoster.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/HttpPoster.java new file mode 100644 index 0000000000..d27980be05 --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/HttpPoster.java @@ -0,0 +1,130 @@ +/*
+ *
+ * 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.info.util;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+
+/**
+ *
+ * An simple Http post class for qpid info service
+ */
+public class HttpPoster implements Runnable
+{
+ private final String _url;
+
+ private final Hashtable<String, String> _header;
+
+ private final List<String> _response = new ArrayList<String>();
+
+ private final StringBuffer _buf;
+
+ /**
+ * Constructor
+ *
+ * @param props Properties containing the URL
+ * @param buf Buffer containing the message to be posted
+ */
+ public HttpPoster(Properties props, StringBuffer buf)
+ {
+ _buf = buf;
+ if (null != props)
+ {
+ _url = props.getProperty("http.url");
+ _header = new Hashtable<String, String>();
+ try
+ {
+ String hostname = InetAddress.getLocalHost().getHostName();
+ _header.put("hostname", hostname);
+ }
+ catch (UnknownHostException e)
+ {
+ // Silently ignoring the error ;)
+ }
+ }
+ else
+ {
+ _url = null;
+ _header = null;
+ }
+ }
+
+ /** Posts the message from the _buf StringBuffer to the http server */
+ public void run()
+ {
+ if (null == _url)
+ {
+ return;
+ }
+ String line;
+ URL urlDest;
+ URLConnection urlConn;
+ try
+ {
+ urlDest = new URL(_url);
+ urlConn = urlDest.openConnection();
+ urlConn.setDoOutput(true);
+ urlConn.setUseCaches(false);
+ for (Iterator<String> it = _header.keySet().iterator(); it.hasNext();)
+ {
+ String prop = it.next();
+ urlConn.setRequestProperty(prop, _header.get(prop));
+ }
+ OutputStreamWriter wr =
+ new OutputStreamWriter(urlConn.getOutputStream());
+ wr.write(_buf.toString());
+ wr.flush();
+ // Get the response
+ BufferedReader rd = new BufferedReader(new InputStreamReader(
+ urlConn.getInputStream()));
+ while ((line = rd.readLine()) != null)
+ {
+ _response.add(line);
+ }
+ }
+ catch (Exception ex)
+ {
+ // Silently ignoring the error ;)
+ }
+ }
+
+ /**
+ * Retrieves the response from the http server
+ *
+ * @return List<String> response received from the http server
+ */
+ public List<String> get_response()
+ {
+ return _response;
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/IniFileReader.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/IniFileReader.java new file mode 100644 index 0000000000..60a025d322 --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/IniFileReader.java @@ -0,0 +1,193 @@ +/* + * + * 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.info.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * This class is simple implementation of an ini file reader. It expects a + * file with the following structure: + * + * ; global values, can be overwritten in sections + * key1=value1 + * key2=value2 + * + * [Section1] + * key1=value1_new ; overwriting the global key1 + * key3=value3 + * key4=value4 + * + * [Section2] + * key5=value5 + * key6=value6 + * key7=value7 + * + * Note: Commentaries are preceded by ; or # and are supported throughout + * A commentary line at the end of section is interpreted as + * a section end marker + * + * A structure <String,Properties> (section name, associated properties) + * is generated as a result of processing the ini file. + */ +public class IniFileReader +{ + private final Map<String, Properties> _sections; + + private final String COMMENT_SEMICOLON = ";"; + + private final String COMMENT_HASH = "#"; + + enum State + { + IN_SECTION, OFF_SECTION, GLOBAL + } + + /* + * IniFileReader constructor + */ + + public IniFileReader() + { + _sections = new HashMap<String, Properties>(); + } + + /** + * Cleans up the after comments or the empty spaces/tabs surrounding the given string + * + * @param str The String to be cleaned + * + * @return String Cleanup Version + */ + private String cleanUp(String str) + { + if (str.contains(COMMENT_SEMICOLON)) + { + str = str.substring(0, str.indexOf(COMMENT_SEMICOLON)); + } + if (str.contains(COMMENT_HASH)) + { + str = str.substring(0, str.indexOf(COMMENT_HASH)); + } + return str.trim(); + } + + /** + * Loads and parses the ini file with the full path specified in the argument + * + * @param fileName Full path to the ini file + * + * @throws IllegalArgumentException If the file cannot be processed + */ + public void load(String fileName) throws IllegalArgumentException + { + if (!new File(fileName).isFile()) + { + throw new IllegalArgumentException("File: " + fileName + " does not exist or cannot be read."); + } + State state = State.GLOBAL; + String line; + Properties sectionProps = new Properties(); + String sectionName = ""; + try + { + BufferedReader in = new BufferedReader(new FileReader(fileName)); + while ((line = in.readLine()) != null) + { + String str = cleanUp(line); + + // Did we get a section header? + if (str.startsWith("[")) + { + if (!str.endsWith("]")) + { + // Index of 1 to skip '[' + throw new IllegalArgumentException(str.substring(1) + + " is not closed"); + } + + // We encountered a new section header + if (state != State.IN_SECTION) + { + _sections.put(sectionName, sectionProps); + sectionProps = new Properties(); + sectionName = str.replace("[", "").replace("]", "") + .trim(); + state = State.IN_SECTION; + } + } + + // Any other line tested separately, ignore if out of a section + // and add if in section + if (str.length() == 0) + { + // We encountered a commented or an empty line, both cases + // mean we are off the section + if (state == State.IN_SECTION) + { + _sections.put(sectionName, sectionProps); + state = State.OFF_SECTION; + } + } + else + { + // proper line, add it to the props + if (state != State.OFF_SECTION) + { + if (str.contains("=")) + { + int ix = str.indexOf("="); + sectionProps.put(str.substring(0, ix).trim(), str + .substring(ix + 1).trim()); + } + } + } + } + in.close(); + } + catch (IOException e) + { + _sections.clear(); + return; + } + if (state != State.OFF_SECTION) + { + _sections.put(sectionName, sectionProps); + } + } + + /** + * Getter for the Sections Map + * + * @return Map<String,Properties> The parsed content of the ini file in this structure + */ + public Map<String, Properties> getSections() + { + return _sections; + } + +} diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/SoapClient.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/SoapClient.java new file mode 100644 index 0000000000..0f66085fc3 --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/SoapClient.java @@ -0,0 +1,155 @@ +/* + * + * 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. + * + */ +/** + * + * @author sorin + * + * An simple SOAP client for qpid info service + */ +package org.apache.qpid.info.util; + +import java.io.BufferedWriter; +import java.io.OutputStreamWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.util.HashMap; +import java.util.Properties; + +public class SoapClient +{ + + private final StringBuffer _xmlData; + + private final Properties _destprops; + + private final String _hostname; + + private final int _port; + + private final String _urlpath; + + private final String _soapenvelope; + + private final String _soapaction; + + private final StringBuffer _soapMessage = new StringBuffer(); + + + public SoapClient(HashMap<String, String> map, Properties destprops) + { + _destprops = destprops; + _hostname = (String) _destprops.get("soap.hostname"); + _port = Integer.parseInt((String) _destprops.get("soap.port")); + _urlpath = (String) destprops.get("soap.path"); + _soapenvelope = (String) destprops.get("soap.envelope"); + _soapaction = (String) destprops.get("soap.action"); + _xmlData = new StringBuffer(_soapenvelope); + replaceVariables(map); + } + + public StringBuffer getXMLData() + { + return _xmlData; + } + + public StringBuffer getSoapMessage() { + return _soapMessage; + } + + public String getSoapEnvelope() { + return _soapenvelope; + } + + /** + * Clears and sets new XML data + * @param sb the new data to set + */ + public void setXMLData(StringBuffer sb) + { + _xmlData.delete(0, _xmlData.length()); + _xmlData.append(sb); + } + + + public void replaceVariables(HashMap<String, String> vars) + { + int ix = 0; + for (String var : vars.keySet()) + { + while ((ix = _xmlData.indexOf("@" + var.toUpperCase())) >= 0) + { + _xmlData.replace(ix, ix + 1 + var.length(), vars.get(var)); + } + } + } + + public void replaceVariables(Properties varProps) + { + if (varProps == null) + { + return; + } + int ix = 0; + for (Object var : varProps.keySet()) + { + while ((ix = _xmlData.indexOf("@" + var)) >= 0) + { + _xmlData.replace(ix, ix + 1 + var.toString().length(), varProps + .get(var).toString()); + } + } + } + + + public void sendSOAPMessage() + { + + try + { + InetAddress addr = InetAddress.getByName(_hostname); + Socket sock = new Socket(addr, _port); + StringBuffer sb = new StringBuffer(); + sb.append("POST " + _urlpath + " HTTP/1.1\r\n"); + sb.append("Host: " + _hostname + ":" + _port + "\r\n"); + sb.append("Content-Length: " + _xmlData.length() + "\r\n"); + sb.append("Content-Type: text/xml; charset=\"utf-8\"\r\n"); + sb.append("SOAPAction: \"urn:"+ _soapaction +"\"\r\n"); + sb.append("User-Agent: Axis2\r\n"); + sb.append("\r\n"); + // Send header + BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(sock + .getOutputStream(), "UTF-8")); + synchronized(_soapMessage) { + _soapMessage.setLength(0); + _soapMessage.append(sb); + _soapMessage.append(_xmlData); + } + // Send data + wr.write(_soapMessage.toString()); + wr.flush(); + wr.close(); + + } catch (Exception ex) + { + // Drop any exception + } + } +} diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/XMLWriter.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/XMLWriter.java new file mode 100644 index 0000000000..a266edae00 --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/XMLWriter.java @@ -0,0 +1,100 @@ +/* + * + * 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.info.util; + +import java.util.Map; + +/** + * + * Naive and rudimentary XML writer + * It has methods to write the header, a tag with attributes + * and values. It escapes the XML special characters + */ +public class XMLWriter +{ + + private final StringBuffer _sb; + + private final String INDENT = " "; + + public XMLWriter(StringBuffer sb) + { + _sb = sb; + } + + public StringBuffer getXML() + { + return _sb; + } + + public void writeXMLHeader() + { + _sb.append("<?xml version=\"1.0\"?>\n"); + } + + public void writeTag(String tagName, Map<String, String> attributes, + String value) + { + writeOpenTag(tagName, attributes); + writeValue(value); + writeCloseTag(tagName); + } + + public void writeOpenTag(String tagName, Map<String, String> attributes) + { + _sb.append("<").append(tagName); + if (null == attributes) + { + _sb.append(">\n"); + return; + } + for (String key : attributes.keySet()) + { + _sb.append(" ").append(key + "=\"" + attributes.get(key) + "\""); + } + _sb.append(">\n"); + + } + + private void writeValue(String val) + { + _sb.append(INDENT).append(escapeXML(val) + "\n"); + } + + public void writeCloseTag(String tagName) + { + _sb.append("</" + tagName + ">\n"); + } + + private String escapeXML(String xmlStr) + { + if (null == xmlStr) + return null; + xmlStr = xmlStr.replaceAll("&", "&"); + xmlStr = xmlStr.replace("<", "<"); + xmlStr = xmlStr.replace(">", ">"); + xmlStr = xmlStr.replace("\"", """); + xmlStr = xmlStr.replace("'", "'"); + return xmlStr; + } + +} diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/systest/InfoPluginTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/systest/InfoPluginTest.java new file mode 100644 index 0000000000..156c9eb138 --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/systest/InfoPluginTest.java @@ -0,0 +1,276 @@ +/* + * + * 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.info.systest; + +import org.apache.qpid.test.utils.QpidBrokerTestCase; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class InfoPluginTest extends QpidBrokerTestCase +{ + private String QPID_HOME = null; + + private ServerSocket _server = null; + + private int _port; + + private static final String CR = System.getProperty("line.separator"); + + private static final String FS = File.separator; + + private final String _cfgRelPath = "etc" + FS + "qpidinfo.ini"; + + private File _tmpCfgFile; + + private final String _soapEnvelopeHead = "<?xml version=\"1.0\"?><soap:Envelope xmlns:soap=\"http://www.w3.org/2001/12/soap-envelope\" soap:encodingStyle=\"http://www.w3.org/2001/12/soap-encoding\">"; + + private final String _soapEnvelopeTail = "</soap:Envelope>"; + + private String _soapMessage1 = "@ACTION" + "-" + "@VERSION"; + + private String _soapMessage2 = "@VERSION" + "-" + "@ACTION"; + + private CountDownLatch _latch = new CountDownLatch(2); + + final List<List<String>> _recv = new ArrayList<List<String>>(); + + Thread _socketAcceptor; + + public void setUp() throws Exception + { + QPID_HOME = System.getProperty("QPID_HOME"); + if (QPID_HOME != null) + { + System.out.println("QPID_HOME=" + QPID_HOME); + } + else + { + fail("QPID_HOME not set"); + } + + startSoapServer(); + // Must start the server first to identify a free port. + createConfigFile(); + } + + public void tearDown() throws Exception + { + System.out.println("*** Stopping socket server..."); + _socketAcceptor.join(2000); + + System.out.println("*** Deleting the config file..."); + if (_tmpCfgFile.isFile()) + { + _tmpCfgFile.delete(); + } + super.tearDown(); + } + + private void createConfigFile() + { + try + { + _tmpCfgFile = new File(QPID_HOME + FS + _cfgRelPath); + _tmpCfgFile.deleteOnExit(); + if (_tmpCfgFile.isFile()) + { + _tmpCfgFile.delete(); + } + assertTrue("Unable to create file.", _tmpCfgFile.createNewFile()); + assertTrue(_tmpCfgFile.isFile()); + FileWriter fwriter = new FileWriter(_tmpCfgFile); + BufferedWriter writer = new BufferedWriter(fwriter); + writer.write("protocol=soap"); + writer.write(CR); + writer.write("soap.hostname=localhost"); + writer.write(CR); + writer.write("soap.port=" + _port); + writer.write(CR); + writer.write(CR); + writer.write("[MSG1]"); + writer.write(CR); + writer.write("soap.path=/info1"); + writer.write(CR); + writer.write("soap.action=submitinfo1"); + writer.write(CR); + writer.write("soap.envelope=" + _soapEnvelopeHead + _soapMessage1 + + _soapEnvelopeTail); + writer.write(CR); + writer.write(CR); + writer.write("[MSG2]"); + writer.write(CR); + writer.write("soap.path=/info2"); + writer.write(CR); + writer.write("soap.action=submitinfo2"); + writer.write(CR); + writer.write("soap.envelope=" + _soapEnvelopeHead + _soapMessage2 + + _soapEnvelopeTail); + writer.write(CR); + writer.write(CR); + writer.close(); + assertTrue("Config file size is zero", _tmpCfgFile.length() > 0); + } + catch (IOException e) + { + fail("Unable to create the qpidinfo.properties due to: " + + e.getMessage()); + } + } + + private void startSoapServer() throws Exception + { + try + { + _server = new ServerSocket(0); + _port = _server.getLocalPort(); + assertTrue("Server not yet bound.", _port != -1); + + assertNotNull("SocketServer is null", _server); + } + catch (Exception ex) + { + fail("Unable to start the socket server due to: " + ex.getMessage()); + } + + _socketAcceptor = new Thread() + { + public void run() + { + while (true) + { + try + { + Socket socket = _server.accept(); + new ConnectionHandler(socket); + } + catch (IOException e) + { + fail("Error opening the socket in accept mode"); + } + } + } + }; + _socketAcceptor.start(); + System.out.println("*** Socket server started..."); + } + + class ConnectionHandler implements Runnable + { + private Socket _socket; + + public ConnectionHandler(Socket socket) + { + _socket = socket; + Thread t = new Thread(this); + t.start(); + } + + public void run() + { + System.out.println("*** Connection handler running..."); + List<String> buf = new ArrayList<String>(); + String line; + try + { + BufferedReader br = new BufferedReader(new InputStreamReader( + _socket.getInputStream())); + assertNotNull(br); + while ((line = br.readLine()) != null) + { + buf.add(line); + } + br.close(); + System.out.println("*** Received buffer: " + buf); + System.out.println("*** Latch countdown"); + _latch.countDown(); + synchronized (_recv) + { + _recv.add(buf); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + fail("Exception while reading from the socket"); + } + + } + + } + + public void testInfoPlugin() throws Exception + { + //Start the broker + super.setUp(); + if (!_latch.await(10, TimeUnit.SECONDS)) + { + fail("Timeout awaiting for the latch, upon startup"); + } + + validateResponses("STARTUP"); + + _recv.clear(); + _latch = new CountDownLatch(2); + + stopBroker(); + + if (!_latch.await(10, TimeUnit.SECONDS)) + { + fail("Timeout awaiting for the latch, upon shutdown"); + } + + validateResponses("SHUTDOWN"); + + } + + /** + * Check the responses from the server to ensure they contain the required messages. + * @param action String to match for the SHUTDOWN or STARTUP action. + */ + private void validateResponses(String action) + { + assertTrue("Received less than 2 messages", _recv.size() > 1); + + // Message 1 + assertTrue("Message does not contain Host: localhost:" + _port + "\n" + _recv.get(0), _recv.get(0).contains("Host: localhost:" + _port)); + assertTrue("Message does not contain: User-Agent: Axis2 " + "\n" + _recv.get(0), _recv.get(0).contains("User-Agent: Axis2")); + assertTrue("Message does not contain: SOAPAction: \"urn:submitinfo\"" + "\n" + _recv.get(0).get(4), _recv.get(0).get(4).startsWith("SOAPAction: \"urn:submitinfo")); + assertTrue("Message does not contain '" + action + "' in the soap envelope" + "\n" + _recv.get(0).get(7), _recv.get(0).get(7).contains(action)); + + // Message 2 + assertTrue("Message does not contain Host: localhost:" + _port + "\n" + _recv.get(1), _recv.get(1).contains("Host: localhost:" + _port)); + assertTrue("Message does not contain: User-Agent: Axis2 " + "\n" + _recv.get(1), _recv.get(1).contains("User-Agent: Axis2")); + assertTrue("Message does not contain: SOAPAction: \"urn:submitinfo\"" + "\n" + _recv.get(1).get(4), _recv.get(1).get(4).startsWith("SOAPAction: \"urn:submitinfo")); + assertTrue("Message does not contain '" + action + "' in the soap envelope" + "\n" + _recv.get(1).get(7), _recv.get(1).get(7).contains(action)); + } + +} diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/HttpPosterTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/HttpPosterTest.java new file mode 100644 index 0000000000..4f76fea8ef --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/HttpPosterTest.java @@ -0,0 +1,107 @@ +/* + * + * 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.info.test; + +import java.util.List; +import java.util.Properties; + +import org.apache.qpid.info.util.HttpPoster; +import org.mortbay.jetty.testing.ServletTester; + +import junit.framework.TestCase; + +/* + * This test verifies that the plugin posts correctly to a webserver + * We use an embedded jetty container to mimic the webserver + */ +public class HttpPosterTest extends TestCase +{ + + private ServletTester tester; + + private String baseURL; + + private final String contextPath = "/info"; + + /* + * This method generates a dummy HttpPoster with a dummy body containing a + * single line. The url we are posting to can be controlled by the parameter + * url + * + * @param url + */ + private HttpPoster getHttpPoster(String url) + { + StringBuffer sb = new StringBuffer("test=TEST"); + Properties props = new Properties(); + props.put("http.url", url); + return new HttpPoster(props, sb); + } + + /* + * (non-Javadoc) + * + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception + { + tester = new ServletTester(); + tester.setContextPath("/"); + tester.addServlet(InfoServlet.class, contextPath); + baseURL = tester.createSocketConnector(true); + tester.start(); + } + + /* + * (non-Javadoc) + * + * @see junit.framework.TestCase#tearDown() + */ + protected void tearDown() throws Exception + { + super.tearDown(); + tester.stop(); + } + + /* + * This test is posting a string to an embedded Jetty Servlet and captures + * the response message. If the servlet receives the message ok, it will + * print Ok. A failure test is following where we post to a non-existent URL + */ + public void testHttpPoster() throws Exception + { + // Test HttpPoster posts correctly to the servlet + HttpPoster hp = getHttpPoster(baseURL + contextPath); + assertNotNull(hp); + hp.run(); + List<String> response = hp.get_response(); + assertTrue(response.size() > 0); + assertEquals("OK <br>", response.get(0).toString()); + + // Failure Test + hp = getHttpPoster("http://localhost/nonexistent"); + hp.run(); + response = hp.get_response(); + assertTrue(response.size() == 0); + + } + +} diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServiceImplTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServiceImplTest.java new file mode 100644 index 0000000000..9f359582a5 --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServiceImplTest.java @@ -0,0 +1,63 @@ +/* + * + * 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.info.test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.apache.qpid.info.Info; +import org.apache.qpid.info.InfoServiceImpl; + +import junit.framework.TestCase; + +/* + * This test verifies the invoke() method for the info service making sure that the parameters are returned + */ +public class InfoServiceImplTest extends TestCase +{ + + InfoServiceImpl _isi = null; + + @SuppressWarnings("unchecked") + public void testInvoke() + { + _isi = new InfoServiceImpl(); + assertNotNull(_isi); + Info<? extends Map<String, String>> info = (Info<? extends Map<String, String>>) _isi + .invoke("START"); + assertNotNull(info); + Properties props = info.toProps(); + assertNotNull(props); + List<String> infoProps = Arrays.asList("java.class.path", + "java.vm.name", "java.class.version", "os.arch", "os.name", + "os.version", "sun.arch.data.model", "user.dir", "user.name", + "user.timezone"); + for (String tag : infoProps) + { + assertNotNull("Info.toProps() does not have the property: " + tag, + props.getProperty(tag)); + } + } + +} diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServlet.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServlet.java new file mode 100644 index 0000000000..6b12a2d80c --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServlet.java @@ -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.info.test; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.GenericServlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +/* + * This is a servlet used by the embedded Jetty to be able to receive http post + * from the info plugin + */ + +public class InfoServlet extends GenericServlet +{ + private static final long serialVersionUID = 1L; + + @Override + public void service(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + String line; + BufferedReader in = request.getReader(); + while ((line = in.readLine()) != null) + { + System.out.println(line); + } + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.println("OK <br>\n"); + System.out.println("ServletResponse: OK"); + } + +}
\ No newline at end of file diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoTest.java new file mode 100644 index 0000000000..bb4965ef1e --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoTest.java @@ -0,0 +1,112 @@ +/* + * + * 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.info.test; + +import java.util.HashMap; +import java.util.Properties; +import junit.framework.TestCase; +import org.apache.qpid.info.Info; + +/* + * This test verifies the toString(), toProps(), toXML() and toStringBuffer() methods of the Info object + * + */ +public class InfoTest extends TestCase +{ + private HashMap<String, String> _infoPayLoad = null; + + private Info<HashMap<String, String>> _info = null; + + protected void setUp() throws Exception + { + super.setUp(); + _infoPayLoad = new HashMap<String, String>(); + _infoPayLoad.put("test", "Test"); + _info = new Info<HashMap<String, String>>(_infoPayLoad); + } + + /* + * Test the conversion toString() of the Info object + */ + public void testToString() + { + assertNotNull("toString() returned null", _info.toString()); + assertEquals("toString() did not return the proper string", + "test=Test\n", _info.toString()); + } + + /* + * Test the conversion toProps() of the Info object + */ + public void testToProps() + { + Properties props = new Properties(); + props.put("test", "Test"); + assertNotNull("toProperties() returned null", _info.toProps()); + assertEquals("toProperties not returned the proper object", props, _info + .toProps()); + } + + /* + * Test the conversion toStringBuffer() of the Info object + */ + public void testToStringBuffer() + { + StringBuffer sb = new StringBuffer("test=Test\n"); + assertNotNull(_info.toStringBuffer()); + assertEquals(sb.toString(), _info.toStringBuffer().toString()); + } + + /* + * Test conversion toXML() of the info object + */ + public void testToXML() + { + String INDENT = " "; + StringBuffer sb = new StringBuffer(); + sb.append("<?xml version=\"1.0\"?>\n"); + sb.append("<qpidinfo>\n"); + sb.append("<test>\n"); + sb.append(INDENT + "Test\n"); + sb.append("</test>\n"); + sb.append("</qpidinfo>\n"); + assertEquals("toString() does not return the proper string", _info + .toXML().toString(), sb.toString()); + } + + /* + * Test the conversion toMap() of the Info object + */ + public void testToMap() + { + HashMap<String, String> thm = _info.toMap(); + assertFalse("toMap() returned empty map", thm.isEmpty()); + assertEquals("testToMap did not returned 1", 1, thm.size()); + assertTrue("toMap() returned a map not containing expected key: test", + thm.containsKey("test")); + assertTrue( + "toMap() returned a map not containing the value for key test: Test", + thm.containsValue("Test")); + + } + +} diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/IniFileReaderTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/IniFileReaderTest.java new file mode 100644 index 0000000000..77ecaa2176 --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/IniFileReaderTest.java @@ -0,0 +1,136 @@ +/* + * + * 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.info.test; + +import junit.framework.TestCase; +import org.apache.qpid.info.util.IniFileReader; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Map; +import java.util.Properties; + +/** + * Test the Loading of the ini file reader by first writing + * out a correct ini file. + */ +public class IniFileReaderTest extends TestCase +{ + + public void testLoad() + { + IniFileReader ifr = new IniFileReader(); + File iniFile = null; + try + { + iniFile = File.createTempFile("temp", "ini"); + iniFile.deleteOnExit(); + BufferedWriter writer = new BufferedWriter(new FileWriter(iniFile)); + writer.write("# Global Comment1\n"); + writer.write("globalprop1=globalval1\n"); + writer.write("globalprop2=globalval2\n"); + writer.write("\n"); + writer.write("[Section1] # Comment on Section\n"); + writer.write("key1=val1 # Comment on Value\n"); + writer.write("key2=val2\n"); + writer.write("\n"); + writer.write("#Section2 Comment\n"); + writer.write("[Section2]\n"); + writer.write("key3=val3\n"); + writer.write("key4=val4\n"); + writer.write("key5=val5\n"); + writer.write("\n"); + writer.write("[Section3]\n"); + writer.write("key6=val6\n"); + writer.write("key7=val7\n"); + writer.write("\n"); + writer.close(); + } + catch (IOException e) + { + e.printStackTrace(); + fail("Unable to create temporary File"); + } + ifr.load(iniFile.getAbsolutePath()); + Map<String, Properties> sections = ifr.getSections(); + assertNotNull("Sections not null", sections); + assertEquals("Have 4 sections", sections.keySet().size(), 4); + assertTrue("Get globalprop1", sections.get("").getProperty("globalprop1").equals("globalval1")); + assertTrue("Get globalprop2", sections.get("").getProperty("globalprop2").equals("globalval2")); + assertNotNull("Section1 not null", sections.get("Section1")); + assertEquals("Section1 has 2 properties", sections.get("Section1").size(), 2); + assertTrue("Section1 key1 has val1", sections.get("Section1").getProperty("key1").equals("val1")); + assertTrue("Section1 key2 has val2", sections.get("Section1").getProperty("key2").equals("val2")); + assertEquals("Section2 has 3 properties", sections.get("Section2").size(), 3); + assertTrue("Section2 key3 has val3", sections.get("Section2").getProperty("key3").equals("val3")); + assertTrue("Section2 key4 has val4", sections.get("Section2").getProperty("key4").equals("val4")); + assertTrue("Section2 key5 has val5", sections.get("Section2").getProperty("key5").equals("val5")); + assertEquals("Section3 has 2 properties", sections.get("Section3").size(), 2); + assertTrue("Section3 key6 has val6", sections.get("Section3").getProperty("key6").equals("val6")); + assertTrue("Section3 key7 has val7", sections.get("Section3").getProperty("key7").equals("val7")); + } + + /** + * Test to ensure that the loading of a file with an unclosed section header + * fails to parse. + * + * Section needs to be fully enclosed in square brackets '[<name>]' + */ + public void testIncompleteSection1Load() + { + IniFileReader ifr = new IniFileReader(); + File iniFile = null; + try + { + iniFile = File.createTempFile(getName(), "ini"); + iniFile.deleteOnExit(); + BufferedWriter writer = new BufferedWriter(new FileWriter(iniFile)); + writer.write("# Global Comment1\n"); + writer.write("globalprop1=globalval1\n"); + writer.write("globalprop2=globalval2\n"); + writer.write("\n"); + writer.write("[Section1\n"); // Note '[Section1' not complete + writer.write("key1=val1\n"); + writer.write("key2=val2\n"); + writer.write("\n"); + writer.close(); + } + catch (IOException e) + { + e.printStackTrace(); + fail("Unable to create temporary File"); + } + try + { + ifr.load(iniFile.getAbsolutePath()); + fail("File should fail to parse"); + } + catch (IllegalArgumentException iae) + { + assertEquals("Incorrect Exception", "Section1 is not closed", iae.getMessage()); + } + + } + +} diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SoapClientTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SoapClientTest.java new file mode 100644 index 0000000000..a3d993a39f --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SoapClientTest.java @@ -0,0 +1,208 @@ +/* + * + * 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.info.test; + +import junit.framework.TestCase; +import org.apache.qpid.info.util.SoapClient; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Properties; + +public class SoapClientTest extends TestCase +{ + + private int _port; + + private final String _hostName = "localhost"; + + private final String _urlPath = "/testSoap"; + + private ServerSocket _server = null; + + /* + * Generate a soap client from a custom URL, hostname, port and soap context + * path to be derived + */ + private SoapClient getSoapClient() + { + Properties destprops = new Properties(); + destprops.setProperty("soap.hostname", _hostName); + destprops.setProperty("soap.port", _port + ""); + destprops.setProperty("soap.urlpath", _urlPath); + destprops.setProperty("soap.envelope", "<ip>@IP</ip>"); + destprops.setProperty("soap.action", "send"); + HashMap<String, String> soapmap = new HashMap<String, String>(); + soapmap.put("IP", "127.0.0.1"); + return new SoapClient(soapmap, destprops); + } + + /* + * A connection handler class that verifies the correct message is received + * + */ + class ConnectionHandler implements Runnable + { + private Socket socket; + + public ConnectionHandler(Socket socket) + { + this.socket = socket; + Thread t = new Thread(this); + t.start(); + } + + public void run() + { + String line; + final List<String> response = new ArrayList<String>(); + try + { + BufferedReader br = new BufferedReader(new InputStreamReader( + socket.getInputStream())); + assertNotNull(br); + while ((line = br.readLine()) != null) + { + response.add(line); + } + br.close(); + } + catch (Exception ex) + { + ex.printStackTrace(); + fail("Exception while reading from the socket"); + } + assertTrue(response.contains("<ip>127.0.0.1</ip>")); + assertTrue(response.contains("SOAPAction: \"urn:send\"")); + assertTrue(response + .contains("Content-Type: text/xml; charset=\"utf-8\"")); + assertTrue(response.contains("Host: localhost" + _port)); + assertTrue(response.contains("User-Agent: Axis2")); + } + + } + + /* + * Test that the SOAP client sends the expected data to the socket We mock a + * simple SOAP envelope: <ip>127.0.0.1</ip> + */ + public void testSoapClient() throws Exception + { + // + try + { + _server = new ServerSocket(0); + _port = _server.getLocalPort(); + assertTrue("Server is not yet bound to a port", _port != -1); + assertNotNull(_server); + } + catch (Exception ex) + { + ex.printStackTrace(); + fail("Unable to start the socket server"); + } + + Thread _socketAcceptor = new Thread() + { + public void run() + { + try + { + Socket socket = _server.accept(); + new ConnectionHandler(socket); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + }; + _socketAcceptor.start(); + // Sleep for 1 second to allow the ServerSocket readiness + Thread.sleep(1000); + SoapClient sc = getSoapClient(); + assertNotNull(sc); + sc.sendSOAPMessage(); + + _socketAcceptor.join(2000); + + assertFalse("Socket Acceptor not stopped.", _socketAcceptor.isAlive()); + } + + /** + * Test SoapClient correctly clears previously set values + */ + public void testSoapClientXMLData() + { + SoapClient sc = getSoapClient(); + + StringBuffer initial = new StringBuffer("Initial Value"); + + sc.setXMLData(initial); + + assertEquals("getXMLData is not set with initial value", + initial.toString(), sc.getXMLData().toString()); + + + StringBuffer sb = new StringBuffer("<?xml version=\"1.0\"?><ip=@IP><port=@PORT>"); + sc.setXMLData(sb); + assertEquals(sc.getXMLData().length(), sb.length()); + assertEquals("getXMLData does not return the same StringBuffer set by setXMLData", + sb.toString(), sc.getXMLData().toString()); + } + + /** + * Test that variable replacement is performed on the soap.envelope. + * Create dummy soap message and validate that the variable have been replaced. + */ + public void testReplaceVariablesMap() + { + Properties props = new Properties(); + // Add dummy values as required to create a soap message + props.setProperty("soap.hostname", _hostName); + props.setProperty("soap.port", "0"); + props.setProperty("soap.urlpath", _urlPath); + props.setProperty("soap.action", "send"); + + /// The envelope is what we care about + props.setProperty("soap.envelope", "<addr>@IP:@PORT</addr>"); + HashMap<String, String> soapmap = new HashMap<String, String>(); + + /// Variables that should be replaced. + final String ip = "127.0.0.1"; + soapmap.put("IP", ip); + final String port = "8080"; + soapmap.put("PORT", port); + + SoapClient sc = new SoapClient(soapmap, props); + assertNotNull("SoapClient is null", sc); + + assertTrue("Replace variables did not work as expected", ("<addr>" + ip + ":" + port + "</addr>").equals(sc.getXMLData().toString())); + } + +} diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SystemInfoTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SystemInfoTest.java new file mode 100644 index 0000000000..6cb8e3a90a --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SystemInfoTest.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.info.test; + +import junit.framework.TestCase; +import org.apache.qpid.info.SystemInfo; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** Test the SystemInfo component */ +public class SystemInfoTest extends TestCase +{ + + /** + * Ensure the list of required properties are returned by the + * SystemInfo.getInfo call + */ + public void testGetInfo() + { + Map<String, String> sysInfoMap = SystemInfo.getInfo(); + assertNotNull("SystemInfo.getInfo() returned null", sysInfoMap); + List<String> sysInfoProps = Arrays.asList( + "java.class.path", + "java.vm.name", "java.class.version", "os.arch", "os.name", + "os.version", "sun.arch.data.model", "user.dir", "user.name", + "user.timezone", "hostname", "ip", "CPUCores", "Maximum_Memory", + "Free_Memory"); + + for (String tag : sysInfoProps) + { + assertNotNull("Map does not contain the tag: " + tag, sysInfoMap.get(tag)); + } + } + +}
\ No newline at end of file diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/XMLWriterTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/XMLWriterTest.java new file mode 100644 index 0000000000..f352226361 --- /dev/null +++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/XMLWriterTest.java @@ -0,0 +1,132 @@ +/* + * + * 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.info.test; + +import junit.framework.TestCase; +import org.apache.qpid.info.util.XMLWriter; + +import java.util.HashMap; + +/* + * This test verifies the XML writer custom class operations + */ + +public class XMLWriterTest extends TestCase +{ + + private XMLWriter xw = null; + + /** Test constructor arg is returned via getXML() */ + public void testXMLWriter() + { + StringBuffer input = new StringBuffer("Test"); + xw = new XMLWriter(input); + assertNotNull("XMLWriter could not instantiate", xw); + assertEquals("XMLWriter.getXML() failed", input, xw.getXML()); + } + + /** Test header generation */ + public void testWriteXMLHeader() + { + xw = new XMLWriter(new StringBuffer()); + assertNotNull(xw); + xw.writeXMLHeader(); + assertEquals("XMLWriter.writeXMLHeader(...) failed", "<?xml version=\"1.0\"?>\n", xw.getXML().toString()); + } + + /** Test tag created and written correctly */ + public void testWriteTag() + { + String INDENT = " "; + xw = new XMLWriter(new StringBuffer()); + assertNotNull("XMLWriter could not instantiate", xw); + xw.writeTag("test", new HashMap<String, String>(), "TEST"); + assertEquals("XMLWriter.writeTag(...) failed", "<test>\n" + INDENT + "TEST\n" + "</test>\n", xw.getXML() + .toString()); + } + + /** Test tag created and written correctly */ + public void testWriteTagWithNullAttribute() + { + String INDENT = " "; + xw = new XMLWriter(new StringBuffer()); + assertNotNull("XMLWriter could not instantiate", xw); + xw.writeTag("test", null, "TEST"); + assertEquals("XMLWriter.writeTag(...) failed", "<test>\n" + INDENT + "TEST\n" + "</test>\n", xw.getXML() + .toString()); + } + + /** Test tag created and written correctly with attribute */ + public void testWriteTagWithAttribute() + { + String INDENT = " "; + xw = new XMLWriter(new StringBuffer()); + assertNotNull("XMLWriter could not instantiate", xw); + HashMap<String, String> attr = new HashMap<String, String>(); + attr.put("id", "1"); + + xw.writeTag("test", attr, "TEST"); + assertEquals("XMLWriter.writeTag(...) failed", "<test id=\"1\">\n" + INDENT + "TEST\n" + "</test>\n", xw.getXML() + .toString()); + } + + /** Test open tag with an empty attribute map. Just creates an open tag */ + public void testWriteOpenTag() + { + xw = new XMLWriter(new StringBuffer()); + assertNotNull(xw); + HashMap<String, String> attr = new HashMap<String, String>(); + xw.writeOpenTag("test", attr); + assertEquals("XMLWriter.writeOpenTag(...) failed", "<test>\n", xw.getXML().toString()); + } + + /** Test open tag with a null attribute map. Just creates an open tag */ + public void testNullAtrributeOnTag() + { + xw = new XMLWriter(new StringBuffer()); + assertNotNull(xw); + xw.writeOpenTag("test", null); + assertEquals("XMLWriter.writeOpenTag(...) failed", "<test>\n", xw.getXML().toString()); + } + + /** Test that setting an attribute value on the tag is correctly outputted. */ + public void testAtrributeOnTag() + { + xw = new XMLWriter(new StringBuffer()); + assertNotNull(xw); + HashMap<String, String> attr = new HashMap<String, String>(); + + attr.put("id", "1"); + xw.writeOpenTag("test1", attr); + assertEquals("XMLWriter.writeOpenTag(...) failed", "<test1 id=\"1\">\n", xw.getXML().toString()); + } + + /** Test Close Tag is correctly written */ + public void testWriteCloseTag() + { + xw = new XMLWriter(new StringBuffer()); + assertNotNull(xw); + xw.writeCloseTag("test"); + assertEquals("</test>\n", xw.getXML().toString()); + } + +} diff --git a/qpid/java/broker-plugins/experimental/shutdown/MANIFEST.MF b/qpid/java/broker-plugins/experimental/shutdown/MANIFEST.MF new file mode 100644 index 0000000000..49e90c6aad --- /dev/null +++ b/qpid/java/broker-plugins/experimental/shutdown/MANIFEST.MF @@ -0,0 +1,15 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Experimental Shutdown +Bundle-Description: Experimental Qpid Broker Shutdown Plugin +Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt +Bundle-DocURL: http://qpid.apache.org/ +Bundle-SymbolicName: broker-plugins-experimental-shutdown;singleton:=true +Bundle-Version: 1.0.0 +Bundle-Activator: org.apache.qpid.shutdown.Activator +Import-Package: javax.management;resolution:=optional, + org.apache.log4j, + org.osgi.framework +Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Bundle-ActivationPolicy: lazy + diff --git a/qpid/java/broker-plugins/experimental/shutdown/build.xml b/qpid/java/broker-plugins/experimental/shutdown/build.xml new file mode 100644 index 0000000000..ec4fce374e --- /dev/null +++ b/qpid/java/broker-plugins/experimental/shutdown/build.xml @@ -0,0 +1,32 @@ +<!-- + - + - 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="AMQ Broker Shutdown Plugin" default="build"> + + <property name="module.depends" value="common broker broker-plugins"/> + <property name="module.test.depends" value="test broker/test management/common client systests"/> + <property name="module.manifest" value="MANIFEST.MF"/> + <property name="module.plugin" value="true"/> + + <import file="../../../module.xml"/> + + <target name="bundle" depends="bundle-tasks"/> + +</project> diff --git a/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Activator.java b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Activator.java new file mode 100644 index 0000000000..ad5e7707b6 --- /dev/null +++ b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Activator.java @@ -0,0 +1,71 @@ +/* + * 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.shutdown; + +import java.lang.management.ManagementFactory; + +import javax.management.InstanceNotFoundException; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.apache.log4j.Logger; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +public class Activator implements BundleActivator +{ + private static final Logger _logger = Logger.getLogger(Activator.class); + + private static final String SHUTDOWN_MBEAN_NAME = "org.apache.qpid:type=ShutdownMBean"; + + /** @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) */ + public void start(BundleContext ctx) throws Exception { + Shutdown shutdown = new Shutdown(); + if (ctx != null) + { + ctx.registerService(ShutdownMBean.class.getName(), shutdown, null); + } + + // MBean registration + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + ObjectName name = new ObjectName(SHUTDOWN_MBEAN_NAME); + mbs.registerMBean(shutdown, name); + + _logger.info("Shutdown plugin MBean registered"); + } + + /** @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) */ + public void stop(BundleContext ctx) throws Exception + { + // Unregister MBean + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + ObjectName name = new ObjectName(SHUTDOWN_MBEAN_NAME); + try + { + mbs.unregisterMBean(name); + } + catch (InstanceNotFoundException e) + { + //ignore + } + + _logger.info("Shutdown plugin MBean unregistered"); + } +} diff --git a/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Shutdown.java b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Shutdown.java new file mode 100644 index 0000000000..9a6f85fe9c --- /dev/null +++ b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Shutdown.java @@ -0,0 +1,104 @@ +/* + * 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.shutdown; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.apache.log4j.Logger; + +/** + * Implementation of the JMX broker shutdown plugin. + */ +public class Shutdown implements ShutdownMBean +{ + private static final Logger _logger = Logger.getLogger(Shutdown.class); + + private static final String FORMAT = "yyyyy/MM/dd hh:mm:ss"; + private static final int THREAD_COUNT = 1; + private static final ScheduledExecutorService EXECUTOR = new ScheduledThreadPoolExecutor(THREAD_COUNT); + + private final Runnable _shutdown = new SystemExiter(); + + /** @see ShutdownMBean#shutdown() */ + public void shutdown() + { + _logger.info("Shutting down at user's request"); + shutdownBroker(0); + } + + /** @see ShutdownMBean#shutdown(long) */ + public void shutdown(long delay) + { + _logger.info("Scheduled broker shutdown after " + delay + "ms"); + shutdownBroker(delay); + } + + /** @see ShutdownMBean#shutdownAt(String) */ + public void shutdownAt(String when) + { + Date date; + DateFormat df = new SimpleDateFormat(FORMAT); + try + { + date = df.parse(when); + } + catch (ParseException e) + { + _logger.error("Invalid date \"" + when + "\": expecting " + FORMAT, e); + return; + } + _logger.info("Scheduled broker shutdown at " + when); + long now = System.currentTimeMillis(); + long time = date.getTime(); + if (time > now) + { + shutdownBroker(time - now); + } + else + { + shutdownBroker(0); + } + } + + /** + * Submits the {@link SystemExiter} job to shutdown the broker. + */ + private void shutdownBroker(long delay) + { + EXECUTOR.schedule(_shutdown, delay, TimeUnit.MILLISECONDS); + } + + /** + * Shutting down the system in another thread to avoid JMX exceptions being thrown. + */ + class SystemExiter implements Runnable + { + public void run() + { + System.exit(0); + } + } +} diff --git a/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/ShutdownMBean.java b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/ShutdownMBean.java new file mode 100644 index 0000000000..6294f869e9 --- /dev/null +++ b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/ShutdownMBean.java @@ -0,0 +1,47 @@ +/* + * 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.shutdown; + +/** + * Shutdown plugin JMX MBean interface. + * + * Shuts the Qpid broker down via JMX. + */ +public interface ShutdownMBean +{ + /** + * Broker will be shut down immediately. + */ + public void shutdown(); + + /** + * Broker will be shutdown after the specified delay + * + * @param delay the number of ms to wait + */ + public void shutdown(long delay); + + /** + * Broker will be shutdown at the specified date and time. + * + * @param when the date and time to shutdown + */ + public void shutdownAt(String when); +} diff --git a/qpid/java/broker-plugins/experimental/shutdown/src/main/java/shutdown.bnd b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/shutdown.bnd new file mode 100755 index 0000000000..f49578ba8c --- /dev/null +++ b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/shutdown.bnd @@ -0,0 +1,25 @@ +# +# 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. +# + +ver: 0.11.0 + +Bundle-SymbolicName: qpid-shutdown-plugin +Bundle-Version: ${ver} +Export-Package: *;version=${ver} +Bundle-RequiredExecutionEnvironment: J2SE-1.5 diff --git a/qpid/java/broker-plugins/extras/MANIFEST.MF b/qpid/java/broker-plugins/extras/MANIFEST.MF new file mode 100644 index 0000000000..f4ef6e8178 --- /dev/null +++ b/qpid/java/broker-plugins/extras/MANIFEST.MF @@ -0,0 +1,21 @@ +Bundle-ManifestVersion: 2 +Bundle-Name: Qpid Broker-Plugins Extras +Bundle-SymbolicName: broker-plugins-extras +Bundle-Description: Extra exchange types plugin for Qpid. +Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt +Bundle-DocURL: http://www.apache.org/ +Bundle-Activator: org.apache.qpid.extras.Activator +Private-Package: org.apache.qpid.extras, + org.apache.qpid.extras.exchanges.diagnostic, + org.apache.qpid.extras.exchanges.example +Import-Package: org.apache.qpid, + org.apache.qpid.framing, + org.apache.qpid.junit.extensions.util, + org.apache.qpid.protocol, + org.apache.qpid.server.exchange, + org.apache.qpid.server.management, + org.apache.qpid.server.queue, + org.apache.qpid.server.virtualhost, + javax.management;version=1.0.0, + javax.management.openmbean;version=1.0.0, + org.osgi.framework;version=1.3 diff --git a/qpid/java/broker-plugins/extras/build.xml b/qpid/java/broker-plugins/extras/build.xml new file mode 100644 index 0000000000..7c1d0be49f --- /dev/null +++ b/qpid/java/broker-plugins/extras/build.xml @@ -0,0 +1,31 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one +nn - or more contributor license agreements. See the NOTICE file + -n 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 Extras" default="build"> + <property name="module.depends" value="common client management/common broker broker-plugins"/> + <property name="module.test.depends" value="test broker/test common/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> diff --git a/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/Activator.java b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/Activator.java new file mode 100644 index 0000000000..ca6c05a435 --- /dev/null +++ b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/Activator.java @@ -0,0 +1,48 @@ +/* + * + * 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.extras; + +import org.apache.qpid.extras.exchanges.diagnostic.DiagnosticExchangeType; +import org.apache.qpid.extras.exchanges.example.TestExchangeType; +import org.apache.qpid.server.exchange.ExchangeType; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +/** + * + * @author aidan + * + * Dummy class, used by PluginTest + */ + +public class Activator implements BundleActivator +{ + + public void start(BundleContext ctx) throws Exception + { + ctx.registerService(ExchangeType.class.getName(), new TestExchangeType(), null); + ctx.registerService(ExchangeType.class.getName(), new DiagnosticExchangeType(), null); + } + + public void stop(BundleContext ctx) throws Exception + { + } +} diff --git a/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchange.java b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchange.java new file mode 100644 index 0000000000..5d2c0dd5b2 --- /dev/null +++ b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchange.java @@ -0,0 +1,229 @@ +/* + * + * 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.extras.exchanges.diagnostic; + +import java.util.ArrayList; +import java.util.Map; + +import javax.management.JMException; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.TabularData; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.management.common.mbeans.annotations.MBeanConstructor; +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.exchange.AbstractExchange; +import org.apache.qpid.server.exchange.AbstractExchangeMBean; +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * This is a special diagnostic exchange type which doesn't actually do anything + * with messages. When it receives a message, it writes information about the + * current memory usage to the "memory" property of the message and places it on the + * diagnosticqueue for retrieval + */ +public class DiagnosticExchange extends AbstractExchange +{ + private static final Logger _logger = Logger.getLogger(DiagnosticExchange.class); + + public static final AMQShortString DIAGNOSTIC_EXCHANGE_CLASS = new AMQShortString("x-diagnostic"); + public static final AMQShortString DIAGNOSTIC_EXCHANGE_NAME = new AMQShortString("diagnostic"); + + /** The logger */ + //private static final Logger _logger = Logger.getLogger(DiagnosticExchange.class); + + /** + * MBean class implementing the management interfaces. + */ + @MBeanDescription("Management Bean for Diagnostic Exchange") + private final class DiagnosticExchangeMBean extends AbstractExchangeMBean<DiagnosticExchange> + { + /** + * Usual constructor. + * + * @throws JMException + */ + @MBeanConstructor("Creates an MBean for AMQ Diagnostic exchange") + public DiagnosticExchangeMBean() throws JMException + { + super(DiagnosticExchange.this); + + init(); + } + + /** + * Returns nothing, there can be no tabular data for this... + * + * @throws OpenDataException + * @returns null + * TODO or can there? Could this actually return all the information in one easy to read table? + */ + @Override + public TabularData bindings() throws OpenDataException + { + return null; + } + + /** + * This exchange type doesn't support queues, so this method does + * nothing. + * + * @param queueName the queue you'll fail to create + * @param binding the binding you'll fail to create + * @throws JMException an exception that will never be thrown + */ + @Override + public void createNewBinding(String queueName, String binding) throws JMException + { + // No Op + } + + /** + * This exchange type doesn't support queues. + * + * @see #createNewBinding(String, String) + */ + @Override + public void removeBinding(String queueName, String binding) throws JMException + { + // No Op + } + } + + + public static final ExchangeType<DiagnosticExchange> TYPE = new ExchangeType<DiagnosticExchange>() + { + + public AMQShortString getName() + { + return DIAGNOSTIC_EXCHANGE_CLASS; + } + + public Class<DiagnosticExchange> getExchangeClass() + { + return DiagnosticExchange.class; + } + + public DiagnosticExchange newInstance(VirtualHost host, + AMQShortString name, + boolean durable, + int ticket, + boolean autoDelete) throws AMQException + { + DiagnosticExchange exch = new DiagnosticExchange(); + exch.initialise(host,name,durable,ticket,autoDelete); + return exch; + } + + public AMQShortString getDefaultExchangeName() + { + return DIAGNOSTIC_EXCHANGE_NAME ; + } + }; + + public DiagnosticExchange() + { + super(TYPE); + } + + /** + * Creates a new MBean instance + * + * @return the newly created MBean + * @throws AMQException + * if something goes wrong + */ + protected AbstractExchangeMBean createMBean() throws JMException + { + return new DiagnosticExchange.DiagnosticExchangeMBean(); + } + + public Logger getLogger() + { + return _logger; + } + + public void registerQueue(String routingKey, AMQQueue queue, Map<String, Object> args) throws AMQException + { + // No op + } + + + public boolean isBound(AMQShortString routingKey, AMQQueue queue) + { + return false; + } + + public boolean isBound(AMQShortString routingKey) + { + return false; + } + + public boolean isBound(AMQQueue queue) + { + return false; + } + + public boolean hasBindings() + { + return false; + } + + public ArrayList<AMQQueue> doRoute(InboundMessage payload) + { + //TODO shouldn't modify messages... perhaps put a new message on the queue? + /* + Long value = new Long(SizeOf.getUsedMemory()); + AMQShortString key = new AMQShortString("memory"); + FieldTable headers = ((BasicContentHeaderProperties)payload.getMessageHeader().properties).getHeaders(); + headers.put(key, value); + ((BasicContentHeaderProperties)payload.getMessageHeader().properties).setHeaders(headers); + */ + AMQQueue q = getQueueRegistry().getQueue(new AMQShortString("diagnosticqueue")); + ArrayList<AMQQueue> queues = new ArrayList<AMQQueue>(); + queues.add(q); + return queues; + } + + + public boolean isBound(AMQShortString routingKey, FieldTable arguments, + AMQQueue queue) { + // TODO Auto-generated method stub + return false; + } + + protected void onBind(final Binding binding) + { + // No op + } + + protected void onUnbind(final Binding binding) + { + // No op + } +} diff --git a/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchangeType.java b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchangeType.java new file mode 100644 index 0000000000..b4d0d1aa0d --- /dev/null +++ b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchangeType.java @@ -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.extras.exchanges.diagnostic; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * Exchange type class for getting hold of the exchange. + */ +public final class DiagnosticExchangeType implements ExchangeType<DiagnosticExchange> +{ + + public AMQShortString getName() + { + return DiagnosticExchange.DIAGNOSTIC_EXCHANGE_CLASS; + } + + public Class<DiagnosticExchange> getExchangeClass() + { + return DiagnosticExchange.class; + } + + public DiagnosticExchange newInstance(VirtualHost host, AMQShortString name, boolean durable, int ticket, boolean autoDelete) + throws AMQException + { + DiagnosticExchange exch = new DiagnosticExchange(); + exch.initialise(host, name, durable, ticket, autoDelete); + return exch; + } + + public AMQShortString getDefaultExchangeName() + { + return DiagnosticExchange.DIAGNOSTIC_EXCHANGE_NAME; + } +} diff --git a/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchange.java b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchange.java new file mode 100644 index 0000000000..def0b3f91a --- /dev/null +++ b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchange.java @@ -0,0 +1,257 @@ +package org.apache.qpid.extras.exchanges.example; +/* + * + * 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. + * + */ + + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.ConfiguredObject; +import org.apache.qpid.server.configuration.ExchangeConfigType; +import org.apache.qpid.server.configuration.VirtualHostConfig; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeReferrer; +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class TestExchange implements Exchange +{ + + public void close() throws AMQException + { + } + + + + public void addBindingListener(final BindingListener listener) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void removeBindingListener(final BindingListener listener) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public AMQShortString getNameShortString() + { + return null; + } + + public AMQShortString getTypeShortString() + { + return null; + } + + public boolean hasBindings() + { + return false; + } + + public boolean isBound(String bindingKey, AMQQueue queue) + { + return false; + } + + public boolean isBound(String bindingKey) + { + return false; + } + + public void addCloseTask(final Task task) + { + + } + + public void removeCloseTask(final Task task) + { + + } + + public Exchange getAlternateExchange() + { + return null; + } + + public Map<String, Object> getArguments() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getBindingCount() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getBindingCountHigh() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getMsgReceives() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getMsgRoutes() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getByteReceives() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getByteRoutes() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getCreateTime() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setAlternateExchange(Exchange exchange) + { + + } + + public void removeReference(ExchangeReferrer exchange) + { + + } + + public void addReference(ExchangeReferrer exchange) + { + + } + + public boolean hasReferrers() + { + return false; + } + + public void addBinding(final Binding binding) + { + + } + + public void removeBinding(final Binding binding) + { + + } + + public Collection<Binding> getBindings() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void initialise(VirtualHost host, AMQShortString name, boolean durable, boolean autoDelete) + throws AMQException + { + } + + public VirtualHostConfig getVirtualHost() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public String getName() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public ExchangeType getType() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isAutoDelete() + { + return false; + } + + public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue) + { + return false; + } + + public boolean isBound(AMQShortString routingKey, AMQQueue queue) + { + return false; + } + + public boolean isBound(AMQShortString routingKey) + { + return false; + } + + public boolean isBound(AMQQueue queue) + { + return false; + } + + public UUID getId() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public ExchangeConfigType getConfigType() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public ConfiguredObject getParent() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isDurable() + { + return false; + } + + public ArrayList<? extends BaseQueue> route(InboundMessage message) + { + return new ArrayList<AMQQueue>(); + } + + public int getTicket() + { + return 0; + } + + public void initialise(VirtualHost arg0, AMQShortString arg1, boolean arg2, int arg3, boolean arg4) + throws AMQException + { + } +} diff --git a/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchangeType.java b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchangeType.java new file mode 100644 index 0000000000..db02ca13ea --- /dev/null +++ b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchangeType.java @@ -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.extras.exchanges.example; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class TestExchangeType implements ExchangeType +{ + + public Class getExchangeClass() + { + return TestExchange.class; + } + + public AMQShortString getName() + { + return null; + } + + public Exchange newInstance(VirtualHost host, AMQShortString name, boolean durable, + int token, boolean autoDelete) + throws AMQException + { + TestExchange ex = new TestExchange(); + ex.initialise(host, name, durable, token, autoDelete); + return ex; + } + + public AMQShortString getDefaultExchangeName() + { + return new AMQShortString("test.exchange"); + } + +} diff --git a/qpid/java/broker-plugins/extras/src/test/java/org/apache/qpid/server/plugins/ExtrasTest.java b/qpid/java/broker-plugins/extras/src/test/java/org/apache/qpid/server/plugins/ExtrasTest.java new file mode 100644 index 0000000000..57b6e19b5d --- /dev/null +++ b/qpid/java/broker-plugins/extras/src/test/java/org/apache/qpid/server/plugins/ExtrasTest.java @@ -0,0 +1,74 @@ +/* + * 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.plugins; + +import junit.framework.TestCase; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.util.TestApplicationRegistry; + +import java.util.Map; + +public class ExtrasTest extends TestCase +{ + private static final String TEST_EXCHANGE_CLASS = "org.apache.qpid.extras.exchanges.example.TestExchangeType"; + + private static final String PLUGIN_DIRECTORY = System.getProperty("example.plugin.target"); + private static final String CACHE_DIRECTORY = System.getProperty("example.cache.target"); + + IApplicationRegistry _registry; + + @Override + public void setUp() throws Exception + { + PropertiesConfiguration properties = new PropertiesConfiguration(); + properties.addProperty("plugin-directory", PLUGIN_DIRECTORY); + properties.addProperty("cache-directory", CACHE_DIRECTORY); + ServerConfiguration config = new ServerConfiguration(properties); + + // This Test requires an application Registry + ApplicationRegistry.initialise(new TestApplicationRegistry(config)); + _registry = ApplicationRegistry.getInstance(); + } + + @Override + public void tearDown() throws Exception + { + ApplicationRegistry.remove(); + } + + public void testLoadExchanges() throws Exception + { + PluginManager manager = _registry.getPluginManager(); + Map<String, ExchangeType<?>> exchanges = manager.getExchanges(); + assertNotNull("No exchanges found in " + PLUGIN_DIRECTORY, exchanges); + assertEquals("Wrong number of exchanges found in " + PLUGIN_DIRECTORY, 2, exchanges.size()); + assertNotNull("Wrong exchange found in " + PLUGIN_DIRECTORY, exchanges.get(TEST_EXCHANGE_CLASS)); + } + + public void testNoExchanges() throws Exception + { + PluginManager manager = new PluginManager("/path/to/nowhere", "/tmp"); + Map<String, ExchangeType<?>> exchanges = manager.getExchanges(); + assertTrue("Exchanges found", exchanges.isEmpty()); + } +} diff --git a/qpid/java/broker-plugins/firewall/MANIFEST.MF b/qpid/java/broker-plugins/firewall/MANIFEST.MF new file mode 100644 index 0000000000..6ceea119da --- /dev/null +++ b/qpid/java/broker-plugins/firewall/MANIFEST.MF @@ -0,0 +1,36 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Qpid Broker-Plugins Firewall +Bundle-SymbolicName: broker-plugins-firewall +Bundle-Description: Firewall 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.FirewallActivator +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-ClassPath: . +Bundle-ActivationPolicy: lazy +Import-Package: org.apache.qpid, + 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;uses:="org.osgi.framework" diff --git a/qpid/java/broker-plugins/firewall/build.xml b/qpid/java/broker-plugins/firewall/build.xml new file mode 100644 index 0000000000..576435de7f --- /dev/null +++ b/qpid/java/broker-plugins/firewall/build.xml @@ -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 Firewall" default="build"> + <property name="module.depends" value="common broker broker-plugins" /> + <property name="module.test.depends" value="test broker/test common/test management/common" /> + + <property name="module.manifest" value="MANIFEST.MF" /> + <property name="module.plugin" value="true" /> + + <import file="../../module.xml" /> + + <target name="bundle" depends="bundle-tasks" /> +</project> diff --git a/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallException.java b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallException.java new file mode 100644 index 0000000000..a9e3fdc242 --- /dev/null +++ b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallException.java @@ -0,0 +1,46 @@ +/* + * + * 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; + +/** + * Firewall plugin exception. + */ +public class FirewallException extends Exception +{ + /** serialVersionUID */ + private static final long serialVersionUID = 4526157149690917805L; + + public FirewallException() { + super(); + } + + public FirewallException(String message) { + super(message); + } + + public FirewallException(String message, Throwable cause) { + super(message, cause); + } + + public FirewallException(Throwable cause) { + super(cause); + } +}
\ No newline at end of file diff --git a/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallRule.java b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallRule.java new file mode 100644 index 0000000000..f257b58867 --- /dev/null +++ b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallRule.java @@ -0,0 +1,137 @@ +/* + * + * 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.net.InetAddress; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.regex.Pattern; + +import org.apache.qpid.server.security.Result; +import org.apache.qpid.util.NetMatcher; + +public class FirewallRule +{ + public static final String ALLOW = "ALLOW"; + public static final String DENY = "DENY"; + + private static final long DNS_TIMEOUT = 30000; + private static final ExecutorService DNS_LOOKUP = Executors.newCachedThreadPool(); + + private Result _access; + private NetMatcher _network; + private Pattern[] _hostnamePatterns; + + public FirewallRule(String access, List networks, List hostnames) + { + _access = (access.equalsIgnoreCase(ALLOW)) ? Result.ALLOWED : Result.DENIED; + + if (networks != null && networks.size() > 0) + { + String[] networkStrings = objListToStringArray(networks); + _network = new NetMatcher(networkStrings); + } + + if (hostnames != null && hostnames.size() > 0) + { + int i = 0; + _hostnamePatterns = new Pattern[hostnames.size()]; + for (String hostname : objListToStringArray(hostnames)) + { + _hostnamePatterns[i++] = Pattern.compile(hostname); + } + } + } + + private String[] objListToStringArray(List objList) + { + String[] networkStrings = new String[objList.size()]; + int i = 0; + for (Object network : objList) + { + networkStrings[i++] = (String) network; + } + return networkStrings; + } + + public boolean match(InetAddress remote) throws FirewallException + { + if (_hostnamePatterns != null) + { + String hostname = getHostname(remote); + if (hostname == null) + { + throw new FirewallException("DNS lookup failed"); + } + for (Pattern pattern : _hostnamePatterns) + { + if (pattern.matcher(hostname).matches()) + { + return true; + } + } + return false; + } + else + { + return _network.matchInetNetwork(remote); + } + } + + /** + * @param remote the InetAddress to look up + * @return the hostname, null if not found, takes longer than 30s to find or otherwise fails + */ + private String getHostname(final InetAddress remote) throws FirewallException + { + FutureTask<String> lookup = new FutureTask<String>(new Callable<String>() + { + public String call() + { + return remote.getCanonicalHostName(); + } + }); + DNS_LOOKUP.execute(lookup); + + try + { + return lookup.get(DNS_TIMEOUT, TimeUnit.MILLISECONDS); + } + catch (Exception e) + { + return null; + } + finally + { + lookup.cancel(true); + } + } + + public Result getAccess() + { + return _access; + } +}
\ No newline at end of file diff --git a/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/Firewall.java b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/Firewall.java new file mode 100644 index 0000000000..a6ea9d261e --- /dev/null +++ b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/Firewall.java @@ -0,0 +1,136 @@ +/* + * + * 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.net.InetAddress; +import java.net.InetSocketAddress; + +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.security.AbstractPlugin; +import org.apache.qpid.server.security.Result; +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.FirewallException; +import org.apache.qpid.server.security.access.config.FirewallRule; + +public class Firewall extends AbstractPlugin +{ + public static final SecurityPluginFactory<Firewall> FACTORY = new SecurityPluginFactory<Firewall>() + { + public Firewall newInstance(ConfigurationPlugin config) throws ConfigurationException + { + FirewallConfiguration configuration = config.getConfiguration(FirewallConfiguration.class.getName()); + + // If there is no configuration for this plugin then don't load it. + if (configuration == null) + { + return null; + } + + Firewall plugin = new Firewall(); + plugin.configure(configuration); + return plugin; + } + + public Class<Firewall> getPluginClass() + { + return Firewall.class; + } + + public String getPluginName() + { + return Firewall.class.getName(); + } + }; + + private Result _default = Result.ABSTAIN; + private FirewallRule[] _rules; + + public Result getDefault() + { + return _default; + } + + public Result authorise(Operation operation, ObjectType objectType, ObjectProperties properties) + { + return Result.ABSTAIN; // We only deal with access requests + } + + public Result access(ObjectType objectType, Object instance) + { + if (objectType != ObjectType.VIRTUALHOST) + { + return Result.ABSTAIN; // We are only interested in access to virtualhosts + } + + if (!(instance instanceof InetSocketAddress)) + { + return Result.ABSTAIN; // We need an internet address + } + + InetAddress address = ((InetSocketAddress) instance).getAddress(); + + try + { + for (FirewallRule rule : _rules) + { + boolean match = rule.match(address); + if (match) + { + return rule.getAccess(); + } + } + return getDefault(); + } + catch (FirewallException fe) + { + return Result.DENIED; + } + } + + + public void configure(ConfigurationPlugin config) + { + super.configure(config); + FirewallConfiguration firewallConfiguration = (FirewallConfiguration) _config; + + // Get default action + _default = firewallConfiguration.getDefaultAction(); + + Configuration finalConfig = firewallConfiguration.getConfiguration(); + + // all rules must have an access attribute + int numRules = finalConfig.getList("rule[@access]").size(); + _rules = new FirewallRule[numRules]; + for (int i = 0; i < numRules; i++) + { + FirewallRule rule = new FirewallRule(finalConfig.getString("rule(" + i + ")[@access]"), + finalConfig.getList("rule(" + i + ")[@network]"), + finalConfig.getList("rule(" + i + ")[@hostname]")); + _rules[i] = rule; + } + + } +} diff --git a/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallActivator.java b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallActivator.java new file mode 100644 index 0000000000..c20bba8d2c --- /dev/null +++ b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallActivator.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 Firewall}. + */ +public class FirewallActivator extends SecurityPluginActivator +{ + public SecurityPluginFactory getFactory() + { + return Firewall.FACTORY; + } + + public ConfigurationPluginFactory getConfigurationFactory() + { + return FirewallConfiguration.FACTORY; + } +} diff --git a/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallConfiguration.java b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallConfiguration.java new file mode 100644 index 0000000000..b10656d622 --- /dev/null +++ b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallConfiguration.java @@ -0,0 +1,102 @@ +/* + * + * 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.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.apache.qpid.server.security.Result; +import org.apache.qpid.server.security.access.config.FirewallRule; + +public class FirewallConfiguration extends ConfigurationPlugin +{ + CompositeConfiguration _finalConfig; + + public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory() + { + public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException + { + ConfigurationPlugin instance = new FirewallConfiguration(); + instance.setConfiguration(path, config); + return instance; + } + + public List<String> getParentPaths() + { + return Arrays.asList("security.firewall", "virtualhosts.virtualhost.security.firewall"); + } + }; + + public String[] getElementsProcessed() + { + return new String[] { "" }; + } + + public Configuration getConfiguration() + { + return _finalConfig; + } + + public Result getDefaultAction() + { + String defaultAction = _configuration.getString("[@default-action]"); + if (defaultAction == null) + { + return Result.ABSTAIN; + } + else if (defaultAction.equalsIgnoreCase(FirewallRule.ALLOW)) + { + return Result.ALLOWED; + } + else + { + return Result.DENIED; + } + } + + + + @Override + public void validateConfiguration() throws ConfigurationException + { + // Valid Configuration either has xml links to new files + _finalConfig = new CompositeConfiguration(_configuration); + List subFiles = _configuration.getList("xml[@fileName]"); + for (Object subFile : subFiles) + { + _finalConfig.addConfiguration(new XMLConfiguration((String) subFile)); + } + + // all rules must have an access attribute or a default value + if (_finalConfig.getList("rule[@access]").size() == 0 && + _configuration.getString("[@default-action]") == null) + { + throw new ConfigurationException("No rules or default-action found in firewall configuration."); + } + } + +} diff --git a/qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallConfigurationTest.java b/qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallConfigurationTest.java new file mode 100644 index 0000000000..ebede414f4 --- /dev/null +++ b/qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallConfigurationTest.java @@ -0,0 +1,355 @@ +/* + * + * 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; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.net.InetSocketAddress; + +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +public class FirewallConfigurationTest extends InternalBrokerBaseCase +{ + public void testFirewallConfiguration() throws Exception + { + // Write out config + File mainFile = File.createTempFile(getClass().getName(), null); + mainFile.deleteOnExit(); + writeConfigFile(mainFile, false); + + // Load config + ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile); + try + { + ApplicationRegistry.initialise(reg, 1); + + // Test config + assertFalse(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535))); + assertTrue(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.1.2.3", 65535))); + } + finally + { + ApplicationRegistry.remove(1); + } + } + + public void testCombinedConfigurationFirewall() throws Exception + { + // Write out config + File mainFile = File.createTempFile(getClass().getName(), null); + File fileA = File.createTempFile(getClass().getName(), null); + File fileB = File.createTempFile(getClass().getName(), null); + + mainFile.deleteOnExit(); + fileA.deleteOnExit(); + fileB.deleteOnExit(); + + FileWriter out = new FileWriter(mainFile); + out.write("<configuration><system/>"); + out.write("<xml fileName=\"" + fileA.getAbsolutePath() + "\"/>"); + out.write("</configuration>"); + out.close(); + + out = new FileWriter(fileA); + out.write("<broker>\n"); + out.write("\t<plugin-directory>${QPID_HOME}/lib/plugins</plugin-directory>\n"); + out.write("\t<cache-directory>${QPID_WORK}/cache</cache-directory>\n"); + out.write("\t<management><enabled>false</enabled></management>\n"); + out.write("\t<security>\n"); + out.write("\t\t<principal-databases>\n"); + out.write("\t\t\t<principal-database>\n"); + out.write("\t\t\t\t<name>passwordfile</name>\n"); + out.write("\t\t\t\t<class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>\n"); + out.write("\t\t\t\t<attributes>\n"); + out.write("\t\t\t\t\t<attribute>\n"); + out.write("\t\t\t\t\t\t<name>passwordFile</name>\n"); + out.write("\t\t\t\t\t\t<value>/dev/null</value>\n"); + out.write("\t\t\t\t\t</attribute>\n"); + out.write("\t\t\t\t</attributes>\n"); + out.write("\t\t\t</principal-database>\n"); + out.write("\t\t</principal-databases>\n"); + out.write("\t\t<jmx>\n"); + out.write("\t\t\t<principal-database>passwordfile</principal-database>\n"); + out.write("\t\t</jmx>\n"); + out.write("\t\t<firewall>\n"); + out.write("\t\t\t<xml fileName=\"" + fileB.getAbsolutePath() + "\"/>"); + out.write("\t\t</firewall>\n"); + out.write("\t</security>\n"); + out.write("\t<virtualhosts>\n"); + out.write("\t\t<virtualhost>\n"); + out.write("\t\t\t<name>test</name>\n"); + out.write("\t\t</virtualhost>\n"); + out.write("\t</virtualhosts>\n"); + out.write("</broker>\n"); + out.close(); + + out = new FileWriter(fileB); + out.write("<firewall>\n"); + out.write("\t<rule access=\"deny\" network=\"127.0.0.1\"/>"); + out.write("</firewall>\n"); + out.close(); + + // Load config + ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile); + try + { + ApplicationRegistry.initialise(reg, 1); + + // Test config + assertFalse(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535))); + } + finally + { + ApplicationRegistry.remove(1); + } + } + + public void testConfigurationFirewallReload() throws Exception + { + // Write out config + File mainFile = File.createTempFile(getClass().getName(), null); + + mainFile.deleteOnExit(); + writeConfigFile(mainFile, false); + + // Load config + ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile); + try + { + ApplicationRegistry.initialise(reg, 1); + + // Test config + assertFalse(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535))); + + // Switch to deny the connection + writeConfigFile(mainFile, true); + + reg.getConfiguration().reparseConfigFileSecuritySections(); + + assertTrue(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535))); + } + finally + { + ApplicationRegistry.remove(1); + } + } + + public void testCombinedConfigurationFirewallReload() throws Exception + { + // Write out config + File mainFile = File.createTempFile(getClass().getName(), null); + File fileA = File.createTempFile(getClass().getName(), null); + File fileB = File.createTempFile(getClass().getName(), null); + + mainFile.deleteOnExit(); + fileA.deleteOnExit(); + fileB.deleteOnExit(); + + FileWriter out = new FileWriter(mainFile); + out.write("<configuration><system/>"); + out.write("<xml fileName=\"" + fileA.getAbsolutePath() + "\"/>"); + out.write("</configuration>"); + out.close(); + + out = new FileWriter(fileA); + out.write("<broker>\n"); + out.write("\t<plugin-directory>${QPID_HOME}/lib/plugins</plugin-directory>\n"); + out.write("\t<management><enabled>false</enabled></management>\n"); + out.write("\t<security>\n"); + out.write("\t\t<principal-databases>\n"); + out.write("\t\t\t<principal-database>\n"); + out.write("\t\t\t\t<name>passwordfile</name>\n"); + out.write("\t\t\t\t<class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>\n"); + out.write("\t\t\t\t<attributes>\n"); + out.write("\t\t\t\t\t<attribute>\n"); + out.write("\t\t\t\t\t\t<name>passwordFile</name>\n"); + out.write("\t\t\t\t\t\t<value>/dev/null</value>\n"); + out.write("\t\t\t\t\t</attribute>\n"); + out.write("\t\t\t\t</attributes>\n"); + out.write("\t\t\t</principal-database>\n"); + out.write("\t\t</principal-databases>\n"); + out.write("\t\t<jmx>\n"); + out.write("\t\t\t<principal-database>passwordfile</principal-database>\n"); + out.write("\t\t</jmx>\n"); + out.write("\t\t<firewall>\n"); + out.write("\t\t\t<xml fileName=\"" + fileB.getAbsolutePath() + "\"/>"); + out.write("\t\t</firewall>\n"); + out.write("\t</security>\n"); + out.write("\t<virtualhosts>\n"); + out.write("\t\t<virtualhost>\n"); + out.write("\t\t\t<name>test</name>\n"); + out.write("\t\t</virtualhost>\n"); + out.write("\t</virtualhosts>\n"); + out.write("</broker>\n"); + out.close(); + + out = new FileWriter(fileB); + out.write("<firewall>\n"); + out.write("\t<rule access=\"deny\" network=\"127.0.0.1\"/>"); + out.write("</firewall>\n"); + out.close(); + + // Load config + ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile); + try + { + ApplicationRegistry.initialise(reg, 1); + + // Test config + assertFalse(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535))); + + RandomAccessFile fileBRandom = new RandomAccessFile(fileB, "rw"); + fileBRandom.setLength(0); + fileBRandom.seek(0); + fileBRandom.close(); + + out = new FileWriter(fileB); + out.write("<firewall>\n"); + out.write("\t<rule access=\"allow\" network=\"127.0.0.1\"/>"); + out.write("</firewall>\n"); + out.close(); + + reg.getConfiguration().reparseConfigFileSecuritySections(); + + assertTrue(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535))); + + fileBRandom = new RandomAccessFile(fileB, "rw"); + fileBRandom.setLength(0); + fileBRandom.seek(0); + fileBRandom.close(); + + out = new FileWriter(fileB); + out.write("<firewall>\n"); + out.write("\t<rule access=\"deny\" network=\"127.0.0.1\"/>"); + out.write("</firewall>\n"); + out.close(); + + reg.getConfiguration().reparseConfigFileSecuritySections(); + + assertFalse(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535))); + } + finally + { + ApplicationRegistry.remove(1); + } + } + + private void writeFirewallVhostsFile(File vhostsFile, boolean allow) throws IOException + { + FileWriter out = new FileWriter(vhostsFile); + String ipAddr = "127.0.0.1"; // FIXME: get this from InetAddress.getLocalHost().getAddress() ? + out.write("<virtualhosts><virtualhost>"); + out.write("<name>test</name>"); + out.write("<test>"); + out.write("<security><firewall>"); + out.write("<rule access=\""+((allow) ? "allow" : "deny")+"\" network=\""+ipAddr +"\"/>"); + out.write("</firewall></security>"); + out.write("</test>"); + out.write("</virtualhost></virtualhosts>"); + out.close(); + } + + private void writeConfigFile(File mainFile, boolean allow) throws IOException { + writeConfigFile(mainFile, allow, true, null, "test"); + } + + /* + XMLConfiguration config = new XMLConfiguration(mainFile); + PluginManager pluginManager = new MockPluginManager(""); + SecurityManager manager = new SecurityManager(config, pluginManager, Firewall.FACTORY); + + */ + private void writeConfigFile(File mainFile, boolean allow, boolean includeVhosts, File vhostsFile, String name) throws IOException { + FileWriter out = new FileWriter(mainFile); + out.write("<broker>\n"); + out.write("\t<plugin-directory>${QPID_HOME}/lib/plugins</plugin-directory>\n"); + out.write("\t<management><enabled>false</enabled></management>\n"); + out.write("\t<security>\n"); + out.write("\t\t<principal-databases>\n"); + out.write("\t\t\t<principal-database>\n"); + out.write("\t\t\t\t<name>passwordfile</name>\n"); + out.write("\t\t\t\t<class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>\n"); + out.write("\t\t\t\t<attributes>\n"); + out.write("\t\t\t\t\t<attribute>\n"); + out.write("\t\t\t\t\t\t<name>passwordFile</name>\n"); + out.write("\t\t\t\t\t\t<value>/dev/null</value>\n"); + out.write("\t\t\t\t\t</attribute>\n"); + out.write("\t\t\t\t</attributes>\n"); + out.write("\t\t\t</principal-database>\n"); + out.write("\t\t</principal-databases>\n"); + out.write("\t\t<jmx>\n"); + out.write("\t\t\t<principal-database>passwordfile</principal-database>\n"); + out.write("\t\t</jmx>\n"); + out.write("\t\t<firewall>\n"); + out.write("\t\t\t<rule access=\""+ ((allow) ? "allow" : "deny") +"\" network=\"127.0.0.1\"/>"); + out.write("\t\t</firewall>\n"); + out.write("\t</security>\n"); + if (includeVhosts) + { + out.write("\t<virtualhosts>\n"); + out.write("\t\t<default>test</default>\n"); + out.write("\t\t<virtualhost>\n"); + out.write(String.format("\t\t\t<name>%s</name>\n", name)); + out.write("\t\t</virtualhost>\n"); + out.write("\t</virtualhosts>\n"); + } + if (vhostsFile != null) + { + out.write("\t<virtualhosts>"+vhostsFile.getAbsolutePath()+"</virtualhosts>\n"); + } + out.write("</broker>\n"); + out.close(); + } + + /** + * Test that configuration loads correctly when virtual hosts are specified in an external + * configuration file only. + * <p> + * Test for QPID-2360 + */ + public void testExternalFirewallVirtualhostXMLFile() throws Exception + { + // Write out config + File mainFile = File.createTempFile(getClass().getName(), "config"); + mainFile.deleteOnExit(); + File vhostsFile = File.createTempFile(getClass().getName(), "vhosts"); + vhostsFile.deleteOnExit(); + writeConfigFile(mainFile, false, false, vhostsFile, null); + writeFirewallVhostsFile(vhostsFile, false); + + // Load config + ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile); + ApplicationRegistry.initialise(reg, 1); + + // Test config + VirtualHostRegistry virtualHostRegistry = reg.getVirtualHostRegistry(); + VirtualHost virtualHost = virtualHostRegistry.getVirtualHost("test"); + + assertEquals("Incorrect virtualhost count", 1, virtualHostRegistry.getVirtualHosts().size()); + assertEquals("Incorrect virtualhost name", "test", virtualHost.getName()); + } +} diff --git a/qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallPluginTest.java b/qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallPluginTest.java new file mode 100644 index 0000000000..2b04962c89 --- /dev/null +++ b/qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallPluginTest.java @@ -0,0 +1,282 @@ +/* + * 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; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.server.security.Result; +import org.apache.qpid.server.security.access.plugins.Firewall; +import org.apache.qpid.server.security.access.plugins.FirewallConfiguration; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +public class FirewallPluginTest extends InternalBrokerBaseCase +{ + public class RuleInfo + { + private String _access; + private String _network; + private String _hostname; + + public void setAccess(String _access) + { + this._access = _access; + } + + public String getAccess() + { + return _access; + } + + public void setNetwork(String _network) + { + this._network = _network; + } + + public String getNetwork() + { + return _network; + } + + public void setHostname(String _hostname) + { + this._hostname = _hostname; + } + + public String getHostname() + { + return _hostname; + } + } + + // IP address + private SocketAddress _address; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _address = new InetSocketAddress("127.0.0.1", 65535); + } + + private Firewall initialisePlugin(String defaultAction, RuleInfo[] rules) throws IOException, ConfigurationException + { + // Create sample config file + File confFile = File.createTempFile(getClass().getSimpleName()+"conffile", null); + confFile.deleteOnExit(); + BufferedWriter buf = new BufferedWriter(new FileWriter(confFile)); + buf.write("<firewall default-action=\""+defaultAction+"\">\n"); + if (rules != null) + { + for (RuleInfo rule : rules) + { + buf.write("<rule"); + buf.write(" access=\""+rule.getAccess()+"\""); + if (rule.getHostname() != null) + { + buf.write(" hostname=\""+rule.getHostname()+"\""); + } + if (rule.getNetwork() != null) + { + buf.write(" network=\""+rule.getNetwork()+"\""); + } + buf.write("/>\n"); + } + } + buf.write("</firewall>"); + buf.close(); + + // Configure plugin + FirewallConfiguration config = new FirewallConfiguration(); + config.setConfiguration("", new XMLConfiguration(confFile)); + Firewall plugin = new Firewall(); + plugin.configure(config); + return plugin; + } + + private Firewall initialisePlugin(String string) throws ConfigurationException, IOException + { + return initialisePlugin(string, null); + } + + public void testDefaultAction() throws Exception + { + // Test simple deny + Firewall plugin = initialisePlugin("deny"); + assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address)); + + // Test simple allow + plugin = initialisePlugin("allow"); + assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address)); + } + + + public void testSingleIPRule() throws Exception + { + RuleInfo rule = new RuleInfo(); + rule.setAccess("allow"); + rule.setNetwork("192.168.23.23"); + + Firewall plugin = initialisePlugin("deny", new RuleInfo[]{rule}); + + assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address)); + + // Set IP so that we're connected from the right address + _address = new InetSocketAddress("192.168.23.23", 65535); + assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address)); + } + + public void testSingleNetworkRule() throws Exception + { + RuleInfo rule = new RuleInfo(); + rule.setAccess("allow"); + rule.setNetwork("192.168.23.0/24"); + + Firewall plugin = initialisePlugin("deny", new RuleInfo[]{rule}); + + assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address)); + + // Set IP so that we're connected from the right address + _address = new InetSocketAddress("192.168.23.23", 65535); + assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address)); + } + + public void testSingleHostRule() throws Exception + { + RuleInfo rule = new RuleInfo(); + rule.setAccess("allow"); + rule.setHostname(new InetSocketAddress("127.0.0.1", 5672).getHostName()); + + Firewall plugin = initialisePlugin("deny", new RuleInfo[]{rule}); + + // Set IP so that we're connected from the right address + _address = new InetSocketAddress("127.0.0.1", 65535); + assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address)); + } + + public void testSingleHostWilcardRule() throws Exception + { + RuleInfo rule = new RuleInfo(); + rule.setAccess("allow"); + String hostname = new InetSocketAddress("127.0.0.1", 0).getHostName(); + rule.setHostname(".*"+hostname.subSequence(hostname.length() - 1, hostname.length())+"*"); + Firewall plugin = initialisePlugin("deny", new RuleInfo[]{rule}); + + // Set IP so that we're connected from the right address + _address = new InetSocketAddress("127.0.0.1", 65535); + assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address)); + } + + public void testSeveralFirstAllowsAccess() throws Exception + { + RuleInfo firstRule = new RuleInfo(); + firstRule.setAccess("allow"); + firstRule.setNetwork("192.168.23.23"); + + RuleInfo secondRule = new RuleInfo(); + secondRule.setAccess("deny"); + secondRule.setNetwork("192.168.42.42"); + + RuleInfo thirdRule = new RuleInfo(); + thirdRule.setAccess("deny"); + thirdRule.setHostname("localhost"); + + Firewall plugin = initialisePlugin("deny", new RuleInfo[]{firstRule, secondRule, thirdRule}); + + assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address)); + + // Set IP so that we're connected from the right address + _address = new InetSocketAddress("192.168.23.23", 65535); + assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address)); + } + + public void testSeveralLastAllowsAccess() throws Exception + { + RuleInfo firstRule = new RuleInfo(); + firstRule.setAccess("deny"); + firstRule.setHostname("localhost"); + + RuleInfo secondRule = new RuleInfo(); + secondRule.setAccess("deny"); + secondRule.setNetwork("192.168.42.42"); + + RuleInfo thirdRule = new RuleInfo(); + thirdRule.setAccess("allow"); + thirdRule.setNetwork("192.168.23.23"); + + Firewall plugin = initialisePlugin("deny", new RuleInfo[]{firstRule, secondRule, thirdRule}); + + assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address)); + + // Set IP so that we're connected from the right address + _address = new InetSocketAddress("192.168.23.23", 65535); + assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address)); + } + + public void testNetmask() throws Exception + { + RuleInfo firstRule = new RuleInfo(); + firstRule.setAccess("allow"); + firstRule.setNetwork("192.168.23.0/24"); + Firewall plugin = initialisePlugin("deny", new RuleInfo[]{firstRule}); + + assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address)); + + // Set IP so that we're connected from the right address + _address = new InetSocketAddress("192.168.23.23", 65535); + assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address)); + } + + public void testCommaSeperatedNetmask() throws Exception + { + RuleInfo firstRule = new RuleInfo(); + firstRule.setAccess("allow"); + firstRule.setNetwork("10.1.1.1/8, 192.168.23.0/24"); + Firewall plugin = initialisePlugin("deny", new RuleInfo[]{firstRule}); + + assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address)); + + // Set IP so that we're connected from the right address + _address = new InetSocketAddress("192.168.23.23", 65535); + assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address)); + } + + public void testCommaSeperatedHostnames() throws Exception + { + RuleInfo firstRule = new RuleInfo(); + firstRule.setAccess("allow"); + firstRule.setHostname("foo, bar, "+new InetSocketAddress("127.0.0.1", 5672).getHostName()); + Firewall plugin = initialisePlugin("deny", new RuleInfo[]{firstRule}); + + // Set IP so that we're connected from the right address + _address = new InetSocketAddress("10.0.0.1", 65535); + assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address)); + + // Set IP so that we're connected from the right address + _address = new InetSocketAddress("127.0.0.1", 65535); + assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address)); + } +} diff --git a/qpid/java/broker-plugins/simple-xml/MANIFEST.MF b/qpid/java/broker-plugins/simple-xml/MANIFEST.MF new file mode 100644 index 0000000000..04fe7518df --- /dev/null +++ b/qpid/java/broker-plugins/simple-xml/MANIFEST.MF @@ -0,0 +1,36 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Qpid Broker-Plugins Simple XML +Bundle-SymbolicName: broker-plugins-simple-xml +Bundle-Description: Simple XML ACL 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.SimpleXMLActivator +Bundle-RequiredExecutionEnvironment: JavaSE-1.5 +Bundle-ClassPath: . +Bundle-ActivationPolicy: lazy +Import-Package: org.apache.qpid, + 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 diff --git a/qpid/java/broker-plugins/simple-xml/build.xml b/qpid/java/broker-plugins/simple-xml/build.xml new file mode 100644 index 0000000000..d3cd451648 --- /dev/null +++ b/qpid/java/broker-plugins/simple-xml/build.xml @@ -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 Simple XML" default="build"> + <property name="module.depends" value="common broker broker-plugins" /> + <property name="module.test.depends" value="test broker/test common/test management/common systests" /> + + <property name="module.manifest" value="MANIFEST.MF" /> + <property name="module.plugin" value="true" /> + + <import file="../../module.xml" /> + + <target name="bundle" depends="bundle-tasks" /> +</project> diff --git a/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/config/PrincipalPermissions.java b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/config/PrincipalPermissions.java new file mode 100755 index 0000000000..d9fc292f03 --- /dev/null +++ b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/config/PrincipalPermissions.java @@ -0,0 +1,687 @@ +/* + * + * 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.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.security.Result; + +@SuppressWarnings("unchecked") +public class PrincipalPermissions +{ + public enum Permission + { + CONSUME, + PUBLISH, + CREATEQUEUE, + CREATEEXCHANGE, + ACCESS, + BIND, + UNBIND, + DELETE, + PURGE + } + + private static final Logger _logger = Logger.getLogger(PrincipalPermissions.class); + + private static final Object CONSUME_QUEUES_KEY = new Object(); + private static final Object CONSUME_TEMPORARY_KEY = new Object(); + private static final Object CONSUME_OWN_QUEUES_ONLY_KEY = new Object(); + + private static final Object CREATE_QUEUES_KEY = new Object(); + private static final Object CREATE_EXCHANGES_KEY = new Object(); + + + private static final Object CREATE_QUEUE_TEMPORARY_KEY = new Object(); + private static final Object CREATE_QUEUE_QUEUES_KEY = new Object(); + private static final Object CREATE_QUEUE_EXCHANGES_KEY = new Object(); + + private static final Object CREATE_QUEUE_EXCHANGES_ROUTINGKEYS_KEY = new Object(); + + private static final int PUBLISH_EXCHANGES_KEY = 0; + + private Map _permissions; + private boolean _fullVHostAccess = false; + + private String _user; + + + public PrincipalPermissions(String user) + { + _user = user; + _permissions = new ConcurrentHashMap(); + } + + /** + * + * @param permission the type of permission to check + * + * @param parameters vararg depending on what permission was passed in + * ACCESS: none + * BIND: none + * CONSUME: AMQShortString queueName, Boolean temporary, Boolean ownQueueOnly + * CREATEQUEUE: Boolean temporary, AMQShortString queueName, AMQShortString exchangeName, AMQShortString routingKey + * CREATEEXCHANGE: AMQShortString exchangeName, AMQShortString Class + * DELETE: none + * PUBLISH: Exchange exchange, AMQShortString routingKey + * PURGE: none + * UNBIND: none + */ + public void grant(Permission permission, Object... parameters) + { + switch (permission) + { + case ACCESS:// Parameters : None + grantAccess(permission); + break; + case CONSUME: // Parameters : AMQShortString queueName, Boolean Temporary, Boolean ownQueueOnly + grantConsume(permission, parameters); + break; + case CREATEQUEUE: // Parameters : Boolean temporary, AMQShortString queueName + // , AMQShortString exchangeName , AMQShortString routingKey + grantCreateQueue(permission, parameters); + break; + case CREATEEXCHANGE: + // Parameters AMQShortString exchangeName , AMQShortString Class + grantCreateExchange(permission, parameters); + break; + case PUBLISH: // Parameters : Exchange exchange, AMQShortString routingKey + grantPublish(permission, parameters); + break; + /* The other cases just fall through to no-op */ + case DELETE: + case BIND: // All the details are currently included in the create setup. + case PURGE: + case UNBIND: + break; + } + + } + + private void grantAccess(Permission permission) + { + _fullVHostAccess = true; + } + + private void grantPublish(Permission permission, Object... parameters) { + Map publishRights = (Map) _permissions.get(permission); + + if (publishRights == null) + { + publishRights = new ConcurrentHashMap(); + _permissions.put(permission, publishRights); + } + + if (parameters == null || parameters.length == 0) + { + //If we have no parameters then allow publish to all destinations + // this is signified by having a null value for publish_exchanges + } + else + { + Map publish_exchanges = (Map) publishRights.get(PUBLISH_EXCHANGES_KEY); + + if (publish_exchanges == null) + { + publish_exchanges = new ConcurrentHashMap(); + publishRights.put(PUBLISH_EXCHANGES_KEY, publish_exchanges); + } + + + HashSet routingKeys = (HashSet) publish_exchanges.get(parameters[0]); + + // Check to see if we have a routing key + if (parameters.length == 2) + { + if (routingKeys == null) + { + routingKeys = new HashSet<AMQShortString>(); + } + //Add routing key to permitted publish destinations + routingKeys.add(parameters[1]); + } + + // Add the updated routingkey list or null if all values allowed + publish_exchanges.put(parameters[0], routingKeys); + } + } + + private void grantCreateExchange(Permission permission, Object... parameters) { + Map rights = (Map) _permissions.get(permission); + if (rights == null) + { + rights = new ConcurrentHashMap(); + _permissions.put(permission, rights); + } + + Map create_exchanges = (Map) rights.get(CREATE_EXCHANGES_KEY); + if (create_exchanges == null) + { + create_exchanges = new ConcurrentHashMap(); + rights.put(CREATE_EXCHANGES_KEY, create_exchanges); + } + + //Should perhaps error if parameters[0] is null; + AMQShortString name = parameters.length > 0 ? (AMQShortString) parameters[0] : null; + AMQShortString className = parameters.length > 1 ? (AMQShortString) parameters[1] : new AMQShortString("direct"); + + //Store the exchangeName / class mapping if the mapping is null + rights.put(name, className); + } + + private void grantCreateQueue(Permission permission, Object... parameters) + { + Map createRights = (Map) _permissions.get(permission); + + if (createRights == null) + { + createRights = new ConcurrentHashMap(); + _permissions.put(permission, createRights); + } + + //The existence of the empty map mean permission to all. + if (parameters.length == 0) + { + return; + } + + // Get the queues map + Map create_queues = (Map) createRights.get(CREATE_QUEUES_KEY); + + //Initialiase the queue permissions if not already done + if (create_queues == null) + { + create_queues = new ConcurrentHashMap(); + //initialise temp queue permission to false and overwrite below if true + create_queues.put(CREATE_QUEUE_TEMPORARY_KEY, false); + createRights.put(CREATE_QUEUES_KEY, create_queues); + } + + //Create empty list of queues + Map create_queues_queues = (Map) create_queues.get(CREATE_QUEUE_QUEUES_KEY); + + if (create_queues_queues == null) + { + create_queues_queues = new ConcurrentHashMap(); + create_queues.put(CREATE_QUEUE_QUEUES_KEY, create_queues_queues); + } + + // If we are initialising and granting CREATE rights to all temporary queues, then that's all we do + Boolean temporary = false; + if (parameters.length == 1) + { + temporary = (Boolean) parameters[0]; + create_queues.put(CREATE_QUEUE_TEMPORARY_KEY, temporary); + return; + } + + //From here we can be permissioning a variety of things, with varying parameters + AMQShortString queueName = parameters.length > 1 ? (AMQShortString) parameters[1] : null; + AMQShortString exchangeName = parameters.length > 2 ? (AMQShortString) parameters[2] : null; + //Set the routingkey to the specified value or the queueName if present + AMQShortString routingKey = (parameters.length > 3 && null != parameters[3]) ? (AMQShortString) parameters[3] : queueName; + // if we have a queueName then we need to store any associated exchange / rk bindings + if (queueName != null) + { + Map queue = (Map) create_queues_queues.get(queueName); + if (queue == null) + { + queue = new ConcurrentHashMap(); + create_queues_queues.put(queueName, queue); + } + + if (exchangeName != null) + { + queue.put(exchangeName, routingKey); + } + + //If no exchange is specified then the presence of the queueName in the map says any exchange is ok + } + + // Store the exchange that we are being granted rights to. This will be used as part of binding + + //Lookup the list of exchanges + Map create_queues_exchanges = (Map) create_queues.get(CREATE_QUEUE_EXCHANGES_KEY); + + if (create_queues_exchanges == null) + { + create_queues_exchanges = new ConcurrentHashMap(); + create_queues.put(CREATE_QUEUE_EXCHANGES_KEY, create_queues_exchanges); + } + + //if we have an exchange + if (exchangeName != null) + { + //Retrieve the list of permitted exchanges. + Map exchanges = (Map) create_queues_exchanges.get(exchangeName); + + if (exchanges == null) + { + exchanges = new ConcurrentHashMap(); + create_queues_exchanges.put(exchangeName, exchanges); + } + + //Store the binding details of queue/rk for this exchange. + if (queueName != null) + { + //Retrieve the list of permitted routingKeys. + Map rKeys = (Map) exchanges.get(exchangeName); + + if (rKeys == null) + { + rKeys = new ConcurrentHashMap(); + exchanges.put(CREATE_QUEUE_EXCHANGES_ROUTINGKEYS_KEY, rKeys); + } + + rKeys.put(queueName, routingKey); + } + } + } + + /** + * Grant consume permissions + */ + private void grantConsume(Permission permission, Object... parameters) + { + Map consumeRights = (Map) _permissions.get(permission); + + if (consumeRights == null) + { + consumeRights = new ConcurrentHashMap(); + _permissions.put(permission, consumeRights); + + //initialise own and temporary rights to false to be overwritten below if set + consumeRights.put(CONSUME_TEMPORARY_KEY, false); + consumeRights.put(CONSUME_OWN_QUEUES_ONLY_KEY, false); + } + + + //if we only have one param then we're permissioning temporary queues and topics + if (parameters.length == 1) + { + Boolean temporary = (Boolean) parameters[0]; + + if (temporary) + { + consumeRights.put(CONSUME_TEMPORARY_KEY, true); + } + } + + //if we have 2 parameters - should be a contract for this, but for now we'll handle it as is + if (parameters.length == 2) + { + AMQShortString queueName = (AMQShortString) parameters[0]; + Boolean ownQueueOnly = (Boolean) parameters[1]; + + if (ownQueueOnly) + { + consumeRights.put(CONSUME_OWN_QUEUES_ONLY_KEY, true); + } + + LinkedList queues = (LinkedList) consumeRights.get(CONSUME_QUEUES_KEY); + if (queues == null) + { + queues = new LinkedList(); + consumeRights.put(CONSUME_QUEUES_KEY, queues); + } + + if (queueName != null) + { + queues.add(queueName); + } + } + } + + /** + * + * @param permission the type of permission to check + * + * @param parameters vararg depending on what permission was passed in + * ACCESS: none + * BIND: QueueBindBody bindmethod, Exchange exchange, AMQQueue queue, AMQShortString routingKey + * CONSUME: AMQQueue queue + * CREATEQUEUE: Boolean autodelete, AMQShortString name + * CREATEEXCHANGE: AMQShortString exchangeName + * DELETE: none + * PUBLISH: Exchange exchange, AMQShortString routingKey + * PURGE: none + * UNBIND: none + */ + public Result authorise(Permission permission, String... parameters) + { + + switch (permission) + { + case ACCESS://No Parameters + return Result.ALLOWED; // The existence of this user-specific PP infers some level of access is authorised + case BIND: // Parameters : QueueBindMethod , exhangeName , queueName, routingKey + return authoriseBind(parameters); + case CREATEQUEUE:// Parameters : autoDelete, queueName + return authoriseCreateQueue(permission, parameters); + case CREATEEXCHANGE: //Parameters: exchangeName + return authoriseCreateExchange(permission, parameters); + case CONSUME: // Parameters : queueName, autoDelete, owner + return authoriseConsume(permission, parameters); + case PUBLISH: // Parameters : exchangeName, routingKey + return authorisePublish(permission, parameters); + /* Fall through */ + case DELETE: + case PURGE: + case UNBIND: + default: + if(_fullVHostAccess) + { + //user has been granted full access to the vhost + return Result.ALLOWED; + } + else + { + //SimpleXML ACL does not implement these permissions and should abstain + return Result.ABSTAIN; + } + } + + } + + private Result authoriseConsume(Permission permission, String... parameters) + { + if(_fullVHostAccess) + { + //user has been granted full access to the vhost + return Result.ALLOWED; + } + + if (parameters.length == 3) + { + AMQShortString queueName = new AMQShortString(parameters[0]); + Boolean autoDelete = Boolean.valueOf(parameters[1]); + AMQShortString owner = new AMQShortString(parameters[2]); + Map queuePermissions = (Map) _permissions.get(permission); + + _logger.error("auth consume on " + StringUtils.join(parameters, ", ")); + + if (queuePermissions == null) + { + //we have a problem - we've never granted this type of permission ..... + return Result.DENIED; + } + + List queues = (List) queuePermissions.get(CONSUME_QUEUES_KEY); + + Boolean temporaryQueues = (Boolean) queuePermissions.get(CONSUME_TEMPORARY_KEY); + Boolean ownQueuesOnly = (Boolean) queuePermissions.get(CONSUME_OWN_QUEUES_ONLY_KEY); + + + // If user is allowed to consume from temporary queues and this is a temp queue then allow it. + if (temporaryQueues && autoDelete) + { + // This will allow consumption from any temporary queue including ones not owned by this user. + // Of course the exclusivity will not be broken. + { + + // if not limited to ownQueuesOnly then ok else check queue Owner. + return (!ownQueuesOnly || owner.equals(_user)) ? Result.ALLOWED : Result.DENIED; + } + } + //if this is a temporary queue and the user does not have permissions for temporary queues then deny + else if (!temporaryQueues && autoDelete) + { + return Result.DENIED; + } + + // if queues are white listed then ensure it is ok + if (queues != null) + { + // if no queues are listed then ALL are ok othereise it must be specified. + if (ownQueuesOnly) + { + if (owner.equals(_user)) + { + return (queues.size() == 0 || queues.contains(queueName)) ? Result.ALLOWED : Result.DENIED; + } + else + { + return Result.DENIED; + } + } + + // If we are + return (queues.size() == 0 || queues.contains(queueName)) ? Result.ALLOWED : Result.DENIED; + } + } + + // Can't authenticate without the right parameters + return Result.DENIED; + } + + private Result authorisePublish(Permission permission, String... parameters) + { + if(_fullVHostAccess) + { + //user has been granted full access to the vhost + return Result.ALLOWED; + } + + Map publishRights = (Map) _permissions.get(permission); + + if (publishRights == null) + { + return Result.DENIED; + } + + Map exchanges = (Map) publishRights.get(PUBLISH_EXCHANGES_KEY); + + // Having no exchanges listed gives full publish rights to all exchanges + if (exchanges == null) + { + return Result.ALLOWED; + } + // Otherwise exchange must be listed in the white list + + // If the map doesn't have the exchange then it isn't allowed + AMQShortString exchangeName = new AMQShortString(parameters[0]); + if (!exchanges.containsKey(exchangeName)) + { + return Result.DENIED; + } + else + { + // Get valid routing keys + HashSet routingKeys = (HashSet) exchanges.get(exchangeName); + + // Having no routingKeys in the map then all are allowed. + if (routingKeys == null) + { + return Result.ALLOWED; + } + else + { + // We have routingKeys so a match must be found to allowed binding + Iterator keys = routingKeys.iterator(); + + AMQShortString publishRKey = new AMQShortString(parameters[1]); + + boolean matched = false; + while (keys.hasNext() && !matched) + { + AMQShortString rkey = (AMQShortString) keys.next(); + + if (rkey.endsWith("*")) + { + matched = publishRKey.startsWith(rkey.subSequence(0, rkey.length() - 1)); + } + else + { + matched = publishRKey.equals(rkey); + } + } + return (matched) ? Result.ALLOWED : Result.DENIED; + } + } + } + + private Result authoriseCreateExchange(Permission permission, String... parameters) + { + if(_fullVHostAccess) + { + //user has been granted full access to the vhost + return Result.ALLOWED; + } + + Map rights = (Map) _permissions.get(permission); + + AMQShortString exchangeName = new AMQShortString(parameters[0]); + + // If the exchange list is doesn't exist then all is allowed else + // check the valid exchanges + if (rights == null || rights.containsKey(exchangeName)) + { + return Result.ALLOWED; + } + else + { + return Result.DENIED; + } + } + + private Result authoriseCreateQueue(Permission permission, String... parameters) + { + if(_fullVHostAccess) + { + //user has been granted full access to the vhost + return Result.ALLOWED; + } + + Map createRights = (Map) _permissions.get(permission); + + // If there are no create rights then deny request + if (createRights == null) + { + return Result.DENIED; + } + + //Look up the Queue Creation Rights + Map create_queues = (Map) createRights.get(CREATE_QUEUES_KEY); + + //Lookup the list of queues allowed to be created + Map create_queues_queues = (Map) create_queues.get(CREATE_QUEUE_QUEUES_KEY); + + + Boolean autoDelete = Boolean.valueOf(parameters[0]); + AMQShortString queueName = new AMQShortString(parameters[1]); + + if (autoDelete)// we have a temporary queue + { + return ((Boolean) create_queues.get(CREATE_QUEUE_TEMPORARY_KEY)) ? Result.ALLOWED : Result.DENIED; + } + else + { + // If there is a white list then check + if (create_queues_queues == null || create_queues_queues.containsKey(queueName)) + { + return Result.ALLOWED; + } + else + { + return Result.DENIED; + } + + } + } + + private Result authoriseBind(String... parameters) + { + if(_fullVHostAccess) + { + //user has been granted full access to the vhost + return Result.ALLOWED; + } + + AMQShortString exchangeName = new AMQShortString(parameters[1]); + AMQShortString bind_queueName = new AMQShortString(parameters[2]); + AMQShortString routingKey = new AMQShortString(parameters[3]); + + //Get all Create Rights for this user + Map bindCreateRights = (Map) _permissions.get(Permission.CREATEQUEUE); + + //Lookup the list of queues + Map bind_create_queues_queues = (Map) bindCreateRights.get(CREATE_QUEUE_QUEUES_KEY); + + // Check and see if we have a queue white list to check + if (bind_create_queues_queues != null) + { + //There a white list for queues + Map exchangeDetails = (Map) bind_create_queues_queues.get(bind_queueName); + + if (exchangeDetails == null) //Then all queue can be bound to all exchanges. + { + return Result.ALLOWED; + } + + // Check to see if we have a white list of routingkeys to check + Map rkeys = (Map) exchangeDetails.get(exchangeName); + + // if keys is null then any rkey is allowed on this exchange + if (rkeys == null) + { + // There is no routingkey white list + return Result.ALLOWED; + } + else + { + // We have routingKeys so a match must be found to allowed binding + Iterator keys = rkeys.keySet().iterator(); + + boolean matched = false; + while (keys.hasNext() && !matched) + { + AMQShortString rkey = (AMQShortString) keys.next(); + if (rkey.endsWith("*")) + { + matched = routingKey.startsWith(rkey.subSequence(0, rkey.length() - 1).toString()); + } + else + { + matched = routingKey.equals(rkey); + } + } + + + return (matched) ? Result.ALLOWED : Result.DENIED; + } + + + } + else + { + //no white list so all allowed. + return Result.ALLOWED; + } + } +} diff --git a/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java new file mode 100644 index 0000000000..ab43653122 --- /dev/null +++ b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java @@ -0,0 +1,427 @@ +/* + * 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 static org.apache.qpid.server.security.access.ObjectProperties.Property.*; + +import java.security.Principal; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.framing.AMQShortString; +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.PrincipalPermissions; +import org.apache.qpid.server.security.access.config.PrincipalPermissions.Permission; + +/** + * This uses the default + */ +public class SimpleXML extends AbstractPlugin +{ + public static final Logger _logger = Logger.getLogger(SimpleXML.class); + + private Map<String, PrincipalPermissions> _users; + + public static final SecurityPluginFactory<SimpleXML> FACTORY = new SecurityPluginFactory<SimpleXML>() + { + public SimpleXML newInstance(ConfigurationPlugin config) throws ConfigurationException + { + SimpleXMLConfiguration configuration = config.getConfiguration(SimpleXMLConfiguration.class.getName()); + + // If there is no configuration for this plugin then don't load it. + if (configuration == null) + { + return null; + } + + SimpleXML plugin = new SimpleXML(); + plugin.configure(configuration); + return plugin; + } + + public String getPluginName() + { + return SimpleXML.class.getName(); + } + + public Class<SimpleXML> getPluginClass() + { + return SimpleXML.class; + } + }; + + public void configure(ConfigurationPlugin config) + { + super.configure(config); + + SimpleXMLConfiguration configuration = (SimpleXMLConfiguration) _config; + + _users = new ConcurrentHashMap<String, PrincipalPermissions>(); + + processConfig(configuration.getConfiguration()); + } + + private void processConfig(Configuration config) + { + processPublish(config); + processConsume(config); + processCreate(config); + processAccess(config); + } + + /** + * @param config XML Configuration + */ + private void processAccess(Configuration config) + { + Configuration accessConfig = config.subset("access"); + + if (accessConfig.isEmpty()) + { + //there is no access configuration to process + return; + } + + // Process users that have full access permission + String[] users = accessConfig.getStringArray("users.user"); + + for (String user : users) + { + grant(Permission.ACCESS, user); + } + } + + /** + * Publish format takes Exchange + Routing Key Pairs + * + * @param config XML Configuration + */ + private void processPublish(Configuration config) + { + Configuration publishConfig = config.subset("publish"); + + // Process users that have full publish permission + String[] users = publishConfig.getStringArray("users.user"); + + for (String user : users) + { + grant(Permission.PUBLISH, user); + } + + // Process exchange limited users + int exchangeCount = 0; + Configuration exchangeConfig = publishConfig.subset("exchanges.exchange(" + exchangeCount + ")"); + + while (!exchangeConfig.isEmpty()) + { + // Get Exchange Name + AMQShortString exchangeName = new AMQShortString(exchangeConfig.getString("name")); + + // Get Routing Keys + int keyCount = 0; + Configuration routingkeyConfig = exchangeConfig.subset("routing_keys.routing_key(" + keyCount + ")"); + + while (!routingkeyConfig.isEmpty()) + { + // Get RoutingKey Value + AMQShortString routingKeyValue = new AMQShortString(routingkeyConfig.getString("value")); + + // Apply Exchange + RoutingKey permissions to Users + users = routingkeyConfig.getStringArray("users.user"); + for (String user : users) + { + grant(Permission.PUBLISH, user, exchangeName, routingKeyValue); + } + + // Apply permissions to Groups + + // Check for more configs + keyCount++; + routingkeyConfig = exchangeConfig.subset("routing_keys.routing_key(" + keyCount + ")"); + } + + // Apply Exchange wide permissions to Users + users = exchangeConfig.getStringArray("exchange(" + exchangeCount + ").users.user"); + + for (String user : users) + { + grant(Permission.PUBLISH, user, exchangeName); + } + + // Apply permissions to Groups + exchangeCount++; + exchangeConfig = publishConfig.subset("exchanges.exchange(" + exchangeCount + ")"); + } + } + + private void grant(Permission permission, String user, Object... parameters) + { + PrincipalPermissions permissions = _users.get(user); + + if (permissions == null) + { + permissions = new PrincipalPermissions(user); + } + + _users.put(user, permissions); + permissions.grant(permission, parameters); + } + + /** + * @param config XML Configuration + */ + private void processConsume(Configuration config) + { + boolean temporary = false; + Configuration tempConfig = null; + Configuration consumeConfig = config.subset("consume"); + + tempConfig = consumeConfig.subset("queues.temporary(0)"); + if (tempConfig != null) + { + temporary = true; + } + + //Permission all users who have rights to temp queues and topics + if (tempConfig != null && !tempConfig.isEmpty()) + { + String[] tempUsers = tempConfig.getStringArray("users.user"); + for (String user : tempUsers) + { + grant(Permission.CONSUME, user, temporary); + } + } + + // Process queue limited users + int queueCount = 0; + Configuration queueConfig = consumeConfig.subset("queues.queue(" + queueCount + ")"); + + while (!queueConfig.isEmpty()) + { + // Get queue Name + AMQShortString queueName = new AMQShortString(queueConfig.getString("name")); + // if there is no name then there may be a temporary element + + boolean ownQueues = queueConfig.containsKey("own_queues"); + + // Process permissions for this queue + String[] users = queueConfig.getStringArray("users.user"); + for (String user : users) + { + grant(Permission.CONSUME, user, queueName, ownQueues); + } + + // See if we have another config + queueCount++; + queueConfig = consumeConfig.subset("queues.queue(" + queueCount + ")"); + } + + // Process users that have full consume permission + String[] users = consumeConfig.getStringArray("users.user"); + + for (String user : users) + { + //NOTE: this call does not appear to do anything inside the grant section for consume + grant(Permission.CONSUME, user); + } + } + + /** + * @param config XML Configuration + */ + private void processCreate(Configuration config) + { + boolean temporary = false; + Configuration tempConfig = null; + + Configuration createConfig = config.subset("create"); + + tempConfig = createConfig.subset("queues.temporary(0)"); + if (tempConfig != null) + { + temporary = true; + } + + //Permission all users who have rights to temp queues and topics + if (tempConfig != null && !tempConfig.isEmpty()) + { + String[] tempUsers = tempConfig.getStringArray("users.user"); + for (String user : tempUsers) + { + grant(Permission.CREATEQUEUE, user, temporary); + } + } + // Process create permissions for queue creation + int queueCount = 0; + Configuration queueConfig = createConfig.subset("queues.queue(" + queueCount + ")"); + + while (!queueConfig.isEmpty()) + { + // Get queue Name + AMQShortString queueName = new AMQShortString(queueConfig.getString("name")); + + int exchangeCount = 0; + Configuration exchangeConfig = queueConfig.subset("exchanges.exchange(" + exchangeCount + ")"); + + while (!exchangeConfig.isEmpty()) + { + + AMQShortString exchange = new AMQShortString(exchangeConfig.getString("name")); + AMQShortString routingKey = new AMQShortString(exchangeConfig.getString("routing_key")); + + // Process permissions for this queue + String[] users = exchangeConfig.getStringArray("users.user"); + for (String user : users) + { + //This is broken as the user name is not stored + grant(Permission.CREATEEXCHANGE, user, exchange); + + //This call could be cleaned up as temporary is now being set earlier (above) + grant(Permission.CREATEQUEUE, user, temporary, (queueName.equals("") ? null : queueName), (exchange + .equals("") ? null : exchange), (routingKey.equals("") ? null : routingKey)); + } + + // See if we have another config + exchangeCount++; + exchangeConfig = queueConfig.subset("exchanges.exchange(" + exchangeCount + ")"); + } + + // Process users that are not bound to an exchange + String[] users = queueConfig.getStringArray("users.user"); + + for (String user : users) + { + grant(Permission.CREATEQUEUE, user, temporary, queueName); + } + + // See if we have another config + queueCount++; + queueConfig = createConfig.subset("queues.queue(" + queueCount + ")"); + } + + // Process create permissions for exchange creation + int exchangeCount = 0; + Configuration exchangeConfig = createConfig.subset("exchanges.exchange(" + exchangeCount + ")"); + + while (!exchangeConfig.isEmpty()) + { + AMQShortString exchange = new AMQShortString(exchangeConfig.getString("name")); + AMQShortString clazz = new AMQShortString(exchangeConfig.getString("class")); + + // Process permissions for this queue + String[] users = exchangeConfig.getStringArray("users.user"); + for (String user : users) + { + //And this is broken too + grant(Permission.CREATEEXCHANGE, user, exchange, clazz); + } + + // See if we have another config + exchangeCount++; + exchangeConfig = queueConfig.subset("exchanges.exchange(" + exchangeCount + ")"); + } + + // Process users that have full create permission + String[] users = createConfig.getStringArray("users.user"); + + for (String user : users) + { + grant(Permission.CREATEEXCHANGE, user); + grant(Permission.CREATEQUEUE, user); + } + } + + public Result access(ObjectType objectType, Object instance) + { + Principal principal = SecurityManager.getThreadPrincipal(); + if (principal == null) + { + return getDefault(); // Default if there is no user associated with the thread + } + PrincipalPermissions principalPermissions = _users.get(principal.getName()); + if (principalPermissions == null) + { + return Result.DENIED; + } + + // Authorise object access + if (objectType == ObjectType.VIRTUALHOST) + { + return principalPermissions.authorise(Permission.ACCESS); + } + + // Default + return getDefault(); + } + + public Result authorise(Operation operation, ObjectType objectType, ObjectProperties properties) + { + Principal principal = SecurityManager.getThreadPrincipal(); + if (principal == null) + { + return getDefault(); // Default if there is no user associated with the thread + } + PrincipalPermissions principalPermissions = _users.get(principal.getName()); + if (principalPermissions == null) + { + return Result.DENIED; + } + + // Authorise operation + switch (operation) + { + case CONSUME: + return principalPermissions.authorise(Permission.CONSUME, properties.get(NAME), properties.get(AUTO_DELETE), properties.get(OWNER)); + case PUBLISH: + return principalPermissions.authorise(Permission.PUBLISH, properties.get(NAME), properties.get(ROUTING_KEY)); + case CREATE: + if (objectType == ObjectType.EXCHANGE) + { + return principalPermissions.authorise(Permission.CREATEEXCHANGE, properties.get(NAME)); + } + else if (objectType == ObjectType.QUEUE) + { + return principalPermissions.authorise(Permission.CREATEQUEUE, properties.get(AUTO_DELETE), properties.get(NAME)); + } + case ACCESS: + return principalPermissions.authorise(Permission.ACCESS); + case BIND: + return principalPermissions.authorise(Permission.BIND, null, properties.get(NAME), properties.get(QUEUE_NAME), properties.get(ROUTING_KEY)); + case UNBIND: + return principalPermissions.authorise(Permission.UNBIND); + case DELETE: + return principalPermissions.authorise(Permission.DELETE); + case PURGE: + return principalPermissions.authorise(Permission.PURGE); + } + + // Default + return getDefault(); + } +} diff --git a/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLActivator.java b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLActivator.java new file mode 100644 index 0000000000..c09a9da0d8 --- /dev/null +++ b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLActivator.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 SimpleXML}. + */ +public class SimpleXMLActivator extends SecurityPluginActivator +{ + public SecurityPluginFactory getFactory() + { + return SimpleXML.FACTORY; + } + + public ConfigurationPluginFactory getConfigurationFactory() + { + return SimpleXMLConfiguration.FACTORY; + } +} diff --git a/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLConfiguration.java b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLConfiguration.java new file mode 100644 index 0000000000..e95c21b590 --- /dev/null +++ b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLConfiguration.java @@ -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 SimpleXMLConfiguration extends ConfigurationPlugin +{ + public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory() + { + public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException + { + ConfigurationPlugin instance = new SimpleXMLConfiguration(); + instance.setConfiguration(path, config); + return instance; + } + + public List<String> getParentPaths() + { + return Arrays.asList("security.access_control_list", "virtualhosts.virtualhost.security.access_control_list"); + } + }; + + public String[] getElementsProcessed() + { + return new String[] { "" }; + } + + public Configuration getConfiguration() + { + return _configuration; + } +} diff --git a/qpid/java/broker-plugins/simple-xml/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java b/qpid/java/broker-plugins/simple-xml/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java new file mode 100644 index 0000000000..65ab12a095 --- /dev/null +++ b/qpid/java/broker-plugins/simple-xml/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java @@ -0,0 +1,209 @@ +/* + * + * 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; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.security.Result; +import org.apache.qpid.server.security.access.config.PrincipalPermissions; +import org.apache.qpid.server.security.access.config.PrincipalPermissions.Permission; +import org.apache.qpid.test.utils.QpidTestCase; + +public class PrincipalPermissionsTest extends QpidTestCase +{ + private String _user = "user"; + private PrincipalPermissions _perms; + + // Common things that are passed to frame constructors + private AMQShortString _queueName = new AMQShortString(this.getClass().getName() + "queue"); + private AMQShortString _tempQueueName = new AMQShortString(this.getClass().getName() + "tempqueue"); + private AMQShortString _exchangeName = new AMQShortString("amq.direct"); + private AMQShortString _routingKey = new AMQShortString(this.getClass().getName() + "route"); + private boolean _autoDelete = false; + private AMQShortString _exchangeType = new AMQShortString("direct"); + private Boolean _temporary = false; + private Boolean _ownQueue = false; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _perms = new PrincipalPermissions(_user); + } + + + public void testPrincipalPermissions() + { + assertNotNull(_perms); + assertEquals(Result.ALLOWED, _perms.authorise(Permission.ACCESS, (String[]) null)); + } + + // FIXME: test has been disabled since the permissions assume that the user has tried to create + // the queue first. QPID-1597 + public void disableTestBind() throws Exception + { + String[] args = new String[]{null, _exchangeName.asString(), _queueName.asString(), _routingKey.asString()}; + + assertEquals(Result.DENIED, _perms.authorise(Permission.BIND, args)); + _perms.grant(Permission.BIND, (Object[]) null); + assertEquals(Result.ALLOWED, _perms.authorise(Permission.BIND, args)); + } + + public void testQueueCreate() + { + Object[] grantArgs = new Object[]{_temporary , _queueName, _exchangeName, _routingKey}; + String[] authArgs = new String[]{Boolean.toString(_autoDelete), _queueName.asString()}; + + assertEquals(Result.DENIED, _perms.authorise(Permission.CREATEQUEUE, authArgs)); + _perms.grant(Permission.CREATEQUEUE, grantArgs); + assertEquals(Result.ALLOWED, _perms.authorise(Permission.CREATEQUEUE, authArgs)); + } + + public void testQueueCreateWithNullRoutingKey() + { + Object[] grantArgs = new Object[]{_temporary , _queueName, _exchangeName, null}; + String[] authArgs = new String[]{Boolean.toString(_autoDelete), _queueName.asString()}; + + assertEquals(Result.DENIED, _perms.authorise(Permission.CREATEQUEUE, authArgs)); + _perms.grant(Permission.CREATEQUEUE, grantArgs); + assertEquals(Result.ALLOWED, _perms.authorise(Permission.CREATEQUEUE, authArgs)); + } + + // FIXME disabled, this fails due to grant putting the grant into the wrong map QPID-1598 + public void disableTestExchangeCreate() + { + String[] authArgs = new String[]{_exchangeName.asString()}; + Object[] grantArgs = new Object[]{_exchangeName, _exchangeType}; + + assertEquals(Result.DENIED, _perms.authorise(Permission.CREATEEXCHANGE, authArgs)); + _perms.grant(Permission.CREATEEXCHANGE, grantArgs); + assertEquals(Result.ALLOWED, _perms.authorise(Permission.CREATEEXCHANGE, authArgs)); + } + + public void testConsume() + { + String[] authArgs = new String[]{_queueName.asString(), Boolean.toString(_autoDelete), _user}; + Object[] grantArgs = new Object[]{_queueName, _ownQueue}; + + // FIXME: This throws a null pointer exception QPID-1599 + // assertFalse(_perms.authorise(Permission.CONSUME, authArgs)); + _perms.grant(Permission.CONSUME, grantArgs); + assertEquals(Result.ALLOWED, _perms.authorise(Permission.CONSUME, authArgs)); + } + + public void testPublish() throws AMQException + { + String[] authArgs = new String[]{_exchangeName.asString(), _routingKey.asString()}; + Object[] grantArgs = new Object[]{_exchangeName, _routingKey}; + + assertEquals(Result.DENIED, _perms.authorise(Permission.PUBLISH, authArgs)); + _perms.grant(Permission.PUBLISH, grantArgs); + assertEquals(Result.ALLOWED, _perms.authorise(Permission.PUBLISH, authArgs)); + } + + public void testVhostAccess() + { + //Tests that granting a user Virtualhost level access allows all authorisation requests + //where previously they would be denied + + //QPID-2133 createExchange rights currently allow all exchange creation unless rights for creating some + //specific exchanges are granted. Grant a specific exchange creation to cause all others to be denied. + Object[] createArgsCreateExchange = new Object[]{new AMQShortString("madeup"), _exchangeType}; + String[] authArgsCreateExchange = new String[]{_exchangeName.asString()}; + assertEquals("Exchange creation was not allowed", Result.ALLOWED, _perms.authorise(Permission.CREATEEXCHANGE, authArgsCreateExchange)); + _perms.grant(Permission.CREATEEXCHANGE, createArgsCreateExchange); + + String[] authArgsPublish = new String[]{_exchangeName.asString(), _routingKey.asString()}; + String[] authArgsConsume = new String[]{_queueName.asString(), Boolean.toString(_autoDelete), _user}; + String[] authArgsCreateQueue = new String[]{Boolean.toString(_autoDelete), _queueName.asString()}; +// QueueBindBodyImpl bind = new QueueBindBodyImpl(_ticket, _queueName, _exchangeName, _routingKey, _nowait, _arguments); + String[] authArgsBind = new String[]{ null, _exchangeName.asString(), _queueName.asString(), _routingKey.asString()}; + + assertEquals("Exchange creation was not denied", Result.DENIED, _perms.authorise(Permission.CREATEEXCHANGE, authArgsCreateExchange)); + assertEquals("Publish was not denied", Result.DENIED, _perms.authorise(Permission.PUBLISH, authArgsPublish)); + assertEquals("Consume creation was not denied", Result.DENIED, _perms.authorise(Permission.CONSUME, authArgsConsume)); + assertEquals("Queue creation was not denied", Result.DENIED, _perms.authorise(Permission.CREATEQUEUE, authArgsCreateQueue)); + //BIND pre-grant authorise check disabled due to QPID-1597 + //assertEquals("Binding creation was not denied", Result.DENIED, _perms.authorise(Permission.BIND, authArgsBind)); + + _perms.grant(Permission.ACCESS); + + assertEquals("Exchange creation was not allowed", Result.ALLOWED, _perms.authorise(Permission.CREATEEXCHANGE, authArgsCreateExchange)); + assertEquals("Publish was not allowed", Result.ALLOWED, _perms.authorise(Permission.PUBLISH, authArgsPublish)); + assertEquals("Consume creation was not allowed", Result.ALLOWED, _perms.authorise(Permission.CONSUME, authArgsConsume)); + assertEquals("Queue creation was not allowed", Result.ALLOWED, _perms.authorise(Permission.CREATEQUEUE, authArgsCreateQueue)); + assertEquals("Binding creation was not allowed", Result.ALLOWED, _perms.authorise(Permission.BIND, authArgsBind)); + } + + /** + * If the consume permission for temporary queues is for an unnamed queue then is should + * be global for any temporary queue but not for any non-temporary queue + */ + public void testTemporaryUnnamedQueueConsume() + { + String[] authNonTempQArgs = new String[]{_queueName.asString(), Boolean.toString(_autoDelete), _user}; + String[] authTempQArgs = new String[]{_tempQueueName.asString(), Boolean.TRUE.toString(), _user}; + Object[] grantArgs = new Object[]{true}; + + _perms.grant(Permission.CONSUME, grantArgs); + + //Next line shows up bug - non temp queue should be denied + assertEquals(Result.DENIED, _perms.authorise(Permission.CONSUME, authNonTempQArgs)); + assertEquals(Result.ALLOWED, _perms.authorise(Permission.CONSUME, authTempQArgs)); + } + + /** + * Test that temporary queue permissions before queue perms in the ACL config work correctly + */ + public void testTemporaryQueueFirstConsume() + { + String[] authNonTempQArgs = new String[]{_queueName.asString(), Boolean.toString(_autoDelete), _user}; + String[] authTempQArgs = new String[]{_tempQueueName.asString(), Boolean.TRUE.toString(), _user}; + Object[] grantArgs = new Object[]{true}; + Object[] grantNonTempQArgs = new Object[]{_queueName, _ownQueue}; + + //should not matter if the temporary permission is processed first or last + _perms.grant(Permission.CONSUME, grantNonTempQArgs); + _perms.grant(Permission.CONSUME, grantArgs); + + assertEquals(Result.ALLOWED, _perms.authorise(Permission.CONSUME, authNonTempQArgs)); + assertEquals(Result.ALLOWED, _perms.authorise(Permission.CONSUME, authTempQArgs)); + } + + /** + * Test that temporary queue permissions after queue perms in the ACL config work correctly + */ + public void testTemporaryQueueLastConsume() + { + String[] authNonTempQArgs = new String[]{_queueName.asString(), Boolean.toString(_autoDelete), _user}; + String[] authTempQArgs = new String[]{_tempQueueName.asString(), Boolean.TRUE.toString(), _user}; + Object[] grantArgs = new Object[]{true}; + Object[] grantNonTempQArgs = new Object[]{_queueName, _ownQueue}; + + //should not matter if the temporary permission is processed first or last + _perms.grant(Permission.CONSUME, grantArgs); + _perms.grant(Permission.CONSUME, grantNonTempQArgs); + + assertEquals(Result.ALLOWED, _perms.authorise(Permission.CONSUME, authNonTempQArgs)); + assertEquals(Result.ALLOWED, _perms.authorise(Permission.CONSUME, authTempQArgs)); + } +} |