Skip to content

Commit f9c8406

Browse files
committed
Includes the nullability annotations from community PR.
Updates the `DefaultScopeParser` to return gracefully rather than throwing an exception (which is ultimately unhandled). Introduces a new unit test to cover `AuthorizedScopes` being null (as apposed to an empty collection).
1 parent 1726bbe commit f9c8406

File tree

4 files changed

+79
-26
lines changed

4 files changed

+79
-26
lines changed

identity-server/src/IdentityServer/Models/RefreshTokenCreationRequest.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ public class RefreshTokenCreationRequest
1616
/// <summary>
1717
/// The client.
1818
/// </summary>
19-
public Client Client { get; set; } = default!;
19+
public required Client Client { get; set; }
2020

2121
/// <summary>
2222
/// The subject.
2323
/// </summary>
24-
public ClaimsPrincipal Subject { get; set; } = default!;
24+
public required ClaimsPrincipal Subject { get; set; }
2525

2626
/// <summary>
2727
/// The description.
@@ -31,7 +31,7 @@ public class RefreshTokenCreationRequest
3131
/// <summary>
3232
/// The scopes.
3333
/// </summary>
34-
public IEnumerable<string> AuthorizedScopes { get; set; } = default!;
34+
public required IEnumerable<string> AuthorizedScopes { get; set; }
3535

3636
/// <summary>
3737
/// The resource indicators. Null indicates there was no authorization step, thus no restrictions.
@@ -47,7 +47,7 @@ public class RefreshTokenCreationRequest
4747
/// <summary>
4848
/// The access token.
4949
/// </summary>
50-
public Token AccessToken { get; set; } = default!;
50+
public required Token AccessToken { get; set; }
5151

5252
/// <summary>
5353
/// The proof type used.

identity-server/src/IdentityServer/Validation/Default/DefaultScopeParser.cs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Duende Software. All rights reserved.
22
// See LICENSE in the project root for license information.
33

4-
54
using Duende.IdentityServer.Extensions;
65
using Microsoft.Extensions.Logging;
76

@@ -10,29 +9,27 @@ namespace Duende.IdentityServer.Validation;
109
/// <summary>
1110
/// Default implementation of IScopeParser.
1211
/// </summary>
13-
public class DefaultScopeParser : IScopeParser
12+
/// <remarks>
13+
/// Ctor.
14+
/// </remarks>
15+
/// <param name="logger"></param>
16+
public class DefaultScopeParser(ILogger<DefaultScopeParser> logger) : IScopeParser
1417
{
15-
private readonly ILogger<DefaultScopeParser> _logger;
16-
17-
/// <summary>
18-
/// Ctor.
19-
/// </summary>
20-
/// <param name="logger"></param>
21-
public DefaultScopeParser(ILogger<DefaultScopeParser> logger)
22-
{
23-
_logger = logger;
24-
}
25-
2618
/// <inheritdoc/>
2719
public ParsedScopesResult ParseScopeValues(IEnumerable<string> scopeValues)
2820
{
2921
using var activity = Tracing.ValidationActivitySource.StartActivity("DefaultScopeParser.ParseScopeValues");
3022
activity?.SetTag(Tracing.Properties.Scope, scopeValues.ToSpaceSeparatedString());
3123

32-
if (scopeValues == null) throw new ArgumentNullException(nameof(scopeValues));
33-
3424
var result = new ParsedScopesResult();
3525

26+
if (scopeValues is null)
27+
{
28+
logger.LogError("A collection of scopes cannot be null.");
29+
result.Errors.Add(new ParsedScopeValidationError("null", "A collection of scopes cannot be null."));
30+
return result;
31+
}
32+
3633
foreach (var scopeValue in scopeValues)
3734
{
3835
var ctx = new ParseScopeContext(scopeValue);
@@ -52,7 +49,7 @@ public ParsedScopesResult ParseScopeValues(IEnumerable<string> scopeValues)
5249
}
5350
else
5451
{
55-
_logger.LogDebug("Scope parsing ignoring scope {scope}", scopeValue.SanitizeLogParameter());
52+
logger.LogDebug("Scope parsing ignoring scope {scope}", scopeValue.SanitizeLogParameter());
5653
}
5754
}
5855

identity-server/test/IdentityServer.UnitTests/Services/Default/DefaultRefreshTokenServiceTests.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Duende Software. All rights reserved.
22
// See LICENSE in the project root for license information.
33

4-
54
using System.Security.Claims;
65
using Duende.IdentityServer;
76
using Duende.IdentityServer.Models;
@@ -46,7 +45,7 @@ public async Task CreateRefreshToken_token_exists_in_store()
4645
var client = new Client();
4746
var accessToken = new Token();
4847

49-
var handle = await _subject.CreateRefreshTokenAsync(new RefreshTokenCreationRequest { Subject = _user, AccessToken = accessToken, Client = client });
48+
var handle = await _subject.CreateRefreshTokenAsync(new RefreshTokenCreationRequest { Subject = _user, AccessToken = accessToken, Client = client, AuthorizedScopes = [] });
5049

5150
(await _store.GetRefreshTokenAsync(handle)).ShouldNotBeNull();
5251
}
@@ -62,7 +61,7 @@ public async Task CreateRefreshToken_should_match_absolute_lifetime()
6261
AbsoluteRefreshTokenLifetime = 10
6362
};
6463

