Skip to content

Get secure setting

Field Value
Owner GetSecureSetting/GetSecureSettingAsync (16 public overloads total) on BaseEnvironmentClient; RetrieveSecureSetting[Async]<T> (always denies on BaseEnvironmentClient, retrieves on SecureEnvironmentClient)
Trigger Consumer call
Inputs configKey, optional section, and either a caller-supplied systemHashValue or a SystemType resolved through the injected ISystemHashService
Outputs The decrypted/plain secure setting value (converted to T), or an EnvironmentClientException/NullReferenceException

Steps

  1. If a SystemType overload is used, the public GetSecureSetting/GetSecureSettingAsync method on BaseEnvironmentClient calls _systemHashService.Get(systemType) to resolve the hash before calling RetrieveSecureSetting[Async] (BR-EC-21). The client registered through RegisterEnvironmentClient (the non-secure registration) supplies ISystemHashService as null by design; calling a SystemType overload against that registration throws NullReferenceException here, before the "operation not allowed" denial is ever reached. The RegisterSecureEnvironmentClient registration always supplies a concrete ISystemHashService, so this failure path is specific to misusing a SystemType overload against a base (non-secure) client registration.
  2. On BaseEnvironmentClient, RetrieveSecureSetting[Async]<T> unconditionally throws EnvironmentClientException("Operation not allowed! This application is restricted from retrieving secure settings.") — no cache check, no HTTP call (BR-EC-20; see also security and authorization, EP-5).
  3. On SecureEnvironmentClient, RetrieveSecureSetting[Async]<T> calls CheckForUpdates() (the same global refresh used by Load section), then calls SecureSettingCache.Get/GetAsync with key "{section}.{configKey}", the client's current LastRefreshed, and a fetch delegate (BR-EC-23).
  4. 3.1. An empty global Settings cache (or a newer server lastRefreshed timestamp, probed at most every 60 seconds) triggers CheckForUpdates()'s full reload: LastRefreshed is set to the start-of-reload timestamp before the root section and every known section are reloaded, Settings is cleared first, and Sections is not cleared — so if an individual named section's reload throws partway through, that section stays recorded in Sections (marked loaded) with no corresponding Settings entries until the next successful global refresh for it (see Load section step 7 and Refresh check and cache reload step 5). Only after this non-secure reload (successful or not, since a thrown EnvironmentClientException here propagates out of the whole secure-setting call) does the secure lookup in step 3.2 proceed.
  5. 3.2. Within SecureSettingCache, an initial cache miss (no entry yet for this key) invokes the fetch delegate and, if the fetched value is non-null, both caches it and returns that fetched T directly — with no Convert.ChangeType pass, since it is already the delegate's native return type; a null result on an initial miss is not cached at all, so the very next call for the same key re-invokes the fetch delegate rather than returning a cached null. A stale existing entry (item.FetchTime < LastRefreshed) instead re-invokes the fetch delegate and unconditionally overwrites that entry's Value/FetchTime — including with null if the delegate returns null — so a secure setting that stops resolving is actively replaced, not left at its old cached value; its result (fresh or just-overwritten) is read back out of item.Value and, only if non-null, converted via Convert.ChangeType(item.Value, typeof(T)) — a null item.Value at this point returns default(T) instead. A fresh existing entry is likewise read back out of item.Value and converted via Convert.ChangeType without re-invoking the delegate. So only the two existing-entry paths (stale-refreshed and fresh) convert through item.Value/Convert.ChangeType; the initial-miss path never does.
  6. Origin: Steps 3.1–3.2 are implemented in Refresh check and cache reload and Secure setting cache, and executed on behalf of this operation.

  7. The fetch delegate (invoked only when the secure-setting cache has no entry, or a stale one, for this key — per step 3.2) first calls HasSetting(configKey, section) against the non-secure settings cache (not the secure cache).
  8. 4.1. HasSetting builds the dictionary key from the literal section/configKey (no TrackName substitution), then calls LoadSection for that literal section — which unconditionally runs the global refresh check (CheckForUpdates()) first, before testing whether the section itself is already loaded. If the section is not yet in Sections, LoadSection acquires SectionsSemaphore, re-checks Sections under the lock, and fetches the section from the Environment API: a successful fetch with 1+ settings marks it loaded, a successful fetch with zero settings is self-healing (removed from Sections, retried next call), and a non-success/malformed response throws EnvironmentClientException — which propagates out of HasSetting, out of this fetch delegate, and out of the whole GetSecureSetting[Async] call, rather than being treated as "key not found." Once loading completes without throwing, HasSetting reports whether the composed key is present in the non-secure Settings dictionary.
  9. Origin: Step 4.1 is implemented in Has setting and executed on behalf of this operation.

  10. If that returns false, the delegate returns default(T) without making any HTTP call (BR-EC-24); see the data-level finding DL-1 in security and authorization.
  11. If HasSetting returned true, the delegate calls GET /v2/secureSetting/{ApplicationName}/{TrackName}/[{section}/]{configKey}/value?systemHashValue={hash} — the URL is scoped by the environment track, not the resolved host (BR-EC-22).
  12. GetValueFromResult maps the HTTP outcome: a transport-level result.ErrorException is rethrown as-is; a 404 throws EnvironmentClientException("Environment API could not find a value for the secure setting: {section}.{configKey}"); any other non-success result throws EnvironmentClientException("Failed to retrieve secure setting: {section}.{configKey}."); on success, the delegate returns Convert.ChangeType(result.GetResult<string>(), typeof(T)) (BR-EC-25). There is no supplied/injectable TypeConverter.
  13. Once the cache call returns (whether a freshly fetched value or a value already cached from a prior call), RetrieveSecureSetting checks: if the resulting value is null or .Equals(default(T)), it throws EnvironmentClientException("No crypto data found for requested secure setting: {section}.{configKey}") (BR-EC-26). This is the actual outcome when step 4's HasSetting check was false — there is no "No crypto data found" value returned to the caller, it is always an exception.

