/*
 * 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 com.datastax.dse.driver.internal.core.auth;

import com.datastax.dse.driver.api.core.auth.DseGssApiAuthProviderBase;
import com.datastax.dse.driver.api.core.config.DseDriverOption;
import com.datastax.oss.driver.api.core.auth.AuthProvider;
import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
import com.datastax.oss.driver.api.core.context.DriverContext;
import com.datastax.oss.driver.api.core.metadata.EndPoint;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Map;
import net.jcip.annotations.ThreadSafe;

/**
 * {@link AuthProvider} that provides GSSAPI authenticator instances for clients to connect to DSE
 * clusters secured with {@code DseAuthenticator}.
 *
 * <p>To activate this provider an {@code auth-provider} section must be included in the driver
 * configuration, for example:
 *
 * <pre>
 * dse-java-driver {
 *  auth-provider {
 *      class = com.datastax.dse.driver.internal.core.auth.DseGssApiAuthProvider
 *      login-configuration {
 *          principal = "user principal here ex cassandra@DATASTAX.COM"
 *          useKeyTab = "true"
 *          refreshKrb5Config = "true"
 *          keyTab = "Path to keytab file here"
 *      }
 *   }
 * }
 * </pre>
 *
 * <h2>Kerberos Authentication</h2>
 *
 * Keytab and ticket cache settings are specified using a standard JAAS configuration file. The
 * location of the file can be set using the <code>java.security.auth.login.config</code> system
 * property or by adding a <code>login.config.url.n</code> entry in the <code>java.security</code>
 * properties file. Alternatively a login-configuration section can be included in the driver
 * configuration.
 *
 * <p>See the following documents for further details:
 *
 * <ol>
 *   <li><a
 *       href="https://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/tutorials/LoginConfigFile.html">JAAS
 *       Login Configuration File</a>;
 *   <li><a
 *       href="https://docs.oracle.com/javase/6/docs/jre/api/security/jaas/spec/com/sun/security/auth/module/Krb5LoginModule.html">Krb5LoginModule
 *       options</a>;
 *   <li><a
 *       href="http://docs.oracle.com/javase/6/docs/technotes/guides/security/jaas/tutorials/GeneralAcnOnly.html">JAAS
 *       Authentication Tutorial</a> for more on JAAS in general.
 * </ol>
 *
 * <h3>Authentication using ticket cache</h3>
 *
 * Run <code>kinit</code> to obtain a ticket and populate the cache before connecting. JAAS config:
 *
 * <pre>
 * DseClient {
 *   com.sun.security.auth.module.Krb5LoginModule required
 *     useTicketCache=true
 *     renewTGT=true;
 * };
 * </pre>
 *
 * <h3>Authentication using a keytab file</h3>
 *
 * To enable authentication using a keytab file, specify its location on disk. If your keytab
 * contains more than one principal key, you should also specify which one to select. This
 * information can also be specified in the driver config, under the login-configuration section.
 *
 * <pre>
 * DseClient {
 *     com.sun.security.auth.module.Krb5LoginModule required
 *       useKeyTab=true
 *       keyTab="/path/to/file.keytab"
 *       principal="user@MYDOMAIN.COM";
 * };
 * </pre>
 *
 * <h2>Specifying SASL protocol name</h2>
 *
 * The SASL protocol name used by this auth provider defaults to "<code>
 * {@value #DEFAULT_SASL_SERVICE_NAME}</code>".
 *
 * <p><strong>Important</strong>: the SASL protocol name should match the username of the Kerberos
 * service principal used by the DSE server. This information is specified in the dse.yaml file by
 * the {@code service_principal} option under the <a
 * href="https://docs.datastax.com/en/dse/5.1/dse-admin/datastax_enterprise/config/configDseYaml.html#configDseYaml__refKerbSupport">kerberos_options</a>
 * section, and <em>may vary from one DSE installation to another</em> – especially if you installed
 * DSE with an automated package installer.
 *
 * <p>For example, if your dse.yaml file contains the following:
 *
 * <pre>{@code
 * kerberos_options:
 *     ...
 *     service_principal: cassandra/my.host.com@MY.REALM.COM
 * }</pre>
 *
 * The correct SASL protocol name to use when authenticating against this DSE server is "{@code
 * cassandra}".
 *
 * <p>Should you need to change the SASL protocol name, use one of the methods below:
 *
 * <ol>
 *   <li>Specify the service name in the driver config.
 *       <pre>
 * dse-java-driver {
 *   auth-provider {
 *     class = com.datastax.dse.driver.internal.core.auth.DseGssApiAuthProvider
 *     service = "alternate"
 *   }
 * }
 * </pre>
 *   <li>Specify the service name with the {@code dse.sasl.service} system property when starting
 *       your application, e.g. {@code -Ddse.sasl.service=cassandra}.
 * </ol>
 *
 * If a non-null SASL service name is provided to the aforementioned config, that name takes
 * precedence over the contents of the {@code dse.sasl.service} system property.
 *
 * <p>Should internal sasl properties need to be set such as qop. This can be accomplished by
 * including a sasl-properties in the driver config, for example:
 *
 * <pre>
 * dse-java-driver {
 *   auth-provider {
 *     class = com.datastax.dse.driver.internal.core.auth.DseGssApiAuthProvider
 *     sasl-properties {
 *       javax.security.sasl.qop = "auth-conf"
 *     }
 *   }
 * }
 * </pre>
 */
@ThreadSafe
public class DseGssApiAuthProvider extends DseGssApiAuthProviderBase {

  private final DriverExecutionProfile config;

  public DseGssApiAuthProvider(DriverContext context) {
    super(context.getSessionName());

    this.config = context.getConfig().getDefaultProfile();
  }

  @NonNull
  @Override
  protected GssApiOptions getOptions(
      @NonNull EndPoint endPoint, @NonNull String serverAuthenticator) {
    // A login configuration is always necessary, throw an exception if that option is missing.
    AuthUtils.validateConfigPresent(
        config,
        DseGssApiAuthProvider.class.getName(),
        endPoint,
        DseDriverOption.AUTH_PROVIDER_LOGIN_CONFIGURATION);

    GssApiOptions.Builder optionsBuilder = GssApiOptions.builder();

    if (config.isDefined(DseDriverOption.AUTH_PROVIDER_AUTHORIZATION_ID)) {
      optionsBuilder.withAuthorizationId(
          config.getString(DseDriverOption.AUTH_PROVIDER_AUTHORIZATION_ID));
    }
    if (config.isDefined(DseDriverOption.AUTH_PROVIDER_SERVICE)) {
      optionsBuilder.withSaslProtocol(config.getString(DseDriverOption.AUTH_PROVIDER_SERVICE));
    }
    if (config.isDefined(DseDriverOption.AUTH_PROVIDER_SASL_PROPERTIES)) {
      for (Map.Entry<String, String> entry :
          config.getStringMap(DseDriverOption.AUTH_PROVIDER_SASL_PROPERTIES).entrySet()) {
        optionsBuilder.addSaslProperty(entry.getKey(), entry.getValue());
      }
    }
    Map<String, String> loginConfigurationMap =
        config.getStringMap(DseDriverOption.AUTH_PROVIDER_LOGIN_CONFIGURATION);
    optionsBuilder.withLoginConfiguration(loginConfigurationMap);
    return optionsBuilder.build();
  }
}
