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

import com.airlock.iam.authentication.application.configuration.targetapp.TargetApplicationsConfig;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.application.configuration.ConfigurationExtension;
import com.airlock.iam.common.api.application.configuration.state.StateRepositoryConfig;
import com.airlock.iam.common.api.domain.model.store.user.UserStore;
import com.airlock.iam.common.application.configuration.device.DeviceUsageRepositoryConfig;
import com.airlock.iam.common.application.configuration.gateway.AirlockMicrogatewayConfig;
import com.airlock.iam.common.application.configuration.gateway.GatewayLoginappConfig;
import com.airlock.iam.common.application.configuration.geolocation.GeolocationProviderConfig;
import com.airlock.iam.common.application.configuration.health.ModuleReadinessConfig;
import com.airlock.iam.common.application.configuration.language.LanguageSettings;
import com.airlock.iam.common.application.configuration.logging.CorrelationIdSettingsConfig;
import com.airlock.iam.common.application.configuration.usertrail.UserTrailLogRepositoryConfig;
import com.airlock.iam.common.infrastructure.service.JwksProvider;
import com.airlock.iam.core.application.configuration.store.user.UserStoreProvider;
import com.airlock.iam.core.misc.cronto.CrontoHandler;
import com.airlock.iam.core.misc.util.context.ContextExtractor;
import com.airlock.iam.core.misc.util.context.NoContextExtractor;
import com.airlock.iam.flow.shared.application.configuration.context.policy.ContextRetentionPolicy;
import com.airlock.iam.flow.shared.application.configuration.context.policy.RequestContextRetentionPolicy;
import com.airlock.iam.flow.shared.application.configuration.context.policy.SessionContextRetentionPolicy;
import com.airlock.iam.flow.ui.application.configuration.ShowLogoutDisclaimerPageConfig;
import com.airlock.iam.login.api.application.configuration.event.LoginappDomainEventSubscriberConfig;
import com.airlock.iam.login.app.application.configuration.SecurityConfig;
import com.airlock.iam.login.app.application.configuration.ui.LoginRestAuthUiConfigValidator;
import com.airlock.iam.login.app.application.configuration.ui.LoginRestPublicSelfServiceUiConfigValidator;
import com.airlock.iam.login.app.application.configuration.ui.LoginRestUserSelfRegUiConfigValidator;
import com.airlock.iam.login.app.application.configuration.ui.ProtectedSelfServiceUiConfigValidator;
import com.airlock.iam.login.app.misc.config.Configuration;
import com.airlock.iam.login.app.misc.configuration.oneshot.OneShotAuthenticationConfig;
import com.airlock.iam.login.application.configuration.event.LoginappEventSettingsConfig;
import com.airlock.iam.login.application.configuration.gateway.LoginappGatewayConfig;
import com.airlock.iam.login.misc.plugin.MaintenanceMessageConfig;
import com.airlock.iam.login.rest.application.configuration.LoginappRestConfigSpec;
import com.airlock.iam.login.rest.application.configuration.LoginappRestFailedTokenCountsValidator;
import com.airlock.iam.login.rest.application.configuration.UserSelfInformationConfigValidatorHelper;
import com.airlock.iam.login.rest.application.configuration.saml2.Saml2FlowIdentityPropagatorConfigValidator;
import com.airlock.iam.login.rest.application.configuration.ui.LoginappUiConfig;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationContextField;
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.PluginPropertyLicenseRestriction;
import com.airlock.iam.plugin.api.annotation.lifecycle.AfterConfigActivation;
import com.airlock.iam.plugin.api.annotation.lifecycle.BeforePluginDeactivation;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
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.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.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.exception.PropertyTypeMismatchException;
import com.airlock.iam.plugin.api.valuereader.PluginListPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
import com.airlock.iam.plugin.framework.domain.service.validation.DurationParser;
import com.airlock.iam.plugin.framework.domain.service.validation.DurationValidator;
import com.airlock.iam.plugin.framework.misc.AppId;
import com.airlock.iam.publicselfservice.application.configuration.PublicSelfServiceFlowsConfig;
import com.airlock.iam.saml2.application.configuration.SamlConfig;
import com.airlock.iam.selfservice.application.configuration.ProtectedSelfServicesConfig;
import com.airlock.iam.techclientreg.application.configuration.TechClientRegistrationRestConfig;
import com.airlock.iam.userselfreg.application.configuration.FlowBasedUserSelfRegRestConfig;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;

