Skip to content

Commit 78ba0a3

Browse files
committed
Implement built-in audiences and resources indicators validation
1 parent faa69c1 commit 78ba0a3

18 files changed

+2062
-48
lines changed

shared/OpenIddict.Extensions/OpenIddictHelpers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ public static IReadOnlyDictionary<string, StringValues> ParseQuery(string query)
406406
Value: parts.Length > 1 && parts[1] is string value ? Uri.UnescapeDataString(value) : null))
407407
.Where(static pair => !string.IsNullOrEmpty(pair.Key))
408408
.GroupBy(static pair => pair.Key)
409-
.ToDictionary(static pair => pair.Key!, static pair => new StringValues(pair.Select(parts => parts.Value).ToArray()));
409+
.ToDictionary(static pair => pair.Key!, static pair => new StringValues([.. pair.Select(parts => parts.Value)]));
410410
}
411411

412412
/// <summary>
@@ -430,7 +430,7 @@ public static IReadOnlyDictionary<string, StringValues> ParseFragment(string fra
430430
Value: parts.Length > 1 && parts[1] is string value ? Uri.UnescapeDataString(value) : null))
431431
.Where(static pair => !string.IsNullOrEmpty(pair.Key))
432432
.GroupBy(static pair => pair.Key)
433-
.ToDictionary(static pair => pair.Key!, static pair => new StringValues(pair.Select(parts => parts.Value).ToArray()));
433+
.ToDictionary(static pair => pair.Key!, static pair => new StringValues([.. pair.Select(parts => parts.Value)]));
434434
}
435435

436436
/// <summary>

src/OpenIddict.Abstractions/OpenIddictConstants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,9 +423,11 @@ public static class GrantTypes
423423

424424
public static class Prefixes
425425
{
426+
public const string Audience = "aud:";
426427
public const string Endpoint = "ept:";
427428
public const string GrantType = "gt:";
428429
public const string ResponseType = "rst:";
430+
public const string Resource = "rsrc:";
429431
public const string Scope = "scp:";
430432
}
431433

src/OpenIddict.Abstractions/OpenIddictResources.resx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1793,6 +1793,9 @@ Alternatively, any value respecting the '[region]-[subregion]-[identifier]' patt
17931793
<data name="ID0494" xml:space="preserve">
17941794
<value>The type of the actor token cannot be resolved from the authentication context.</value>
17951795
</data>
1796+
<data name="ID0495" xml:space="preserve">
1797+
<value>The '{0}' parameter cannot contain values that are not valid absolute URIs containing no fragment component.</value>
1798+
</data>
17961799
<data name="ID2000" xml:space="preserve">
17971800
<value>The security token is missing.</value>
17981801
</data>
@@ -2360,6 +2363,24 @@ Alternatively, any value respecting the '[region]-[subregion]-[identifier]' patt
23602363
<data name="ID2189" xml:space="preserve">
23612364
<value>The specified actor token cannot be used by this client application.</value>
23622365
</data>
2366+
<data name="ID2190" xml:space="preserve">
2367+
<value>One of the specified '{0}' parameters is invalid.</value>
2368+
</data>
2369+
<data name="ID2191" xml:space="preserve">
2370+
<value>This client application is not allowed to use the specified audience(s).</value>
2371+
</data>
2372+
<data name="ID2192" xml:space="preserve">
2373+
<value>This client application is not allowed to use the specified resource(s).</value>
2374+
</data>
2375+
<data name="ID2193" xml:space="preserve">
2376+
<value>The '{0}' parameter is not allowed in authorization requests.</value>
2377+
</data>
2378+
<data name="ID2194" xml:space="preserve">
2379+
<value>The '{0}' parameter is not allowed in pushed authorization requests.</value>
2380+
</data>
2381+
<data name="ID2195" xml:space="preserve">
2382+
<value>The '{0}' parameter is only allowed for OAuth 2.0 Token Exchange requests.</value>
2383+
</data>
23632384
<data name="ID4000" xml:space="preserve">
23642385
<value>The '{0}' parameter shouldn't be null or empty at this point.</value>
23652386
</data>
@@ -3137,6 +3158,36 @@ This may indicate that the hashed entry is corrupted or malformed.</value>
31373158
<data name="ID6271" xml:space="preserve">
31383159
<value>The token request was rejected because the actor token was issued to a different client or for another resource server.</value>
31393160
</data>
3161+
<data name="ID6272" xml:space="preserve">
3162+
<value>The token request was rejected because invalid audiences were specified: {Audiences}.</value>
3163+
</data>
3164+
<data name="ID6273" xml:space="preserve">
3165+
<value>The token request was rejected because invalid resources were specified: {Resources}.</value>
3166+
</data>
3167+
<data name="ID6274" xml:space="preserve">
3168+
<value>The authorization request was rejected because invalid resources were specified: {Resources}.</value>
3169+
</data>
3170+
<data name="ID6275" xml:space="preserve">
3171+
<value>The pushed authorization request was rejected because invalid resources were specified: {Resources}.</value>
3172+
</data>
3173+
<data name="ID6276" xml:space="preserve">
3174+
<value>The token request was rejected because the application '{ClientId}' was not allowed to use the audience {Audience}.</value>
3175+
</data>
3176+
<data name="ID6277" xml:space="preserve">
3177+
<value>The token request was rejected because the application '{ClientId}' was not allowed to use the resource {Resource}.</value>
3178+
</data>
3179+
<data name="ID6278" xml:space="preserve">
3180+
<value>The authorization request was rejected because the application '{ClientId}' was not allowed to use the resource {Resource}.</value>
3181+
</data>
3182+
<data name="ID6279" xml:space="preserve">
3183+
<value>The pushed authorization request was rejected because the application '{ClientId}' was not allowed to use the resource {Resource}.</value>
3184+
</data>
3185+
<data name="ID6280" xml:space="preserve">
3186+
<value>The token request was rejected because the '{Parameter}' parameter wasn't a valid absolute URI: {RedirectUri}.</value>
3187+
</data>
3188+
<data name="ID6281" xml:space="preserve">
3189+
<value>The token request was rejected because the '{Parameter}' contained a URI fragment: {RedirectUri}.</value>
3190+
</data>
31403191
<data name="ID8000" xml:space="preserve">
31413192
<value>https://documentation.openiddict.com/errors/{0}</value>
31423193
</data>

