| /** |
| * 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.broker.ConnectionContext; |
| import org.apache.activemq.broker.ProducerBrokerExchange; |
| import org.apache.activemq.broker.region.Destination; |
| import org.apache.activemq.broker.region.Subscription; |
| import org.apache.activemq.command.ActiveMQDestination; |
| import org.apache.activemq.command.ConsumerInfo; |
| import org.apache.activemq.command.DestinationInfo; |
| import org.apache.activemq.command.Message; |
| import org.apache.activemq.command.ProducerInfo; |
| import org.apache.activemq.security.SecurityContext; |
| import org.apache.activemq.shiro.env.EnvironmentFilter; |
| import org.apache.activemq.shiro.subject.ConnectionSubjectResolver; |
| import org.apache.shiro.authz.Permission; |
| import org.apache.shiro.authz.UnauthorizedException; |
| import org.apache.shiro.subject.PrincipalCollection; |
| import org.apache.shiro.subject.Subject; |
| |
| import java.util.Collection; |
| |
| /** |
| * The {@code AuthorizationFilter} asserts that actions are allowed to execute first before they are actually |
| * executed. Such actions include creating, removing, reading from and writing to destinations. |
| * <p/> |
| * This implementation is strictly permission-based, allowing for the finest-grained security policies possible. |
| * Whenever a {@link Subject} associated with a connection attempts to perform an {@link org.apache.activemq.shiro.authz.Action} (such as creating a |
| * destination, or reading from a queue, etc), one or more {@link Permission}s representing that {@code action} are |
| * checked. |
| * <p/> |
| * If the {@code Subject}{@link Subject#isPermitted(org.apache.shiro.authz.Permission) isPermitted} to perform the |
| * {@code action}, the action is allowed to execute and the broker filter chain executes uninterrupted. |
| * <p/> |
| * However, if the {@code Subject} is not permitted to perform the action, an {@link UnauthorizedException} will be |
| * thrown, preventing the filter chain from executing that action. |
| * <h2>ActionPermissionResolver</h2> |
| * The attempted {@code Action} is guarded by one or more {@link Permission}s as indicated by a configurable |
| * {@link #setActionPermissionResolver(org.apache.activemq.shiro.authz.ActionPermissionResolver) actionPermissionResolver}. The |
| * {@code actionPermissionResolver} indicates which permissions must be granted to the connection {@code Subject} in |
| * order for the action to execute. |
| * <p/> |
| * The default {@code actionPermissionResolver} instance is a |
| * {@link org.apache.activemq.shiro.authz.DestinationActionPermissionResolver DestinationActionPermissionResolver}, which indicates which permissions |
| * are required to perform any action on a particular destination. Those familiar with Shiro's |
| * {@link org.apache.shiro.authz.permission.WildcardPermission WildcardPermission} syntax will find the |
| * {@code DestinationActionPermissionResolver}'s |
| * {@link org.apache.activemq.shiro.authz.DestinationActionPermissionResolver#createPermissionString createPermissionString} method |
| * documentation valuable for understanding how destination actions are represented as permissions. |
| * |
| * @see org.apache.activemq.shiro.authz.ActionPermissionResolver |
| * @see org.apache.activemq.shiro.authz.DestinationActionPermissionResolver |
| * @since 5.10.0 |
| */ |
| public class AuthorizationFilter extends EnvironmentFilter { |
| |
| private ActionPermissionResolver actionPermissionResolver; |
| |
| public AuthorizationFilter() { |
| this.actionPermissionResolver = new DestinationActionPermissionResolver(); |
| } |
| |
| /** |
| * Returns the {@code ActionPermissionResolver} used to indicate which permissions are required to be granted to |
| * a {@link Subject} to perform a particular destination {@link org.apache.activemq.shiro.authz.Action}, (such as creating a |
| * destination, or reading from a queue, etc). The default instance is a |
| * {@link DestinationActionPermissionResolver}. |
| * |
| * @return the {@code ActionPermissionResolver} used to indicate which permissions are required to be granted to |
| * a {@link Subject} to perform a particular destination {@link org.apache.activemq.shiro.authz.Action}, (such as creating a |
| * destination, or reading from a queue, etc). |
| */ |
| public ActionPermissionResolver getActionPermissionResolver() { |
| return actionPermissionResolver; |
| } |
| |
| /** |
| * Sets the {@code ActionPermissionResolver} used to indicate which permissions are required to be granted to |
| * a {@link Subject} to perform a particular destination {@link org.apache.activemq.shiro.authz.Action}, (such as creating a |
| * destination, or reading from a queue, etc). Unless overridden by this method, the default instance is a |
| * {@link DestinationActionPermissionResolver}. |
| * |
| * @param actionPermissionResolver the {@code ActionPermissionResolver} used to indicate which permissions are |
| * required to be granted to a {@link Subject} to perform a particular destination |
| * {@link org.apache.activemq.shiro.authz.Action}, (such as creating a destination, or reading from a queue, etc). |
| */ |
| public void setActionPermissionResolver(ActionPermissionResolver actionPermissionResolver) { |
| this.actionPermissionResolver = actionPermissionResolver; |
| } |
| |
| /** |
| * Returns the {@code Subject} associated with the specified connection using a |
| * {@link org.apache.activemq.shiro.subject.ConnectionSubjectResolver}. |
| * |
| * @param ctx the connection context |
| * @return the {@code Subject} associated with the specified connection. |
| */ |
| protected Subject getSubject(ConnectionContext ctx) { |
| return new ConnectionSubjectResolver(ctx).getSubject(); |
| } |
| |
| protected String toString(Subject subject) { |
| PrincipalCollection pc = subject.getPrincipals(); |
| if (pc != null && !pc.isEmpty()) { |
| return "[" + pc.toString() + "] "; |
| } |
| return ""; |
| } |
| |
| protected void assertAuthorized(DestinationAction action) { |
| assertAuthorized(action, action.getVerb()); |
| } |
| |
| //ActiveMQ internals will create a ConnectionContext with a SecurityContext that is not |
| //Shiro specific. We need to allow actions for internal system operations: |
| protected boolean isSystemBroker(DestinationAction action) { |
| ConnectionContext context = action.getConnectionContext(); |
| SecurityContext securityContext = context.getSecurityContext(); |
| return securityContext != null && securityContext.isBrokerContext(); |
| } |
| |
| protected void assertAuthorized(DestinationAction action, String verbText) { |
| if (!isEnabled() || isSystemBroker(action)) { |
| return; |
| } |
| |
| final Subject subject = getSubject(action.getConnectionContext()); |
| |
| Collection<Permission> perms = this.actionPermissionResolver.getPermissions(action); |
| |
| if (!subject.isPermittedAll(perms)) { |
| String msg = createUnauthorizedMessage(subject, action, verbText); |
| throw new UnauthorizedException(msg); |
| } |
| } |
| |
| protected String createUnauthorizedMessage(Subject subject, DestinationAction action, String verbDisplayText) { |
| return "Subject " + toString(subject) + "is not authorized to " + verbDisplayText + " destination: " + action.getDestination(); |
| } |
| |
| @Override |
| public void addDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception { |
| |
| DestinationAction action = new DestinationAction(context, info.getDestination(), "create"); |
| assertAuthorized(action); |
| |
| super.addDestinationInfo(context, info); |
| } |
| |
| @Override |
| public Destination addDestination(ConnectionContext context, ActiveMQDestination destination, boolean create) throws Exception { |
| |
| DestinationAction action = new DestinationAction(context, destination, "create"); |
| assertAuthorized(action); |
| |
| return super.addDestination(context, destination, create); |
| } |
| |
| @Override |
| public void removeDestination(ConnectionContext context, ActiveMQDestination destination, long timeout) throws Exception { |
| |
| DestinationAction action = new DestinationAction(context, destination, "remove"); |
| assertAuthorized(action); |
| |
| super.removeDestination(context, destination, timeout); |
| } |
| |
| @Override |
| public void removeDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception { |
| |
| DestinationAction action = new DestinationAction(context, info.getDestination(), "remove"); |
| assertAuthorized(action); |
| |
| super.removeDestinationInfo(context, info); |
| } |
| |
| @Override |
| public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception { |
| |
| //Unlike when adding a producer, consumers must specify the destination at creation time, so we can rely on |
| //a destination being available to perform the authz check: |
| DestinationAction action = new DestinationAction(context, info.getDestination(), "read"); |
| assertAuthorized(action, "read from"); |
| |
| return super.addConsumer(context, info); |
| } |
| |
| @Override |
| public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception { |
| |
| // JMS allows producers to be created without first specifying a destination. In these cases, every send |
| // operation must specify a destination. Because of this, we only authorize 'addProducer' if a destination is |
| // specified. If not specified, the authz check in the 'send' method below will ensure authorization. |
| if (info.getDestination() != null) { |
| DestinationAction action = new DestinationAction(context, info.getDestination(), "write"); |
| assertAuthorized(action, "write to"); |
| } |
| |
| super.addProducer(context, info); |
| } |
| |
| @Override |
| public void send(ProducerBrokerExchange exchange, Message message) throws Exception { |
| |
| DestinationAction action = new DestinationAction(exchange.getConnectionContext(), message.getDestination(), "write"); |
| assertAuthorized(action, "write to"); |
| |
| super.send(exchange, message); |
| } |
| |
| } |