/** * 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.activemq.command.ActiveMQDestination; import org.apache.shiro.authz.Permission; import org.apache.shiro.authz.permission.WildcardPermission; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; /** * A {@code DestinationActionPermissionResolver} inspects {@link DestinationAction}s and returns one or more * {@link WildcardPermission}s that must be granted to a {@code Subject} in order for that {@code Subject} to * perform the action being taken on an {@link ActiveMQDestination}. * <p/> * See the {@link #createPermissionString createPermissionString documentation} to see what the * resulting {@link WildcardPermission} instances would look like. * * @see #createPermissionString(org.apache.activemq.command.ActiveMQDestination, String) ) * @see #setPermissionStringPrefix(String) * @since 5.10.0 */ public class DestinationActionPermissionResolver implements ActionPermissionResolver { private String permissionStringPrefix; private boolean permissionStringCaseSensitive = true; /** * Returns the String prefix that should be automatically prepended to a permission String before the * String is converted to a {@link WildcardPermission} instance. This is convenient if you want to provide a * 'scope' or 'namespace' for ActiveMQ Destinations to clearly distinguish ActiveMQ-specific permissions from any * others you might assign to user accounts. The default value is {@code null}, indicating no prefix will be * set by default. * <p/> * For example, the default settings might result in permissions Strings that look like this: * <pre> * topic:TEST:create * temp-queue:MyQueue:remove * topic:ActiveMQ.Advisory.*:read * </pre> * <p/> * However, if your application has any application-specific permissions that start with the tokens {@code topic}, * {@code temp-topic}, {@code queue}, or {@code temp-queue}, you wouldn't be able to distinguish between * application-specific permissions and those specific to ActiveMQ. In this case you might set the * {@code permissionStringPrefix}. For example, if you set: * {@code resolver.setPermissionStringPrefix("jms");}, the above permission strings would look like this: * <pre> * jms:topic:TEST:create * jms:temp-queue:MyQueue:remove * jms:topic:ActiveMQ.Advisory.*:read * </pre> * <p/> * Similarly, if the {@code permissionStringPrefix} was equal to {@code activeMQ}: * <pre> * activeMQ:topic:TEST:create * activeMQ:temp-queue:MyQueue:remove * activeMQ:topic:ActiveMQ.Advisory.*:read * </pre> * * @return any String prefix that should be automatically prepended to a permission String before the * String is converted to a {@link WildcardPermission} instance. Useful for namespacing permissions. */ public String getPermissionStringPrefix() { return permissionStringPrefix; } /** * Sets the String prefix that should be automatically prepended to a permission String before the * String is converted to a {@link WildcardPermission} instance. This is convenient if you want to provide a * 'scope' or 'namespace' for ActiveMQ Destinations to clearly distinguish ActiveMQ-specific permissions from any * others you might assign to user accounts. The default value is {@code null}, indicating no prefix will be * set by default. * <p/> * For example, the default settings might result in permissions Strings that look like this: * <pre> * topic:TEST:create * temp-queue:MyQueue:remove * topic:ActiveMQ.Advisory.*:read * </pre> * <p/> * However, if your application has any application-specific permissions that start with the tokens {@code topic}, * {@code temp-topic}, {@code queue}, or {@code temp-queue}, you wouldn't be able to distinguish between * application-specific permissions and those specific to ActiveMQ. In this case you might set the * {@code permissionStringPrefix}. For example, if you set: * {@code resolver.setPermissionStringPrefix("jms");}, the above permission strings would look like this: * <pre> * jms:topic:TEST:create * jms:temp-queue:MyQueue:remove * jms:topic:ActiveMQ.Advisory.*:read * </pre> * <p/> * Similarly, if the {@code permissionStringPrefix} was equal to {@code activeMQ}: * <pre> * activeMQ:topic:TEST:create * activeMQ:temp-queue:MyQueue:remove * activeMQ:topic:ActiveMQ.Advisory.*:read * </pre> * * @param permissionStringPrefix any String prefix that should be automatically prepended to a permission String * before the String is converted to a {@link WildcardPermission} instance. Useful * for namespacing permissions. */ public void setPermissionStringPrefix(String permissionStringPrefix) { this.permissionStringPrefix = permissionStringPrefix; } /** * Returns {@code true} if returned {@link WildcardPermission} instances should be considered case-sensitive, * {@code false} otherwise. The default value is {@code true}, which is <em>not</em> the normal * {@link WildcardPermission} default setting. This default was chosen to reflect ActiveMQ's * <a href="http://activemq.apache.org/are-destinations-case-sensitive.html">case-sensitive destination names</a>. * * @return {@code true} if returned {@link WildcardPermission} instances should be considered case-sensitive, * {@code false} otherwise. */ public boolean isPermissionStringCaseSensitive() { return permissionStringCaseSensitive; } /** * Sets whether returned {@link WildcardPermission} instances should be considered case-sensitive. * The default value is {@code true}, which is <em>not</em> the normal * {@link WildcardPermission} default setting. This default was chosen to accurately reflect ActiveMQ's * <a href="http://activemq.apache.org/are-destinations-case-sensitive.html">case-sensitive destination names</a>. * * @param permissionStringCaseSensitive whether returned {@link WildcardPermission} instances should be considered * case-sensitive. */ public void setPermissionStringCaseSensitive(boolean permissionStringCaseSensitive) { this.permissionStringCaseSensitive = permissionStringCaseSensitive; } @Override public Collection<Permission> getPermissions(Action action) { if (!(action instanceof DestinationAction)) { throw new IllegalArgumentException("Action argument must be a " + DestinationAction.class.getName() + " instance."); } DestinationAction da = (DestinationAction) action; return getPermissions(da); } protected Collection<Permission> getPermissions(DestinationAction da) { ActiveMQDestination dest = da.getDestination(); String verb = da.getVerb(); return createPermissions(dest, verb); } protected Collection<Permission> createPermissions(ActiveMQDestination dest, String verb) { Set<Permission> set; if (dest.isComposite()) { ActiveMQDestination[] composites = dest.getCompositeDestinations(); set = new LinkedHashSet<Permission>(composites.length); for(ActiveMQDestination d : composites) { Collection<Permission> perms = createPermissions(d, verb); set.addAll(perms); } } else { set = new HashSet<Permission>(1); String permString = createPermissionString(dest, verb); Permission perm = createPermission(permString); set.add(perm); } return set; } /** * Inspects the specified {@code destination} and {@code verb} and returns a {@link WildcardPermission}-compatible * String the represents the action. * <h3>Format</h3> * This implementation returns WildcardPermission strings with the following format: * <pre> * optionalPermissionStringPrefix + destinationType + ':' + destinationPhysicalName + ':' + actionVerb * </pre> * where: * <ol> * <li>{@code optionalPermissionStringPrefix} is the {@link #getPermissionStringPrefix() permissionStringPrefix} * followed by a colon delimiter (':'). This is only present if the {@code permissionStringPrefix} has been * specified and is non-null</li> * <li>{@code destinationType} is one of the following four string tokens: * <ul> * <li>{@code topic}</li> * <li>{@code temp-topic}</li> * <li>{@code queue}</li> * <li>{@code temp-queue}</li> * </ul> * based on whether the {@link DestinationAction#getDestination() destination} is * a topic, temporary topic, queue, or temporary queue (respectively). * </li> * <li> * {@code destinationPhysicalName} is * {@link org.apache.activemq.command.ActiveMQDestination#getPhysicalName() destination.getPhysicalName()} * </li> * <li> * {@code actionVerb} is {@link DestinationAction#getVerb() action.getVerb()} * </li> * </ol> * <h3>Examples</h3> * With the default settings (no {@link #getPermissionStringPrefix() permissionStringPrefix}), this might produce * strings that look like the following: * <pre> * topic:TEST:create * temp-queue:MyTempQueue:remove * queue:ActiveMQ.Advisory.*:read * </pre> * If {@link #getPermissionStringPrefix() permissionStringPrefix} was set to {@code jms}, the above examples would * look like this: * <pre> * jms:topic:TEST:create * jms:temp-queue:MyTempQueue:remove * jms:queue:ActiveMQ.Advisory.*:read * </pre> * * @param dest the destination to inspect and convert to a {@link WildcardPermission} string. * @param verb the behavior taken on the destination * @return a {@link WildcardPermission} string that represents the specified {@code action}. * @see #getPermissionStringPrefix() getPermissionStringPrefix() for more on why you might want to set this value */ protected String createPermissionString(ActiveMQDestination dest, String verb) { if (dest.isComposite()) { throw new IllegalArgumentException("Use createPermissionStrings for composite destinations."); } StringBuilder sb = new StringBuilder(); if (permissionStringPrefix != null) { sb.append(permissionStringPrefix); if (!permissionStringPrefix.endsWith(":")) { sb.append(":"); } } if (dest.isTemporary()) { sb.append("temp-"); } if (dest.isTopic()) { sb.append("topic:"); } else { sb.append("queue:"); } sb.append(dest.getPhysicalName()); sb.append(':'); sb.append(verb); return sb.toString(); } protected Permission createPermission(String permissionString) { return new ActiveMQWildcardPermission(permissionString, isPermissionStringCaseSensitive()); } }