@Plugin(description = "Configures the Loginapp component.", displayName = "Loginapp")
@Groups(
   groupSequence = {"Flows and Services", "Basic Settings", "OpenID Connect, OAuth, SAML, One-Shot", "Advanced Settings"},
   openGroups = {"Flows and Services", "Basic Settings", "OpenID Connect, OAuth, SAML, One-Shot"}
)
public class Loginapp {
   public static final String LOGINAPP_DISPLAY_NAME = "Loginapp";
   static final String FLOWS_GROUP = "Flows and Services";
   static final String FEDERATION_AND_ENDPOINTS = "OpenID Connect, OAuth, SAML, One-Shot";
   public static final AppId LOGINAPP_CONFIG_IDENTIFIER = AppId.appId("loginapp");
   @PluginProperty(
      displayName = "Applications and Authentication",
      description = "Settings for target applications with authentication and authorization flows. This property configures the behavior of the REST endpoints. To enable the user interface (single-page application), configure corresponding \"UI Settings\"",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Flows and Services")
   private TargetApplicationsConfig authenticationFlows;
   public static final String AUTHENTICATION_FLOWS_PROPERTY = "authenticationFlows";
   public static final String AUTHENTICATION_FLOWS_DISPLAY_NAME = "Applications and Authentication";
   @PluginProperty(
      description = "Settings for user self-registration flows. This property configures the behavior of the REST endpoints. To enable the user interface (single-page application), configure corresponding \"UI Settings\"",
      optional = true,
      displayName = "Self-Registration"
   )
   @GroupProperty(groupIdentifier = "Flows and Services")
   private FlowBasedUserSelfRegRestConfig selfRegFlows;
   public static final String SELF_REG_FLOWS_PROPERTY = "selfRegFlows";
   @PluginProperty(
      description = "Settings for flow-based public self-service flows. This property configures the behavior of the REST endpoints. To enable the user interface (single-page application), configure corresponding \"UI Settings\"",
      optional = true,
      displayName = "Public Self-Services"
   )
   @GroupProperty(groupIdentifier = "Flows and Services")
   private PublicSelfServiceFlowsConfig publicSelfServiceFlows;
   public static final String PUBLIC_SELF_SERVICE_FLOWS_FIELD = "publicSelfServiceFlows";
   @PluginProperty(
      description = "Settings for protected self-services (flows and token management that are accessible to authenticated users). This property configures the behavior of the REST endpoints. To enable the user interface (single-page application), configure corresponding \"UI Settings\"",
      optional = true,
      displayName = "Protected Self-Services"
   )
   @GroupProperty(groupIdentifier = "Flows and Services")
   private ProtectedSelfServicesConfig protectedSelfServices;
   public static final String PROTECTED_SELF_SERVICES_PROPERTY = "protectedSelfServices";
   @PluginProperty(description = "User interface settings.", displayName = "UI Settings", optional = true)
   @GroupProperty(groupIdentifier = "Flows and Services")
   private LoginappUiConfig ui;
   public static final String UI_PROPERTY = "ui";
   static final String UI_SETTINGS_DISPLAY_NAME = "UI Settings";
   private static final String UI_HINT = "This property configures the behavior of the REST endpoints. To enable the user interface (single-page application), configure corresponding \"UI Settings\"";
   @PluginProperty(
      description = "<p>The user store for the REST API.</p><p><b>Important:</b> A user store is almost always required unless there are only persistency-less authentication flows configured.</p>",
      displayName = "User Store",
      optional = true,
      globalPluginReference = "globalUserStore"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private UserStoreProvider userStore;
   public static final String USER_STORE_FIELD = "userStore";
   public static final String USER_STORE_DISPLAY_NAME = "User Store";
   @PluginProperty(description = "Loginapp security settings.", displayName = "Security Settings", defaultValue = SecurityConfig.class, optional = true)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private SecurityConfig securitySettings;
   public static final String SECURITY_SETTINGS_DISPLAY_NAME = "Security Settings";
   @PluginProperty(description = "Configures language settings.", optional = true, defaultValue = LanguageSettings.class)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private LanguageSettings languageSettings;
   @PluginProperty(description = "Configures handling of events in the Loginapp.", optional = true)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private LoginappEventSettingsConfig eventSettings;
   @PluginProperty(description = "Configures settings related to maintenance messages.", optional = true, displayName = "Maintenance Messages")
   @GroupProperty(groupIdentifier = "Basic Settings")
   private MaintenanceMessageConfig maintenanceMessages;
   public static final String MAINTENANCE_MESSAGE_CONFIG_KEY = "maintenanceMessages";
   @PluginProperty(
      description = "Settings regarding an Airlock Gateway or Airlock Microgateway reverse proxy placed in front of Airlock IAM.<p>If no settings are configured, extra information from the reverse proxy will not be available and it may be harder to correlate log messages that are written to different log files.</p>",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private GatewayLoginappConfig gatewaySettings;
   @StringProperty(
      description = "Session idle timeout for the Loginapp. When IAM is deployed behind an Airlock Gateway (WAF), timeout and lifetime values should always be longer than those maintained by the Gateway.",
      displayName = "Session Idle Timeout",
      exampleValues = {"30m", "2h 15m"},
      optional = true,
      defaultValue = "30m"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String sessionIdleTimeout;
   private static final String SESSION_TIMEOUT_PROPERTY = "sessionIdleTimeout";
   private static final String SESSION_TIMEOUT_DISPLAY_NAME = "Session Idle Timeout";
   @StringProperty(
      description = "Session lifetime for the Loginapp. Unlike an idle timeout, the lifetime cannot be extended by activity and is always terminated once the lifetime has been reached. When IAM is deployed behind an Airlock Gateway (WAF), timeout and lifetime values should always be longer than those maintained by the Gateway.",
      displayName = "Session Lifetime",
      exampleValues = {"4h 30m", "8h"},
      optional = true,
      defaultValue = "8h"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String sessionLifetime;
   private static final String SESSION_LIFETIME_PROPERTY = "sessionLifetime";
   private static final String SESSION_LIFETIME_DISPLAY_NAME = "Session Lifetime";
   @PluginProperty(
      description = "Defines SAML IdP (identity propagator) and SAML SP (service provider) settings.",
      optional = true,
      displayName = "SAML Settings"
   )
   @GroupProperty(groupIdentifier = "OpenID Connect, OAuth, SAML, One-Shot")
   @PluginPropertyLicenseRestriction(moduleAffiliation = {"SamlIdp", "SamlSp"})
   private SamlConfig samlSettings;
   public static final String SAML_SETTINGS_PROPERTY = "samlSettings";
   @PluginProperty(description = "Configuration for OAuth 2.0 SSO (Airlock IAM as Client).", optional = true, displayName = "OAuth 2.0/OIDC Clients")
   @GroupProperty(groupIdentifier = "OpenID Connect, OAuth, SAML, One-Shot")
   @PluginPropertyLicenseRestriction(moduleAffiliation = "OAuthClient")
   private OAuth2SSOClientSettings oAuth2SSOSettings;
   private static final String OAUTH2_SSO_SETTINGS_PROPERTY = "oAuth2SSOSettings";
   public static final String OAUTH_2_0_CLIENT_SETTINGS_DISPLAY_NAME = "OAuth 2.0/OIDC Clients";
   @PluginProperty(
      description = "<p>Configuration for OAuth 2.0 / OpenID Connect SSO (Airlock IAM as Authorization Server (AS)).</p>",
      optional = true,
      displayName = "OAuth 2.0/OIDC Authorization Servers"
   )
   @GroupProperty(groupIdentifier = "OpenID Connect, OAuth, SAML, One-Shot")
   @PluginPropertyLicenseRestriction(moduleAffiliation = "OAuthServer")
   private OAuth2AuthorizationServersConfig oAuth2ASSettings;
   public static final String OAUTH2_AS_SETTINGS_DISPLAY_NAME = "OAuth 2.0/OIDC Authorization Servers";
   @PluginProperty(
      description = "Configures settings to the registration of technical clients.",
      optional = true,
      displayName = "Technical Client Registration"
   )
   @GroupProperty(groupIdentifier = "OpenID Connect, OAuth, SAML, One-Shot")
   @PluginPropertyLicenseRestriction(moduleAffiliation = "TechClientRegistration")
   private TechClientRegistrationRestConfig techClientRegistration;
   @PluginProperty(description = "Configures the one-shot authentication using Airlock Gateway.", optional = true, displayName = "One-Shot Authentication")
   @GroupProperty(groupIdentifier = "OpenID Connect, OAuth, SAML, One-Shot")
   private OneShotAuthenticationConfig oneShotAuthentication;
   @PluginProperty(
      description = "\t<p>\n\t\tConfigures session-less endpoints of the Loginapp REST API. These endpoints require\n\t\tauthentication credentials attached to each request, but don't require previous authentication with a flow.\n\t</p>\n\t<p>\n\t\tThese REST endpoints begin with the resource path <code>/&lt;loginapp-uri&gt;/rest/protected/my/</code>.\n\t</p>\n\t<p>\n\t\tFor most of the session-less protected REST APIs, there is a corresponding flow-based API in the protected self-service REST APIs.\n\t\tWhenever possible, prefer the flow-based variant over the session-less configured here.\n\t</p>\n",
      optional = true,
      displayName = "Session-less REST Endpoints"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private LoginappRestConfigSpec rest;
   public static final String REST_PROPERTY = "rest";
   @PluginProperty(
      description = "If configured, all IPs are geolocalized to provide additional input for the flow engine about approximate geographical location of the request origin. If a geolocation provider is specified, data is potentially persisted.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private GeolocationProviderConfig geolocationProvider;
   @PluginProperty(
      description = "<p>Defines where IAM stores all state. As long as only one instance of IAM is running (no horizontal scaling),\nthe in-memory repository can be used.</p>\n<p>If session context retention is used, this plugin may only be configured in the default context.</p>\n",
      optional = false,
      globalPluginReference = "globalStateRepository"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private StateRepositoryConfig stateRepository;
   public static final String STATE_REPOSITORY_PROPERTY = "stateRepository";
   @PluginProperty(
      description = "<p>Specifies how a context is to be extracted from a request.</p>\n<p>Depending on what context retention policy is configured, this value might then retained e.g. for the\nduration of the request, or the duration of the session. Additionally, depending on what retention policy\nis used, this extractor might be evaluated e.g. for each request, or once per session.</p>\n",
      optional = true,
      defaultValue = NoContextExtractor.class,
      supportsContexts = false
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private ContextExtractor contextExtractor;
   @PluginProperty(
      description = "<p>Specifies how contexts are retained.</p>\n<p>This property defines when the context extractor is evaluated, and how long the resulting value is\nretained. Depending on the retention policy, the context extractor may be e.g. evaluated once per request,\nor once per session.</p>\n",
      optional = true,
      defaultValue = RequestContextRetentionPolicy.class,
      supportsContexts = false
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private ContextRetentionPolicy contextRetentionPolicy;
   private static final String CONTEXT_RETENTION_POLICY_PROPERTY = "contextRetentionPolicy";
   @PluginProperty(
      description = "<p>Cronto Handler to handle direct communication from the Cronto apps. This handler is used by the technical Cronto servlets that handle requests to approve push or online validation messages, return the transaction list or handle push notificiation ID registration.</p><p>This Cronto Handler is only used if push or online validation are active.</p>",
      globalPluginReference = "globalCrontoSettings",
      displayName = "Cronto App Communication",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   @PluginPropertyLicenseRestriction(moduleAffiliation = "Cronto")
   private CrontoHandler crontoAppCommunication;
   public static final String CRONTO_APP_COMM_DISPLAY_NAME = "Cronto App Communication";
   @PluginListProperty(
      description = "Custom extensions for the Loginapp. Allows connecting custom configuration plugins to the IAM Loginapp module.",
      displayName = "Custom Extensions",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private List<ConfigurationExtension> customExtensions;
   public static final String CUSTOM_EXTENSIONS_DISPLAY_NAME = "Custom Extensions";
   @PluginProperty(
      description = "Readiness health check endpoint for the Loginapp module.",
      optional = true,
      supportsContexts = false,
      defaultValue = ModuleReadinessConfig.class
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private ModuleReadinessConfig readinessHealthCheckEndpoint;
   @PluginProperty(
      description = "<p>Configures the database settings to use when persisting user trail log entries.</p>\n<p>If this value is defined, then all user trail log messages generated by the Loginapp\nmodule will additionally be forwarded to the database configured within the referenced\nrepository plugin.</p>\n<p>All forwarded log entries are stored inside the table \"USER_TRAIL_LOG\". Note that\nsetting this value does not disable writing log messages to the Loginapp log file.</p>",
      globalPluginReference = "globalUserTrailLogRepository",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private UserTrailLogRepositoryConfig logUserTrailToDatabase;
   @PluginProperty(
      displayName = "Correlation ID Settings",
      description = "<p>Defines settings for correlation ID transfer and logging inside the Loginapp module.</p>\n<p>If undefined, no correlation ID will be logged for this module.</p>",
      globalPluginReference = "globalCorrelationIdSettings",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private CorrelationIdSettingsConfig correlationIdSettings;
   @PluginProperty(
      description = "Configures the database settings to use when persisting device usage data. This repository is used by the Device Usage Processor. If not configured, the device usages are not stored and thus no conditions and events based on previous/first device usage can be used.",
      globalPluginReference = "globalDeviceUsageRepository",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private DeviceUsageRepositoryConfig deviceUsageRepositoryConfig;
   @PluginProperty(
      description = "<p>Enables a JWKS endpoint for all keys used in Loginapp, if configured.</p>\n<p>The JWKS endpoint URL is <code>/&lt;loginapp-uri&gt;/rest/public/jwks/</code><p>\n",
      displayName = "JWKS Settings",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private JwksLoginappConfig jwksSettings;
   @ConfigurationContextField
   private ConfigurationContext configurationContext = ConfigurationContext.defaultContext();

   public static Loginapp get(ConfigurationContext context) {
      return Configuration.getInstance().getLoginApp(context);
   }

   public Loginapp(
      TargetApplicationsConfig authenticationFlows,
      FlowBasedUserSelfRegRestConfig selfRegFlows,
      PublicSelfServiceFlowsConfig publicSelfServiceFlows,
      ProtectedSelfServicesConfig protectedSelfServices,
      LoginappUiConfig ui,
      UserStoreProvider userStore,
      SecurityConfig securitySettings,
      LanguageSettings languageSettings,
      LoginappEventSettingsConfig eventSettings,
      MaintenanceMessageConfig maintenanceMessages,
      GatewayLoginappConfig gatewaySettings,
      String sessionIdleTimeout,
      String sessionLifetime,
      SamlConfig samlSettings,
      OAuth2SSOClientSettings oAuth2SSOSettings,
      OAuth2AuthorizationServersConfig oAuth2ASSettings,
      TechClientRegistrationRestConfig techClientRegistration,
      OneShotAuthenticationConfig oneShotAuthentication,
      LoginappRestConfigSpec rest,
      GeolocationProviderConfig geolocationProvider,
      StateRepositoryConfig stateRepository,
      ContextExtractor contextExtractor,
      ContextRetentionPolicy contextRetentionPolicy,
      CrontoHandler crontoAppCommunication,
      List<ConfigurationExtension> customExtensions,
      ModuleReadinessConfig readinessHealthCheckEndpoint,
      UserTrailLogRepositoryConfig logUserTrailToDatabase,
      CorrelationIdSettingsConfig correlationIdSettings,
      DeviceUsageRepositoryConfig deviceUsageRepositoryConfig,
      JwksLoginappConfig jwksSettings
   ) {
      this.authenticationFlows = authenticationFlows;
      this.selfRegFlows = selfRegFlows;
      this.publicSelfServiceFlows = publicSelfServiceFlows;
      this.protectedSelfServices = protectedSelfServices;
      this.ui = ui;
      this.userStore = userStore;
      this.securitySettings = securitySettings;
      this.languageSettings = languageSettings;
      this.eventSettings = eventSettings;
      this.maintenanceMessages = maintenanceMessages;
      this.gatewaySettings = gatewaySettings;
      this.sessionIdleTimeout = sessionIdleTimeout;
      this.sessionLifetime = sessionLifetime;
      this.samlSettings = samlSettings;
      this.oAuth2SSOSettings = oAuth2SSOSettings;
      this.oAuth2ASSettings = oAuth2ASSettings;
      this.techClientRegistration = techClientRegistration;
      this.oneShotAuthentication = oneShotAuthentication;
      this.rest = rest;
      this.geolocationProvider = geolocationProvider;
      this.stateRepository = stateRepository;
      this.contextExtractor = contextExtractor;
      this.contextRetentionPolicy = contextRetentionPolicy;
      this.crontoAppCommunication = crontoAppCommunication;
      this.customExtensions = customExtensions;
      this.readinessHealthCheckEndpoint = readinessHealthCheckEndpoint;
      this.logUserTrailToDatabase = logUserTrailToDatabase;
      this.correlationIdSettings = correlationIdSettings;
      this.deviceUsageRepositoryConfig = deviceUsageRepositoryConfig;
      this.jwksSettings = jwksSettings;
   }

   public Collection<LoginappDomainEventSubscriberConfig> getEventSubscribers() {
      return this.eventSettings != null ? this.eventSettings.getEventSubscribers() : Collections.emptyList();
   }

   public Optional<LoginappEventSettingsConfig> getEventSettings() {
      return Optional.ofNullable(this.eventSettings);
   }

   public Duration getSessionIdleTimeout() {
      return new DurationParser(this.sessionIdleTimeout).getDuration();
   }

   public Duration getSessionLifetime() {
      return new DurationParser(this.sessionLifetime).getDuration();
   }

   public Optional<SamlConfig> getSamlSettings() {
      return Optional.ofNullable(this.samlSettings);
   }

   public Optional<OAuth2SSOClientSettings> getOAuth2SSOSettings() {
      return Optional.ofNullable(this.oAuth2SSOSettings);
   }

   public Optional<OAuth2AuthorizationServersConfig> getOAuth2ASSettings() {
      return Optional.ofNullable(this.oAuth2ASSettings);
   }

   public Optional<OneShotAuthenticationConfig> getOneShotSettings() {
      return Optional.ofNullable(this.oneShotAuthentication);
   }

   public Optional<LoginappRestConfigSpec> getRestConfig() {
      return Optional.ofNullable(this.rest);
   }

   public <T> Optional<T> getCustomExtension(Class<T> requiredType) {
      return this.customExtensions.stream().filter(ext -> requiredType.isAssignableFrom(ext.getClass())).map(requiredType::cast).findFirst();
   }

   public Optional<TargetApplicationsConfig> getAuthenticationFlows() {
      return Optional.ofNullable(this.authenticationFlows);
   }

   public Optional<FlowBasedUserSelfRegRestConfig> getSelfRegistrationFlows() {
      return Optional.ofNullable(this.selfRegFlows);
   }

   public Optional<ProtectedSelfServicesConfig> getProtectedSelfServices() {
      return Optional.ofNullable(this.protectedSelfServices);
   }

   public Optional<PublicSelfServiceFlowsConfig> getPublicSelfServiceFlows() {
      return Optional.ofNullable(this.publicSelfServiceFlows);
   }

   public Optional<LoginappUiConfig> getUi() {
      return Optional.ofNullable(this.ui);
   }

   public Optional<UserStore> getUserStore() {
      return Optional.ofNullable(this.userStore).map(UserStoreProvider::getUserStore);
   }

   public Optional<MaintenanceMessageConfig> getMaintenanceMessages() {
      return Optional.ofNullable(this.maintenanceMessages);
   }

   public Optional<TechClientRegistrationRestConfig> getTechnicalClientRegistrationConfig() {
      return Optional.ofNullable(this.techClientRegistration);
   }

   public Optional<LoginappGatewayConfig> getGatewayConfig() {
      return Optional.ofNullable(this.gatewaySettings).filter(LoginappGatewayConfig.class::isInstance).map(LoginappGatewayConfig.class::cast);
   }

   public Optional<AirlockMicrogatewayConfig> getMicrogatewayConfig() {
      return Optional.ofNullable(this.gatewaySettings).filter(AirlockMicrogatewayConfig.class::isInstance).map(AirlockMicrogatewayConfig.class::cast);
   }

   public Optional<CorrelationIdSettingsConfig> getCorrelationIdSettingsConfig() {
      return Optional.ofNullable(this.correlationIdSettings);
   }

   public Optional<CrontoHandler> getCrontoAppCommunication() {
      return Optional.ofNullable(this.crontoAppCommunication);
   }

   public Optional<GeolocationProviderConfig> getGeolocationProvider() {
      return Optional.ofNullable(this.geolocationProvider);
   }

   public Optional<UserTrailLogRepositoryConfig> getUserTrailLogRepositoryConfig() {
      return Optional.ofNullable(this.logUserTrailToDatabase);
   }

   public Optional<DeviceUsageRepositoryConfig> getDeviceUsageRepositoryConfig() {
      return Optional.ofNullable(this.deviceUsageRepositoryConfig);
   }

   public Optional<JwksLoginappConfig> getJwksSettings() {
      return Optional.ofNullable(this.jwksSettings);
   }

   @ConfigurationValidator
   public static void checkConfigDependencies(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      assurePersisterConfiguredIfAccountLinkingListsSelfServiceConfigured(config, env, context);
   }

   static void assurePersisterConfiguredIfAccountLinkingListsSelfServiceConfigured(
      PluginConfiguration loginAppConfig, ConfigurationEnvironment env, ConfigurationContext context
   ) throws ConfigurationException {
      PluginConfiguration oauthSsoSettings = PluginPropertyValueReader.effectivePluginPropertyValue(loginAppConfig, "oAuth2SSOSettings", env, context);
      PluginConfiguration protectedSelfServices = PluginPropertyValueReader.effectivePluginPropertyValue(loginAppConfig, "protectedSelfServices", env, context);
      if (oauthSsoSettings == null || !OAuth2SSOClientSettings.isAccountLinkPersisterConfigured(oauthSsoSettings, env, context)) {
         if (protectedSelfServices != null) {
            PluginConfiguration accountLinkingListsSelfServiceSettings = PluginPropertyValueReader.effectivePluginPropertyValue(
               protectedSelfServices, "accountLinkingLists", env, context
            );
            if (accountLinkingListsSelfServiceSettings != null) {
               if (oauthSsoSettings == null) {
                  throw new PropertyConstraintViolationException(
                     "oAuth2SSOSettings",
                     loginAppConfig,
                     env,
                     context,
                     LogUtil.quote("OAuth 2.0/OIDC Clients")
                        + " with an Account Link Persister is required when self-service "
                        + LogUtil.quote("Account Linking Lists")
                        + " is configured."
                  );
               } else {
                  throw new PropertyConstraintViolationException(
                     "accountLinkPersister",
                     oauthSsoSettings,
                     env,
                     context,
                     "Account Link Persister is required when self-service " + LogUtil.quote("Account Linking Lists") + " is configured."
                  );
               }
            }
         }
      }
   }

   @ConfigurationValidator
   static void validateFailedTokenCountsConfigured(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment valEnv) throws ConfigurationException {
      PluginConfiguration storeConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "userStore", env, context);
      PluginConfiguration authFlowsConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "authenticationFlows", env, context);
      if (authFlowsConfig != null && storeConfig != null) {
         LoginappRestFailedTokenCountsValidator authUiValidator = new LoginappRestFailedTokenCountsValidator(storeConfig, env, context);
         authUiValidator.validateFailedTokenCountsConfigured();
      }
   }

   @ConfigurationValidator
   static void validateAuthUiConfiguration(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      PluginConfiguration uiConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "ui", env, context);
      if (uiConfig != null) {
         PluginConfiguration authFlowsConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "authenticationFlows", env, context);
         LoginRestAuthUiConfigValidator authUiValidator = new LoginRestAuthUiConfigValidator(authFlowsConfig, uiConfig, config, env, context);
         authUiValidator.validateAuthUiConfig();
      }
   }

   @ConfigurationValidator
   static void validateSelfRegUiConfiguration(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      PluginConfiguration uiConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "ui", env, context);
      if (uiConfig != null) {
         PluginConfiguration selfRegConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "selfRegFlows", env, context);
         LoginRestUserSelfRegUiConfigValidator selfRegUiValidator = new LoginRestUserSelfRegUiConfigValidator(selfRegConfig, uiConfig, config, env, context);
         selfRegUiValidator.validateSelfRegUiConfig();
      }
   }

   @ConfigurationValidator
   static void validateProtectedSelfServiceUiConfiguration(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      PluginConfiguration uiConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "ui", env, context);
      if (uiConfig != null) {
         PluginConfiguration protectedSelfServicesConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "protectedSelfServices", env, context);
         ProtectedSelfServiceUiConfigValidator validator = new ProtectedSelfServiceUiConfigValidator(
            protectedSelfServicesConfig, uiConfig, config, env, context
         );
         validator.validate();
      }
   }

   @ConfigurationValidator
   static void validatPublicSelfServiceUiConfiguration(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      PluginConfiguration uiConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "ui", env, context);
      if (uiConfig != null) {
         PluginConfiguration publicSelfServiceFlowsConfig = PluginPropertyValueReader.effectivePluginPropertyValue(
            config, "publicSelfServiceFlows", env, context
         );
         LoginRestPublicSelfServiceUiConfigValidator validator = new LoginRestPublicSelfServiceUiConfigValidator(
            publicSelfServiceFlowsConfig, uiConfig, config, env, context
         );
         validator.validate();
      }
   }

   @ConfigurationValidator
   static void validatePersistency(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment) throws ConfigurationException {
      LoginappUserStoreValidator validator = new LoginappUserStoreValidator(config, env, context);
      validator.validateUserStoreRequired();
   }

   @ConfigurationValidator
   static void validateUserSelfInformationContextDataFieldNamesAvailable(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      PluginConfiguration restConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "rest", env, context);
      if (restConfig != null) {
         PluginConfiguration storeConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "userStore", env, context);
         Set<String> userSelfInformationContextDataNames = UserSelfInformationConfigValidatorHelper.getContextDataNamesFromUserSelfInformationConfig(
            restConfig, env, context
         );
         Optional<Set<String>> userStoreContextDataNames = UserSelfInformationConfigValidatorHelper.getContextDataNamesFromUserStoreConfig(
            storeConfig, env, context
         );
         if (userStoreContextDataNames.isPresent() && !userStoreContextDataNames.get().containsAll(userSelfInformationContextDataNames)) {
            userSelfInformationContextDataNames.removeAll(userStoreContextDataNames.get());
            String offendingNames = String.join(", ", userSelfInformationContextDataNames);
            throw new PropertyConstraintViolationException(
               "userSelfServiceSettings",
               restConfig,
               env,
               context,
               String.format(
                  "The User Self-Service Settings refer to items not configured as (additional) context data items in the user store. Name(s): '%s'",
                  offendingNames
               )
            );
         }
      }
   }

   @ConfigurationValidator
   static void validateSamlDefaultSpInIdentityPropagator(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      Optional<PluginConfiguration> authenticationFlows = Optional.ofNullable(
         PluginPropertyValueReader.effectivePluginPropertyValue(config, "authenticationFlows", env, context)
      );
      Optional<PluginConfiguration> samlConfig = Optional.ofNullable(
         PluginPropertyValueReader.effectivePluginPropertyValue(config, "samlSettings", env, context)
      );
      Saml2FlowIdentityPropagatorConfigValidator.builder()
         .authenticationFlows(authenticationFlows)
         .samlConfig(samlConfig)
         .environment(env)
         .context(context)
         .build()
         .validate();
   }

   @ConfigurationValidator
   static void validateSamlUserIdentifyingFlowStep(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      Optional<PluginConfiguration> authenticationFlows = Optional.ofNullable(
         PluginPropertyValueReader.effectivePluginPropertyValue(config, "authenticationFlows", env, context)
      );
      Optional<PluginConfiguration> samlConfig = Optional.ofNullable(
         PluginPropertyValueReader.effectivePluginPropertyValue(config, "samlSettings", env, context)
      );
      Saml2UserIdentifyingFlowStepConfigValidator.builder()
         .authenticationFlows(authenticationFlows)
         .samlConfig(samlConfig)
         .environment(env)
         .context(context)
         .build()
         .validate();
   }

   @ConfigurationValidator
   static void validateOnLogoutUiIsValidForSaml(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      PluginConfiguration loginRestUiConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "ui", env, context);
      PluginConfiguration samlConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "samlSettings", env, context);
      if (samlConfig != null && loginRestUiConfig != null) {
         PluginConfiguration samlIdpFlowConfig = PluginPropertyValueReader.effectivePluginPropertyValue(samlConfig, "samlFlowIdpSettings", env, context);
         List<PluginConfiguration> samlSpFlowConfig = PluginListPropertyValueReader.effectivePluginListPropertyValue(
            samlConfig, "samlFlowSpSettings", env, context
         );
         PluginConfiguration authenticationUiConfig = PluginPropertyValueReader.effectivePluginPropertyValue(
            loginRestUiConfig, "authenticationUi", env, context
         );
         if (authenticationUiConfig != null && (samlIdpFlowConfig != null || !samlSpFlowConfig.isEmpty())) {
            PluginConfiguration onLogoutConfig = PluginPropertyValueReader.effectivePluginPropertyValue(authenticationUiConfig, "onLogout", env, context);
            if (onLogoutConfig != null && onLogoutConfig.isOfPluginClass(ShowLogoutDisclaimerPageConfig.class)) {
               throw new PropertyTypeMismatchException(
                  "onLogout",
                  authenticationUiConfig,
                  env,
                  context,
                  "On Logout Settings must not be of type: \"Show Logout Disclaimer Page Config\" if a SAML Flow is present"
               );
            }
         }
      }
   }

   @ConfigurationValidator
   static void validateFlowSpDefaultAppHasSaml2UserIdentifiyingStep(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      Optional<PluginConfiguration> authenticationFlowsConfig = Optional.ofNullable(
         PluginPropertyValueReader.effectivePluginPropertyValue(config, "authenticationFlows", env, context)
      );
      Optional<PluginConfiguration> saml2Config = Optional.ofNullable(
         PluginPropertyValueReader.effectivePluginPropertyValue(config, "samlSettings", env, context)
      );
      Saml2SpDefaultAppValidator.builder()
         .authenticationFlowsConfig(authenticationFlowsConfig)
         .saml2Config(saml2Config)
         .environment(env)
         .context(context)
         .build()
         .validate();
   }

   @ConfigurationValidator
   public static void validateSessionDurations(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment valEnv) throws ConfigurationException {
      DurationValidator.validateDurationField1IsLargerOrEqualField2IfBothSet(
         config, "sessionLifetime", "Session Lifetime", "sessionIdleTimeout", "Session Idle Timeout", env, context
      );
      DurationValidator.validateDurationMinValueIfSet(config, "sessionIdleTimeout", Duration.ofSeconds(10L), env, context);
      DurationValidator.validateDurationMinValueIfSet(config, "sessionLifetime", Duration.ofSeconds(10L), env, context);
   }

   @ConfigurationValidator
   public static void validateStateRepositoryOnlyForDefaultContextIfSessionContextRetentionPolicy(
      PluginConfiguration loginappConfiguration,
      ConfigurationEnvironment env,
      ConfigurationContext loginappConfigurationContext,
      Environment validationEnvironment
   ) throws ConfigurationException {
      PluginConfiguration contextRetentionPolicy = PluginPropertyValueReader.effectivePluginPropertyValue(
         loginappConfiguration, "contextRetentionPolicy", env, loginappConfigurationContext
      );
      if (contextRetentionPolicy.isOfPluginClass(SessionContextRetentionPolicy.class)) {
         for (ConfigurationContext context : loginappConfiguration.getUsedConfigurationContexts(env)) {
            if (!context.isDefaultContext() && loginappConfiguration.isPropertyDefinedForContext("stateRepository", env, context)) {
               throw new PropertyConstraintViolationException(
                  "stateRepository",
                  contextRetentionPolicy,
                  env,
                  context,
                  "Session context retention is being used but the state repository is configured for the following non-default configuration context: "
                     + LogUtil.quote(context.getIdentifier())
               );
            }
         }
      }
   }

   public SecurityConfig getSecuritySettings() {
      return this.securitySettings;
   }

   public LanguageSettings getLanguageSettings() {
      return this.languageSettings;
   }

   public ContextExtractor getContextExtractor() {
      return this.contextExtractor;
   }

   public ContextRetentionPolicy getContextRetentionPolicy() {
      return this.contextRetentionPolicy;
   }

   public List<ConfigurationExtension> getCustomExtensions() {
      return this.customExtensions;
   }

   public ModuleReadinessConfig getReadinessHealthCheckEndpoint() {
      return this.readinessHealthCheckEndpoint;
   }

   public StateRepositoryConfig getStateRepository() {
      return this.stateRepository;
   }

   @AfterConfigActivation
   void activateJwksIfPresent() {
      JwksProvider.setEnabled(this.configurationContext, this.jwksSettings != null);
   }

   @BeforePluginDeactivation
   void deactivateJwks() {
      JwksProvider.setEnabled(this.configurationContext, false);
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class LoginappBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private TargetApplicationsConfig authenticationFlows;
      @SuppressFBWarnings(justification = "generated code")
      private FlowBasedUserSelfRegRestConfig selfRegFlows;
      @SuppressFBWarnings(justification = "generated code")
      private PublicSelfServiceFlowsConfig publicSelfServiceFlows;
      @SuppressFBWarnings(justification = "generated code")
      private ProtectedSelfServicesConfig protectedSelfServices;
      @SuppressFBWarnings(justification = "generated code")
      private LoginappUiConfig ui;
      @SuppressFBWarnings(justification = "generated code")
      private UserStoreProvider userStore;
      @SuppressFBWarnings(justification = "generated code")
      private SecurityConfig securitySettings;
      @SuppressFBWarnings(justification = "generated code")
      private LanguageSettings languageSettings;
      @SuppressFBWarnings(justification = "generated code")
      private LoginappEventSettingsConfig eventSettings;
      @SuppressFBWarnings(justification = "generated code")
      private MaintenanceMessageConfig maintenanceMessages;
      @SuppressFBWarnings(justification = "generated code")
      private GatewayLoginappConfig gatewaySettings;
      @SuppressFBWarnings(justification = "generated code")
      private String sessionIdleTimeout;
      @SuppressFBWarnings(justification = "generated code")
      private String sessionLifetime;
      @SuppressFBWarnings(justification = "generated code")
      private SamlConfig samlSettings;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2SSOClientSettings oAuth2SSOSettings;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2AuthorizationServersConfig oAuth2ASSettings;
      @SuppressFBWarnings(justification = "generated code")
      private TechClientRegistrationRestConfig techClientRegistration;
      @SuppressFBWarnings(justification = "generated code")
      private OneShotAuthenticationConfig oneShotAuthentication;
      @SuppressFBWarnings(justification = "generated code")
      private LoginappRestConfigSpec rest;
      @SuppressFBWarnings(justification = "generated code")
      private GeolocationProviderConfig geolocationProvider;
      @SuppressFBWarnings(justification = "generated code")
      private StateRepositoryConfig stateRepository;
      @SuppressFBWarnings(justification = "generated code")
      private ContextExtractor contextExtractor;
      @SuppressFBWarnings(justification = "generated code")
      private ContextRetentionPolicy contextRetentionPolicy;
      @SuppressFBWarnings(justification = "generated code")
      private CrontoHandler crontoAppCommunication;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<ConfigurationExtension> customExtensions;
      @SuppressFBWarnings(justification = "generated code")
      private ModuleReadinessConfig readinessHealthCheckEndpoint;
      @SuppressFBWarnings(justification = "generated code")
      private UserTrailLogRepositoryConfig logUserTrailToDatabase;
      @SuppressFBWarnings(justification = "generated code")
      private CorrelationIdSettingsConfig correlationIdSettings;
      @SuppressFBWarnings(justification = "generated code")
      private DeviceUsageRepositoryConfig deviceUsageRepositoryConfig;
      @SuppressFBWarnings(justification = "generated code")
      private JwksLoginappConfig jwksSettings;

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

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder authenticationFlows(TargetApplicationsConfig authenticationFlows) {
         this.authenticationFlows = authenticationFlows;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder selfRegFlows(FlowBasedUserSelfRegRestConfig selfRegFlows) {
         this.selfRegFlows = selfRegFlows;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder publicSelfServiceFlows(PublicSelfServiceFlowsConfig publicSelfServiceFlows) {
         this.publicSelfServiceFlows = publicSelfServiceFlows;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder protectedSelfServices(ProtectedSelfServicesConfig protectedSelfServices) {
         this.protectedSelfServices = protectedSelfServices;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder ui(LoginappUiConfig ui) {
         this.ui = ui;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder userStore(UserStoreProvider userStore) {
         this.userStore = userStore;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder securitySettings(SecurityConfig securitySettings) {
         this.securitySettings = securitySettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder languageSettings(LanguageSettings languageSettings) {
         this.languageSettings = languageSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder eventSettings(LoginappEventSettingsConfig eventSettings) {
         this.eventSettings = eventSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder maintenanceMessages(MaintenanceMessageConfig maintenanceMessages) {
         this.maintenanceMessages = maintenanceMessages;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder gatewaySettings(GatewayLoginappConfig gatewaySettings) {
         this.gatewaySettings = gatewaySettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder sessionIdleTimeout(String sessionIdleTimeout) {
         this.sessionIdleTimeout = sessionIdleTimeout;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder sessionLifetime(String sessionLifetime) {
         this.sessionLifetime = sessionLifetime;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder samlSettings(SamlConfig samlSettings) {
         this.samlSettings = samlSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder oAuth2SSOSettings(OAuth2SSOClientSettings oAuth2SSOSettings) {
         this.oAuth2SSOSettings = oAuth2SSOSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder oAuth2ASSettings(OAuth2AuthorizationServersConfig oAuth2ASSettings) {
         this.oAuth2ASSettings = oAuth2ASSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder techClientRegistration(TechClientRegistrationRestConfig techClientRegistration) {
         this.techClientRegistration = techClientRegistration;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder oneShotAuthentication(OneShotAuthenticationConfig oneShotAuthentication) {
         this.oneShotAuthentication = oneShotAuthentication;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder rest(LoginappRestConfigSpec rest) {
         this.rest = rest;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder geolocationProvider(GeolocationProviderConfig geolocationProvider) {
         this.geolocationProvider = geolocationProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder stateRepository(StateRepositoryConfig stateRepository) {
         this.stateRepository = stateRepository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder contextExtractor(ContextExtractor contextExtractor) {
         this.contextExtractor = contextExtractor;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder contextRetentionPolicy(ContextRetentionPolicy contextRetentionPolicy) {
         this.contextRetentionPolicy = contextRetentionPolicy;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder crontoAppCommunication(CrontoHandler crontoAppCommunication) {
         this.crontoAppCommunication = crontoAppCommunication;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder customExtension(ConfigurationExtension customExtension) {
         if (this.customExtensions == null) {
            this.customExtensions = new ArrayList<>();
         }

         this.customExtensions.add(customExtension);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder customExtensions(Collection<? extends ConfigurationExtension> customExtensions) {
         if (customExtensions == null) {
            throw new NullPointerException("customExtensions cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder clearCustomExtensions() {
         if (this.customExtensions != null) {
            this.customExtensions.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder readinessHealthCheckEndpoint(ModuleReadinessConfig readinessHealthCheckEndpoint) {
         this.readinessHealthCheckEndpoint = readinessHealthCheckEndpoint;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder logUserTrailToDatabase(UserTrailLogRepositoryConfig logUserTrailToDatabase) {
         this.logUserTrailToDatabase = logUserTrailToDatabase;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder correlationIdSettings(CorrelationIdSettingsConfig correlationIdSettings) {
         this.correlationIdSettings = correlationIdSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder deviceUsageRepositoryConfig(DeviceUsageRepositoryConfig deviceUsageRepositoryConfig) {
         this.deviceUsageRepositoryConfig = deviceUsageRepositoryConfig;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp.LoginappBuilder jwksSettings(JwksLoginappConfig jwksSettings) {
         this.jwksSettings = jwksSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Loginapp build() {
         return new Loginapp(
            this.authenticationFlows,
            this.selfRegFlows,
            this.publicSelfServiceFlows,
            this.protectedSelfServices,
            this.ui,
            this.userStore,
            this.securitySettings,
            this.languageSettings,
            this.eventSettings,
            this.maintenanceMessages,
            this.gatewaySettings,
            this.sessionIdleTimeout,
            this.sessionLifetime,
            this.samlSettings,
            this.oAuth2SSOSettings,
            this.oAuth2ASSettings,
            this.techClientRegistration,
            this.oneShotAuthentication,
            this.rest,
            this.geolocationProvider,
            this.stateRepository,
            this.contextExtractor,
            this.contextRetentionPolicy,
            this.crontoAppCommunication,
            switch (this.customExtensions == null ? 0 : this.customExtensions.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.customExtensions.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.customExtensions));
            },
            this.readinessHealthCheckEndpoint,
            this.logUserTrailToDatabase,
            this.correlationIdSettings,
            this.deviceUsageRepositoryConfig,
            this.jwksSettings
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Loginapp.LoginappBuilder(authenticationFlows="
            + this.authenticationFlows
            + ", selfRegFlows="
            + this.selfRegFlows
            + ", publicSelfServiceFlows="
            + this.publicSelfServiceFlows
            + ", protectedSelfServices="
            + this.protectedSelfServices
            + ", ui="
            + this.ui
            + ", userStore="
            + this.userStore
            + ", securitySettings="
            + this.securitySettings
            + ", languageSettings="
            + this.languageSettings
            + ", eventSettings="
            + this.eventSettings
            + ", maintenanceMessages="
            + this.maintenanceMessages
            + ", gatewaySettings="
            + this.gatewaySettings
            + ", sessionIdleTimeout="
            + this.sessionIdleTimeout
            + ", sessionLifetime="
            + this.sessionLifetime
            + ", samlSettings="
            + this.samlSettings
            + ", oAuth2SSOSettings="
            + this.oAuth2SSOSettings
            + ", oAuth2ASSettings="
            + this.oAuth2ASSettings
            + ", techClientRegistration="
            + this.techClientRegistration
            + ", oneShotAuthentication="
            + this.oneShotAuthentication
            + ", rest="
            + this.rest
            + ", geolocationProvider="
            + this.geolocationProvider
            + ", stateRepository="
            + this.stateRepository
            + ", contextExtractor="
            + this.contextExtractor
            + ", contextRetentionPolicy="
            + this.contextRetentionPolicy
            + ", crontoAppCommunication="
            + this.crontoAppCommunication
            + ", customExtensions="
            + this.customExtensions
            + ", readinessHealthCheckEndpoint="
            + this.readinessHealthCheckEndpoint
            + ", logUserTrailToDatabase="
            + this.logUserTrailToDatabase
            + ", correlationIdSettings="
            + this.correlationIdSettings
            + ", deviceUsageRepositoryConfig="
            + this.deviceUsageRepositoryConfig
            + ", jwksSettings="
            + this.jwksSettings
            + ")";
      }
   }
}
