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

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.login.application.configuration.targetapp.StaticWafCredentialProviderConfig;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
import com.airlock.iam.plugin.api.annotation.config.Groups;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.license.PluginLicenseRestriction;
import com.airlock.iam.plugin.api.annotation.property.BooleanProperty;
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.RegexProperty;
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.PropertyConfigurationException;
import com.airlock.iam.plugin.api.exception.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.valuereader.IntegerPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginListPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.StringListPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import com.airlock.iam.plugin.framework.misc.exception.MandatoryPropertyMissingException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;

@Plugin(
   description = "<p>Enables and configures the SAML 2.0 Identity Provider (IdP) for use in the flow-based authentication.</p><p>The IdP provides assertions to receiving applications called \"SAML Service Providers\". </p>",
   displayName = "SAML 2.0 Flow IdP"
)
@Groups(groupSequence = {"Basic Settings", "Custom SPA URIs", "Single-Logout Settings", "Load-Balancing"}, openGroups = "Basic Settings")
@PluginLicenseRestriction(moduleAffiliation = "SamlIdp")
public final class Saml2FlowIdpConfig {
   static final String LOAD_BALANCING_SETTINGS = "Load-Balancing";
   static final String CUSTOM_URI_SETTINGS = "Custom SPA URIs";
   static final String SINGLE_LOGOUT_SETTINGS = "Single-Logout Settings";
   @PluginProperty(
      description = "Configures the identity provider (IdP) entity.",
      optional = true,
      defaultValue = Saml2IDPEntityConfig.class,
      displayName = "IdP Entity Settings"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private Saml2IDPEntityConfig idpEntitySettings;
   @PluginListProperty(description = "The list of supported Service Providers.", optional = false, minElements = 1)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private List<Saml2ServiceProviderAccessConfig> serviceProviders;
   public static final String SERVICE_PROVIDERS_PROPERTY = "serviceProviders";
   @PluginListProperty(
      description = "<p>Assertions contain an 'Authentication Context Class Reference' referencing the type or strength of the performed authentication of the user. This can be used by a Service Provider (SP) in order to assess the level of confidence it can put in the assertion.</p><p>If configured, the first fulfilled condition defines the final 'Authentication Context Class Reference'. If left empty (or no condition is fulfilled), the logic of the \"Default Authentication Context\" takes place. (See property description)</p>",
      optional = true,
      displayName = "Authentication Context Mappings"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private List<Saml2FlowConditionToAuthnContextMapping> authnContextMappings;
   @StringProperty(
      displayName = "Default Authentication Context",
      description = "<p>Assertions contain an 'Authentication Context Class Reference' referencing the type or strength of the performed authentication of the user. This can be used by a Service Provider (SP) in order to assess the level of confidence it can put in the assertion.</p><p>This property defines the default 'Authentication Context Class Reference' used if no URI can be determined based on the 'Authentication Context Mappings'.</p><p>If left empty, the SAML default resolution is used which also honors the requested URI of an SP in the SP-initiated SSO scenario. For IdP-initiated SSO or if no requested 'Authentication Context Class Reference' can be met in SP-initiated SSO, the first URI defined in the IdP extended metadata file is used (see the \"Extended Metadata File\" property in the IdP entity settings).</p><p>The available context classes are configured in the IdP extended metadata attribute \"idpAuthncontextClassrefMapping\". Only URIs defined by the attribute are valid to be set here.</p>",
      optional = true,
      suggestedValues = {
            "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocolPassword",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:MobileOneFactorUnregistered",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorUnregistered",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:MobileOneFactorContract",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorContract",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:Password",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:X509",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:PGP",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:SPKI",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:XMLDSig",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:Smartcard",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:SoftwarePKI",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:Telephony",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:NomadTelephony",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:PersonalTelephony",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:AuthenticatedTelephony",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:SecureRemotePassword",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:TLSClient",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken",
            "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified"
      }
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String defaultAuthnContextUri;
   private static final String DEFAULT_AUTHN_CONTEXT_DISPLAY_NAME = "Default Authentication Context";
   @StringProperty(
      description = "<p>The URI to start authentication in case of SP-Initiated SSO.</p><p><b>Must only be configured when using a custom SPA.</b></p><p>Relative URIs not starting with a slash are resolved against the current context path.</p><p>When customized and behind an Airlock Gateway (WAF), a \"URL Encryption Exception\" must be configured.</p>",
      optional = true,
      displayName = "Authentication URI",
      exampleValues = {"https://example.com/custom-ui/saml2/init", "/custom-ui/saml2/init"}
   )
   @GroupProperty(groupIdentifier = "Custom SPA URIs")
   private String customAuthenticationUri;
   @StringProperty(
      description = "<p>The URI to start the logout process in the UI.</p><p>This is mainly used in SP-initiated Single-Logout (the logout is started by a Service Provider). In this case, the browser must first be redirected to the UI in order to start the regular logout before being able to finish the SAML 2.0 Single-Logout (SLO).<br>It is also used in certain error cases where a logout must be performed.</p><p><b>Must only be configured when using a custom SPA.</b></p><p>Relative URIs not starting with a slash are resolved against the current context path.</p><p>When customized and behind an Airlock Gateway (WAF), a \"URL Encryption Exception\" must be configured.</p>",
      optional = true,
      displayName = "Logout URI",
      exampleValues = {"https://example.com/custom-ui/logout", "/custom-ui/logout"}
   )
   @GroupProperty(groupIdentifier = "Custom SPA URIs")
   private String customLogoutUri;
   @RegexProperty(
      description = "<p>During IdP-Initiated Single-Logout (SLO), the SPA has to send the location where to resume the logout process after SAML 2.0 Single-Logout has been finished using a \"Location\" URL parameter. <p>If not configured, the standard URL for logout resume in the Loginapp UI (ui/app/auth/logout/resume) is used.</p>That absolute location will be validated against this pattern.</p><p><b>Must only be configured when using a custom SPA.</b></p><p>If behind an Airlock Gateway (WAF), a \"URL Encryption Exception\" must also be configured for this URL.</p>",
      optional = true,
      displayName = "Logout Resume URI Pattern",
      exampleValues = {"^https://example\\.com/custom-ui/logout/resume$", "example.com/.*"}
   )
   @GroupProperty(groupIdentifier = "Custom SPA URIs")
   private Pattern customLogoutResumeUriPattern;
   @PluginProperty(
      description = "<p>The Airlock Gateway (WAF) role granted to a user on IdP logout before performing the SP logout requests.</p> <p>Allows requests to temporarily access protected logout endpoints during single-logout if the SP is protected by the same Airlock Gateway (WAF). If this is left empty, the current gateway credentials are still deleted but no new ones are set. This does not pose a security risk, but the logout might not work properly on the SP.</p><b>Security notes:</b><ul><li>The temporary credential should only be used to protect the logout URLs of the SPs in their corresponding mapping configuration in Airlock Gateway (WAF).</li><li>It is recommended to set an Idle Timeout and a Lifetime as low as possible. These values can be defined inside the plugin configured in this property. In order to guarantee that the SLO flow terminates even with slow connections (e.g. mobile connections), a value of 60 seconds is recommended for both properties.</li></ul>",
      optional = true,
      displayName = "Temporary Single-Logout Gateway Credential"
   )
   @GroupProperty(groupIdentifier = "Single-Logout Settings")
   private StaticWafCredentialProviderConfig temporarySloCredentialProvider;
   @BooleanProperty(
      description = "If enabled, the Airlock Gateway (WAF) session will be cleared at the beginning of a Single-Logout. The Cookies and\nCredentials stored at the Airlock Gateway (WAF) for backend services will be deleted already at the beginning of a\nSingle-Logout, before the logout has been propagated to the backend services.\nThis means that:\n<ul>\n<li>SPs which are behind the same Airlock Gateway (WAF) as Airlock IAM to which the logout is propagated, will not\nrecognize the session because the session-cookie has already been deleted at that point.</li>\n<li>Logout propagation configured on the Airlock Gateway (WAF) does not work, because the session cookie of the\ncorresponding backend will have been deleted at this point.</li>\n</ul>\nIf disabled, no cookies on the Airlock Gateway (WAF) will be deleted. Credentials will still be cleared or overriden\nby the \"Temporary Single-Logout Gateway Credential\" in case it is set. A temporary credential is usually\nnecessary to properly log out an SP which is behind the same Airlock Gateway (WAF). Consider the security aspects mentioned\nin the documentation of the \"Temporary Single-Logout Gateway Credential\".\n<p>Regardless of this setting, the Airlock Gateway (WAF) session will be terminated at the end of a single-logout,\nwhich means that all cookies and credentials will be deleted.</p>",
      optional = true,
      displayName = "Clear Gateway Session on Single-Logout",
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = "Single-Logout Settings")
   private boolean clearGatewaySessionOnSlo;
   @StringProperty(
      description = "If load balancing is used, specify the IdP's protocol. Make sure it matches the URLs in the IdP metadata files.<br/>(see the \"Metadata File\" and \"Extended Metadata File\" properties in the IdP entity settings. Together with the other values, this <b>must</b> match one of the entries in 'Server List'.",
      optional = true,
      allowedValues = {"https", "http"},
      defaultValue = "https"
   )
   @GroupProperty(groupIdentifier = "Load-Balancing")
   private String protocol;
   private static final String PROTOCOL_PROPERTY = "protocol";
   @StringProperty(
      description = "If load balancing is used, specify the IdP's host name. Make sure it matches the URLs in the IdP metadata files.<br/>(see the \"Metadata File\" and \"Extended Metadata File\" properties in the IdP entity settings. Together with the other values, this <b>must</b> match one of the entries in 'Server List'.",
      optional = true,
      exampleValues = {"localhost", "idp"}
   )
   @GroupProperty(groupIdentifier = "Load-Balancing")
   private String host;
   private static final String HOST_PROPERTY = "host";
   @IntegerProperty(
      description = "If load balancing is used, specify the IdP's port. Make sure it matches the URLs in the IdP metadata files.<br/>(see the \"Metadata File\" and \"Extended Metadata File\" properties in the IdP entity settings. Together with the other values, this <b>must</b> match one of the entries in 'Server List'.",
      optional = true,
      exampleValues = {443, 80, 8443, 8080},
      minValue = 1,
      maxValue = 65535
   )
   @GroupProperty(groupIdentifier = "Load-Balancing")
   private Integer port;
   private static final String PORT_PROPERTY = "port";
   @StringProperty(
      description = "If load balancing is used, specify the IdP's context path with leading but no trailing slash. Make sure it matches the URLs in the IdP metadata files.<br/>(see the \"Metadata File\" and \"Extended Metadata File\" properties in the IdP entity settings. Together with the other values, this <b>must</b> match one of the entries in 'Server List'.",
      optional = true,
      exampleValues = {"/auth", "/saml-login"}
   )
   @GroupProperty(groupIdentifier = "Load-Balancing")
   private String contextPath;
   private static final String CONTEXT_PATH_PROPERTY = "contextPath";
   @StringListProperty(
      description = "<p>For load balancing, specify all participating servers in the form \"&lt;protocol&gt;://&lt;hostname&gt;:&lt;port&gt;/&lt;path&gt;\".</p><p>This list must be specified on ALL participating load balanced servers for all servers <b>identically</b>.</p><p>This setting is only used if more than one server is involved.</p>",
      exampleElements = {"https://host1:443/path", "https://host2:443/path"},
      optional = true,
      minElements = 2
   )
   @GroupProperty(groupIdentifier = "Load-Balancing")
   private List<String> serverList;
   private static final String SERVER_LIST_PROPERTY = "serverList";

   @ConfigurationValidator
   public static void validateNoDuplicateServiceProvider(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws PropertyConfigurationException {
      Set<String> spEntityIds = new HashSet<>();

      for (PluginConfiguration sp : PluginListPropertyValueReader.effectivePluginListPropertyValue(config, "serviceProviders", env, context)) {
         PluginConfiguration entityConfig = PluginPropertyValueReader.effectivePluginPropertyValue(sp, "spEntityConfig", env, context);
         PluginConfiguration entityIdConfig = PluginPropertyValueReader.effectivePluginPropertyValue(entityConfig, "entityId", env, context);
         if (entityIdConfig != null) {
            String entityId = StringPropertyValueReader.effectiveStringConfigValue(entityIdConfig, "spEntityId", env, context);
            if (!spEntityIds.add(entityId)) {
               throw new PropertyConstraintViolationException(
                  "serviceProviders", config, env, context, "Duplicate Service Provider Entity ID: " + LogUtil.quote(entityId)
               );
            }
         }
      }
   }

   @ConfigurationValidator
   public static void validateLoadBalancingSettings(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws PropertyConfigurationException {
      List<String> serverList = StringListPropertyValueReader.effectiveStringListConfigValue(config, "serverList", env, context);
      if (!serverList.isEmpty()) {
         String protocol = StringPropertyValueReader.effectiveStringConfigValue(config, "protocol", env, context);
         String host = StringPropertyValueReader.effectiveStringConfigValue(config, "host", env, context);
         if (host == null) {
            throw new MandatoryPropertyMissingException("host", config, env, context);
         }

         Integer port = IntegerPropertyValueReader.effectiveIntegerConfigValue(config, "port", env, context);
         if (port == null) {
            throw new MandatoryPropertyMissingException("port", config, env, context);
         }

         String contextPath = StringPropertyValueReader.effectiveStringConfigValue(config, "contextPath", env, context);
         if (contextPath == null) {
            throw new MandatoryPropertyMissingException("contextPath", config, env, context);
         }

         StringBuilder localPath = new StringBuilder().append(protocol).append("://").append(host).append(':').append(port);
         if (StringUtils.isNotBlank(contextPath)) {
            localPath.append(contextPath);
         }

         if (!serverList.contains(localPath.toString())) {
            throw new PropertyConstraintViolationException(
               "serverList", config, env, context, "Server List must contain the full URL generated from the above fields: " + localPath
            );
         }
      }
   }

   public Optional<URI> getCustomAuthenticationUri() {
      return Optional.ofNullable(this.customAuthenticationUri).map(URI::create);
   }

   public Optional<URI> getCustomLogoutUri() {
      return Optional.ofNullable(this.customLogoutUri).map(URI::create);
   }

   public Optional<Pattern> getCustomLogoutResumeUriPattern() {
      return Optional.ofNullable(this.customLogoutResumeUriPattern);
   }

   public Optional<Saml2ServiceProviderAccessConfig> getServiceProvider(String spEntityId) {
      return this.serviceProviders.stream().filter(s -> s.getSpEntityConfig().getEntityId().equals(spEntityId)).findFirst();
   }

   public Optional<String> getDefaultAuthnContextUri() {
      return Optional.ofNullable(this.defaultAuthnContextUri);
   }

   public Optional<StaticWafCredentialProviderConfig> getTemporarySloCredentialProvider() {
      return Optional.ofNullable(this.temporarySloCredentialProvider);
   }

   public Integer getPort() {
      return this.port;
   }

   public String getHost() {
      return this.host;
   }

   public List<String> getServerList() {
      return this.serverList;
   }

   public String getProtocol() {
      return this.protocol;
   }

   public String getContextPath() {
      return this.contextPath;
   }

   public List<Saml2ServiceProviderAccessConfig> getServiceProviders() {
      return this.serviceProviders;
   }

   public Saml2IDPEntityConfig getIdpEntitySettings() {
      return this.idpEntitySettings;
   }

   public List<Saml2FlowConditionToAuthnContextMapping> getAuthnContextMappings() {
      return this.authnContextMappings;
   }

   public boolean isClearGatewaySessionOnSlo() {
      return this.clearGatewaySessionOnSlo;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder builder() {
      return new Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public Saml2FlowIdpConfig(
      Saml2IDPEntityConfig idpEntitySettings,
      List<Saml2ServiceProviderAccessConfig> serviceProviders,
      List<Saml2FlowConditionToAuthnContextMapping> authnContextMappings,
      String defaultAuthnContextUri,
      String customAuthenticationUri,
      String customLogoutUri,
      Pattern customLogoutResumeUriPattern,
      StaticWafCredentialProviderConfig temporarySloCredentialProvider,
      boolean clearGatewaySessionOnSlo,
      String protocol,
      String host,
      Integer port,
      String contextPath,
      List<String> serverList
   ) {
      this.idpEntitySettings = idpEntitySettings;
      this.serviceProviders = serviceProviders;
      this.authnContextMappings = authnContextMappings;
      this.defaultAuthnContextUri = defaultAuthnContextUri;
      this.customAuthenticationUri = customAuthenticationUri;
      this.customLogoutUri = customLogoutUri;
      this.customLogoutResumeUriPattern = customLogoutResumeUriPattern;
      this.temporarySloCredentialProvider = temporarySloCredentialProvider;
      this.clearGatewaySessionOnSlo = clearGatewaySessionOnSlo;
      this.protocol = protocol;
      this.host = host;
      this.port = port;
      this.contextPath = contextPath;
      this.serverList = serverList;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class Saml2FlowIdpConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Saml2IDPEntityConfig idpEntitySettings;
      @SuppressFBWarnings(justification = "generated code")
      private List<Saml2ServiceProviderAccessConfig> serviceProviders;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<Saml2FlowConditionToAuthnContextMapping> authnContextMappings;
      @SuppressFBWarnings(justification = "generated code")
      private String defaultAuthnContextUri;
      @SuppressFBWarnings(justification = "generated code")
      private String customAuthenticationUri;
      @SuppressFBWarnings(justification = "generated code")
      private String customLogoutUri;
      @SuppressFBWarnings(justification = "generated code")
      private Pattern customLogoutResumeUriPattern;
      @SuppressFBWarnings(justification = "generated code")
      private StaticWafCredentialProviderConfig temporarySloCredentialProvider;
      @SuppressFBWarnings(justification = "generated code")
      private boolean clearGatewaySessionOnSlo;
      @SuppressFBWarnings(justification = "generated code")
      private String protocol;
      @SuppressFBWarnings(justification = "generated code")
      private String host;
      @SuppressFBWarnings(justification = "generated code")
      private Integer port;
      @SuppressFBWarnings(justification = "generated code")
      private String contextPath;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> serverList;

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

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder idpEntitySettings(Saml2IDPEntityConfig idpEntitySettings) {
         this.idpEntitySettings = idpEntitySettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder serviceProviders(List<Saml2ServiceProviderAccessConfig> serviceProviders) {
         this.serviceProviders = serviceProviders;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder authnContextMapping(Saml2FlowConditionToAuthnContextMapping authnContextMapping) {
         if (this.authnContextMappings == null) {
            this.authnContextMappings = new ArrayList<>();
         }

         this.authnContextMappings.add(authnContextMapping);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder authnContextMappings(
         Collection<? extends Saml2FlowConditionToAuthnContextMapping> authnContextMappings
      ) {
         if (authnContextMappings == null) {
            throw new NullPointerException("authnContextMappings cannot be null");
         }

         if (this.authnContextMappings == null) {
            this.authnContextMappings = new ArrayList<>();
         }

         this.authnContextMappings.addAll(authnContextMappings);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder clearAuthnContextMappings() {
         if (this.authnContextMappings != null) {
            this.authnContextMappings.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder defaultAuthnContextUri(String defaultAuthnContextUri) {
         this.defaultAuthnContextUri = defaultAuthnContextUri;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder customAuthenticationUri(String customAuthenticationUri) {
         this.customAuthenticationUri = customAuthenticationUri;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder customLogoutUri(String customLogoutUri) {
         this.customLogoutUri = customLogoutUri;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder customLogoutResumeUriPattern(Pattern customLogoutResumeUriPattern) {
         this.customLogoutResumeUriPattern = customLogoutResumeUriPattern;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder temporarySloCredentialProvider(StaticWafCredentialProviderConfig temporarySloCredentialProvider) {
         this.temporarySloCredentialProvider = temporarySloCredentialProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder clearGatewaySessionOnSlo(boolean clearGatewaySessionOnSlo) {
         this.clearGatewaySessionOnSlo = clearGatewaySessionOnSlo;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder protocol(String protocol) {
         this.protocol = protocol;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder host(String host) {
         this.host = host;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder port(Integer port) {
         this.port = port;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder contextPath(String contextPath) {
         this.contextPath = contextPath;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder serverList(List<String> serverList) {
         this.serverList = serverList;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2FlowIdpConfig build() {
         return new Saml2FlowIdpConfig(
            this.idpEntitySettings,
            this.serviceProviders,
            switch (this.authnContextMappings == null ? 0 : this.authnContextMappings.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.authnContextMappings.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.authnContextMappings));
            },
            this.defaultAuthnContextUri,
            this.customAuthenticationUri,
            this.customLogoutUri,
            this.customLogoutResumeUriPattern,
            this.temporarySloCredentialProvider,
            this.clearGatewaySessionOnSlo,
            this.protocol,
            this.host,
            this.port,
            this.contextPath,
            this.serverList
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Saml2FlowIdpConfig.Saml2FlowIdpConfigBuilder(idpEntitySettings="
            + this.idpEntitySettings
            + ", serviceProviders="
            + this.serviceProviders
            + ", authnContextMappings="
            + this.authnContextMappings
            + ", defaultAuthnContextUri="
            + this.defaultAuthnContextUri
            + ", customAuthenticationUri="
            + this.customAuthenticationUri
            + ", customLogoutUri="
            + this.customLogoutUri
            + ", customLogoutResumeUriPattern="
            + this.customLogoutResumeUriPattern
            + ", temporarySloCredentialProvider="
            + this.temporarySloCredentialProvider
            + ", clearGatewaySessionOnSlo="
            + this.clearGatewaySessionOnSlo
            + ", protocol="
            + this.protocol
            + ", host="
            + this.host
            + ", port="
            + this.port
            + ", contextPath="
            + this.contextPath
            + ", serverList="
            + this.serverList
            + ")";
      }
   }
}