Process flow

flowchart TD
    A["GetSecureSetting configKey, section, hash/SystemType"] --> B{"SystemType overload and<br>ISystemHashService null?"}
    B -- Yes --> C[NullReferenceException]
    B -- No --> D{BaseEnvironmentClient?}
    D -- Yes --> E["EnvironmentClientException: Operation not allowed"]
    D -- No / SecureEnvironmentClient --> F[Refresh check and cache reload]
    F --> G[Secure setting cache Get]
    G -->|cached and fresh| H["Convert.ChangeType(cached value, T)"]
    G -->|"no entry: initial miss"| FD[Has setting non-secure check]
    G -->|"cached but stale:<br>FetchTime less than LastRefreshed"| FD
    FD -->|false| NV["val = default(T) / null"]
    FD -->|true| GV["GET /v2/secureSetting/.../track/.../value<br>?systemHashValue=hash"]
    GV -->|transport exception| L[Rethrow as-is]
    GV -->|404| M["EnvironmentClientException: not found"]
    GV -->|other failure| N["EnvironmentClientException: failed to retrieve"]
    GV -->|success| CV["Convert.ChangeType(result, T)"]
    NV --> SD{"Initial miss or stale?"}
    CV --> SD
    SD -->|"initial miss, val not null"| INS["TryAdd new cache entry"]
    SD -->|"initial miss, val null"| SKIP["Not cached; retry next call"]
    SD -->|stale| OW["Overwrite entry Value/FetchTime<br>(even if null)"]
    INS --> P
    SKIP --> P
    OW --> P
    H --> P
    P{"Result null or default(T)?"}
    P -- Yes --> Q["EnvironmentClientException: No crypto data found"]
    P -- No --> R[Return value]

Sources

  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureSettingCache.cs
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs