package com.airlock.iam.saml2.application.configuration;

import com.airlock.iam.base.misc.collection.Pair;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.core.misc.authen.Authentee;
import com.airlock.iam.core.misc.impl.sso.AssertionAttributeSpec;
import com.airlock.iam.core.misc.impl.sso.IdentityPropagatorUtils;
import com.airlock.iam.core.misc.plugin.config.validation.KeystoreValidator;
import com.airlock.iam.core.misc.plugin.config.validation.KeystoreValidatorHelper;
import com.airlock.iam.core.misc.plugin.config.validation.OpenedKeystoreValidatorHelper;
import com.airlock.iam.core.misc.sso.IdentityPropagatorException;
import com.airlock.iam.core.misc.tanserver.LogTypes;
import com.airlock.iam.core.misc.util.ticket.KeyMultiValue;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.log.audit.AuditLogger;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
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.GroupProperty;
import com.airlock.iam.plugin.api.annotation.property.IntegerProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginListProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.plugin.api.annotation.property.StringListProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.Environment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.api.exception.InitializeException;
import com.airlock.iam.saml2.infrastructure.plugin.SamlSignatureProvider;
import com.airlock.iam.security.util.crypto.KeyStoreUtils.KeyStoreType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.sun.identity.saml2.assertion.Assertion;
import com.sun.identity.saml2.assertion.AssertionFactory;
import com.sun.identity.saml2.assertion.Attribute;
import com.sun.identity.saml2.assertion.AttributeStatement;
import com.sun.identity.saml2.assertion.AudienceRestriction;
import com.sun.identity.saml2.assertion.AuthnContext;
import com.sun.identity.saml2.assertion.AuthnStatement;
import com.sun.identity.saml2.assertion.Conditions;
import com.sun.identity.saml2.assertion.Issuer;
import com.sun.identity.saml2.assertion.NameID;
import com.sun.identity.saml2.assertion.Subject;
import com.sun.identity.saml2.assertion.SubjectConfirmation;
import com.sun.identity.saml2.common.SAML2Exception;
import com.sun.identity.saml2.common.SAML2SDKUtils;
import com.sun.identity.saml2.xmlsig.SigManager;
import com.sun.identity.saml2.xmlsig.SigProvider;
import com.sun.identity.shared.xml.XMLUtils;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.text.StringSubstitutor;
import org.w3c.dom.Element;

