content: /**
   * 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.security;
  
  import java.util.Arrays;
  import java.util.Set;
  
  import org.apache.activemq.broker.Broker;
  import org.apache.activemq.broker.BrokerFilter;
  import org.apache.activemq.broker.ConnectionContext;
  import org.apache.activemq.broker.ProducerBrokerExchange;
  import org.apache.activemq.broker.region.CompositeDestinationInterceptor;
  import org.apache.activemq.broker.region.Destination;
  import org.apache.activemq.broker.region.DestinationInterceptor;
  import org.apache.activemq.broker.region.RegionBroker;
  import org.apache.activemq.broker.region.Subscription;
  import org.apache.activemq.command.ActiveMQDestination;
  import org.apache.activemq.command.ActiveMQQueue;
  import org.apache.activemq.command.ActiveMQTopic;
  import org.apache.activemq.command.ConsumerInfo;
  import org.apache.activemq.command.DestinationInfo;
  import org.apache.activemq.command.Message;
  import org.apache.activemq.command.ProducerInfo;
  
  /**
   * Verifies if a authenticated user can do an operation against the broker using
   * an authorization map.
   *
   *
   */
  public class AuthorizationBroker extends BrokerFilter implements SecurityAdminMBean {
  
      private volatile AuthorizationMap authorizationMap;
  
      public AuthorizationBroker(Broker next, AuthorizationMap authorizationMap) {
          super(next);
          this.authorizationMap = authorizationMap;
  
          // add DestinationInterceptor
          final RegionBroker regionBroker = (RegionBroker) next.getAdaptor(RegionBroker.class);
          final CompositeDestinationInterceptor compositeInterceptor = (CompositeDestinationInterceptor) regionBroker.getDestinationInterceptor();
          DestinationInterceptor[] interceptors = compositeInterceptor.getInterceptors();
          interceptors = Arrays.copyOf(interceptors, interceptors.length + 1);
          interceptors[interceptors.length - 1] = new AuthorizationDestinationInterceptor(this);
          compositeInterceptor.setInterceptors(interceptors);
      }
  
      public AuthorizationMap getAuthorizationMap() {
          return authorizationMap;
      }
  
      public void setAuthorizationMap(AuthorizationMap map) {
          authorizationMap = map;
      }
  
      protected SecurityContext checkSecurityContext(ConnectionContext context) throws SecurityException {
          final SecurityContext securityContext = context.getSecurityContext();
          if (securityContext == null) {
              throw new SecurityException("User is not authenticated.");
          }
          return securityContext;
      }
  
      protected boolean checkDestinationAdmin(SecurityContext securityContext, ActiveMQDestination destination) {
          Destination existing = this.getDestinationMap(destination).get(destination);
          if (existing != null) {
              return true;
          }
  
          if (!securityContext.isBrokerContext()) {
              Set<?> allowedACLs = null;
              if (!destination.isTemporary()) {
                  allowedACLs = authorizationMap.getAdminACLs(destination);
              } else {
                  allowedACLs = authorizationMap.getTempDestinationAdminACLs();
              }
  
              if (allowedACLs != null && !securityContext.isInOneOf(allowedACLs)) {
                  return false;
              }
          }
          return true;
      }
  
      @Override
      public void addDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
          final SecurityContext securityContext = checkSecurityContext(context);
  
          if (!checkDestinationAdmin(securityContext, info.getDestination())) {
              throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to create: " + info.getDestination());
          }
  
          super.addDestinationInfo(context, info);
      }
  
      @Override
      public Destination addDestination(ConnectionContext context, ActiveMQDestination destination,boolean create) throws Exception {
          final SecurityContext securityContext = checkSecurityContext(context);
  
          if (!checkDestinationAdmin(securityContext, destination)) {
              throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to create: " + destination);
          }
  
          return super.addDestination(context, destination,create);
      }
  
      @Override
      public void removeDestination(ConnectionContext context, ActiveMQDestination destination, long timeout) throws Exception {
          final SecurityContext securityContext = checkSecurityContext(context);
  
          if (!checkDestinationAdmin(securityContext, destination)) {
              throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to remove: " + destination);
          }
  
          securityContext.getAuthorizedWriteDests().remove(destination);
  
          super.removeDestination(context, destination, timeout);
      }
  
      @Override
      public void removeDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
          final SecurityContext securityContext = checkSecurityContext(context);
  
          if (!checkDestinationAdmin(securityContext, info.getDestination())) {
              throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to remove: " + info.getDestination());
          }
  
          securityContext.getAuthorizedWriteDests().remove(info.getDestination());
  
          super.removeDestinationInfo(context, info);
      }
  
      @Override
      public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
          final SecurityContext securityContext = checkSecurityContext(context);
  
          Set<?> allowedACLs = null;
          if (!info.getDestination().isTemporary()) {
              allowedACLs = authorizationMap.getReadACLs(info.getDestination());
          } else {
              allowedACLs = authorizationMap.getTempDestinationReadACLs();
          }
  
          if (!securityContext.isBrokerContext() && allowedACLs != null && !securityContext.isInOneOf(allowedACLs) ) {
              throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to read from: " + info.getDestination());
          }
  
          /*
           * Need to think about this a little more. We could do per message
           * security checking to implement finer grained security checking. For
           * example a user can only see messages with price>1000 . Perhaps this
           * should just be another additional broker filter that installs this
           * type of feature. If we did want to do that, then we would install a
           * predicate. We should be careful since there may be an existing
           * predicate already assigned and the consumer info may be sent to a
           * remote broker, so it also needs to support being marshaled.
           * info.setAdditionalPredicate(new BooleanExpression() { public boolean
           * matches(MessageEvaluationContext message) throws JMSException { if(
           * !subject.getAuthorizedReadDests().contains(message.getDestination()) ) {
           * Set allowedACLs =
           * authorizationMap.getReadACLs(message.getDestination());
           * if(allowedACLs!=null && !subject.isInOneOf(allowedACLs)) return
           * false; subject.getAuthorizedReadDests().put(message.getDestination(),
           * message.getDestination()); } return true; } public Object
           * evaluate(MessageEvaluationContext message) throws JMSException {
           * return matches(message) ? Boolean.TRUE : Boolean.FALSE; } });
           */
  
          return super.addConsumer(context, info);
      }
  
      @Override
      public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception {
          final SecurityContext securityContext = checkSecurityContext(context);
  
          if (!securityContext.isBrokerContext() && info.getDestination() != null) {
  
              Set<?> allowedACLs = null;
              if (!info.getDestination().isTemporary()) {
                  allowedACLs = authorizationMap.getWriteACLs(info.getDestination());
              } else {
                  allowedACLs = authorizationMap.getTempDestinationWriteACLs();
              }
              if (allowedACLs != null && !securityContext.isInOneOf(allowedACLs)) {
                  throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to write to: " + info.getDestination());
              }
              securityContext.getAuthorizedWriteDests().put(info.getDestination(), info.getDestination());
          }
  
          super.addProducer(context, info);
      }
  
      @Override
      public void send(ProducerBrokerExchange producerExchange, Message messageSend) throws Exception {
          final SecurityContext securityContext = checkSecurityContext(producerExchange.getConnectionContext());
  
          if (!securityContext.isBrokerContext() && !securityContext.getAuthorizedWriteDests().containsValue(messageSend.getDestination())) {
  
              Set<?> allowedACLs = null;
              if (!messageSend.getDestination().isTemporary()) {
                  allowedACLs = authorizationMap.getWriteACLs(messageSend.getDestination());
              } else {
                  allowedACLs = authorizationMap.getTempDestinationWriteACLs();
              }
  
              if (allowedACLs != null && !securityContext.isInOneOf(allowedACLs)) {
                  throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to write to: " + messageSend.getDestination());
              }
              securityContext.getAuthorizedWriteDests().put(messageSend.getDestination(), messageSend.getDestination());
          }
  
          super.send(producerExchange, messageSend);
      }
  
      // SecurityAdminMBean interface
      // -------------------------------------------------------------------------
  
      @Override
      public void addQueueRole(String queue, String operation, String role) {
          addDestinationRole(new ActiveMQQueue(queue), operation, role);
      }
  
      @Override
      public void addTopicRole(String topic, String operation, String role) {
          addDestinationRole(new ActiveMQTopic(topic), operation, role);
      }
  
      @Override
      public void removeQueueRole(String queue, String operation, String role) {
          removeDestinationRole(new ActiveMQQueue(queue), operation, role);
      }
  
      @Override
      public void removeTopicRole(String topic, String operation, String role) {
          removeDestinationRole(new ActiveMQTopic(topic), operation, role);
      }
  
      public void addDestinationRole(jakarta.jms.Destination destination, String operation, String role) {
      }
  
      public void removeDestinationRole(jakarta.jms.Destination destination, String operation, String role) {
      }
  
      @Override
      public void addRole(String role) {
      }
  
      @Override
      public void addUserRole(String user, String role) {
      }
  
      @Override
      public void removeRole(String role) {
      }
  
      @Override
      public void removeUserRole(String user, String role) {
      }
  
  }
  