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.broker.ConnectionContext; 020import org.apache.activemq.broker.ProducerBrokerExchange; 021import org.apache.activemq.broker.region.Destination; 022import org.apache.activemq.broker.region.Subscription; 023import org.apache.activemq.command.ActiveMQDestination; 024import org.apache.activemq.command.ConsumerInfo; 025import org.apache.activemq.command.DestinationInfo; 026import org.apache.activemq.command.Message; 027import org.apache.activemq.command.ProducerInfo; 028import org.apache.activemq.security.SecurityContext; 029import org.apache.activemq.shiro.env.EnvironmentFilter; 030import org.apache.activemq.shiro.subject.ConnectionSubjectResolver; 031import org.apache.shiro.authz.Permission; 032import org.apache.shiro.authz.UnauthorizedException; 033import org.apache.shiro.subject.PrincipalCollection; 034import org.apache.shiro.subject.Subject; 035 036import java.util.Collection; 037 038/** 039 * The {@code AuthorizationFilter} asserts that actions are allowed to execute first before they are actually 040 * executed. Such actions include creating, removing, reading from and writing to destinations. 041 * <p/> 042 * This implementation is strictly permission-based, allowing for the finest-grained security policies possible. 043 * Whenever a {@link Subject} associated with a connection attempts to perform an {@link org.apache.activemq.shiro.authz.Action} (such as creating a 044 * destination, or reading from a queue, etc), one or more {@link Permission}s representing that {@code action} are 045 * checked. 046 * <p/> 047 * If the {@code Subject}{@link Subject#isPermitted(org.apache.shiro.authz.Permission) isPermitted} to perform the 048 * {@code action}, the action is allowed to execute and the broker filter chain executes uninterrupted. 049 * <p/> 050 * However, if the {@code Subject} is not permitted to perform the action, an {@link UnauthorizedException} will be 051 * thrown, preventing the filter chain from executing that action. 052 * <h2>ActionPermissionResolver</h2> 053 * The attempted {@code Action} is guarded by one or more {@link Permission}s as indicated by a configurable 054 * {@link #setActionPermissionResolver(org.apache.activemq.shiro.authz.ActionPermissionResolver) actionPermissionResolver}. The 055 * {@code actionPermissionResolver} indicates which permissions must be granted to the connection {@code Subject} in 056 * order for the action to execute. 057 * <p/> 058 * The default {@code actionPermissionResolver} instance is a 059 * {@link org.apache.activemq.shiro.authz.DestinationActionPermissionResolver DestinationActionPermissionResolver}, which indicates which permissions 060 * are required to perform any action on a particular destination. Those familiar with Shiro's 061 * {@link org.apache.shiro.authz.permission.WildcardPermission WildcardPermission} syntax will find the 062 * {@code DestinationActionPermissionResolver}'s 063 * {@link org.apache.activemq.shiro.authz.DestinationActionPermissionResolver#createPermissionString createPermissionString} method 064 * documentation valuable for understanding how destination actions are represented as permissions. 065 * 066 * @see org.apache.activemq.shiro.authz.ActionPermissionResolver 067 * @see org.apache.activemq.shiro.authz.DestinationActionPermissionResolver 068 * @since 5.10.0 069 */ 070public class AuthorizationFilter extends EnvironmentFilter { 071 072 private ActionPermissionResolver actionPermissionResolver; 073 074 public AuthorizationFilter() { 075 this.actionPermissionResolver = new DestinationActionPermissionResolver(); 076 } 077 078 /** 079 * Returns the {@code ActionPermissionResolver} used to indicate which permissions are required to be granted to 080 * a {@link Subject} to perform a particular destination {@link org.apache.activemq.shiro.authz.Action}, (such as creating a 081 * destination, or reading from a queue, etc). The default instance is a 082 * {@link DestinationActionPermissionResolver}. 083 * 084 * @return the {@code ActionPermissionResolver} used to indicate which permissions are required to be granted to 085 * a {@link Subject} to perform a particular destination {@link org.apache.activemq.shiro.authz.Action}, (such as creating a 086 * destination, or reading from a queue, etc). 087 */ 088 public ActionPermissionResolver getActionPermissionResolver() { 089 return actionPermissionResolver; 090 } 091 092 /** 093 * Sets the {@code ActionPermissionResolver} used to indicate which permissions are required to be granted to 094 * a {@link Subject} to perform a particular destination {@link org.apache.activemq.shiro.authz.Action}, (such as creating a 095 * destination, or reading from a queue, etc). Unless overridden by this method, the default instance is a 096 * {@link DestinationActionPermissionResolver}. 097 * 098 * @param actionPermissionResolver the {@code ActionPermissionResolver} used to indicate which permissions are 099 * required to be granted to a {@link Subject} to perform a particular destination 100 * {@link org.apache.activemq.shiro.authz.Action}, (such as creating a destination, or reading from a queue, etc). 101 */ 102 public void setActionPermissionResolver(ActionPermissionResolver actionPermissionResolver) { 103 this.actionPermissionResolver = actionPermissionResolver; 104 } 105 106 /** 107 * Returns the {@code Subject} associated with the specified connection using a 108 * {@link org.apache.activemq.shiro.subject.ConnectionSubjectResolver}. 109 * 110 * @param ctx the connection context 111 * @return the {@code Subject} associated with the specified connection. 112 */ 113 protected Subject getSubject(ConnectionContext ctx) { 114 return new ConnectionSubjectResolver(ctx).getSubject(); 115 } 116 117 protected String toString(Subject subject) { 118 PrincipalCollection pc = subject.getPrincipals(); 119 if (pc != null && !pc.isEmpty()) { 120 return "[" + pc.toString() + "] "; 121 } 122 return ""; 123 } 124 125 protected void assertAuthorized(DestinationAction action) { 126 assertAuthorized(action, action.getVerb()); 127 } 128 129 //ActiveMQ internals will create a ConnectionContext with a SecurityContext that is not 130 //Shiro specific. We need to allow actions for internal system operations: 131 protected boolean isSystemBroker(DestinationAction action) { 132 ConnectionContext context = action.getConnectionContext(); 133 SecurityContext securityContext = context.getSecurityContext(); 134 return securityContext != null && securityContext.isBrokerContext(); 135 } 136 137 protected void assertAuthorized(DestinationAction action, String verbText) { 138 if (!isEnabled() || isSystemBroker(action)) { 139 return; 140 } 141 142 final Subject subject = getSubject(action.getConnectionContext()); 143 144 Collection<Permission> perms = this.actionPermissionResolver.getPermissions(action); 145 146 if (!subject.isPermittedAll(perms)) { 147 String msg = createUnauthorizedMessage(subject, action, verbText); 148 throw new UnauthorizedException(msg); 149 } 150 } 151 152 protected String createUnauthorizedMessage(Subject subject, DestinationAction action, String verbDisplayText) { 153 return "Subject " + toString(subject) + "is not authorized to " + verbDisplayText + " destination: " + action.getDestination(); 154 } 155 156 @Override 157 public void addDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception { 158 159 DestinationAction action = new DestinationAction(context, info.getDestination(), "create"); 160 assertAuthorized(action); 161 162 super.addDestinationInfo(context, info); 163 } 164 165 @Override 166 public Destination addDestination(ConnectionContext context, ActiveMQDestination destination, boolean create) throws Exception { 167 168 DestinationAction action = new DestinationAction(context, destination, "create"); 169 assertAuthorized(action); 170 171 return super.addDestination(context, destination, create); 172 } 173 174 @Override 175 public void removeDestination(ConnectionContext context, ActiveMQDestination destination, long timeout) throws Exception { 176 177 DestinationAction action = new DestinationAction(context, destination, "remove"); 178 assertAuthorized(action); 179 180 super.removeDestination(context, destination, timeout); 181 } 182 183 @Override 184 public void removeDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception { 185 186 DestinationAction action = new DestinationAction(context, info.getDestination(), "remove"); 187 assertAuthorized(action); 188 189 super.removeDestinationInfo(context, info); 190 } 191 192 @Override 193 public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception { 194 195 //Unlike when adding a producer, consumers must specify the destination at creation time, so we can rely on 196 //a destination being available to perform the authz check: 197 DestinationAction action = new DestinationAction(context, info.getDestination(), "read"); 198 assertAuthorized(action, "read from"); 199 200 return super.addConsumer(context, info); 201 } 202 203 @Override 204 public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception { 205 206 // JMS allows producers to be created without first specifying a destination. In these cases, every send 207 // operation must specify a destination. Because of this, we only authorize 'addProducer' if a destination is 208 // specified. If not specified, the authz check in the 'send' method below will ensure authorization. 209 if (info.getDestination() != null) { 210 DestinationAction action = new DestinationAction(context, info.getDestination(), "write"); 211 assertAuthorized(action, "write to"); 212 } 213 214 super.addProducer(context, info); 215 } 216 217 @Override 218 public void send(ProducerBrokerExchange exchange, Message message) throws Exception { 219 220 DestinationAction action = new DestinationAction(exchange.getConnectionContext(), message.getDestination(), "write"); 221 assertAuthorized(action, "write to"); 222 223 super.send(exchange, message); 224 } 225 226}