public class SamlAssertionBase {
   static final String SHA1_AUTOMATIC_RSA_DSA = "SHA1 (automatic RSA/DSA)";
   @StringProperty(description = "The Issuer set in the SAML2 Assertion.", optional = false, exampleValues = {"AirlockIAM", "AirlockIdp"})
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String issuer;
   @StringProperty(
      description = "The template used to insert the user's ID in the Subject element of the Assertion. The string \"${userId}\" gets replaced by the user's name.",
      optional = true,
      exampleValues = {"${userId}", "User_${userId}"},
      defaultValue = "${userId}"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String subjectTemplate;
   @StringProperty(
      description = "The subject confirmation method to use in the assertion.",
      optional = true,
      suggestedValues = {
            "urn:oasis:names:tc:SAML:2.0:cm:sender-vouches", "urn:oasis:names:tc:SAML:2.0:cm:bearer", "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key"
      },
      defaultValue = "urn:oasis:names:tc:SAML:2.0:cm:sender-vouches"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String subjectConfirmationMethod;
   @StringProperty(
      description = "The NameID Format to use in the assertion.",
      optional = true,
      suggestedValues = {
            "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
            "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
            "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
      },
      defaultValue = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String nameIdFormat;
   @PluginProperty(
      description = "The SPProvidedId is an attribute of the NameID tag and refers to the actual name that was provided at authentication. If set, this attribute will be added to the assertion. The name of the attribute does not matter. Use @param:PROVIDED_USERNAME as \"value\" to refer to the username as provided by the user.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private AssertionAttributeSpec spProvidedId;
   @PluginListProperty(
      description = "Defines a list of attributes to be added to the issued assertion. The value of the attribute can either be statically configured or taken from the user.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private List<AssertionAttributeSpec> attributes;
   @IntegerProperty(
      description = "The Assertion Validity time in milliseconds. This sets the <i>NotOnOrAfter</i> attribute to the specified instant in the future.",
      optional = true,
      exampleValues = {60000, 10000},
      minValue = 1,
      defaultValue = 30000
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private int assertionValidityMillis;
   @IntegerProperty(
      description = "The maximum number of milliseconds the clocks on the involved parties are allowed to be different. This sets the <i>NotBefore</i> attribute in the SAML assertion to the current time minus the specified number of milliseconds.",
      optional = true,
      exampleValues = {5000, 60000},
      minValue = 0,
      defaultValue = 5000
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private int assertionNotBeforeSkewMillis;
   @StringProperty(
      description = "XML signature algorithm. Used for SAML XML signature generation.<br/>The (deprecated) value \"SHA1 (automatic RSA/DSA)\" automatically chooses \"http://www.w3.org/2000/09/xmldsig#rsa-sha1\" or \"http://www.w3.org/2000/09/xmldsig#dsa-sha1\" depending on the type of the key found in the keystore. However please use a more secure hash instead, as SHA-1 is not considered to be secure.",
      optional = true,
      defaultValue = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
      allowedValues = {
            "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
            "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
            "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
            "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
            "http://www.w3.org/2000/09/xmldsig#dsa-sha1",
            "http://www.w3.org/2000/09/xmldsig#hmac-sha1",
            "http://www.w3.org/2001/04/xmldsig-more#rsa-ripemd160",
            "http://www.w3.org/2001/04/xmldsig-more#rsa-md5",
            "http://www.w3.org/2001/04/xmldsig-more#hmac-md5",
            "http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160",
            "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
            "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384",
            "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512",
            "SHA1 (automatic RSA/DSA)"
      }
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private String xmlSignatureAlgorithm;
   public static final String XML_SIGNATURE_ALGORITHM_FIELD = "xmlSignatureAlgorithm";
   @StringProperty(
      description = "XML signature digest method. Used for SAML XML signature generation and verification.",
      optional = true,
      allowedValues = {
            "http://www.w3.org/2001/04/xmlenc#sha512",
            "http://www.w3.org/2001/04/xmlenc#sha256",
            "http://www.w3.org/2000/09/xmldsig#sha1",
            "http://www.w3.org/2001/04/xmlenc#ripemd160"
      },
      defaultValue = "http://www.w3.org/2001/04/xmlenc#sha256"
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private String xmlSignatureDigestMethod;
   public static final String XML_SIGNATURE_DIGEST_METHOD = "xmlSignatureDigestMethod";
   @FileProperty(
      description = "JKS Keystore file name containing the certificate and key used to sign the SAML2 Assertion.",
      optional = false,
      exampleValues = "instances/test/saml/samlKeystore.jks",
      assureExistence = true,
      assureReadable = true,
      isFile = true
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private File keystoreFile;
   private static final String KEYSTORE_FILE_FIELD = "keystoreFile";
   @StringProperty(description = "The password used open the keystore.", optional = false, isSensitive = true)
   @GroupProperty(groupIdentifier = "Security Settings")
   private String keystorePassword;
   private static final String KEYSTORE_PASSWORD_FIELD = "keystorePassword";
   @StringProperty(description = "The alias of the key used to sign the Assertion.", optional = false, exampleValues = "medusaCert")
   @GroupProperty(groupIdentifier = "Security Settings")
   private String signingKeyAlias;
   private static final String SIGNING_KEY_ALIAS_FIELD = "signingKeyAlias";
   @StringProperty(
      description = "The password used to retrieve the key from the keystore. This password can be the same as the keystore password.",
      optional = false,
      isSensitive = true
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private String signingKeyPassword;
   private static final String SIGNING_KEY_PASSWORD_FIELD = "signingKeyPassword";
   @StringListProperty(
      description = "<p>If set, adds the given audiences to an AudienceRestriction element. This is usually not required. Each element of this list is included in the AudienceRestriction as a separate Audience.</p> e.g. If this list contains the Strings: <code>{\"https://1.airlock.com\",\"https://2.airlock.com\"}</code> the resulting condition contains: <code><br>&lt;saml:Conditions ...&gt;<br>    &lt;saml:AudienceRestriction&gt;<br>        &lt;saml:Audience&gt;https://1.airlock.com&lt;/saml:Audience&gt;<br>        &lt;saml:Audience&gt;https://2.airlock.com&lt;/saml:Audience&gt;<br>    &lt;/saml:AudienceRestriction&gt;<br>&lt;/saml:Conditions&gt;</code>",
      optional = true,
      exampleElements = "https://sp.airlock.com"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private List<String> audienceRestrictions;
   @BooleanProperty(
      description = "If enabled, a SubjectConfirmation element is added to the Assertion. This should only be disabled if the receiver doesn't support this element.",
      defaultValue = "true",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private boolean enableSubjectConfirmation;
   @BooleanProperty(
      description = "If enabled, an AuthnStatement element is added to the Assertion. This should only be disabled if the receiver doesn't support this element.",
      defaultValue = "true",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private boolean enableAuthnStatement;
   public static final String USERNAME_REF = "@username";
   public static final String ROLES_REF = "@roles";
   public static final String ADDITIONAL_INFO_PREFIX = "@info:";
   public static final String PARAMETER_PREFIX = "@param:";
   private static final ELogger LOG = ELoggerFactory.getLogger(SamlAssertionBase.class);
   private static final String TEMPLATE_USERID_TOKEN = "userId";

   @ConfigurationValidator(localOnly = true)
   public static void checkKeystoreDependencies(
      PluginConfiguration conf, ConfigurationEnvironment confEnv, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      if (validationEnvironment == Environment.CURRENT_VM) {
         KeystoreValidatorHelper validator = KeystoreValidator.getKeystoreValidator(
            "keystoreFile", "keystorePassword", KeyStoreType.JKS, conf, confEnv, context
         );
         OpenedKeystoreValidatorHelper extendedValiator = validator.validateKeystore();
         extendedValiator.matchingPrivateKeyWithSpecificPasswordOrElseThrow("signingKeyAlias", "signingKeyPassword", Optional.empty());
      }
   }

   protected Assertion createSamlAssertion(Authentee authentee, List<KeyMultiValue> additionalInformationToPropagate, Map<String, Object> parameters) throws IdentityPropagatorException {
      String username = IdentityPropagatorUtils.getUsernameFromMap(authentee, parameters);
      List<KeyMultiValue> attributeKmvs = new ArrayList<>(this.getAttributes().size());

      for (AssertionAttributeSpec att : this.getAttributes()) {
         String[] value = att.getAttributeValue(authentee, username, additionalInformationToPropagate, parameters);
         if (StringUtils.isNotEmpty(att.getName()) && ArrayUtils.isNotEmpty(value)) {
            attributeKmvs.add(new KeyMultiValue(att.getName(), value));
         }
      }

      String providedId = null;
      if (this.spProvidedId != null) {
         String[] providedIds = this.spProvidedId.getAttributeValue(authentee, username, additionalInformationToPropagate, parameters);
         if (providedIds != null && providedIds.length == 1) {
            providedId = providedIds[0];
         }
      }

      Assertion assertion = this.createAssertion(username, providedId, attributeKmvs);
      if (LOG.isDebugEnabled()) {
         try {
            String assertionString = assertion.toXMLString(true, true);
            LOG.debug("propagateIdentity() - SAMLv2 assertion: " + assertionString);
         } catch (SAML2Exception e) {
            LOG.info("WARNING: Assertion could not be written to XML string: " + e.getMessage());
         }
      }

      StringBuilder auditMessage = new StringBuilder(100);
      auditMessage.append("SAMLv2 assertion created for user ")
         .append(LogUtil.quote(authentee.getName()))
         .append(" using SAML2 Subject ")
         .append(LogUtil.quote(this.getSubject(username)))
         .append("; attributes={");

      for (KeyMultiValue keyMultiValue : attributeKmvs) {
         auditMessage.append(keyMultiValue.getKey()).append("=[").append(Joiner.on(",").join(keyMultiValue.getValues())).append("],");
      }

      auditMessage.append('}');
      AuditLogger.log(this.getClass(), LogTypes.SERVER_INFO_SYS, auditMessage.toString());
      return assertion;
   }

   protected Assertion createAssertion(String username, String providedId, List<KeyMultiValue> attributeKmvs) throws IdentityPropagatorException {
      Pair<X509Certificate, PrivateKey> signingInfo;
      try {
         signingInfo = this.getSigningCertAndKey();
      } catch (InitializeException e) {
         throw new IdentityPropagatorException(e.getMessage(), e);
      }

      Date now = new Date();

      try {
         Assertion assertion = AssertionFactory.getInstance().createAssertion();
         assertion.setIssueInstant(now);
         assertion.setID(SAML2SDKUtils.generateID());
         assertion.setVersion("2.0");
         NameID subjectNameId = AssertionFactory.getInstance().createNameID();
         subjectNameId.setFormat(this.nameIdFormat);
         String subjectName = this.getSubject(username);
         subjectNameId.setValue(subjectName);
         if (providedId != null) {
            subjectNameId.setSPProvidedID(providedId);
         }

         Subject subject = AssertionFactory.getInstance().createSubject();
         subject.setNameID(subjectNameId);
         assertion.setSubject(subject);
         if (this.enableSubjectConfirmation) {
            SubjectConfirmation subjectConfirmation = AssertionFactory.getInstance().createSubjectConfirmation();
            subjectConfirmation.setMethod(this.subjectConfirmationMethod);
            subject.setSubjectConfirmation(Arrays.asList(subjectConfirmation));
         }

         Issuer assertionIssuer = AssertionFactory.getInstance().createIssuer();
         assertionIssuer.setValue(this.issuer);
         assertion.setIssuer(assertionIssuer);
         Conditions conditions = AssertionFactory.getInstance().createConditions();
         conditions.setNotBefore(DateUtils.addMilliseconds(now, -this.assertionNotBeforeSkewMillis));
         conditions.setNotOnOrAfter(DateUtils.addMilliseconds(now, this.assertionValidityMillis));
         if (!this.audienceRestrictions.isEmpty()) {
            AudienceRestriction audienceRestriction = AssertionFactory.getInstance().createAudienceRestriction();
            audienceRestriction.setAudience(this.audienceRestrictions);
            conditions.setAudienceRestrictions(Lists.newArrayList(new AudienceRestriction[]{audienceRestriction}));
         }

         assertion.setConditions(conditions);
         if (this.enableAuthnStatement) {
            AuthnStatement authStmt = AssertionFactory.getInstance().createAuthnStatement();
            AuthnContext authCtx = AssertionFactory.getInstance().createAuthnContext();
            authCtx.setAuthenticatingAuthority(Collections.singletonList(this.issuer));
            authCtx.setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");
            authStmt.setAuthnContext(authCtx);
            authStmt.setAuthnInstant(now);
            assertion.setAuthnStatements(Collections.singletonList(authStmt));
         }

         Optional<AttributeStatement> attributeStatement = this.createAttributeStatement(attributeKmvs);
         if (attributeStatement.isPresent()) {
            assertion.setAttributeStatements(Collections.singletonList(attributeStatement.get()));
         }

         return this.getSignedAssertion(assertion, signingInfo.getSecond(), signingInfo.getFirst());
      } catch (SAML2Exception e) {
         throw new IdentityPropagatorException("Could not create assertion: " + e.getMessage(), e);
      } catch (RuntimeException e) {
         LOG.warn(com.airlock.iam.plugin.api.config.LogTypes.PLUGIN_INFO, "Could not create assertion: " + e.getMessage(), e);
         throw e;
      }
   }

   protected Optional<AttributeStatement> createAttributeStatement(List<KeyMultiValue> attributeKmvs) throws SAML2Exception {
      if (attributeKmvs.isEmpty()) {
         return Optional.empty();
      }

      List<Attribute> attributeElements = new ArrayList<>(attributeKmvs.size());

      for (KeyMultiValue attribute : attributeKmvs) {
         Attribute attr = AssertionFactory.getInstance().createAttribute();
         attr.setName(attribute.getKey());
         attr.setAttributeValueString(Arrays.asList(attribute.getValues()));
         attributeElements.add(attr);
      }

      AttributeStatement attrStmt = AssertionFactory.getInstance().createAttributeStatement();
      attrStmt.setAttribute(attributeElements);
      return Optional.of(attrStmt);
   }

   private Assertion getSignedAssertion(Assertion assertion, PrivateKey privateKey, X509Certificate cert) throws SAML2Exception {
      String signatureAlgorithm;
      if ("SHA1 (automatic RSA/DSA)".equals(this.xmlSignatureAlgorithm)) {
         signatureAlgorithm = null;
      } else {
         signatureAlgorithm = this.xmlSignatureAlgorithm;
      }

      SigProvider sigProvider = SigManager.getSigInstance();
      Element signatureElement;
      if (sigProvider instanceof SamlSignatureProvider) {
         signatureElement = ((SamlSignatureProvider)sigProvider)
            .sign(assertion.toXMLString(true, true), assertion.getID(), privateKey, cert, signatureAlgorithm, this.xmlSignatureDigestMethod);
      } else {
         LOG.warn(
            LogTypes.CONFIGURATION_ERROR,
            "Signature Provider is not of expected type "
               + SamlSignatureProvider.class.getName()
               + " but of type "
               + sigProvider.getClass().getName()
               + ". Cannot honor signature algorithm settings."
         );
         signatureElement = sigProvider.sign(assertion.toXMLString(true, true), assertion.getID(), privateKey, cert);
      }

      String signedAssertionString = XMLUtils.print(signatureElement.getOwnerDocument().getDocumentElement(), "UTF-8");
      return AssertionFactory.getInstance().createAssertion(signedAssertionString);
   }

   private Pair<X509Certificate, PrivateKey> getSigningCertAndKey() throws InitializeException {
      try {
         KeyStore keystore = KeyStore.getInstance("JKS");

         try (InputStream is = Files.newInputStream(this.keystoreFile.toPath())) {
            keystore.load(is, this.keystorePassword.toCharArray());
         }

         PrivateKey signingKey = (PrivateKey)keystore.getKey(this.signingKeyAlias, this.signingKeyPassword.toCharArray());
         if (signingKey == null) {
            throw new InitializeException(
               "No signing key found in keystore using alias \"" + this.signingKeyAlias + "\" and key store \"" + this.keystoreFile + "\"."
            );
         } else {
            X509Certificate certificate = (X509Certificate)keystore.getCertificate(this.signingKeyAlias);
            if (certificate == null) {
               throw new InitializeException(
                  "No certificate found in keystore using alias \"" + this.signingKeyAlias + "\" and key store \"" + this.keystoreFile + "\"."
               );
            } else {
               return Pair.pair(certificate, signingKey);
            }
         }
      } catch (FileNotFoundException e) {
         throw new InitializeException("Keystore not found: " + e.getMessage(), e);
      } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
         throw new InitializeException("Could not load keystore: " + e.getMessage(), e);
      } catch (UnrecoverableKeyException e) {
         throw new InitializeException("Could not load key \"" + this.signingKeyAlias + "\" from keystore: " + e.getMessage(), e);
      }
   }

   private String getSubject(String uid) {
      Map<String, String> subst = new HashMap<>(1);
      subst.put("userId", uid);
      return new StringSubstitutor(subst).replace(this.subjectTemplate);
   }

   @VisibleForTesting
   void setAttributes(List<AssertionAttributeSpec> attributes) {
      this.attributes = attributes;
   }

   protected List<AssertionAttributeSpec> getAttributes() {
      return this.attributes;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static SamlAssertionBase.SamlAssertionBaseBuilder assertionBaseBuilder() {
      return new SamlAssertionBase.SamlAssertionBaseBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public SamlAssertionBase(
      String issuer,
      String subjectTemplate,
      String subjectConfirmationMethod,
      String nameIdFormat,
      AssertionAttributeSpec spProvidedId,
      List<AssertionAttributeSpec> attributes,
      int assertionValidityMillis,
      int assertionNotBeforeSkewMillis,
      String xmlSignatureAlgorithm,
      String xmlSignatureDigestMethod,
      File keystoreFile,
      String keystorePassword,
      String signingKeyAlias,
      String signingKeyPassword,
      List<String> audienceRestrictions,
      boolean enableSubjectConfirmation,
      boolean enableAuthnStatement
   ) {
      this.issuer = issuer;
      this.subjectTemplate = subjectTemplate;
      this.subjectConfirmationMethod = subjectConfirmationMethod;
      this.nameIdFormat = nameIdFormat;
      this.spProvidedId = spProvidedId;
      this.attributes = attributes;
      this.assertionValidityMillis = assertionValidityMillis;
      this.assertionNotBeforeSkewMillis = assertionNotBeforeSkewMillis;
      this.xmlSignatureAlgorithm = xmlSignatureAlgorithm;
      this.xmlSignatureDigestMethod = xmlSignatureDigestMethod;
      this.keystoreFile = keystoreFile;
      this.keystorePassword = keystorePassword;
      this.signingKeyAlias = signingKeyAlias;
      this.signingKeyPassword = signingKeyPassword;
      this.audienceRestrictions = audienceRestrictions;
      this.enableSubjectConfirmation = enableSubjectConfirmation;
      this.enableAuthnStatement = enableAuthnStatement;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class SamlAssertionBaseBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private String issuer;
      @SuppressFBWarnings(justification = "generated code")
      private String subjectTemplate;
      @SuppressFBWarnings(justification = "generated code")
      private String subjectConfirmationMethod;
      @SuppressFBWarnings(justification = "generated code")
      private String nameIdFormat;
      @SuppressFBWarnings(justification = "generated code")
      private AssertionAttributeSpec spProvidedId;
      @SuppressFBWarnings(justification = "generated code")
      private List<AssertionAttributeSpec> attributes;
      @SuppressFBWarnings(justification = "generated code")
      private int assertionValidityMillis;
      @SuppressFBWarnings(justification = "generated code")
      private int assertionNotBeforeSkewMillis;
      @SuppressFBWarnings(justification = "generated code")
      private String xmlSignatureAlgorithm;
      @SuppressFBWarnings(justification = "generated code")
      private String xmlSignatureDigestMethod;
      @SuppressFBWarnings(justification = "generated code")
      private File keystoreFile;
      @SuppressFBWarnings(justification = "generated code")
      private String keystorePassword;
      @SuppressFBWarnings(justification = "generated code")
      private String signingKeyAlias;
      @SuppressFBWarnings(justification = "generated code")
      private String signingKeyPassword;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> audienceRestrictions;
      @SuppressFBWarnings(justification = "generated code")
      private boolean enableSubjectConfirmation;
      @SuppressFBWarnings(justification = "generated code")
      private boolean enableAuthnStatement;

      @SuppressFBWarnings(justification = "generated code")
      SamlAssertionBaseBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder issuer(String issuer) {
         this.issuer = issuer;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder subjectTemplate(String subjectTemplate) {
         this.subjectTemplate = subjectTemplate;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder subjectConfirmationMethod(String subjectConfirmationMethod) {
         this.subjectConfirmationMethod = subjectConfirmationMethod;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder nameIdFormat(String nameIdFormat) {
         this.nameIdFormat = nameIdFormat;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder spProvidedId(AssertionAttributeSpec spProvidedId) {
         this.spProvidedId = spProvidedId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder attributes(List<AssertionAttributeSpec> attributes) {
         this.attributes = attributes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder assertionValidityMillis(int assertionValidityMillis) {
         this.assertionValidityMillis = assertionValidityMillis;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder assertionNotBeforeSkewMillis(int assertionNotBeforeSkewMillis) {
         this.assertionNotBeforeSkewMillis = assertionNotBeforeSkewMillis;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder xmlSignatureAlgorithm(String xmlSignatureAlgorithm) {
         this.xmlSignatureAlgorithm = xmlSignatureAlgorithm;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder xmlSignatureDigestMethod(String xmlSignatureDigestMethod) {
         this.xmlSignatureDigestMethod = xmlSignatureDigestMethod;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder keystoreFile(File keystoreFile) {
         this.keystoreFile = keystoreFile;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder keystorePassword(String keystorePassword) {
         this.keystorePassword = keystorePassword;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder signingKeyAlias(String signingKeyAlias) {
         this.signingKeyAlias = signingKeyAlias;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder signingKeyPassword(String signingKeyPassword) {
         this.signingKeyPassword = signingKeyPassword;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder audienceRestrictions(List<String> audienceRestrictions) {
         this.audienceRestrictions = audienceRestrictions;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder enableSubjectConfirmation(boolean enableSubjectConfirmation) {
         this.enableSubjectConfirmation = enableSubjectConfirmation;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase.SamlAssertionBaseBuilder enableAuthnStatement(boolean enableAuthnStatement) {
         this.enableAuthnStatement = enableAuthnStatement;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SamlAssertionBase build() {
         return new SamlAssertionBase(
            this.issuer,
            this.subjectTemplate,
            this.subjectConfirmationMethod,
            this.nameIdFormat,
            this.spProvidedId,
            this.attributes,
            this.assertionValidityMillis,
            this.assertionNotBeforeSkewMillis,
            this.xmlSignatureAlgorithm,
            this.xmlSignatureDigestMethod,
            this.keystoreFile,
            this.keystorePassword,
            this.signingKeyAlias,
            this.signingKeyPassword,
            this.audienceRestrictions,
            this.enableSubjectConfirmation,
            this.enableAuthnStatement
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "SamlAssertionBase.SamlAssertionBaseBuilder(issuer="
            + this.issuer
            + ", subjectTemplate="
            + this.subjectTemplate
            + ", subjectConfirmationMethod="
            + this.subjectConfirmationMethod
            + ", nameIdFormat="
            + this.nameIdFormat
            + ", spProvidedId="
            + this.spProvidedId
            + ", attributes="
            + this.attributes
            + ", assertionValidityMillis="
            + this.assertionValidityMillis
            + ", assertionNotBeforeSkewMillis="
            + this.assertionNotBeforeSkewMillis
            + ", xmlSignatureAlgorithm="
            + this.xmlSignatureAlgorithm
            + ", xmlSignatureDigestMethod="
            + this.xmlSignatureDigestMethod
            + ", keystoreFile="
            + this.keystoreFile
            + ", keystorePassword="
            + this.keystorePassword
            + ", signingKeyAlias="
            + this.signingKeyAlias
            + ", signingKeyPassword="
            + this.signingKeyPassword
            + ", audienceRestrictions="
            + this.audienceRestrictions
            + ", enableSubjectConfirmation="
            + this.enableSubjectConfirmation
            + ", enableAuthnStatement="
            + this.enableAuthnStatement
            + ")";
      }
   }
}
