Add currentRole to Health Summary Report at /health#3158
Add currentRole to Health Summary Report at /health#3158
Conversation
Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
|
@copilot revert your change of global.json |
Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Done. Reverted |
There was a problem hiding this comment.
Pull request overview
Adds visibility into the caller’s effective role by including a currentRole field in the comprehensive health report returned from /health, which helps validate authentication/authorization behavior during testing.
Changes:
- Added
CurrentRole("currentRole") toComprehensiveHealthCheckReport. - Populated
CurrentRoleinHealthCheckHelper.GetHealthCheckResponseAsyncbased on the incoming role header (fallbacking toanonymous).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/Service/HealthCheck/Model/ComprehensiveHealthCheckReport.cs | Extends the health report contract with a new currentRole property. |
| src/Service/HealthCheck/HealthCheckHelper.cs | Sets CurrentRole when generating the comprehensive health report. |
| ComprehensiveHealthCheckReport comprehensiveHealthCheckReport = new(); | ||
| UpdateVersionAndAppName(ref comprehensiveHealthCheckReport); | ||
| UpdateTimestampOfResponse(ref comprehensiveHealthCheckReport); | ||
| comprehensiveHealthCheckReport.CurrentRole = string.IsNullOrEmpty(_incomingRoleHeader) ? AuthorizationResolver.ROLE_ANONYMOUS : _incomingRoleHeader; |
There was a problem hiding this comment.
HealthCheckHelper is registered as a singleton (see Startup.cs) but CurrentRole is sourced from the mutable instance field _incomingRoleHeader, which is set per-request in StoreIncomingRoleHeader. Under concurrent requests this can race and cause the /health response to report the wrong role (and potentially leak another request’s role). Refactor to avoid per-request state on a singleton (e.g., read the role into a local in the response writer and pass it into helper methods, or store it in HttpContext.Items, or change lifetimes so the helper is scoped/transient).
There was a problem hiding this comment.
Fixed in 4931f45. Removed the mutable _incomingRoleHeader and _incomingRoleToken instance fields entirely from the singleton. StoreIncomingRoleHeader was replaced by ReadRoleHeaders(HttpContext) which returns a (roleHeader, roleToken) tuple — the caller in ComprehensiveHealthReportResponseWriter holds these as locals and passes them into GetHealthCheckResponseAsync, GetCurrentRole, and IsUserAllowedToAccessHealthCheck. The values flow down through the private call chain (UpdateHealthCheckDetailsAsync → UpdateEntityHealthCheckResultsAsync → PopulateEntityHealthAsync → ExecuteRestEntityQueryAsync/ExecuteGraphQlEntityQueryAsync) as parameters, so concurrent requests never share state.
|
@copilot The logic is: if _incomingRoleHeader is null or empty, default to "anonymous". It will never show "authenticated" on its own — it only shows the literal value of the incoming X-MS-API-ROLE header. The _incomingRoleHeader field is initialized to string.Empty and is only updated when the X-MS-API-ROLE header is explicitly present in the request. The problem is the last case. An authenticated user who provides a valid bearer token but does not include the X-MS-API-ROLE header will be reported as "anonymous" rather than "authenticated". The PR doesn't inspect the authentication state of the request (e.g., httpContext.User.Identity.IsAuthenticated) — it relies solely on the role header. To properly reflect "authenticated", the fallback logic should also check whether the user is authenticated. Something like this: comprehensiveHealthCheckReport.CurrentRole = !string.IsNullOrEmpty(_incomingRoleHeader)
? _incomingRoleHeader
: !string.IsNullOrEmpty(_incomingRoleToken)
? AuthorizationResolver.ROLE_AUTHENTICATED
: AuthorizationResolver.ROLE_ANONYMOUS; |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Done in commit |
… of storing on instance Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Why make this change?
Adds
currentRoleto the/healthendpoint response to surface the effective role of the requesting user, enabling easier authentication testing without needing external tooling to inspect request headers.What is this change?
ComprehensiveHealthCheckReport: NewcurrentRolestring property (JSON:"currentRole") inserted aftertimestampin the report header.HealthCheckHelper.ReadRoleHeaders(HttpContext): Replaces the formerStoreIncomingRoleHeadermethod. Instead of writing to mutable instance fields, it returns a(roleHeader, roleToken)tuple that the caller holds as request-local values, eliminating a singleton race condition under concurrent requests.HealthCheckHelper.GetCurrentRole(roleHeader, roleToken): Determines the effective role using a three-way fallback: explicitX-MS-API-ROLEheader →"authenticated"(if a bearer token is present) →"anonymous". This correctly handles authenticated users who provide a bearer token without an explicit role header.HealthCheckHelper.IsUserAllowedToAccessHealthCheck/GetHealthCheckResponseAsync: Updated to acceptroleHeader/roleTokenas explicit parameters; the values flow down the entire private call chain (UpdateHealthCheckDetailsAsync→UpdateEntityHealthCheckResultsAsync→PopulateEntityHealthAsync→ExecuteRestEntityQueryAsync/ExecuteGraphQlEntityQueryAsync) so no per-request state is stored on the singleton.ComprehensiveHealthReportResponseWriter: Reads role headers once per request into locals, passes them through all downstream calls, and stampscurrentRoleper-request after cache retrieval (using a non-destructivewithexpression) so cached responses never leak one caller's role to another request. The cache now stores theComprehensiveHealthCheckReportobject (withoutcurrentRole) rather than a serialized string.Example response shape:
{ "status": "Healthy", "version": "1.x.x", "app-name": "dab_oss_1.x.x", "timestamp": "2026-02-24T22:28:30Z", "currentRole": "authenticated", "configuration": { ... }, "checks": [ ... ] }How was this tested?
Sample Request(s)
Response includes
"currentRole": "myrole".Response includes
"currentRole": "authenticated"(bearer token present, no explicit role header).Response includes
"currentRole": "anonymous"(no role header, no bearer token).Original prompt
🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.