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.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);
      }
  
  }
  