65-
var handle = await _subject.CreateRefreshTokenAsync(new RefreshTokenCreationRequest { Subject = _user, AccessToken = new Token(), Client = client });
64+
var handle = await _subject.CreateRefreshTokenAsync(new RefreshTokenCreationRequest { Subject = _user, AccessToken = new Token(), Client = client, AuthorizedScopes = [] });
6665

6766
var refreshToken = (await _store.GetRefreshTokenAsync(handle));
6867

@@ -82,7 +81,7 @@ public async Task CreateRefreshToken_should_cap_sliding_lifetime_that_exceeds_ab
8281
AbsoluteRefreshTokenLifetime = 10
8382
};
8483

85-
var handle = await _subject.CreateRefreshTokenAsync(new RefreshTokenCreationRequest { Subject = _user, AccessToken = new Token(), Client = client });
84+
var handle = await _subject.CreateRefreshTokenAsync(new RefreshTokenCreationRequest { Subject = _user, AccessToken = new Token(), Client = client, AuthorizedScopes = [] });
8685

8786
var refreshToken = (await _store.GetRefreshTokenAsync(handle));
8887

@@ -101,15 +100,14 @@ public async Task CreateRefreshToken_should_match_sliding_lifetime()
101100
SlidingRefreshTokenLifetime = 10
102101
};
103102

104-
var handle = await _subject.CreateRefreshTokenAsync(new RefreshTokenCreationRequest { Subject = _user, AccessToken = new Token(), Client = client });
103+
var handle = await _subject.CreateRefreshTokenAsync(new RefreshTokenCreationRequest { Subject = _user, AccessToken = new Token(), Client = client, AuthorizedScopes = [] });
105104

106105
var refreshToken = (await _store.GetRefreshTokenAsync(handle));
107106

108107
refreshToken.ShouldNotBeNull();
109108
refreshToken.Lifetime.ShouldBe(client.SlidingRefreshTokenLifetime);
110109
}
111110

112-
113111
[Fact]
114112
public async Task UpdateRefreshToken_one_time_use_should_create_new_token()
115113
{
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) Duende Software. All rights reserved.
2+
// See LICENSE in the project root for license information.
3+
4+
using System.Collections.Specialized;
5+
using System.Security.Claims;
6+
using Duende.IdentityModel;
7+
using Duende.IdentityServer;
8+
using Duende.IdentityServer.Models;
9+
using Duende.IdentityServer.Stores;
10+
using UnitTests.Validation.Setup;
11+
12+
namespace UnitTests.Validation.TokenRequest_Validation;
13+
14+
public class TokenRequestValidation_Invalid
15+
{
16+
private const string Category = "TokenRequest Validation - General - Invalid";
17+
18+
private readonly IClientStore _clients = Factory.CreateClientStore();
19+
20+
[Fact]
21+
[Trait("Category", Category)]
22+
public async Task Invalid_refresh_token_request_should_fail()
23+
{
24+
var subjectClaim = new Claim(JwtClaimTypes.Subject, "foo");
25+
26+
var refreshToken = new RefreshToken
27+
{
28+
ClientId = "roclient",
29+
Subject = new IdentityServerUser(subjectClaim.Value).CreatePrincipal(),
30+
AuthorizedScopes = null, // Passing a null value to AuthorizedScopes should be invalid
31+
Lifetime = 600,
32+
CreationTime = DateTime.UtcNow
33+
};
34+
35+
refreshToken.SetAccessToken(new Token("access_token")
36+
{
37+
Claims = [subjectClaim],
38+
ClientId = "roclient"
39+
});
40+
41+
var grants = Factory.CreateRefreshTokenStore();
42+
var handle = await grants.StoreRefreshTokenAsync(refreshToken);
43+
44+
var client = await _clients.FindEnabledClientByIdAsync("roclient");
45+
46+
var validator = Factory.CreateTokenRequestValidator(refreshTokenStore: grants);
47+
48+
var parameters = new NameValueCollection
49+
{
50+
{ OidcConstants.TokenRequest.GrantType, "refresh_token" },
51+
{ OidcConstants.TokenRequest.RefreshToken, handle }
52+
};
53+
54+
var result = await validator.ValidateRequestAsync(parameters, client.ToValidationResult());
55+
56+
result.IsError.ShouldBeTrue();
57+
}
58+
}

0 commit comments

Comments
 (0)