src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs

Lines changed: 136 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,35 @@ public static ImmutableArray<string> GetAcrValues(this OpenIddictRequest request
3838
return GetValues(request.AcrValues, Separators.Space);
3939
}
4040

41+
/// <summary>
42+
/// Extracts the audiences from an <see cref="OpenIddictRequest"/>.
43+
/// </summary>
44+
/// <param name="request">The <see cref="OpenIddictRequest"/> instance.</param>
45+
public static ImmutableArray<string> GetAudiences(this OpenIddictRequest request)
46+
{
47+
if (request is null)
48+
{
49+
throw new ArgumentNullException(nameof(request));
50+
}
51+
52+
if (request.Audiences is not { IsDefaultOrEmpty: false } audiences)
53+
{
54+
return [];
55+
}
56+
57+
HashSet<string> set = [];
58+
59+
foreach (var audience in audiences)
60+
{
61+
if (!string.IsNullOrEmpty(audience))
62+
{
63+
set.Add(audience);
64+
}
65+
}
66+
67+
return [.. set];
68+
}
69+
4170
/// <summary>
4271
/// Extracts the prompt values from an <see cref="OpenIddictRequest"/>.
4372
/// </summary>
@@ -52,6 +81,35 @@ public static ImmutableArray<string> GetPromptValues(this OpenIddictRequest requ
5281
return GetValues(request.Prompt, Separators.Space);
5382
}
5483

84+
/// <summary>
85+
/// Extracts the resources from an <see cref="OpenIddictRequest"/>.
86+
/// </summary>
87+
/// <param name="request">The <see cref="OpenIddictRequest"/> instance.</param>
88+
public static ImmutableArray<string> GetResources(this OpenIddictRequest request)
89+
{
90+
if (request is null)
91+
{
92+
throw new ArgumentNullException(nameof(request));
93+
}
94+
95+
if (request.Resources is not { IsDefaultOrEmpty: false } resources)
96+
{
97+
return [];
98+
}
99+
100+
HashSet<string> set = [];
101+
102+
foreach (var resource in resources)
103+
{
104+
if (!string.IsNullOrEmpty(resource))
105+
{
106+
set.Add(resource);
107+
}
108+
}
109+
110+
return [.. set];
111+
}
112+
55113
/// <summary>
56114
/// Extracts the response types from an <see cref="OpenIddictRequest"/>.
57115
/// </summary>
@@ -94,30 +152,100 @@ public static bool HasAcrValue(this OpenIddictRequest request, string value)
94152

95153
if (string.IsNullOrEmpty(value))
96154
{
97-
throw new ArgumentException(SR.GetResourceString(SR.ID0177), nameof(value));
155+
throw new ArgumentException(SR.FormatID0366(nameof(value)), nameof(value));
98156
}
99157

100158
return HasValue(request.AcrValues, value, Separators.Space);
101159
}
102160

