v1.0.39 - #103
Conversation
Add two members to ICurrentUser. Position carries the caller's
organizational posting from the `position` claim header. Role is the
first entry of Roles, for legacy systems that send a single `role`
claim where consumers otherwise write Roles?.FirstOrDefault() at
every call site.
Move the header/claim dictionary conversion into the SDK as
CurrentUserHeaderExtensions (Core): ChangeFromHeaders restores a
captured user in scopes with no ambient HTTP request — background
jobs, message consumers, resumed workflows — and ToForwardHeaders
turns the current user back into claim headers for outbound calls.
Both work over IReadOnlyDictionary, so Core carries no ASP.NET
dependency. HttpRequestCurrentUserExtensions (AspNetCore) is the
HTTP-side counterpart that captures those headers off a request.
Role parsing now accepts space-separated values and trims entries,
via the single ParseRolesFromHeader used by both the resolver and
ChangeFromHeaders. HeaderCurrentUserResolver previously did a bare
Split(','), which missed space-separated legacy values and turned an
empty `role` header into a one-element array holding "".
Change gains a BasicUserInfo overload, now the single code path; the
positional overload delegates to it, so call sites like
AetherCurrentUserMiddleware no longer need revisiting when the user
model grows a field. Position stays null when absent, unlike the
older fields that default to empty string, so consumers can fall
through with `?? fallback`.
BREAKING CHANGE: ICurrentUser gains Role, Position and a
Change(BasicUserInfo) overload. Types outside the framework that
implement ICurrentUser must add these members.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sition Feature/current user role position
Reviewer's GuideThis release expands ambient caller identity with position and primary-role support, adds configurable header capture/restoration/forwarding for HTTP and background workflows, documents the integration model, provides a local NuGet packaging workflow, and backs the behavior with comprehensive tests. Sequence diagram for current user resolution from HTTP headerssequenceDiagram
participant Request as HTTP_Request
participant Resolver as HeaderCurrentUserResolver
participant Middleware as AetherCurrentUserMiddleware
participant User as ICurrentUser
participant App as Application_Service
Request->>Resolver: GetCurrentUserAsync()
Resolver->>Request: GetClaimHeader()
Resolver->>Resolver: ParseRolesFromHeader()
Resolver-->>Middleware: BasicUserInfo
Middleware->>User: Change(BasicUserInfo)
Middleware->>App: next(context)
App->>User: IsInRole() / Position
App-->>Middleware: response
Middleware->>User: Dispose()
Sequence diagram for current user capture and background restorationsequenceDiagram
participant Request as HTTP_Request
participant Job as Background_Job
participant User as ICurrentUser
participant Service as Report_Service
Request->>Request: GetCurrentUserHeaders()
Request->>Job: EnqueueAsync(payload with ClaimHeaders)
Job->>User: ChangeFromHeaders(payload.ClaimHeaders)
User->>User: ParseRolesFromHeader()
Job->>Service: GenerateAsync(reportId)
Service->>User: UserName / Role / Position
Job->>User: Dispose()
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="framework/src/BBT.Aether.Core/BBT/Aether/Users/CurrentUserHeaderExtensions.cs" line_range="41-50" />
<code_context>
+ }
+
+ return currentUser.Change(new BasicUserInfo(
+ headers.GetValueOrDefault(AetherClaimTypes.UserId),
+ headers.GetValueOrDefault(AetherClaimTypes.UserName),
+ headers.GetValueOrDefault(AetherClaimTypes.Name),
+ headers.GetValueOrDefault(AetherClaimTypes.SurName),
+ ParseRolesFromHeader(headers.GetValueOrDefault(AetherClaimTypes.Role)),
+ headers.GetValueOrDefault(AetherClaimTypes.ActorUserId),
+ headers.GetValueOrDefault(AetherClaimTypes.ActorSub),
+ headers.GetValueOrDefault(AetherClaimTypes.ConsentId),
+ headers.GetValueOrDefault(AetherClaimTypes.Position)));
+ }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** ChangeFromHeaders performs case-sensitive lookups against IReadOnlyDictionary, so a valid claim-header dictionary whose keys use different casing silently restores a user with missing identity, roles, and position fields.
**Triggers:** When callers provide a regular case-sensitive Dictionary with HTTP-style header names in casing different from AetherClaimTypes.
**Suggested fix:** Look up headers with an ordinal case-insensitive comparer or normalize the input dictionary before reading it.
```suggestion
string? GetHeader(string key) =>
headers.FirstOrDefault(pair => StringComparer.OrdinalIgnoreCase.Equals(pair.Key, key)).Value;
return currentUser.Change(new BasicUserInfo(
GetHeader(AetherClaimTypes.UserId),
GetHeader(AetherClaimTypes.UserName),
GetHeader(AetherClaimTypes.Name),
GetHeader(AetherClaimTypes.SurName),
ParseRolesFromHeader(GetHeader(AetherClaimTypes.Role)),
GetHeader(AetherClaimTypes.ActorUserId),
GetHeader(AetherClaimTypes.ActorSub),
GetHeader(AetherClaimTypes.ConsentId),
GetHeader(AetherClaimTypes.Position)));
```
</issue_to_address>Sourcery assessment
Needs a human reviewer. 1 finding to address first, and this introduces and propagates an ambient caller identity from HTTP headers, which downstream authorization, auditing, and service calls may trust; accepting malformed or untrusted headers could grant access or attribute actions to the wrong user. Reverting stops future requests, but any access, action, or audit record produced under an incorrect identity would require separate investigation and repair.
Blocking findings: framework/src/BBT.Aether.Core/BBT/Aether/Users/CurrentUserHeaderExtensions.cs:50
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| return currentUser.Change(new BasicUserInfo( | ||
| headers.GetValueOrDefault(AetherClaimTypes.UserId), | ||
| headers.GetValueOrDefault(AetherClaimTypes.UserName), | ||
| headers.GetValueOrDefault(AetherClaimTypes.Name), | ||
| headers.GetValueOrDefault(AetherClaimTypes.SurName), | ||
| ParseRolesFromHeader(headers.GetValueOrDefault(AetherClaimTypes.Role)), | ||
| headers.GetValueOrDefault(AetherClaimTypes.ActorUserId), | ||
| headers.GetValueOrDefault(AetherClaimTypes.ActorSub), | ||
| headers.GetValueOrDefault(AetherClaimTypes.ConsentId), | ||
| headers.GetValueOrDefault(AetherClaimTypes.Position))); |
There was a problem hiding this comment.
issue (bug_risk): ChangeFromHeaders performs case-sensitive lookups against IReadOnlyDictionary, so a valid claim-header dictionary whose keys use different casing silently restores a user with missing identity, roles, and position fields.
Triggers: When callers provide a regular case-sensitive Dictionary with HTTP-style header names in casing different from AetherClaimTypes.
Suggested fix: Look up headers with an ordinal case-insensitive comparer or normalize the input dictionary before reading it.
| return currentUser.Change(new BasicUserInfo( | |
| headers.GetValueOrDefault(AetherClaimTypes.UserId), | |
| headers.GetValueOrDefault(AetherClaimTypes.UserName), | |
| headers.GetValueOrDefault(AetherClaimTypes.Name), | |
| headers.GetValueOrDefault(AetherClaimTypes.SurName), | |
| ParseRolesFromHeader(headers.GetValueOrDefault(AetherClaimTypes.Role)), | |
| headers.GetValueOrDefault(AetherClaimTypes.ActorUserId), | |
| headers.GetValueOrDefault(AetherClaimTypes.ActorSub), | |
| headers.GetValueOrDefault(AetherClaimTypes.ConsentId), | |
| headers.GetValueOrDefault(AetherClaimTypes.Position))); | |
| string? GetHeader(string key) => | |
| headers.FirstOrDefault(pair => StringComparer.OrdinalIgnoreCase.Equals(pair.Key, key)).Value; | |
| return currentUser.Change(new BasicUserInfo( | |
| GetHeader(AetherClaimTypes.UserId), | |
| GetHeader(AetherClaimTypes.UserName), | |
| GetHeader(AetherClaimTypes.Name), | |
| GetHeader(AetherClaimTypes.SurName), | |
| ParseRolesFromHeader(GetHeader(AetherClaimTypes.Role)), | |
| GetHeader(AetherClaimTypes.ActorUserId), | |
| GetHeader(AetherClaimTypes.ActorSub), | |
| GetHeader(AetherClaimTypes.ConsentId), | |
| GetHeader(AetherClaimTypes.Position))); |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Duplication | 12 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
|



Summary by Sourcery
Expand current-user context handling and provide tooling for consuming unreleased framework packages locally.
New Features:
Enhancements:
Build:
Documentation:
Tests: