package com.airlock.iam.login.app.misc.configuration;

import com.airlock.iam.authentication.application.configuration.kerberos.KerberosLoginConfig;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.elog.api.LogTypes;
import com.airlock.iam.plugin.api.annotation.lifecycle.AfterConfigActivation;
import com.airlock.iam.plugin.api.annotation.property.BooleanProperty;
import com.airlock.iam.plugin.api.annotation.property.FileProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.exception.InitializeException;
import com.google.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.File;
import java.security.Principal;
import java.util.HashSet;
import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import org.apache.commons.lang3.StringUtils;

public abstract class KerberosConfigBase {
   private static final ELogger LOG = ELoggerFactory.getLogger(KerberosConfigBase.class);
   private static final String SUN_SECURITY_KRB5_PRINCIPAL = "sun.security.krb5.principal";
   @FileProperty(
      description = "The path of the Kerberos keytab file that should be used for Kerberos SPNEGO identity assertion.",
      optional = false,
      assureExistence = true,
      isFile = true,
      assureReadable = true,
      exampleValues = "/etc/medusa.keytab"
   )
   private File keytabFile;
   @StringProperty(
      description = "The Kerberos Service Principal Name (SPN) associated with Airlock IAM. It is usually in the form <tt>HTTP/<i>&lt;FQDN&gt;</i></tt> or <tt>*</tt> to allow all SPNs present in the keytab file.<br/>Optionally, the realm can be specified if there's no default realm in the kerberos configuration file or if the SPN is associated with a different realm/domain (see example values). ",
      optional = false,
      exampleValues = {"HTTP/login.ergon.ch", "HTTP/login.ergon.ch@REALM", "*"}
   )
   private String servicePrincipal;
   @BooleanProperty(description = "Enable/Disable Kerberos debug logs.", optional = true, defaultValue = "false")
   private boolean enableDebugging;
   private Subject serviceSubject;

   protected KerberosConfigBase(File keytabFile, String servicePrincipal, boolean enableDebugging) {
      this.keytabFile = keytabFile;
      this.servicePrincipal = servicePrincipal;
      this.enableDebugging = enableDebugging;
   }

   public Subject getServiceSubject() {
      return (Subject)Preconditions.checkNotNull(this.serviceSubject, "@AfterConfigActivation not called.");
   }

   @AfterConfigActivation
   void checkKerberosSystemProperties() {
      if (System.getProperty("sun.security.krb5.principal") != null) {
         LOG.error(
            LogTypes.CONFIGURATION_ERROR,
            "Kerberos related Java System Property \"sun.security.krb5.principal\" set to \""
               + System.getProperty("sun.security.krb5.principal")
               + "\". Will interfere with Kerberos SPNEGO authentication!"
         );
      }
   }

   @AfterConfigActivation
   void createServiceSubject() throws InitializeException {
      try {
         Configuration loginConfig = new KerberosLoginConfig(this.keytabFile.getPath(), this.servicePrincipal, this.enableDebugging);
         Set<Principal> princ = new HashSet<>(1);
         princ.add(new KerberosPrincipal(this.servicePrincipal));
         Subject sub = new Subject(false, princ, new HashSet(), new HashSet());
         LoginContext lc = new LoginContext("", sub, null, loginConfig);
         lc.login();
         if (lc.getSubject() == null) {
            LOG.error(com.airlock.iam.core.misc.tanserver.LogTypes.CONFIGURATION_ERROR, "Could not login Service Subject");
            throw new InitializeException("Could not login Service Subject");
         }

         this.serviceSubject = lc.getSubject();
         LOG.debug("Initialized Kerberos SPNEGO config for: " + StringUtils.normalizeSpace(lc.getSubject().toString()));
      } catch (RuntimeException e) {
         LOG.error(
            com.airlock.iam.core.misc.tanserver.LogTypes.CONFIGURATION_ERROR, "Invalid service principal name or invalid realm: " + this.servicePrincipal
         );
         throw new InitializeException("Invalid service principal name or invalid realm: " + this.servicePrincipal, e);
      } catch (LoginException e) {
         LOG.error(com.airlock.iam.core.misc.tanserver.LogTypes.CONFIGURATION_ERROR, "Could not login Service Subject", e);
         throw new InitializeException("Could not login the Service Subject", e);
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   public KerberosConfigBase() {
   }

   @SuppressFBWarnings(justification = "generated code")
   public File getKeytabFile() {
      return this.keytabFile;
   }

   @SuppressFBWarnings(justification = "generated code")
   public String getServicePrincipal() {
      return this.servicePrincipal;
   }
}
