Skip to content

Commit f0bab00

Browse files
Merge pull request #342 from DuendeSoftware/ev/atm/prevent-nre-in-non-httpcontext-scenario
prevent nre in non httpcontext scenario
2 parents b59919e + ca7c99f commit f0bab00

File tree

4 files changed

+124
-3
lines changed

4 files changed

+124
-3
lines changed

access-token-management/src/AccessTokenManagement.OpenIdConnect/Internal/BlazorServerUserAccessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public async Task<ClaimsPrincipal> GetCurrentUserAsync(CT ct = default)
4343

4444
// If we are in neither blazor server or SSR, something weird is going on.
4545
logger.LogWarning("Neither an authentication state provider or http context are available to obtain the current principal.");
46-
return new ClaimsPrincipal();
46+
return new ClaimsPrincipal(new ClaimsIdentity());
4747
}
4848

4949
}

access-token-management/src/AccessTokenManagement.OpenIdConnect/Internal/HttpContextUserAccessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ internal class HttpContextUserAccessor : IUserAccessor
1919
public HttpContextUserAccessor(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor;
2020

2121
/// <inheritdoc/>
22-
public Task<ClaimsPrincipal> GetCurrentUserAsync(CT ct = default) => Task.FromResult(_httpContextAccessor.HttpContext?.User ?? new ClaimsPrincipal());
22+
public Task<ClaimsPrincipal> GetCurrentUserAsync(CT ct = default) => Task.FromResult(_httpContextAccessor.HttpContext?.User ?? new ClaimsPrincipal(new ClaimsIdentity()));
2323
}

access-token-management/src/AccessTokenManagement.OpenIdConnect/Internal/UserAccessTokenManagementService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public async Task<TokenResult<UserToken>> GetAccessTokenAsync(
3434

3535
parameters ??= new UserTokenRequestParameters();
3636

37-
if (!user.Identity!.IsAuthenticated)
37+
if (user.Identity is not { IsAuthenticated: true })
3838
{
3939
logger.CannotRetrieveAccessTokenDueToNoActiveUser(LogLevel.Information);
4040
return TokenResult.Failure("No active user");
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright (c) Duende Software. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
using System.Security.Claims;
5+
using Duende.AccessTokenManagement.OpenIdConnect;
6+
using Duende.AccessTokenManagement.OpenIdConnect.Internal;
7+
using Duende.AccessTokenManagement.OTel;
8+
using Microsoft.Extensions.Logging.Abstractions;
9+
using Microsoft.Extensions.Options;
10+
11+
namespace Duende.AccessTokenManagement;
12+
13+
public class UserAccessAccessTokenManagerTests
14+
{
15+
private readonly CancellationToken _ct = TestContext.Current.CancellationToken;
16+
17+
[Fact]
18+
public async Task GetAccessTokenAsync_with_null_identity_should_return_failure()
19+
{
20+
// A ClaimsPrincipal created with no arguments has Identity == null.
21+
// This can happen if a custom IUserAccessor returns a default principal.
22+
var user = new ClaimsPrincipal();
23+
24+
var sut = CreateSut();
25+
26+
var result = await sut.GetAccessTokenAsync(user, ct: _ct);
27+
28+
result.Succeeded.ShouldBeFalse();
29+
result.FailedResult.ShouldNotBeNull();
30+
result.FailedResult!.Error.ShouldBe("No active user");
31+
}
32+
33+
[Fact]
34+
public async Task GetAccessTokenAsync_with_unauthenticated_identity_should_return_failure()
35+
{
36+
// A ClaimsPrincipal with an empty ClaimsIdentity is unauthenticated.
37+
var user = new ClaimsPrincipal(new ClaimsIdentity());
38+
39+
var sut = CreateSut();
40+
41+
var result = await sut.GetAccessTokenAsync(user, ct: _ct);
42+
43+
result.Succeeded.ShouldBeFalse();
44+
result.FailedResult.ShouldNotBeNull();
45+
result.FailedResult!.Error.ShouldBe("No active user");
46+
}
47+
48+
private static UserAccessAccessTokenManager CreateSut()
49+
{
50+
var metrics = new AccessTokenManagementMetrics(new TestMeterFactory());
51+
var sync = new StubUserTokenRequestConcurrencyControl();
52+
var store = new StubUserTokenStore();
53+
var clock = TimeProvider.System;
54+
var options = Options.Create(new UserTokenManagementOptions());
55+
var tokenClient = new StubOpenIdConnectUserTokenEndpoint();
56+
var logger = new NullLogger<UserAccessAccessTokenManager>();
57+
58+
return new UserAccessAccessTokenManager(
59+
metrics,
60+
sync,
61+
store,
62+
clock,
63+
options,
64+
tokenClient,
65+
logger);
66+
}
67+
68+
private class TestMeterFactory : System.Diagnostics.Metrics.IMeterFactory
69+
{
70+
public System.Diagnostics.Metrics.Meter Create(System.Diagnostics.Metrics.MeterOptions options) =>
71+
new(options);
72+
73+
public void Dispose() { }
74+
}
75+
76+
private class StubUserTokenRequestConcurrencyControl : IUserTokenRequestConcurrencyControl
77+
{
78+
public Task<TokenResult<UserToken>> ExecuteWithConcurrencyControlAsync(
79+
UserRefreshToken key,
80+
Func<Task<TokenResult<UserToken>>> tokenRetriever,
81+
CancellationToken ct = default) =>
82+
tokenRetriever();
83+
}
84+
85+
private class StubUserTokenStore : IUserTokenStore
86+
{
87+
public Task<TokenResult<TokenForParameters>> GetTokenAsync(
88+
ClaimsPrincipal user,
89+
UserTokenRequestParameters? parameters = null,
90+
CancellationToken ct = default) =>
91+
throw new NotImplementedException("Should not be reached when user has no identity");
92+
93+
public Task StoreTokenAsync(
94+
ClaimsPrincipal user,
95+
UserToken token,
96+
UserTokenRequestParameters? parameters = null,
97+
CancellationToken ct = default) =>
98+
throw new NotImplementedException("Should not be reached when user has no identity");
99+
100+
public Task ClearTokenAsync(
101+
ClaimsPrincipal user,
102+
UserTokenRequestParameters? parameters = null,
103+
CancellationToken ct = default) =>
104+
throw new NotImplementedException("Should not be reached when user has no identity");
105+
}
106+
107+
private class StubOpenIdConnectUserTokenEndpoint : IOpenIdConnectUserTokenEndpoint
108+
{
109+
public Task<TokenResult<UserToken>> RefreshAccessTokenAsync(
110+
UserRefreshToken userToken,
111+
UserTokenRequestParameters parameters,
112+
CancellationToken ct = default) =>
113+
throw new NotImplementedException("Should not be reached when user has no identity");
114+
115+
public Task RevokeRefreshTokenAsync(
116+
UserRefreshToken userToken,
117+
UserTokenRequestParameters parameters,
118+
CancellationToken ct = default) =>
119+
throw new NotImplementedException("Should not be reached when user has no identity");
120+
}
121+
}

0 commit comments

Comments
 (0)