public ApplicationConfiguration()

in sources/src/main/java/com/google/solutions/jitaccess/web/ApplicationConfiguration.java [170:287]


  public ApplicationConfiguration(@NotNull Map<String, String> settingsData) {
    super(settingsData);

    //
    // Read required settings.
    //
    // NB. CustomerIDs, primary domains, organization IDs can be translated
    //     into each other by using the organization.search API, but this
    //     API requires the `resourcemanager.organizations.get` permission.
    //
    //     Service accounts don't have this permission by default, and granting
    //     the corresponding `Organization Viewer` role requires changing the
    //     organization's IAM policy, which is a lot to ask for.
    //
    //     Therefore, we require the configuration to contain all 3 pieces of
    //     information, even if it's somewhat redundant.
    //
    this.customerId = readStringSetting(
      "CUSTOMER_ID",
      "RESOURCE_CUSTOMER_ID") // Name used in 1.x
      .map(CustomerId::new)
      .orElseThrow(() -> new IllegalStateException(
        "The environment variable 'CUSTOMER_ID' must contain the customer ID " +
          "of a Cloud Identity or Workspace account, see" +
          "https://support.google.com/a/answer/10070793"));
    this.primaryDomain = readStringSetting("PRIMARY_DOMAIN")
      .map(d -> new Domain(d, Domain.Type.PRIMARY))
      .orElseThrow(() -> new IllegalStateException(
        "The environment variable 'PRIMARY_DOMAIN' must contain the primary domain name " +
          "of a Cloud Identity/Workspace account, see https://support.google.com/a/answer/182080"));
    this.organizationId = readStringSetting("ORGANIZATION_ID")
      .map(OrganizationId::new)
      .orElseThrow(() -> new IllegalStateException(
        "The environment variable 'ORGANIZATION_ID' must contain the organization ID of " +
          "a Google Cloud organization, see " +
          "https://cloud.google.com/resource-manager/docs/creating-managing-organization"));

    //
    // Read optional settings.
    //
    this.groupsDomain = readStringSetting("GROUPS_DOMAIN")
      .map(d -> new Domain(d, d.equalsIgnoreCase(this.primaryDomain.name())
        ? Domain.Type.PRIMARY
        : Domain.Type.SECONDARY))
      .orElse(this.primaryDomain);
    this.proposalTimeout = readDurationSetting(
      ChronoUnit.MINUTES,
      "APPROVAL_TIMEOUT",
      "ACTIVATION_REQUEST_TIMEOUT") // Name used in 1.x
      .orElse(Duration.ofHours(1));
    this.environments = readStringSetting("ENVIRONMENTS").stream()
      .flatMap(s -> Arrays.stream(s.split(",")))
      .map(String::trim)
      .filter(s -> !s.isBlank())
      .toList();
    this.environmentCacheTimeout = readDurationSetting(
      ChronoUnit.SECONDS,
      "RESOURCE_CACHE_TIMEOUT")
      .orElse(Duration.ofMinutes(5));

    //
    // SMTP settings.
    //
    this.smtpAddressMapping = readStringSetting("SMTP_ADDRESS_MAPPING");
    this.smtpHost = readStringSetting("SMTP_HOST")
      .orElse("smtp.gmail.com");
    this.smtpPort = readSetting(Integer::parseInt, "SMTP_PORT")
      .orElse(587);
    this.smtpEnableStartTls = readSetting(Boolean::parseBoolean, "SMTP_ENABLE_STARTTLS")
      .orElse(true);
    this.smtpSenderName = readStringSetting("SMTP_SENDER_NAME")
      .orElse("JIT Groups");
    this.smtpSenderAddress = readStringSetting("SMTP_SENDER_ADDRESS");
    this.smtpUsername = readStringSetting("SMTP_USERNAME");
    this.smtpPassword = readStringSetting("SMTP_PASSWORD");
    this.smtpSecret = readStringSetting("SMTP_SECRET");
    this.smtpExtraOptions = readStringSetting("SMTP_OPTIONS");

    //
    // Notification settings.
    //
    this.notificationTimeZone = readSetting(ZoneId::of, "NOTIFICATION_TIMEZONE")
      .orElse(ZoneOffset.UTC);

    //
    // Backend service id (Cloud Run only).
    //
    this.backendServiceId = readStringSetting("IAP_BACKEND_SERVICE_ID");
    this.verifyIapAudience = readSetting(Boolean::parseBoolean, "IAP_VERIFY_AUDIENCE")
      .orElse(true);

    //
    // Backend settings.
    //
    this.backendConnectTimeout = readDurationSetting(ChronoUnit.SECONDS, "BACKEND_CONNECT_TIMEOUT")
      .orElse(Duration.ofSeconds(5));
    this.backendReadTimeout = readDurationSetting(ChronoUnit.SECONDS, "BACKEND_READ_TIMEOUT")
      .orElse(Duration.ofSeconds(20));
    this.backendWriteTimeout = readDurationSetting(ChronoUnit.SECONDS, "BACKEND_WRITE_TIMEOUT")
      .orElse(Duration.ofSeconds(5));

    //
    // Legacy settings.
    //
    this.legacyCatalog = readStringSetting("RESOURCE_CATALOG").orElse("AssetInventory");
    this.legacyScope = readStringSetting("RESOURCE_SCOPE");
    this.legacyActivationTimeout = readDurationSetting(
      ChronoUnit.MINUTES,
      "ACTIVATION_TIMEOUT",
      "ELEVATION_DURATION")
      .orElse(Duration.ofHours(2));
    this.legacyJustificationPattern = readStringSetting("JUSTIFICATION_PATTERN")
      .orElse(".*");
    this.legacyJustificationHint = readStringSetting("JUSTIFICATION_HINT")
      .orElse("Bug or case number");
    this.legacyProjectsQuery = readStringSetting("AVAILABLE_PROJECTS_QUERY")
      .orElse("state:ACTIVE");
  }