package com.airlock.iam.common.application.configuration.jwt;

import com.airlock.iam.base.misc.time.RealTimeProvider;
import com.airlock.iam.common.api.application.configuration.claims.ClaimsValidatorConfig;
import com.airlock.iam.common.domain.service.jwt.JwtTicketDecoderImpl;
import com.airlock.iam.common.domain.service.jwt.JwtTicketEncoderDecoderSettingsValidator;
import com.airlock.iam.common.domain.service.jwt.encryption.JwtTicketDecrypter;
import com.airlock.iam.common.domain.service.jwt.signature.JwtSignatureVerifier;
import com.airlock.iam.core.domain.service.jwt.JwtTicketDecoder;
import com.airlock.iam.core.misc.util.ticket.Ticket;
import com.airlock.iam.core.misc.util.ticket.TicketEncodingException;
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.lifecycle.AfterConfigActivation;
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.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.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.exception.PropertyTypeMismatchException;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;

@Plugin(
   description = "<p>Configures the JWT (JSON Web Token) ticket decoder.</p><p>The <em>decoder</em> verifies a ticket's MAC or signature and decrypts it in case encryption is enabled. A ticket is required to contain at least a subject ('sub' claim) and an expiration date ('exp' claim). Additionally an issuer ('iss' claim) and audience ('aud' claim) might be required, if configured. A 'not before' ('nbf' claim) is validated if present but only mandatory if configured to be.</p><p>If the ticket starts with a \"Bearer \" prefix (e.g. from a HTTP Authorization header), the prefix is removed before decoding the JWT.</p>",
   displayName = "JWT Ticket Decoder"
)
@Groups(groupSequence = {" Claim Settings", "Security Settings"}, openGroups = {" Claim Settings", "Security Settings"})
public class JwtTicketDecoderSettings implements JwtTicketDecoder {
   public static final String CLAIM_SETTINGS = " Claim Settings";
   @StringProperty(
      description = "<p>The ticket key for the username. The username in the 'sub' claim of the JWT will be written to the IAM ticket using this ticket key.</p><p>If using this ticket decoder for the loginapp's \"SSO Ticket Feature\", the value of this attribute must be \"username\".</p>",
      exampleValues = "username",
      optional = false
   )
   @GroupProperty(groupIdentifier = " Claim Settings")
   private String usernameTicketKey;
   private static final String USERNAME_TICKET_KEY_PROPERTY_NAME = "usernameTicketKey";
   @BooleanProperty(
      description = "If set to true, the incoming JWT must have a 'nbf' claim. Note that this setting does not affect validation of the 'nbf' claim. If this claim is present, it will always be validated.",
      optional = true,
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = " Claim Settings")
   private boolean notBeforeIsMandatory;
   @IntegerProperty(
      description = "The configured value in seconds is added to the current time before validating the 'nbf' claim. The motivation to compare the 'nbf' claim with a time in the future is to avoid clock synchronization problems with the JWT issuer.",
      optional = true,
      defaultValue = 5,
      minValue = 0,
      exampleValues = {5, 60, 3600}
   )
   @GroupProperty(groupIdentifier = " Claim Settings")
   private int validNotBeforeSkew;
   @StringProperty(
      description = "The ticket key for the not before claim. The JWT not before claim ('nbf') will be written to that field. Note that this field is optional. If not set, the 'nbf' claim of the JWT won't be written into the ticket.",
      optional = true
   )
   @GroupProperty(groupIdentifier = " Claim Settings")
   private String notBeforeTicketKey;
   private static final String NOT_BEFORE_TICKET_KEY_PROPERTY_NAME = "notBeforeTicketKey";
   @BooleanProperty(description = "If set to true, the incoming JWT must have a 'iat' claim.", optional = true, defaultValue = "true")
   @GroupProperty(groupIdentifier = " Claim Settings")
   private boolean issuedAtIsMandatory;
   @StringProperty(
      description = "The ticket key for the issued at claim. The JWT issued at claim ('iat') will be written to that field. Note that this field is optional. If not set, the 'iat' claim of the JWT won't be written into the ticket.",
      optional = true
   )
   @GroupProperty(groupIdentifier = " Claim Settings")
   private String issuedAtTicketKey;
   private static final String ISSUED_AT_TICKET_KEY_PROPERTY_NAME = "issuedAtTicketKey";
   @BooleanProperty(
      description = "If set to true, the incoming JWT must have an 'iss' claim. Note that this setting does not affect validation of the 'iss' claim. If this claim is present, it will always be validated against the configured list of allowed issuers.",
      optional = true,
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = " Claim Settings")
   private boolean issuerIsMandatory;
   @StringListProperty(
      description = "The allowed values for the issuer claim ('iss') in the JWT. If not configured, the issuer claim is not validated.",
      optional = true,
      exampleElements = {"Airlock IAM", "Example Issuer"}
   )
   @GroupProperty(groupIdentifier = " Claim Settings")
   private List<String> allowedIssuers;
   @StringProperty(
      description = "The ticket key for the issuer claim. The JWT issuer claim ('iss') will be written to that field. Note that this field is optional. If not set, the 'iss' claim of the JWT won't be written into the ticket.",
      optional = true
   )
   @GroupProperty(groupIdentifier = " Claim Settings")
   private String issuerTicketKey;
   private static final String ISSUER_TICKET_KEY_PROPERTY_NAME = "issuerTicketKey";
   @BooleanProperty(
      description = "If set to true, the incoming JWT must have a 'aud' claim. Note that this setting does not affect validation of the 'aud' claim. If this claim is present, it will always be validated against the configured expected audience entry.",
      optional = true,
      defaultValue = "false"
   )
   @GroupProperty(groupIdentifier = " Claim Settings")
   private boolean audienceIsMandatory;
   @StringListProperty(
      description = "A list of accepted entries for the audience claim ('aud') of the JWT. If not configured, the audience claim is not validated. To pass validation there must be at least one entry that is present in the audience claim and in the configured list.",
      optional = true,
      exampleElements = {"Airlock IAM", "Example Audience"}
   )
   @GroupProperty(groupIdentifier = " Claim Settings")
   private List<String> allowedAudiences;
   @StringProperty(
      description = "The ticket key for the audience claim. The JWT audience claim ('aud') will be written to that field. Note that this field is optional. If not set, the 'aud' claim of the JWT won't be written into the ticket.",
      optional = true
   )
   @GroupProperty(groupIdentifier = " Claim Settings")
   private String audienceTicketKey;
   private static final String AUDIENCE_TICKET_KEY_PROPERTY_NAME = "audienceTicketKey";
   @BooleanProperty(description = "If set to true, the incoming JWT must have a 'jti' claim.", optional = true, defaultValue = "false")
   @GroupProperty(groupIdentifier = " Claim Settings")
   private boolean jwtIdIsMandatory;
   @StringListProperty(
      description = "The claim names that should be interpreted as JSON in the received JWT. If such a claim does not exist in the JWT, it is not written into the ticket. The ticket key is always the claim name. If the JSON of this claim is invalid, an exception is thrown. <br><b>Note:</b> It is not allowed to specify registered claims here. Registered claims are always propagated as specified in RFC 7519. ",
      displayName = "Claims Stored As JSON",
      optional = true,
      unique = true,
      exampleElements = "orgs"
   )
   @GroupProperty(groupIdentifier = " Claim Settings")
   private List<String> claimsStoredAsJson;
   private static final String CLAIMS_STORED_AS_JSON_PROPERTY_NAME = "claimsStoredAsJson";
   @StringProperty(
      description = "The ticket key for the JWT ID claim. The JWT ID claim ('jti') will be written to that field. Note that this field is optional. If not set, the 'jti' claim of the JWT won't be written into the ticket. In order to use the 'jti' claim as unique ID for the ticket, you must specify 'uniqueId' here. This is mandatory for Ticket-SSO-Setups.",
      optional = true,
      exampleValues = "uniqueId"
   )
   @GroupProperty(groupIdentifier = " Claim Settings")
   private String jwtIdTicketKey;
   private static final String JWT_ID_TICKET_KEY_PROPERTY_NAME = "jwtIdTicketKey";
   @PluginListProperty(description = "List of additional claim validators.", optional = true)
   @GroupProperty(groupIdentifier = " Claim Settings")
   private List<ClaimsValidatorConfig> additionalClaimValidators;
   @PluginProperty(description = "The settings that are used for verifying the MAC or signature of the JWT.", optional = false)
   @GroupProperty(groupIdentifier = "Security Settings")
   private JwtSignatureVerifier signatureVerifier;
   @PluginProperty(
      description = "The settings that are used for decrypting the JWT. If no plugin is configured, the JWT must be unencrypted.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private JwtTicketDecrypter decrypter;
   private JwtTicketDecoderImpl ticketDecoder;

   public JwtTicketDecoderSettings(
      String usernameTicketKey,
      boolean notBeforeIsMandatory,
      int validNotBeforeSkew,
      String notBeforeTicketKey,
      boolean issuedAtIsMandatory,
      String issuedAtTicketKey,
      boolean issuerIsMandatory,
      List<String> allowedIssuers,
      String issuerTicketKey,
      boolean audienceIsMandatory,
      List<String> allowedAudiences,
      String audienceTicketKey,
      boolean jwtIdIsMandatory,
      List<String> claimsStoredAsJson,
      String jwtIdTicketKey,
      List<ClaimsValidatorConfig> additionalClaimValidators,
      JwtSignatureVerifier signatureVerifier,
      JwtTicketDecrypter decrypter
   ) {
      this.usernameTicketKey = usernameTicketKey;
      this.notBeforeIsMandatory = notBeforeIsMandatory;
      this.validNotBeforeSkew = validNotBeforeSkew;
      this.notBeforeTicketKey = notBeforeTicketKey;
      this.issuedAtIsMandatory = issuedAtIsMandatory;
      this.issuedAtTicketKey = issuedAtTicketKey;
      this.issuerIsMandatory = issuerIsMandatory;
      this.allowedIssuers = allowedIssuers;
      this.issuerTicketKey = issuerTicketKey;
      this.audienceIsMandatory = audienceIsMandatory;
      this.allowedAudiences = allowedAudiences;
      this.audienceTicketKey = audienceTicketKey;
      this.jwtIdIsMandatory = jwtIdIsMandatory;
      this.claimsStoredAsJson = claimsStoredAsJson;
      this.jwtIdTicketKey = jwtIdTicketKey;
      this.additionalClaimValidators = additionalClaimValidators;
      this.signatureVerifier = signatureVerifier;
      this.decrypter = decrypter;
   }

   @ConfigurationValidator(localOnly = true)
   public static void ensureTicketKeysAreNotConflicting(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment valenv) throws PropertyConstraintViolationException, PropertyTypeMismatchException {
      List<String> notEmptyTicketKeys = listWithNonEmptyItems(
         Arrays.asList(
            StringPropertyValueReader.effectiveStringConfigValue(pc, "usernameTicketKey", env, context),
            StringPropertyValueReader.effectiveStringConfigValue(pc, "notBeforeTicketKey", env, context),
            StringPropertyValueReader.effectiveStringConfigValue(pc, "issuedAtTicketKey", env, context),
            StringPropertyValueReader.effectiveStringConfigValue(pc, "issuerTicketKey", env, context),
            StringPropertyValueReader.effectiveStringConfigValue(pc, "audienceTicketKey", env, context),
            StringPropertyValueReader.effectiveStringConfigValue(pc, "jwtIdTicketKey", env, context)
         )
      );
      if (notEmptyTicketKeys.size() != new HashSet<>(notEmptyTicketKeys).size()) {
         throw new PropertyConstraintViolationException(
            "Ticket Keys", pc, env, context, "There are conflicting ticket key names. The keys must be pairwise unequal."
         );
      }
   }

   private static List<String> listWithNonEmptyItems(List<String> list) {
      return list.stream().filter(StringUtils::isNotEmpty).collect(Collectors.toList());
   }

   @ConfigurationValidator(localOnly = true)
   public static void ensureClaimsStoredAsJsonContainsNoRegisteredClaims(
      PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment valenv
   ) throws PropertyConstraintViolationException, PropertyTypeMismatchException {
      JwtTicketEncoderDecoderSettingsValidator.ensureListDoesNotContainRegisteredClaims(pc, env, context, "claimsStoredAsJson");
   }

   @AfterConfigActivation
   void afterConfigActivation() {
      this.ticketDecoder = JwtTicketDecoderImpl.builder(this.usernameTicketKey, this.signatureVerifier, new RealTimeProvider())
         .notBeforeIsMandatory(this.notBeforeIsMandatory)
         .issuedAtIsMandatory(this.issuedAtIsMandatory)
         .issuerIsMandatory(this.issuerIsMandatory)
         .audienceIsMandatory(this.audienceIsMandatory)
         .jwtIdIsMandatory(this.jwtIdIsMandatory)
         .additionalClaimValidators(this.additionalClaimValidators.stream().map(ClaimsValidatorConfig::getLogic).collect(Collectors.toList()))
         .validNotBeforeSkew(this.validNotBeforeSkew)
         .notBeforeTicketKey(Optional.ofNullable(this.notBeforeTicketKey))
         .issuedAtTicketKey(Optional.ofNullable(this.issuedAtTicketKey))
         .allowedIssuers(this.allowedIssuers)
         .issuerTicketKey(Optional.ofNullable(this.issuerTicketKey))
         .allowedAudiences(this.allowedAudiences)
         .audienceTicketKey(Optional.ofNullable(this.audienceTicketKey))
         .jwtIdTicketKey(Optional.ofNullable(this.jwtIdTicketKey))
         .decrypter(Optional.ofNullable(this.decrypter))
         .claimsStoredAsJson(this.claimsStoredAsJson)
         .build();
   }

   @Override
   public Ticket decode(String ticketString, @Nullable String urlEncodingScheme) throws TicketEncodingException {
      return Objects.requireNonNull(this.ticketDecoder, "AfterConfigActivation has not been called").decode(ticketString, urlEncodingScheme);
   }

   @Override
   public String getUsernameTicketKey() {
      return this.usernameTicketKey;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class JwtTicketDecoderSettingsBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private String usernameTicketKey;
      @SuppressFBWarnings(justification = "generated code")
      private boolean notBeforeIsMandatory;
      @SuppressFBWarnings(justification = "generated code")
      private int validNotBeforeSkew;
      @SuppressFBWarnings(justification = "generated code")
      private String notBeforeTicketKey;
      @SuppressFBWarnings(justification = "generated code")
      private boolean issuedAtIsMandatory;
      @SuppressFBWarnings(justification = "generated code")
      private String issuedAtTicketKey;
      @SuppressFBWarnings(justification = "generated code")
      private boolean issuerIsMandatory;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> allowedIssuers;
      @SuppressFBWarnings(justification = "generated code")
      private String issuerTicketKey;
      @SuppressFBWarnings(justification = "generated code")
      private boolean audienceIsMandatory;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> allowedAudiences;
      @SuppressFBWarnings(justification = "generated code")
      private String audienceTicketKey;
      @SuppressFBWarnings(justification = "generated code")
      private boolean jwtIdIsMandatory;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> claimsStoredAsJson;
      @SuppressFBWarnings(justification = "generated code")
      private String jwtIdTicketKey;
      @SuppressFBWarnings(justification = "generated code")
      private List<ClaimsValidatorConfig> additionalClaimValidators;
      @SuppressFBWarnings(justification = "generated code")
      private JwtSignatureVerifier signatureVerifier;
      @SuppressFBWarnings(justification = "generated code")
      private JwtTicketDecrypter decrypter;

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

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder usernameTicketKey(String usernameTicketKey) {
         this.usernameTicketKey = usernameTicketKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder notBeforeIsMandatory(boolean notBeforeIsMandatory) {
         this.notBeforeIsMandatory = notBeforeIsMandatory;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder validNotBeforeSkew(int validNotBeforeSkew) {
         this.validNotBeforeSkew = validNotBeforeSkew;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder notBeforeTicketKey(String notBeforeTicketKey) {
         this.notBeforeTicketKey = notBeforeTicketKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder issuedAtIsMandatory(boolean issuedAtIsMandatory) {
         this.issuedAtIsMandatory = issuedAtIsMandatory;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder issuedAtTicketKey(String issuedAtTicketKey) {
         this.issuedAtTicketKey = issuedAtTicketKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder issuerIsMandatory(boolean issuerIsMandatory) {
         this.issuerIsMandatory = issuerIsMandatory;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder allowedIssuers(List<String> allowedIssuers) {
         this.allowedIssuers = allowedIssuers;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder issuerTicketKey(String issuerTicketKey) {
         this.issuerTicketKey = issuerTicketKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder audienceIsMandatory(boolean audienceIsMandatory) {
         this.audienceIsMandatory = audienceIsMandatory;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder allowedAudiences(List<String> allowedAudiences) {
         this.allowedAudiences = allowedAudiences;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder audienceTicketKey(String audienceTicketKey) {
         this.audienceTicketKey = audienceTicketKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder jwtIdIsMandatory(boolean jwtIdIsMandatory) {
         this.jwtIdIsMandatory = jwtIdIsMandatory;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder claimsStoredAsJson(List<String> claimsStoredAsJson) {
         this.claimsStoredAsJson = claimsStoredAsJson;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder jwtIdTicketKey(String jwtIdTicketKey) {
         this.jwtIdTicketKey = jwtIdTicketKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder additionalClaimValidators(List<ClaimsValidatorConfig> additionalClaimValidators) {
         this.additionalClaimValidators = additionalClaimValidators;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder signatureVerifier(JwtSignatureVerifier signatureVerifier) {
         this.signatureVerifier = signatureVerifier;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder decrypter(JwtTicketDecrypter decrypter) {
         this.decrypter = decrypter;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDecoderSettings build() {
         return new JwtTicketDecoderSettings(
            this.usernameTicketKey,
            this.notBeforeIsMandatory,
            this.validNotBeforeSkew,
            this.notBeforeTicketKey,
            this.issuedAtIsMandatory,
            this.issuedAtTicketKey,
            this.issuerIsMandatory,
            this.allowedIssuers,
            this.issuerTicketKey,
            this.audienceIsMandatory,
            this.allowedAudiences,
            this.audienceTicketKey,
            this.jwtIdIsMandatory,
            this.claimsStoredAsJson,
            this.jwtIdTicketKey,
            this.additionalClaimValidators,
            this.signatureVerifier,
            this.decrypter
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "JwtTicketDecoderSettings.JwtTicketDecoderSettingsBuilder(usernameTicketKey="
            + this.usernameTicketKey
            + ", notBeforeIsMandatory="
            + this.notBeforeIsMandatory
            + ", validNotBeforeSkew="
            + this.validNotBeforeSkew
            + ", notBeforeTicketKey="
            + this.notBeforeTicketKey
            + ", issuedAtIsMandatory="
            + this.issuedAtIsMandatory
            + ", issuedAtTicketKey="
            + this.issuedAtTicketKey
            + ", issuerIsMandatory="
            + this.issuerIsMandatory
            + ", allowedIssuers="
            + this.allowedIssuers
            + ", issuerTicketKey="
            + this.issuerTicketKey
            + ", audienceIsMandatory="
            + this.audienceIsMandatory
            + ", allowedAudiences="
            + this.allowedAudiences
            + ", audienceTicketKey="
            + this.audienceTicketKey
            + ", jwtIdIsMandatory="
            + this.jwtIdIsMandatory
            + ", claimsStoredAsJson="
            + this.claimsStoredAsJson
            + ", jwtIdTicketKey="
            + this.jwtIdTicketKey
            + ", additionalClaimValidators="
            + this.additionalClaimValidators
            + ", signatureVerifier="
            + this.signatureVerifier
            + ", decrypter="
            + this.decrypter
            + ")";
      }
   }
}
