001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.shiro.authz;
018
019import org.apache.activemq.command.ActiveMQDestination;
020import org.apache.shiro.authz.Permission;
021import org.apache.shiro.authz.permission.WildcardPermission;
022
023import java.util.Collection;
024import java.util.HashSet;
025import java.util.LinkedHashSet;
026import java.util.Set;
027
028/**
029 * A {@code DestinationActionPermissionResolver} inspects {@link DestinationAction}s and returns one or more
030 * {@link WildcardPermission}s that must be granted to a {@code Subject} in order for that {@code Subject} to
031 * perform the action being taken on an {@link ActiveMQDestination}.
032 * <p/>
033 * See the {@link #createPermissionString createPermissionString documentation} to see what the
034 * resulting {@link WildcardPermission} instances would look like.
035 *
036 * @see #createPermissionString(org.apache.activemq.command.ActiveMQDestination, String) )
037 * @see #setPermissionStringPrefix(String)
038 * @since 5.10.0
039 */
040public class DestinationActionPermissionResolver implements ActionPermissionResolver {
041
042    private String permissionStringPrefix;
043    private boolean permissionStringCaseSensitive = true;
044
045    /**
046     * Returns the String prefix that should be automatically prepended to a permission String before the
047     * String is converted to a {@link WildcardPermission} instance.  This is convenient if you want to provide a
048     * 'scope' or 'namespace' for ActiveMQ Destinations to clearly distinguish ActiveMQ-specific permissions from any
049     * others you might assign to user accounts.  The default value is {@code null}, indicating no prefix will be
050     * set by default.
051     * <p/>
052     * For example, the default settings might result in permissions Strings that look like this:
053     * <pre>
054     * topic:TEST:create
055     * temp-queue:MyQueue:remove
056     * topic:ActiveMQ.Advisory.*:read
057     * </pre>
058     * <p/>
059     * However, if your application has any application-specific permissions that start with the tokens {@code topic},
060     * {@code temp-topic}, {@code queue}, or {@code temp-queue}, you wouldn't be able to distinguish between
061     * application-specific permissions and those specific to ActiveMQ.  In this case you might set the
062     * {@code permissionStringPrefix}. For example, if you set:
063     * {@code resolver.setPermissionStringPrefix(&quot;jms&quot;);}, the above permission strings would look like this:
064     * <pre>
065     * jms:topic:TEST:create
066     * jms:temp-queue:MyQueue:remove
067     * jms:topic:ActiveMQ.Advisory.*:read
068     * </pre>
069     * <p/>
070     * Similarly, if the {@code permissionStringPrefix} was equal to {@code activeMQ}:
071     * <pre>
072     * activeMQ:topic:TEST:create
073     * activeMQ:temp-queue:MyQueue:remove
074     * activeMQ:topic:ActiveMQ.Advisory.*:read
075     * </pre>
076     *
077     * @return any String prefix that should be automatically prepended to a permission String before the
078     *         String is converted to a {@link WildcardPermission} instance.  Useful for namespacing permissions.
079     */
080    public String getPermissionStringPrefix() {
081        return permissionStringPrefix;
082    }
083
084    /**
085     * Sets the String prefix that should be automatically prepended to a permission String before the
086     * String is converted to a {@link WildcardPermission} instance.  This is convenient if you want to provide a
087     * 'scope' or 'namespace' for ActiveMQ Destinations to clearly distinguish ActiveMQ-specific permissions from any
088     * others you might assign to user accounts. The default value is {@code null}, indicating no prefix will be
089     * set by default.
090     * <p/>
091     * For example, the default settings might result in permissions Strings that look like this:
092     * <pre>
093     * topic:TEST:create
094     * temp-queue:MyQueue:remove
095     * topic:ActiveMQ.Advisory.*:read
096     * </pre>
097     * <p/>
098     * However, if your application has any application-specific permissions that start with the tokens {@code topic},
099     * {@code temp-topic}, {@code queue}, or {@code temp-queue}, you wouldn't be able to distinguish between
100     * application-specific permissions and those specific to ActiveMQ.  In this case you might set the
101     * {@code permissionStringPrefix}. For example, if you set:
102     * {@code resolver.setPermissionStringPrefix(&quot;jms&quot;);}, the above permission strings would look like this:
103     * <pre>
104     * jms:topic:TEST:create
105     * jms:temp-queue:MyQueue:remove
106     * jms:topic:ActiveMQ.Advisory.*:read
107     * </pre>
108     * <p/>
109     * Similarly, if the {@code permissionStringPrefix} was equal to {@code activeMQ}:
110     * <pre>
111     * activeMQ:topic:TEST:create
112     * activeMQ:temp-queue:MyQueue:remove
113     * activeMQ:topic:ActiveMQ.Advisory.*:read
114     * </pre>
115     *
116     * @param permissionStringPrefix any String prefix that should be automatically prepended to a permission String
117     *                               before the String is converted to a {@link WildcardPermission} instance.  Useful
118     *                               for namespacing permissions.
119     */
120    public void setPermissionStringPrefix(String permissionStringPrefix) {
121        this.permissionStringPrefix = permissionStringPrefix;
122    }
123
124    /**
125     * Returns {@code true} if returned {@link WildcardPermission} instances should be considered case-sensitive,
126     * {@code false} otherwise.  The default value is {@code true}, which is <em>not</em> the normal
127     * {@link WildcardPermission} default setting.  This default was chosen to reflect ActiveMQ's
128     * <a href="http://activemq.apache.org/are-destinations-case-sensitive.html">case-sensitive destination names</a>.
129     *
130     * @return {@code true} if returned {@link WildcardPermission} instances should be considered case-sensitive,
131     *         {@code false} otherwise.
132     */
133    public boolean isPermissionStringCaseSensitive() {
134        return permissionStringCaseSensitive;
135    }
136
137    /**
138     * Sets whether returned {@link WildcardPermission} instances should be considered case-sensitive.
139     * The default value is {@code true}, which is <em>not</em> the normal
140     * {@link WildcardPermission} default setting.  This default was chosen to accurately reflect ActiveMQ's
141     * <a href="http://activemq.apache.org/are-destinations-case-sensitive.html">case-sensitive destination names</a>.
142     *
143     * @param permissionStringCaseSensitive whether returned {@link WildcardPermission} instances should be considered
144     *                                      case-sensitive.
145     */
146    public void setPermissionStringCaseSensitive(boolean permissionStringCaseSensitive) {
147        this.permissionStringCaseSensitive = permissionStringCaseSensitive;
148    }
149
150    @Override
151    public Collection<Permission> getPermissions(Action action) {
152        if (!(action instanceof DestinationAction)) {
153            throw new IllegalArgumentException("Action argument must be a " + DestinationAction.class.getName() + " instance.");
154        }
155        DestinationAction da = (DestinationAction) action;
156        return getPermissions(da);
157    }
158
159    protected Collection<Permission> getPermissions(DestinationAction da) {
160        ActiveMQDestination dest = da.getDestination();
161        String verb = da.getVerb();
162        return createPermissions(dest, verb);
163    }
164
165    protected Collection<Permission> createPermissions(ActiveMQDestination dest, String verb) {
166
167        Set<Permission> set;
168
169        if (dest.isComposite()) {
170            ActiveMQDestination[] composites = dest.getCompositeDestinations();
171            set = new LinkedHashSet<Permission>(composites.length);
172            for(ActiveMQDestination d : composites) {
173                Collection<Permission> perms = createPermissions(d, verb);
174                set.addAll(perms);
175            }
176        } else {
177            set = new HashSet<Permission>(1);
178            String permString = createPermissionString(dest, verb);
179            Permission perm = createPermission(permString);
180            set.add(perm);
181        }
182
183        return set;
184    }
185
186    /**
187     * Inspects the specified {@code destination} and {@code verb} and returns a {@link WildcardPermission}-compatible
188     * String the represents the action.
189     * <h3>Format</h3>
190     * This implementation returns WildcardPermission strings with the following format:
191     * <pre>
192     * optionalPermissionStringPrefix + destinationType + ':' + destinationPhysicalName + ':' + actionVerb
193     * </pre>
194     * where:
195     * <ol>
196     * <li>{@code optionalPermissionStringPrefix} is the {@link #getPermissionStringPrefix() permissionStringPrefix}
197     * followed by a colon delimiter (':').  This is only present if the {@code permissionStringPrefix} has been
198     * specified and is non-null</li>
199     * <li>{@code destinationType} is one of the following four string tokens:
200     * <ul>
201     * <li>{@code topic}</li>
202     * <li>{@code temp-topic}</li>
203     * <li>{@code queue}</li>
204     * <li>{@code temp-queue}</li>
205     * </ul>
206     * based on whether the {@link DestinationAction#getDestination() destination} is
207     * a topic, temporary topic, queue, or temporary queue (respectively).
208     * </li>
209     * <li>
210     * {@code destinationPhysicalName} is
211     * {@link org.apache.activemq.command.ActiveMQDestination#getPhysicalName() destination.getPhysicalName()}
212     * </li>
213     * <li>
214     * {@code actionVerb} is {@link DestinationAction#getVerb() action.getVerb()}
215     * </li>
216     * </ol>
217     * <h3>Examples</h3>
218     * With the default settings (no {@link #getPermissionStringPrefix() permissionStringPrefix}), this might produce
219     * strings that look like the following:
220     * <pre>
221     * topic:TEST:create
222     * temp-queue:MyTempQueue:remove
223     * queue:ActiveMQ.Advisory.*:read
224     * </pre>
225     * If {@link #getPermissionStringPrefix() permissionStringPrefix} was set to {@code jms}, the above examples would
226     * look like this:
227     * <pre>
228     * jms:topic:TEST:create
229     * jms:temp-queue:MyTempQueue:remove
230     * jms:queue:ActiveMQ.Advisory.*:read
231     * </pre>
232     *
233     * @param dest the destination to inspect and convert to a {@link WildcardPermission} string.
234     * @param verb the behavior taken on the destination
235     * @return a {@link WildcardPermission} string that represents the specified {@code action}.
236     * @see #getPermissionStringPrefix() getPermissionStringPrefix() for more on why you might want to set this value
237     */
238    protected String createPermissionString(ActiveMQDestination dest, String verb) {
239        if (dest.isComposite()) {
240            throw new IllegalArgumentException("Use createPermissionStrings for composite destinations.");
241        }
242
243        StringBuilder sb = new StringBuilder();
244
245        if (permissionStringPrefix != null) {
246            sb.append(permissionStringPrefix);
247            if (!permissionStringPrefix.endsWith(":")) {
248                sb.append(":");
249            }
250        }
251
252        if (dest.isTemporary()) {
253            sb.append("temp-");
254        }
255        if (dest.isTopic()) {
256            sb.append("topic:");
257        } else {
258            sb.append("queue:");
259        }
260
261        sb.append(dest.getPhysicalName());
262        sb.append(':');
263        sb.append(verb);
264
265        return sb.toString();
266
267    }
268
269    protected Permission createPermission(String permissionString) {
270        return new ActiveMQWildcardPermission(permissionString, isPermissionStringCaseSensitive());
271    }
272}