Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,24 @@ public static AuthorizationOptions GetAuthorizationOptions(
this ISchemaBuilder builder)
=> builder.Features.GetOrSet<AuthorizationOptions>();

// Legacy well-known ContextData key for UserState, removed when UserState moved to the typed
// Features collection (PRs #8267/#8329). Consumers that bypass the request interceptors
// (notably the Strawberry Shake in-memory client) still inject a ready-made UserState under
// this key via SetGlobalState, so it is promoted into Features here.
private const string LegacyUserStateContextDataKey = "HotChocolate.Authorization.UserState";

public static RequestContext TryCreateUserStateIfNotExists(
this RequestContext context)
{
var userState = context.Features.Get<UserState>();

if (userState is null
&& context.ContextData.TryGetValue(LegacyUserStateContextDataKey, out var legacyValue)
&& legacyValue is UserState legacyUserState)
{
context.Features.Set(legacyUserState);
}
else if (userState is null
&& context.ContextData.TryGetValue(nameof(ClaimsPrincipal), out var value)
&& value is ClaimsPrincipal principal)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,72 @@ public async Task Assert_UserState_Exists()
""");
}

[Fact]
public async Task Authorize_Should_Use_Explicit_UserState_When_Passed_Through_ContextData()
{
// arrange
var handler = new AuthHandler(
resolver: (ctx, _)
=> ctx.Features.TryGet(out UserState? _)
? AuthorizeResult.Allowed
: AuthorizeResult.NotAllowed,
validation: (ctx, _)
=> ctx.Features.TryGet(out UserState? _)
? AuthorizeResult.Allowed
: AuthorizeResult.NotAllowed);

var services = CreateServices(
handler,
options =>
{
options.ConfigureNodeFields =
descriptor => descriptor.Authorize("READ_NODE");
});

var executor = await services.GetRequestExecutorAsync(cancellationToken: TestContext.Current.CancellationToken);

// act
// supply a ready-made UserState under the legacy well-known key (as the in-memory client does)
var result = await executor.ExecuteAsync(
builder =>
builder
.SetDocument(
"""
{
nodes(ids: "abc") {
__typename
}
}
""")
.SetGlobalState(
"HotChocolate.Authorization.UserState",
new UserState(new ClaimsPrincipal(), isAuthenticated: true)),
TestContext.Current.CancellationToken);

// assert
// auth passes (legacy UserState promoted into Features), so it fails later on the node ID
Snapshot
.Create()
.Add(result)
.MatchInline(
"""
{
"errors": [
{
"message": "The node ID string has an invalid format.",
"path": [
"nodes"
],
"extensions": {
"originalValue": "abc"
}
}
],
"data": null
}
""");
}

[Fact]
public async Task Skip_After_Validation_For_Null()
{
Expand Down
Loading