ARTEMIS-308 security+LDAP example

This commit is contained in:
jbertram 2015-12-09 15:20:35 -06:00
parent 9cae390645
commit dd4a8d6879
13 changed files with 1394 additions and 1 deletions

View File

@ -82,6 +82,7 @@ under the License.
<module>request-reply</module>
<module>scheduled-message</module>
<module>security</module>
<module>security-ldap</module>
<module>send-acknowledgements</module>
<module>spring-integration</module>
<module>ssl-enabled</module>
@ -142,7 +143,7 @@ under the License.
<module>rest</module>
<module>scheduled-message</module>
<module>security</module>
<module>security-jaas</module>
<module>security-ldap</module>
<module>send-acknowledgements</module>
<module>spring-integration</module>

View File

@ -0,0 +1,151 @@
<?xml version='1.0'?>
<!--
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.examples.broker</groupId>
<artifactId>jms-examples</artifactId>
<version>1.1.1-SNAPSHOT</version>
</parent>
<artifactId>security-ldap</artifactId>
<packaging>jar</packaging>
<name>ActiveMQ Artemis JMS Security LDAP Example</name>
<properties>
<activemq.basedir>${project.basedir}/../../../..</activemq.basedir>
<version.org.apache.ds>2.0.0-M20</version.org.apache.ds>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-parent</artifactId>
<version>${version.org.apache.ds}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-jms-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-core-annotations</artifactId>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.directory.api</groupId>
<artifactId>api-ldap-codec-standalone</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-protocol-ldap</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-maven-plugin</artifactId>
<executions>
<execution>
<id>create</id>
<goals>
<goal>create</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
</configuration>
</execution>
<execution>
<id>start</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<spawn>true</spawn>
<testURI>tcp://localhost:61616</testURI>
<testUser>myClusterUser</testUser>
<testPassword>myClusterPassword</testPassword>
<args>
<param>run</param>
</args>
</configuration>
</execution>
<execution>
<id>runClient</id>
<goals>
<goal>runClient</goal>
</goals>
<configuration>
<clientClass>org.apache.activemq.artemis.jms.example.SecurityExample</clientClass>
<args>
<arg>${project.build.outputDirectory}/example.ldif</arg>
</args>
</configuration>
</execution>
<execution>
<id>stop</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<args>
<param>stop</param>
</args>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.apache.activemq.examples.broker</groupId>
<artifactId>security-ldap</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,326 @@
<!--
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.
-->
<html>
<head>
<title>ActiveMQ Artemis JMS Security Example</title>
<link rel="stylesheet" type="text/css" href="../../../common/common.css" />
<link rel="stylesheet" type="text/css" href="../../../common/prettify.css" />
<script type="text/javascript" src="../../../common/prettify.js"></script>
</head>
<body onload="prettyPrint()">
<h1>JMS Security Example</h1>
<pre>To run the example, simply type <b>mvn verify</b> from this directory, <br>or <b>mvn -PnoServer verify</b> if you want to start and create the server manually.</pre>
<p>This example shows how to configure and use security using ActiveMQ Artemis with LDAP.</p>
<p>With security properly configured, ActiveMQ Artemis can restrict client access to its resources, including
connection creation, message sending/receiving, etc. This is done by configuring users and roles as well as permissions in
the configuration files. </p>
<p>ActiveMQ Artemis supports wild-card security configuration. This feature makes security configuration very
flexible and enables fine-grained control over permissions in an efficient way.</p>
<p>For a full description of how to configure security with ActiveMQ Artemis, please consult the user
manual.</p>
<p>This example demonstrates how to configure users/roles, how to configure topics with proper permissions using wild-card
expressions, and how they take effects in a simple program. </p>
<p>First we need to configure users with roles. Users and Roles are configured in <code>activemq-users.xml</code>. This example has four users
configured as below </p>
<pre class="prettyprint">
<code>
&lt;user name=&quot;bill&quot; password=&quot;activemq&quot;&gt;
&lt;role name=&quot;user&quot;/&gt;
&lt;/user&gt;
&lt;user name=&quot;andrew&quot; password=&quot;activemq1&quot;&gt;
&lt;role name=&quot;europe-user&quot;/&gt;
&lt;role name=&quot;user&quot;/&gt;
&lt;/user&gt;
&lt;user name=&quot;frank&quot; password=&quot;activemq2&quot;&gt;
&lt;role name=&quot;us-user&quot;/&gt;
&lt;role name=&quot;news-user&quot;/&gt;
&lt;role name=&quot;user&quot;/&gt;
&lt;/user&gt;
&lt;user name=&quot;sam&quot; password=&quot;activemq3&quot;&gt;
&lt;role name=&quot;news-user&quot;/&gt;
&lt;role name=&quot;user&quot;/&gt;
&lt;/user&gt;
</code>
</pre>
<p>
Each user has three properties available: user name, password, and roles it belongs to. It should be noted that
a user can belong to more than one role. In the above configuration, all users belong to role 'user'. User 'andrew' also
belongs to role 'europe-user', user 'frank' also belongs to 'us-user' and 'news-user' and user 'sam' also belongs to 'news-user'.
</p>
<p>
User name and password consists of a valid account that can be used to establish connections to a ActiveMQ Artemis server, while
roles are used in controlling the access privileges against ActiveMQ Artemis topics and queues. You can achieve this control by
configuring proper permissions in <code>broker.xml</code>, like the following
</p>
<pre class="prettyprint"><code>
&lt;security-settings&gt;
&lt;!-- any user can have full control of generic topics --&gt;
&lt;security-setting match=&quot;jms.topic.#&quot;&gt;
&lt;permission type=&quot;createDurableQueue&quot; roles=&quot;user&quot;/&gt;
&lt;permission type=&quot;deleteDurableQueue&quot; roles=&quot;user&quot;/&gt;
&lt;permission type=&quot;createNonDurableQueue&quot; roles=&quot;user&quot;/&gt;
&lt;permission type=&quot;deleteNonDurableQueue&quot; roles=&quot;user&quot;/&gt;
&lt;permission type=&quot;send&quot; roles=&quot;user&quot;/&gt;
&lt;permission type=&quot;consume&quot; roles=&quot;user&quot;/&gt;
&lt;/security-setting&gt;
&lt;security-setting match=&quot;jms.topic.news.europe.#&quot;&gt;
&lt;permission type=&quot;createDurableQueue&quot; roles=&quot;user&quot;/&gt;
&lt;permission type=&quot;deleteDurableQueue&quot; roles=&quot;user&quot;/&gt;
&lt;permission type=&quot;createNonDurableQueue&quot; roles=&quot;user&quot;/&gt;
&lt;permission type=&quot;deleteNonDurableQueue&quot; roles=&quot;user&quot;/&gt;
&lt;permission type=&quot;send&quot; roles=&quot;europe-user&quot;/&gt;
&lt;permission type=&quot;consume&quot; roles=&quot;news-user&quot;/&gt;
&lt;/security-setting&gt;
&lt;security-setting match=&quot;jms.topic.news.us.#&quot;&gt;
&lt;permission type=&quot;createDurableQueue&quot; roles=&quot;user&quot;/&gt;
&lt;permission type=&quot;deleteDurableQueue&quot; roles=&quot;user&quot;/&gt;
&lt;permission type=&quot;createNonDurableQueue&quot; roles=&quot;user&quot;/&gt;
&lt;permission type=&quot;deleteNonDurableQueue&quot; roles=&quot;user&quot;/&gt;
&lt;permission type=&quot;send&quot; roles=&quot;us-user&quot;/&gt;
&lt;permission type=&quot;consume&quot; roles=&quot;news-user&quot;/&gt;
&lt;/security-setting&gt;
&lt;/security-settings&gt;
</code></pre>
<p>Permissions can be defined on any group of queues, by using a wildcard. You can easily specify
wildcards to apply certain permissions to a set of matching queues and topics. In the above configuration
we have created four sets of permissions, each set matches against a special group of targets, indicated by wild-card match attributes.</p>
<p>You can provide a very broad permission control as a default and then add more strict control
over specific addresses. By the above we define the following access rules:</p>
<li>Only role 'us-user' can create/delete and pulish messages to topics whose names match wild-card pattern 'news.us.#'.</li>
<li>Only role 'europe-user' can create/delete and publish messages to topics whose names match wild-card pattern 'news.europe.#'.</li>
<li>Only role 'news-user' can subscribe messages to topics whose names match wild-card pattern 'news.us.#' and 'news.europe.#'.</li>
<li>For any other topics that don't match any of the above wild-card patterns, permissions are granted to users of role 'user'.</li>
<p>To illustrate the effect of permissions, three topics are deployed. Topic 'genericTopic' matches 'jms.topic.#' wild-card, topic 'news.europe.europeTopic' matches
jms.topic.news.europe.#' wild-cards, and topic 'news.us.usTopic' matches 'jms.topic.news.us.#'.</p>
<p>With ActiveMQ Artemis, the security manager is also configurable. You can use JAASSecurityManager or JBossASSecurityManager based on you need. Please
check out the activemq-beans.xml for how to do. In this example we just use the basic ActiveMQSecurityManagerImpl which reads users/roles/passwords from the xml
file <code>activemq-users.xml</code>.
<h2>Example step-by-step</h2>
<p><i>To run the example, simply type <code>mvn verify -Pexample</code> from this directory</i></p>
<ol>
<li>First we need to get an initial context so we can look-up the JMS connection factory and destination objects from JNDI. This initial context will get it's properties from the <code>client-jndi.properties</code> file in the directory <code>../common/config</code></li>
<pre class="prettyprint">
<code>
InitialContext initialContext = getContext(0);
</code>
</pre>
<li>We perform lookup on the topics</li>
<pre class="prettyprint">
<code>
Topic genericTopic = (Topic) initialContext.lookup("/topic/genericTopic");
Topic europeTopic = (Topic) initialContext.lookup("/topic/europeTopic");
Topic usTopic = (Topic) initialContext.lookup("/topic/usTopic");
</code>
</pre>
<li>We perform a lookup on the Connection Factory</li>
<pre class="prettyprint">
<code>
ConnectionFactory cf = (ConnectionFactory) initialContext.lookup("/ConnectionFactory");
</code>
</pre>
<li>We try to create a JMS Connection without user/password. It will fail.</li>
<pre class="prettyprint">
<code>
try
{
cf.createConnection();
result = false;
}
catch (JMSSecurityException e)
{
System.out.println("Default user cannot get a connection. Details: " + e.getMessage());
}
</code>
</pre>
<li>Bill tries to make a connection using wrong password</li>
<pre class="prettyprint">
<code>
billConnection = null;
try
{
billConnection = createConnection("bill", "activemq1", cf);
result = false;
}
catch (JMSException e)
{
System.out.println("User bill failed to connect. Details: " + e.getMessage());
}
</code>
</pre>
<li>Bill makes a good connection.</li>
<pre class="prettyprint">
<code>
billConnection = createConnection("bill", "activemq", cf);
billConnection.start();
</code>
</pre>
<li>Andrew makes a good connection</li>
<pre class="prettyprint">
<code>
andrewConnection = createConnection("andrew", "activemq1", cf);
andrewConnection.start();
</code>
</pre>
<li>Frank makes a good connection</li>
<pre class="prettyprint">
<code>
frankConnection = createConnection("frank", "activemq2", cf);
frankConnection.start();
</code>
</pre>
<li>Sam makes a good connection</li>
<pre class="prettyprint">
<code>
samConnection = createConnection("sam", "activemq3", cf);
samConnection.start();
</code>
</pre>
<li>We check every user can publish/subscribe genericTopics</li>
<pre class="prettyprint">
<code>
checkUserSendAndReceive(genericTopic, billConnection, "bill");
checkUserSendAndReceive(genericTopic, andrewConnection, "andrew");
checkUserSendAndReceive(genericTopic, frankConnection, "frank");
checkUserSendAndReceive(genericTopic, samConnection, "sam");
</code>
</pre>
<li>We check permissions on news.europe.europeTopic for bill: can't send and can't receive</li>
<pre class="prettyprint">
<code>
checkUserNoSendNoReceive(europeTopic, billConnection, "bill", andrewConnection, frankConnection);
</code>
</pre>
<li>We check permissions on news.europe.europeTopic for andrew: can send but can't receive</li>
<pre class="prettyprint">
<code>
checkUserSendNoReceive(europeTopic, andrewConnection, "andrew", frankConnection);
</code>
</pre>
<li>We check permissions on news.europe.europeTopic for frank: can't send but can receive</li>
<pre class="prettyprint">
<code>
checkUserReceiveNoSend(europeTopic, frankConnection, "frank", andrewConnection);
</code>
</pre>
<li>We check permissions on news.europe.europeTopic for sam: can't send but can receive</li>
<pre class="prettyprint">
<code>
checkUserReceiveNoSend(europeTopic, samConnection, "sam", andrewConnection);
</code>
</pre>
<li>We check permissions on news.us.usTopic for bill: can't send and can't receive</li>
<pre class="prettyprint">
<code>
checkUserNoSendNoReceive(usTopic, billConnection, "bill");
</code>
</pre>
<li>We check permissions on news.us.usTopic for andrew: can't send and can't receive</li>
<pre class="prettyprint">
<code>
checkUserNoSendNoReceive(usTopic, andrewConnection, "andrew");
</code>
</pre>
<li>We check permissions on news.us.usTopic for frank: can both send and receive</li>
<pre class="prettyprint">
<code>
checkUserSendAndReceive(usTopic, frankConnection, "frank");
</code>
</pre>
<li>We check permissions on news.us.usTopic for sam: can't send but can receive</li>
<pre class="prettyprint">
<code>
checkUserReceiveNoSend(usTopic, samConnection, "sam", frankConnection);
</code>
</pre>
<li>And finally, <b>always</b> remember to close your JMS connections and resources after use, in a <code>finally</code> block. Closing a JMS connection will automatically close all of its sessions, consumers, producer and browser objects</li>
<pre class="prettyprint">
<code>
finally
{
if (billConnection != null)
{
billConnection.close();
}
if (andrewConnection != null)
{
andrewConnection.close();
}
if (frankConnection != null)
{
frankConnection.close();
}
if (samConnection != null)
{
samConnection.close();
}
// Also the initialContext
if (initialContext != null)
{
initialContext.close();
}
}
</code>
</pre>
</ol>
</body>
</html>

View File

@ -0,0 +1,288 @@
/*
* 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.artemis.jms.example;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.JMSSecurityException;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.naming.InitialContext;
import org.apache.activemq.artemis.jms.example.ldap.LdapServer;
public class SecurityExample {
public static void main(final String[] args) throws Exception {
LdapServer ldapServer = new LdapServer(args[0]);
boolean result = true;
Connection failConnection = null;
Connection billConnection = null;
Connection andrewConnection = null;
Connection frankConnection = null;
Connection samConnection = null;
InitialContext initialContext = null;
try {
// /Step 1. Create an initial context to perform the JNDI lookup.
initialContext = new InitialContext();
// Step 2. perform lookup on the topics
Topic genericTopic = (Topic) initialContext.lookup("topic/genericTopic");
Topic europeTopic = (Topic) initialContext.lookup("topic/europeTopic");
Topic usTopic = (Topic) initialContext.lookup("topic/usTopic");
// Step 3. perform a lookup on the Connection Factory
ConnectionFactory cf = (ConnectionFactory) initialContext.lookup("ConnectionFactory");
// Step 4. Try to create a JMS Connection without user/password. It will fail.
try {
failConnection = cf.createConnection();
result = false;
}
catch (JMSSecurityException e) {
System.out.println("Default user cannot get a connection. Details: " + e.getMessage());
}
// Step 5. bill tries to make a connection using wrong password
billConnection = null;
try {
billConnection = createConnection("bill", "activemq1", cf);
result = false;
}
catch (JMSException e) {
System.out.println("User bill failed to connect. Details: " + e.getMessage());
}
// Step 6. bill makes a good connection.
billConnection = createConnection("bill", "activemq", cf);
billConnection.start();
// Step 7. andrew makes a good connection.
andrewConnection = createConnection("andrew", "activemq1", cf);
andrewConnection.start();
// Step 8. frank makes a good connection.
frankConnection = createConnection("frank", "activemq2", cf);
frankConnection.start();
// Step 9. sam makes a good connection.
samConnection = createConnection("sam", "activemq3", cf);
samConnection.start();
// Step 10. Check every user can publish/subscribe genericTopics.
System.out.println("------------------------Checking permissions on " + genericTopic + "----------------");
checkUserSendAndReceive(genericTopic, billConnection, "bill");
checkUserSendAndReceive(genericTopic, andrewConnection, "andrew");
checkUserSendAndReceive(genericTopic, frankConnection, "frank");
checkUserSendAndReceive(genericTopic, samConnection, "sam");
System.out.println("-------------------------------------------------------------------------------------");
System.out.println("------------------------Checking permissions on " + europeTopic + "----------------");
// Step 11. Check permissions on news.europe.europeTopic for bill: can't send and can't receive
checkUserNoSendNoReceive(europeTopic, billConnection, "bill");
// Step 12. Check permissions on news.europe.europeTopic for andrew: can send but can't receive
checkUserSendNoReceive(europeTopic, andrewConnection, "andrew", frankConnection);
// Step 13. Check permissions on news.europe.europeTopic for frank: can't send but can receive
checkUserReceiveNoSend(europeTopic, frankConnection, "frank", andrewConnection);
// Step 14. Check permissions on news.europe.europeTopic for sam: can't send but can receive
checkUserReceiveNoSend(europeTopic, samConnection, "sam", andrewConnection);
System.out.println("-------------------------------------------------------------------------------------");
System.out.println("------------------------Checking permissions on " + usTopic + "----------------");
// Step 15. Check permissions on news.us.usTopic for bill: can't send and can't receive
checkUserNoSendNoReceive(usTopic, billConnection, "bill");
// Step 16. Check permissions on news.us.usTopic for andrew: can't send and can't receive
checkUserNoSendNoReceive(usTopic, andrewConnection, "andrew");
// Step 17. Check permissions on news.us.usTopic for frank: can both send and receive
checkUserSendAndReceive(usTopic, frankConnection, "frank");
// Step 18. Check permissions on news.us.usTopic for sam: can't send but can receive
checkUserReceiveNoSend(usTopic, samConnection, "sam", frankConnection);
System.out.println("-------------------------------------------------------------------------------------");
}
finally {
// Step 19. Be sure to close our JMS resources!
if (failConnection != null) {
failConnection.close();
}
if (billConnection != null) {
billConnection.close();
}
if (andrewConnection != null) {
andrewConnection.close();
}
if (frankConnection != null) {
frankConnection.close();
}
if (samConnection != null) {
samConnection.close();
}
// Also the initialContext
if (initialContext != null) {
initialContext.close();
}
ldapServer.stop();
}
}
// Check the user can receive message but cannot send message.
private static void checkUserReceiveNoSend(final Topic topic,
final Connection connection,
final String user,
final Connection sendingConn) throws JMSException {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(topic);
MessageConsumer consumer = session.createConsumer(topic);
TextMessage msg = session.createTextMessage("hello-world-1");
try {
producer.send(msg);
throw new IllegalStateException("Security setting is broken! User " + user +
" can send message [" +
msg.getText() +
"] to topic " +
topic);
}
catch (JMSException e) {
System.out.println("User " + user + " cannot send message [" + msg.getText() + "] to topic: " + topic);
}
// Now send a good message
Session session1 = sendingConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
producer = session1.createProducer(topic);
producer.send(msg);
TextMessage receivedMsg = (TextMessage) consumer.receive(2000);
if (receivedMsg != null) {
System.out.println("User " + user + " can receive message [" + receivedMsg.getText() + "] from topic " + topic);
}
else {
throw new IllegalStateException("Security setting is broken! User " + user + " cannot receive message from topic " + topic);
}
session1.close();
session.close();
}
// Check the user can send message but cannot receive message
private static void checkUserSendNoReceive(final Topic topic,
final Connection connection,
final String user,
final Connection receivingConn) throws JMSException {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(topic);
try {
session.createConsumer(topic);
}
catch (JMSException e) {
System.out.println("User " + user + " cannot receive any message from topic " + topic);
}
Session session1 = receivingConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer goodConsumer = session1.createConsumer(topic);
TextMessage msg = session.createTextMessage("hello-world-2");
producer.send(msg);
TextMessage receivedMsg = (TextMessage) goodConsumer.receive(2000);
if (receivedMsg != null) {
System.out.println("User " + user + " can send message [" + receivedMsg.getText() + "] to topic " + topic);
}
else {
throw new IllegalStateException("Security setting is broken! User " + user +
" cannot send message [" +
msg.getText() +
"] to topic " +
topic);
}
session.close();
session1.close();
}
// Check the user has neither send nor receive permission on topic
private static void checkUserNoSendNoReceive(final Topic topic,
final Connection connection,
final String user) throws JMSException {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(topic);
try {
session.createConsumer(topic);
}
catch (JMSException e) {
System.out.println("User " + user + " cannot create consumer on topic " + topic);
}
TextMessage msg = session.createTextMessage("hello-world-3");
try {
producer.send(msg);
throw new IllegalStateException("Security setting is broken! User " + user +
" can send message [" +
msg.getText() +
"] to topic " +
topic);
}
catch (JMSException e) {
System.out.println("User " + user + " cannot send message [" + msg.getText() + "] to topic: " + topic);
}
session.close();
}
// Check the user connection has both send and receive permissions on the topic
private static void checkUserSendAndReceive(final Topic topic,
final Connection connection,
final String user) throws JMSException {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
TextMessage msg = session.createTextMessage("hello-world-4");
MessageProducer producer = session.createProducer(topic);
MessageConsumer consumer = session.createConsumer(topic);
producer.send(msg);
TextMessage receivedMsg = (TextMessage) consumer.receive(5000);
if (receivedMsg != null) {
System.out.println("User " + user + " can send message: [" + msg.getText() + "] to topic: " + topic);
System.out.println("User " + user + " can receive message: [" + msg.getText() + "] from topic: " + topic);
}
else {
throw new IllegalStateException("Error! User " + user + " cannot receive the message! ");
}
session.close();
}
private static Connection createConnection(final String username,
final String password,
final ConnectionFactory cf) throws JMSException {
return cf.createConnection(username, password);
}
}

View File

@ -0,0 +1,168 @@
/*
* 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.artemis.jms.example.ldap;
import java.io.File;
import java.io.IOException;
import java.util.List;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.Configuration;
import org.apache.commons.io.FileUtils;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.schema.LdapComparator;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.directory.api.ldap.model.schema.comparators.NormalizingComparator;
import org.apache.directory.api.ldap.model.schema.registries.ComparatorRegistry;
import org.apache.directory.api.ldap.model.schema.registries.SchemaLoader;
import org.apache.directory.api.ldap.schema.loader.JarLdifSchemaLoader;
import org.apache.directory.api.ldap.schema.manager.impl.DefaultSchemaManager;
import org.apache.directory.api.util.exception.Exceptions;
import org.apache.directory.server.constants.ServerDNConstants;
import org.apache.directory.server.core.DefaultDirectoryService;
import org.apache.directory.server.core.api.CacheService;
import org.apache.directory.server.core.api.DirectoryService;
import org.apache.directory.server.core.api.InstanceLayout;
import org.apache.directory.server.core.api.partition.Partition;
import org.apache.directory.server.core.api.schema.SchemaPartition;
import org.apache.directory.server.core.factory.AvlPartitionFactory;
import org.apache.directory.server.core.factory.DirectoryServiceFactory;
import org.apache.directory.server.core.factory.PartitionFactory;
import org.apache.directory.server.i18n.I18n;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Factory for a fast (mostly in-memory-only) ApacheDS DirectoryService. Use only for tests!!
*/
public class InMemoryDirectoryServiceFactory implements DirectoryServiceFactory {
private static Logger LOG = LoggerFactory.getLogger(InMemoryDirectoryServiceFactory.class);
private final DirectoryService directoryService;
private final PartitionFactory partitionFactory;
/**
* Default constructor which creates {@link DefaultDirectoryService} instance and configures {@link AvlPartitionFactory} as
* the {@link PartitionFactory} implementation.
*/
public InMemoryDirectoryServiceFactory() {
try {
directoryService = new DefaultDirectoryService();
}
catch (Exception e) {
throw new RuntimeException(e);
}
directoryService.setShutdownHookEnabled(false);
partitionFactory = new AvlPartitionFactory();
}
/**
* Constructor which uses provided {@link DirectoryService} and {@link PartitionFactory} implementations.
*
* @param directoryService must be not-<code>null</code>
* @param partitionFactory must be not-<code>null</code>
*/
public InMemoryDirectoryServiceFactory(DirectoryService directoryService, PartitionFactory partitionFactory) {
this.directoryService = directoryService;
this.partitionFactory = partitionFactory;
}
/**
* {@inheritDoc}
*/
public void init(String name) throws Exception {
if ((directoryService != null) && directoryService.isStarted()) {
return;
}
directoryService.setInstanceId(name);
// instance layout
InstanceLayout instanceLayout = new InstanceLayout(System.getProperty("java.io.tmpdir") + "/server-work-" + name);
if (instanceLayout.getInstanceDirectory().exists()) {
try {
FileUtils.deleteDirectory(instanceLayout.getInstanceDirectory());
}
catch (IOException e) {
LOG.warn("couldn't delete the instance directory before initializing the DirectoryService", e);
}
}
directoryService.setInstanceLayout(instanceLayout);
// EhCache in disabled-like-mode
Configuration ehCacheConfig = new Configuration();
CacheConfiguration defaultCache = new CacheConfiguration("default", 1)
.eternal(false)
.timeToIdleSeconds(30)
.timeToLiveSeconds(30)
.overflowToDisk(false);
ehCacheConfig.addDefaultCache(defaultCache);
CacheService cacheService = new CacheService(new CacheManager(ehCacheConfig));
directoryService.setCacheService(cacheService);
// Init the schema
// SchemaLoader loader = new SingleLdifSchemaLoader();
SchemaLoader loader = new JarLdifSchemaLoader();
SchemaManager schemaManager = new DefaultSchemaManager(loader);
schemaManager.loadAllEnabled();
ComparatorRegistry comparatorRegistry = schemaManager.getComparatorRegistry();
for (LdapComparator<?> comparator : comparatorRegistry) {
if (comparator instanceof NormalizingComparator) {
((NormalizingComparator) comparator).setOnServer();
}
}
directoryService.setSchemaManager(schemaManager);
InMemorySchemaPartition inMemorySchemaPartition = new InMemorySchemaPartition(schemaManager);
SchemaPartition schemaPartition = new SchemaPartition(schemaManager);
schemaPartition.setWrappedPartition(inMemorySchemaPartition);
directoryService.setSchemaPartition(schemaPartition);
List<Throwable> errors = schemaManager.getErrors();
if (errors.size() != 0) {
throw new Exception(I18n.err(I18n.ERR_317, Exceptions.printErrors(errors)));
}
// Init system partition
Partition systemPartition = partitionFactory.createPartition(directoryService.getSchemaManager(), directoryService
.getDnFactory(), "system", ServerDNConstants.SYSTEM_DN, 500, new File(directoryService
.getInstanceLayout()
.getPartitionsDirectory(), "system"));
systemPartition.setSchemaManager(directoryService.getSchemaManager());
partitionFactory.addIndex(systemPartition, SchemaConstants.OBJECT_CLASS_AT, 100);
directoryService.setSystemPartition(systemPartition);
directoryService.startup();
}
/**
* {@inheritDoc}
*/
public DirectoryService getDirectoryService() throws Exception {
return directoryService;
}
/**
* {@inheritDoc}
*/
public PartitionFactory getPartitionFactory() throws Exception {
return partitionFactory;
}
}

View File

@ -0,0 +1,95 @@
/*
* 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.artemis.jms.example.ldap;
import java.net.URL;
import java.util.Map;
import java.util.TreeSet;
import java.util.UUID;
import java.util.regex.Pattern;
import javax.naming.InvalidNameException;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.ldif.LdifEntry;
import org.apache.directory.api.ldap.model.ldif.LdifReader;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.directory.api.ldap.schema.extractor.impl.DefaultSchemaLdifExtractor;
import org.apache.directory.api.ldap.schema.extractor.impl.ResourceMap;
import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
import org.apache.directory.server.core.partition.ldif.AbstractLdifPartition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* In-memory schema-only partition which loads the data in the similar way as the
* {@link org.apache.directory.api.ldap.schema.loader.JarLdifSchemaLoader}.
*/
public class InMemorySchemaPartition extends AbstractLdifPartition {
private static Logger LOG = LoggerFactory.getLogger(InMemorySchemaPartition.class);
/**
* Filesystem path separator pattern, either forward slash or backslash. java.util.regex.Pattern is immutable so only one
* instance is needed for all uses.
*/
public InMemorySchemaPartition(SchemaManager schemaManager) {
super(schemaManager);
}
/**
* Partition initialization - loads schema entries from the files on classpath.
*
* @see org.apache.directory.server.core.partition.impl.avl.AvlPartition#doInit()
*/
@Override
protected void doInit() throws InvalidNameException, Exception {
if (initialized) {
return;
}
LOG.debug("Initializing schema partition " + getId());
suffixDn.apply(schemaManager);
super.doInit();
// load schema
final Map<String, Boolean> resMap = ResourceMap.getResources(Pattern.compile("schema[/\\Q\\\\E]ou=schema.*"));
for (String resourcePath : new TreeSet<String>(resMap.keySet())) {
if (resourcePath.endsWith(".ldif")) {
URL resource = DefaultSchemaLdifExtractor.getUniqueResource(resourcePath, "Schema LDIF file");
LdifReader reader = new LdifReader(resource.openStream());
LdifEntry ldifEntry = reader.next();
reader.close();
Entry entry = new DefaultEntry(schemaManager, ldifEntry.getEntry());
// add mandatory attributes
if (entry.get(SchemaConstants.ENTRY_CSN_AT) == null) {
entry.add(SchemaConstants.ENTRY_CSN_AT, defaultCSNFactory.newInstance().toString());
}
if (entry.get(SchemaConstants.ENTRY_UUID_AT) == null) {
entry.add(SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString());
}
AddOperationContext addContext = new AddOperationContext(null, entry);
super.add(addContext);
}
}
}
}

View File

@ -0,0 +1,102 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.jms.example.ldap;
import java.io.IOException;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.ldif.LdifEntry;
import org.apache.directory.api.ldap.model.ldif.LdifReader;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.directory.server.core.api.DirectoryService;
import org.apache.directory.server.core.partition.impl.avl.AvlPartition;
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
/**
* Creates and starts LDAP server(s).
*/
public class LdapServer {
private final DirectoryService directoryService;
private final org.apache.directory.server.ldap.LdapServer ldapServer;
/**
* Create a single LDAP server.
*
* @param ldifFile
*
* @throws Exception
*/
public LdapServer(String ldifFile) throws Exception {
InMemoryDirectoryServiceFactory dsFactory = new InMemoryDirectoryServiceFactory();
dsFactory.init("ds");
directoryService = dsFactory.getDirectoryService();
final SchemaManager schemaManager = directoryService.getSchemaManager();
importLdif(directoryService, schemaManager, new LdifReader(ldifFile));
ldapServer = new org.apache.directory.server.ldap.LdapServer();
ldapServer.setTransports(new TcpTransport("127.0.0.1", 1024));
ldapServer.setDirectoryService(directoryService);
ldapServer.start();
}
/**
* Stops LDAP server and the underlying directory service.
*
* @throws Exception
*/
public void stop() throws Exception {
ldapServer.stop();
directoryService.shutdown();
}
private void importLdif(DirectoryService directoryService, final SchemaManager schemaManager, LdifReader ldifReader) throws Exception {
try {
for (LdifEntry ldifEntry : ldifReader) {
checkPartition(ldifEntry);
directoryService.getAdminSession().add(new DefaultEntry(schemaManager, ldifEntry.getEntry()));
}
}
finally {
try {
ldifReader.close();
}
catch (IOException ioe) {
// ignore
}
}
}
private void checkPartition(LdifEntry ldifEntry) throws Exception {
Dn dn = ldifEntry.getDn();
Dn parent = dn.getParent();
try {
directoryService.getAdminSession().exists(parent);
}
catch (Exception e) {
System.out.println("Creating new partition for DN=" + dn + "\n");
AvlPartition partition = new AvlPartition(directoryService.getSchemaManager());
partition.setId(dn.getName());
partition.setSuffixDn(dn);
directoryService.addPartition(partition);
}
}
}

View File

@ -0,0 +1,20 @@
## ---------------------------------------------------------------------------
## 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.
## ---------------------------------------------------------------------------
user=bill,andrew,frank,sam
europe-user=andrew
news-user=frank,sam
us-user=frank

View File

@ -0,0 +1,20 @@
## ---------------------------------------------------------------------------
## 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.
## ---------------------------------------------------------------------------
bill=activemq
andrew=activemq1
frank=activemq2
sam=activemq3

View File

@ -0,0 +1,84 @@
<?xml version='1.0'?>
<!--
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.
-->
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:activemq"
xsi:schemaLocation="urn:activemq /schema/artemis-server.xsd">
<jms xmlns="urn:activemq:jms">
<topic name="genericTopic"/>
<topic name="news.europe.europeTopic"/>
<topic name="news.us.usTopic"/>
</jms>
<core xmlns="urn:activemq:core">
<bindings-directory>./data/messaging/bindings</bindings-directory>
<journal-directory>./data/messaging/journal</journal-directory>
<large-messages-directory>./data/messaging/largemessages</large-messages-directory>
<paging-directory>./data/messaging/paging</paging-directory>
<cluster-user>myClusterUser</cluster-user>
<cluster-password>myClusterPassword</cluster-password>
<!-- Acceptors -->
<acceptors>
<acceptor name="netty-acceptor">tcp://localhost:61616</acceptor>
</acceptors>
<!-- Other config -->
<security-settings>
<!-- any user can have full control of generic topics -->
<security-setting match="jms.topic.#">
<permission type="createDurableQueue" roles="user"/>
<permission type="deleteDurableQueue" roles="user"/>
<permission type="createNonDurableQueue" roles="user"/>
<permission type="deleteNonDurableQueue" roles="user"/>
<permission type="send" roles="user"/>
<permission type="consume" roles="user"/>
</security-setting>
<security-setting match="jms.topic.news.europe.#">
<permission type="createDurableQueue" roles="user"/>
<permission type="deleteDurableQueue" roles="user"/>
<permission type="createNonDurableQueue" roles="user"/>
<permission type="deleteNonDurableQueue" roles="user"/>
<permission type="send" roles="europe-user"/>
<permission type="consume" roles="news-user"/>
</security-setting>
<security-setting match="jms.topic.news.us.#">
<permission type="createDurableQueue" roles="user"/>
<permission type="deleteDurableQueue" roles="user"/>
<permission type="createNonDurableQueue" roles="user"/>
<permission type="deleteNonDurableQueue" roles="user"/>
<permission type="send" roles="us-user"/>
<permission type="consume" roles="news-user"/>
</security-setting>
</security-settings>
</core>
</configuration>

