diff options
Diffstat (limited to 'java/broker')
78 files changed, 3847 insertions, 519 deletions
diff --git a/java/broker/etc/broker_example.acl b/java/broker/etc/broker_example.acl index 45a48bda09..a5e01fb895 100644 --- a/java/broker/etc/broker_example.acl +++ b/java/broker/etc/broker_example.acl @@ -19,14 +19,7 @@ ### EXAMPLE ACL V2 FILE ### NOTE: Rules are considered from top to bottom, and the first matching rule governs the decision. - -### DEFINE GROUPS ### - -#Define a 'messaging-users' group with users 'client' and 'server' in it -GROUP messaging-users client server - -#Define a group for management web console users -GROUP webadmins webadmin +### Rules may refer to users or groups. Groups are currently defined in the etc/groups file. ### JMX MANAGEMENT #### @@ -34,9 +27,9 @@ GROUP webadmins webadmin # This is used for items such as querying the management API and broker release versions. ACL ALLOW ALL ACCESS METHOD component="ServerInformation" -# Allow 'admin' all management operations. To reduce log file noise, only non-read-only operations are logged. -ACL ALLOW admin ACCESS METHOD -ACL ALLOW-LOG admin ALL METHOD +# Allow 'administrators' all management operations. To reduce log file noise, only non-read-only operations are logged. +ACL ALLOW administrators ACCESS METHOD +ACL ALLOW-LOG administrators ALL METHOD # Allow 'guest' to view logger levels, and use getter methods on LoggingManagement ACL ALLOW guest ACCESS METHOD component="LoggingManagement" name="viewEffectiveRuntimeLoggerLevels" @@ -87,6 +80,13 @@ ACL ALLOW-LOG webadmins CREATE EXCHANGE ACL ALLOW-LOG webadmins DELETE EXCHANGE ACL ALLOW-LOG webadmins BIND EXCHANGE ACL ALLOW-LOG webadmins UNBIND EXCHANGE +ACL ALLOW-LOG webadmins CREATE GROUP +ACL ALLOW-LOG webadmins DELETE GROUP +ACL ALLOW-LOG webadmins UPDATE GROUP +ACL ALLOW-LOG webadmins CREATE USER +ACL ALLOW-LOG webadmins DELETE USER +ACL ALLOW-LOG webadmins UPDATE USER + ACL ALLOW-LOG webadmins UPDATE METHOD # at the moment only the following UPDATE METHOD rules are supported by web management console @@ -96,5 +96,5 @@ ACL ALLOW-LOG webadmins UPDATE METHOD ### DEFAULT ### -#Deny all users from performing all operations +# Deny all users from performing all operations ACL DENY-LOG all all diff --git a/java/broker/etc/config.xml b/java/broker/etc/config.xml index 08c7c23d13..7bd4ceb128 100644 --- a/java/broker/etc/config.xml +++ b/java/broker/etc/config.xml @@ -79,13 +79,24 @@ </principal-database> </pd-auth-manager> - <!-- By default, all authenticated users have permissions to perform all actions --> + <!-- + <file-group-manager> + <attributes> + <attribute> + <name>groupFile</name> + <value>${conf}/groups</value> + </attribute> + </attributes> + </file-group-manager> + --> + + <!-- + ACL Example. This example illustrates securing both management and messaging. + By default, all authenticated users have permissions to perform all actions - <!-- ACL Example - This example illustrates securing the both Management (JMX) and Messaging. <acl>${conf}/broker_example.acl</acl> --> - + <msg-auth>false</msg-auth> </security> diff --git a/java/broker/etc/groups b/java/broker/etc/groups new file mode 100644 index 0000000000..e3912ece99 --- /dev/null +++ b/java/broker/etc/groups @@ -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. +# + +# +# To define a group, use the format: +# +# <groupname>.users=<user1>,<user2>,...,<usern> +# + +messaging-users.users=guest,client,server +administrators.users=admin +webadmins.users=webadmin + diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java index b8c8411c5d..e65fe10eb5 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java @@ -32,9 +32,8 @@ import org.apache.qpid.framing.MethodRegistry; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.server.protocol.AMQProtocolSession; import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.server.security.auth.AuthenticationResult; -import org.apache.qpid.server.security.auth.manager.AuthenticationManager; -import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; import org.apache.qpid.server.state.AMQState; import org.apache.qpid.server.state.AMQStateManager; import org.apache.qpid.server.state.StateAwareMethodListener; @@ -61,7 +60,7 @@ public class ConnectionSecureOkMethodHandler implements StateAwareMethodListener { AMQProtocolSession session = stateManager.getProtocolSession(); - AuthenticationManager authMgr = stateManager.getAuthenticationManager(); + SubjectCreator subjectCreator = stateManager.getSubjectCreator(); SaslServer ss = session.getSaslServer(); if (ss == null) @@ -69,7 +68,7 @@ public class ConnectionSecureOkMethodHandler implements StateAwareMethodListener throw new AMQException("No SASL context set up in session"); } MethodRegistry methodRegistry = session.getMethodRegistry(); - AuthenticationResult authResult = authMgr.authenticate(ss, body.getResponse()); + SubjectAuthenticationResult authResult = subjectCreator.authenticate(ss, body.getResponse()); switch (authResult.getStatus()) { case ERROR: diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java index a522b9f60f..9f596a4637 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java @@ -32,9 +32,8 @@ import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.server.configuration.ServerConfiguration; import org.apache.qpid.server.protocol.AMQProtocolSession; import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.server.security.auth.AuthenticationResult; -import org.apache.qpid.server.security.auth.manager.AuthenticationManager; -import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; import org.apache.qpid.server.state.AMQState; import org.apache.qpid.server.state.AMQStateManager; import org.apache.qpid.server.state.StateAwareMethodListener; @@ -65,11 +64,11 @@ public class ConnectionStartOkMethodHandler implements StateAwareMethodListener< _logger.info("SASL Mechanism selected: " + body.getMechanism()); _logger.info("Locale selected: " + body.getLocale()); - AuthenticationManager authMgr = stateManager.getAuthenticationManager(); + SubjectCreator subjectCreator = stateManager.getSubjectCreator(); SaslServer ss = null; try { - ss = authMgr.createSaslServer(String.valueOf(body.getMechanism()), session.getLocalFQDN(), session.getPeerPrincipal()); + ss = subjectCreator.createSaslServer(String.valueOf(body.getMechanism()), session.getLocalFQDN(), session.getPeerPrincipal()); if (ss == null) { @@ -78,7 +77,7 @@ public class ConnectionStartOkMethodHandler implements StateAwareMethodListener< session.setSaslServer(ss); - final AuthenticationResult authResult = authMgr.authenticate(ss, body.getResponse()); + final SubjectAuthenticationResult authResult = subjectCreator.authenticate(ss, body.getResponse()); //save clientProperties session.setClientProperties(body.getClientProperties()); diff --git a/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AbstractManagementActor.java b/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AbstractManagementActor.java new file mode 100644 index 0000000000..f67c7a1c6a --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AbstractManagementActor.java @@ -0,0 +1,48 @@ +package org.apache.qpid.server.logging.actors; + +import java.security.AccessController; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; + +public abstract class AbstractManagementActor extends AbstractActor +{ + /** + * Holds the principal name to display when principal subject is not available. + * <p> + * This is useful for cases when users invoke JMX operation over JConsole + * attached to the local JVM. + */ + protected static final String UNKNOWN_PRINCIPAL = "N/A"; + + /** used when the principal name cannot be discovered from the Subject */ + private final String _fallbackPrincipalName; + + public AbstractManagementActor(RootMessageLogger rootLogger, String fallbackPrincipalName) + { + super(rootLogger); + _fallbackPrincipalName = fallbackPrincipalName; + } + + /** + * Returns current {@link AuthenticatedPrincipal} name or {@link #_fallbackPrincipalName} + * if it can't be found. + */ + protected String getPrincipalName() + { + String identity = _fallbackPrincipalName; + + final Subject subject = Subject.getSubject(AccessController.getContext()); + if (subject != null) + { + AuthenticatedPrincipal authenticatedPrincipal = AuthenticatedPrincipal.getOptionalAuthenticatedPrincipalFromSubject(subject); + if(authenticatedPrincipal != null) + { + identity = authenticatedPrincipal.getName(); + } + } + return identity; + } +}
\ No newline at end of file diff --git a/java/broker/src/main/java/org/apache/qpid/server/logging/actors/HttpManagementActor.java b/java/broker/src/main/java/org/apache/qpid/server/logging/actors/HttpManagementActor.java new file mode 100644 index 0000000000..9b445c2bd9 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/logging/actors/HttpManagementActor.java @@ -0,0 +1,62 @@ +/* + * + * 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.logging.actors; + +import java.text.MessageFormat; + +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.subjects.LogSubjectFormat; + +/** + * HttpManagement actor to use in {@link AbstractServlet} to log all http management operational logging. + * + * An instance is required per http Session. + */ +public class HttpManagementActor extends AbstractManagementActor +{ + private String _cachedLogString; + private String _lastPrincipalName; + private String _address; + + public HttpManagementActor(RootMessageLogger rootLogger, String ip, int port) + { + super(rootLogger, UNKNOWN_PRINCIPAL); + _address = ip + ":" + port; + } + + private synchronized String getAndCacheLogString() + { + String principalName = getPrincipalName(); + + if(!principalName.equals(_lastPrincipalName)) + { + _lastPrincipalName = principalName; + _cachedLogString = "[" + MessageFormat.format(LogSubjectFormat.MANAGEMENT_FORMAT, principalName, _address) + "] "; + } + + return _cachedLogString; + } + + public String getLogMessage() + { + return getAndCacheLogString(); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/logging/actors/ManagementActor.java b/java/broker/src/main/java/org/apache/qpid/server/logging/actors/ManagementActor.java index a2f3506502..ba5ea47fc1 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/logging/actors/ManagementActor.java +++ b/java/broker/src/main/java/org/apache/qpid/server/logging/actors/ManagementActor.java @@ -21,58 +21,31 @@ package org.apache.qpid.server.logging.actors; import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.subjects.LogSubjectFormat; -import javax.management.remote.JMXPrincipal; -import javax.security.auth.Subject; -import java.security.AccessController; -import java.security.Principal; import java.text.MessageFormat; -import java.util.Set; /** * Management actor to use in {@link MBeanInvocationHandlerImpl} to log all management operational logging. */ -public class ManagementActor extends AbstractActor +public class ManagementActor extends AbstractManagementActor { - /** - * Holds the principal name to display when principal subject is not available. - * <p> - * This is useful for cases when users invoke JMX operation over JConsole - * attached to the local JVM. - */ - private static final String UNKNOWN_PRINCIPAL = "N/A"; - private String _lastThreadName = null; /** - * LOG FORMAT for the ManagementActor, - * Uses a MessageFormat call to insert the required values according to - * these indices: - * - * 0 - User ID - * 1 - IP - */ - public static final String MANAGEMENT_FORMAT = "mng:{0}({1})"; - - /** * The logString to be used for logging */ private String _logStringContainingPrincipal; - /** used when the principal name cannot be discovered from the Subject */ - private final String _fallbackPrincipalName; - /** @param rootLogger The RootLogger to use for this Actor */ public ManagementActor(RootMessageLogger rootLogger) { - super(rootLogger); - _fallbackPrincipalName = UNKNOWN_PRINCIPAL; + super(rootLogger, UNKNOWN_PRINCIPAL); } public ManagementActor(RootMessageLogger rootLogger, String principalName) { - super(rootLogger); - _fallbackPrincipalName = principalName; + super(rootLogger, principalName); } private synchronized String getAndCacheLogString() @@ -96,7 +69,7 @@ public class ManagementActor extends AbstractActor if (split.length == 2) { String ip = currentName.split("-")[1]; - actor = MessageFormat.format(MANAGEMENT_FORMAT, principalName, ip); + actor = MessageFormat.format(LogSubjectFormat.MANAGEMENT_FORMAT, principalName, ip); } else { @@ -119,33 +92,8 @@ public class ManagementActor extends AbstractActor return logString; } - /** - * Returns current JMX principal name. - * - * @return principal name or null if principal can not be found - */ - private String getPrincipalName() - { - String identity = _fallbackPrincipalName; - - // retrieve Subject from current AccessControlContext - final Subject subject = Subject.getSubject(AccessController.getContext()); - if (subject != null) - { - // retrieve JMXPrincipal from Subject - final Set<JMXPrincipal> principals = subject.getPrincipals(JMXPrincipal.class); - if (principals != null && !principals.isEmpty()) - { - final Principal principal = principals.iterator().next(); - identity = principal.getName(); - } - } - return identity; - } - public String getLogMessage() { return getAndCacheLogString(); } - } diff --git a/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/LogSubjectFormat.java b/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/LogSubjectFormat.java index 28c4f0d52a..7611ee1a88 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/LogSubjectFormat.java +++ b/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/LogSubjectFormat.java @@ -32,11 +32,19 @@ package org.apache.qpid.server.logging.subjects; public class LogSubjectFormat { + private LogSubjectFormat() { } /** + * LOG FORMAT for the ManagementActors, + * 0 - User ID + * 1 - IP[:Port] + */ + public static final String MANAGEMENT_FORMAT = "mng:{0}({1})"; + + /** * LOG FORMAT for the Subscription Log Subject * 0 - Subscription ID */ diff --git a/java/broker/src/main/java/org/apache/qpid/server/model/Group.java b/java/broker/src/main/java/org/apache/qpid/server/model/Group.java new file mode 100644 index 0000000000..aacd515107 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/model/Group.java @@ -0,0 +1,52 @@ +/* + * 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.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +public interface Group extends ConfiguredObject +{ + String CREATED = "created"; + String DURABLE = "durable"; + String ID = "id"; + String LIFETIME_POLICY = "lifetimePolicy"; + String NAME = "name"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + String UPDATED = "updated"; + + // Attributes + public static final Collection<String> AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList( + ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED + )); + + +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/model/GroupMember.java b/java/broker/src/main/java/org/apache/qpid/server/model/GroupMember.java new file mode 100644 index 0000000000..6832cc6fa6 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/model/GroupMember.java @@ -0,0 +1,52 @@ +/* + * 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.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +public interface GroupMember extends ConfiguredObject +{ + String CREATED = "created"; + String DURABLE = "durable"; + String ID = "id"; + String LIFETIME_POLICY = "lifetimePolicy"; + String NAME = "name"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + String UPDATED = "updated"; + + // Attributes + public static final Collection<String> AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList( + ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED + )); + + +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/model/GroupProvider.java b/java/broker/src/main/java/org/apache/qpid/server/model/GroupProvider.java new file mode 100644 index 0000000000..76fa379c1b --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/model/GroupProvider.java @@ -0,0 +1,51 @@ +/* + * 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.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +public interface GroupProvider extends ConfiguredObject +{ + public static final String ID = "id"; + public static final String DESCRIPTION = "description"; + public static final String NAME = "name"; + public static final String STATE = "state"; + public static final String DURABLE = "durable"; + public static final String LIFETIME_POLICY = "lifetimePolicy"; + public static final String TIME_TO_LIVE = "timeToLive"; + public static final String CREATED = "created"; + public static final String UPDATED = "updated"; + public static final String TYPE = "type"; + + public static final Collection<String> AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList(ID, + NAME, + DESCRIPTION, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + TYPE)); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/model/Model.java b/java/broker/src/main/java/org/apache/qpid/server/model/Model.java index 36179fc105..329574f017 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/model/Model.java +++ b/java/broker/src/main/java/org/apache/qpid/server/model/Model.java @@ -47,6 +47,7 @@ public class Model addRelationship(Broker.class, VirtualHost.class); addRelationship(Broker.class, Port.class); addRelationship(Broker.class, AuthenticationProvider.class); + addRelationship(Broker.class, GroupProvider.class); addRelationship(VirtualHost.class, Exchange.class); addRelationship(VirtualHost.class, Queue.class); @@ -54,6 +55,10 @@ public class Model addRelationship(VirtualHost.class, VirtualHostAlias.class); addRelationship(AuthenticationProvider.class, User.class); + addRelationship(User.class, GroupMember.class); + + addRelationship(GroupProvider.class, Group.class); + addRelationship(Group.class, GroupMember.class); addRelationship(Connection.class, Session.class); diff --git a/java/broker/src/main/java/org/apache/qpid/server/model/UUIDGenerator.java b/java/broker/src/main/java/org/apache/qpid/server/model/UUIDGenerator.java index 36b6a454dc..6606944dc5 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/model/UUIDGenerator.java +++ b/java/broker/src/main/java/org/apache/qpid/server/model/UUIDGenerator.java @@ -63,6 +63,11 @@ public class UUIDGenerator return createUUID(User.class.getName(), authenticationProviderName, userName); } + public static UUID generateGroupUUID(String groupProviderName, String groupName) + { + return createUUID(Group.class.getName(), groupProviderName, groupName); + } + public static UUID generateVhostUUID(String virtualHostName) { return createUUID(VirtualHost.class.getName(), virtualHostName); @@ -77,4 +82,9 @@ public class UUIDGenerator { return createUUID(Consumer.class.getName(), virtualHostName, queueName, connectionRemoteAddress, channelNumber, consumerName); } + + public static UUID generateGroupMemberUUID(String groupProviderName, String groupName, String groupMemberName) + { + return createUUID(GroupMember.class.getName(), groupProviderName, groupName, groupMemberName); + } } diff --git a/java/broker/src/main/java/org/apache/qpid/server/model/User.java b/java/broker/src/main/java/org/apache/qpid/server/model/User.java index d97bf46d31..675dc8f0d3 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/model/User.java +++ b/java/broker/src/main/java/org/apache/qpid/server/model/User.java @@ -52,8 +52,6 @@ public interface User extends ConfiguredObject PASSWORD )); - public String getPassword(); - public void setPassword(String password); } diff --git a/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java b/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java index 8c2bc98ba7..adad2a355c 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java +++ b/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java @@ -38,7 +38,7 @@ import org.apache.qpid.server.security.access.Operation; import org.apache.qpid.server.security.auth.database.PrincipalDatabase; import org.apache.qpid.server.security.auth.manager.AuthenticationManager; import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; -import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.UsernamePrincipal; public abstract class AuthenticationProviderAdapter<T extends AuthenticationManager> extends AbstractAdapter implements AuthenticationProvider { @@ -147,7 +147,7 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana { if(TYPE.equals(name)) { - return _authManager.getClass().getSimpleName(); + return getName(); } else if(CREATED.equals(name)) { @@ -195,7 +195,8 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana Map<String, Object> attributes, ConfiguredObject... otherParents) { - return null; + throw new IllegalArgumentException("This authentication provider does not support" + + " creating children of type: " + childClass); } private static class SimpleAuthenticationProviderAdapter extends AuthenticationProviderAdapter<AuthenticationManager> @@ -220,15 +221,20 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana @Override public boolean createUser(String username, String password, Map<String, String> attributes) { - return getPrincipalDatabase().createPrincipal(new UsernamePrincipal(username), password.toCharArray()); + if(getSecurityManager().authoriseUserOperation(Operation.CREATE, username)) + { + return getPrincipalDatabase().createPrincipal(new UsernamePrincipal(username), password.toCharArray()); + } + else + { + throw new AccessControlException("Do not have permission to create new user"); + } } @Override public void deleteUser(String username) throws AccountNotFoundException { - if(getSecurityManager().authoriseMethod(Operation.DELETE, - "UserManagement", - "deleteUser")) + if(getSecurityManager().authoriseUserOperation(Operation.DELETE, username)) { getPrincipalDatabase().deletePrincipal(new UsernamePrincipal(username)); @@ -252,18 +258,13 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana @Override public void setPassword(String username, String password) throws AccountNotFoundException { - getPrincipalDatabase().updatePassword(new UsernamePrincipal(username), password.toCharArray()); - } - - public void reload() throws IOException - { - if(getSecurityManager().authoriseMethod(Operation.UPDATE, "UserManagement", "reload")) + if(getSecurityManager().authoriseUserOperation(Operation.UPDATE, username)) { - getPrincipalDatabase().reload(); + getPrincipalDatabase().updatePassword(new UsernamePrincipal(username), password.toCharArray()); } else { - throw new AccessControlException("Do not have permission to reload principal database"); + throw new AccessControlException("Do not have permission to set password"); } } @@ -279,6 +280,11 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana return users; } + public void reload() throws IOException + { + getPrincipalDatabase().reload(); + } + @Override public <C extends ConfiguredObject> C createChild(Class<C> childClass, Map<String, Object> attributes, @@ -286,19 +292,19 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana { if(childClass == User.class) { - Principal p = new UsernamePrincipal((String) attributes.get("name")); - if(getSecurityManager().authoriseMethod(Operation.UPDATE, "UserManagement", "createUser")) + String username = (String) attributes.get("name"); + String password = (String) attributes.get("password"); + Principal p = new UsernamePrincipal(username); + + if(createUser(username, password,null)) { - if(getPrincipalDatabase().createPrincipal(p, ((String)attributes.get("password")).toCharArray())) - { - return (C) new PrincipalAdapter(p); - } + return (C) new PrincipalAdapter(p); } else { - throw new AccessControlException("Do not have permission to create a new user"); + //TODO? Silly interface on the PrincipalDatabase at fault + throw new RuntimeException("Failed to create user"); } - } return super.createChild(childClass, attributes, otherParents); @@ -336,12 +342,6 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana } @Override - public String getPassword() - { - return null; - } - - @Override public void setPassword(String password) { try @@ -445,6 +445,10 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana { return getId(); } + else if(PASSWORD.equals(name)) + { + return null; // for security reasons we don't expose the password + } else if(NAME.equals(name)) { return getName(); diff --git a/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java b/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java index f1cce2d45c..276dc83540 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java +++ b/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java @@ -32,6 +32,7 @@ import org.apache.qpid.common.QpidProperties; import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.GroupProvider; import org.apache.qpid.server.model.LifetimePolicy; import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.Protocol; @@ -43,12 +44,14 @@ import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.registry.IApplicationRegistry; import org.apache.qpid.server.security.auth.manager.AuthenticationManager; import org.apache.qpid.server.security.auth.manager.IAuthenticationManagerRegistry; +import org.apache.qpid.server.security.group.GroupManager; import org.apache.qpid.server.transport.QpidAcceptor; import org.apache.qpid.server.virtualhost.VirtualHostRegistry; public class BrokerAdapter extends AbstractAdapter implements Broker, VirtualHostRegistry.RegistryChangeListener, IApplicationRegistry.PortBindingListener, - IAuthenticationManagerRegistry.RegistryChangeListener + IAuthenticationManagerRegistry.RegistryChangeListener, + IApplicationRegistry.GroupManagerChangeListener { @@ -62,6 +65,8 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, VirtualHos private final Map<AuthenticationManager, AuthenticationProviderAdapter> _authManagerAdapters = new HashMap<AuthenticationManager, AuthenticationProviderAdapter>(); + private final Map<GroupManager, GroupProviderAdapter> _groupManagerAdapters = + new HashMap<GroupManager, GroupProviderAdapter>(); public BrokerAdapter(final IApplicationRegistry instance) @@ -75,8 +80,10 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, VirtualHos populateVhosts(); instance.addPortBindingListener(this); populatePorts(); - instance.addRegistryChangeListener(this); + instance.addAuthenticationManagerRegistryChangeListener(this); populateAuthenticationManagers(); + instance.addGroupManagerChangeListener(this); + populateGroupManagers(); } private void populateVhosts() @@ -171,6 +178,25 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, VirtualHos } } + private void populateGroupManagers() + { + synchronized (_groupManagerAdapters) + { + List<GroupManager> groupManagers = _applicationRegistry.getGroupManagers(); + if(groupManagers != null) + { + for (GroupManager groupManager : groupManagers) + { + if(!_groupManagerAdapters.containsKey(groupManager)) + { + _groupManagerAdapters.put(groupManager, + GroupProviderAdapter.createGroupProviderAdapter(this, groupManager)); + } + } + } + } + } + public Collection<AuthenticationProvider> getAuthenticationProviders() { synchronized (_authManagerAdapters) @@ -179,7 +205,16 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, VirtualHos new ArrayList<AuthenticationProvider>(_authManagerAdapters.values()); return authManagers; } + } + public Collection<GroupProvider> getGroupProviders() + { + synchronized (_groupManagerAdapters) + { + final ArrayList<GroupProvider> groupManagers = + new ArrayList<GroupProvider>(_groupManagerAdapters.values()); + return groupManagers; + } } public VirtualHost createVirtualHost(final String name, @@ -277,6 +312,10 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, VirtualHos { return (Collection<C>) getAuthenticationProviders(); } + else if(clazz == GroupProvider.class) + { + return (Collection<C>) getGroupProviders(); + } return Collections.emptySet(); } @@ -491,4 +530,36 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, VirtualHos { return super.setAttribute(name, expected, desired); //TODO - Implement. } + + @Override + public void groupManagerRegistered(GroupManager groupManager) + { + GroupProviderAdapter adapter = null; + synchronized (_groupManagerAdapters) + { + if(!_groupManagerAdapters.containsKey(groupManager)) + { + adapter = GroupProviderAdapter.createGroupProviderAdapter(this, groupManager); + _groupManagerAdapters.put(groupManager, adapter); + } + } + if(adapter != null) + { + childAdded(adapter); + } + } + + @Override + public void groupManagerUnregistered(GroupManager groupManager) + { + GroupProviderAdapter adapter; + synchronized (_groupManagerAdapters) + { + adapter = _groupManagerAdapters.remove(groupManager); + } + if(adapter != null) + { + childRemoved(adapter); + } + } } diff --git a/java/broker/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderAdapter.java b/java/broker/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderAdapter.java new file mode 100644 index 0000000000..adc98919d5 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderAdapter.java @@ -0,0 +1,548 @@ +/* + * 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.model.adapter; + +import java.security.AccessControlException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Group; +import org.apache.qpid.server.model.GroupMember; +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.model.IllegalStateTransitionException; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.security.group.GroupManager; + +public class GroupProviderAdapter extends AbstractAdapter implements + GroupProvider +{ + private final GroupManager _groupManager; + + protected GroupProviderAdapter(GroupManager groupManager) + { + super(UUIDGenerator.generateRandomUUID()); + + if (groupManager == null) + { + throw new IllegalArgumentException("GroupManager must not be null"); + } + _groupManager = groupManager; + } + + public static GroupProviderAdapter createGroupProviderAdapter( + BrokerAdapter brokerAdapter, GroupManager groupManager) + { + final GroupProviderAdapter groupProviderAdapter = new GroupProviderAdapter( + groupManager); + groupProviderAdapter.addParent(Broker.class, brokerAdapter); + return groupProviderAdapter; + } + + @Override + public String getName() + { + return _groupManager.getClass().getSimpleName(); + } + + @Override + public String setName(String currentName, String desiredName) + throws IllegalStateException, AccessControlException + { + return null; + } + + @Override + public State getActualState() + { + return null; + } + + @Override + public boolean isDurable() + { + return true; + } + + @Override + public void setDurable(boolean durable) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, + LifetimePolicy desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + return null; + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) + throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + return 0; + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public Collection<String> getAttributeNames() + { + return GroupProvider.AVAILABLE_ATTRIBUTES; + } + + @Override + public Object getAttribute(String name) + { + if (TYPE.equals(name)) + { + return getName(); + } + else if (CREATED.equals(name)) + { + // TODO + } + else if (DURABLE.equals(name)) + { + return true; + } + else if (ID.equals(name)) + { + return getId(); + } + else if (LIFETIME_POLICY.equals(name)) + { + return LifetimePolicy.PERMANENT; + } + else if (NAME.equals(name)) + { + return getName(); + } + else if (STATE.equals(name)) + { + return State.ACTIVE; // TODO + } + else if (TIME_TO_LIVE.equals(name)) + { + // TODO + } + else if (UPDATED.equals(name)) + { + // TODO + } + return super.getAttribute(name); + } + + @Override + public <C extends ConfiguredObject> C createChild(Class<C> childClass, + Map<String, Object> attributes, ConfiguredObject... otherParents) + { + if (childClass == Group.class) + { + String groupName = (String) attributes.get(Group.NAME); + + if (getSecurityManager().authoriseGroupOperation(Operation.CREATE, groupName)) + { + _groupManager.createGroup(groupName); + return (C) new GroupAdapter(groupName); + } + else + { + throw new AccessControlException("Do not have permission" + + " to create new group"); + } + } + + throw new IllegalArgumentException( + "This group provider does not support creating children of type: " + + childClass); + } + + @SuppressWarnings("unchecked") + @Override + public <C extends ConfiguredObject> Collection<C> getChildren(Class<C> clazz) + { + if (clazz == Group.class) + { + Set<Principal> groups = _groupManager.getGroupPrincipals(); + Collection<Group> principals = new ArrayList<Group>(groups.size()); + for (Principal group : groups) + { + principals.add(new GroupAdapter(group.getName())); + } + return (Collection<C>) Collections + .unmodifiableCollection(principals); + } + else + { + return null; + } + } + + private org.apache.qpid.server.security.SecurityManager getSecurityManager() + { + return ApplicationRegistry.getInstance().getSecurityManager(); + } + + private class GroupAdapter extends AbstractAdapter implements Group + { + private final String _group; + + public GroupAdapter(String group) + { + super(UUIDGenerator.generateGroupUUID( + GroupProviderAdapter.this.getName(), group)); + _group = group; + + } + + @Override + public String getName() + { + return _group; + } + + @Override + public String setName(String currentName, String desiredName) + throws IllegalStateException, AccessControlException + { + throw new IllegalStateException("Names cannot be updated"); + } + + @Override + public State getActualState() + { + return State.ACTIVE; + } + + @Override + public boolean isDurable() + { + return true; + } + + @Override + public void setDurable(boolean durable) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new IllegalStateException("Durability cannot be updated"); + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, + LifetimePolicy desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new IllegalStateException("LifetimePolicy cannot be updated"); + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) + throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + throw new IllegalStateException("ttl cannot be updated"); + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public <C extends ConfiguredObject> Collection<C> getChildren( + Class<C> clazz) + { + if (clazz == GroupMember.class) + { + Set<Principal> usersInGroup = _groupManager + .getUserPrincipalsForGroup(_group); + Collection<GroupMember> members = new ArrayList<GroupMember>(); + for (Principal principal : usersInGroup) + { + members.add(new GroupMemberAdapter(principal.getName())); + } + return (Collection<C>) Collections + .unmodifiableCollection(members); + } + else + { + return null; + } + + } + + @Override + public <C extends ConfiguredObject> C createChild(Class<C> childClass, + Map<String, Object> attributes, + ConfiguredObject... otherParents) + { + if (childClass == GroupMember.class) + { + String memberName = (String) attributes.get(GroupMember.NAME); + + if (getSecurityManager().authoriseGroupOperation(Operation.UPDATE, _group)) + { + _groupManager.addUserToGroup(memberName, _group); + return (C) new GroupMemberAdapter(memberName); + } + else + { + throw new AccessControlException("Do not have permission" + + " to add new group member"); + } + } + + throw new IllegalArgumentException( + "This group provider does not support creating children of type: " + + childClass); + } + + @Override + public Collection<String> getAttributeNames() + { + return Group.AVAILABLE_ATTRIBUTES; + } + + @Override + public Object getAttribute(String name) + { + if (ID.equals(name)) + { + return getId(); + } + else if (NAME.equals(name)) + { + return getName(); + } + return super.getAttribute(name); + } + + @Override + public Object setAttribute(String name, Object expected, Object desired) + throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + return super.setAttribute(name, expected, desired); + } + + @Override + public State setDesiredState(State currentState, State desiredState) + throws IllegalStateTransitionException, AccessControlException + { + if (desiredState == State.DELETED) + { + if (getSecurityManager().authoriseGroupOperation(Operation.DELETE, _group)) + { + _groupManager.removeGroup(_group); + return State.DELETED; + } + else + { + throw new AccessControlException("Do not have permission" + + " to delete group"); + } + } + + return super.setDesiredState(currentState, desiredState); + } + + private class GroupMemberAdapter extends AbstractAdapter implements + GroupMember + { + private String _memberName; + + public GroupMemberAdapter(String memberName) + { + super(UUIDGenerator + .generateGroupMemberUUID( + GroupProviderAdapter.this.getName(), _group, + memberName)); + _memberName = memberName; + } + + @Override + public Collection<String> getAttributeNames() + { + return GroupMember.AVAILABLE_ATTRIBUTES; + } + + @Override + public Object getAttribute(String name) + { + if (ID.equals(name)) + { + return getId(); + } + else if (NAME.equals(name)) + { + return getName(); + } + return super.getAttribute(name); + } + + @Override + public String getName() + { + return _memberName; + } + + @Override + public String setName(String currentName, String desiredName) + throws IllegalStateException, AccessControlException + { + return null; + } + + @Override + public State getActualState() + { + return null; + } + + @Override + public boolean isDurable() + { + return false; + } + + @Override + public void setDurable(boolean durable) + throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return null; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, + LifetimePolicy desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + return null; + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) + throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + return 0; + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public <C extends ConfiguredObject> Collection<C> getChildren( + Class<C> clazz) + { + return null; + } + + @Override + public <C extends ConfiguredObject> C createChild( + Class<C> childClass, Map<String, Object> attributes, + ConfiguredObject... otherParents) + { + return null; + } + + @Override + public State setDesiredState(State currentState, State desiredState) + throws IllegalStateTransitionException, + AccessControlException + { + if (desiredState == State.DELETED) + { + if (getSecurityManager().authoriseGroupOperation(Operation.UPDATE, _group)) + { + _groupManager.removeUserFromGroup(_memberName, _group); + return State.DELETED; + } + else + { + throw new AccessControlException("Do not have permission" + + " to remove group member"); + } + } + + return super.setDesiredState(currentState, desiredState); + } + + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java b/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java index 74abbccd2b..ff38d5ec58 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java +++ b/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java @@ -49,6 +49,8 @@ import org.apache.qpid.server.security.auth.manager.ExternalAuthenticationManage import org.apache.qpid.server.security.auth.manager.KerberosAuthenticationManager; import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; import org.apache.qpid.server.security.auth.manager.SimpleLDAPAuthenticationManager; +import org.apache.qpid.server.security.group.FileGroupManager; +import org.apache.qpid.server.security.group.GroupManagerPluginFactory; import org.apache.qpid.server.virtualhost.plugins.SlowConsumerDetection; import org.apache.qpid.server.virtualhost.plugins.VirtualHostPluginFactory; import org.apache.qpid.server.virtualhost.plugins.policies.TopicDeletePolicy; @@ -90,6 +92,7 @@ public class PluginManager implements Closeable private ServiceTracker _virtualHostTracker = null; private ServiceTracker _policyTracker = null; private ServiceTracker _authenticationManagerTracker = null; + private ServiceTracker _groupManagerTracker = null; private Activator _activator; @@ -99,6 +102,7 @@ public class PluginManager implements Closeable private Map<String, VirtualHostPluginFactory> _vhostPlugins = new HashMap<String, VirtualHostPluginFactory>(); private Map<String, SlowConsumerPolicyPluginFactory> _policyPlugins = new HashMap<String, SlowConsumerPolicyPluginFactory>(); private Map<String, AuthenticationManagerPluginFactory<? extends Plugin>> _authenticationManagerPlugins = new HashMap<String, AuthenticationManagerPluginFactory<? extends Plugin>>(); + private Map<String, GroupManagerPluginFactory<? extends Plugin>> _groupManagerPlugins = new HashMap<String, GroupManagerPluginFactory<? extends Plugin>>(); /** The default name of the OSGI system package list. */ private static final String DEFAULT_RESOURCE_NAME = "org/apache/qpid/server/plugins/OsgiSystemPackages.properties"; @@ -162,7 +166,8 @@ public class PluginManager implements Closeable AnonymousAuthenticationManager.AnonymousAuthenticationManagerConfiguration.FACTORY, KerberosAuthenticationManager.KerberosAuthenticationManagerConfiguration.FACTORY, SimpleLDAPAuthenticationManager.SimpleLDAPAuthenticationManagerConfiguration.FACTORY, - ExternalAuthenticationManager.ExternalAuthenticationManagerConfiguration.FACTORY + ExternalAuthenticationManager.ExternalAuthenticationManagerConfiguration.FACTORY, + FileGroupManager.FileGroupManagerConfiguration.FACTORY )) { _configPlugins.put(configFactory.getParentPaths(), configFactory); @@ -186,6 +191,12 @@ public class PluginManager implements Closeable _authenticationManagerPlugins.put(pluginFactory.getPluginName(), pluginFactory); } + for (GroupManagerPluginFactory<? extends Plugin> pluginFactory : Arrays.asList( + FileGroupManager.FACTORY)) + { + _groupManagerPlugins.put(pluginFactory.getPluginName(), pluginFactory); + } + if(bundleContext == null) { // Check the plugin directory path is set and exist @@ -283,6 +294,11 @@ public class PluginManager implements Closeable _authenticationManagerTracker.open(); _trackers.add(_authenticationManagerTracker); + _groupManagerTracker = new ServiceTracker(bundleContext, GroupManagerPluginFactory.class.getName(), null); + _groupManagerTracker.open(); + _trackers.add(_groupManagerTracker); + + _logger.info("Opened service trackers"); } @@ -358,6 +374,11 @@ public class PluginManager implements Closeable return getServices(_authenticationManagerTracker, _authenticationManagerPlugins); } + public Map<String, GroupManagerPluginFactory<? extends Plugin>> getGroupManagerPlugins() + { + return getServices(_groupManagerTracker, _groupManagerPlugins); + } + public void close() { try diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolEngine.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolEngine.java index 1e649c3cb7..aac7562be2 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolEngine.java +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolEngine.java @@ -68,6 +68,7 @@ import org.apache.qpid.server.output.ProtocolOutputConverter; import org.apache.qpid.server.output.ProtocolOutputConverterRegistry; import org.apache.qpid.server.queue.QueueEntry; import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; import org.apache.qpid.server.state.AMQState; import org.apache.qpid.server.state.AMQStateManager; import org.apache.qpid.server.stats.StatisticsCounter; @@ -367,7 +368,7 @@ public class AMQProtocolEngine implements ServerProtocolEngine, AMQProtocolSessi // This sets the protocol version (and hence framing classes) for this session. setProtocolVersion(pv); - String mechanisms = ApplicationRegistry.getInstance().getAuthenticationManager(getLocalAddress()).getMechanisms(); + String mechanisms = ApplicationRegistry.getInstance().getSubjectCreator(getLocalAddress()).getMechanisms(); String locales = "en_US"; @@ -1017,7 +1018,7 @@ public class AMQProtocolEngine implements ServerProtocolEngine, AMQProtocolSessi public Principal getAuthorizedPrincipal() { - return _authorizedSubject == null ? null : _authorizedSubject.getPrincipals().iterator().next(); + return _authorizedSubject == null ? null : _authorizedSubject.getPrincipals(AuthenticatedPrincipal.class).iterator().next(); } public SocketAddress getRemoteAddress() diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngine.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngine.java index 5c92aa95b6..152f591e66 100755 --- a/java/broker/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngine.java +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngine.java @@ -302,7 +302,7 @@ public class MultiVersionProtocolEngine implements ServerProtocolEngine public ServerProtocolEngine getProtocolEngine() { final ConnectionDelegate connDelegate = - new org.apache.qpid.server.transport.ServerConnectionDelegate(_appRegistry, _fqdn, _appRegistry.getAuthenticationManager(getLocalAddress())); + new org.apache.qpid.server.transport.ServerConnectionDelegate(_appRegistry, _fqdn, _appRegistry.getSubjectCreator(getLocalAddress())); ServerConnection conn = new ServerConnection(_id); conn.setConnectionDelegate(connDelegate); diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/ProtocolEngine_1_0_0.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/ProtocolEngine_1_0_0.java index e6282315c6..ba5348c0ed 100755 --- a/java/broker/src/main/java/org/apache/qpid/server/protocol/ProtocolEngine_1_0_0.java +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/ProtocolEngine_1_0_0.java @@ -43,6 +43,7 @@ import org.apache.qpid.server.configuration.ConfigStore; import org.apache.qpid.server.configuration.ConnectionConfigType; import org.apache.qpid.server.protocol.v1_0.Connection_1_0; import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.server.security.auth.manager.AuthenticationManager; import org.apache.qpid.transport.Sender; import org.apache.qpid.transport.network.NetworkConnection; @@ -144,7 +145,7 @@ public class ProtocolEngine_1_0_0 implements ServerProtocolEngine, FrameOutputHa Container container = new Container(_appRegistry.getBrokerId().toString()); - _conn = new ConnectionEndpoint(container, asSaslServerProvider(_appRegistry.getAuthenticationManager( + _conn = new ConnectionEndpoint(container, asSaslServerProvider(_appRegistry.getSubjectCreator( getLocalAddress()))); _conn.setConnectionEventListener(new Connection_1_0(_appRegistry, _conn, _connectionId)); _conn.setFrameOutputHandler(this); @@ -157,14 +158,14 @@ public class ProtocolEngine_1_0_0 implements ServerProtocolEngine, FrameOutputHa _sender.flush(); } - private SaslServerProvider asSaslServerProvider(final AuthenticationManager authenticationManager) + private SaslServerProvider asSaslServerProvider(final SubjectCreator subjectCreator) { return new SaslServerProvider() { @Override public SaslServer getSaslServer(String mechanism, String fqdn) throws SaslException { - return authenticationManager.createSaslServer(mechanism, fqdn, null); + return subjectCreator.createSaslServer(mechanism, fqdn, null); } }; } diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/ProtocolEngine_1_0_0_SASL.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/ProtocolEngine_1_0_0_SASL.java index a48441bf30..b80080b991 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/protocol/ProtocolEngine_1_0_0_SASL.java +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/ProtocolEngine_1_0_0_SASL.java @@ -45,7 +45,7 @@ import org.apache.qpid.server.configuration.ConnectionConfigType; import org.apache.qpid.server.protocol.v1_0.Connection_1_0; import org.apache.qpid.server.registry.ApplicationRegistry; import org.apache.qpid.server.registry.IApplicationRegistry; -import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.transport.Sender; import org.apache.qpid.transport.network.NetworkConnection; @@ -165,7 +165,7 @@ public class ProtocolEngine_1_0_0_SASL implements ServerProtocolEngine, FrameOut Container container = new Container(_appRegistry.getBrokerId().toString()); _conn = new ConnectionEndpoint(container, asSaslServerProvider(ApplicationRegistry.getInstance() - .getAuthenticationManager(getLocalAddress()))); + .getSubjectCreator(getLocalAddress()))); _conn.setConnectionEventListener(new Connection_1_0(_appRegistry, _conn, _connectionId)); _conn.setRemoteAddress(getRemoteAddress()); @@ -175,8 +175,6 @@ public class ProtocolEngine_1_0_0_SASL implements ServerProtocolEngine, FrameOut _conn.setOnSaslComplete(new Runnable() { - - public void run() { if(_conn.isAuthenticated()) @@ -201,14 +199,14 @@ public class ProtocolEngine_1_0_0_SASL implements ServerProtocolEngine, FrameOut } - private SaslServerProvider asSaslServerProvider(final AuthenticationManager authenticationManager) + private SaslServerProvider asSaslServerProvider(final SubjectCreator subjectCreator) { return new SaslServerProvider() { @Override public SaslServer getSaslServer(String mechanism, String fqdn) throws SaslException { - return authenticationManager.createSaslServer(mechanism, fqdn, null); + return subjectCreator.createSaslServer(mechanism, fqdn, null); } }; } diff --git a/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java index e0e317f75d..f31275fa36 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java +++ b/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java @@ -42,11 +42,17 @@ import org.apache.qpid.server.logging.messages.BrokerMessages; import org.apache.qpid.server.logging.messages.VirtualHostMessages; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.adapter.BrokerAdapter; +import org.apache.qpid.server.plugins.Plugin; import org.apache.qpid.server.plugins.PluginManager; import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.SecurityManager.SecurityConfiguration; import org.apache.qpid.server.security.auth.manager.AuthenticationManager; import org.apache.qpid.server.security.auth.manager.AuthenticationManagerRegistry; import org.apache.qpid.server.security.auth.manager.IAuthenticationManagerRegistry; +import org.apache.qpid.server.security.group.GroupManager; +import org.apache.qpid.server.security.group.GroupManagerPluginFactory; +import org.apache.qpid.server.security.group.GroupPrincipalAccessor; import org.apache.qpid.server.stats.StatisticsCounter; import org.apache.qpid.server.transport.QpidAcceptor; import org.apache.qpid.server.virtualhost.VirtualHost; @@ -87,7 +93,7 @@ public abstract class ApplicationRegistry implements IApplicationRegistry private ConfigurationManager _configurationManager; - private RootMessageLogger _rootMessageLogger; + private volatile RootMessageLogger _rootMessageLogger; private CompositeStartupMessageLogger _startupMessageLogger; @@ -115,6 +121,11 @@ public abstract class ApplicationRegistry implements IApplicationRegistry private List<IAuthenticationManagerRegistry.RegistryChangeListener> _authManagerChangeListeners = new ArrayList<IAuthenticationManagerRegistry.RegistryChangeListener>(); + private List<GroupManagerChangeListener> _groupManagerChangeListeners = + new ArrayList<GroupManagerChangeListener>(); + + private List<GroupManager> _groupManagerList = new ArrayList<GroupManager>(); + public Map<InetSocketAddress, QpidAcceptor> getAcceptors() { synchronized (_acceptors) @@ -314,7 +325,25 @@ public abstract class ApplicationRegistry implements IApplicationRegistry _securityManager = new SecurityManager(_configuration, _pluginManager); - _authenticationManagerRegistry = createAuthenticationManagerRegistry(_configuration, _pluginManager); + final Collection<GroupManagerPluginFactory<? extends Plugin>> factories = _pluginManager.getGroupManagerPlugins().values(); + final SecurityConfiguration securityConfiguration = _configuration.getConfiguration(SecurityConfiguration.class.getName()); + + for(GroupManagerPluginFactory<? extends Plugin> factory : factories) + { + final GroupManager groupManager = factory.newInstance(securityConfiguration); + if(groupManager != null) + { + _groupManagerList.add(groupManager); + + for(GroupManagerChangeListener listener : _groupManagerChangeListeners) + { + listener.groupManagerRegistered(groupManager); + } + } + } + _logger.debug("Created " + _groupManagerList.size() + " group manager(s)"); + + _authenticationManagerRegistry = createAuthenticationManagerRegistry(_configuration, _pluginManager, new GroupPrincipalAccessor(_groupManagerList)); if(!_authManagerChangeListeners.isEmpty()) { @@ -348,10 +377,10 @@ public abstract class ApplicationRegistry implements IApplicationRegistry } } - protected IAuthenticationManagerRegistry createAuthenticationManagerRegistry(ServerConfiguration _configuration, PluginManager _pluginManager) + protected IAuthenticationManagerRegistry createAuthenticationManagerRegistry(ServerConfiguration configuration, PluginManager pluginManager, GroupPrincipalAccessor groupManagerList) throws ConfigurationException { - return new AuthenticationManagerRegistry(_configuration, _pluginManager); + return new AuthenticationManagerRegistry(configuration, pluginManager, groupManagerList); } protected void initialiseVirtualHosts() throws Exception @@ -588,9 +617,9 @@ public abstract class ApplicationRegistry implements IApplicationRegistry } @Override - public AuthenticationManager getAuthenticationManager(SocketAddress address) + public SubjectCreator getSubjectCreator(SocketAddress localAddress) { - return _authenticationManagerRegistry.getAuthenticationManager(address); + return _authenticationManagerRegistry.getSubjectCreator(localAddress); } @Override @@ -599,6 +628,12 @@ public abstract class ApplicationRegistry implements IApplicationRegistry return _authenticationManagerRegistry; } + @Override + public List<GroupManager> getGroupManagers() + { + return _groupManagerList; + } + public PluginManager getPluginManager() { return _pluginManager; @@ -758,7 +793,7 @@ public abstract class ApplicationRegistry implements IApplicationRegistry } @Override - public void addRegistryChangeListener(IAuthenticationManagerRegistry.RegistryChangeListener registryChangeListener) + public void addAuthenticationManagerRegistryChangeListener(IAuthenticationManagerRegistry.RegistryChangeListener registryChangeListener) { if(_authenticationManagerRegistry == null) { @@ -769,4 +804,10 @@ public abstract class ApplicationRegistry implements IApplicationRegistry _authenticationManagerRegistry.addRegistryChangeListener(registryChangeListener); } } + + @Override + public void addGroupManagerChangeListener(GroupManagerChangeListener groupManagerChangeListener) + { + _groupManagerChangeListeners.add(groupManagerChangeListener); + } } diff --git a/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java index 88c3c93156..5959b6fbe2 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java +++ b/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java @@ -30,8 +30,10 @@ import org.apache.qpid.server.logging.RootMessageLogger; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.plugins.PluginManager; import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.server.security.auth.manager.AuthenticationManager; import org.apache.qpid.server.security.auth.manager.IAuthenticationManagerRegistry; +import org.apache.qpid.server.security.group.GroupManager; import org.apache.qpid.server.stats.StatisticsGatherer; import org.apache.qpid.server.transport.QpidAcceptor; import org.apache.qpid.server.virtualhost.VirtualHost; @@ -39,6 +41,7 @@ import org.apache.qpid.server.virtualhost.VirtualHostRegistry; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -64,18 +67,16 @@ public interface IApplicationRegistry extends StatisticsGatherer ServerConfiguration getConfiguration(); /** - * Get the AuthenticationManager for the given socket address - * - * If no AuthenticationManager has been specifically set for the given address, then use the default - * AuthenticationManager + * Get the SubjectCreator for the given socket address. * * @param address The (listening) socket address for which the AuthenticationManager is required - * @return the AuthenticationManager */ - AuthenticationManager getAuthenticationManager(SocketAddress address); + SubjectCreator getSubjectCreator(SocketAddress localAddress); IAuthenticationManagerRegistry getAuthenticationManagerRegistry(); + List<GroupManager> getGroupManagers(); + VirtualHostRegistry getVirtualHostRegistry(); SecurityManager getSecurityManager(); @@ -123,7 +124,7 @@ public interface IApplicationRegistry extends StatisticsGatherer int getHTTPSManagementPort(); - void addRegistryChangeListener(IAuthenticationManagerRegistry.RegistryChangeListener registryChangeListener); + void addAuthenticationManagerRegistryChangeListener(IAuthenticationManagerRegistry.RegistryChangeListener registryChangeListener); public interface PortBindingListener { @@ -132,4 +133,11 @@ public interface IApplicationRegistry extends StatisticsGatherer } + void addGroupManagerChangeListener(GroupManagerChangeListener groupManagerChangeListener); + + public static interface GroupManagerChangeListener + { + void groupManagerRegistered(GroupManager groupManager); + void groupManagerUnregistered(GroupManager groupManager); + } } diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/AuthorizationHolder.java b/java/broker/src/main/java/org/apache/qpid/server/security/AuthorizationHolder.java index 8f3bdf7738..8243fc3f75 100755 --- a/java/broker/src/main/java/org/apache/qpid/server/security/AuthorizationHolder.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/AuthorizationHolder.java @@ -24,14 +24,14 @@ import javax.security.auth.Subject; import java.security.Principal; /** - * Represents the authorization of the logged on user. - * + * Represents the authorization of the logged on user. + * */ public interface AuthorizationHolder { - /** + /** * Returns the {@link Subject} of the authorized user. This is guaranteed to - * contain at least one {@link org.apache.qpid.server.security.auth.sasl.UsernamePrincipal}, representing the the identity + * contain at least one {@link org.apache.qpid.server.security.auth.UsernamePrincipal}, representing the the identity * used when the user logged on to the application, and zero or more {@link org.apache.qpid.server.security.auth.sasl.GroupPrincipal} * representing the group(s) to which the user belongs. * @@ -39,10 +39,10 @@ public interface AuthorizationHolder */ Subject getAuthorizedSubject(); - /** + /** * Returns the {@link Principal} representing the the identity * used when the user logged on to the application. - * + * * @return a Principal */ Principal getAuthorizedPrincipal(); diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java index 436660cfaf..088d120821 100755 --- a/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java @@ -32,8 +32,10 @@ import org.apache.qpid.server.security.access.ObjectProperties; import org.apache.qpid.server.security.access.Operation; 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; 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.CONSUME; @@ -387,6 +389,27 @@ public class SecurityManager }); } + public boolean authoriseGroupOperation(final Operation operation, final String groupName) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + return plugin.authorise(operation, GROUP, new ObjectProperties(groupName)); + } + }); + } + + public boolean authoriseUserOperation(final Operation operation, final String userName) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + return plugin.authorise(operation, USER, new ObjectProperties(userName)); + } + }); + } private ConcurrentHashMap<String, ConcurrentHashMap<String, PublishAccessCheck>> _immediatePublishPropsCache = new ConcurrentHashMap<String, ConcurrentHashMap<String, PublishAccessCheck>>(); diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/SubjectCreator.java b/java/broker/src/main/java/org/apache/qpid/server/security/SubjectCreator.java new file mode 100644 index 0000000000..8138745486 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/SubjectCreator.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; + +import java.security.Principal; + +import javax.security.auth.Subject; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.group.GroupPrincipalAccessor; + +/** + * Creates a {@link Subject} formed by the {@link Principal}'s returned from: + * <ol> + * <li>Authenticating using an {@link AuthenticationManager}</li> + * <li>A {@link GroupPrincipalAccessor}</li> + * </ol> + * + * <p> + * SubjectCreator is a facade to the {@link AuthenticationManager}, and is intended to be + * the single place that {@link Subject}'s are created in the broker. + * </p> + */ +public class SubjectCreator +{ + private AuthenticationManager _authenticationManager; + private GroupPrincipalAccessor _groupAccessor; + + public SubjectCreator(AuthenticationManager authenticationManager, GroupPrincipalAccessor groupAccessor) + { + _authenticationManager = authenticationManager; + _groupAccessor = groupAccessor; + } + + /** + * Gets the known SASL mechanisms + * + * @return SASL mechanism names, space separated. + */ + public String getMechanisms() + { + return _authenticationManager.getMechanisms(); + } + + /** + * @see AuthenticationManager#createSaslServer(String, String, Principal) + */ + public SaslServer createSaslServer(String mechanism, String localFQDN, Principal externalPrincipal) throws SaslException + { + return _authenticationManager.createSaslServer(mechanism, localFQDN, externalPrincipal); + } + + /** + * Authenticates a user using SASL negotiation. + * + * @param server SASL server + * @param response SASL response to process + */ + public SubjectAuthenticationResult authenticate(SaslServer server, byte[] response) + { + AuthenticationResult authenticationResult = _authenticationManager.authenticate(server, response); + if(server.isComplete()) + { + String username = server.getAuthorizationID(); + + return createResultWithGroups(username, authenticationResult); + } + else + { + return new SubjectAuthenticationResult(authenticationResult); + } + } + + /** + * Authenticates a user using their username and password. + */ + public SubjectAuthenticationResult authenticate(String username, String password) + { + final AuthenticationResult authenticationResult = _authenticationManager.authenticate(username, password); + + return createResultWithGroups(username, authenticationResult); + } + + private SubjectAuthenticationResult createResultWithGroups(String username, final AuthenticationResult authenticationResult) + { + if(authenticationResult.getStatus() == AuthenticationStatus.SUCCESS) + { + final Subject authenticationSubject = new Subject(); + + authenticationSubject.getPrincipals().addAll(authenticationResult.getPrincipals()); + authenticationSubject.getPrincipals().addAll(_groupAccessor.getGroupPrincipals(username)); + + authenticationSubject.setReadOnly(); + + return new SubjectAuthenticationResult(authenticationResult, authenticationSubject); + } + else + { + return new SubjectAuthenticationResult(authenticationResult); + } + } + + public Subject createSubjectWithGroups(String username) + { + Subject authenticationSubject = new Subject(); + + authenticationSubject.getPrincipals().add(new AuthenticatedPrincipal(username)); + authenticationSubject.getPrincipals().addAll(_groupAccessor.getGroupPrincipals(username)); + authenticationSubject.setReadOnly(); + + return authenticationSubject; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java b/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java index 90ecd1dd17..043d4909d5 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java @@ -45,8 +45,10 @@ public enum ObjectType EXCHANGE(Operation.ALL, ACCESS, CREATE, DELETE, BIND, UNBIND, PUBLISH), LINK, // Not allowed in the Java broker ROUTE, // Not allowed in the Java broker - METHOD(Operation.ALL, ACCESS, UPDATE); - + METHOD(Operation.ALL, ACCESS, UPDATE), + USER(Operation.ALL, CREATE, DELETE, UPDATE), + GROUP(Operation.ALL, CREATE, DELETE, UPDATE); + private EnumSet<Operation> _actions; private ObjectType() diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipal.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipal.java new file mode 100644 index 0000000000..96360e83e4 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipal.java @@ -0,0 +1,126 @@ +/* + * 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.auth; + +import java.security.Principal; +import java.util.Set; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.auth.UsernamePrincipal; + +/** + * A simple Principal wrapper. Exists to allow us to identify the "primary" principal + * by calling {@link Subject#getPrincipals(Class)}, passing in {@link AuthenticatedPrincipal}.class, + * e.g. when logging. + */ +public final class AuthenticatedPrincipal implements Principal +{ + private final Principal _wrappedPrincipal; + + /** convenience constructor for the common case where we're wrapping a {@link UsernamePrincipal} */ + public AuthenticatedPrincipal(String userPrincipalName) + { + this(new UsernamePrincipal(userPrincipalName)); + } + + public AuthenticatedPrincipal(Principal wrappedPrincipal) + { + if(wrappedPrincipal == null) + { + throw new IllegalArgumentException("Wrapped principal is null"); + } + + _wrappedPrincipal = wrappedPrincipal; + } + + @Override + public String getName() + { + return _wrappedPrincipal.getName(); + } + + @Override + public int hashCode() + { + return _wrappedPrincipal.hashCode(); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + + if (!(obj instanceof AuthenticatedPrincipal)) + { + return false; + } + + AuthenticatedPrincipal other = (AuthenticatedPrincipal) obj; + + return _wrappedPrincipal.equals(other._wrappedPrincipal); + } + + public static AuthenticatedPrincipal getOptionalAuthenticatedPrincipalFromSubject(final Subject authSubject) + { + return getAuthenticatedPrincipalFromSubject(authSubject, true); + } + + public static AuthenticatedPrincipal getAuthenticatedPrincipalFromSubject(final Subject authSubject) + { + return getAuthenticatedPrincipalFromSubject(authSubject, false); + } + + private static AuthenticatedPrincipal getAuthenticatedPrincipalFromSubject(final Subject authSubject, boolean isPrincipalOptional) + { + if (authSubject == null) + { + throw new IllegalArgumentException("No authenticated subject."); + } + + final Set<AuthenticatedPrincipal> principals = authSubject.getPrincipals(AuthenticatedPrincipal.class); + int numberOfAuthenticatedPrincipals = principals.size(); + + if(numberOfAuthenticatedPrincipals == 0 && isPrincipalOptional) + { + return null; + } + else + { + if (numberOfAuthenticatedPrincipals != 1) + { + throw new IllegalArgumentException( + "Can't find single AuthenticatedPrincipal in authenticated subject. There were " + + numberOfAuthenticatedPrincipals + + " authenticated principals out of a total number of principals of: " + authSubject.getPrincipals()); + } + return principals.iterator().next(); + } + } + + @Override + public String toString() + { + return getName(); + } + +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java index 949c0f2b89..a49dd6793f 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java @@ -7,9 +7,9 @@ * 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 @@ -20,15 +20,20 @@ */ package org.apache.qpid.server.security.auth; -import javax.security.auth.Subject; +import java.security.Principal; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; /** - * Encapsulates the result of an attempt to authenticate. + * Encapsulates the result of an attempt to authenticate using an {@link AuthenticationManager}. * <p> * The authentication status describes the overall outcome. * <p> * <ol> - * <li>If authentication status is SUCCESS, the subject will be populated. + * <li>If authentication status is SUCCESS, at least one {@link Principal} will be populated. * </li> * <li>If authentication status is CONTINUE, the authentication has failed because the user * supplied incorrect credentials (etc). If the authentication requires it, the next challenge @@ -40,6 +45,8 @@ import javax.security.auth.Subject; * </li> * </ol> * + * The main principal provided to the constructor is wrapped in an {@link AuthenticatedPrincipal} + * to make it easier for the rest of the application to identify it among the set of other principals. */ public class AuthenticationResult { @@ -56,37 +63,54 @@ public class AuthenticationResult private final AuthenticationStatus _status; private final byte[] _challenge; private final Exception _cause; - private final Subject _subject; + private final Set<Principal> _principals = new HashSet<Principal>(); public AuthenticationResult(final AuthenticationStatus status) { this(null, status, null); } + public AuthenticationResult(Principal mainPrincipal) + { + this(mainPrincipal, Collections.<Principal>emptySet()); + } + + public AuthenticationResult(Principal mainPrincipal, Set<Principal> otherPrincipals) + { + AuthenticatedPrincipal specialQpidAuthenticatedPrincipal = new AuthenticatedPrincipal(mainPrincipal); + _principals.addAll(otherPrincipals); + _principals.remove(mainPrincipal); + _principals.add(specialQpidAuthenticatedPrincipal); + + _status = AuthenticationStatus.SUCCESS; + _challenge = null; + _cause = null; + } + public AuthenticationResult(final byte[] challenge, final AuthenticationStatus status) { - this(challenge, status, null); + _challenge = challenge; + _status = status; + _cause = null; } public AuthenticationResult(final AuthenticationStatus error, final Exception cause) { - this(null, error, cause); + _status = error; + _challenge = null; + _cause = cause; } public AuthenticationResult(final byte[] challenge, final AuthenticationStatus status, final Exception cause) { + if(status == AuthenticationStatus.SUCCESS) + { + throw new IllegalArgumentException("Successful authentication requires at least one principal"); + } + this._status = status; this._challenge = challenge; this._cause = cause; - this._subject = null; - } - - public AuthenticationResult(final Subject subject) - { - this._status = AuthenticationStatus.SUCCESS; - this._challenge = null; - this._cause = null; - this._subject = subject; } public Exception getCause() @@ -104,9 +128,8 @@ public class AuthenticationResult return _challenge; } - public Subject getSubject() + public Set<Principal> getPrincipals() { - return _subject; + return _principals; } - } diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/SubjectAuthenticationResult.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/SubjectAuthenticationResult.java new file mode 100644 index 0000000000..3be96b87eb --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/SubjectAuthenticationResult.java @@ -0,0 +1,76 @@ +/* + * + * 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.auth; + +import java.security.Principal; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.SubjectCreator; + +/** + * Encapsulates the result of an attempt to authenticate using a {@link SubjectCreator}. + * + * <p> + * iff authentication was successful, {@link #getSubject()} will return a non-null value and + * {@link #getStatus()} will return {@link AuthenticationResult.AuthenticationStatus#SUCCESS}. + * + * In this case, the {@link Subject} will contain the user {@link Principal} and zero or more other principals + * representing groups. + * </p> + * @see SubjectCreator + */ +public class SubjectAuthenticationResult +{ + private final AuthenticationResult _authenticationResult; + private final Subject _subject; + + public SubjectAuthenticationResult(AuthenticationResult authenticationResult, Subject subject) + { + _authenticationResult = authenticationResult; + _subject = subject; + } + + public SubjectAuthenticationResult(AuthenticationResult unsuccessfulAuthenticationResult) + { + this(unsuccessfulAuthenticationResult, null); + } + + public Exception getCause() + { + return _authenticationResult.getCause(); + } + + public AuthenticationResult.AuthenticationStatus getStatus() + { + return _authenticationResult.getStatus(); + } + + public byte[] getChallenge() + { + return _authenticationResult.getChallenge(); + } + + public Subject getSubject() + { + return _subject; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipal.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java index 9e7db94216..cc414f801a 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipal.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java @@ -7,9 +7,9 @@ * 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 @@ -18,11 +18,9 @@ * under the License. * */ -package org.apache.qpid.server.security.auth.sasl; +package org.apache.qpid.server.security.auth; -import javax.security.auth.Subject; import java.security.Principal; -import java.util.Set; /** A principal that is just a wrapper for a simple username. */ public class UsernamePrincipal implements Principal @@ -48,9 +46,6 @@ public class UsernamePrincipal implements Principal return _name; } - /** - * @see java.lang.Object#hashCode() - */ @Override public int hashCode() { @@ -58,9 +53,6 @@ public class UsernamePrincipal implements Principal return prime * _name.hashCode(); } - /** - * @see java.lang.Object#equals(java.lang.Object) - */ @Override public boolean equals(Object obj) { @@ -81,19 +73,4 @@ public class UsernamePrincipal implements Principal } } } - - public static UsernamePrincipal getUsernamePrincipalFromSubject(final Subject authSubject) - { - if (authSubject == null) - { - throw new IllegalArgumentException("No authenticated subject."); - } - - final Set<UsernamePrincipal> principals = authSubject.getPrincipals(UsernamePrincipal.class); - if (principals.size() != 1) - { - throw new IllegalArgumentException("Can't find single UsernamePrincipal in authenticated subject"); - } - return principals.iterator().next(); - } } diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java index cac60a5283..578bb96efa 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java @@ -21,9 +21,9 @@ package org.apache.qpid.server.security.auth.database; import org.apache.log4j.Logger; +import org.apache.qpid.server.security.auth.UsernamePrincipal; import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; -import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.login.AccountNotFoundException; diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java index 4203cb0e07..ecf08de72e 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java @@ -21,7 +21,7 @@ package org.apache.qpid.server.security.auth.database; import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; -import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.UsernamePrincipal; import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5Initialiser; import org.apache.qpid.server.security.auth.sasl.plain.PlainInitialiser; diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManager.java index 5676c43754..9ac7a8b11a 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManager.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManager.java @@ -24,7 +24,6 @@ import java.security.Principal; import java.util.Arrays; import java.util.List; import javax.security.auth.Subject; -import javax.security.auth.callback.CallbackHandler; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; import org.apache.commons.configuration.Configuration; @@ -33,7 +32,7 @@ import org.apache.log4j.Logger; import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; import org.apache.qpid.server.security.auth.AuthenticationResult; -import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.UsernamePrincipal; import org.apache.qpid.server.security.auth.sasl.anonymous.AnonymousInitialiser; import org.apache.qpid.server.security.auth.sasl.anonymous.AnonymousSaslServer; @@ -45,7 +44,9 @@ public class AnonymousAuthenticationManager implements AuthenticationManager private static final String ANONYMOUS = SASL_INITIALISER.getMechanismName(); - private static final Principal ANONYMOUS_PRINCIPAL = new UsernamePrincipal("ANONYMOUS"); + public static final String ANONYMOUS_USERNAME = "ANONYMOUS"; + + public static final Principal ANONYMOUS_PRINCIPAL = new UsernamePrincipal(ANONYMOUS_USERNAME); public static final Subject ANONYMOUS_SUBJECT = new Subject(); static @@ -53,10 +54,7 @@ public class AnonymousAuthenticationManager implements AuthenticationManager ANONYMOUS_SUBJECT.getPrincipals().add(ANONYMOUS_PRINCIPAL); } - private static final AuthenticationResult ANONYMOUS_AUTHENTICATION = new AuthenticationResult(ANONYMOUS_SUBJECT); - - - private static CallbackHandler _callbackHandler = SASL_INITIALISER.getCallbackHandler(); + private static final AuthenticationResult ANONYMOUS_AUTHENTICATION = new AuthenticationResult(ANONYMOUS_PRINCIPAL); static final AnonymousAuthenticationManager INSTANCE = new AnonymousAuthenticationManager(); diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java index ccddcb7669..ba635cd023 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java @@ -30,14 +30,15 @@ import org.apache.qpid.server.security.auth.AuthenticationResult; /** * Implementations of the AuthenticationManager are responsible for determining * the authenticity of a user's credentials. - * - * If the authentication is successful, the manager is responsible for producing a populated - * {@link javax.security.auth.Subject} containing the user's identity and zero or more principals representing - * groups to which the user belongs. + * <p> + * If the authentication is successful, the manager is responsible for producing an + * {@link AuthenticationResult} containing the user's main {@link Principal} and zero or + * more other implementation-specific principals. + * </p> * <p> * The {@link #initialise()} method is responsible for registering SASL mechanisms required by * the manager. The {@link #close()} method must reverse this registration. - * + * </p> */ public interface AuthenticationManager extends Closeable, Plugin { @@ -88,5 +89,4 @@ public interface AuthenticationManager extends Closeable, Plugin * @return authentication result */ AuthenticationResult authenticate(String username, String password); - } diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManagerRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManagerRegistry.java index 89a4d8ae66..323ee15dd9 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManagerRegistry.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManagerRegistry.java @@ -25,7 +25,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -35,6 +34,8 @@ import org.apache.qpid.server.configuration.ServerConfiguration; import org.apache.qpid.server.plugins.Plugin; import org.apache.qpid.server.plugins.PluginManager; import org.apache.qpid.server.security.SecurityManager.SecurityConfiguration; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.group.GroupPrincipalAccessor; /** * A concrete implementation of {@link IAuthenticationManagerRegistry} that registers all {@link AuthenticationManager} @@ -50,12 +51,12 @@ import org.apache.qpid.server.security.SecurityManager.SecurityConfiguration; public class AuthenticationManagerRegistry implements Closeable, IAuthenticationManagerRegistry { private final Map<String,AuthenticationManager> _classToAuthManagerMap = new HashMap<String,AuthenticationManager>(); - private final AuthenticationManager _defaultAuthenticationManager; - private final Map<Integer,AuthenticationManager> _portToAuthenticationManagerMap; + private final SubjectCreator _defaultSubjectCreator; + private final Map<Integer, SubjectCreator> _portToSubjectCreatorMap; private final List<RegistryChangeListener> _listeners = Collections.synchronizedList(new ArrayList<RegistryChangeListener>()); - public AuthenticationManagerRegistry(ServerConfiguration serverConfiguration, PluginManager _pluginManager) + public AuthenticationManagerRegistry(ServerConfiguration serverConfiguration, PluginManager _pluginManager, GroupPrincipalAccessor groupPrincipalAccessor) throws ConfigurationException { final Collection<AuthenticationManagerPluginFactory<? extends Plugin>> factories = _pluginManager.getAuthenticationManagerPlugins().values(); @@ -78,9 +79,9 @@ public class AuthenticationManagerRegistry implements Closeable, IAuthentication throw new ConfigurationException("No authentication managers configured within the configuration file."); } - _defaultAuthenticationManager = getDefaultAuthenticationManager(serverConfiguration); + _defaultSubjectCreator = createDefaultSubectCreator(serverConfiguration, groupPrincipalAccessor); - _portToAuthenticationManagerMap = getPortToAuthenticationManagerMap(serverConfiguration); + _portToSubjectCreatorMap = createPortToSubjectCreatorMap(serverConfiguration, groupPrincipalAccessor); willClose = false; } finally @@ -95,14 +96,14 @@ public class AuthenticationManagerRegistry implements Closeable, IAuthentication } @Override - public AuthenticationManager getAuthenticationManager(SocketAddress address) + public SubjectCreator getSubjectCreator(SocketAddress address) { - AuthenticationManager authManager = + SubjectCreator subjectCreator = address instanceof InetSocketAddress - ? _portToAuthenticationManagerMap.get(((InetSocketAddress)address).getPort()) + ? _portToSubjectCreatorMap.get(((InetSocketAddress)address).getPort()) : null; - return authManager == null ? _defaultAuthenticationManager : authManager; + return subjectCreator == null ? _defaultSubjectCreator : subjectCreator; } @Override @@ -140,8 +141,8 @@ public class AuthenticationManagerRegistry implements Closeable, IAuthentication } } - private AuthenticationManager getDefaultAuthenticationManager( - ServerConfiguration serverConfiguration) + private SubjectCreator createDefaultSubectCreator( + ServerConfiguration serverConfiguration, GroupPrincipalAccessor groupAccessor) throws ConfigurationException { final AuthenticationManager defaultAuthenticationManager; @@ -164,14 +165,14 @@ public class AuthenticationManagerRegistry implements Closeable, IAuthentication { throw new ConfigurationException("If more than one authentication manager is configured a default MUST be specified."); } - return defaultAuthenticationManager; + return new SubjectCreator(defaultAuthenticationManager, groupAccessor); } - private Map<Integer,AuthenticationManager> getPortToAuthenticationManagerMap( - ServerConfiguration serverConfiguration) + private Map<Integer, SubjectCreator> createPortToSubjectCreatorMap( + ServerConfiguration serverConfiguration, GroupPrincipalAccessor groupPrincipalAccessor) throws ConfigurationException { - Map<Integer,AuthenticationManager> portToAuthenticationManagerMap = new HashMap<Integer, AuthenticationManager>(); + Map<Integer,SubjectCreator> portToSubjectCreatorMap = new HashMap<Integer, SubjectCreator>(); for(Map.Entry<Integer,String> portMapping : serverConfiguration.getPortAuthenticationMappings().entrySet()) { @@ -182,10 +183,12 @@ public class AuthenticationManagerRegistry implements Closeable, IAuthentication throw new ConfigurationException("Unknown authentication manager class " + portMapping.getValue() + " configured for port " + portMapping.getKey()); } - portToAuthenticationManagerMap.put(portMapping.getKey(), authenticationManager); + + SubjectCreator subjectCreator = new SubjectCreator(authenticationManager, groupPrincipalAccessor); + portToSubjectCreatorMap.put(portMapping.getKey(), subjectCreator); } - return portToAuthenticationManagerMap; + return portToSubjectCreatorMap; } @Override diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManager.java index 2d6866b657..a1328ca0de 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManager.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManager.java @@ -21,7 +21,6 @@ package org.apache.qpid.server.security.auth.manager; import java.security.Principal; import java.util.Arrays; import java.util.List; -import javax.security.auth.Subject; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; import org.apache.commons.configuration.Configuration; @@ -137,15 +136,13 @@ public class ExternalAuthenticationManager implements AuthenticationManager // Process response from the client try { - byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]); + server.evaluateResponse(response != null ? response : new byte[0]); Principal principal = ((ExternalSaslServer)server).getAuthenticatedPrincipal(); if(principal != null) { - final Subject subject = new Subject(); - subject.getPrincipals().add(principal); - return new AuthenticationResult(subject); + return new AuthenticationResult(principal); } else { diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/IAuthenticationManagerRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/IAuthenticationManagerRegistry.java index 485ca2e1e9..6ed8f95512 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/IAuthenticationManagerRegistry.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/IAuthenticationManagerRegistry.java @@ -23,13 +23,11 @@ import java.net.SocketAddress; import java.util.Map; import org.apache.qpid.common.Closeable; -import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.security.SubjectCreator; /** - * Registry for {@link AuthenticationManager} instances. - * - * <p>A lookup method {@link #getAuthenticationManager(SocketAddress)} allows a caller to determine - * the AuthenticationManager associated with a particular port number.</p> + * Registry for {@link AuthenticationManager} instances, also exposing them wrapped in {@link SubjectCreator}'s + * as a convenience. * * <p>It is important to {@link #close()} the registry after use and this allows the AuthenticationManagers * to reverse any security registrations they have performed.</p> @@ -37,14 +35,11 @@ import org.apache.qpid.server.virtualhost.VirtualHost; public interface IAuthenticationManagerRegistry extends Closeable { /** - * Returns the {@link AuthenticationManager} associated with a particular {@link SocketAddress}. - * If no authentication manager is associated with this address, a default authentication manager will be + * Returns the {@link SubjectCreator} associated with a particular {@link SocketAddress}. + * If no subject creator is associated with this address, a default will be * returned. Null is never returned. - * - * @param address - * @return authentication manager. */ - public AuthenticationManager getAuthenticationManager(SocketAddress address); + public SubjectCreator getSubjectCreator(SocketAddress address); Map<String, AuthenticationManager> getAvailableAuthenticationManagers(); @@ -55,5 +50,4 @@ public interface IAuthenticationManagerRegistry extends Closeable } public void addRegistryChangeListener(RegistryChangeListener listener); - }
\ No newline at end of file diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManager.java index d735ecb1d4..ed15d244eb 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManager.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManager.java @@ -23,7 +23,6 @@ import java.security.Principal; import java.util.Arrays; import java.util.HashMap; import java.util.List; -import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; @@ -37,7 +36,7 @@ import org.apache.log4j.Logger; import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; import org.apache.qpid.server.security.auth.AuthenticationResult; -import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.UsernamePrincipal; public class KerberosAuthenticationManager implements AuthenticationManager { @@ -158,10 +157,7 @@ public class KerberosAuthenticationManager implements AuthenticationManager if (server.isComplete()) { - final Subject subject = new Subject(); - _logger.debug("Authenticated as " + server.getAuthorizationID()); - subject.getPrincipals().add(new UsernamePrincipal(server.getAuthorizationID())); - return new AuthenticationResult(subject); + return new AuthenticationResult(new UsernamePrincipal(server.getAuthorizationID())); } else { diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java index e6498919a1..529f2bf6a8 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java @@ -34,9 +34,8 @@ import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationS import org.apache.qpid.server.security.auth.database.PrincipalDatabase; import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; import org.apache.qpid.server.security.auth.sasl.JCAProvider; -import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.UsernamePrincipal; -import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.AccountNotFoundException; import javax.security.sasl.Sasl; @@ -164,6 +163,7 @@ public class PrincipalDatabaseAuthenticationManager implements AuthenticationMan return getConfig().getString("principal-database.class"); } + @SuppressWarnings({ "unchecked", "rawtypes" }) public Map<String,String> getPdClassAttributeMap() throws ConfigurationException { final List<String> argumentNames = (List) getConfig().getList("principal-database.attributes.attribute.name"); @@ -284,9 +284,8 @@ public class PrincipalDatabaseAuthenticationManager implements AuthenticationMan if (server.isComplete()) { - final Subject subject = new Subject(); - subject.getPrincipals().add(new UsernamePrincipal(server.getAuthorizationID())); - return new AuthenticationResult(subject); + final String userId = server.getAuthorizationID(); + return new AuthenticationResult(new UsernamePrincipal(userId)); } else { @@ -308,9 +307,7 @@ public class PrincipalDatabaseAuthenticationManager implements AuthenticationMan { if (_principalDatabase.verifyPassword(username, password.toCharArray())) { - final Subject subject = new Subject(); - subject.getPrincipals().add(new UsernamePrincipal(username)); - return new AuthenticationResult(subject); + return new AuthenticationResult(new UsernamePrincipal(username)); } else { @@ -353,6 +350,16 @@ public class PrincipalDatabaseAuthenticationManager implements AuthenticationMan } } + public PrincipalDatabase getPrincipalDatabase() + { + return _principalDatabase; + } + + protected void setPrincipalDatabase(final PrincipalDatabase principalDatabase) + { + _principalDatabase = principalDatabase; + } + private void configPrincipalDatabase(final PrincipalDatabase principalDatabase, final PrincipalDatabaseAuthenticationManagerConfiguration config) throws ConfigurationException { @@ -400,11 +407,6 @@ public class PrincipalDatabaseAuthenticationManager implements AuthenticationMan } } - public PrincipalDatabase getPrincipalDatabase() - { - return _principalDatabase; - } - private String generateSetterName(String argName) throws ConfigurationException { if ((argName == null) || (argName.length() == 0)) @@ -421,8 +423,4 @@ public class PrincipalDatabaseAuthenticationManager implements AuthenticationMan return methodName; } - protected void setPrincipalDatabase(final PrincipalDatabase principalDatabase) - { - _principalDatabase = principalDatabase; - } } diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java index 64b24e28bc..8490a1c373 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java @@ -32,7 +32,6 @@ import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; -import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; @@ -47,7 +46,7 @@ import org.apache.log4j.Logger; import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; import org.apache.qpid.server.security.auth.AuthenticationResult; -import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.UsernamePrincipal; import org.apache.qpid.server.security.auth.sasl.plain.PlainPasswordCallback; public class SimpleLDAPAuthenticationManager implements AuthenticationManager @@ -205,10 +204,10 @@ public class SimpleLDAPAuthenticationManager implements AuthenticationManager if (server.isComplete()) { - final Subject subject = new Subject(); - _logger.debug("Authenticated as " + server.getAuthorizationID()); - subject.getPrincipals().add(new UsernamePrincipal(server.getAuthorizationID())); - return new AuthenticationResult(subject); + String authorizationID = server.getAuthorizationID(); + _logger.debug("Authenticated as " + authorizationID); + + return new AuthenticationResult(new UsernamePrincipal(authorizationID)); } else { @@ -249,9 +248,8 @@ public class SimpleLDAPAuthenticationManager implements AuthenticationManager env.put(Context.SECURITY_CREDENTIALS, password); DirContext ctx = new InitialDirContext(env); ctx.close(); - final Subject subject = new Subject(); - subject.getPrincipals().add(new UsernamePrincipal(username)); - return new AuthenticationResult(subject); + + return new AuthenticationResult(new UsernamePrincipal(username)); } @Override diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java index 2e21cfbb07..808447b7ff 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java @@ -23,12 +23,11 @@ package org.apache.qpid.server.security.auth.rmi; import java.net.SocketAddress; import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; -import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; import javax.management.remote.JMXAuthenticator; -import javax.management.remote.JMXPrincipal; import javax.security.auth.Subject; public class RMIPasswordAuthenticator implements JMXAuthenticator @@ -41,7 +40,7 @@ public class RMIPasswordAuthenticator implements JMXAuthenticator static final String CREDENTIALS_REQUIRED = "User details are required. " + "Please ensure you are using an up to date management console to connect."; - private AuthenticationManager _authenticationManager = null; + private SubjectCreator _subjectCreator = null; private SocketAddress _socketAddress; public RMIPasswordAuthenticator(SocketAddress socketAddress) @@ -49,9 +48,9 @@ public class RMIPasswordAuthenticator implements JMXAuthenticator _socketAddress = socketAddress; } - public void setAuthenticationManager(final AuthenticationManager authenticationManager) + public void setSubjectCreator(final SubjectCreator subjectCreator) { - _authenticationManager = authenticationManager; + _subjectCreator = subjectCreator; } public Subject authenticate(Object credentials) throws SecurityException @@ -85,14 +84,14 @@ public class RMIPasswordAuthenticator implements JMXAuthenticator throw new SecurityException(SHOULD_BE_NON_NULL); } - // Verify that an AuthenticationManager has been set. - if (_authenticationManager == null) + // Verify that an SubjectCreator has been set. + if (_subjectCreator == null) { try { - if(ApplicationRegistry.getInstance().getAuthenticationManager(_socketAddress) != null) + if(ApplicationRegistry.getInstance().getSubjectCreator(_socketAddress) != null) { - _authenticationManager = ApplicationRegistry.getInstance().getAuthenticationManager(_socketAddress); + _subjectCreator = ApplicationRegistry.getInstance().getSubjectCreator(_socketAddress); } else { @@ -104,7 +103,7 @@ public class RMIPasswordAuthenticator implements JMXAuthenticator throw new SecurityException(UNABLE_TO_LOOKUP); } } - final AuthenticationResult result = _authenticationManager.authenticate(username, password); + final SubjectAuthenticationResult result = _subjectCreator.authenticate(username, password); if (AuthenticationStatus.ERROR.equals(result.getStatus())) { @@ -112,10 +111,7 @@ public class RMIPasswordAuthenticator implements JMXAuthenticator } else if (AuthenticationStatus.SUCCESS.equals(result.getStatus())) { - final Subject subject = result.getSubject(); - subject.getPrincipals().add(new JMXPrincipal(username)); - subject.setReadOnly(); - return subject; + return result.getSubject(); } else { diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java index f4e8f800c6..b70a987107 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java @@ -23,6 +23,7 @@ package org.apache.qpid.server.security.auth.sasl; import org.apache.commons.configuration.Configuration; import org.apache.log4j.Logger; +import org.apache.qpid.server.security.auth.UsernamePrincipal; import org.apache.qpid.server.security.auth.database.PrincipalDatabase; import javax.security.auth.callback.Callback; diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServer.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServer.java index 52d36023c2..d10193e743 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServer.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServer.java @@ -23,6 +23,8 @@ package org.apache.qpid.server.security.auth.sasl.anonymous; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; +import org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManager; + public class AnonymousSaslServer implements SaslServer { @@ -52,7 +54,7 @@ public class AnonymousSaslServer implements SaslServer public String getAuthorizationID() { - return null; + return AnonymousAuthenticationManager.ANONYMOUS_PRINCIPAL.getName(); } public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/group/FileGroupDatabase.java b/java/broker/src/main/java/org/apache/qpid/server/security/group/FileGroupDatabase.java new file mode 100644 index 0000000000..2e4fc9e3a3 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/group/FileGroupDatabase.java @@ -0,0 +1,265 @@ +/* + * 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.group; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.Date; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListSet; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; + +/** + * A group database that reads/writes the following file format: + * + * group1.users=user1,user2 + * group2.users=user2,user3 + */ +public class FileGroupDatabase implements GroupDatabase +{ + private static final Logger LOGGER = Logger.getLogger(FileGroupDatabase.class); + + private Map<String, Set<String>> _groupToUserMap = new ConcurrentHashMap<String, Set<String>>(); + private Map<String, Set<String>> _userToGroupMap = new ConcurrentHashMap<String, Set<String>>(); + private String _groupFile; + + @Override + public Set<String> getAllGroups() + { + return Collections.unmodifiableSet(_groupToUserMap.keySet()); + } + + public synchronized void setGroupFile(String groupFile) throws IOException + { + File file = new File(groupFile); + + if (!file.canRead()) + { + throw new FileNotFoundException(groupFile + + " cannot be found or is not readable"); + } + + readGroupFile(groupFile); + } + + @Override + public Set<String> getUsersInGroup(String group) + { + if (group == null) + { + LOGGER.warn("Requested user set for null group. Returning empty set."); + return Collections.emptySet(); + } + + Set<String> set = _groupToUserMap.get(group); + if (set == null) + { + return Collections.emptySet(); + } + else + { + return Collections.unmodifiableSet(set); + } + } + + @Override + public synchronized void addUserToGroup(String user, String group) + { + Set<String> users = _groupToUserMap.get(group); + if (users == null) + { + throw new IllegalArgumentException("Group " + group + " does not exist so could not add " + user + " to it"); + } + + users.add(user); + + Set<String> groups = _userToGroupMap.get(user); + if (groups == null) + { + groups = new ConcurrentSkipListSet<String>(); + _userToGroupMap.put(user, groups); + } + groups.add(group); + + update(); + } + + @Override + public synchronized void removeUserFromGroup(String user, String group) + { + Set<String> users = _groupToUserMap.get(group); + if (users == null) + { + throw new IllegalArgumentException("Group " + group + " does not exist so could not remove " + user + " from it"); + } + + users.remove(user); + + Set<String> groups = _userToGroupMap.get(user); + if (groups != null) + { + groups.remove(group); + } + + update(); + } + + @Override + public Set<String> getGroupsForUser(String user) + { + if(user == null) + { + LOGGER.warn("Requested group set for null user. Returning empty set."); + return Collections.emptySet(); + } + + Set<String> groups = _userToGroupMap.get(user); + if (groups == null) + { + return Collections.emptySet(); + } + else + { + return Collections.unmodifiableSet(groups); + } + } + + @Override + public synchronized void createGroup(String group) + { + Set<String> users = new ConcurrentSkipListSet<String>(); + _groupToUserMap.put(group, users); + + update(); + } + + @Override + public synchronized void removeGroup(String group) + { + _groupToUserMap.remove(group); + for (Set<String> groupsForUser : _userToGroupMap.values()) + { + groupsForUser.remove(group); + } + + update(); + } + + private synchronized void update() + { + if (_groupFile != null) + { + try + { + writeGroupFile(_groupFile); + } + catch (IOException e) + { + throw new RuntimeException("Unable to persist change to file " + _groupFile); + } + } + } + + private synchronized void readGroupFile(String groupFile) throws IOException + { + _groupFile = groupFile; + _groupToUserMap.clear(); + _userToGroupMap.clear(); + Properties propertiesFile = new Properties(); + propertiesFile.load(new FileInputStream(groupFile)); + + for (String propertyName : propertiesFile.stringPropertyNames()) + { + validatePropertyNameIsGroupName(propertyName); + + String groupName = propertyName.replaceAll("\\.users$", ""); + String userString = propertiesFile.getProperty(propertyName); + + final Set<String> userSet = buildUserSetFromCommaSeparateValue(userString); + + _groupToUserMap.put(groupName, userSet); + + for (String userName : userSet) + { + Set<String> groupsForThisUser = _userToGroupMap.get(userName); + + if (groupsForThisUser == null) + { + groupsForThisUser = new ConcurrentSkipListSet<String>(); + _userToGroupMap.put(userName, groupsForThisUser); + } + + groupsForThisUser.add(groupName); + } + } + } + + private synchronized void writeGroupFile(String groupFile) throws IOException + { + Properties propertiesFile = new Properties(); + + for (String group : _groupToUserMap.keySet()) + { + Set<String> users = _groupToUserMap.get(group); + String userList = StringUtils.join(users, ","); + + propertiesFile.setProperty(group + ".users", userList); + } + + String comment = "Written " + new Date(); + propertiesFile.store(new FileOutputStream(groupFile), comment); + } + + private void validatePropertyNameIsGroupName(String propertyName) + { + if (!propertyName.endsWith(".users")) + { + throw new IllegalArgumentException( + "Invalid definition with name '" + + propertyName + + "'. Group definitions must end with suffix '.users'"); + } + } + + private ConcurrentSkipListSet<String> buildUserSetFromCommaSeparateValue(String userString) + { + String[] users = userString.split(","); + final ConcurrentSkipListSet<String> userSet = new ConcurrentSkipListSet<String>(); + for (String user : users) + { + final String trimmed = user.trim(); + if (!trimmed.isEmpty()) + { + userSet.add(trimmed); + } + } + return userSet; + } + +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/group/FileGroupManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/group/FileGroupManager.java new file mode 100644 index 0000000000..1b393c3ecf --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/group/FileGroupManager.java @@ -0,0 +1,251 @@ +/* + * 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.group; + +import java.io.IOException; +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.apache.qpid.server.security.auth.UsernamePrincipal; + +/** + * Implementation of a group manager whose implementation is backed by a flat group file. + * <p> + * This plugin is configured in the following manner: + * </p> + * <pre> + * <file-group-manager> + * <attributes> + * <attribute> + * <name>groupFile</name> + * <value>${conf}/groups</value> + * </attribute> + * </attributes> + * </file-group-manager> + * </pre> + */ +public class FileGroupManager implements GroupManager +{ + private static final Logger LOGGER = Logger.getLogger(FileGroupManager.class); + + public static final GroupManagerPluginFactory<FileGroupManager> FACTORY = new GroupManagerPluginFactory<FileGroupManager>() + { + public FileGroupManager newInstance(final ConfigurationPlugin config) throws ConfigurationException + { + final FileGroupManagerConfiguration configuration = + config == null + ? null + : (FileGroupManagerConfiguration) config.getConfiguration(FileGroupManagerConfiguration.class.getName()); + + // If there is no configuration for this plugin then don't load it. + if (configuration == null) + { + LOGGER.info("No file-group-manager configuration found for FileGroupManager"); + return null; + } + + final FileGroupManager fgm = new FileGroupManager(); + fgm.configure(configuration); + return fgm; + } + + public Class<FileGroupManager> getPluginClass() + { + return FileGroupManager.class; + } + + public String getPluginName() + { + return FileGroupManager.class.getName(); + } + }; + + private FileGroupDatabase _groupDatabase; + + public static class FileGroupManagerConfiguration extends ConfigurationPlugin { + + public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory() + { + public List<String> getParentPaths() + { + return Arrays.asList("security.file-group-manager"); + } + + public ConfigurationPlugin newInstance(final String path, final Configuration config) throws ConfigurationException + { + final ConfigurationPlugin instance = new FileGroupManagerConfiguration(); + + instance.setConfiguration(path, config); + return instance; + } + }; + + public String[] getElementsProcessed() + { + return new String[] {"attributes.attribute.name", + "attributes.attribute.value"}; + } + + public void validateConfiguration() throws ConfigurationException + { + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Map<String,String> getAttributeMap() throws ConfigurationException + { + final List<String> argumentNames = (List) getConfig().getList("attributes.attribute.name"); + final List<String> argumentValues = (List) getConfig().getList("attributes.attribute.value"); + final Map<String,String> attributes = new HashMap<String,String>(argumentNames.size()); + + for (int i = 0; i < argumentNames.size(); i++) + { + final String argName = argumentNames.get(i); + final String argValue = argumentValues.get(i); + + attributes.put(argName, argValue); + } + + return Collections.unmodifiableMap(attributes); + } + } + + @Override + public void configure(ConfigurationPlugin config) + throws ConfigurationException + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("configuring file group plugin"); + } + + FileGroupManagerConfiguration fileGroupMangerConfig = (FileGroupManagerConfiguration) config; + Map<String,String> attribMap = fileGroupMangerConfig.getAttributeMap(); + String groupFile = attribMap.get("groupFile"); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Group file : " + groupFile); + } + + _groupDatabase = new FileGroupDatabase(); + try + { + _groupDatabase.setGroupFile(groupFile); + } + catch (IOException e) + { + throw new ConfigurationException("Unable to set group file " + groupFile, e); + } + } + + @Override + public Set<Principal> getGroupPrincipalsForUser(String userId) + { + Set<String> groups = _groupDatabase.getGroupsForUser(userId); + if (groups.isEmpty()) + { + return Collections.emptySet(); + } + else + { + Set<Principal> principals = new HashSet<Principal>(); + for (String groupName : groups) + { + principals.add(new GroupPrincipal(groupName)); + } + return principals; + } + } + + @Override + public Set<Principal> getUserPrincipalsForGroup(String group) + { + Set<String> users = _groupDatabase.getUsersInGroup(group); + if (users.isEmpty()) + { + return Collections.emptySet(); + } + else + { + Set<Principal> principals = new HashSet<Principal>(); + for (String user : users) + { + principals.add(new UsernamePrincipal(user)); + } + return principals; + } + } + + @Override + public Set<Principal> getGroupPrincipals() + { + Set<String> groups = _groupDatabase.getAllGroups(); + if (groups.isEmpty()) + { + return Collections.emptySet(); + } + else + { + Set<Principal> principals = new HashSet<Principal>(); + for (String groupName : groups) + { + principals.add(new GroupPrincipal(groupName)); + } + return principals; + } + } + + @Override + public void createGroup(String group) + { + _groupDatabase.createGroup(group); + } + + @Override + public void removeGroup(String group) + { + _groupDatabase.removeGroup(group); + } + + @Override + public void addUserToGroup(String user, String group) + { + _groupDatabase.addUserToGroup(user, group); + } + + @Override + public void removeUserFromGroup(String user, String group) + { + _groupDatabase.removeUserFromGroup(user, group); + + } + +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupDatabase.java b/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupDatabase.java new file mode 100644 index 0000000000..98c12782d8 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupDatabase.java @@ -0,0 +1,34 @@ +/* + * 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.group; + +import java.util.Set; + +public interface GroupDatabase +{ + Set<String> getAllGroups(); + Set<String> getUsersInGroup(String group); + + void addUserToGroup(String user, String group); + void removeUserFromGroup(String user, String group); + Set<String> getGroupsForUser(String user); + void createGroup(String group); + void removeGroup(String group); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupManager.java new file mode 100644 index 0000000000..30510eaad5 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupManager.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.group; + +import java.security.Principal; +import java.util.Set; + +import org.apache.qpid.server.plugins.Plugin; + +public interface GroupManager extends Plugin +{ + Set<Principal> getGroupPrincipalsForUser(String user); + + Set<Principal> getGroupPrincipals(); + + Set<Principal> getUserPrincipalsForGroup(String group); + + void createGroup(String group); + + void removeGroup(String group); + + void addUserToGroup(String user, String group); + + void removeUserFromGroup(String user, String group); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupManagerPluginFactory.java b/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupManagerPluginFactory.java new file mode 100644 index 0000000000..dc5ab33668 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupManagerPluginFactory.java @@ -0,0 +1,27 @@ +/* + * 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.group; + +import org.apache.qpid.server.plugins.PluginFactory; + +public interface GroupManagerPluginFactory<S extends GroupManager> extends PluginFactory<S> +{ + +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/GroupPrincipal.java b/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupPrincipal.java index 30a503c769..ccb446b719 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/GroupPrincipal.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupPrincipal.java @@ -7,9 +7,9 @@ * 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 @@ -18,7 +18,7 @@ * under the License. * */ -package org.apache.qpid.server.security.auth.sasl; +package org.apache.qpid.server.security.group; import java.security.Principal; import java.security.acl.Group; @@ -34,7 +34,7 @@ public class GroupPrincipal implements Group { /** Name of the group */ private final String _groupName; - + public GroupPrincipal(final String groupName) { _groupName = groupName; @@ -83,7 +83,7 @@ public class GroupPrincipal implements Group { return true; } - else + else { if (obj instanceof GroupPrincipal) { diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupPrincipalAccessor.java b/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupPrincipalAccessor.java new file mode 100644 index 0000000000..9ab8ee586c --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupPrincipalAccessor.java @@ -0,0 +1,51 @@ +/* + * 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.group; + +import java.security.Principal; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + + +public class GroupPrincipalAccessor +{ + private final List<GroupManager> _groupManagerList; + + public GroupPrincipalAccessor(List<GroupManager> groupManagerList) + { + _groupManagerList = groupManagerList; + } + + public Set<Principal> getGroupPrincipals(String username) + { + Set<Principal> principals = new HashSet<Principal>(); + for (GroupManager groupManager : _groupManagerList) + { + Set<Principal> groups = groupManager.getGroupPrincipalsForUser(username); + if (groups != null) + { + principals.addAll(groups); + } + } + + return Collections.unmodifiableSet(principals); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java b/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java index f352bbdd2c..f8b8d14abf 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java +++ b/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java @@ -34,6 +34,7 @@ import org.apache.qpid.protocol.AMQMethodListener; import org.apache.qpid.server.protocol.AMQProtocolSession; import org.apache.qpid.server.registry.IApplicationRegistry; import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.server.security.auth.manager.AuthenticationManager; import org.apache.qpid.server.virtualhost.VirtualHostRegistry; @@ -157,13 +158,9 @@ public class AMQStateManager implements AMQMethodListener return _protocolSession; } - /** - * Get the AuthenticationManager associated with the ProtocolSession of the AMQStateManager - * - * @return the AuthenticationManager - */ - public AuthenticationManager getAuthenticationManager() + + public SubjectCreator getSubjectCreator() { - return getApplicationRegistry().getAuthenticationManager(getProtocolSession().getLocalAddress()); + return getApplicationRegistry().getSubjectCreator(getProtocolSession().getLocalAddress()); } } diff --git a/java/broker/src/main/java/org/apache/qpid/server/transport/ServerConnectionDelegate.java b/java/broker/src/main/java/org/apache/qpid/server/transport/ServerConnectionDelegate.java index c13f63b44d..615fce2909 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/transport/ServerConnectionDelegate.java +++ b/java/broker/src/main/java/org/apache/qpid/server/transport/ServerConnectionDelegate.java @@ -37,9 +37,9 @@ import org.apache.qpid.server.configuration.BrokerConfig; import org.apache.qpid.server.protocol.AMQConnectionModel; import org.apache.qpid.server.registry.IApplicationRegistry; import org.apache.qpid.server.security.SecurityManager; -import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; -import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; import org.apache.qpid.server.subscription.Subscription_0_10; import org.apache.qpid.server.virtualhost.State; import org.apache.qpid.server.virtualhost.VirtualHost; @@ -57,25 +57,25 @@ public class ServerConnectionDelegate extends ServerDelegate private final IApplicationRegistry _appRegistry; private int _maxNoOfChannels; private Map<String,Object> _clientProperties; - private final AuthenticationManager _authManager; + private final SubjectCreator _subjectCreator; - public ServerConnectionDelegate(IApplicationRegistry appRegistry, String localFQDN, AuthenticationManager authManager) + public ServerConnectionDelegate(IApplicationRegistry appRegistry, String localFQDN, SubjectCreator subjectCreator) { - this(createConnectionProperties(appRegistry.getBrokerConfig()), Collections.singletonList((Object)"en_US"), appRegistry, localFQDN, authManager); + this(createConnectionProperties(appRegistry.getBrokerConfig()), Collections.singletonList((Object)"en_US"), appRegistry, localFQDN, subjectCreator); } private ServerConnectionDelegate(Map<String, Object> properties, List<Object> locales, IApplicationRegistry appRegistry, String localFQDN, - AuthenticationManager authManager) + SubjectCreator subjectCreator) { - super(properties, parseToList(authManager.getMechanisms()), locales); + super(properties, parseToList(subjectCreator.getMechanisms()), locales); _appRegistry = appRegistry; _localFQDN = localFQDN; _maxNoOfChannels = appRegistry.getConfiguration().getMaxChannelCount(); - _authManager = authManager; + _subjectCreator = subjectCreator; } private static Map<String, Object> createConnectionProperties(final BrokerConfig brokerConfig) @@ -112,14 +112,14 @@ public class ServerConnectionDelegate extends ServerDelegate protected SaslServer createSaslServer(Connection conn, String mechanism) throws SaslException { - return _authManager.createSaslServer(mechanism, _localFQDN, ((ServerConnection) conn).getPeerPrincipal()); + return _subjectCreator.createSaslServer(mechanism, _localFQDN, ((ServerConnection) conn).getPeerPrincipal()); } protected void secure(final SaslServer ss, final Connection conn, final byte[] response) { final ServerConnection sconn = (ServerConnection) conn; - final AuthenticationResult authResult = _authManager.authenticate(ss, response); + final SubjectAuthenticationResult authResult = _subjectCreator.authenticate(ss, response); if (AuthenticationStatus.SUCCESS.equals(authResult.getStatus())) { diff --git a/java/broker/src/test/java/org/apache/qpid/server/logging/actors/AbstractManagementActorTest.java b/java/broker/src/test/java/org/apache/qpid/server/logging/actors/AbstractManagementActorTest.java new file mode 100644 index 0000000000..bf38bb64bf --- /dev/null +++ b/java/broker/src/test/java/org/apache/qpid/server/logging/actors/AbstractManagementActorTest.java @@ -0,0 +1,86 @@ +/* + * + * 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.logging.actors; + +import java.security.Principal; +import java.security.PrivilegedAction; +import java.util.Collections; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.logging.NullRootMessageLogger; +import org.apache.qpid.server.security.auth.TestPrincipalUtils; +import org.apache.qpid.test.utils.QpidTestCase; + +public class AbstractManagementActorTest extends QpidTestCase +{ + private AbstractManagementActor _logActor; + + @Override + public void setUp() + { + _logActor = new AbstractManagementActor(new NullRootMessageLogger(), AbstractManagementActor.UNKNOWN_PRINCIPAL) + { + @Override + public String getLogMessage() + { + return null; + } + }; + } + + public void testGetPrincipalName() + { + Subject subject = TestPrincipalUtils.createTestSubject("guest"); + + final String principalName = Subject.doAs(subject, + new PrivilegedAction<String>() + { + public String run() + { + return _logActor.getPrincipalName(); + } + }); + + assertEquals("guest", principalName); + } + + public void testGetPrincipalNameUsingSubjectWithoutAuthenticatedPrincipal() + { + Subject subject = new Subject(true, Collections.<Principal>emptySet(), Collections.emptySet(), Collections.emptySet()); + + final String principalName = Subject.doAs(subject, + new PrivilegedAction<String>() + { + public String run() + { + return _logActor.getPrincipalName(); + } + }); + + assertEquals(AbstractManagementActor.UNKNOWN_PRINCIPAL, principalName); + } + + public void testGetPrincipalWithoutSubject() + { + assertEquals(AbstractManagementActor.UNKNOWN_PRINCIPAL, _logActor.getPrincipalName()); + } +} diff --git a/java/broker/src/test/java/org/apache/qpid/server/logging/actors/HttpManagementActorTest.java b/java/broker/src/test/java/org/apache/qpid/server/logging/actors/HttpManagementActorTest.java new file mode 100644 index 0000000000..b40405e1f4 --- /dev/null +++ b/java/broker/src/test/java/org/apache/qpid/server/logging/actors/HttpManagementActorTest.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.server.logging.actors; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.auth.TestPrincipalUtils; + +import java.security.PrivilegedAction; +import java.util.List; + +public class HttpManagementActorTest extends BaseActorTestCase +{ + private static final String IP = "127.0.0.1"; + private static final int PORT = 1; + private static final String SUFFIX = "(" + IP + ":" + PORT + ")] "; + + @Override + public void createBroker() throws Exception + { + super.createBroker(); + _amqpActor = new HttpManagementActor(_rootLogger, IP, PORT); + } + + public void testSubjectPrincipalNameAppearance() + { + Subject subject = TestPrincipalUtils.createTestSubject("guest"); + + final String message = Subject.doAs(subject, new PrivilegedAction<String>() + { + public String run() + { + return sendTestLogMessage(_amqpActor); + } + }); + + assertNotNull("Test log message is not created!", message); + + List<Object> logs = _rawLogger.getLogMessages(); + assertEquals("Message log size not as expected.", 1, logs.size()); + + String logMessage = logs.get(0).toString(); + assertTrue("Message was not found in log message", logMessage.contains(message)); + assertTrue("Message does not contain expected value: " + logMessage, logMessage.contains("[mng:guest" + SUFFIX)); + } + + /** It's necessary to test successive calls because HttpManagementActor caches + * its log message based on principal name */ + public void testGetLogMessageCaching() + { + assertLogMessageWithoutPrincipal(); + assertLogMessageWithPrincipal("my_principal"); + assertLogMessageWithPrincipal("my_principal2"); + assertLogMessageWithoutPrincipal(); + } + + private void assertLogMessageWithoutPrincipal() + { + String message = _amqpActor.getLogMessage(); + assertEquals("Unexpected log message", "[mng:" + AbstractManagementActor.UNKNOWN_PRINCIPAL + SUFFIX, message); + } + + private void assertLogMessageWithPrincipal(String principalName) + { + Subject subject = TestPrincipalUtils.createTestSubject(principalName); + final String message = Subject.doAs(subject, new PrivilegedAction<String>() + { + public String run() + { + return _amqpActor.getLogMessage(); + } + }); + + assertEquals("Unexpected log message", "[mng:" + principalName + SUFFIX, message); + } +} diff --git a/java/broker/src/test/java/org/apache/qpid/server/logging/actors/ManagementActorTest.java b/java/broker/src/test/java/org/apache/qpid/server/logging/actors/ManagementActorTest.java index cb866245f0..20cc321aab 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/logging/actors/ManagementActorTest.java +++ b/java/broker/src/test/java/org/apache/qpid/server/logging/actors/ManagementActorTest.java @@ -20,10 +20,11 @@ */ package org.apache.qpid.server.logging.actors; -import javax.management.remote.JMXPrincipal; import javax.security.auth.Subject; + +import org.apache.qpid.server.security.auth.TestPrincipalUtils; + import java.security.PrivilegedAction; -import java.util.Collections; import java.util.List; public class ManagementActorTest extends BaseActorTestCase @@ -94,8 +95,7 @@ public class ManagementActorTest extends BaseActorTestCase */ public void testSubjectPrincipalNameAppearance() { - Subject subject = new Subject(true, Collections.singleton(new JMXPrincipal("guest")), Collections.EMPTY_SET, - Collections.EMPTY_SET); + Subject subject = TestPrincipalUtils.createTestSubject("guest"); final String message = Subject.doAs(subject, new PrivilegedAction<String>() { @@ -172,9 +172,7 @@ public class ManagementActorTest extends BaseActorTestCase private void assertLogMessageInRMIThreadWithPrincipal(String threadName, String principalName) { Thread.currentThread().setName(threadName); - Subject subject = new Subject(true, Collections.singleton(new JMXPrincipal(principalName)), Collections.EMPTY_SET, - Collections.EMPTY_SET); - + Subject subject = TestPrincipalUtils.createTestSubject(principalName); final String message = Subject.doAs(subject, new PrivilegedAction<String>() { public String run() diff --git a/java/broker/src/test/java/org/apache/qpid/server/model/UUIDGeneratorTest.java b/java/broker/src/test/java/org/apache/qpid/server/model/UUIDGeneratorTest.java index 643132d371..c686a24e99 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/model/UUIDGeneratorTest.java +++ b/java/broker/src/test/java/org/apache/qpid/server/model/UUIDGeneratorTest.java @@ -70,8 +70,12 @@ public class UUIDGeneratorTest extends QpidTestCase idSet.add(id6); UUID id7 = UUIDGenerator.generateVhostAliasUUID(value, value); idSet.add(id7); + UUID id8 = UUIDGenerator.generateGroupUUID(value, value); + idSet.add(id8); + UUID id9 = UUIDGenerator.generateGroupMemberUUID(value, value, value); + idSet.add(id9); - assertEquals("The produced UUIDs were not all unique", 7, idSet.size()); + assertEquals("The produced UUIDs were not all unique", 9, idSet.size()); } public void testQueueIdGeneration() throws Exception diff --git a/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java b/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java index 96c67941f9..0016e31236 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java +++ b/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java @@ -31,7 +31,8 @@ import org.apache.qpid.server.message.MessageContentSource; import org.apache.qpid.server.output.ProtocolOutputConverter; import org.apache.qpid.server.queue.QueueEntry; import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; +import org.apache.qpid.server.security.auth.UsernamePrincipal; import org.apache.qpid.server.subscription.ClientDeliveryMethod; import org.apache.qpid.server.subscription.Subscription; import org.apache.qpid.server.subscription.SubscriptionImpl; @@ -39,6 +40,8 @@ import org.apache.qpid.server.virtualhost.VirtualHost; import org.apache.qpid.transport.TestNetworkConnection; import javax.security.auth.Subject; + +import java.security.Principal; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -61,13 +64,22 @@ public class InternalTestProtocolSession extends AMQProtocolEngine implements Pr _channelDelivers = new HashMap<Integer, Map<AMQShortString, LinkedList<DeliveryPair>>>(); - // Need to authenticate session for it to be representative testing. - setAuthorizedSubject(new Subject(true, Collections.singleton(new UsernamePrincipal("InternalTestProtocolSession")), - Collections.EMPTY_SET, Collections.EMPTY_SET)); - + setTestAuthorizedSubject(); setVirtualHost(virtualHost); } + private void setTestAuthorizedSubject() + { + Principal principal = new AuthenticatedPrincipal(new UsernamePrincipal("InternalTestProtocolSession")); + Subject authorizedSubject = new Subject( + true, + Collections.singleton(principal), + Collections.emptySet(), + Collections.emptySet()); + + setAuthorizedSubject(authorizedSubject); + } + public ProtocolOutputConverter getProtocolOutputConverter() { return this; diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/SubjectCreatorTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/SubjectCreatorTest.java new file mode 100644 index 0000000000..b1bc9bea68 --- /dev/null +++ b/java/broker/src/test/java/org/apache/qpid/server/security/SubjectCreatorTest.java @@ -0,0 +1,138 @@ +/* + * 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; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.security.Principal; +import java.util.Arrays; +import java.util.HashSet; + +import javax.security.auth.Subject; +import javax.security.sasl.SaslServer; + +import junit.framework.TestCase; + +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.group.GroupPrincipalAccessor; + +public class SubjectCreatorTest extends TestCase +{ + private static final String USERNAME = "username"; + private static final String PASSWORD = "password"; + + private AuthenticationManager _authenticationManager = mock(AuthenticationManager.class); + private GroupPrincipalAccessor _groupPrincipalAccessor = mock(GroupPrincipalAccessor.class); + private SubjectCreator _subjectCreator = new SubjectCreator(_authenticationManager, _groupPrincipalAccessor); + + private Principal _userPrincipal = mock(Principal.class); + private Principal _group1 = mock(Principal.class); + private Principal _group2 = mock(Principal.class); + + private AuthenticationResult _authenticationResult; + private SaslServer _testSaslServer = mock(SaslServer.class); + private byte[] _saslResponseBytes = PASSWORD.getBytes(); + + @Override + public void setUp() + { + _authenticationResult = new AuthenticationResult(_userPrincipal); + when(_authenticationManager.authenticate(USERNAME, PASSWORD)).thenReturn(_authenticationResult); + + when(_groupPrincipalAccessor.getGroupPrincipals(USERNAME)) + .thenReturn(new HashSet<Principal>(Arrays.asList(_group1, _group2))); + } + + public void testAuthenticateUsernameAndPasswordReturnsSubjectWithUserAndGroupPrincipals() + { + final SubjectAuthenticationResult actualResult = _subjectCreator.authenticate(USERNAME, PASSWORD); + + assertEquals(AuthenticationStatus.SUCCESS, actualResult.getStatus()); + + final Subject actualSubject = actualResult.getSubject(); + + assertEquals("Should contain one user principal and two groups ", 3, actualSubject.getPrincipals().size()); + + assertTrue(actualSubject.getPrincipals().contains(new AuthenticatedPrincipal(_userPrincipal))); + assertTrue(actualSubject.getPrincipals().contains(_group1)); + assertTrue(actualSubject.getPrincipals().contains(_group2)); + + assertTrue(actualSubject.isReadOnly()); + } + + public void testSaslAuthenticationSuccessReturnsSubjectWithUserAndGroupPrincipals() throws Exception + { + when(_authenticationManager.authenticate(_testSaslServer, _saslResponseBytes)).thenReturn(_authenticationResult); + when(_testSaslServer.isComplete()).thenReturn(true); + when(_testSaslServer.getAuthorizationID()).thenReturn(USERNAME); + + SubjectAuthenticationResult result = _subjectCreator.authenticate(_testSaslServer, _saslResponseBytes); + + final Subject actualSubject = result.getSubject(); + assertEquals("Should contain one user principal and two groups ", 3, actualSubject.getPrincipals().size()); + + assertTrue(actualSubject.getPrincipals().contains(new AuthenticatedPrincipal(_userPrincipal))); + assertTrue(actualSubject.getPrincipals().contains(_group1)); + assertTrue(actualSubject.getPrincipals().contains(_group2)); + + assertTrue(actualSubject.isReadOnly()); + } + + public void testAuthenticateUnsuccessfulWithUsernameReturnsNullSubjectAndCorrectStatus() + { + testUnsuccessfulAuthentication(AuthenticationResult.AuthenticationStatus.CONTINUE); + testUnsuccessfulAuthentication(AuthenticationResult.AuthenticationStatus.ERROR); + } + + private void testUnsuccessfulAuthentication(AuthenticationStatus expectedStatus) + { + AuthenticationResult failedAuthenticationResult = new AuthenticationResult(expectedStatus); + + when(_authenticationManager.authenticate(USERNAME, PASSWORD)).thenReturn(failedAuthenticationResult); + + SubjectAuthenticationResult subjectAuthenticationResult = _subjectCreator.authenticate(USERNAME, PASSWORD); + + assertSame(expectedStatus, subjectAuthenticationResult.getStatus()); + assertNull(subjectAuthenticationResult.getSubject()); + } + + public void testAuthenticateUnsuccessfulWithSaslServerReturnsNullSubjectAndCorrectStatus() + { + testUnsuccessfulAuthenticationWithSaslServer(AuthenticationResult.AuthenticationStatus.CONTINUE); + testUnsuccessfulAuthenticationWithSaslServer(AuthenticationResult.AuthenticationStatus.ERROR); + } + + private void testUnsuccessfulAuthenticationWithSaslServer(AuthenticationStatus expectedStatus) + { + AuthenticationResult failedAuthenticationResult = new AuthenticationResult(expectedStatus); + + when(_authenticationManager.authenticate(_testSaslServer, _saslResponseBytes)).thenReturn(failedAuthenticationResult); + when(_testSaslServer.isComplete()).thenReturn(false); + + SubjectAuthenticationResult subjectAuthenticationResult = _subjectCreator.authenticate(_testSaslServer, _saslResponseBytes); + + assertSame(expectedStatus, subjectAuthenticationResult.getStatus()); + assertNull(subjectAuthenticationResult.getSubject()); + } +} diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTest.java new file mode 100644 index 0000000000..cd5791952f --- /dev/null +++ b/java/broker/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTest.java @@ -0,0 +1,147 @@ +/* + * 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.auth; + +import java.security.Principal; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.auth.UsernamePrincipal; + +import junit.framework.TestCase; + +public class AuthenticatedPrincipalTest extends TestCase +{ + + private AuthenticatedPrincipal _authenticatedPrincipal = new AuthenticatedPrincipal(new UsernamePrincipal("name")); + + public void testGetAuthenticatedPrincipalFromSubject() + { + final Subject subject = createSubjectContainingAuthenticatedPrincipal(); + final AuthenticatedPrincipal actual = AuthenticatedPrincipal.getAuthenticatedPrincipalFromSubject(subject); + assertSame(_authenticatedPrincipal, actual); + } + + public void testAuthenticatedPrincipalNotInSubject() + { + try + { + AuthenticatedPrincipal.getAuthenticatedPrincipalFromSubject(new Subject()); + fail("Exception not thrown"); + } + catch (IllegalArgumentException iae) + { + // PASS + } + } + + public void testGetOptionalAuthenticatedPrincipalFromSubject() + { + final Subject subject = createSubjectContainingAuthenticatedPrincipal(); + final AuthenticatedPrincipal actual = AuthenticatedPrincipal.getOptionalAuthenticatedPrincipalFromSubject(subject); + assertSame(_authenticatedPrincipal, actual); + } + + public void testGetOptionalAuthenticatedPrincipalFromSubjectReturnsNullIfMissing() + { + Subject subjectWithNoPrincipals = new Subject(); + assertNull(AuthenticatedPrincipal.getOptionalAuthenticatedPrincipalFromSubject(subjectWithNoPrincipals)); + + Subject subjectWithoutAuthenticatedPrincipal = new Subject(); + subjectWithoutAuthenticatedPrincipal.getPrincipals().add(new UsernamePrincipal("name1")); + assertNull("Should return null for a subject containing a principal that isn't an AuthenticatedPrincipal", + AuthenticatedPrincipal.getOptionalAuthenticatedPrincipalFromSubject(subjectWithoutAuthenticatedPrincipal)); + } + + public void testTooManyAuthenticatedPrincipalsInSubject() + { + final Subject subject = new Subject(); + subject.getPrincipals().add(new AuthenticatedPrincipal(new UsernamePrincipal("name1"))); + subject.getPrincipals().add(new AuthenticatedPrincipal(new UsernamePrincipal("name2"))); + + try + { + AuthenticatedPrincipal.getAuthenticatedPrincipalFromSubject(subject); + fail("Exception not thrown"); + } + catch (IllegalArgumentException iae) + { + // PASS + } + } + + private Subject createSubjectContainingAuthenticatedPrincipal() + { + final Principal other = new Principal() + { + public String getName() + { + return "otherprincipal"; + } + }; + + final Subject subject = new Subject(); + subject.getPrincipals().add(_authenticatedPrincipal); + subject.getPrincipals().add(other); + return subject; + } + + public void testEqualsAndHashcode() + { + AuthenticatedPrincipal user1principal1 = new AuthenticatedPrincipal(new UsernamePrincipal("user1")); + AuthenticatedPrincipal user1principal2 = new AuthenticatedPrincipal(new UsernamePrincipal("user1")); + + assertTrue(user1principal1.equals(user1principal1)); + assertTrue(user1principal1.equals(user1principal2)); + assertTrue(user1principal2.equals(user1principal1)); + + assertEquals(user1principal1.hashCode(), user1principal2.hashCode()); + } + + public void testEqualsAndHashcodeWithSameWrappedObject() + { + UsernamePrincipal wrappedPrincipal = new UsernamePrincipal("user1"); + AuthenticatedPrincipal user1principal1 = new AuthenticatedPrincipal(wrappedPrincipal); + AuthenticatedPrincipal user1principal2 = new AuthenticatedPrincipal(wrappedPrincipal); + + assertTrue(user1principal1.equals(user1principal1)); + assertTrue(user1principal1.equals(user1principal2)); + assertTrue(user1principal2.equals(user1principal1)); + + assertEquals(user1principal1.hashCode(), user1principal2.hashCode()); + } + + public void testEqualsWithDifferentUsernames() + { + AuthenticatedPrincipal user1principal1 = new AuthenticatedPrincipal(new UsernamePrincipal("user1")); + AuthenticatedPrincipal user1principal2 = new AuthenticatedPrincipal(new UsernamePrincipal("user2")); + + assertFalse(user1principal1.equals(user1principal2)); + assertFalse(user1principal2.equals(user1principal1)); + } + + public void testEqualsWithDisimilarObjects() + { + UsernamePrincipal wrappedPrincipal = new UsernamePrincipal("user1"); + AuthenticatedPrincipal authenticatedPrincipal = new AuthenticatedPrincipal(wrappedPrincipal); + + assertFalse(authenticatedPrincipal.equals(wrappedPrincipal)); + assertFalse(wrappedPrincipal.equals(authenticatedPrincipal)); + } +} diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTestHelper.java b/java/broker/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTestHelper.java new file mode 100644 index 0000000000..e9d8d16fce --- /dev/null +++ b/java/broker/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTestHelper.java @@ -0,0 +1,54 @@ +/* + * 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.auth; + +import java.security.Principal; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.Assert; + +/** + * Helper class for testing that sets of principals contain {@link AuthenticatedPrincipal}'s that wrap + * expected {@link Principal}'s. + */ +public class AuthenticatedPrincipalTestHelper +{ + public static void assertOnlyContainsWrapped(Principal wrappedPrincipal, Set<Principal> principals) + { + assertOnlyContainsWrappedAndSecondaryPrincipals(wrappedPrincipal, Collections.<Principal>emptySet(), principals); + } + + + public static void assertOnlyContainsWrappedAndSecondaryPrincipals( + Principal expectedWrappedPrincipal, + Set<Principal> expectedSecondaryPrincipals, + Set<Principal> actualPrincipals) + { + Assert.assertEquals("Principal set should contain one principal " + "but the principal set is: " + actualPrincipals, + 1 + expectedSecondaryPrincipals.size(), + actualPrincipals.size()); + + Set<Principal> expectedSet = new HashSet<Principal>(expectedSecondaryPrincipals); + expectedSet.add(new AuthenticatedPrincipal(expectedWrappedPrincipal)); + + Assert.assertEquals(expectedSet, actualPrincipals); + } +} diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/auth/AuthenticationResultTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/auth/AuthenticationResultTest.java new file mode 100644 index 0000000000..a023cbdbb2 --- /dev/null +++ b/java/broker/src/test/java/org/apache/qpid/server/security/auth/AuthenticationResultTest.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.server.security.auth; + +import static org.apache.qpid.server.security.auth.AuthenticatedPrincipalTestHelper.assertOnlyContainsWrapped; +import static org.apache.qpid.server.security.auth.AuthenticatedPrincipalTestHelper.assertOnlyContainsWrappedAndSecondaryPrincipals; +import static org.mockito.Mockito.mock; + +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestCase; + +public class AuthenticationResultTest extends TestCase +{ + public void testConstructWithAuthenticationStatusContinue() + { + AuthenticationResult authenticationResult = new AuthenticationResult(AuthenticationResult.AuthenticationStatus.CONTINUE); + assertSame(AuthenticationResult.AuthenticationStatus.CONTINUE, authenticationResult.getStatus()); + assertTrue(authenticationResult.getPrincipals().isEmpty()); + } + + public void testConstructWithAuthenticationStatusError() + { + AuthenticationResult authenticationResult = new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR); + assertSame(AuthenticationResult.AuthenticationStatus.ERROR, authenticationResult.getStatus()); + assertTrue(authenticationResult.getPrincipals().isEmpty()); + } + + public void testConstructWithAuthenticationStatusSuccessThrowsException() + { + try + { + new AuthenticationResult(AuthenticationResult.AuthenticationStatus.SUCCESS); + fail("Exception not thrown"); + } + catch(IllegalArgumentException e) + { + // PASS + } + } + + public void testConstructWithPrincipal() + { + Principal mainPrincipal = mock(Principal.class); + AuthenticationResult authenticationResult = new AuthenticationResult(mainPrincipal); + + assertOnlyContainsWrapped(mainPrincipal, authenticationResult.getPrincipals()); + assertSame(AuthenticationResult.AuthenticationStatus.SUCCESS, authenticationResult.getStatus()); + } + + public void testConstructWithNullPrincipalThrowsException() + { + try + { + new AuthenticationResult((Principal)null); + fail("Exception not thrown"); + } + catch(IllegalArgumentException e) + { + // pass + } + } + + public void testConstructWithSetOfPrincipals() + { + Principal mainPrincipal = mock(Principal.class); + Principal secondaryPrincipal = mock(Principal.class); + Set<Principal> secondaryPrincipals = Collections.singleton(secondaryPrincipal); + + AuthenticationResult authenticationResult = new AuthenticationResult(mainPrincipal, secondaryPrincipals); + + assertOnlyContainsWrappedAndSecondaryPrincipals(mainPrincipal, secondaryPrincipals, authenticationResult.getPrincipals()); + assertSame(AuthenticationResult.AuthenticationStatus.SUCCESS, authenticationResult.getStatus()); + } + + public void testConstructWithSetOfPrincipalsDeDuplicatesMainPrincipal() + { + Principal mainPrincipal = mock(Principal.class); + Principal secondaryPrincipal = mock(Principal.class); + + Set<Principal> secondaryPrincipalsContainingDuplicateOfMainPrincipal = new HashSet<Principal>( + Arrays.asList(secondaryPrincipal, mainPrincipal)); + Set<Principal> deDuplicatedSecondaryPrincipals = Collections.singleton(secondaryPrincipal); + + AuthenticationResult authenticationResult = new AuthenticationResult( + mainPrincipal, secondaryPrincipalsContainingDuplicateOfMainPrincipal); + + assertOnlyContainsWrappedAndSecondaryPrincipals(mainPrincipal, deDuplicatedSecondaryPrincipals, authenticationResult.getPrincipals()); + + assertSame(AuthenticationResult.AuthenticationStatus.SUCCESS, authenticationResult.getStatus()); + } +} diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalUtils.java b/java/broker/src/test/java/org/apache/qpid/server/security/auth/TestPrincipalUtils.java index 7ce03eaa79..ea6b40e3de 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalUtils.java +++ b/java/broker/src/test/java/org/apache/qpid/server/security/auth/TestPrincipalUtils.java @@ -18,9 +18,12 @@ * under the License. * */ -package org.apache.qpid.server.security.auth.sasl; +package org.apache.qpid.server.security.auth; import javax.security.auth.Subject; + +import org.apache.qpid.server.security.group.GroupPrincipal; + import java.security.Principal; import java.util.Collections; import java.util.HashSet; @@ -28,21 +31,19 @@ import java.util.Set; public class TestPrincipalUtils { - /** - * Creates a test subject, with exactly one UsernamePrincipal and zero or more GroupPrincipals. + * Creates a test subject, with exactly one {@link AuthenticatedPrincipal} and zero or more GroupPrincipals. */ public static Subject createTestSubject(final String username, final String... groups) { final Set<Principal> principals = new HashSet<Principal>(1 + groups.length); - principals.add(new UsernamePrincipal(username)); + principals.add(new AuthenticatedPrincipal(username)); for (String group : groups) { principals.add(new GroupPrincipal(group)); } - - final Subject subject = new Subject(true, principals, Collections.EMPTY_SET, Collections.EMPTY_SET); - return subject; + + return new Subject(true, principals, Collections.EMPTY_SET, Collections.EMPTY_SET); } } diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipalTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/auth/UsernamePrincipalTest.java index 75bc76c688..5e025d3ca8 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipalTest.java +++ b/java/broker/src/test/java/org/apache/qpid/server/security/auth/UsernamePrincipalTest.java @@ -18,13 +18,10 @@ * under the License. * */ -package org.apache.qpid.server.security.auth.sasl; +package org.apache.qpid.server.security.auth; import junit.framework.TestCase; -import javax.security.auth.Subject; -import java.security.Principal; - /** * Tests the UsernamePrincipal. * @@ -70,54 +67,4 @@ public class UsernamePrincipalTest extends TestCase UsernamePrincipal principal = new UsernamePrincipal("string"); assertFalse(principal.equals(null)); } - - public void testGetUsernamePrincipalFromSubject() - { - final UsernamePrincipal expected = new UsernamePrincipal("name"); - final Principal other = new Principal() - { - public String getName() - { - return "otherprincipal"; - } - }; - - final Subject subject = new Subject(); - subject.getPrincipals().add(expected); - subject.getPrincipals().add(other); - - final UsernamePrincipal actual = UsernamePrincipal.getUsernamePrincipalFromSubject(subject); - assertSame(expected, actual); - } - - public void testUsernamePrincipalNotInSubject() - { - try - { - UsernamePrincipal.getUsernamePrincipalFromSubject(new Subject()); - fail("Exception not thrown"); - } - catch (IllegalArgumentException iae) - { - // PASS - } - } - - public void testTooManyUsernamePrincipalInSubject() - { - final Subject subject = new Subject(); - subject.getPrincipals().add(new UsernamePrincipal("name1")); - subject.getPrincipals().add(new UsernamePrincipal("name2")); - try - { - - UsernamePrincipal.getUsernamePrincipalFromSubject(subject); - fail("Exception not thrown"); - } - catch (IllegalArgumentException iae) - { - // PASS - } - } - } diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java index 33740af1e7..7b244e219e 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java +++ b/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java @@ -23,7 +23,7 @@ package org.apache.qpid.server.security.auth.database; import junit.framework.TestCase; import org.apache.commons.codec.binary.Base64; -import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.UsernamePrincipal; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.login.AccountNotFoundException; diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java index b8601f0e5c..8e62324f7d 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java +++ b/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java @@ -22,7 +22,7 @@ package org.apache.qpid.server.security.auth.database; import junit.framework.TestCase; -import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.UsernamePrincipal; import javax.security.auth.login.AccountNotFoundException; import java.io.BufferedReader; diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerTest.java index 9dcd22c088..a36e97199f 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerTest.java +++ b/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerTest.java @@ -20,6 +20,8 @@ */ package org.apache.qpid.server.security.auth.manager; +import static org.apache.qpid.server.security.auth.AuthenticatedPrincipalTestHelper.assertOnlyContainsWrapped; + import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; import org.apache.commons.configuration.CompositeConfiguration; @@ -102,7 +104,8 @@ public class AnonymousAuthenticationManagerTest extends InternalBrokerBaseCase assertEquals("Expected authentication to be successful", AuthenticationResult.AuthenticationStatus.SUCCESS, result.getStatus()); - assertNotNull("Subject should not be null", result.getSubject()); + + assertOnlyContainsWrapped(AnonymousAuthenticationManager.ANONYMOUS_PRINCIPAL, result.getPrincipals()); } diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/AuthenticationManagerRegistryTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/AuthenticationManagerRegistryTest.java index efb8df3a38..9b7131c71a 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/AuthenticationManagerRegistryTest.java +++ b/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/AuthenticationManagerRegistryTest.java @@ -35,6 +35,8 @@ import org.apache.qpid.server.configuration.ServerConfiguration; import org.apache.qpid.server.plugins.Plugin; import org.apache.qpid.server.plugins.PluginManager; import org.apache.qpid.server.security.SecurityManager.SecurityConfiguration; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.group.GroupPrincipalAccessor; import org.mockito.Mockito; import junit.framework.TestCase; @@ -49,6 +51,8 @@ public class AuthenticationManagerRegistryTest extends TestCase private List<AuthenticationManager> _allCreatedAuthManagers = new ArrayList<AuthenticationManager>(); + private GroupPrincipalAccessor _groupPrincipalAccessor = mock(GroupPrincipalAccessor.class);; + @Override protected void setUp() throws Exception { @@ -76,7 +80,7 @@ public class AuthenticationManagerRegistryTest extends TestCase when(_pluginManager.getAuthenticationManagerPlugins()).thenReturn(EMPTY_PLUGINMAP); try { - new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager); + new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager, _groupPrincipalAccessor); fail("Exception not thrown"); } catch (ConfigurationException ce) @@ -97,7 +101,7 @@ public class AuthenticationManagerRegistryTest extends TestCase try { - new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager); + new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager, _groupPrincipalAccessor); fail("Exception not thrown"); } catch (ConfigurationException ce) @@ -120,7 +124,7 @@ public class AuthenticationManagerRegistryTest extends TestCase try { - new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager); + new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager, _groupPrincipalAccessor); fail("Exception not thrown"); } catch (ConfigurationException ce) @@ -145,7 +149,7 @@ public class AuthenticationManagerRegistryTest extends TestCase try { - new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager); + new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager, _groupPrincipalAccessor); fail("Exception not thrown"); } catch (ConfigurationException ce) @@ -170,7 +174,7 @@ public class AuthenticationManagerRegistryTest extends TestCase try { - new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager); + new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager, _groupPrincipalAccessor); fail("Exception not thrown"); } catch (ConfigurationException ce) @@ -187,10 +191,10 @@ public class AuthenticationManagerRegistryTest extends TestCase when(_pluginManager.getAuthenticationManagerPlugins()).thenReturn(pluginMap); - AuthenticationManagerRegistry registry = new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager); + AuthenticationManagerRegistry registry = new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager, _groupPrincipalAccessor); - AuthenticationManager authenticationManager = registry.getAuthenticationManager(new InetSocketAddress(1234)); - assertEquals("TestAuthenticationManager1", authenticationManager.getMechanisms()); + SubjectCreator subjectCreator = registry.getSubjectCreator(new InetSocketAddress(1234)); + assertEquals("TestAuthenticationManager1", subjectCreator.getMechanisms()); registry.close(); } @@ -202,10 +206,10 @@ public class AuthenticationManagerRegistryTest extends TestCase when(_pluginManager.getAuthenticationManagerPlugins()).thenReturn(pluginMap); - AuthenticationManagerRegistry registry = new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager); + AuthenticationManagerRegistry registry = new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager, _groupPrincipalAccessor); - AuthenticationManager authenticationManager = registry.getAuthenticationManager(mock(SocketAddress.class)); - assertEquals("TestAuthenticationManager1", authenticationManager.getMechanisms()); + SubjectCreator subjectCreator = registry.getSubjectCreator(mock(SocketAddress.class)); + assertEquals("TestAuthenticationManager1", subjectCreator.getMechanisms()); registry.close(); } @@ -225,13 +229,13 @@ public class AuthenticationManagerRegistryTest extends TestCase when(_serverConfiguration.getDefaultAuthenticationManager()).thenReturn(defaultAuthManger); when(_serverConfiguration.getPortAuthenticationMappings()).thenReturn(Collections.singletonMap(mappedPortNumber, mappedAuthManager)); - AuthenticationManagerRegistry registry = new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager); + AuthenticationManagerRegistry registry = new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager, _groupPrincipalAccessor); - AuthenticationManager authenticationManager1 = registry.getAuthenticationManager(new InetSocketAddress(unmappedPortNumber)); - assertEquals("TestAuthenticationManager1", authenticationManager1.getMechanisms()); + SubjectCreator subjectCreator = registry.getSubjectCreator(new InetSocketAddress(unmappedPortNumber)); + assertEquals("TestAuthenticationManager1", subjectCreator.getMechanisms()); - AuthenticationManager authenticationManager2 = registry.getAuthenticationManager(new InetSocketAddress(mappedPortNumber)); - assertEquals("TestAuthenticationManager2", authenticationManager2.getMechanisms()); + SubjectCreator subjectCreator2 = registry.getSubjectCreator(new InetSocketAddress(mappedPortNumber)); + assertEquals("TestAuthenticationManager2", subjectCreator2.getMechanisms()); registry.close(); } @@ -246,7 +250,7 @@ public class AuthenticationManagerRegistryTest extends TestCase when(_pluginManager.getAuthenticationManagerPlugins()).thenReturn(pluginMap); when(_serverConfiguration.getDefaultAuthenticationManager()).thenReturn(defaultAuthManger); - AuthenticationManagerRegistry registry = new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager); + AuthenticationManagerRegistry registry = new AuthenticationManagerRegistry(_serverConfiguration, _pluginManager, _groupPrincipalAccessor); registry.close(); } diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerTest.java index c1a55ef2ad..4e0643e229 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerTest.java +++ b/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerTest.java @@ -18,6 +18,8 @@ */ package org.apache.qpid.server.security.auth.manager; +import static org.apache.qpid.server.security.auth.AuthenticatedPrincipalTestHelper.assertOnlyContainsWrapped; + import javax.security.auth.x500.X500Principal; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; @@ -25,6 +27,7 @@ import org.apache.commons.configuration.CompositeConfiguration; 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.security.auth.AuthenticationResult; import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase; import org.apache.qpid.server.util.InternalBrokerBaseCase; @@ -103,12 +106,12 @@ public class ExternalAuthenticationManagerTest extends InternalBrokerBaseCase assertEquals("Expected authentication to be successful", AuthenticationResult.AuthenticationStatus.SUCCESS, result.getStatus()); - assertEquals("Expected principal to be unchanged", - principal, - result.getSubject().getPrincipals().iterator().next()); + + assertOnlyContainsWrapped(principal, result.getPrincipals()); saslServer = _manager.createSaslServer("EXTERNAL", "example.example.com", null); result = _manager.authenticate(saslServer, new byte[0]); + assertNotNull(result); assertEquals("Expected authentication to be unsuccessful", AuthenticationResult.AuthenticationStatus.ERROR, diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java index 47c189e4fa..391eb4e665 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java +++ b/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java @@ -20,6 +20,8 @@ */ package org.apache.qpid.server.security.auth.manager; +import static org.apache.qpid.server.security.auth.AuthenticatedPrincipalTestHelper.assertOnlyContainsWrapped; + import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.XMLConfiguration; @@ -28,10 +30,9 @@ import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; import org.apache.qpid.server.security.auth.AuthenticationResult; import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase; -import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.UsernamePrincipal; import org.apache.qpid.server.util.InternalBrokerBaseCase; -import javax.security.auth.Subject; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; import java.io.BufferedWriter; @@ -48,6 +49,7 @@ import java.security.Security; */ public class PrincipalDatabaseAuthenticationManagerTest extends InternalBrokerBaseCase { + private static final UsernamePrincipal PRINCIPAL = new UsernamePrincipal("guest"); private AuthenticationManager _manager = null; // Class under test private String TEST_USERNAME = "guest"; private String TEST_PASSWORD = "guest"; @@ -72,7 +74,7 @@ public class PrincipalDatabaseAuthenticationManagerTest extends InternalBrokerBa public void setUp() throws Exception { super.setUp(); - + final String passwdFilename = createPasswordFile().getCanonicalPath(); final ConfigurationPlugin config = getConfig(PlainPasswordFilePrincipalDatabase.class.getName(), "passwordFile", passwdFilename); @@ -121,7 +123,7 @@ public class PrincipalDatabaseAuthenticationManagerTest extends InternalBrokerBa { try { - _manager = PrincipalDatabaseAuthenticationManager.FACTORY.newInstance(getConfig(PlainPasswordFilePrincipalDatabase.class.getName(), "noMethod", "test")); + _manager = PrincipalDatabaseAuthenticationManager.FACTORY.newInstance(getConfig(PlainPasswordFilePrincipalDatabase.class.getName(), "noMethod", "test")); fail("Exception not thrown"); } catch (ConfigurationException ce) @@ -137,7 +139,7 @@ public class PrincipalDatabaseAuthenticationManagerTest extends InternalBrokerBa { try { - _manager = PrincipalDatabaseAuthenticationManager.FACTORY.newInstance(getConfig(PlainPasswordFilePrincipalDatabase.class.getName(), "passwordFile", "/not/found")); + _manager = PrincipalDatabaseAuthenticationManager.FACTORY.newInstance(getConfig(PlainPasswordFilePrincipalDatabase.class.getName(), "passwordFile", "/not/found")); fail("Exception not thrown"); } catch (ConfigurationException ce) @@ -157,7 +159,7 @@ public class PrincipalDatabaseAuthenticationManagerTest extends InternalBrokerBa // relies on those mechanisms attached to PropertiesPrincipalDatabaseManager assertEquals("AMQPLAIN PLAIN CRAM-MD5", _manager.getMechanisms()); - Provider qpidProvider = Security.getProvider(PrincipalDatabaseAuthenticationManager.PROVIDER_NAME); + Provider qpidProvider = Security.getProvider(AuthenticationManager.PROVIDER_NAME); assertNotNull(qpidProvider); } @@ -172,49 +174,51 @@ public class PrincipalDatabaseAuthenticationManagerTest extends InternalBrokerBa // Merely tests the creation of the mechanism. Mechanisms themselves are tested // by their own tests. } - + /** * Tests that the authenticate method correctly interprets an * authentication success. - * + * */ public void testSaslAuthenticationSuccess() throws Exception { + SaslServer testServer = createTestSaslServer(true, false); - + AuthenticationResult result = _manager.authenticate(testServer, "12345".getBytes()); - final Subject subject = result.getSubject(); - assertTrue(subject.getPrincipals().contains(new UsernamePrincipal("guest"))); + + assertOnlyContainsWrapped(PRINCIPAL, result.getPrincipals()); assertEquals(AuthenticationStatus.SUCCESS, result.getStatus()); } /** - * + * * Tests that the authenticate method correctly interprets an * authentication not complete. - * + * */ public void testSaslAuthenticationNotCompleted() throws Exception { SaslServer testServer = createTestSaslServer(false, false); - + AuthenticationResult result = _manager.authenticate(testServer, "12345".getBytes()); - assertNull(result.getSubject()); + assertEquals("Principals was not expected size", 0, result.getPrincipals().size()); + assertEquals(AuthenticationStatus.CONTINUE, result.getStatus()); } /** - * + * * Tests that the authenticate method correctly interprets an * authentication error. - * + * */ public void testSaslAuthenticationError() throws Exception { SaslServer testServer = createTestSaslServer(false, true); - + AuthenticationResult result = _manager.authenticate(testServer, "12345".getBytes()); - assertNull(result.getSubject()); + assertEquals("Principals was not expected size", 0, result.getPrincipals().size()); assertEquals(AuthenticationStatus.ERROR, result.getStatus()); } @@ -226,9 +230,7 @@ public class PrincipalDatabaseAuthenticationManagerTest extends InternalBrokerBa public void testNonSaslAuthenticationSuccess() throws Exception { AuthenticationResult result = _manager.authenticate("guest", "guest"); - final Subject subject = result.getSubject(); - assertFalse("Subject should not be set read-only", subject.isReadOnly()); - assertTrue(subject.getPrincipals().contains(new UsernamePrincipal("guest"))); + assertOnlyContainsWrapped(PRINCIPAL, result.getPrincipals()); assertEquals(AuthenticationStatus.SUCCESS, result.getStatus()); } @@ -240,23 +242,23 @@ public class PrincipalDatabaseAuthenticationManagerTest extends InternalBrokerBa public void testNonSaslAuthenticationNotCompleted() throws Exception { AuthenticationResult result = _manager.authenticate("guest", "wrongpassword"); - assertNull(result.getSubject()); + assertEquals("Principals was not expected size", 0, result.getPrincipals().size()); assertEquals(AuthenticationStatus.CONTINUE, result.getStatus()); } - + /** * Tests the ability to de-register the provider. */ public void testClose() throws Exception { assertEquals("AMQPLAIN PLAIN CRAM-MD5", _manager.getMechanisms()); - assertNotNull(Security.getProvider(PrincipalDatabaseAuthenticationManager.PROVIDER_NAME)); + assertNotNull(Security.getProvider(AuthenticationManager.PROVIDER_NAME)); _manager.close(); // Check provider has been removed. assertNull(_manager.getMechanisms()); - assertNull(Security.getProvider(PrincipalDatabaseAuthenticationManager.PROVIDER_NAME)); + assertNull(Security.getProvider(AuthenticationManager.PROVIDER_NAME)); _manager = null; } @@ -343,7 +345,7 @@ public class PrincipalDatabaseAuthenticationManagerTest extends InternalBrokerBa writer = new BufferedWriter(new FileWriter(testFile)); writer.write(TEST_USERNAME + ":" + TEST_PASSWORD); writer.newLine(); - + return testFile; } diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java index c0c55de92a..efdb286866 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java +++ b/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java @@ -20,20 +20,21 @@ */ package org.apache.qpid.server.security.auth.rmi; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.InetSocketAddress; import java.security.Principal; + +import javax.security.auth.Subject; + import junit.framework.TestCase; -import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.server.security.auth.AuthenticationResult; import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; -import org.apache.qpid.server.security.auth.manager.AuthenticationManager; - -import javax.management.remote.JMXPrincipal; -import javax.security.auth.Subject; -import javax.security.sasl.SaslException; -import javax.security.sasl.SaslServer; -import java.net.InetSocketAddress; -import java.util.Collections; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; /** * Tests the RMIPasswordAuthenticator and its collaboration with the AuthenticationManager. @@ -41,6 +42,7 @@ import java.util.Collections; */ public class RMIPasswordAuthenticatorTest extends TestCase { + private static final Subject SUBJECT = new Subject(); private final String USERNAME = "guest"; private final String PASSWORD = "guest"; private RMIPasswordAuthenticator _rmipa; @@ -54,23 +56,14 @@ public class RMIPasswordAuthenticatorTest extends TestCase } /** - * Tests a successful authentication. Ensures that a populated read-only subject it returned. + * Tests a successful authentication. Ensures that the expected subject is returned. */ public void testAuthenticationSuccess() { - final Subject expectedSubject = new Subject(true, - Collections.singleton(new JMXPrincipal(USERNAME)), - Collections.EMPTY_SET, - Collections.EMPTY_SET); - - _rmipa.setAuthenticationManager(createTestAuthenticationManager(true, null)); - + _rmipa.setSubjectCreator(createMockSubjectCreator(true, null)); Subject newSubject = _rmipa.authenticate(_credentials); - assertTrue("Subject must be readonly", newSubject.isReadOnly()); - assertTrue("Returned subject does not equal expected value", - newSubject.equals(expectedSubject)); - + assertSame("Subject must be unchanged", SUBJECT, newSubject); } /** @@ -78,7 +71,7 @@ public class RMIPasswordAuthenticatorTest extends TestCase */ public void testUsernameOrPasswordInvalid() { - _rmipa.setAuthenticationManager(createTestAuthenticationManager(false, null)); + _rmipa.setSubjectCreator(createMockSubjectCreator(false, null)); try { @@ -99,7 +92,7 @@ public class RMIPasswordAuthenticatorTest extends TestCase public void testAuthenticationFailure() { final Exception mockAuthException = new Exception("Mock Auth system failure"); - _rmipa.setAuthenticationManager(createTestAuthenticationManager(false, mockAuthException)); + _rmipa.setSubjectCreator(createMockSubjectCreator(false, mockAuthException)); try { @@ -118,7 +111,7 @@ public class RMIPasswordAuthenticatorTest extends TestCase */ public void testNullAuthenticationManager() throws Exception { - _rmipa.setAuthenticationManager(null); + _rmipa.setSubjectCreator(null); try { _rmipa.authenticate(_credentials); @@ -209,55 +202,30 @@ public class RMIPasswordAuthenticatorTest extends TestCase } } - private AuthenticationManager createTestAuthenticationManager(final boolean successfulAuth, final Exception exception) + private SubjectCreator createMockSubjectCreator(final boolean successfulAuth, final Exception exception) { - return new AuthenticationManager() - { - public void configure(ConfigurationPlugin config) - { - throw new UnsupportedOperationException(); - } + SubjectCreator subjectCreator = mock(SubjectCreator.class); - public void initialise() - { - throw new UnsupportedOperationException(); - } + SubjectAuthenticationResult subjectAuthenticationResult; - public void close() - { - throw new UnsupportedOperationException(); - } + if (exception != null) { - public String getMechanisms() - { - throw new UnsupportedOperationException(); - } - - public SaslServer createSaslServer(String mechanism, String localFQDN, Principal externalPrincipal) throws SaslException - { - throw new UnsupportedOperationException(); - } + subjectAuthenticationResult = new SubjectAuthenticationResult( + new AuthenticationResult(AuthenticationStatus.ERROR, exception)); + } + else if (successfulAuth) + { - public AuthenticationResult authenticate(SaslServer server, byte[] response) - { - throw new UnsupportedOperationException(); - } + subjectAuthenticationResult = new SubjectAuthenticationResult( + new AuthenticationResult(mock(Principal.class)), SUBJECT); + } + else + { + subjectAuthenticationResult = new SubjectAuthenticationResult(new AuthenticationResult(AuthenticationStatus.CONTINUE)); + } - public AuthenticationResult authenticate(String username, String password) - { - if (exception != null) { - return new AuthenticationResult(AuthenticationStatus.ERROR, exception); - } - else if (successfulAuth) - { - return new AuthenticationResult(new Subject()); - } - else - { - return new AuthenticationResult(AuthenticationStatus.CONTINUE); - } - } + when(subjectCreator.authenticate(anyString(), anyString())).thenReturn(subjectAuthenticationResult); - }; + return subjectCreator; } } diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/group/FileGroupDatabaseTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/group/FileGroupDatabaseTest.java new file mode 100644 index 0000000000..b020c1655a --- /dev/null +++ b/java/broker/src/test/java/org/apache/qpid/server/security/group/FileGroupDatabaseTest.java @@ -0,0 +1,456 @@ +/* + * 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.group; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Properties; +import java.util.Set; + +import org.apache.qpid.server.security.group.FileGroupDatabase; + +import junit.framework.TestCase; + +public class FileGroupDatabaseTest extends TestCase +{ + private static final String USER1 = "user1"; + private static final String USER2 = "user2"; + private static final String USER3 = "user3"; + + private static final String MY_GROUP = "myGroup"; + private static final String MY_GROUP2 = "myGroup2"; + private static final String MY_GROUP1 = "myGroup1"; + + private FileGroupDatabase _groupDatabase = new FileGroupDatabase(); + private String _groupFile; + + public void testGetAllGroups() throws Exception + { + writeAndSetGroupFile("myGroup.users", USER1); + + Set<String> groups = _groupDatabase.getAllGroups(); + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP)); + } + + public void testGetAllGroupsWhenGroupFileEmpty() throws Exception + { + _groupDatabase.setGroupFile(_groupFile); + + Set<String> groups = _groupDatabase.getAllGroups(); + assertEquals(0, groups.size()); + } + + public void testMissingGroupFile() throws Exception + { + try + { + _groupDatabase.setGroupFile("/not/a/file"); + fail("Exception not thrown"); + } + catch (FileNotFoundException fnfe) + { + // PASS + } + } + + public void testInvalidFormat() throws Exception + { + writeGroupFile("name.notvalid", USER1); + + try + { + _groupDatabase.setGroupFile(_groupFile); + fail("Exception not thrown"); + } + catch (IllegalArgumentException gde) + { + // PASS + } + } + + public void testGetUsersInGroup() throws Exception + { + writeGroupFile("myGroup.users", "user1,user2,user3"); + + _groupDatabase.setGroupFile(_groupFile); + + Set<String> users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(3, users.size()); + } + + public void testDuplicateUsersInGroupAreConflated() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user1,user3,user1"); + + Set<String> users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(2, users.size()); + } + + public void testGetUsersWithEmptyGroup() throws Exception + { + writeAndSetGroupFile("myGroup.users", ""); + + Set<String> users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertTrue(users.isEmpty()); + } + + public void testGetUsersInNonExistentGroup() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2,user3"); + + Set<String> users = _groupDatabase.getUsersInGroup("groupDoesntExist"); + assertNotNull(users); + assertTrue(users.isEmpty()); + } + + public void testGetUsersInNullGroup() throws Exception + { + writeAndSetGroupFile(); + assertTrue(_groupDatabase.getUsersInGroup(null).isEmpty()); + } + + public void testGetGroupPrincipalsForUserWhenUserBelongsToOneGroup() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2"); + Set<String> groups = _groupDatabase.getGroupsForUser(USER1); + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP)); + } + + public void testGetGroupPrincipalsForUserWhenUserBelongsToTwoGroup() throws Exception + { + writeAndSetGroupFile("myGroup1.users", "user1,user2", + "myGroup2.users", "user1,user3"); + Set<String> groups = _groupDatabase.getGroupsForUser(USER1); + assertEquals(2, groups.size()); + assertTrue(groups.contains(MY_GROUP1)); + assertTrue(groups.contains(MY_GROUP2)); + } + + public void testGetGroupPrincipalsForUserWhenUserAddedToGroup() throws Exception + { + writeAndSetGroupFile("myGroup1.users", "user1,user2", + "myGroup2.users", USER2); + Set<String> groups = _groupDatabase.getGroupsForUser(USER1); + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP1)); + + _groupDatabase.addUserToGroup(USER1, MY_GROUP2); + + groups = _groupDatabase.getGroupsForUser(USER1); + assertEquals(2, groups.size()); + assertTrue(groups.contains(MY_GROUP1)); + assertTrue(groups.contains(MY_GROUP2)); + + Set<String> users = _groupDatabase.getUsersInGroup(MY_GROUP2); + assertEquals(2, users.size()); + assertTrue(users.contains(USER1)); + assertTrue(users.contains(USER2)); + } + + public void testGetGroupPrincipalsForUserWhenUserRemovedFromGroup() throws Exception + { + writeAndSetGroupFile("myGroup1.users", "user1,user2", + "myGroup2.users", "user1,user2"); + Set<String> groups = _groupDatabase.getGroupsForUser(USER1); + assertEquals(2, groups.size()); + assertTrue(groups.contains(MY_GROUP1)); + assertTrue(groups.contains(MY_GROUP2)); + + _groupDatabase.removeUserFromGroup(USER1, MY_GROUP2); + + groups = _groupDatabase.getGroupsForUser(USER1); + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP1)); + } + + public void testGetGroupPrincipalsForUserWhenUserAdddedToGroupTheyAreAlreadyIn() throws Exception + { + writeAndSetGroupFile("myGroup.users", USER1); + _groupDatabase.addUserToGroup(USER1, MY_GROUP); + + Set<String> groups = _groupDatabase.getGroupsForUser(USER1); + + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP)); + + Set<String> users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertEquals(1, users.size()); + assertTrue(users.contains(USER1)); + } + + public void testGetGroupPrincipalsForUserWhenUserNotKnown() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2"); + Set<String> groups = _groupDatabase.getGroupsForUser(USER3); + assertEquals(0, groups.size()); + } + + public void testGetGroupPrincipalsForNullUser() throws Exception + { + writeAndSetGroupFile(); + assertTrue(_groupDatabase.getGroupsForUser(null).isEmpty()); + } + + public void testAddUserToExistingGroup() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2"); + + Set<String> users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(2, users.size()); + + _groupDatabase.addUserToGroup(USER3, MY_GROUP); + + users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(3, users.size()); + } + + public void testAddUserToEmptyGroup() throws Exception + { + writeAndSetGroupFile("myGroup.users", ""); + + Set<String> users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(0, users.size()); + + _groupDatabase.addUserToGroup(USER3, MY_GROUP); + + users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(1, users.size()); + } + + public void testAddUserToNonExistentGroup() throws Exception + { + writeAndSetGroupFile(); + + Set<String> users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(0, users.size()); + + try + { + _groupDatabase.addUserToGroup(USER3, MY_GROUP); + fail("Expected exception not thrown"); + } + catch(IllegalArgumentException e) + { + // pass + } + + users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(0, users.size()); + } + + public void testRemoveUserFromExistingGroup() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2"); + + Set<String> users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(2, users.size()); + + _groupDatabase.removeUserFromGroup(USER2, MY_GROUP); + + users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(1, users.size()); + } + + public void testRemoveUserFromNonexistentGroup() throws Exception + { + writeAndSetGroupFile(); + + try + { + _groupDatabase.removeUserFromGroup(USER1, MY_GROUP); + fail("Expected exception not thrown"); + } + catch(IllegalArgumentException e) + { + // pass + } + + assertTrue(_groupDatabase.getUsersInGroup(MY_GROUP).isEmpty()); + } + + public void testRemoveUserFromGroupTwice() throws Exception + { + writeAndSetGroupFile("myGroup.users", USER1); + assertTrue(_groupDatabase.getUsersInGroup(MY_GROUP).contains(USER1)); + + _groupDatabase.removeUserFromGroup(USER1, MY_GROUP); + assertTrue(_groupDatabase.getUsersInGroup(MY_GROUP).isEmpty()); + + _groupDatabase.removeUserFromGroup(USER1, MY_GROUP); + assertTrue(_groupDatabase.getUsersInGroup(MY_GROUP).isEmpty()); + } + + public void testAddUserPersistedToFile() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2"); + + Set<String> users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertEquals(2, users.size()); + + _groupDatabase.addUserToGroup(USER3, MY_GROUP); + assertEquals(3, users.size()); + + FileGroupDatabase newGroupDatabase = new FileGroupDatabase(); + newGroupDatabase.setGroupFile(_groupFile); + + Set<String> newUsers = newGroupDatabase.getUsersInGroup(MY_GROUP); + assertEquals(users.size(), newUsers.size()); + } + + public void testRemoveUserPersistedToFile() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2"); + + Set<String> users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertEquals(2, users.size()); + + _groupDatabase.removeUserFromGroup(USER2, MY_GROUP); + assertEquals(1, users.size()); + + FileGroupDatabase newGroupDatabase = new FileGroupDatabase(); + newGroupDatabase.setGroupFile(_groupFile); + + Set<String> newUsers = newGroupDatabase.getUsersInGroup(MY_GROUP); + assertEquals(users.size(), newUsers.size()); + } + + public void testCreateGroupPersistedToFile() throws Exception + { + writeAndSetGroupFile(); + + Set<String> groups = _groupDatabase.getAllGroups(); + assertEquals(0, groups.size()); + + _groupDatabase.createGroup(MY_GROUP); + + groups = _groupDatabase.getAllGroups(); + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP)); + + FileGroupDatabase newGroupDatabase = new FileGroupDatabase(); + newGroupDatabase.setGroupFile(_groupFile); + + Set<String> newGroups = newGroupDatabase.getAllGroups(); + assertEquals(1, newGroups.size()); + assertTrue(newGroups.contains(MY_GROUP)); + } + + public void testRemoveGroupPersistedToFile() throws Exception + { + writeAndSetGroupFile("myGroup1.users", "user1,user2", + "myGroup2.users", "user1,user2"); + + Set<String> groups = _groupDatabase.getAllGroups(); + assertEquals(2, groups.size()); + + Set<String> groupsForUser1 = _groupDatabase.getGroupsForUser(USER1); + assertEquals(2, groupsForUser1.size()); + + _groupDatabase.removeGroup(MY_GROUP1); + + groups = _groupDatabase.getAllGroups(); + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP2)); + + groupsForUser1 = _groupDatabase.getGroupsForUser(USER1); + assertEquals(1, groupsForUser1.size()); + + FileGroupDatabase newGroupDatabase = new FileGroupDatabase(); + newGroupDatabase.setGroupFile(_groupFile); + + Set<String> newGroups = newGroupDatabase.getAllGroups(); + assertEquals(1, newGroups.size()); + assertTrue(newGroups.contains(MY_GROUP2)); + + Set<String> newGroupsForUser1 = newGroupDatabase.getGroupsForUser(USER1); + assertEquals(1, newGroupsForUser1.size()); + assertTrue(newGroupsForUser1.contains(MY_GROUP2)); +} + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _groupFile = createEmptyTestGroupFile(); + } + + private void writeAndSetGroupFile(String... groupAndUsers) throws Exception + { + writeGroupFile(groupAndUsers); + _groupDatabase.setGroupFile(_groupFile); + } + + private void writeGroupFile(String... groupAndUsers) throws Exception + { + if (groupAndUsers.length % 2 != 0) + { + throw new IllegalArgumentException("Number of groupAndUsers must be even"); + } + + Properties props = new Properties(); + for (int i = 0 ; i < groupAndUsers.length; i=i+2) + { + String group = groupAndUsers[i]; + String users = groupAndUsers[i+1]; + props.put(group, users); + } + + props.store(new FileOutputStream(_groupFile), "test group file"); + } + + private String createEmptyTestGroupFile() throws IOException + { + File tmpGroupFile = File.createTempFile("groups", "grp"); + tmpGroupFile.deleteOnExit(); + + return tmpGroupFile.getAbsolutePath(); + } + + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + + if (_groupFile != null) + { + File groupFile = new File(_groupFile); + if (groupFile.exists()) + { + groupFile.delete(); + } + } + } + +} diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/group/FileGroupManagerTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/group/FileGroupManagerTest.java new file mode 100644 index 0000000000..165ecb098f --- /dev/null +++ b/java/broker/src/test/java/org/apache/qpid/server/security/group/FileGroupManagerTest.java @@ -0,0 +1,236 @@ +/* + * 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.group; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.security.Principal; +import java.util.Properties; +import java.util.Set; + +import org.apache.commons.configuration.CompositeConfiguration; +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.security.auth.UsernamePrincipal; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +public class FileGroupManagerTest extends InternalBrokerBaseCase +{ + private static final String MYGROUP_USERS = "user1"; + private static final String MY_GROUP = "myGroup.users"; + private static final String MY_GROUP2 = "myGroup2.users"; + private File _tmpGroupFile; + private FileGroupManager _manager; + + @Override + public void tearDown() throws Exception + { + super.tearDown(); +//TODO: implement closable +// if (_manager != null) +// { +// _manager.close(); +// } + + if (_tmpGroupFile != null) + { + if (_tmpGroupFile.exists()) + { + _tmpGroupFile.delete(); + } + } + } + + public void testValidGroupFile() throws Exception + { + final String groupFileName = writeGroupFile(); + final ConfigurationPlugin config = getConfig("groupFile", groupFileName); + + _manager = FileGroupManager.FACTORY.newInstance(config); + assertNotNull(_manager); + } + + public void testNonExistentGroupFile() throws Exception + { + final String filePath = "/does.not.exist/"; + final File fileFile = new File(filePath); + + assertFalse("File already exists", fileFile.exists()); + final ConfigurationPlugin config = getConfig("groupFile", filePath); + + try + { + _manager = FileGroupManager.FACTORY.newInstance(config); + fail("expected exception was not thrown"); + } + catch(ConfigurationException ce) + { + assertNotNull(ce.getCause()); + assertTrue(ce.getCause() instanceof FileNotFoundException); + } + } + + public void testGetGroupPrincipalsForUser() throws Exception + { + final String groupFileName = writeGroupFile(); + final ConfigurationPlugin config = getConfig("groupFile", groupFileName); + _manager = FileGroupManager.FACTORY.newInstance(config); + + Set<Principal> principals = _manager.getGroupPrincipalsForUser("user1"); + assertEquals(1, principals.size()); + assertTrue(principals.contains(new GroupPrincipal("myGroup"))); + } + + public void testGetUserPrincipalsForGroup() throws Exception + { + final String groupFileName = writeGroupFile(); + final ConfigurationPlugin config = getConfig("groupFile", groupFileName); + _manager = FileGroupManager.FACTORY.newInstance(config); + + Set<Principal> principals = _manager.getUserPrincipalsForGroup("myGroup"); + assertEquals(1, principals.size()); + assertTrue(principals.contains(new UsernamePrincipal("user1"))); + } + + public void testGetGroupPrincipals() throws Exception + { + final String groupFileName = writeGroupFile(MY_GROUP, MYGROUP_USERS, MY_GROUP2, MYGROUP_USERS); + final ConfigurationPlugin config = getConfig("groupFile", groupFileName); + _manager = FileGroupManager.FACTORY.newInstance(config); + + Set<Principal> principals = _manager.getGroupPrincipals(); + assertEquals(2, principals.size()); + assertTrue(principals.contains(new GroupPrincipal("myGroup"))); + assertTrue(principals.contains(new GroupPrincipal("myGroup2"))); + } + + public void testCreateGroup() throws Exception + { + final String groupFileName = writeGroupFile(); + final ConfigurationPlugin config = getConfig("groupFile", groupFileName); + _manager = FileGroupManager.FACTORY.newInstance(config); + + Set<Principal> principals = _manager.getGroupPrincipals(); + assertEquals(1, principals.size()); + + _manager.createGroup("myGroup2"); + + principals = _manager.getGroupPrincipals(); + assertEquals(2, principals.size()); + assertTrue(principals.contains(new GroupPrincipal("myGroup2"))); + } + + public void testRemoveGroup() throws Exception + { + final String groupFileName = writeGroupFile(MY_GROUP, MYGROUP_USERS); + final ConfigurationPlugin config = getConfig("groupFile", groupFileName); + _manager = FileGroupManager.FACTORY.newInstance(config); + + Set<Principal> principals = _manager.getGroupPrincipals(); + assertEquals(1, principals.size()); + + _manager.removeGroup("myGroup"); + + principals = _manager.getGroupPrincipals(); + assertEquals(0, principals.size()); + } + + public void testAddUserToGroup() throws Exception + { + final String groupFileName = writeGroupFile(MY_GROUP, MYGROUP_USERS); + final ConfigurationPlugin config = getConfig("groupFile", groupFileName); + _manager = FileGroupManager.FACTORY.newInstance(config); + + Set<Principal> principals = _manager.getUserPrincipalsForGroup("myGroup"); + assertEquals(1, principals.size()); + assertFalse(principals.contains(new UsernamePrincipal("user2"))); + + _manager.addUserToGroup("user2", "myGroup"); + + principals = _manager.getUserPrincipalsForGroup("myGroup"); + assertEquals(2, principals.size()); + assertTrue(principals.contains(new UsernamePrincipal("user2"))); + } + + public void testRemoveUserInGroup() throws Exception + { + final String groupFileName = writeGroupFile(MY_GROUP, MYGROUP_USERS); + final ConfigurationPlugin config = getConfig("groupFile", groupFileName); + _manager = FileGroupManager.FACTORY.newInstance(config); + + Set<Principal> principals = _manager.getUserPrincipalsForGroup("myGroup"); + assertEquals(1, principals.size()); + assertTrue(principals.contains(new UsernamePrincipal("user1"))); + + _manager.removeUserFromGroup("user1", "myGroup"); + + principals = _manager.getUserPrincipalsForGroup("myGroup"); + assertEquals(0, principals.size()); + } + + private ConfigurationPlugin getConfig(final String argName, final String argValue) throws Exception + { + final ConfigurationPlugin config = new FileGroupManager.FileGroupManagerConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + + if (argName != null) + { + xmlconfig.addProperty("file-group-manager.attributes.attribute.name", argName); + xmlconfig.addProperty("file-group-manager.attributes.attribute.value", argValue); + } + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + config.setConfiguration("security", xmlconfig); + return config; + } + + private String writeGroupFile() throws Exception + { + return writeGroupFile(MY_GROUP, MYGROUP_USERS); + } + + private String writeGroupFile(String... groupAndUsers) throws Exception + { + if (groupAndUsers.length % 2 != 0) + { + throw new IllegalArgumentException("Number of groupAndUsers must be even"); + } + + _tmpGroupFile = File.createTempFile("groups", "grp"); + _tmpGroupFile.deleteOnExit(); + + Properties props = new Properties(); + for (int i = 0 ; i < groupAndUsers.length; i=i+2) + { + String group = groupAndUsers[i]; + String users = groupAndUsers[i+1]; + props.put(group, users); + } + + props.store(new FileOutputStream(_tmpGroupFile), "test group file"); + + return _tmpGroupFile.getCanonicalPath(); + } +} diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/group/GroupPrincipalAccessorTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/group/GroupPrincipalAccessorTest.java new file mode 100644 index 0000000000..6f9b73845d --- /dev/null +++ b/java/broker/src/test/java/org/apache/qpid/server/security/group/GroupPrincipalAccessorTest.java @@ -0,0 +1,81 @@ +/* + * 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.group; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestCase; + +import org.apache.qpid.server.security.group.GroupManager; +import org.apache.qpid.server.security.group.GroupPrincipalAccessor; + +public class GroupPrincipalAccessorTest extends TestCase +{ + private static final String USERNAME = "username"; + + private GroupManager _groupManager1 = mock(GroupManager.class); + private GroupManager _groupManager2 = mock(GroupManager.class); + + private Principal _group1 = mock(Principal.class); + private Principal _group2 = mock(Principal.class); + + @Override + public void setUp() + { + when(_groupManager1.getGroupPrincipalsForUser(USERNAME)).thenReturn(Collections.singleton(_group1)); + when(_groupManager2.getGroupPrincipalsForUser(USERNAME)).thenReturn(Collections.singleton(_group2)); + } + + public void testGetGroupPrincipals() + { + getAndAssertGroupPrincipals(_group1, _group2); + } + + public void testGetGroupPrincipalsWhenAGroupManagerReturnsNull() + { + when(_groupManager1.getGroupPrincipalsForUser(USERNAME)).thenReturn(null); + + getAndAssertGroupPrincipals(_group2); + } + + public void testGetGroupPrincipalsWhenAGroupManagerReturnsEmptySet() + { + when(_groupManager2.getGroupPrincipalsForUser(USERNAME)).thenReturn(new HashSet<Principal>()); + + getAndAssertGroupPrincipals(_group1); + } + + private void getAndAssertGroupPrincipals(Principal... expectedGroups) + { + GroupPrincipalAccessor groupPrincipalAccessor = new GroupPrincipalAccessor(Arrays.asList(_groupManager1, _groupManager2)); + + Set<Principal> actualGroupPrincipals = groupPrincipalAccessor.getGroupPrincipals(USERNAME); + + Set<Principal> expectedGroupPrincipals = new HashSet<Principal>(Arrays.asList(expectedGroups)); + + assertEquals(expectedGroupPrincipals, actualGroupPrincipals); + } +} diff --git a/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/GroupPrincipalTest.java b/java/broker/src/test/java/org/apache/qpid/server/security/group/GroupPrincipalTest.java index 076b7c9248..d285a0797a 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/GroupPrincipalTest.java +++ b/java/broker/src/test/java/org/apache/qpid/server/security/group/GroupPrincipalTest.java @@ -7,9 +7,9 @@ * 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 @@ -18,7 +18,9 @@ * under the License. * */ -package org.apache.qpid.server.security.auth.sasl; +package org.apache.qpid.server.security.group; + +import org.apache.qpid.server.security.auth.UsernamePrincipal; import junit.framework.TestCase; @@ -34,7 +36,7 @@ public class GroupPrincipalTest extends TestCase { final GroupPrincipal principal = new GroupPrincipal("group"); final UsernamePrincipal user = new UsernamePrincipal("name"); - + try { principal.addMember(user); @@ -45,7 +47,7 @@ public class GroupPrincipalTest extends TestCase // PASS } } - + public void testEqualitySameName() { final String string = "string"; @@ -80,7 +82,7 @@ public class GroupPrincipalTest extends TestCase assertFalse(principal.equals(null)); } - + } diff --git a/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java b/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java index a64ab620ab..411ed81d2a 100644 --- a/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java +++ b/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java @@ -33,10 +33,12 @@ import org.apache.qpid.server.logging.actors.CurrentActor; import org.apache.qpid.server.logging.actors.GenericActor; import org.apache.qpid.server.plugins.PluginManager; import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.server.security.auth.database.PropertiesPrincipalDatabase; import org.apache.qpid.server.security.auth.manager.AuthenticationManager; import org.apache.qpid.server.security.auth.manager.IAuthenticationManagerRegistry; import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; +import org.apache.qpid.server.security.group.GroupPrincipalAccessor; import java.util.Properties; @@ -58,7 +60,7 @@ public class TestApplicationRegistry extends ApplicationRegistry @Override protected IAuthenticationManagerRegistry createAuthenticationManagerRegistry( - ServerConfiguration _configuration, PluginManager _pluginManager) + ServerConfiguration configuration, PluginManager pluginManager, final GroupPrincipalAccessor groupPrincipalAccessor) throws ConfigurationException { final Properties users = new Properties(); @@ -98,10 +100,9 @@ public class TestApplicationRegistry extends ApplicationRegistry } @Override - public AuthenticationManager getAuthenticationManager( - SocketAddress address) + public SubjectCreator getSubjectCreator(SocketAddress address) { - return pdam; + return new SubjectCreator(pdam, groupPrincipalAccessor); } @Override |