161+
/// <summary>
162+
/// Determines whether the requested audiences contains the specified value.
163+
/// </summary>
164+
/// <param name="request">The <see cref="OpenIddictRequest"/> instance.</param>
165+
/// <param name="audience">The value to look for in the parameters.</param>
166+
public static bool HasAudience(this OpenIddictRequest request, string audience)
167+
{
168+
if (request is null)
169+
{
170+
throw new ArgumentNullException(nameof(request));
171+
}
172+
173+
if (string.IsNullOrEmpty(audience))
174+
{
175+
throw new ArgumentException(SR.FormatID0366(nameof(audience)), nameof(audience));
176+
}
177+
178+
var audiences = request.Audiences;
179+
if (audiences is null or [])
180+
{
181+
return false;
182+
}
183+
184+
for (var index = 0; index < audiences.Value.Length; index++)
185+
{
186+
if (audiences.Value[index] is { Length: > 0 } value &&
187+
string.Equals(value, audience, StringComparison.Ordinal))
188+
{
189+
return true;
190+
}
191+
}
192+
193+
return false;
194+
}
195+
103196
/// <summary>
104197
/// Determines whether the requested prompt contains the specified value.
105198
/// </summary>
106199
/// <param name="request">The <see cref="OpenIddictRequest"/> instance.</param>
107-
/// <param name="prompt">The component to look for in the parameter.</param>
108-
public static bool HasPromptValue(this OpenIddictRequest request, string prompt)
200+
/// <param name="value">The component to look for in the parameter.</param>
201+
public static bool HasPromptValue(this OpenIddictRequest request, string value)
202+
{
203+
if (request is null)
204+
{
205+
throw new ArgumentNullException(nameof(request));
206+
}
207+
208+
if (string.IsNullOrEmpty(value))
209+
{
210+
throw new ArgumentException(SR.FormatID0366(nameof(value)), nameof(value));
211+
}
212+
213+
return HasValue(request.Prompt, value, Separators.Space);
214+
}
215+
216+
/// <summary>
217+
/// Determines whether the requested resources contains the specified value.
218+
/// </summary>
219+
/// <param name="request">The <see cref="OpenIddictRequest"/> instance.</param>
220+
/// <param name="resource">The value to look for in the parameters.</param>
221+
public static bool HasResource(this OpenIddictRequest request, string resource)
109222
{
110223
if (request is null)
111224
{
112225
throw new ArgumentNullException(nameof(request));
113226
}
114227

115-
if (string.IsNullOrEmpty(prompt))
228+
if (string.IsNullOrEmpty(resource))
229+
{
230+
throw new ArgumentException(SR.FormatID0366(nameof(resource)), nameof(resource));
231+
}
232+
233+
var resources = request.Resources;
234+
if (resources is null or [])
235+
{
236+
return false;
237+
}
238+
239+
for (var index = 0; index < resources.Value.Length; index++)
116240
{
117-
throw new ArgumentException(SR.GetResourceString(SR.ID0178), nameof(prompt));
241+
if (resources.Value[index] is { Length: > 0 } value &&
242+
string.Equals(value, resource, StringComparison.Ordinal))
243+
{
244+
return true;
245+
}
118246
}
119247

120-
return HasValue(request.Prompt, prompt, Separators.Space);
248+
return false;
121249
}
122250

123251
/// <summary>
@@ -134,7 +262,7 @@ public static bool HasResponseType(this OpenIddictRequest request, string type)
134262

135263
if (string.IsNullOrEmpty(type))
136264
{
137-
throw new ArgumentException(SR.GetResourceString(SR.ID0179), nameof(type));
265+
throw new ArgumentException(SR.FormatID0366(nameof(type)), nameof(type));
138266
}
139267

140268
return HasValue(request.ResponseType, type, Separators.Space);
@@ -154,7 +282,7 @@ public static bool HasScope(this OpenIddictRequest request, string scope)
154282

155283
if (string.IsNullOrEmpty(scope))
156284
{
157-
throw new ArgumentException(SR.GetResourceString(SR.ID0180), nameof(scope));
285+
throw new ArgumentException(SR.FormatID0366(nameof(scope)), nameof(scope));
158286
}
159287

160288
return HasValue(request.Scope, scope, Separators.Space);

src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ public OpenIddictMessage(IEnumerable<KeyValuePair<string, ImmutableArray<string?
182182
// parameters with the same name to represent a multi-valued parameter.
183183
AddParameter(parameter.Key, parameter.Value switch
184184
{
185-
null or [] => default,
185+
null or [] => default,
186186
[string value] => new OpenIddictParameter(value),
187187
[..] values => new OpenIddictParameter(values)
188188
});

src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,7 @@ public static explicit operator StringValues(OpenIddictParameter? parameter)
11651165
null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null,
11661166

11671167
// When the parameter is an array of strings, return a StringValues instance wrapping the cloned array.
1168-
string?[] value => new StringValues(value.ToArray().ToArray()),
1168+
string?[] value => new StringValues([.. value]),
11691169

11701170
// When the parameter is a string value, return a StringValues instance with a single entry.
11711171
string value => new StringValues(value),

src/OpenIddict.Client/OpenIddictClientBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,7 +1144,7 @@ public OpenIddictClientBuilder SetPostLogoutRedirectionEndpointUris(
11441144
throw new ArgumentNullException(nameof(uris));
11451145
}
11461146

1147-
return SetPostLogoutRedirectionEndpointUris(uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute)).ToArray());
1147+
return SetPostLogoutRedirectionEndpointUris([.. uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute))]);
11481148
}
11491149

11501150
/// <summary>
@@ -1197,7 +1197,7 @@ public OpenIddictClientBuilder SetRedirectionEndpointUris(
11971197
throw new ArgumentNullException(nameof(uris));
11981198
}
11991199

1200-
return SetRedirectionEndpointUris(uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute)).ToArray());
1200+
return SetRedirectionEndpointUris([.. uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute))]);
12011201
}
12021202

12031203
/// <summary>

0 commit comments

Comments
 (0)