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.security;
018
019import java.util.Arrays;
020import java.util.Set;
021
022import org.apache.activemq.broker.Broker;
023import org.apache.activemq.broker.BrokerFilter;
024import org.apache.activemq.broker.ConnectionContext;
025import org.apache.activemq.broker.ProducerBrokerExchange;
026import org.apache.activemq.broker.region.CompositeDestinationInterceptor;
027import org.apache.activemq.broker.region.Destination;
028import org.apache.activemq.broker.region.DestinationInterceptor;
029import org.apache.activemq.broker.region.RegionBroker;
030import org.apache.activemq.broker.region.Subscription;
031import org.apache.activemq.command.ActiveMQDestination;
032import org.apache.activemq.command.ActiveMQQueue;
033import org.apache.activemq.command.ActiveMQTopic;
034import org.apache.activemq.command.ConsumerInfo;
035import org.apache.activemq.command.DestinationInfo;
036import org.apache.activemq.command.Message;
037import org.apache.activemq.command.ProducerInfo;
038
039/**
040 * Verifies if a authenticated user can do an operation against the broker using
041 * an authorization map.
042 *
043 *
044 */
045public class AuthorizationBroker extends BrokerFilter implements SecurityAdminMBean {
046
047    private volatile AuthorizationMap authorizationMap;
048
049    public AuthorizationBroker(Broker next, AuthorizationMap authorizationMap) {
050        super(next);
051        this.authorizationMap = authorizationMap;
052
053        // add DestinationInterceptor
054        final RegionBroker regionBroker = (RegionBroker) next.getAdaptor(RegionBroker.class);
055        final CompositeDestinationInterceptor compositeInterceptor = (CompositeDestinationInterceptor) regionBroker.getDestinationInterceptor();
056        DestinationInterceptor[] interceptors = compositeInterceptor.getInterceptors();
057        interceptors = Arrays.copyOf(interceptors, interceptors.length + 1);
058        interceptors[interceptors.length - 1] = new AuthorizationDestinationInterceptor(this);
059        compositeInterceptor.setInterceptors(interceptors);
060    }
061
062    public AuthorizationMap getAuthorizationMap() {
063        return authorizationMap;
064    }
065
066    public void setAuthorizationMap(AuthorizationMap map) {
067        authorizationMap = map;
068    }
069
070    protected SecurityContext checkSecurityContext(ConnectionContext context) throws SecurityException {
071        final SecurityContext securityContext = context.getSecurityContext();
072        if (securityContext == null) {
073            throw new SecurityException("User is not authenticated.");
074        }
075        return securityContext;
076    }
077
078    protected boolean checkDestinationAdmin(SecurityContext securityContext, ActiveMQDestination destination) {
079        Destination existing = this.getDestinationMap(destination).get(destination);
080        if (existing != null) {
081            return true;
082        }
083
084        if (!securityContext.isBrokerContext()) {
085            Set<?> allowedACLs = null;
086            if (!destination.isTemporary()) {
087                allowedACLs = authorizationMap.getAdminACLs(destination);
088            } else {
089                allowedACLs = authorizationMap.getTempDestinationAdminACLs();
090            }
091
092            if (allowedACLs != null && !securityContext.isInOneOf(allowedACLs)) {
093                return false;
094            }
095        }
096        return true;
097    }
098
099    @Override
100    public void addDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
101        final SecurityContext securityContext = checkSecurityContext(context);
102
103        if (!checkDestinationAdmin(securityContext, info.getDestination())) {
104            throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to create: " + info.getDestination());
105        }
106
107        super.addDestinationInfo(context, info);
108    }
109
110    @Override
111    public Destination addDestination(ConnectionContext context, ActiveMQDestination destination,boolean create) throws Exception {
112        final SecurityContext securityContext = checkSecurityContext(context);
113
114        if (!checkDestinationAdmin(securityContext, destination)) {
115            throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to create: " + destination);
116        }
117
118        return super.addDestination(context, destination,create);
119    }
120
121    @Override
122    public void removeDestination(ConnectionContext context, ActiveMQDestination destination, long timeout) throws Exception {
123        final SecurityContext securityContext = checkSecurityContext(context);
124
125        if (!checkDestinationAdmin(securityContext, destination)) {
126            throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to remove: " + destination);
127        }
128
129        securityContext.getAuthorizedWriteDests().remove(destination);
130
131        super.removeDestination(context, destination, timeout);
132    }
133
134    @Override
135    public void removeDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
136        final SecurityContext securityContext = checkSecurityContext(context);
137
138        if (!checkDestinationAdmin(securityContext, info.getDestination())) {
139            throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to remove: " + info.getDestination());
140        }
141
142        securityContext.getAuthorizedWriteDests().remove(info.getDestination());
143
144        super.removeDestinationInfo(context, info);
145    }
146
147    @Override
148    public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
149        final SecurityContext securityContext = checkSecurityContext(context);
150
151        Set<?> allowedACLs = null;
152        if (!info.getDestination().isTemporary()) {
153            allowedACLs = authorizationMap.getReadACLs(info.getDestination());
154        } else {
155            allowedACLs = authorizationMap.getTempDestinationReadACLs();
156        }
157
158        if (!securityContext.isBrokerContext() && allowedACLs != null && !securityContext.isInOneOf(allowedACLs) ) {
159            throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to read from: " + info.getDestination());
160        }
161
162        /*
163         * Need to think about this a little more. We could do per message
164         * security checking to implement finer grained security checking. For
165         * example a user can only see messages with price>1000 . Perhaps this
166         * should just be another additional broker filter that installs this
167         * type of feature. If we did want to do that, then we would install a
168         * predicate. We should be careful since there may be an existing
169         * predicate already assigned and the consumer info may be sent to a
170         * remote broker, so it also needs to support being marshaled.
171         * info.setAdditionalPredicate(new BooleanExpression() { public boolean
172         * matches(MessageEvaluationContext message) throws JMSException { if(
173         * !subject.getAuthorizedReadDests().contains(message.getDestination()) ) {
174         * Set allowedACLs =
175         * authorizationMap.getReadACLs(message.getDestination());
176         * if(allowedACLs!=null && !subject.isInOneOf(allowedACLs)) return
177         * false; subject.getAuthorizedReadDests().put(message.getDestination(),
178         * message.getDestination()); } return true; } public Object
179         * evaluate(MessageEvaluationContext message) throws JMSException {
180         * return matches(message) ? Boolean.TRUE : Boolean.FALSE; } });
181         */
182
183        return super.addConsumer(context, info);
184    }
185
186    @Override
187    public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception {
188        final SecurityContext securityContext = checkSecurityContext(context);
189
190        if (!securityContext.isBrokerContext() && info.getDestination() != null) {
191
192            Set<?> allowedACLs = null;
193            if (!info.getDestination().isTemporary()) {
194                allowedACLs = authorizationMap.getWriteACLs(info.getDestination());
195            } else {
196                allowedACLs = authorizationMap.getTempDestinationWriteACLs();
197            }
198            if (allowedACLs != null && !securityContext.isInOneOf(allowedACLs)) {
199                throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to write to: " + info.getDestination());
200            }
201            securityContext.getAuthorizedWriteDests().put(info.getDestination(), info.getDestination());
202        }
203
204        super.addProducer(context, info);
205    }
206
207    @Override
208    public void send(ProducerBrokerExchange producerExchange, Message messageSend) throws Exception {
209        final SecurityContext securityContext = checkSecurityContext(producerExchange.getConnectionContext());
210
211        if (!securityContext.isBrokerContext() && !securityContext.getAuthorizedWriteDests().containsValue(messageSend.getDestination())) {
212
213            Set<?> allowedACLs = null;
214            if (!messageSend.getDestination().isTemporary()) {
215                allowedACLs = authorizationMap.getWriteACLs(messageSend.getDestination());
216            } else {
217                allowedACLs = authorizationMap.getTempDestinationWriteACLs();
218            }
219
220            if (allowedACLs != null && !securityContext.isInOneOf(allowedACLs)) {
221                throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to write to: " + messageSend.getDestination());
222            }
223            securityContext.getAuthorizedWriteDests().put(messageSend.getDestination(), messageSend.getDestination());
224        }
225
226        super.send(producerExchange, messageSend);
227    }
228
229    // SecurityAdminMBean interface
230    // -------------------------------------------------------------------------
231
232    @Override
233    public void addQueueRole(String queue, String operation, String role) {
234        addDestinationRole(new ActiveMQQueue(queue), operation, role);
235    }
236
237    @Override
238    public void addTopicRole(String topic, String operation, String role) {
239        addDestinationRole(new ActiveMQTopic(topic), operation, role);
240    }
241
242    @Override
243    public void removeQueueRole(String queue, String operation, String role) {
244        removeDestinationRole(new ActiveMQQueue(queue), operation, role);
245    }
246
247    @Override
248    public void removeTopicRole(String topic, String operation, String role) {
249        removeDestinationRole(new ActiveMQTopic(topic), operation, role);
250    }
251
252    public void addDestinationRole(javax.jms.Destination destination, String operation, String role) {
253    }
254
255    public void removeDestinationRole(javax.jms.Destination destination, String operation, String role) {
256    }
257
258    @Override
259    public void addRole(String role) {
260    }
261
262    @Override
263    public void addUserRole(String user, String role) {
264    }
265
266    @Override
267    public void removeRole(String role) {
268    }
269
270    @Override
271    public void removeUserRole(String user, String role) {
272    }
273
274}