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¶
- If a
SystemTypeoverload is used, the publicGetSecureSetting/GetSecureSettingAsyncmethod onBaseEnvironmentClientcalls_systemHashService.Get(systemType)to resolve the hash before callingRetrieveSecureSetting[Async](BR-EC-21). The client registered throughRegisterEnvironmentClient(the non-secure registration) suppliesISystemHashServiceasnullby design; calling aSystemTypeoverload against that registration throwsNullReferenceExceptionhere, before the "operation not allowed" denial is ever reached. TheRegisterSecureEnvironmentClientregistration always supplies a concreteISystemHashService, so this failure path is specific to misusing aSystemTypeoverload against a base (non-secure) client registration. - On
BaseEnvironmentClient,RetrieveSecureSetting[Async]<T>unconditionally throwsEnvironmentClientException("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). - On
SecureEnvironmentClient,RetrieveSecureSetting[Async]<T>callsCheckForUpdates()(the same global refresh used by Load section), then callsSecureSettingCache.Get/GetAsyncwith key"{section}.{configKey}", the client's currentLastRefreshed, and a fetch delegate (BR-EC-23). - 3.1. An empty global
Settingscache (or a newer serverlastRefreshedtimestamp, probed at most every 60 seconds) triggersCheckForUpdates()'s full reload:LastRefreshedis set to the start-of-reload timestamp before the root section and every known section are reloaded,Settingsis cleared first, andSectionsis not cleared — so if an individual named section's reload throws partway through, that section stays recorded inSections(marked loaded) with no correspondingSettingsentries 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 thrownEnvironmentClientExceptionhere propagates out of the whole secure-setting call) does the secure lookup in step 3.2 proceed. - 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 fetchedTdirectly — with noConvert.ChangeTypepass, since it is already the delegate's native return type; anullresult 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 cachednull. A stale existing entry (item.FetchTime < LastRefreshed) instead re-invokes the fetch delegate and unconditionally overwrites that entry'sValue/FetchTime— including withnullif the delegate returnsnull— 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 ofitem.Valueand, only if non-null, converted viaConvert.ChangeType(item.Value, typeof(T))— anullitem.Valueat this point returnsdefault(T)instead. A fresh existing entry is likewise read back out ofitem.Valueand converted viaConvert.ChangeTypewithout re-invoking the delegate. So only the two existing-entry paths (stale-refreshed and fresh) convert throughitem.Value/Convert.ChangeType; the initial-miss path never does. -
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.
- 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). - 4.1.
HasSettingbuilds the dictionary key from the literalsection/configKey(noTrackNamesubstitution), then callsLoadSectionfor 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 inSections,LoadSectionacquiresSectionsSemaphore, re-checksSectionsunder 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 fromSections, retried next call), and a non-success/malformed response throwsEnvironmentClientException— which propagates out ofHasSetting, out of this fetch delegate, and out of the wholeGetSecureSetting[Async]call, rather than being treated as "key not found." Once loading completes without throwing,HasSettingreports whether the composed key is present in the non-secureSettingsdictionary. -
Origin: Step 4.1 is implemented in Has setting and executed on behalf of this operation.
- If that returns
false, the delegate returnsdefault(T)without making any HTTP call (BR-EC-24); see the data-level finding DL-1 in security and authorization. - If
HasSettingreturnedtrue, the delegate callsGET /v2/secureSetting/{ApplicationName}/{TrackName}/[{section}/]{configKey}/value?systemHashValue={hash}— the URL is scoped by the environment track, not the resolved host (BR-EC-22). GetValueFromResultmaps the HTTP outcome: a transport-levelresult.ErrorExceptionis rethrown as-is; a 404 throwsEnvironmentClientException("Environment API could not find a value for the secure setting: {section}.{configKey}"); any other non-success result throwsEnvironmentClientException("Failed to retrieve secure setting: {section}.{configKey}."); on success, the delegate returnsConvert.ChangeType(result.GetResult<string>(), typeof(T))(BR-EC-25). There is no supplied/injectableTypeConverter.- Once the cache call returns (whether a freshly fetched value or a value already cached from a prior call),
RetrieveSecureSettingchecks: if the resulting valueis nullor.Equals(default(T)), it throwsEnvironmentClientException("No crypto data found for requested secure setting: {section}.{configKey}")(BR-EC-26). This is the actual outcome when step 4'sHasSettingcheck wasfalse— 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.cssrc/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cssrc/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureSettingCache.cssrc/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs