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.authc;
  
  import org.apache.activemq.shiro.ConnectionReference;
  import org.apache.activemq.shiro.subject.SubjectConnectionReference;
  import org.apache.shiro.subject.PrincipalCollection;
  import org.apache.shiro.subject.SimplePrincipalCollection;
  import org.apache.shiro.subject.Subject;
  
  import java.util.Collection;
  
  /**
   * @since 5.10.0
   */
  public class DefaultAuthenticationPolicy implements AuthenticationPolicy {
  
      private boolean vmConnectionAuthenticationRequired = false;
      private String systemAccountUsername = "system";
      private String systemAccountRealmName = "iniRealm";
  
      private boolean anonymousAccessAllowed = false;
      private String anonymousAccountUsername = "anonymous";
      private String anonymousAccountRealmName = "iniRealm";
  
      public boolean isVmConnectionAuthenticationRequired() {
          return vmConnectionAuthenticationRequired;
      }
  
      public void setVmConnectionAuthenticationRequired(boolean vmConnectionAuthenticationRequired) {
          this.vmConnectionAuthenticationRequired = vmConnectionAuthenticationRequired;
      }
  
      public String getSystemAccountUsername() {
          return systemAccountUsername;
      }
  
      public void setSystemAccountUsername(String systemAccountUsername) {
          this.systemAccountUsername = systemAccountUsername;
      }
  
      public String getSystemAccountRealmName() {
          return systemAccountRealmName;
      }
  
      public void setSystemAccountRealmName(String systemAccountRealmName) {
          this.systemAccountRealmName = systemAccountRealmName;
      }
  
      public boolean isAnonymousAccessAllowed() {
          return anonymousAccessAllowed;
      }
  
      public void setAnonymousAccessAllowed(boolean anonymousAccessAllowed) {
          this.anonymousAccessAllowed = anonymousAccessAllowed;
      }
  
      public String getAnonymousAccountUsername() {
          return anonymousAccountUsername;
      }
  
      public void setAnonymousAccountUsername(String anonymousAccountUsername) {
          this.anonymousAccountUsername = anonymousAccountUsername;
      }
  
      public String getAnonymousAccountRealmName() {
          return anonymousAccountRealmName;
      }
  
      public void setAnonymousAccountRealmName(String anonymousAccountRealmName) {
          this.anonymousAccountRealmName = anonymousAccountRealmName;
      }
  
      /**
       * Returns {@code true} if the client connection has supplied credentials to authenticate itself, {@code false}
       * otherwise.
       *
       * @param conn the client's connection context
       * @return {@code true} if the client connection has supplied credentials to authenticate itself, {@code false}
       *         otherwise.
       */
      protected boolean credentialsAvailable(ConnectionReference conn) {
          return conn.getConnectionInfo().getUserName() != null || conn.getConnectionInfo().getPassword() != null;
      }
  
      @Override
      public boolean isAuthenticationRequired(SubjectConnectionReference conn) {
          Subject subject = conn.getSubject();
  
          if (subject.isAuthenticated()) {
              //already authenticated:
              return false;
          }
          //subject is not authenticated.  Authentication is required by default for all accounts other than
          //the anonymous user (if enabled) or the vm account (if enabled)
          if (isAnonymousAccessAllowed()) {
              if (isAnonymousAccount(subject)) {
                  return false;
              }
          }
  
          if (!isVmConnectionAuthenticationRequired()) {
              if (isSystemAccount(subject)) {
                  return false;
              }
          }
  
          return true;
      }
  
      protected boolean isAnonymousAccount(Subject subject) {
          PrincipalCollection pc = subject.getPrincipals();
          return pc != null && matches(pc, anonymousAccountUsername, anonymousAccountRealmName);
      }
  
      protected boolean isSystemAccount(Subject subject) {
          PrincipalCollection pc = subject.getPrincipals();
          return pc != null && matches(pc, systemAccountUsername, systemAccountRealmName);
      }
  
      protected boolean matches(PrincipalCollection principals, String username, String realmName) {
          Collection realmPrincipals = principals.fromRealm(realmName);
          if (realmPrincipals != null && !realmPrincipals.isEmpty()) {
              if (realmPrincipals.iterator().next().equals(username)) {
                  return true;
              }
          }
          return false;
      }
  
      protected boolean isSystemConnection(ConnectionReference conn) {
          String remoteAddress = conn.getConnectionContext().getConnection().getRemoteAddress();
          return remoteAddress.startsWith("vm:");
      }
  
      @Override
      public void customizeSubject(Subject.Builder subjectBuilder, ConnectionReference conn) {
          // We only need to specify a custom identity or authentication state if a normal authentication will not occur.
          // If the client supplied connection credentials, the AuthenticationFilter will perform a normal authentication,
          // so we should exit immediately:
          if (credentialsAvailable(conn)) {
              return;
          }
  
          //The connection cannot be authenticated, potentially implying a system or anonymous connection.  Check if so:
          if (isAssumeIdentity(conn)) {
              PrincipalCollection assumedIdentity = createAssumedIdentity(conn);
              subjectBuilder.principals(assumedIdentity);
          }
      }
  
      /**
       * Returns {@code true} if an unauthenticated connection should still assume a specific identity, {@code false}
       * otherwise.  This method will <em>only</em> be called if there are no connection
       * {@link #credentialsAvailable(ConnectionReference) credentialsAvailable}.
       * If a client supplies connection credentials, they will always be used to authenticate the client with that
       * identity.
       * <p/>
       * If {@code true} is returned, the assumed identity will be returned by
       * {@link #createAssumedIdentity(ConnectionReference) createAssumedIdentity}.
       * <h3>Warning</h3>
       * This method exists primarily to support the system and anonymous accounts - it is probably unsafe to return
       * {@code true} in most other scenarios.
       *
       * @param conn a reference to the client's connection
       * @return {@code true} if an unauthenticated connection should still assume a specific identity, {@code false}
       *         otherwise.
       */
      protected boolean isAssumeIdentity(ConnectionReference conn) {
          return isAnonymousAccessAllowed() ||
                  (isSystemConnection(conn) && !isVmConnectionAuthenticationRequired());
      }
  
      /**
       * Returns a Shiro {@code PrincipalCollection} representing the identity to assume (without true authentication) for
       * the specified Connection.
       * <p/>
       * This method is <em>only</em> called if {@link #isAssumeIdentity(ConnectionReference)} is {@code true}.
       *
       * @param conn a reference to the client's connection
       * @return a Shiro {@code PrincipalCollection} representing the identity to assume (without true authentication) for
       *         the specified Connection.
       */
      protected PrincipalCollection createAssumedIdentity(ConnectionReference conn) {
  
          //anonymous by default:
          String username = anonymousAccountUsername;
          String realmName = anonymousAccountRealmName;
  
          //vm connections are special and should assume the system account:
          if (isSystemConnection(conn)) {
              username = systemAccountUsername;
              realmName = systemAccountRealmName;
          }
  
          return new SimplePrincipalCollection(username, realmName);
      }
  }
  