View File

@ -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.
*/
activemq {
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required
debug=true
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
connectionURL="ldap://localhost:1024"
connectionUsername="uid=admin,ou=system"
connectionPassword=secret
connectionProtocol=s
authentication=simple
userBase="dc=activemq,dc=org"
userSearchMatching="(uid={0})"
userSearchSubtree=false
roleBase="dc=activemq,dc=org"
roleName=cn
roleSearchMatching="(member=uid={1},dc=activemq,dc=org)"
roleSearchSubtree=false
;
};

View File

@ -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.
## ---------------------------------------------------------------------------
dn: dc=activemq,dc=org
dc: activemq
objectClass: top
objectClass: domain
dn: uid=bill,dc=activemq,dc=org
uid: bill
userPassword: activemq
objectClass: account
objectClass: simpleSecurityObject
objectClass: top
dn: uid=andrew,dc=activemq,dc=org
uid: andrew
userPassword: activemq1
objectClass: account
objectClass: simpleSecurityObject
objectClass: top
dn: uid=frank,dc=activemq,dc=org
uid: frank
userPassword: activemq2
objectClass: account
objectClass: simpleSecurityObject
objectClass: top
dn: uid=sam,dc=activemq,dc=org
uid: sam
userPassword: activemq3
objectClass: account
objectClass: simpleSecurityObject
objectClass: top
###################
## Define groups ##
###################
dn: cn=user,dc=activemq,dc=org
cn: user
member: uid=bill,dc=activemq,dc=org
member: uid=andrew,dc=activemq,dc=org
member: uid=frank,dc=activemq,dc=org
member: uid=sam,dc=activemq,dc=org
objectClass: groupOfNames
objectClass: top
dn: cn=europe-user,dc=activemq,dc=org
cn: europe-user
member: uid=andrew,dc=activemq,dc=org
objectClass: groupOfNames
objectClass: top
dn: cn=news-user,dc=activemq,dc=org
cn: news-user
member: uid=frank,dc=activemq,dc=org
member: uid=sam,dc=activemq,dc=org
objectClass: groupOfNames
objectClass: top
dn: cn=us-user,dc=activemq,dc=org
cn: us-user
member: uid=frank,dc=activemq,dc=org
objectClass: groupOfNames
objectClass: top

View File

@ -0,0 +1,22 @@
# 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.
java.naming.factory.initial=org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory
connectionFactory.ConnectionFactory=tcp://localhost:61616
topic.topic/genericTopic=genericTopic
topic.topic/europeTopic=news.europe.europeTopic
topic.topic/usTopic=news.us.usTopic