Feature/current user role position - #102
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>
Reviewer's GuideThis PR extends ambient current-user propagation with nullable position and primary-role support, standardizes structured user changes, and adds configurable header capture, restoration, and forwarding for HTTP, background, and downstream-service scenarios. It documents and tests the complete flow, and adds a local NuGet packaging script to support consumer validation against unreleased framework builds. Sequence diagram for current-user resolution and ambient accesssequenceDiagram
participant Request as HTTP_Request
participant Resolver as HeaderCurrentUserResolver
participant Middleware as AetherCurrentUserMiddleware
participant CurrentUser as ICurrentUser
participant Service as ApplicationService
Request->>Resolver: ResolveAsync()
Resolver->>Request: GetClaimHeader()
Resolver->>Resolver: ParseRolesFromHeader()
Resolver-->>Middleware: BasicUserInfo
Middleware->>CurrentUser: Change(BasicUserInfo)
Middleware->>Service: next(context)
Service->>CurrentUser: IsInRole() / Position
Middleware->>CurrentUser: Dispose()
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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/CurrentUser.cs" line_range="72" />
<code_context>
{
+ Check.NotNull(user, nameof(user));
+
var parentScope = currentUserAccessor.Current;
- currentUserAccessor.Current = new BasicUserInfo(id, userName, name, surname, roles, actorUserId, actorUserName, consentId);
+ currentUserAccessor.Current = user;
return new DisposeAction(() => { currentUserAccessor.Current = parentScope; });
}
</code_context>
<issue_to_address>
**issue (bug_risk):** Change(BasicUserInfo) stores the caller-supplied mutable BasicUserInfo instance directly, so changing its properties or Roles array after entering the scope changes the ambient caller identity while the scope is active. The positional Change overload previously created a separate BasicUserInfo object, so this overload introduces observable aliasing.
**Triggers:** When a caller reuses or mutates the BasicUserInfo instance after passing it to Change.
**Suggested fix:** Copy the BasicUserInfo fields, and preferably clone the Roles array, before assigning the copy to currentUserAccessor.Current.
```suggestion
currentUserAccessor.Current = new BasicUserInfo(
user.Id,
user.UserName,
user.Name,
user.Surname,
user.Roles?.ToArray(),
user.ActorUserId,
user.ActorUserName,
user.ConsentId,
user.Position);
```
</issue_to_address>Sourcery assessment
Needs a human reviewer. 1 finding to address first, and this changes the identity data resolved from request headers, including role parsing, and adds forwarding of that identity to downstream services. If a role or caller field is interpreted incorrectly, downstream authorization or audit actions could occur under the wrong identity; reverting stops future propagation but cannot undo decisions already made.
Blocking findings: framework/src/BBT.Aether.Core/BBT/Aether/Users/CurrentUser.cs:72
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
|
||
| var parentScope = currentUserAccessor.Current; | ||
| currentUserAccessor.Current = new BasicUserInfo(id, userName, name, surname, roles, actorUserId, actorUserName, consentId); | ||
| currentUserAccessor.Current = user; |
There was a problem hiding this comment.
issue (bug_risk): Change(BasicUserInfo) stores the caller-supplied mutable BasicUserInfo instance directly, so changing its properties or Roles array after entering the scope changes the ambient caller identity while the scope is active. The positional Change overload previously created a separate BasicUserInfo object, so this overload introduces observable aliasing.
Triggers: When a caller reuses or mutates the BasicUserInfo instance after passing it to Change.
Suggested fix: Copy the BasicUserInfo fields, and preferably clone the Roles array, before assigning the copy to currentUserAccessor.Current.
| currentUserAccessor.Current = user; | |
| currentUserAccessor.Current = new BasicUserInfo( | |
| user.Id, | |
| user.UserName, | |
| user.Name, | |
| user.Surname, | |
| user.Roles?.ToArray(), | |
| user.ActorUserId, | |
| user.ActorUserName, | |
| user.ConsentId, | |
| user.Position); |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 0 |
| 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
Extend current-user identity handling with role and position support plus reusable claim-header propagation.
New Features:
Enhancements:
Build:
Documentation:
Tests: