Skip to content

Commit 8410484

Browse files
committed
Unify platform callbacks handling using a new OpenIddictClientSystemIntegrationPlatformCallback type
1 parent 290e415 commit 8410484

11 files changed

+375
-447
lines changed

src/OpenIddict.Abstractions/OpenIddictResources.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1480,7 +1480,7 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
14801480
<value>The web authentication broker is only supported on UWP and requires running Windows 10 version 1709 (Fall Creators) or higher.</value>
14811481
</data>
14821482
<data name="ID0393" xml:space="preserve">
1483-
<value>The web authentication result cannot be resolved or contains invalid data.</value>
1483+
<value>The platform callback cannot be resolved or contains invalid data.</value>
14841484
</data>
14851485
<data name="ID0394" xml:space="preserve">
14861486
<value>The issuer attached to the static configuration must be the same as the one configured in the validation options.</value>
@@ -1692,6 +1692,9 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
16921692
<data name="ID0452" xml:space="preserve">
16931693
<value>Custom tabs intents are only supported on Android.</value>
16941694
</data>
1695+
<data name="ID0453" xml:space="preserve">
1696+
<value>The specified intent doesn't contain a valid data URI.</value>
1697+
</data>
16951698
<data name="ID2000" xml:space="preserve">
16961699
<value>The security token is missing.</value>
16971700
</data>

src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationExtensions.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,18 @@ public static OpenIddictClientSystemIntegrationBuilder UseSystemIntegration(this
103103

104104
// Register the built-in filters used by the default OpenIddict client system integration event handlers.
105105
builder.Services.TryAddSingleton<RequireASWebAuthenticationSession>();
106-
builder.Services.TryAddSingleton<RequireASWebAuthenticationCallbackUrl>();
107106
builder.Services.TryAddSingleton<RequireAuthenticationNonce>();
108107
builder.Services.TryAddSingleton<RequireCustomTabsIntent>();
109-
builder.Services.TryAddSingleton<RequireCustomTabsIntentData>();
110108
builder.Services.TryAddSingleton<RequireEmbeddedWebServerEnabled>();
111109
builder.Services.TryAddSingleton<RequireHttpListenerContext>();
112110
builder.Services.TryAddSingleton<RequireInteractiveSession>();
111+
builder.Services.TryAddSingleton<RequirePlatformCallback>();
113112
builder.Services.TryAddSingleton<RequireProtocolActivation>();
114113
builder.Services.TryAddSingleton<RequireSystemBrowser>();
115114
builder.Services.TryAddSingleton<RequireWebAuthenticationBroker>();
115+
#pragma warning disable CS0618
116116
builder.Services.TryAddSingleton<RequireWebAuthenticationResult>();
117+
#pragma warning restore CS0618
117118

118119
// Register the built-in event handlers used by the OpenIddict client system integration components.
119120
// Note: the order used here is not important, as the actual order is set in the options.

src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlerFilters.cs

Lines changed: 19 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,6 @@ namespace OpenIddict.Client.SystemIntegration;
1717
[EditorBrowsable(EditorBrowsableState.Advanced)]
1818
public static class OpenIddictClientSystemIntegrationHandlerFilters
1919
{
20-
/// <summary>
21-
/// Represents a filter that excludes the associated handlers if no AS web
22-
/// authentication callback URL can be found in the transaction properties.
23-
/// </summary>
24-
public sealed class RequireASWebAuthenticationCallbackUrl : IOpenIddictClientHandlerFilter<BaseContext>
25-
{
26-
/// <inheritdoc/>
27-
public ValueTask<bool> IsActiveAsync(BaseContext context)
28-
{
29-
if (context is null)
30-
{
31-
throw new ArgumentNullException(nameof(context));
32-
}
33-
34-
#if SUPPORTS_AUTHENTICATION_SERVICES && SUPPORTS_FOUNDATION
35-
if (IsASWebAuthenticationSessionSupported())
36-
{
37-
return new(ContainsASWebAuthenticationSessionResult(context.Transaction));
38-
}
39-
40-
[MethodImpl(MethodImplOptions.NoInlining)]
41-
static bool ContainsASWebAuthenticationSessionResult(OpenIddictClientTransaction transaction)
42-
=> transaction.GetASWebAuthenticationCallbackUrl() is not null;
43-
#endif
44-
return new(false);
45-
}
46-
}
47-
4820
/// <summary>
4921
/// Represents a filter that excludes the associated handlers if
5022
/// the AS web authentication session integration was not enabled.
@@ -133,33 +105,6 @@ public ValueTask<bool> IsActiveAsync(BaseContext context)
133105
return new(false);
134106
}
135107
}
136-
/// <summary>
137-
/// Represents a filter that excludes the associated handlers if no
138-
/// custom tabs intent data can be found in the transaction properties.
139-
/// </summary>
140-
public sealed class RequireCustomTabsIntentData : IOpenIddictClientHandlerFilter<BaseContext>
141-
{
142-
/// <inheritdoc/>
143-
public ValueTask<bool> IsActiveAsync(BaseContext context)
144-
{
145-
if (context is null)
146-
{
147-
throw new ArgumentNullException(nameof(context));
148-
}
149-
150-
#if SUPPORTS_ANDROID && SUPPORTS_ANDROIDX_BROWSER
151-
if (IsCustomTabsIntentSupported())
152-
{
153-
return new(ContainsCustomTabsIntentData(context.Transaction));
154-
}
155-
156-
[MethodImpl(MethodImplOptions.NoInlining)]
157-
static bool ContainsCustomTabsIntentData(OpenIddictClientTransaction transaction)
158-
=> transaction.GetCustomTabsIntentData() is not null;
159-
#endif
160-
return new(false);
161-
}
162-
}
163108

164109
/// <summary>
165110
/// Represents a filter that excludes the associated handlers if the embedded web server was not enabled.
@@ -217,6 +162,24 @@ public ValueTask<bool> IsActiveAsync(BaseContext context)
217162
}
218163
}
219164

165+
/// <summary>
166+
/// Represents a filter that excludes the associated handlers if no
167+
/// platform callback can be found in the transaction properties.
168+
/// </summary>
169+
public sealed class RequirePlatformCallback : IOpenIddictClientHandlerFilter<BaseContext>
170+
{
171+
/// <inheritdoc/>
172+
public ValueTask<bool> IsActiveAsync(BaseContext context)
173+
{
174+
if (context is null)
175+
{
176+
throw new ArgumentNullException(nameof(context));
177+
}
178+
179+
return new(context.Transaction.GetPlatformCallback() is not null);
180+
}
181+
}
182+
220183
/// <summary>
221184
/// Represents a filter that excludes the associated handlers if no protocol activation was found.
222185
/// </summary>
@@ -304,6 +267,7 @@ public ValueTask<bool> IsActiveAsync(BaseContext context)
304267
/// Represents a filter that excludes the associated handlers if no
305268
/// web authentication operation was triggered during the transaction.
306269
/// </summary>
270+
[Obsolete("This filter is obsolete and will be removed in a future version.")]
307271
public sealed class RequireWebAuthenticationResult : IOpenIddictClientHandlerFilter<BaseContext>
308272
{
309273
/// <inheritdoc/>

src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Authentication.cs

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,7 @@ public static class Authentication
5757
*/
5858
ExtractGetOrPostHttpListenerRequest<ExtractRedirectionRequestContext>.Descriptor,
5959
ExtractProtocolActivationParameters<ExtractRedirectionRequestContext>.Descriptor,
60-
ExtractASWebAuthenticationCallbackUrlData<ExtractRedirectionRequestContext>.Descriptor,
61-
ExtractCustomTabsIntentData<ExtractRedirectionRequestContext>.Descriptor,
62-
ExtractWebAuthenticationResultData<ExtractRedirectionRequestContext>.Descriptor,
60+
ExtractPlatformCallbackParameters<ExtractRedirectionRequestContext>.Descriptor,
6361

6462
/*
6563
* Redirection response handling:
@@ -68,9 +66,7 @@ public static class Authentication
6866
AttachCacheControlHeader<ApplyRedirectionResponseContext>.Descriptor,
6967
ProcessEmptyHttpResponse.Descriptor,
7068
ProcessProtocolActivationResponse<ApplyRedirectionResponseContext>.Descriptor,
71-
ProcessASWebAuthenticationSessionResponse<ApplyRedirectionResponseContext>.Descriptor,
72-
ProcessCustomTabsIntentResponse<ApplyRedirectionResponseContext>.Descriptor,
73-
ProcessWebAuthenticationResultResponse<ApplyRedirectionResponseContext>.Descriptor
69+
ProcessPlatformCallbackResponse<ApplyRedirectionResponseContext>.Descriptor
7470
]);
7571

7672
/// <summary>
@@ -123,7 +119,8 @@ public async ValueTask HandleAsync(ApplyAuthorizationRequestContext context)
123119
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
124120
}
125121

126-
var source = new TaskCompletionSource<NSUrl>(TaskCreationOptions.RunContinuationsAsynchronously);
122+
var source = new TaskCompletionSource<OpenIddictClientSystemIntegrationPlatformCallback>(
123+
TaskCreationOptions.RunContinuationsAsynchronously);
127124

128125
// OpenIddict represents the complete interactive authentication dance as a two-phase process:
129126
// - The challenge, during which the user is redirected to the authorization server, either
@@ -161,11 +158,11 @@ public async ValueTask HandleAsync(ApplyAuthorizationRequestContext context)
161158
throw new InvalidOperationException(SR.GetResourceString(SR.ID0448));
162159
}
163160

164-
NSUrl url;
161+
OpenIddictClientSystemIntegrationPlatformCallback callback;
165162

166163
try
167164
{
168-
url = await source.Task.WaitAsync(context.CancellationToken);
165+
callback = await source.Task.WaitAsync(context.CancellationToken);
169166
}
170167

171168
// Since the result of this operation is known by the time the task signaled by ASWebAuthenticationSession
@@ -195,7 +192,7 @@ public async ValueTask HandleAsync(ApplyAuthorizationRequestContext context)
195192
return;
196193
}
197194

198-
await _service.HandleASWebAuthenticationCallbackUrlAsync(url, context.CancellationToken);
195+
await _service.HandlePlatformCallbackAsync(callback, context.CancellationToken);
199196
context.HandleRequest();
200197
return;
201198

@@ -250,7 +247,43 @@ void HandleCallback(NSUrl? url, NSError? error)
250247
{
251248
if (url is not null)
252249
{
253-
source.SetResult(url);
250+
var parameters = new Dictionary<string, OpenIddictParameter>(StringComparer.Ordinal);
251+
252+
if (!string.IsNullOrEmpty(url.Query))
253+
{
254+
foreach (var parameter in OpenIddictHelpers.ParseQuery(url.Query))
255+
{
256+
parameters[parameter.Key] = parameter.Value.Count switch
257+
{
258+
0 => default,
259+
1 => parameter.Value[0],
260+
_ => parameter.Value.ToArray()
261+
};
262+
}
263+
}
264+
265+
// Note: the fragment is always processed after the query string to ensure that
266+
// parameters extracted from the fragment are preferred to parameters extracted
267+
// from the query string when they are present in both parts.
268+
269+
if (!string.IsNullOrEmpty(url.Fragment))
270+
{
271+
foreach (var parameter in OpenIddictHelpers.ParseFragment(url.Fragment))
272+
{
273+
parameters[parameter.Key] = parameter.Value.Count switch
274+
{
275+
0 => default,
276+
1 => parameter.Value[0],
277+
_ => parameter.Value.ToArray()
278+
};
279+
}
280+
}
281+
282+
source.SetResult(new OpenIddictClientSystemIntegrationPlatformCallback(url!, parameters)
283+
{
284+
// Attach the raw URL to the callback properties.
285+
Properties = { [typeof(NSUrl).FullName!] = url }
286+
});
254287
}
255288

256289
else if (error is not null)
@@ -426,8 +459,47 @@ public async ValueTask HandleAsync(ApplyAuthorizationRequestContext context)
426459
parameter => new StringValues((string?[]?) parameter.Value))),
427460
callbackUri: new Uri(context.RedirectUri, UriKind.Absolute)))
428461
{
429-
case { ResponseStatus: WebAuthenticationStatus.Success } result:
430-
await _service.HandleWebAuthenticationResultAsync(result, context.CancellationToken);
462+
case { ResponseStatus: WebAuthenticationStatus.Success } result
463+
when Uri.TryCreate(result.ResponseData, UriKind.Absolute, out Uri? uri):
464+
var parameters = new Dictionary<string, OpenIddictParameter>(StringComparer.Ordinal);
465+
466+
if (!string.IsNullOrEmpty(uri.Query))
467+
{
468+
foreach (var parameter in OpenIddictHelpers.ParseQuery(uri.Query))
469+
{
470+
parameters[parameter.Key] = parameter.Value.Count switch
471+
{
472+
0 => default,
473+
1 => parameter.Value[0],
474+
_ => parameter.Value.ToArray()
475+
};
476+
}
477+
}
478+
479+
// Note: the fragment is always processed after the query string to ensure that
480+
// parameters extracted from the fragment are preferred to parameters extracted
481+
// from the query string when they are present in both parts.
482+
483+
if (!string.IsNullOrEmpty(uri.Fragment))
484+
{
485+
foreach (var parameter in OpenIddictHelpers.ParseFragment(uri.Fragment))
486+
{
487+
parameters[parameter.Key] = parameter.Value.Count switch
488+
{
489+
0 => default,
490+
1 => parameter.Value[0],
491+
_ => parameter.Value.ToArray()
492+
};
493+
}
494+
}
495+
496+
var callback = new OpenIddictClientSystemIntegrationPlatformCallback(uri, parameters)
497+
{
498+
// Attach the authentication result to the properties.
499+
Properties = { [typeof(WebAuthenticationResult).FullName!] = result }
500+
};
501+
502+
await _service.HandlePlatformCallbackAsync(callback, context.CancellationToken);
431503
context.HandleRequest();
432504
return;
433505

0 commit comments

Comments
 (0)