/*
* Copyright 2015 The Apache Software Foundation.
*
* Licensed 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.wso2.carbon.registry.event.core.internal.topic.registry;
import org.apache.axis2.databinding.utils.ConverterUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.registry.event.core.exception.EventBrokerException;
import org.wso2.carbon.registry.event.core.internal.util.EventBrokerHolder;
import org.wso2.carbon.registry.event.core.internal.util.JavaUtil;
import org.wso2.carbon.registry.event.core.subscription.Subscription;
import org.wso2.carbon.registry.event.core.topic.TopicManager;
import org.wso2.carbon.registry.event.core.topic.TopicNode;
import org.wso2.carbon.registry.event.core.topic.TopicRolePermission;
import org.wso2.carbon.registry.event.core.util.EventBrokerConstants;
import org.wso2.carbon.registry.core.Collection;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.service.RegistryService;
import org.wso2.carbon.registry.core.session.UserRegistry;
import org.wso2.carbon.user.api.UserRealm;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.api.UserStoreManager;
import org.wso2.carbon.user.core.util.UserCoreUtil;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.regex.Pattern;
/**
* This class is utilized to perform actions related to topics.
*/
public class RegistryTopicManager implements TopicManager {
private static Log log = LogFactory.getLog(RegistryTopicManager.class);
private static final String AT_REPLACE_CHAR = "_";
private static final String TOPIC_ROLE_PREFIX = "T_";
private String topicStoragePath;
private RegistryService registryService;
/**
* Initializes Registry Topic Manager
*
* @param topicStoragePath the topic registry path
*/
public RegistryTopicManager(String topicStoragePath) {
this.topicStoragePath = topicStoragePath;
this.registryService = EventBrokerHolder.getInstance().getRegistryService();
}
/**
* {@inheritDoc}
*/
@Override
public TopicNode getTopicTree() throws EventBrokerException {
try {
UserRegistry userRegistry =
this.registryService.getGovernanceSystemRegistry(EventBrokerHolder.getInstance().getTenantId());
if (!userRegistry.resourceExists(topicStoragePath)) {
userRegistry.put(topicStoragePath, userRegistry.newCollection());
}
Resource root = userRegistry.get(this.topicStoragePath);
TopicNode rootTopic = new TopicNode("/", "/");
buildTopicTree(rootTopic, (Collection) root, userRegistry);
return rootTopic;
} catch (RegistryException e) {
throw new EventBrokerException(e.getMessage(), e);
}
}
/**
* Building the topic tree
*
* @param topicNode node of the topic
* @param resource the resource that holds child topics
* @param userRegistry user registry
* @throws EventBrokerException
*/
private void buildTopicTree(TopicNode topicNode, Collection resource, UserRegistry userRegistry)
throws EventBrokerException {
try {
String[] children = resource.getChildren();
if (children != null) {
List<TopicNode> nodes = new ArrayList<TopicNode>();
for (String childTopic : children) {
Resource childResource = userRegistry.get(childTopic);
if (childResource instanceof Collection) {
if (childTopic.endsWith("/")) {
childTopic = childTopic.substring(0, childTopic.length() - 2);
}
String nodeName = childTopic.substring(childTopic.lastIndexOf("/") + 1);
if (!nodeName.equals(EventBrokerConstants.EB_CONF_WS_SUBSCRIPTION_COLLECTION_NAME) &&
!nodeName.equals(EventBrokerConstants.EB_CONF_JMS_SUBSCRIPTION_COLLECTION_NAME)) {
childTopic =
childTopic.substring(childTopic.indexOf(this.topicStoragePath)
+ this.topicStoragePath.length() + 1);
TopicNode childNode = new TopicNode(nodeName, childTopic);
nodes.add(childNode);
buildTopicTree(childNode, (Collection) childResource, userRegistry);
}
}
}
topicNode.setChildren(nodes.toArray(new TopicNode[nodes.size()]));
}
} catch (RegistryException e) {
throw new EventBrokerException(e.getMessage(), e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void addTopic(String topicName) throws EventBrokerException {
if (!validateTopicName(topicName)) {
throw new EventBrokerException("Topic name " + topicName + " is not a valid topic name. " +
"Only alphanumeric characters, hyphens (-), stars(*)," +
" hash(#) ,dot(.),question mark(?)" +
" and underscores (_) are allowed.");
}
String loggedInUser = CarbonContext.getThreadLocalCarbonContext().getUsername();
try {
UserRegistry userRegistry =
this.registryService.getGovernanceSystemRegistry(EventBrokerHolder.getInstance().getTenantId());
String resourcePath = JavaUtil.getResourcePath(topicName, this.topicStoragePath);
//we add the topic only if it does not exits. if the topic exists then
//we don't do any thing.
if (!userRegistry.resourceExists(resourcePath)) {
Collection collection = userRegistry.newCollection();
userRegistry.put(resourcePath, collection);
// Grant this user (owner) rights to update permission on newly created topic
UserRealm userRealm = EventBrokerHolder.getInstance().getRealmService().getTenantUserRealm(
CarbonContext.getThreadLocalCarbonContext().getTenantId());
userRealm.getAuthorizationManager().authorizeUser(
loggedInUser, resourcePath, EventBrokerConstants.EB_PERMISSION_CHANGE_PERMISSION);
userRealm.getAuthorizationManager().authorizeUser(
loggedInUser, resourcePath, EventBrokerConstants.EB_PERMISSION_PUBLISH);
userRealm.getAuthorizationManager().authorizeUser(
loggedInUser, resourcePath, EventBrokerConstants.EB_PERMISSION_SUBSCRIBE);
}
} catch (RegistryException e) {
throw new EventBrokerException("Cannot access the config registry", e);
} catch (UserStoreException e) {
throw new EventBrokerException("Error while granting user " + loggedInUser +
", permission " + EventBrokerConstants.EB_PERMISSION_CHANGE_PERMISSION +
", on topic " + topicName, e);
}
}
/**
* Gets a topic name without the resource path
*
* @param topic topic name
* @return a topic name
*/
private String removeResourcePath(String topic) {
String resourcePath = this.topicStoragePath;
if (topic.contains(resourcePath)) {
topic = topic.substring(topic.indexOf(resourcePath) + resourcePath.length());
}
return topic;
}
/**
* {@inheritDoc}
*/
@Override
public TopicRolePermission[] getTopicRolePermission(String topicName)
throws EventBrokerException {
String topicResourcePath = JavaUtil.getResourcePath(topicName, this.topicStoragePath);
List<TopicRolePermission> topicRolePermissions = new ArrayList<TopicRolePermission>();
UserRealm userRealm = CarbonContext.getThreadLocalCarbonContext().getUserRealm();
String adminRole =
EventBrokerHolder.getInstance().getRealmService().
getBootstrapRealmConfiguration().getAdminRoleName();
TopicRolePermission topicRolePermission;
try {
for (String role : userRealm.getUserStoreManager().getRoleNames()) {
// remove admin role and anonymous role related permissions
if (!(role.equals(adminRole) ||
CarbonConstants.REGISTRY_ANONNYMOUS_ROLE_NAME.equals(role))) {
topicRolePermission = new TopicRolePermission();
topicRolePermission.setRoleName(role);
topicRolePermission.setAllowedToSubscribe(
userRealm.getAuthorizationManager().isRoleAuthorized(
role, topicResourcePath, EventBrokerConstants.EB_PERMISSION_SUBSCRIBE));
topicRolePermission.setAllowedToPublish(
userRealm.getAuthorizationManager().isRoleAuthorized(
role, topicResourcePath, EventBrokerConstants.EB_PERMISSION_PUBLISH));
topicRolePermissions.add(topicRolePermission);
}
}
return topicRolePermissions.toArray(
new TopicRolePermission[topicRolePermissions.size()]);
} catch (UserStoreException e) {
throw new EventBrokerException("Cannot access the UserStore manager ", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void updatePermissions(String topicName, TopicRolePermission[] topicRolePermissions)
throws EventBrokerException {
String topicResourcePath = JavaUtil.getResourcePath(topicName, this.topicStoragePath);
UserRealm userRealm = CarbonContext.getThreadLocalCarbonContext().getUserRealm();
String role;
String loggedInUser = CarbonContext.getThreadLocalCarbonContext().getUsername();
try {
if (!userRealm.getAuthorizationManager().isUserAuthorized(
loggedInUser, topicResourcePath,
EventBrokerConstants.EB_PERMISSION_CHANGE_PERMISSION)) {
if (!JavaUtil.isAdmin(loggedInUser)) {
throw new EventBrokerException(" User " + loggedInUser + " cannot change" +
" the permissions of " + topicName);
}
}
for (TopicRolePermission topicRolePermission : topicRolePermissions) {
role = topicRolePermission.getRoleName();
if (topicRolePermission.isAllowedToSubscribe()) {
if (!userRealm.getAuthorizationManager().isRoleAuthorized(
role, topicResourcePath, EventBrokerConstants.EB_PERMISSION_SUBSCRIBE)) {
userRealm.getAuthorizationManager().authorizeRole(
role, topicResourcePath, EventBrokerConstants.EB_PERMISSION_SUBSCRIBE);
}
} else {
if (userRealm.getAuthorizationManager().isRoleAuthorized(
role, topicResourcePath, EventBrokerConstants.EB_PERMISSION_SUBSCRIBE)) {
userRealm.getAuthorizationManager().denyRole(
role, topicResourcePath, EventBrokerConstants.EB_PERMISSION_SUBSCRIBE);
}
}
if (topicRolePermission.isAllowedToPublish()) {
if (!userRealm.getAuthorizationManager().isRoleAuthorized(
role, topicResourcePath, EventBrokerConstants.EB_PERMISSION_PUBLISH)) {
userRealm.getAuthorizationManager().authorizeRole(
role, topicResourcePath, EventBrokerConstants.EB_PERMISSION_PUBLISH);
}
} else {
if (userRealm.getAuthorizationManager().isRoleAuthorized(
role, topicResourcePath, EventBrokerConstants.EB_PERMISSION_PUBLISH)) {
userRealm.getAuthorizationManager().denyRole(
role, topicResourcePath, EventBrokerConstants.EB_PERMISSION_PUBLISH);
}
}
}
//Internal role create by topic name and grant subscribe and publish permission to it
//By this way we restricted permission to user who create topic and allow subscribe and publish
//Admin has to give permission to other roles to subscribe and publish if necessary
authorizePermissionsToLoggedInUser(loggedInUser, topicName, topicResourcePath, userRealm);
} catch (UserStoreException e) {
throw new EventBrokerException("Cannot access the user store manager", e);
}
}
/**
* Gets the topic storage path
*
* @return the topic storage path
*/
public String getTopicStoragePath() {
return topicStoragePath;
}
/**
* The topic storage path
*
* @param topicStoragePath path for topic storage
*/
public void setTopicStoragePath(String topicStoragePath) {
this.topicStoragePath = topicStoragePath;
}
/**
* {@inheritDoc}
*/
@Override
public Subscription[] getSubscriptions(String topicName,
boolean withChildren) throws EventBrokerException {
List<Subscription> subscriptions = new ArrayList<Subscription>();
Queue<String> pathsQueue = new LinkedList<String>();
String resourcePath = JavaUtil.getResourcePath(topicName, this.topicStoragePath);
pathsQueue.add(resourcePath);
while (!pathsQueue.isEmpty()) {
addSubscriptions(pathsQueue.remove(), subscriptions, pathsQueue, withChildren);
}
return subscriptions.toArray(new Subscription[subscriptions.size()]);
}
/**
* {@inheritDoc}
*/
@Override
public Subscription[] getJMSSubscriptions(String topicName) throws EventBrokerException {
try {
Subscription[] subscriptionsArray = new Subscription[0];
UserRegistry userRegistry =
this.registryService.getGovernanceSystemRegistry(EventBrokerHolder.getInstance().getTenantId());
String resourcePath = JavaUtil.getResourcePath(topicName, this.topicStoragePath);
if (!resourcePath.endsWith("/")) {
resourcePath = resourcePath + "/";
}
resourcePath = resourcePath + EventBrokerConstants.EB_CONF_JMS_SUBSCRIPTION_COLLECTION_NAME;
// Get subscriptions
if (userRegistry.resourceExists(resourcePath)) {
Collection subscriptionCollection = (Collection) userRegistry.get(resourcePath);
subscriptionsArray =
new Subscription[subscriptionCollection.getChildCount()];
int index = 0;
for (String subs : subscriptionCollection.getChildren()) {
Collection subscription = (Collection) userRegistry.get(subs);
Subscription subscriptionDetails = new Subscription();
subscriptionDetails.setId(subscription.getProperty("Name"));
subscriptionDetails.setOwner(subscription.getProperty("Owner"));
subscriptionDetails.setCreatedTime(ConverterUtil.convertToDate(subscription.getProperty("createdTime")));
subscriptionsArray[index++] = subscriptionDetails;
}
}
return subscriptionsArray;
} catch (RegistryException e) {
throw new EventBrokerException("Cannot read the registry resources ", e);
}
}
/**
* Adds a subscriptions to a list using the resource path provided
*
* @param resourcePath the topic nam
* @param subscriptions a list of subscriptions for the topic
* @param pathsQueue the topic folder
* @param withChildren to add subscriptions to children. i.e subtopics
* @throws EventBrokerException
*/
private void addSubscriptions(String resourcePath,
List<Subscription> subscriptions,
Queue<String> pathsQueue,
boolean withChildren) throws EventBrokerException {
try {
UserRegistry userRegistry =
this.registryService.getGovernanceSystemRegistry(EventBrokerHolder.getInstance().getTenantId());
String subscriptionsPath = getSubscriptionsPath(resourcePath);
//first if there are subscriptions for this topic add them. else go to the other folders.
if (userRegistry.resourceExists(subscriptionsPath)) {
Collection collection = (Collection) userRegistry.get(subscriptionsPath);
for (String subscriptionPath : collection.getChildren()) {
Resource subscriptionResource = userRegistry.get(subscriptionPath);
Subscription subscription = JavaUtil.getSubscription(subscriptionResource);
subscription.setTopicName(removeResourcePath(resourcePath));
if (subscriptionPath.endsWith("/")) {
subscriptionPath = subscriptionsPath.substring(0, subscriptionPath.lastIndexOf("/"));
}
subscription.setId(subscriptionPath.substring(subscriptionPath.lastIndexOf("/") + 1));
subscriptions.add(subscription);
}
}
// add child subscriptions only for resource collections
if (withChildren) {
Resource resource = userRegistry.get(resourcePath);
if (resource instanceof Collection) {
Collection childResources = (Collection) resource;
for (String childResourcePath : childResources.getChildren()) {
if ((!EventBrokerConstants.EB_CONF_WS_SUBSCRIPTION_COLLECTION_NAME
.contains(childResourcePath)) &&
(!EventBrokerConstants.EB_CONF_JMS_SUBSCRIPTION_COLLECTION_NAME
.contains(childResourcePath))) {
// i.e. this folder is a topic folder
pathsQueue.add(childResourcePath);
}
}
}
}
} catch (RegistryException e) {
throw new EventBrokerException("Cannot access the registry", e);
}
}
/**
* Gets the subscription path for a topic
*
* @param topicName topic name
* @return the subscription path as string
*/
private String getSubscriptionsPath(String topicName) {
if (!topicName.endsWith("/")) {
topicName = topicName + "/";
}
topicName = topicName + EventBrokerConstants.EB_CONF_WS_SUBSCRIPTION_COLLECTION_NAME;
return topicName;
}
/**
* Validates a topic name. Checks for invalid characters
*
* @param topicName topic name
* @return true if topic name is valid, false otherwise.
*/
private boolean validateTopicName(String topicName) {
return Pattern.matches("[[a-zA-Z]+[^(\\x00-\\x80)]+[0-9_\\-/#*:.?&\\s()]+]+", topicName);
}
/**
* {@inheritDoc}
*/
@Override
public String[] getBackendRoles() throws EventBrokerException {
UserRealm userRealm = CarbonContext.getThreadLocalCarbonContext().getUserRealm();
String[] cleanedRoles = new String[0];
try {
String adminRole =
EventBrokerHolder.getInstance().getRealmService().
getBootstrapRealmConfiguration().getAdminRoleName();
String[] allRoles = userRealm.getUserStoreManager().getRoleNames();
// check if there is only admin role exists.
if (allRoles != null && allRoles.length > 1) {
// check if more roles available than admin role and anonymous role
List<String> allRolesArrayList = new ArrayList<>();
Collections.addAll(allRolesArrayList, allRoles);
Iterator<String> it = allRolesArrayList.iterator();
while (it.hasNext()) {
String nextRole = it.next();
if (nextRole.equals(adminRole) || nextRole.equals(CarbonConstants.REGISTRY_ANONNYMOUS_ROLE_NAME)) {
it.remove();
}
}
cleanedRoles = allRolesArrayList.toArray(new String[allRolesArrayList.size()]);
}
} catch (UserStoreException e) {
throw new EventBrokerException("Unable to get Roles from user store", e);
}
return cleanedRoles;
}
/**
* {@inheritDoc}
*/
@Override
public boolean removeTopic(String topicName) throws EventBrokerException {
try {
UserRegistry userRegistry =
this.registryService.getGovernanceSystemRegistry(EventBrokerHolder.getInstance().getTenantId());
String resourcePath = JavaUtil.getResourcePath(topicName, this.topicStoragePath);
removeRoleCreateForLoggedInUser(topicName);
if (userRegistry.resourceExists(resourcePath)) {
userRegistry.delete(resourcePath);
return true;
} else {
return false;
}
} catch (RegistryException e) {
throw new EventBrokerException("Cannot access the config registry", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isTopicExists(String topicName) throws EventBrokerException {
try {
UserRegistry userRegistry =
this.registryService.getGovernanceSystemRegistry(EventBrokerHolder.getInstance().getTenantId());
String resourcePath = JavaUtil.getResourcePath(topicName, this.topicStoragePath);
return userRegistry.resourceExists(resourcePath);
} catch (RegistryException e) {
throw new EventBrokerException("Cannot access the config registry");
}
}
/**
* Create a new role which has the same name as the destinationName and assign the logged in
* user to the newly created role. Then, authorize the newly created role to subscribe and
* publish to the destination.
*
* @param username name of the logged in user
* @param destinationName destination name. Either topic or queue name
* @param destinationId ID given to the destination
* @param userRealm the user store
* @throws UserStoreException
*/
private static void authorizePermissionsToLoggedInUser(String username, String destinationName,
String destinationId,
UserRealm userRealm) throws
UserStoreException {
//For registry we use a modified queue name
String newDestinationName = destinationName.replace("@", AT_REPLACE_CHAR);
// creating the internal role name
String roleName = UserCoreUtil.addInternalDomainName(TOPIC_ROLE_PREFIX +
newDestinationName.replace("/", "-"));
// the interface to store user data
UserStoreManager userStoreManager = CarbonContext.getThreadLocalCarbonContext().getUserRealm().getUserStoreManager();
if (!userStoreManager.isExistingRole(roleName)) {
String[] user = {MultitenantUtils.getTenantAwareUsername(username)};
// adds the internal role to user store
userStoreManager.addRole(roleName, user, null);
// gives subscribe permissions to the internal role in the user store
userRealm.getAuthorizationManager().authorizeRole(
roleName, destinationId, EventBrokerConstants.EB_PERMISSION_SUBSCRIBE);
// gives publish permissions to the internal role in the user store
userRealm.getAuthorizationManager().authorizeRole(
roleName, destinationId, EventBrokerConstants.EB_PERMISSION_PUBLISH);
// gives change permissions to the internal role in the user store
userRealm.getAuthorizationManager().authorizeRole(
roleName, destinationId, EventBrokerConstants.EB_PERMISSION_CHANGE_PERMISSION);
} else {
log.warn("Unable to provide permissions to the user, " +
" " + username + ", to subscribe and publish to " + newDestinationName);
}
}
/**
* Every queue/topic has a role with the same name as the queue/topic name. This role is used
* to store the permissions for the user who created the queue/topic.This role should be
* deleted when the queue/topic is deleted.
*
* @param destinationName name of the queue or topic
* @throws EventBrokerException
*/
private static void removeRoleCreateForLoggedInUser(String destinationName)
throws EventBrokerException {
//For registry we use a modified queue name
String newDestinationName = destinationName.replace("@", AT_REPLACE_CHAR);
String roleName = UserCoreUtil.addInternalDomainName(TOPIC_ROLE_PREFIX +
newDestinationName.replace("/", "-"));
try {
UserStoreManager userStoreManager = CarbonContext.getThreadLocalCarbonContext().getUserRealm().getUserStoreManager();
if (userStoreManager.isExistingRole(roleName)) {
userStoreManager.deleteRole(roleName);
}
} catch (UserStoreException e) {
throw new EventBrokerException("Error while deleting " + newDestinationName, e);
}
}
}