diff options
author | Robert Godfrey <rgodfrey@apache.org> | 2013-04-22 10:36:01 +0000 |
---|---|---|
committer | Robert Godfrey <rgodfrey@apache.org> | 2013-04-22 10:36:01 +0000 |
commit | a51d443320d2406dd370db4fef1567b0e4b73c13 (patch) | |
tree | 86c7dad9458e422c9f8e4bbe3a3997f2fb30d888 | |
parent | ba219af8c78c7f6c23590a7b9f3cbc73dd6fd0e0 (diff) | |
download | qpid-python-a51d443320d2406dd370db4fef1567b0e4b73c13.tar.gz |
QPID-4678 : merged to QPID-4659 branch
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/QPID-4659@1470438 13f79535-47bb-0310-9956-ffa450edef68
14 files changed, 711 insertions, 9 deletions
diff --git a/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/PlainConfigurationTest.java b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/PlainConfigurationTest.java index cbfc9003c8..3a324bc571 100644 --- a/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/PlainConfigurationTest.java +++ b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/PlainConfigurationTest.java @@ -447,6 +447,13 @@ public class PlainConfigurationTest extends TestCase "user1", Operation.ACCESS, ObjectType.MANAGEMENT, ObjectProperties.EMPTY); } + public void testBrokerRuleParsing() throws Exception + { + validateRule(writeACLConfig("ACL ALLOW user1 CONFIGURE BROKER"), "user1", Operation.CONFIGURE, ObjectType.BROKER, + ObjectProperties.EMPTY); + validateRule(writeACLConfig("ACL ALLOW user1 ALL BROKER"), "user1", Operation.ALL, ObjectType.BROKER, ObjectProperties.EMPTY); + } + private void validateRule(final PlainConfiguration config, String username, Operation operation, ObjectType objectType, ObjectProperties objectProperties) { final RuleSet rs = config.getConfiguration(); diff --git a/qpid/java/broker/etc/broker_example.acl b/qpid/java/broker/etc/broker_example.acl index fc650801c8..bb85ef5e57 100644 --- a/qpid/java/broker/etc/broker_example.acl +++ b/qpid/java/broker/etc/broker_example.acl @@ -72,6 +72,9 @@ ACL ALLOW-LOG webadmins UPDATE USER ACL ALLOW-LOG webadmins UPDATE METHOD +# authorise operations changing broker model +ACL ALLOW-LOG webadmins CONFIGURE BROKER + # at the moment only the following UPDATE METHOD rules are supported by web management console #ACL ALLOW-LOG webadmins UPDATE METHOD component="VirtualHost.Queue" name="moveMessages" #ACL ALLOW-LOG webadmins UPDATE METHOD component="VirtualHost.Queue" name="copyMessages" diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java index ce8e06bf4f..8ff0b6d9e1 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java @@ -90,6 +90,7 @@ abstract class AbstractAdapter implements ConfiguredObject public final State setDesiredState(final State currentState, final State desiredState) throws IllegalStateTransitionException, AccessControlException { + authoriseSetDesiredState(currentState, desiredState); if (_taskExecutor.isTaskExecutorThread()) { if (setState(currentState, desiredState)) @@ -224,6 +225,7 @@ abstract class AbstractAdapter implements ConfiguredObject public Object setAttribute(final String name, final Object expected, final Object desired) throws IllegalStateException, AccessControlException, IllegalArgumentException { + authoriseSetAttribute(name, expected, desired); if (_taskExecutor.isTaskExecutorThread()) { if (changeAttribute(name, expected, desired)) @@ -302,6 +304,7 @@ abstract class AbstractAdapter implements ConfiguredObject @Override public <C extends ConfiguredObject> C createChild(Class<C> childClass, Map<String, Object> attributes, ConfiguredObject... otherParents) { + authoriseCreateChild(childClass, attributes, otherParents); if (_taskExecutor.isTaskExecutorThread()) { C child = addChild(childClass, attributes, otherParents); @@ -331,6 +334,7 @@ abstract class AbstractAdapter implements ConfiguredObject @Override public void setAttributes(final Map<String, Object> attributes) throws IllegalStateException, AccessControlException, IllegalArgumentException { + authoriseSetAttributes(attributes); if (getTaskExecutor().isTaskExecutorThread()) { changeAttributes(attributes); @@ -357,4 +361,24 @@ abstract class AbstractAdapter implements ConfiguredObject } } } + + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + // allowed by default + } + + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + // allowed by default + } + + protected <C extends ConfiguredObject> void authoriseCreateChild(Class<C> childClass, Map<String, Object> attributes, ConfiguredObject... otherParents) throws AccessControlException + { + // allowed by default + } + + protected void authoriseSetAttributes(Map<String, Object> attributes) throws AccessControlException + { + // allowed by default + } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java index 0c17637e2f..594ef7520a 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java @@ -316,6 +316,36 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana return manager; } + @Override + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + if(desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), AuthenticationProvider.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of authentication provider is denied"); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), AuthenticationProvider.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of authentication provider attributes is denied"); + } + } + + @Override + protected void authoriseSetAttributes(Map<String, Object> attributes) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), AuthenticationProvider.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of authentication provider attributes is denied"); + } + } + public static class SimpleAuthenticationProviderAdapter extends AuthenticationProviderAdapter<AuthenticationManager> { diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java index 841dfb8229..2bf7938b47 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java @@ -59,8 +59,7 @@ import org.apache.qpid.server.model.TrustStore; import org.apache.qpid.server.model.UUIDGenerator; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.configuration.updater.TaskExecutor; -import org.apache.qpid.server.security.auth.manager.Base64MD5PasswordFileAuthenticationManagerFactory; -import org.apache.qpid.server.security.auth.manager.PlainPasswordFileAuthenticationManagerFactory; +import org.apache.qpid.server.security.access.Operation; import org.apache.qpid.server.security.group.FileGroupManager; import org.apache.qpid.server.security.group.GroupManager; import org.apache.qpid.server.security.SecurityManager; @@ -390,7 +389,18 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat final VirtualHostAdapter virtualHostAdapter = new VirtualHostAdapter(UUID.randomUUID(), attributes, this, _statisticsGatherer, getTaskExecutor()); addVirtualHost(virtualHostAdapter); - virtualHostAdapter.setDesiredState(State.INITIALISING, State.ACTIVE); + + // permission has already been granted to create the virtual host + // disable further access check on other operations, e.g. create exchange + _securityManager.setAccessChecksDisabled(true); + try + { + virtualHostAdapter.setDesiredState(State.INITIALISING, State.ACTIVE); + } + finally + { + _securityManager.setAccessChecksDisabled(false); + } return virtualHostAdapter; } @@ -1031,7 +1041,6 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat @Override protected void changeAttributes(Map<String, Object> attributes) { - //TODO: Add ACL check //TODO: Add management mode check Map<String, Object> convertedAttributes = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); validateAttributes(convertedAttributes); @@ -1200,4 +1209,32 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat } } } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + if (!_securityManager.authoriseConfiguringBroker(getName(), Broker.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of broker attributes is denied"); + } + } + + @Override + protected <C extends ConfiguredObject> void authoriseCreateChild(Class<C> childClass, Map<String, Object> attributes, + ConfiguredObject... otherParents) throws AccessControlException + { + if (!_securityManager.authoriseConfiguringBroker(String.valueOf(attributes.get(NAME)), childClass, Operation.CREATE)) + { + throw new AccessControlException("Creation of new broker level entity is denied"); + } + } + + @Override + protected void authoriseSetAttributes(Map<String, Object> attributes) throws AccessControlException + { + if (!_securityManager.authoriseConfiguringBroker(getName(), Broker.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of broker attributes is denied"); + } + } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java index 63063ff58c..a37c2dceb7 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java @@ -45,6 +45,7 @@ import org.apache.qpid.server.model.Statistics; import org.apache.qpid.server.model.Transport; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.model.VirtualHostAlias; +import org.apache.qpid.server.security.access.Operation; import org.apache.qpid.server.util.MapValueConverter; import org.apache.qpid.server.util.ParameterizedTypeImpl; import org.apache.qpid.server.configuration.updater.TaskExecutor; @@ -356,4 +357,34 @@ public class PortAdapter extends AbstractAdapter implements Port } super.changeAttributes(MapValueConverter.convert(attributes, ATTRIBUTE_TYPES)); } + + @Override + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + if(desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), Port.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of port is denied"); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), Port.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of port attributes is denied"); + } + } + + @Override + protected void authoriseSetAttributes(Map<String, Object> attributes) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), Port.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of port attributes is denied"); + } + } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAdapter.java index 8680911000..50a83997be 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAdapter.java @@ -71,6 +71,7 @@ import org.apache.qpid.server.queue.AMQQueueFactory; import org.apache.qpid.server.queue.QueueEntry; import org.apache.qpid.server.queue.QueueRegistry; import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.access.Operation; import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; import org.apache.qpid.server.stats.StatisticsGatherer; import org.apache.qpid.server.store.MessageStore; @@ -980,8 +981,6 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual } else if (desiredState == State.DELETED) { - //TODO: add ACL check to authorize the operation - String hostName = getName(); if (hostName.equals(_broker.getAttribute(Broker.DEFAULT_VIRTUAL_HOST))) @@ -1091,4 +1090,34 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual { throw new UnsupportedOperationException("Changing attributes on virtualhosts is not supported."); } + + @Override + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + if(desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), VirtualHost.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of virtual host is denied"); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), VirtualHost.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of virtual host attributes is denied"); + } + } + + @Override + protected void authoriseSetAttributes(Map<String, Object> attributes) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), VirtualHost.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of virtual host attributes is denied"); + } + } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java index 9ef1ae1a3a..3fb3f70f2e 100755 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java @@ -33,7 +33,9 @@ import org.apache.qpid.server.queue.AMQQueue; 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.OperationLoggingDetails; +import static org.apache.qpid.server.security.access.ObjectType.BROKER; import static org.apache.qpid.server.security.access.ObjectType.EXCHANGE; import static org.apache.qpid.server.security.access.ObjectType.GROUP; import static org.apache.qpid.server.security.access.ObjectType.METHOD; @@ -41,6 +43,7 @@ import static org.apache.qpid.server.security.access.ObjectType.QUEUE; import static org.apache.qpid.server.security.access.ObjectType.USER; import static org.apache.qpid.server.security.access.ObjectType.VIRTUALHOST; import static org.apache.qpid.server.security.access.Operation.BIND; +import static org.apache.qpid.server.security.access.Operation.CONFIGURE; import static org.apache.qpid.server.security.access.Operation.CONSUME; import static org.apache.qpid.server.security.access.Operation.CREATE; import static org.apache.qpid.server.security.access.Operation.DELETE; @@ -549,4 +552,20 @@ public class SecurityManager implements ConfigurationChangeListener } } + public boolean authoriseConfiguringBroker(String configuredObjectName, Class<? extends ConfiguredObject> configuredObjectType, Operation configuredObjectOperation) + { + String description = String.format("%s %s '%s'", + configuredObjectOperation == null? null : configuredObjectOperation.name().toLowerCase(), + configuredObjectType == null ? null : configuredObjectType.getSimpleName().toLowerCase(), + configuredObjectName); + final OperationLoggingDetails properties = new OperationLoggingDetails(description); + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(CONFIGURE, BROKER, properties); + } + }); + } + } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectProperties.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectProperties.java index 8e38681e68..6c631fc360 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectProperties.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectProperties.java @@ -350,4 +350,9 @@ public class ObjectProperties { return _properties.toString(); } + + public boolean isEmpty() + { + return _properties.isEmpty(); + } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java index 8bc4b9d278..e93f487bdb 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java @@ -20,6 +20,7 @@ package org.apache.qpid.server.security.access; import static org.apache.qpid.server.security.access.Operation.ACCESS; import static org.apache.qpid.server.security.access.Operation.BIND; +import static org.apache.qpid.server.security.access.Operation.CONFIGURE; import static org.apache.qpid.server.security.access.Operation.CONSUME; import static org.apache.qpid.server.security.access.Operation.CREATE; import static org.apache.qpid.server.security.access.Operation.DELETE; @@ -48,7 +49,8 @@ public enum ObjectType ROUTE, // Not allowed in the Java broker METHOD(Operation.ALL, ACCESS, UPDATE), USER(Operation.ALL, CREATE, DELETE, UPDATE), - GROUP(Operation.ALL, CREATE, DELETE, UPDATE); + GROUP(Operation.ALL, CREATE, DELETE, UPDATE), + BROKER(Operation.ALL, CONFIGURE); private EnumSet<Operation> _actions; diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Operation.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Operation.java index 21ea042eed..df5289b7bf 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Operation.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Operation.java @@ -32,8 +32,9 @@ public enum Operation UNBIND, DELETE, PURGE, - UPDATE; - + UPDATE, + CONFIGURE; + public static Operation parse(String text) { for (Operation operation : values()) diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/OperationLoggingDetails.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/OperationLoggingDetails.java new file mode 100644 index 0000000000..a683199abc --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/OperationLoggingDetails.java @@ -0,0 +1,53 @@ +/* + * + * 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; + + +public class OperationLoggingDetails extends ObjectProperties +{ + private String _description; + + public OperationLoggingDetails(String description) + { + super(); + _description = description; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder("("); + if (!super.isEmpty()) + { + sb.append("properties=").append(super.toString()); + } + if (_description != null) + { + if (sb.length() > 1) + { + sb.append(", "); + } + sb.append(_description); + } + sb.append(")"); + return sb.toString(); + } +} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java index 30d5b195f1..f83eb391e7 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java @@ -95,4 +95,10 @@ public class QpidRestTestCase extends QpidBrokerTestCase { return _restTestHelper; } + + protected void restartBrokerInManagementMode() throws Exception + { + stopBroker(); + startBroker(0, true); + } } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/acl/BrokerACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/acl/BrokerACLTest.java new file mode 100644 index 0000000000..5d23219336 --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/acl/BrokerACLTest.java @@ -0,0 +1,455 @@ +/* + * + * 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.systest.rest.acl; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Protocol; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.security.acl.AbstractACLTestCase; +import org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManagerFactory; +import org.apache.qpid.server.security.auth.manager.PlainPasswordFileAuthenticationManagerFactory; +import org.apache.qpid.systest.rest.QpidRestTestCase; +import org.apache.qpid.test.utils.TestBrokerConfiguration; +import org.apache.qpid.test.utils.TestFileUtils; + +public class BrokerACLTest extends QpidRestTestCase +{ + private static final String ALLOWED_USER = "user1"; + private static final String DENIED_USER = "user2"; + + @Override + protected void customizeConfiguration() throws ConfigurationException, IOException + { + super.customizeConfiguration(); + getRestTestHelper().configureTemporaryPasswordFile(this, ALLOWED_USER, DENIED_USER); + + AbstractACLTestCase.writeACLFileUtil(this, null, + "ACL ALLOW-LOG ALL ACCESS MANAGEMENT", + "ACL ALLOW-LOG " + ALLOWED_USER + " CONFIGURE BROKER", + "ACL DENY-LOG " + DENIED_USER + " CONFIGURE BROKER", + "ACL DENY-LOG ALL ALL"); + + getBrokerConfiguration().setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_HTTP_MANAGEMENT, + "httpBasicAuthenticationEnabled", true); + } + + public void testCreateAuthenticationProviderAllowed() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + String authenticationProviderName = getTestName(); + + int responseCode = createAuthenticationProvider(authenticationProviderName); + assertEquals("Provider creation should be allowed", 201, responseCode); + + assertAuthenticationProviderExists(authenticationProviderName); + } + + public void testCreateAuthenticationProviderDenied() throws Exception + { + getRestTestHelper().setUsernameAndPassword(DENIED_USER, DENIED_USER); + + String authenticationProviderName = getTestName(); + + int responseCode = createAuthenticationProvider(authenticationProviderName); + assertEquals("Provider creation should be denied", 403, responseCode); + + assertAuthenticationProviderDoesNotExist(authenticationProviderName); + } + + public void testDeleteAuthenticationProviderAllowed() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + String providerName = getTestName(); + + int responseCode = createAuthenticationProvider(providerName); + assertEquals("Provider creation should be allowed", 201, responseCode); + + assertAuthenticationProviderExists(providerName); + + responseCode = getRestTestHelper().submitRequest("/rest/authenticationprovider/" + providerName, "DELETE", null); + assertEquals("Provider deletion should be allowed", 200, responseCode); + + assertAuthenticationProviderDoesNotExist(TEST2_VIRTUALHOST); + } + + public void testDeleteAuthenticationProviderDenied() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + String providerName = getTestName(); + + int responseCode = createAuthenticationProvider(providerName); + assertEquals("Provider creation should be allowed", 201, responseCode); + + assertAuthenticationProviderExists(providerName); + + getRestTestHelper().setUsernameAndPassword(DENIED_USER, DENIED_USER); + responseCode = getRestTestHelper().submitRequest("/rest/authenticationprovider/" + providerName, "DELETE", null); + assertEquals("Provider deletion should be denied", 403, responseCode); + + assertAuthenticationProviderExists(providerName); + } + + public void testSetAuthenticationProviderAttributesAllowed() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + String providerName = TestBrokerConfiguration.ENTRY_NAME_AUTHENTICATION_PROVIDER; + + assertAuthenticationProviderExists(providerName); + + File file = TestFileUtils.createTempFile(this, ".users", "guest:guest\n" + ALLOWED_USER + ":" + ALLOWED_USER + "\n" + + DENIED_USER + ":" + DENIED_USER); + + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(AuthenticationProvider.NAME, providerName); + attributes.put(AuthenticationProvider.TYPE, PlainPasswordFileAuthenticationManagerFactory.PROVIDER_TYPE); + attributes.put(PlainPasswordFileAuthenticationManagerFactory.ATTRIBUTE_PATH, file.getAbsolutePath()); + + int responseCode = getRestTestHelper().submitRequest("/rest/authenticationprovider/" + providerName, "PUT", attributes); + assertEquals("Setting of provider attribites should be allowed", 200, responseCode); + } + + public void testSetAuthenticationProviderAttributesDenied() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + String providerName = TestBrokerConfiguration.ENTRY_NAME_AUTHENTICATION_PROVIDER; + + Map<String, Object> providerData = getRestTestHelper().getJsonAsSingletonList( + "/rest/authenticationprovider/" + providerName); + + File file = TestFileUtils.createTempFile(this, ".users", "guest:guest\n" + ALLOWED_USER + ":" + ALLOWED_USER + "\n" + + DENIED_USER + ":" + DENIED_USER); + + getRestTestHelper().setUsernameAndPassword(DENIED_USER, DENIED_USER); + + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(AuthenticationProvider.NAME, providerName); + attributes.put(AuthenticationProvider.TYPE, AnonymousAuthenticationManagerFactory.PROVIDER_TYPE); + attributes.put(PlainPasswordFileAuthenticationManagerFactory.ATTRIBUTE_PATH, file.getAbsolutePath()); + + int responseCode = getRestTestHelper().submitRequest("/rest/authenticationprovider/" + providerName, "PUT", attributes); + assertEquals("Setting of provider attribites should be allowed", 403, responseCode); + + Map<String, Object> provider = getRestTestHelper().getJsonAsSingletonList("/rest/authenticationprovider/" + providerName); + assertEquals("Unexpected PATH attribute value", + providerData.get(PlainPasswordFileAuthenticationManagerFactory.ATTRIBUTE_PATH), + provider.get(PlainPasswordFileAuthenticationManagerFactory.ATTRIBUTE_PATH)); + } + + public void testCreateVirtualHostAllowed() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + String hostName = getTestName(); + + int responseCode = createHost(hostName); + assertEquals("Host creation should be allowed", 201, responseCode); + + assertVirtualHostExists(hostName); + } + + public void testCreateVirtualHostDenied() throws Exception + { + getRestTestHelper().setUsernameAndPassword(DENIED_USER, DENIED_USER); + + String hostName = getTestName(); + + int responseCode = createHost(hostName); + assertEquals("Host creation should be denied", 403, responseCode); + + assertVirtualHostDoesNotExist(hostName); + } + + public void testDeleteVirtualHostAllowed() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + assertVirtualHostExists(TEST2_VIRTUALHOST); + + int responseCode = getRestTestHelper().submitRequest("/rest/virtualhost/" + TEST2_VIRTUALHOST, "DELETE", null); + assertEquals("Host deletion should be allowed", 200, responseCode); + + assertVirtualHostDoesNotExist(TEST2_VIRTUALHOST); + } + + public void testDeleteVirtualHostDenied() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + assertVirtualHostExists(TEST2_VIRTUALHOST); + + getRestTestHelper().setUsernameAndPassword(DENIED_USER, DENIED_USER); + + int responseCode = getRestTestHelper().submitRequest("/rest/virtualhost/" + TEST2_VIRTUALHOST, "DELETE", null); + assertEquals("Host deletion should be denied", 403, responseCode); + + assertVirtualHostExists(TEST2_VIRTUALHOST); + } + + public void testCreatePortAllowed() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + String portName = getTestName(); + + int responseCode = createPort(portName); + assertEquals("Port creation should be allowed", 201, responseCode); + + assertPortExists(portName); + } + + public void testCreatePortDenied() throws Exception + { + getRestTestHelper().setUsernameAndPassword(DENIED_USER, DENIED_USER); + + String portName = getTestName(); + + int responseCode = createPort(portName); + assertEquals("Port creation should be denied", 403, responseCode); + + assertPortDoesNotExist(portName); + } + + public void testDeletePortDenied() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + String portName = TestBrokerConfiguration.ENTRY_NAME_AMQP_PORT; + assertPortExists(portName); + + restartBrokerInManagementMode(); + + getRestTestHelper().setUsernameAndPassword(DENIED_USER, DENIED_USER); + + int responseCode = getRestTestHelper().submitRequest("/rest/port/" + portName, "DELETE", null); + assertEquals("Port deletion should be denied", 403, responseCode); + + assertPortExists(portName); + } + + public void testDeletePortAllowed() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + String portName = TestBrokerConfiguration.ENTRY_NAME_AMQP_PORT; + assertPortExists(portName); + + restartBrokerInManagementMode(); + + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + int responseCode = getRestTestHelper().submitRequest("/rest/port/" + portName, "DELETE", null); + assertEquals("Port deletion should be allowed", 200, responseCode); + + assertPortDoesNotExist(portName); + } + + public void testSetPortAttributesAllowed() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + String portName = getTestName(); + + int responseCode = createPort(portName); + assertEquals("Port creation should be allowed", 201, responseCode); + + assertPortExists(portName); + + restartBrokerInManagementMode(); + + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(Port.NAME, portName); + attributes.put(Port.AUTHENTICATION_PROVIDER, ANONYMOUS_AUTHENTICATION_PROVIDER); + responseCode = getRestTestHelper().submitRequest("/rest/port/" + portName, "PUT", attributes); + assertEquals("Setting of port attribites should be allowed", 200, responseCode); + + Map<String, Object> port = getRestTestHelper().getJsonAsSingletonList("/rest/port/" + portName); + assertEquals("Unexpected authentication provider attribute value", ANONYMOUS_AUTHENTICATION_PROVIDER, + port.get(Port.AUTHENTICATION_PROVIDER)); + } + + public void testSetPortAttributesDenied() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + String portName = getTestName(); + + int responseCode = createPort(portName); + assertEquals("Port creation should be allowed", 201, responseCode); + + assertPortExists(portName); + + restartBrokerInManagementMode(); + + getRestTestHelper().setUsernameAndPassword(DENIED_USER, DENIED_USER); + + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(Port.NAME, portName); + attributes.put(Port.PROTOCOLS, Arrays.asList(Protocol.AMQP_0_9)); + attributes.put(Port.AUTHENTICATION_PROVIDER, ANONYMOUS_AUTHENTICATION_PROVIDER); + responseCode = getRestTestHelper().submitRequest("/rest/port/" + portName, "PUT", attributes); + assertEquals("Setting of port attribites should be denied", 403, responseCode); + + Map<String, Object> port = getRestTestHelper().getJsonAsSingletonList("/rest/port/" + portName); + assertEquals("Unexpected authentication provider attribute value", + TestBrokerConfiguration.ENTRY_NAME_AUTHENTICATION_PROVIDER, port.get(Port.AUTHENTICATION_PROVIDER)); + } + + public void testSetBrokerAttributesAllowed() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + String defaultAuthenticationProvider = TestBrokerConfiguration.ENTRY_NAME_AUTHENTICATION_PROVIDER; + Map<String, Object> brokerAttributes = getRestTestHelper().getJsonAsSingletonList("/rest/broker"); + assertEquals("Unexpected authentication provider", defaultAuthenticationProvider, + brokerAttributes.get(Broker.DEFAULT_AUTHENTICATION_PROVIDER)); + restartBrokerInManagementMode(); + + Map<String, Object> newAttributes = new HashMap<String, Object>(); + newAttributes.put(Broker.DEFAULT_AUTHENTICATION_PROVIDER, ANONYMOUS_AUTHENTICATION_PROVIDER); + int responseCode = getRestTestHelper().submitRequest("/rest/broker", "PUT", newAttributes); + assertEquals("Setting of port attribites should be allowed", 200, responseCode); + + brokerAttributes = getRestTestHelper().getJsonAsSingletonList("/rest/broker"); + assertEquals("Unexpected default authentication provider attribute value", ANONYMOUS_AUTHENTICATION_PROVIDER, + brokerAttributes.get(Broker.DEFAULT_AUTHENTICATION_PROVIDER)); + } + + public void testSetBrokerAttributesDenied() throws Exception + { + getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER); + + String defaultAuthenticationProvider = TestBrokerConfiguration.ENTRY_NAME_AUTHENTICATION_PROVIDER; + + Map<String, Object> brokerAttributes = getRestTestHelper().getJsonAsSingletonList("/rest/broker"); + assertEquals("Unexpected authentication provider", defaultAuthenticationProvider, + brokerAttributes.get(Broker.DEFAULT_AUTHENTICATION_PROVIDER)); + restartBrokerInManagementMode(); + + getRestTestHelper().setUsernameAndPassword(DENIED_USER, DENIED_USER); + Map<String, Object> newAttributes = new HashMap<String, Object>(); + newAttributes.put(Broker.DEFAULT_AUTHENTICATION_PROVIDER, ANONYMOUS_AUTHENTICATION_PROVIDER); + int responseCode = getRestTestHelper().submitRequest("/rest/broker", "PUT", newAttributes); + assertEquals("Setting of port attribites should be allowed", 403, responseCode); + + brokerAttributes = getRestTestHelper().getJsonAsSingletonList("/rest/broker"); + assertEquals("Unexpected default authentication provider attribute value", defaultAuthenticationProvider, + brokerAttributes.get(Broker.DEFAULT_AUTHENTICATION_PROVIDER)); + } + + private int createPort(String portName) throws Exception + { + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(Port.NAME, portName); + attributes.put(Port.PORT, findFreePort()); + attributes.put(Port.AUTHENTICATION_PROVIDER, TestBrokerConfiguration.ENTRY_NAME_AUTHENTICATION_PROVIDER); + + return getRestTestHelper().submitRequest("/rest/port/" + portName, "PUT", attributes); + } + + private void assertPortExists(String portName) throws Exception + { + assertPortExistence(portName, true); + } + + private void assertPortDoesNotExist(String portName) throws Exception + { + assertPortExistence(portName, false); + } + + private void assertPortExistence(String portName, boolean exists) throws Exception + { + List<Map<String, Object>> hosts = getRestTestHelper().getJsonAsList("/rest/port/" + portName); + assertEquals("Unexpected result", exists, !hosts.isEmpty()); + } + + private int createHost(String hostName) throws Exception + { + Map<String, Object> hostData = new HashMap<String, Object>(); + hostData.put(VirtualHost.NAME, hostName); + hostData.put(VirtualHost.STORE_PATH, getStoreLocation(hostName)); + hostData.put(VirtualHost.STORE_TYPE, getTestProfileMessageStoreType()); + + return getRestTestHelper().submitRequest("/rest/virtualhost/" + hostName, "PUT", hostData); + } + + private void assertVirtualHostDoesNotExist(String hostName) throws Exception + { + assertVirtualHostExistence(hostName, false); + } + + private void assertVirtualHostExists(String hostName) throws Exception + { + assertVirtualHostExistence(hostName, true); + } + + private void assertVirtualHostExistence(String hostName, boolean exists) throws Exception + { + List<Map<String, Object>> hosts = getRestTestHelper().getJsonAsList("/rest/virtualhost/" + hostName); + assertEquals("Unexpected result", exists, !hosts.isEmpty()); + } + + private String getStoreLocation(String hostName) + { + return new File(TMP_FOLDER, "store-" + hostName + "-" + System.currentTimeMillis()).getAbsolutePath(); + } + + private int createAuthenticationProvider(String authenticationProviderName) throws Exception + { + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(AuthenticationProvider.NAME, authenticationProviderName); + attributes.put(AuthenticationProvider.TYPE, AnonymousAuthenticationManagerFactory.PROVIDER_TYPE); + + return getRestTestHelper().submitRequest("/rest/authenticationprovider/" + authenticationProviderName, "PUT", attributes); + } + + private void assertAuthenticationProviderDoesNotExist(String authenticationProviderName) throws Exception + { + assertAuthenticationProviderExistence(authenticationProviderName, false); + } + + private void assertAuthenticationProviderExists(String authenticationProviderName) throws Exception + { + assertAuthenticationProviderExistence(authenticationProviderName, true); + } + + private void assertAuthenticationProviderExistence(String authenticationProviderName, boolean exists) throws Exception + { + String path = "/rest/authenticationprovider/" + authenticationProviderName; + List<Map<String, Object>> providers = getRestTestHelper().getJsonAsList(path); + assertEquals("Unexpected result", exists, !providers.isEmpty()); + } + +} |