Skip to content

Add currentRole to Health Summary Report at /health#3158

Open
Copilot wants to merge 7 commits intomainfrom
copilot/add-current-role-to-health-summary
Open

Add currentRole to Health Summary Report at /health#3158
Copilot wants to merge 7 commits intomainfrom
copilot/add-current-role-to-health-summary

Conversation

Copy link
Contributor

Copilot AI commented Feb 24, 2026

Why make this change?

Adds currentRole to the /health endpoint 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: New currentRole string property (JSON: "currentRole") inserted after timestamp in the report header.
  • HealthCheckHelper.ReadRoleHeaders(HttpContext): Replaces the former StoreIncomingRoleHeader method. 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: explicit X-MS-API-ROLE header → "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 accept roleHeader/roleToken as explicit parameters; the values flow down the entire private call chain (UpdateHealthCheckDetailsAsyncUpdateEntityHealthCheckResultsAsyncPopulateEntityHealthAsyncExecuteRestEntityQueryAsync / 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 stamps currentRole per-request after cache retrieval (using a non-destructive with expression) so cached responses never leak one caller's role to another request. The cache now stores the ComprehensiveHealthCheckReport object (without currentRole) 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?

  • Integration Tests
  • Unit Tests

Sample Request(s)

GET /health
X-MS-API-ROLE: myrole

Response includes "currentRole": "myrole".

GET /health
Authorization: ******

Response includes "currentRole": "authenticated" (bearer token present, no explicit role header).

GET /health

Response includes "currentRole": "anonymous" (no role header, no bearer token).

Original prompt

This section details on the original issue you should resolve

<issue_title>[Enh]: Add to Health Summary</issue_title>
<issue_description>## What?

  1. Add to the header part of the Health Summary Report returned at /health a scalar property "currentRole" which returns the current role "anonymous", "authenticated", etc.

Why?

To help with authentication testing.</issue_description>

<agent_instructions>with as little new code as possible</agent_instructions>

Comments on the Issue (you are @copilot in this section)


🔒 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.

Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Copilot AI changed the title [WIP] Add currentRole property to health summary report Add currentRole to Health Summary Report at /health Feb 24, 2026
Copilot AI requested a review from JerryNixon February 24, 2026 22:35
@JerryNixon
Copy link
Contributor

@copilot revert your change of global.json

@JerryNixon JerryNixon marked this pull request as ready for review February 26, 2026 04:53
Copilot AI review requested due to automatic review settings February 26, 2026 04:53
Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 26, 2026

@copilot revert your change of global.json

Done. Reverted global.json back to 8.0.418 in commit 158097b.

@JerryNixon JerryNixon assigned Copilot and unassigned JerryNixon and Copilot Feb 26, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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") to ComprehensiveHealthCheckReport.
  • Populated CurrentRole in HealthCheckHelper.GetHealthCheckResponseAsync based on the incoming role header (fallbacking to anonymous).

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;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (UpdateHealthCheckDetailsAsyncUpdateEntityHealthCheckResultsAsyncPopulateEntityHealthAsyncExecuteRestEntityQueryAsync/ExecuteGraphQlEntityQueryAsync) as parameters, so concurrent requests never share state.

@JerryNixon
Copy link
Contributor

@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>
Copy link
Contributor Author

Copilot AI commented Feb 26, 2026

@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 ...

Done in commit 76db65c. Added GetCurrentRole() to HealthCheckHelper with the corrected three-way fallback: explicit role header → bearer token present → anonymous. Also moved CurrentRole assignment out of the cacheable path so each request always sees its own effective role regardless of cache state.

… of storing on instance

Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enh]: Add to Health Summary

3 participants