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.cassandra.auth.jmx;
  
  import java.security.AccessController;
  import java.security.PrivilegedAction;
  import javax.management.remote.JMXAuthenticator;
  import javax.security.auth.Subject;
  import javax.security.auth.callback.*;
  import javax.security.auth.login.LoginContext;
  import javax.security.auth.login.LoginException;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import org.apache.cassandra.exceptions.ConfigurationException;
  
  /**
   * An alternative to the JAAS based implementation of JMXAuthenticator provided
   * by the JDK (JMXPluggableAuthenticator).
   *
   * Authentication is performed via delegation to a LoginModule. The JAAS login
   * config is specified by passing its identifier in a custom system property:
   *     cassandra.jmx.remote.login.config
   *
   * The location of the JAAS configuration file containing that config is
   * specified in the standard way, using the java.security.auth.login.config
   * system property.
   *
   * If authentication is successful then a Subject containing one or more
   * Principals is returned. This Subject may then be used during authorization
   * if a JMX authorization is enabled.
   */
  public final class AuthenticationProxy implements JMXAuthenticator
  {
      private static Logger logger = LoggerFactory.getLogger(AuthenticationProxy.class);
  
      // Identifier of JAAS configuration to be used for subject authentication
      private final String loginConfigName;
  
      /**
       * Creates an instance of <code>JMXPluggableAuthenticator</code>
       * and initializes it with a {@link LoginContext}.
       *
       * @param loginConfigName name of the specifig JAAS login configuration to
       *                        use when authenticating JMX connections
       * @throws SecurityException if the authentication mechanism cannot be
       *         initialized.
       */
      public AuthenticationProxy(String loginConfigName)
      {
          if (loginConfigName == null)
              throw new ConfigurationException("JAAS login configuration missing for JMX authenticator setup");
  
          this.loginConfigName = loginConfigName;
      }
  
      /**
       * Perform authentication of the client opening the {@code}MBeanServerConnection{@code}
       *
       * @param credentials optionally these credentials may be supplied by the JMX user.
       *                    Out of the box, the JDK's {@code}RMIServerImpl{@code} is capable
       *                    of supplying a two element String[], containing username and password.
       *                    If present, these credentials will be made available to configured
       *                    {@code}LoginModule{@code}s via {@code}JMXCallbackHandler{@code}.
       *
       * @return the authenticated subject containing any {@code}Principal{@code}s added by
       *the {@code}LoginModule{@code}s
       *
       * @throws SecurityException if the server cannot authenticate the user
       *         with the provided credentials.
       */
      public Subject authenticate(Object credentials)
      {
          // The credentials object is expected to be a string array holding the subject's
          // username & password. Those values are made accessible to LoginModules via the
          // JMXCallbackHandler.
          JMXCallbackHandler callbackHandler = new JMXCallbackHandler(credentials);
          try
          {
              LoginContext loginContext = new LoginContext(loginConfigName, callbackHandler);
              loginContext.login();
              final Subject subject = loginContext.getSubject();
              if (!subject.isReadOnly())
              {
                  AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
                      subject.setReadOnly();
                      return null;
                  });
              }
  
              return subject;
          }
          catch (LoginException e)
          {
              logger.trace("Authentication exception", e);
              throw new SecurityException("Authentication error", e);
          }
      }
  
      /**
       * This callback handler supplies the username and password (which was
       * optionally supplied by the JMX user) to the JAAS login module performing
       * the authentication, should it require them . No interactive user
       * prompting is necessary because the credentials are already available to
       * this class (via its enclosing class).
       */
      private static final class JMXCallbackHandler implements CallbackHandler
      {
          private char[] username;
          private char[] password;
          private JMXCallbackHandler(Object credentials)
          {
              // if username/password credentials were supplied, store them in
              // the relevant variables to make them accessible to LoginModules
              // via JMXCallbackHandler
              if (credentials instanceof String[])
              {
                  String[] strings = (String[]) credentials;
                  if (strings[0] != null)
                      username = strings[0].toCharArray();
                  if (strings[1] != null)
                      password = strings[1].toCharArray();
              }
          }
  
          public void handle(Callback[] callbacks) throws UnsupportedCallbackException
          {
              for (int i = 0; i < callbacks.length; i++)
              {
                  if (callbacks[i] instanceof NameCallback)
                      ((NameCallback)callbacks[i]).setName(username == null ? null : new String(username));
                  else if (callbacks[i] instanceof PasswordCallback)
                      ((PasswordCallback)callbacks[i]).setPassword(password == null ? null : password);
                  else
                      throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback: " + callbacks[i].getClass().getName());
              }
          }
      }
  }
  
  