You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by ce...@apache.org on 2014/02/24 16:30:51 UTC
[4/4] git commit: Fix for
https://issues.apache.org/jira/browse/AMQ-3621 - Integrate Apache Shiro with
ActiveMQ as "security solution" - many thanks to Les Hazlewood (lhazlewood)
for the patch!
Fix for https://issues.apache.org/jira/browse/AMQ-3621 - Integrate Apache Shiro with ActiveMQ as "security solution" - many thanks to Les Hazlewood (lhazlewood) for the patch!
Commit dev changes to branch.
Signed-off-by: Christian Posta <ch...@gmail.com>
Added shiro support to 5.9.x
Signed-off-by: Christian Posta <ch...@gmail.com>
Added Shiro deps to dist zip/tarball and 'all' .jar
Signed-off-by: Christian Posta <ch...@gmail.com>
updated version number to reflect correctly packaging Shiro features
Signed-off-by: Christian Posta <ch...@gmail.com>
Handled case when adding a producer might not have a specified destination.
Signed-off-by: Christian Posta <ch...@gmail.com>
Merged latest Connection API changes from trunk.
Signed-off-by: Christian Posta <ch...@gmail.com>
removed accidental addition of Stormpath's m2 repo
Signed-off-by: Christian Posta <ch...@gmail.com>
reverted accidental deletion of <distributionManagement>
Signed-off-by: Christian Posta <ch...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/f9451e56
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/f9451e56
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/f9451e56
Branch: refs/heads/trunk
Commit: f9451e56e20f8d1712ed09c579d493feee514898
Parents: e7e317d
Author: Les Hazlewood <le...@hazlewood.com>
Authored: Wed Dec 11 18:42:27 2013 -0800
Committer: Christian Posta <ch...@gmail.com>
Committed: Mon Feb 24 08:17:32 2014 -0700
----------------------------------------------------------------------
activemq-all/pom.xml | 8 +
activemq-shiro/pom.xml | 178 ++++++++
.../activemq/shiro/ConnectionReference.java | 66 +++
.../shiro/DefaultSecurityContextFactory.java | 45 +++
.../activemq/shiro/SecurityContextFactory.java | 56 +++
.../apache/activemq/shiro/SecurityFilter.java | 41 ++
.../org/apache/activemq/shiro/ShiroPlugin.java | 246 +++++++++++
.../shiro/authc/AuthenticationFilter.java | 137 +++++++
.../shiro/authc/AuthenticationPolicy.java | 58 +++
.../shiro/authc/AuthenticationTokenFactory.java | 46 +++
.../authc/DefaultAuthenticationPolicy.java | 212 ++++++++++
.../DefaultAuthenticationTokenFactory.java | 59 +++
.../org/apache/activemq/shiro/authz/Action.java | 35 ++
.../shiro/authz/ActionPermissionResolver.java | 50 +++
.../shiro/authz/ActiveMQPermissionResolver.java | 57 +++
.../shiro/authz/ActiveMQWildcardPermission.java | 275 +++++++++++++
.../shiro/authz/AuthorizationFilter.java | 226 +++++++++++
.../activemq/shiro/authz/DestinationAction.java | 90 +++++
.../DestinationActionPermissionResolver.java | 272 +++++++++++++
.../activemq/shiro/env/EnvironmentFilter.java | 45 +++
.../activemq/shiro/env/IniEnvironment.java | 136 +++++++
.../mgt/DefaultActiveMqSecurityManager.java | 41 ++
.../session/mgt/DisabledSessionManager.java | 39 ++
.../shiro/subject/ConnectionSubjectFactory.java | 48 +++
.../subject/ConnectionSubjectResolver.java | 67 +++
.../DefaultConnectionSubjectFactory.java | 52 +++
.../subject/SubjectConnectionReference.java | 46 +++
.../activemq/shiro/subject/SubjectFilter.java | 119 ++++++
.../activemq/shiro/subject/SubjectResolver.java | 32 ++
.../shiro/subject/SubjectSecurityContext.java | 89 ++++
.../activemq/shiro/ConnectionReferenceTest.java | 43 ++
.../activemq/shiro/SecurityFilterTest.java | 37 ++
.../apache/activemq/shiro/ShiroPluginTest.java | 403 +++++++++++++++++++
.../shiro/authc/AuthenticationFilterTest.java | 86 ++++
.../authc/DefaultAuthenticationPolicyTest.java | 339 ++++++++++++++++
.../authz/ActiveMQPermissionResolverTest.java | 53 +++
.../authz/ActiveMQWildcardPermissionTest.java | 158 ++++++++
.../shiro/authz/AuthorizationFilterTest.java | 364 +++++++++++++++++
...DestinationActionPermissionResolverTest.java | 153 +++++++
.../shiro/authz/DestinationActionTest.java | 58 +++
.../shiro/env/EnvironmentFilterTest.java | 30 ++
.../activemq/shiro/env/IniEnvironmentTest.java | 121 ++++++
.../session/mgt/DisabledSessionManagerTest.java | 47 +++
.../subject/ConnectionSubjectResolverTest.java | 80 ++++
.../DefaultConnectionSubjectFactoryTest.java | 55 +++
.../activemq/shiro/subject/SubjectAdapter.java | 185 +++++++++
.../subject/SubjectConnectionReferenceTest.java | 33 ++
.../shiro/subject/SubjectFilterTest.java | 59 +++
.../subject/SubjectSecurityContextTest.java | 58 +++
.../src/test/resources/empty.shiro.ini | 20 +
.../src/test/resources/minimal.shiro.ini | 63 +++
.../src/test/resources/nosystem.shiro.ini | 38 ++
.../activemq/shiro/embedded-ini-config.xml | 95 +++++
.../activemq/shiro/external-ini-config.xml | 53 +++
.../org/apache/activemq/shiro/no-ini-config.xml | 78 ++++
activemq-shiro/src/test/resources/shiro.ini | 64 +++
assembly/pom.xml | 12 +
assembly/src/main/descriptors/common-bin.xml | 7 +-
pom.xml | 36 ++
59 files changed, 5698 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-all/pom.xml
----------------------------------------------------------------------
diff --git a/activemq-all/pom.xml b/activemq-all/pom.xml
index 3007874..1eb5430 100644
--- a/activemq-all/pom.xml
+++ b/activemq-all/pom.xml
@@ -106,6 +106,7 @@
<include>${project.groupId}:activemq-jaas</include>
<include>${project.groupId}:activemq-broker</include>
<include>${project.groupId}:activemq-console</include>
+ <include>${project.groupId}:activemq-shiro</include>
<include>${project.groupId}:activemq-spring</include>
<include>${project.groupId}:activemq-pool</include>
<include>${project.groupId}:activemq-jms-pool</include>
@@ -231,6 +232,13 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
+ <artifactId>activemq-shiro</artifactId>
+ <version>${project.version}</version>
+ <classifier>sources</classifier>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
<artifactId>activemq-spring</artifactId>
<version>${project.version}</version>
<classifier>sources</classifier>
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/pom.xml
----------------------------------------------------------------------
diff --git a/activemq-shiro/pom.xml b/activemq-shiro/pom.xml
new file mode 100644
index 0000000..c273b9e
--- /dev/null
+++ b/activemq-shiro/pom.xml
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.activemq</groupId>
+ <artifactId>activemq-parent</artifactId>
+ <version>5.10-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>activemq-shiro</artifactId>
+ <packaging>bundle</packaging>
+ <name>ActiveMQ :: Shiro</name>
+ <description>ActiveMQ secured by Apache Shiro</description>
+
+ <dependencies>
+
+ <!-- =============================== -->
+ <!-- Required Dependencies -->
+ <!-- =============================== -->
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>activemq-broker</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-spring</artifactId>
+ </dependency>
+
+ <!-- =============================== -->
+ <!-- Optional Dependencies -->
+ <!-- =============================== -->
+ <!-- <dependency>
+ <groupId>com.thoughtworks.xstream</groupId>
+ <artifactId>xstream</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jettison</groupId>
+ <artifactId>jettison</artifactId>
+ <optional>true</optional>
+ </dependency> -->
+
+ <!-- =============================== -->
+ <!-- Testing Dependencies -->
+ <!-- =============================== -->
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>activemq-kahadb-store</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>activemq-broker</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>activemq-unit-tests</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <!-- <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>activemq-jaas</artifactId>
+ <scope>test</scope>
+ </dependency> -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>org.apache.activemq.shiro</Bundle-SymbolicName>
+ <Export-Package>org.apache.activemq.shiro*;version=${project.version};-noimport:=true;-split-package:=merge-first</Export-Package>
+ <Import-Package>
+ org.apache.activemq*;version=${project.version};resolution:=optional,
+ org.apache.shiro*;version="[1.2,2]",
+ *
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <!-- <build>
+ <resources>
+ <resource>
+ <directory>${project.basedir}/src/main/resources</directory>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>${project.basedir}/src/main/filtered-resources</directory>
+ <filtering>true</filtering>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ </resource>
+ </resources>
+
+ <plugins>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkMode>always</forkMode>
+ <argLine>${surefire.argLine}</argLine>
+ <runOrder>alphabetical</runOrder>
+ <failIfNoTests>false</failIfNoTests>
+ <systemProperties>
+ <property>
+ <name>org.apache.activemq.default.directory.prefix</name>
+ <value>target/</value>
+ </property>
+ <!- - Uncomment the following if you want to configure custom logging (using src/test/resources/log4j.properties)
+ while running mvn:test
+ Note: if you want to see log messages on the console window remove
+ "redirectTestOutputToFile" from the parent pom
+ - ->
+ <!- -
+ <property>
+ <name>log4j.configuration</name>
+ <value>file:target/test-classes/log4j.properties</value>
+ </property>
+ - ->
+ </systemProperties>
+ <includes>
+ <include>**/*Test.*</include>
+ </includes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build> -->
+</project>
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/ConnectionReference.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/ConnectionReference.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/ConnectionReference.java
new file mode 100644
index 0000000..06c277a
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/ConnectionReference.java
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.shiro;
+
+import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.shiro.env.Environment;
+
+/**
+ * A reference (handle) to a client's {@link ConnectionContext} and {@link ConnectionInfo} as well as the Shiro
+ * {@link Environment}.
+ * <p/>
+ * This implementation primarily exists as a <a href="http://sourcemaking.com/refactoring/introduce-parameter-object">
+ * Parameter Object Design Pattern</a> implementation to eliminate long parameter lists, but provides additional
+ * benefits, such as immutability and non-null guarantees, and possibility for future data without forcing method
+ * signature changes.
+ *
+ * @since 5.10.0
+ */
+public class ConnectionReference {
+
+ private final ConnectionContext connectionContext;
+ private final ConnectionInfo connectionInfo;
+ private final Environment environment;
+
+ public ConnectionReference(ConnectionContext connCtx, ConnectionInfo connInfo, Environment environment) {
+ if (connCtx == null) {
+ throw new IllegalArgumentException("ConnectionContext argument cannot be null.");
+ }
+ if (connInfo == null) {
+ throw new IllegalArgumentException("ConnectionInfo argument cannot be null.");
+ }
+ if (environment == null) {
+ throw new IllegalArgumentException("Environment argument cannot be null.");
+ }
+ this.connectionContext = connCtx;
+ this.connectionInfo = connInfo;
+ this.environment = environment;
+ }
+
+ public ConnectionContext getConnectionContext() {
+ return connectionContext;
+ }
+
+ public ConnectionInfo getConnectionInfo() {
+ return connectionInfo;
+ }
+
+ public Environment getEnvironment() {
+ return environment;
+ }
+}
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/DefaultSecurityContextFactory.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/DefaultSecurityContextFactory.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/DefaultSecurityContextFactory.java
new file mode 100644
index 0000000..2ac65bd
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/DefaultSecurityContextFactory.java
@@ -0,0 +1,45 @@
+/**
+ * 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.activemq.shiro;
+
+import org.apache.activemq.security.SecurityContext;
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.activemq.shiro.subject.SubjectSecurityContext;
+import org.apache.shiro.env.Environment;
+
+/**
+ * Default {@code SecurityContextFactory} implementation that creates
+ * {@link org.apache.activemq.shiro.subject.SubjectSecurityContext} instances, allowing the connection's {@code Subject} and the Shiro
+ * {@link Environment} to be available to downstream security broker filters.
+ *
+ * @since 5.10.0
+ */
+public class DefaultSecurityContextFactory implements SecurityContextFactory {
+
+ /**
+ * Returns a new {@link org.apache.activemq.shiro.subject.SubjectSecurityContext} instance, allowing the connection's {@code Subject} and the Shiro
+ * {@link Environment} to be available to downstream security broker filters.
+ *
+ * @param conn the subject's connection
+ * @return a new {@link org.apache.activemq.shiro.subject.SubjectSecurityContext} instance, allowing the connection's {@code Subject} and the Shiro
+ * {@link Environment} to be available to downstream security broker filters.
+ */
+ @Override
+ public SecurityContext createSecurityContext(SubjectConnectionReference conn) {
+ return new SubjectSecurityContext(conn);
+ }
+}
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityContextFactory.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityContextFactory.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityContextFactory.java
new file mode 100644
index 0000000..044fb03
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityContextFactory.java
@@ -0,0 +1,56 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.shiro;
+
+import org.apache.activemq.security.SecurityContext;
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * A {@code SecurityContextFactory} returns a {@link SecurityContext} instance that retains a client
+ * connection's {@link Subject} instance.
+ * <p/>
+ * It should be noted that at the time a {@code SecurityContextFactory} is invoked, a {@link Subject} is already
+ * associated with the client connection. A {@code SecurityContextFactory} is merely responsible for creating
+ * a Shiro-specific {@link org.apache.activemq.security.SecurityContext SecurityContext} instance.
+ * <p/>
+ * The returned {@code SecurityContext} instance will then be made available to any downstream Broker Filters via
+ * {@code connectionContext.}{@link org.apache.activemq.broker.ConnectionContext#getSecurityContext() getSecurityContext()}
+ * to ensure it may be used for Shiro-based security checks.
+ *
+ * @see org.apache.activemq.shiro.subject.SubjectSecurityContext
+ * @since 5.10.0
+ */
+public interface SecurityContextFactory {
+
+ /**
+ * Creates a new {@link SecurityContext} retaining the client connection's {@link Subject} instance.
+ * <p/>
+ * It should be noted that at the time a {@code SecurityContextFactory} is invoked, a {@code Subject} is already
+ * associated with the client connection. A {@code SecurityContextFactory} is merely responsible for creating
+ * a Shiro-specific {@link org.apache.activemq.security.SecurityContext SecurityContext} instance.
+ * <p/>
+ * The returned {@code SecurityContext} instance will then be made available to any downstream Broker Filters via
+ * {@code connectionContext.}{@link org.apache.activemq.broker.ConnectionContext#getSecurityContext() getSecurityContext()}
+ * to ensure it may be used for Shiro-based security checks.
+ *
+ * @param ref the client's connection and subject
+ * @return a new {@link SecurityContext} retaining the client connection's {@link Subject} instance.
+ * @see org.apache.activemq.shiro.subject.SubjectSecurityContext
+ */
+ SecurityContext createSecurityContext(SubjectConnectionReference ref);
+}
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityFilter.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityFilter.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityFilter.java
new file mode 100644
index 0000000..8fbd9de
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityFilter.java
@@ -0,0 +1,41 @@
+/**
+ * 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.activemq.shiro;
+
+import org.apache.activemq.broker.MutableBrokerFilter;
+
+/**
+ * @since 5.10.0
+ */
+public abstract class SecurityFilter extends MutableBrokerFilter {
+
+ private volatile boolean enabled;
+
+ public SecurityFilter() {
+ super(null);
+ this.enabled = true;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/ShiroPlugin.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/ShiroPlugin.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/ShiroPlugin.java
new file mode 100644
index 0000000..95e69b1
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/ShiroPlugin.java
@@ -0,0 +1,246 @@
+/**
+ * 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.activemq.shiro;
+
+import org.apache.activemq.ConfigurationException;
+import org.apache.activemq.broker.Broker;
+import org.apache.activemq.broker.BrokerPluginSupport;
+import org.apache.activemq.shiro.authc.AuthenticationFilter;
+import org.apache.activemq.shiro.authc.AuthenticationPolicy;
+import org.apache.activemq.shiro.authc.DefaultAuthenticationPolicy;
+import org.apache.activemq.shiro.authz.AuthorizationFilter;
+import org.apache.activemq.shiro.env.IniEnvironment;
+import org.apache.activemq.shiro.subject.ConnectionSubjectFactory;
+import org.apache.activemq.shiro.subject.DefaultConnectionSubjectFactory;
+import org.apache.activemq.shiro.subject.SubjectFilter;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.env.Environment;
+import org.apache.shiro.mgt.SecurityManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @since 5.10.0
+ */
+public class ShiroPlugin extends BrokerPluginSupport {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ShiroPlugin.class);
+
+ private volatile boolean enabled = true;
+
+ private Broker broker; //the downstream broker after any/all Shiro-specific broker filters
+
+ private SecurityManager securityManager;
+ private Environment environment;
+ private IniEnvironment iniEnvironment; //only used if the above environment instance is not explicitly configured
+
+ private SubjectFilter subjectFilter;
+
+ private AuthenticationFilter authenticationFilter;
+
+ private AuthorizationFilter authorizationFilter;
+
+ public ShiroPlugin() {
+
+ //Default if this.environment is not configured. See the ensureEnvironment() method below.
+ iniEnvironment = new IniEnvironment();
+
+ authorizationFilter = new AuthorizationFilter();
+
+ // we want to share one AuthenticationPolicy instance across both the AuthenticationFilter and the
+ // ConnectionSubjectFactory:
+ AuthenticationPolicy authcPolicy = new DefaultAuthenticationPolicy();
+
+ authenticationFilter = new AuthenticationFilter();
+ authenticationFilter.setAuthenticationPolicy(authcPolicy);
+ authenticationFilter.setNext(authorizationFilter);
+
+ subjectFilter = new SubjectFilter();
+ DefaultConnectionSubjectFactory subjectFactory = new DefaultConnectionSubjectFactory();
+ subjectFactory.setAuthenticationPolicy(authcPolicy);
+ subjectFilter.setConnectionSubjectFactory(subjectFactory);
+ subjectFilter.setNext(authenticationFilter);
+ }
+
+ public SubjectFilter getSubjectFilter() {
+ return subjectFilter;
+ }
+
+ public void setSubjectFilter(SubjectFilter subjectFilter) {
+ this.subjectFilter = subjectFilter;
+ this.subjectFilter.setNext(this.authenticationFilter);
+ }
+
+ public AuthenticationFilter getAuthenticationFilter() {
+ return authenticationFilter;
+ }
+
+ public void setAuthenticationFilter(AuthenticationFilter authenticationFilter) {
+ this.authenticationFilter = authenticationFilter;
+ this.authenticationFilter.setNext(this.authorizationFilter);
+ this.subjectFilter.setNext(authenticationFilter);
+ }
+
+ public AuthorizationFilter getAuthorizationFilter() {
+ return authorizationFilter;
+ }
+
+ public void setAuthorizationFilter(AuthorizationFilter authorizationFilter) {
+ this.authorizationFilter = authorizationFilter;
+ this.authorizationFilter.setNext(this.broker);
+ this.authenticationFilter.setNext(authorizationFilter);
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ if (isInstalled()) {
+ //we're running, so apply the changes now:
+ applyEnabled(enabled);
+ }
+ }
+
+ public boolean isEnabled() {
+ if (isInstalled()) {
+ return getNext() == this.subjectFilter;
+ }
+ return enabled;
+ }
+
+ private void applyEnabled(boolean enabled) {
+ if (enabled) {
+ //ensure the SubjectFilter and downstream filters are used:
+ super.setNext(this.subjectFilter);
+ } else {
+ //Shiro is not enabled, restore the original downstream broker:
+ super.setNext(this.broker);
+ }
+ }
+
+ public Environment getEnvironment() {
+ return environment;
+ }
+
+ public void setEnvironment(Environment environment) {
+ this.environment = environment;
+ }
+
+ public SecurityManager getSecurityManager() {
+ return securityManager;
+ }
+
+ public void setSecurityManager(SecurityManager securityManager) {
+ this.securityManager = securityManager;
+ }
+
+ public void setIni(Ini ini) {
+ this.iniEnvironment.setIni(ini);
+ }
+
+ public void setIniConfig(String iniConfig) {
+ this.iniEnvironment.setIniConfig(iniConfig);
+ }
+
+ public void setIniResourcePath(String resourcePath) {
+ this.iniEnvironment.setIniResourcePath(resourcePath);
+ }
+
+ // ===============================================================
+ // Authentication Configuration
+ // ===============================================================
+ public void setAuthenticationEnabled(boolean authenticationEnabled) {
+ this.authenticationFilter.setEnabled(authenticationEnabled);
+ }
+
+ public boolean isAuthenticationEnabled() {
+ return this.authenticationFilter.isEnabled();
+ }
+
+ public AuthenticationPolicy getAuthenticationPolicy() {
+ return authenticationFilter.getAuthenticationPolicy();
+ }
+
+ public void setAuthenticationPolicy(AuthenticationPolicy authenticationPolicy) {
+ authenticationFilter.setAuthenticationPolicy(authenticationPolicy);
+ //also set it on the ConnectionSubjectFactory:
+ ConnectionSubjectFactory factory = subjectFilter.getConnectionSubjectFactory();
+ if (factory instanceof DefaultConnectionSubjectFactory) {
+ ((DefaultConnectionSubjectFactory) factory).setAuthenticationPolicy(authenticationPolicy);
+ }
+ }
+
+ // ===============================================================
+ // Authorization Configuration
+ // ===============================================================
+ public void setAuthorizationEnabled(boolean authorizationEnabled) {
+ this.authorizationFilter.setEnabled(authorizationEnabled);
+ }
+
+ public boolean isAuthorizationEnabled() {
+ return this.authorizationFilter.isEnabled();
+ }
+
+ private Environment ensureEnvironment() throws ConfigurationException {
+ if (this.environment != null) {
+ return this.environment;
+ }
+
+ //this.environment is null - set it:
+ if (this.securityManager != null) {
+ this.environment = new Environment() {
+ @Override
+ public SecurityManager getSecurityManager() {
+ return ShiroPlugin.this.securityManager;
+ }
+ };
+ return this.environment;
+ }
+
+ this.iniEnvironment.init(); //will automatically catch any config errors and throw.
+
+ this.environment = iniEnvironment;
+
+ return this.iniEnvironment;
+ }
+
+ @Override
+ public Broker installPlugin(Broker broker) throws Exception {
+
+ Environment environment = ensureEnvironment();
+
+ this.authorizationFilter.setEnvironment(environment);
+ this.authenticationFilter.setEnvironment(environment);
+ this.subjectFilter.setEnvironment(environment);
+
+ this.broker = broker;
+ this.authorizationFilter.setNext(broker);
+ this.authenticationFilter.setNext(this.authorizationFilter);
+ this.subjectFilter.setNext(this.authenticationFilter);
+
+ Broker next = this.subjectFilter;
+ if (!this.enabled) {
+ //not enabled at startup - default to the original broker:
+ next = broker;
+ }
+
+ setNext(next);
+ return this;
+ }
+
+ private boolean isInstalled() {
+ return getNext() != null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationFilter.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationFilter.java
new file mode 100644
index 0000000..de68820
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationFilter.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.activemq.shiro.authc;
+
+import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.security.SecurityContext;
+import org.apache.activemq.shiro.ConnectionReference;
+import org.apache.activemq.shiro.env.EnvironmentFilter;
+import org.apache.activemq.shiro.subject.ConnectionSubjectResolver;
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.activemq.shiro.subject.SubjectSecurityContext;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.subject.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@code AuthenticationFilter} enforces if authentication is required before allowing the broker filter chain
+ * to continue.
+ * <p/>
+ * This implementation performs a connection-level authentication assertion: If the {@link Subject} associated with the
+ * connection<b>*</b> is not authenticated, and the
+ * {@link AuthenticationPolicy AuthenticationPolicy} requires the {@code Subject} to be authenticated, it will attempt
+ * to {@link Subject#login(org.apache.shiro.authc.AuthenticationToken) login} the Subject automatically. The
+ * {@link AuthenticationToken} used to login is created by the
+ * {@link #getAuthenticationTokenFactory() authenticationTokenFactory}, typically by acquiring any credentials
+ * associated with the connection.
+ * <p/>
+ * Once the connection's {@code Subject} is authenticated as necessary, the broker filter chain will continue
+ * as expected.
+ * <p/>
+ * <b>*</b>: The upstream {@link org.apache.activemq.shiro.subject.SubjectFilter} is expected to execute before this one, ensuring a Subject instance
+ * is already associated with the connection.
+ *
+ * @since 5.10.0
+ */
+public class AuthenticationFilter extends EnvironmentFilter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AuthenticationFilter.class);
+
+ private AuthenticationPolicy authenticationPolicy;
+ private AuthenticationTokenFactory authenticationTokenFactory;
+
+ public AuthenticationFilter() {
+ this.authenticationPolicy = new DefaultAuthenticationPolicy();
+ this.authenticationTokenFactory = new DefaultAuthenticationTokenFactory();
+ }
+
+ public AuthenticationPolicy getAuthenticationPolicy() {
+ return authenticationPolicy;
+ }
+
+ public void setAuthenticationPolicy(AuthenticationPolicy authenticationPolicy) {
+ this.authenticationPolicy = authenticationPolicy;
+ }
+
+ public AuthenticationTokenFactory getAuthenticationTokenFactory() {
+ return authenticationTokenFactory;
+ }
+
+ public void setAuthenticationTokenFactory(AuthenticationTokenFactory authenticationTokenFactory) {
+ this.authenticationTokenFactory = authenticationTokenFactory;
+ }
+
+ protected Subject getSubject(ConnectionReference conn) {
+ return new ConnectionSubjectResolver(conn).getSubject();
+ }
+
+ @Override
+ public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
+
+ if (isEnabled()) { //disabled means don't enforce authentication (i.e. allow anonymous access):
+
+ Subject subject = getSubject(new ConnectionReference(context, info, getEnvironment()));
+
+ if (!subject.isAuthenticated()) {
+
+ SubjectConnectionReference connection = new SubjectConnectionReference(context, info, getEnvironment(), subject);
+
+ if (this.authenticationPolicy.isAuthenticationRequired(connection)) {
+ AuthenticationToken token = this.authenticationTokenFactory.getAuthenticationToken(connection);
+ if (token == null) {
+ String msg = "Unable to obtain authentication credentials for newly established connection. " +
+ "Authentication is required.";
+ throw new AuthenticationException(msg);
+ }
+ //token is not null - login the current subject:
+ subject.login(token);
+ }
+ }
+ }
+
+ super.addConnection(context, info);
+ }
+
+ @Override
+ public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception {
+ try {
+ super.removeConnection(context, info, error);
+ } finally {
+ SecurityContext secCtx = context.getSecurityContext();
+
+ if (secCtx instanceof SubjectSecurityContext) {
+
+ SubjectSecurityContext subjectSecurityContext = (SubjectSecurityContext) secCtx;
+ Subject subject = subjectSecurityContext.getSubject();
+
+ if (subject != null) {
+ try {
+ subject.logout();
+ } catch (Throwable t) {
+ String msg = "Unable to cleanly logout connection Subject during connection removal. This is " +
+ "unexpected but not critical: it can be safely ignored because the " +
+ "connection will no longer be used.";
+ LOG.info(msg, t);
+ }
+ }
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationPolicy.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationPolicy.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationPolicy.java
new file mode 100644
index 0000000..76c8464
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationPolicy.java
@@ -0,0 +1,58 @@
+/**
+ * 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.activemq.shiro.authc;
+
+import org.apache.activemq.shiro.ConnectionReference;
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * An {@code AuthenticationPolicy} customizes the behavior of the {@link AuthenticationFilter}, such as whether or not
+ * authentication is required or how to represent trusted/known {@code Subject} identities.
+ * <p/>
+ * Most will find customizing properties on the {@link DefaultAuthenticationPolicy} easier than implementing this
+ * interface directly.
+ *
+ * @see DefaultAuthenticationPolicy
+ * @since 5.10.0
+ */
+public interface AuthenticationPolicy {
+
+ /**
+ * Allows customization of the {@code Subject} being built for the specified client
+ * connection. This allows for any pre-existing connection-specific identity or state to be applied to the
+ * {@link Subject.Builder} before the {@code Subject} instance is actually created.
+ * <p/>
+ * <b>NOTE:</b> This method is called by the {@link org.apache.activemq.shiro.subject.SubjectFilter SubjectFilter} <em>before</em> the filter chain
+ * is executed (and before an authentication attempt occurs). Implementations <b><em>MUST NOT</em></b>
+ * attempt to actually {@link org.apache.shiro.subject.Subject.Builder#buildSubject() build} the subject or perform
+ * an authentication attempt in this method.
+ *
+ * @param subjectBuilder the builder for the Subject that will be created representing the associated client connection
+ * @param ref a reference to the client's connection metadata
+ * @see org.apache.activemq.shiro.subject.SubjectFilter
+ */
+ void customizeSubject(Subject.Builder subjectBuilder, ConnectionReference ref);
+
+ /**
+ * Returns {@code true} if the connection's {@code Subject} instance should be authenticated, {@code false} otherwise.
+ *
+ * @param ref the subject's connection
+ * @return {@code true} if the connection's {@code Subject} instance should be authenticated, {@code false} otherwise.
+ */
+ boolean isAuthenticationRequired(SubjectConnectionReference ref);
+}
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationTokenFactory.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationTokenFactory.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationTokenFactory.java
new file mode 100644
index 0000000..2134929
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationTokenFactory.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.shiro.authc;
+
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.shiro.authc.AuthenticationToken;
+
+/**
+ * A {@code AuthenticationTokenFactory} inspects a newly-added ActiveMQ connection and returns a Shiro
+ * {@link AuthenticationToken} instance representing credentials associated with the connection. These credentials can
+ * be used to {@link org.apache.shiro.subject.Subject#login(org.apache.shiro.authc.AuthenticationToken) authenticate}
+ * the connection, allowing for later identity and authorization (access control) checks.
+ *
+ * @see AuthenticationFilter#addConnection(org.apache.activemq.broker.ConnectionContext, org.apache.activemq.command.ConnectionInfo)
+ * @since 5.10.0
+ */
+public interface AuthenticationTokenFactory {
+
+ /**
+ * Returns a Shiro {@code AuthenticationToken} instance that should be used to authenticate the connection's
+ * {@link org.apache.shiro.subject.Subject}, or {@code null} if no authentication information can be obtained.
+ * <p/>
+ * If no {@code AuthenticationToken} can be obtained, the connection's Subject will be considered anonymous and any
+ * downstream security checks that enforce authentication or authorization will fail (as would be expected).
+ *
+ * @param ref the subject's connection
+ * @return a Shiro {@code AuthenticationToken} instance that should be used to authenticate the connection's
+ * {@link org.apache.shiro.subject.Subject}, or {@code null} if no authentication information can be obtained.
+ * @throws Exception if there is a problem acquiring/creating an expected {@code AuthenticationToken}.
+ */
+ AuthenticationToken getAuthenticationToken(SubjectConnectionReference ref) throws Exception;
+}
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationPolicy.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationPolicy.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationPolicy.java
new file mode 100644
index 0000000..654c5e5
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationPolicy.java
@@ -0,0 +1,212 @@
+/**
+ * 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.activemq.shiro.authc;
+
+import org.apache.activemq.shiro.ConnectionReference;
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.subject.Subject;
+
+import java.util.Collection;
+
+/**
+ * @since 5.10.0
+ */
+public class DefaultAuthenticationPolicy implements AuthenticationPolicy {
+
+ private boolean vmConnectionAuthenticationRequired = false;
+ private String systemAccountUsername = "system";
+ private String systemAccountRealmName = "iniRealm";
+
+ private boolean anonymousAccessAllowed = false;
+ private String anonymousAccountUsername = "anonymous";
+ private String anonymousAccountRealmName = "iniRealm";
+
+ public boolean isVmConnectionAuthenticationRequired() {
+ return vmConnectionAuthenticationRequired;
+ }
+
+ public void setVmConnectionAuthenticationRequired(boolean vmConnectionAuthenticationRequired) {
+ this.vmConnectionAuthenticationRequired = vmConnectionAuthenticationRequired;
+ }
+
+ public String getSystemAccountUsername() {
+ return systemAccountUsername;
+ }
+
+ public void setSystemAccountUsername(String systemAccountUsername) {
+ this.systemAccountUsername = systemAccountUsername;
+ }
+
+ public String getSystemAccountRealmName() {
+ return systemAccountRealmName;
+ }
+
+ public void setSystemAccountRealmName(String systemAccountRealmName) {
+ this.systemAccountRealmName = systemAccountRealmName;
+ }
+
+ public boolean isAnonymousAccessAllowed() {
+ return anonymousAccessAllowed;
+ }
+
+ public void setAnonymousAccessAllowed(boolean anonymousAccessAllowed) {
+ this.anonymousAccessAllowed = anonymousAccessAllowed;
+ }
+
+ public String getAnonymousAccountUsername() {
+ return anonymousAccountUsername;
+ }
+
+ public void setAnonymousAccountUsername(String anonymousAccountUsername) {
+ this.anonymousAccountUsername = anonymousAccountUsername;
+ }
+
+ public String getAnonymousAccountRealmName() {
+ return anonymousAccountRealmName;
+ }
+
+ public void setAnonymousAccountRealmName(String anonymousAccountRealmName) {
+ this.anonymousAccountRealmName = anonymousAccountRealmName;
+ }
+
+ /**
+ * Returns {@code true} if the client connection has supplied credentials to authenticate itself, {@code false}
+ * otherwise.
+ *
+ * @param conn the client's connection context
+ * @return {@code true} if the client connection has supplied credentials to authenticate itself, {@code false}
+ * otherwise.
+ */
+ protected boolean credentialsAvailable(ConnectionReference conn) {
+ return conn.getConnectionInfo().getUserName() != null || conn.getConnectionInfo().getPassword() != null;
+ }
+
+ @Override
+ public boolean isAuthenticationRequired(SubjectConnectionReference conn) {
+ Subject subject = conn.getSubject();
+
+ if (subject.isAuthenticated()) {
+ //already authenticated:
+ return false;
+ }
+ //subject is not authenticated. Authentication is required by default for all accounts other than
+ //the anonymous user (if enabled) or the vm account (if enabled)
+ if (isAnonymousAccessAllowed()) {
+ if (isAnonymousAccount(subject)) {
+ return false;
+ }
+ }
+
+ if (!isVmConnectionAuthenticationRequired()) {
+ if (isSystemAccount(subject)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected boolean isAnonymousAccount(Subject subject) {
+ PrincipalCollection pc = subject.getPrincipals();
+ return pc != null && matches(pc, anonymousAccountUsername, anonymousAccountRealmName);
+ }
+
+ protected boolean isSystemAccount(Subject subject) {
+ PrincipalCollection pc = subject.getPrincipals();
+ return pc != null && matches(pc, systemAccountUsername, systemAccountRealmName);
+ }
+
+ protected boolean matches(PrincipalCollection principals, String username, String realmName) {
+ Collection realmPrincipals = principals.fromRealm(realmName);
+ if (realmPrincipals != null && !realmPrincipals.isEmpty()) {
+ if (realmPrincipals.iterator().next().equals(username)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected boolean isSystemConnection(ConnectionReference conn) {
+ String remoteAddress = conn.getConnectionContext().getConnection().getRemoteAddress();
+ return remoteAddress.startsWith("vm:");
+ }
+
+ @Override
+ public void customizeSubject(Subject.Builder subjectBuilder, ConnectionReference conn) {
+ // We only need to specify a custom identity or authentication state if a normal authentication will not occur.
+ // If the client supplied connection credentials, the AuthenticationFilter will perform a normal authentication,
+ // so we should exit immediately:
+ if (credentialsAvailable(conn)) {
+ return;
+ }
+
+ //The connection cannot be authenticated, potentially implying a system or anonymous connection. Check if so:
+ if (isAssumeIdentity(conn)) {
+ PrincipalCollection assumedIdentity = createAssumedIdentity(conn);
+ subjectBuilder.principals(assumedIdentity);
+ }
+ }
+
+ /**
+ * Returns {@code true} if an unauthenticated connection should still assume a specific identity, {@code false}
+ * otherwise. This method will <em>only</em> be called if there are no connection
+ * {@link #credentialsAvailable(ConnectionReference) credentialsAvailable}.
+ * If a client supplies connection credentials, they will always be used to authenticate the client with that
+ * identity.
+ * <p/>
+ * If {@code true} is returned, the assumed identity will be returned by
+ * {@link #createAssumedIdentity(ConnectionReference) createAssumedIdentity}.
+ * <h3>Warning</h3>
+ * This method exists primarily to support the system and anonymous accounts - it is probably unsafe to return
+ * {@code true} in most other scenarios.
+ *
+ * @param conn a reference to the client's connection
+ * @return {@code true} if an unauthenticated connection should still assume a specific identity, {@code false}
+ * otherwise.
+ */
+ protected boolean isAssumeIdentity(ConnectionReference conn) {
+ return isAnonymousAccessAllowed() ||
+ (isSystemConnection(conn) && !isVmConnectionAuthenticationRequired());
+ }
+
+ /**
+ * Returns a Shiro {@code PrincipalCollection} representing the identity to assume (without true authentication) for
+ * the specified Connection.
+ * <p/>
+ * This method is <em>only</em> called if {@link #isAssumeIdentity(ConnectionReference)} is {@code true}.
+ *
+ * @param conn a reference to the client's connection
+ * @return a Shiro {@code PrincipalCollection} representing the identity to assume (without true authentication) for
+ * the specified Connection.
+ */
+ protected PrincipalCollection createAssumedIdentity(ConnectionReference conn) {
+
+ //anonymous by default:
+ String username = anonymousAccountUsername;
+ String realmName = anonymousAccountRealmName;
+
+ //vm connections are special and should assume the system account:
+ if (isSystemConnection(conn)) {
+ username = systemAccountUsername;
+ realmName = systemAccountRealmName;
+ }
+
+ return new SimplePrincipalCollection(username, realmName);
+ }
+}
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationTokenFactory.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationTokenFactory.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationTokenFactory.java
new file mode 100644
index 0000000..1c3a0d3
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationTokenFactory.java
@@ -0,0 +1,59 @@
+/**
+ * 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.activemq.shiro.authc;
+
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+
+/**
+ * Default implementation of the {@link AuthenticationTokenFactory} interface that returns
+ * {@link org.apache.shiro.authc.UsernamePasswordToken UsernamePasswordToken} instances based on inspecting the
+ * {@link ConnectionInfo}.
+ *
+ * @since 5.10.0
+ */
+public class DefaultAuthenticationTokenFactory implements AuthenticationTokenFactory {
+
+ public DefaultAuthenticationTokenFactory() {
+ }
+
+ /**
+ * Returns a new {@link UsernamePasswordToken} instance populated based on the ConnectionInfo's
+ * {@link org.apache.activemq.command.ConnectionInfo#getUserName() userName} and
+ * {@link org.apache.activemq.command.ConnectionInfo#getPassword() password} properties.
+ *
+ * @param conn the subject's connection
+ * @return a new {@link UsernamePasswordToken} instance populated based on the ConnectionInfo's
+ * ConnectionInfo's {@link org.apache.activemq.command.ConnectionInfo#getUserName() userName} and
+ * {@link org.apache.activemq.command.ConnectionInfo#getPassword() password} properties.
+ */
+ @Override
+ public AuthenticationToken getAuthenticationToken(SubjectConnectionReference conn) {
+
+ String username = conn.getConnectionInfo().getUserName();
+ String password = conn.getConnectionInfo().getPassword();
+
+ if (username == null && password == null) {
+ //no identity or credentials provided by the client for the connection - return null to reflect this
+ return null;
+ }
+
+ return new UsernamePasswordToken(username, password);
+ }
+}
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/Action.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/Action.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/Action.java
new file mode 100644
index 0000000..dfde09f
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/Action.java
@@ -0,0 +1,35 @@
+/**
+ * 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.activemq.shiro.authz;
+
+/**
+ * An {@code Action} represents an attempt to perform some behavior, typically on a particular resource.
+ *
+ * @see DestinationAction
+ * @since 5.10.0
+ */
+public interface Action {
+
+ /**
+ * Returns a human readable string that indicates what this action is, for example "open doors" or
+ * "delete file /usr/local/foo"
+ *
+ * @return a human readable string that indicates what this action is
+ */
+ String toString();
+
+}
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActionPermissionResolver.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActionPermissionResolver.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActionPermissionResolver.java
new file mode 100644
index 0000000..a379d21
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActionPermissionResolver.java
@@ -0,0 +1,50 @@
+/**
+ * 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.activemq.shiro.authz;
+
+import org.apache.shiro.authz.Permission;
+
+import java.util.Collection;
+
+/**
+ * An {@code ActionPermissionResolver} will inspect an {@link Action} and return
+ * {@link Permission}s that must be granted to a {@link org.apache.shiro.subject.Subject Subject} in order for the
+ * {@code Subject} to execute the action.
+ * <p/>
+ * If a {@code Subject} is not granted all of the returned permissions, the {@code Action} will not be executed.
+ *
+ * @since 5.10.0
+ */
+public interface ActionPermissionResolver {
+
+ /**
+ * Returns all {@link Permission}s that must be granted to a
+ * {@link org.apache.shiro.subject.Subject Subject} in order for the {@code Subject} to execute the action, or
+ * an empty collection if no permissions are required.
+ * <p/>
+ * Most implementations will probably return a single Permission, but multiple permissions are possible, especially
+ * if the Action represents behavior attempted on a
+ * <a href="http://activemq.apache.org/composite-destinations.html">Composite Destination</a>.
+ *
+ * @param action the action attempted
+ * @return all {@link Permission}s that must be granted to a
+ * {@link org.apache.shiro.subject.Subject Subject} in order for the {@code Subject} to execute the action,
+ * or an empty collection if no permissions are required.
+ */
+ Collection<Permission> getPermissions(Action action);
+
+}
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQPermissionResolver.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQPermissionResolver.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQPermissionResolver.java
new file mode 100644
index 0000000..620957b
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQPermissionResolver.java
@@ -0,0 +1,57 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.shiro.authz;
+
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.authz.permission.WildcardPermission;
+import org.apache.shiro.authz.permission.WildcardPermissionResolver;
+
+/**
+ * {@link WildcardPermissionResolver} that can create case-sensitive (or case-insensitive)
+ * {@link WildcardPermission} instances as expected for ActiveMQ.
+ *
+ * @since 5.10.0
+ */
+public class ActiveMQPermissionResolver extends WildcardPermissionResolver {
+
+ private boolean caseSensitive;
+
+ public ActiveMQPermissionResolver() {
+ caseSensitive = true;
+ }
+
+ public boolean isCaseSensitive() {
+ return caseSensitive;
+ }
+
+ public void setCaseSensitive(boolean caseSensitive) {
+ this.caseSensitive = caseSensitive;
+ }
+
+ /**
+ * Creates a new {@link WildcardPermission} instance, with case-sensitivity determined by the
+ * {@link #isCaseSensitive() caseSensitive} setting.
+ *
+ * @param permissionString the wildcard permission-formatted string.
+ * @return a new {@link WildcardPermission} instance, with case-sensitivity determined by the
+ * {@link #isCaseSensitive() caseSensitive} setting.
+ */
+ @Override
+ public Permission resolvePermission(String permissionString) {
+ return new ActiveMQWildcardPermission(permissionString, isCaseSensitive());
+ }
+}
http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQWildcardPermission.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQWildcardPermission.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQWildcardPermission.java
new file mode 100644
index 0000000..38a514c
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQWildcardPermission.java
@@ -0,0 +1,275 @@
+/**
+ * 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.activemq.shiro.authz;
+
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.authz.permission.WildcardPermission;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @since 5.10.0
+ */
+public class ActiveMQWildcardPermission extends WildcardPermission {
+
+ private final boolean caseSensitive;
+
+ public ActiveMQWildcardPermission(String wildcardString) {
+ this(wildcardString, true);
+ }
+
+ public ActiveMQWildcardPermission(String wildcardString, boolean caseSensitive) {
+ super(wildcardString, caseSensitive);
+ this.caseSensitive = caseSensitive;
+ }
+
+ @Override
+ public boolean implies(Permission p) {
+ // By default only supports comparisons with other WildcardPermissions
+ if (!(p instanceof WildcardPermission)) {
+ return false;
+ }
+
+ WildcardPermission wp = (WildcardPermission) p;
+
+ List<Set<String>> otherParts = getParts(wp);
+
+ int i = 0;
+ for (Set<String> otherPart : otherParts) {
+ // If this permission has less parts than the other permission, everything after the number of parts contained
+ // in this permission is automatically implied, so return true
+ if (getParts().size() - 1 < i) {
+ return true;
+ } else {
+ Set<String> thisPart = getParts().get(i);
+
+ for (String token : thisPart) {
+ if (token.equals(WILDCARD_TOKEN)) {
+ continue;
+ }
+ for (String otherToken : otherPart) {
+ if (!caseSensitive) {
+ otherToken = otherToken.toLowerCase();
+ }
+ if (!matches(token, otherToken)) {
+ return false;
+ }
+ }
+ }
+ i++;
+ }
+ }
+
+ // If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards
+ for (; i < getParts().size(); i++) {
+ Set<String> part = getParts().get(i);
+ if (!part.contains(WILDCARD_TOKEN)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:<br>
+ * '*' means zero or more characters<br>
+ * '?' means one and only one character
+ *
+ * @param pattern pattern to match against.
+ * Must not be <code>null</code>.
+ * @param value string which must be matched against the pattern.
+ * Must not be <code>null</code>.
+ * @return <code>true</code> if the string matches against the
+ * pattern, or <code>false</code> otherwise.
+ */
+ protected boolean matches(String pattern, String value) {
+
+ char[] patArr = pattern.toCharArray();
+ char[] valArr = value.toCharArray();
+ int patIndex = 0;
+ int patEndIndex = patArr.length - 1;
+ int valIndex = 0;
+ int valEndIndex = valArr.length - 1;
+ char ch;
+
+ boolean patternContainsStar = false;
+ for (char patternChar : patArr) {
+ if (patternChar == '*') {
+ patternContainsStar = true;
+ break;
+ }
+ }
+
+ if (!patternContainsStar) {
+ // No '*'s, so we make a shortcut
+ if (patEndIndex != valEndIndex) {
+ return false; // Pattern and string do not have the same size
+ }
+ for (int i = 0; i <= patEndIndex; i++) {
+ ch = patArr[i];
+ if (ch != '?') {
+ if (ch != valArr[i]) {
+ return false;// Character mismatch
+ }
+ }
+ }
+ return true; // String matches against pattern
+ }
+
+
+ // Process characters before first star
+ while ((ch = patArr[patIndex]) != '*' && valIndex <= valEndIndex) {
+ if (ch != '?') {
+ if (ch != valArr[valIndex]) {
+ return false;// Character mismatch
+ }
+ }
+ patIndex++;
+ valIndex++;
+ }
+ if (valIndex > valEndIndex) {
+ // All characters in the value are used. Check if only '*'s remain
+ // in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIndex; i <= patEndIndex; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Process characters after last star
+ while ((ch = patArr[patEndIndex]) != '*' && valIndex <= valEndIndex) {
+ if (ch != '?') {
+ if (ch != valArr[valEndIndex]) {
+ return false;// Character mismatch
+ }
+ }
+ patEndIndex--;
+ valEndIndex--;
+ }
+ if (valIndex > valEndIndex) {
+ // All characters in the value are used. Check if only '*'s remain
+ // in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIndex; i <= patEndIndex; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // process pattern between stars. patIndex and patEndIndex always point to a '*'.
+ while (patIndex != patEndIndex && valIndex <= valEndIndex) {
+ int innerPatternIndex = -1;
+ for (int i = patIndex + 1; i <= patEndIndex; i++) {
+ if (patArr[i] == '*') {
+ innerPatternIndex = i;
+ break;
+ }
+ }
+ if (innerPatternIndex == patIndex + 1) {
+ // Two stars next to each other, skip the first one.
+ patIndex++;
+ continue;
+ }
+ // Find the pattern between patIndex & innerPatternIndex in the value between
+ // valIndex and valEndIndex
+ int innerPatternLength = (innerPatternIndex - patIndex - 1);
+ int innerValueLength = (valEndIndex - valIndex + 1);
+ int foundIndex = -1;
+ innerValueLoop:
+ for (int i = 0; i <= innerValueLength - innerPatternLength; i++) {
+ for (int j = 0; j < innerPatternLength; j++) {
+ ch = patArr[patIndex + j + 1];
+ if (ch != '?') {
+ if (ch != valArr[valIndex + i + j]) {
+ continue innerValueLoop;
+ }
+ }
+ }
+
+ foundIndex = valIndex + i;
+ break;
+ }
+
+ if (foundIndex == -1) {
+ return false;
+ }
+
+ patIndex = innerPatternIndex;
+ valIndex = foundIndex + innerPatternLength;
+ }
+
+ // All characters in the string are used. Check if only '*'s are left
+ // in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIndex; i <= patEndIndex; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected List<Set<String>> getParts(WildcardPermission wp) {
+ if (wp instanceof ActiveMQWildcardPermission) {
+ return ((ActiveMQWildcardPermission) wp).getParts();
+ } else {
+ return getPartsByReflection(wp);
+ }
+ }
+
+ protected List<Set<String>> getPartsByReflection(WildcardPermission wp) {
+ try {
+ return doGetPartsByReflection(wp);
+ } catch (Exception e) {
+ String msg = "Unable to obtain WildcardPermission instance's 'parts' value.";
+ throw new IllegalStateException(msg, e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected List<Set<String>> doGetPartsByReflection(WildcardPermission wp) throws Exception {
+ Method getParts = WildcardPermission.class.getDeclaredMethod("getParts");
+ getParts.setAccessible(true);
+ return (List<Set<String>>) getParts.invoke(wp);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buffer = new StringBuilder();
+ for (Set<String> part : getParts()) {
+ if (buffer.length() > 0) {
+ buffer.append(":");
+ }
+ boolean first = true;
+ for (String token : part) {
+ if (!first) {
+ buffer.append(",");
+ }
+ buffer.append(token);
+ first = false;
+ }
+ }
+ return buffer.toString();
+ }
+}