Browse Source

Support using Process.Start() on macOS and remove the runtime checks that prevent the generic version of OpenIddict.Client.SystemIntegration from being used on macOS

Kévin Chalet 1 year ago
parent
commit
3ce5dcd539
  1. 2
      sandbox/OpenIddict.Sandbox.WinForms.Client/Worker.cs
  2. 2
      sandbox/OpenIddict.Sandbox.Wpf.Client/Worker.cs
  3. 4
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  4. 18
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationBuilder.cs
  5. 13
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationConfiguration.cs
  6. 13
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationExtensions.cs
  7. 33
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Authentication.cs
  8. 33
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Session.cs
  9. 57
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHelpers.cs
  10. 2
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHttpListener.cs

2
sandbox/OpenIddict.Sandbox.WinForms.Client/Worker.cs

@ -27,7 +27,7 @@ public class Worker : IHostedService
// in HKEY_CLASSES_ROOT (in this case, it should be added by a dedicated installer). // in HKEY_CLASSES_ROOT (in this case, it should be added by a dedicated installer).
// //
// Alternatively, the application can be packaged and use windows.protocol to // Alternatively, the application can be packaged and use windows.protocol to
// register the protocol handler/custom URI scheme with the operation system. // register the protocol handler/custom URI scheme with the operating system.
using var root = Registry.CurrentUser.CreateSubKey("SOFTWARE\\Classes\\com.openiddict.sandbox.winforms.client"); using var root = Registry.CurrentUser.CreateSubKey("SOFTWARE\\Classes\\com.openiddict.sandbox.winforms.client");
root.SetValue(string.Empty, "URL:com.openiddict.sandbox.winforms.client"); root.SetValue(string.Empty, "URL:com.openiddict.sandbox.winforms.client");
root.SetValue("URL Protocol", string.Empty); root.SetValue("URL Protocol", string.Empty);

2
sandbox/OpenIddict.Sandbox.Wpf.Client/Worker.cs

@ -27,7 +27,7 @@ public class Worker : IHostedService
// in HKEY_CLASSES_ROOT (in this case, it should be added by a dedicated installer). // in HKEY_CLASSES_ROOT (in this case, it should be added by a dedicated installer).
// //
// Alternatively, the application can be packaged and use windows.protocol to // Alternatively, the application can be packaged and use windows.protocol to
// register the protocol handler/custom URI scheme with the operation system. // register the protocol handler/custom URI scheme with the operating system.
using var root = Registry.CurrentUser.CreateSubKey("SOFTWARE\\Classes\\com.openiddict.sandbox.wpf.client"); using var root = Registry.CurrentUser.CreateSubKey("SOFTWARE\\Classes\\com.openiddict.sandbox.wpf.client");
root.SetValue(string.Empty, "URL:com.openiddict.sandbox.wpf.client"); root.SetValue(string.Empty, "URL:com.openiddict.sandbox.wpf.client");
root.SetValue("URL Protocol", string.Empty); root.SetValue("URL Protocol", string.Empty);

4
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -1669,7 +1669,7 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
<value>An explicit grant type must be attached when specifying a specific response type (except when using the special response_type=none value).</value> <value>An explicit grant type must be attached when specifying a specific response type (except when using the special response_type=none value).</value>
</data> </data>
<data name="ID0446" xml:space="preserve"> <data name="ID0446" xml:space="preserve">
<value>AS web authentication sessions are only supported on iOS 12.0 and higher.</value> <value>AS web authentication sessions are only supported on iOS 12.0+/macOS 10.15+/Mac Catalyst 12.0+ and require using an OS-specific target framework moniker on macOS (e.g 'net8.0-macos15.0').</value>
</data> </data>
<data name="ID0447" xml:space="preserve"> <data name="ID0447" xml:space="preserve">
<value>The current UI window cannot be resolved.</value> <value>The current UI window cannot be resolved.</value>
@ -1678,7 +1678,7 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
<value>An unknown error occurred while trying to start an AS web authentication session.</value> <value>An unknown error occurred while trying to start an AS web authentication session.</value>
</data> </data>
<data name="ID0449" xml:space="preserve"> <data name="ID0449" xml:space="preserve">
<value>The generic version of the OpenIddict.Client.SystemIntegration package cannot be used on this platform. Make sure your application is referencing the correct version by using the appropriate OS-specific TFM (e.g on macOS, 'net8.0-macos10.15').</value> <value>The portable version of the OpenIddict.Client.SystemIntegration package cannot be used on Android, iOS and Mac Catalyst. Make sure your application is referencing the correct version by using the appropriate OS-specific TFM (e.g on iOS, 'net8.0-ios18.0').</value>
</data> </data>
<data name="ID0450" xml:space="preserve"> <data name="ID0450" xml:space="preserve">
<value>An HTTP redirect_uri or post_logout_redirect_uri cannot be used when using AS web authentication sessions. Make sure you're using a custom protocol scheme for all the callback URIs attached to the client registration. Alternatively, you can register an associated domain and use an HTTPS redirect_uri or post_logout_redirect_uri pointing to that domain (supported only on iOS 17.4+, Mac Catalyst 17.4+ and macOS 14.4+).</value> <value>An HTTP redirect_uri or post_logout_redirect_uri cannot be used when using AS web authentication sessions. Make sure you're using a custom protocol scheme for all the callback URIs attached to the client registration. Alternatively, you can register an associated domain and use an HTTPS redirect_uri or post_logout_redirect_uri pointing to that domain (supported only on iOS 17.4+, Mac Catalyst 17.4+ and macOS 14.4+).</value>

18
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationBuilder.cs

@ -121,7 +121,7 @@ public sealed class OpenIddictClientSystemIntegrationBuilder
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns> /// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
public OpenIddictClientSystemIntegrationBuilder SetAllowedEmbeddedWebServerPorts(params int[] ports) public OpenIddictClientSystemIntegrationBuilder SetAllowedEmbeddedWebServerPorts(params int[] ports)
{ {
if (Array.Exists(ports, static port => port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)) if (Array.Exists(ports, static port => port is < IPEndPoint.MinPort or > IPEndPoint.MaxPort))
{ {
throw new ArgumentOutOfRangeException(nameof(ports)); throw new ArgumentOutOfRangeException(nameof(ports));
} }
@ -278,25 +278,15 @@ public sealed class OpenIddictClientSystemIntegrationBuilder
return Configure(options => options.PipeSecurity = security); return Configure(options => options.PipeSecurity = security);
} }
/// <summary> /// <inheritdoc/>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><see langword="true"/> if the specified object is equal to the current object; otherwise, false.</returns>
[EditorBrowsable(EditorBrowsableState.Never)] [EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj); public override bool Equals(object? obj) => base.Equals(obj);
/// <summary> /// <inheritdoc/>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
[EditorBrowsable(EditorBrowsableState.Never)] [EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode(); public override int GetHashCode() => base.GetHashCode();
/// <summary> /// <inheritdoc/>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
[EditorBrowsable(EditorBrowsableState.Never)] [EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString(); public override string? ToString() => base.ToString();
} }

13
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationConfiguration.cs

@ -88,19 +88,16 @@ public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureO
} }
#if !SUPPORTS_ANDROID #if !SUPPORTS_ANDROID
// When running on Android, iOS, Mac Catalyst or macOS, ensure the version compiled for // When running on Android, iOS or Mac Catalyst, ensure the version compiled for these platforms
// these platforms is used to prevent the generic/non-OS specific TFM from being used. // is used to prevent the generic/non-OS specific TFM from being used as launching the system
// browser cannot be done using Process.Start() and requires using OS-specific APIs that are
// not available on the portable version of the OpenIddict.Client.SystemIntegration package.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("android"))) if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("android")))
{ {
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449)); throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449));
} }
#endif #endif
#if !SUPPORTS_APPKIT
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449));
}
#endif
#if !SUPPORTS_UIKIT #if !SUPPORTS_UIKIT
if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("ios")) || if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("ios")) ||
RuntimeInformation.IsOSPlatform(OSPlatform.Create("maccatalyst"))) RuntimeInformation.IsOSPlatform(OSPlatform.Create("maccatalyst")))

13
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationExtensions.cs

@ -43,19 +43,16 @@ public static class OpenIddictClientSystemIntegrationExtensions
} }
#if !SUPPORTS_ANDROID #if !SUPPORTS_ANDROID
// When running on Android, iOS, Mac Catalyst or macOS, ensure the version compiled for // When running on Android, iOS or Mac Catalyst, ensure the version compiled for these platforms
// these platforms is used to prevent the generic/non-OS specific TFM from being used. // is used to prevent the generic/non-OS specific TFM from being used as launching the system
// browser cannot be done using Process.Start() and requires using OS-specific APIs that are
// not available on the portable version of the OpenIddict.Client.SystemIntegration package.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("android"))) if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("android")))
{ {
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449)); throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449));
} }
#endif #endif
#if !SUPPORTS_APPKIT
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449));
}
#endif
#if !SUPPORTS_UIKIT #if !SUPPORTS_UIKIT
if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("ios")) || if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("ios")) ||
RuntimeInformation.IsOSPlatform(OSPlatform.Create("maccatalyst"))) RuntimeInformation.IsOSPlatform(OSPlatform.Create("maccatalyst")))

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

@ -5,7 +5,6 @@
*/ */
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Text; using System.Text;
@ -105,8 +104,6 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
#if SUPPORTS_AUTHENTICATION_SERVICES && SUPPORTS_FOUNDATION #if SUPPORTS_AUTHENTICATION_SERVICES && SUPPORTS_FOUNDATION
if (string.IsNullOrEmpty(context.RedirectUri) || if (string.IsNullOrEmpty(context.RedirectUri) ||
!Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri)) !Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri))
@ -239,7 +236,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
NSUrl CreateUrl() => new(OpenIddictHelpers.AddQueryStringParameters( NSUrl CreateUrl() => new(OpenIddictHelpers.AddQueryStringParameters(
uri: new Uri(context.AuthorizationEndpoint, UriKind.Absolute), uri: new Uri(context.AuthorizationEndpoint, UriKind.Absolute),
parameters: context.Transaction.Request.GetParameters().ToDictionary( parameters: context.Request.GetParameters().ToDictionary(
parameter => parameter.Key, parameter => parameter.Key,
parameter => new StringValues((string?[]?) parameter.Value))).AbsoluteUri); parameter => new StringValues((string?[]?) parameter.Value))).AbsoluteUri);
@ -341,8 +338,6 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
#if SUPPORTS_ANDROID && SUPPORTS_ANDROIDX_BROWSER #if SUPPORTS_ANDROID && SUPPORTS_ANDROIDX_BROWSER
if (string.IsNullOrEmpty(context.RedirectUri)) if (string.IsNullOrEmpty(context.RedirectUri))
{ {
@ -369,7 +364,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// custom activity responsible for handling callback URIs pointing to a custom scheme. // custom activity responsible for handling callback URIs pointing to a custom scheme.
intent.LaunchUrl(Application.Context, NativeUri.Parse(OpenIddictHelpers.AddQueryStringParameters( intent.LaunchUrl(Application.Context, NativeUri.Parse(OpenIddictHelpers.AddQueryStringParameters(
uri: new Uri(context.AuthorizationEndpoint, UriKind.Absolute), uri: new Uri(context.AuthorizationEndpoint, UriKind.Absolute),
parameters: context.Transaction.Request.GetParameters().ToDictionary( parameters: context.Request.GetParameters().ToDictionary(
parameter => parameter.Key, parameter => parameter.Key,
parameter => new StringValues((string?[]?) parameter.Value))).AbsoluteUri)!); parameter => new StringValues((string?[]?) parameter.Value))).AbsoluteUri)!);
@ -414,8 +409,6 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
#if SUPPORTS_WINDOWS_RUNTIME #if SUPPORTS_WINDOWS_RUNTIME
if (string.IsNullOrEmpty(context.RedirectUri)) if (string.IsNullOrEmpty(context.RedirectUri))
{ {
@ -454,7 +447,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
options : WebAuthenticationOptions.None, options : WebAuthenticationOptions.None,
requestUri : OpenIddictHelpers.AddQueryStringParameters( requestUri : OpenIddictHelpers.AddQueryStringParameters(
uri: new Uri(context.AuthorizationEndpoint, UriKind.Absolute), uri: new Uri(context.AuthorizationEndpoint, UriKind.Absolute),
parameters: context.Transaction.Request.GetParameters().ToDictionary( parameters: context.Request.GetParameters().ToDictionary(
parameter => parameter.Key, parameter => parameter.Key,
parameter => new StringValues((string?[]?) parameter.Value))), parameter => new StringValues((string?[]?) parameter.Value))),
callbackUri: new Uri(context.RedirectUri, UriKind.Absolute))) callbackUri: new Uri(context.RedirectUri, UriKind.Absolute)))
@ -571,11 +564,9 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
var uri = OpenIddictHelpers.AddQueryStringParameters( var uri = OpenIddictHelpers.AddQueryStringParameters(
uri: new Uri(context.AuthorizationEndpoint, UriKind.Absolute), uri: new Uri(context.AuthorizationEndpoint, UriKind.Absolute),
parameters: context.Transaction.Request.GetParameters().ToDictionary( parameters: context.Request.GetParameters().ToDictionary(
parameter => parameter.Key, parameter => parameter.Key,
parameter => new StringValues((string?[]?) parameter.Value))); parameter => new StringValues((string?[]?) parameter.Value)));
@ -614,6 +605,8 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
} }
} }
// On Android, iOS and Mac Catalyst, Process.Start() is not supported and
// OS-specific/non-portable APIs must be used to launch the system browser.
#if SUPPORTS_ANDROID #if SUPPORTS_ANDROID
if (OperatingSystem.IsAndroid() && TryLaunchBrowserWithGenericIntent(uri)) if (OperatingSystem.IsAndroid() && TryLaunchBrowserWithGenericIntent(uri))
{ {
@ -621,20 +614,26 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
return; return;
} }
#endif #endif
#if SUPPORTS_UIKIT #if SUPPORTS_UIKIT
if ((OperatingSystem.IsIOS() || OperatingSystem.IsMacCatalyst()) && await TryLaunchBrowserWithUIApplicationAsync(uri)) if ((OperatingSystem.IsIOS() || OperatingSystem.IsMacCatalyst()) && await TryLaunchBrowserWithUIApplicationAsync(uri))
{ {
context.HandleRequest(); context.HandleRequest();
return; return;
} }
#elif SUPPORTS_APPKIT #endif
#if SUPPORTS_APPKIT
if (OperatingSystem.IsMacOS() && TryLaunchBrowserWithNSWorkspace(uri)) if (OperatingSystem.IsMacOS() && TryLaunchBrowserWithNSWorkspace(uri))
{ {
context.HandleRequest(); context.HandleRequest();
return; return;
} }
#endif #endif
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && await TryLaunchBrowserWithOpenAsync(uri))
{
context.HandleRequest();
return;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && await TryLaunchBrowserWithXdgOpenAsync(uri)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && await TryLaunchBrowserWithXdgOpenAsync(uri))
{ {
context.HandleRequest(); context.HandleRequest();
@ -670,8 +669,6 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007));
// This handler only applies to HTTP listener requests. If the HTTP context cannot be resolved, // This handler only applies to HTTP listener requests. If the HTTP context cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack. // this may indicate that the request was incorrectly processed by another server stack.
var response = context.Transaction.GetHttpListenerContext()?.Response ?? var response = context.Transaction.GetHttpListenerContext()?.Response ??
@ -683,7 +680,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// Return a message indicating whether the authentication process // Return a message indicating whether the authentication process
// succeeded or failed and that will be visible by the user. // succeeded or failed and that will be visible by the user.
var buffer = Encoding.UTF8.GetBytes(context.Transaction.Response.Error switch var buffer = Encoding.UTF8.GetBytes(context.Response.Error switch
{ {
null or { Length: 0 } => "Login completed. Please return to the application.", null or { Length: 0 } => "Login completed. Please return to the application.",
Errors.AccessDenied => "Authorization denied. Please return to the application.", Errors.AccessDenied => "Authorization denied. Please return to the application.",

33
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Session.cs

@ -5,7 +5,6 @@
*/ */
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Text; using System.Text;
@ -105,8 +104,6 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
#if SUPPORTS_AUTHENTICATION_SERVICES && SUPPORTS_FOUNDATION #if SUPPORTS_AUTHENTICATION_SERVICES && SUPPORTS_FOUNDATION
if (string.IsNullOrEmpty(context.PostLogoutRedirectUri) || if (string.IsNullOrEmpty(context.PostLogoutRedirectUri) ||
!Uri.TryCreate(context.PostLogoutRedirectUri, UriKind.Absolute, out Uri? uri)) !Uri.TryCreate(context.PostLogoutRedirectUri, UriKind.Absolute, out Uri? uri))
@ -239,7 +236,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
NSUrl CreateUrl() => new(OpenIddictHelpers.AddQueryStringParameters( NSUrl CreateUrl() => new(OpenIddictHelpers.AddQueryStringParameters(
uri: new Uri(context.EndSessionEndpoint, UriKind.Absolute), uri: new Uri(context.EndSessionEndpoint, UriKind.Absolute),
parameters: context.Transaction.Request.GetParameters().ToDictionary( parameters: context.Request.GetParameters().ToDictionary(
parameter => parameter.Key, parameter => parameter.Key,
parameter => new StringValues((string?[]?) parameter.Value))).AbsoluteUri); parameter => new StringValues((string?[]?) parameter.Value))).AbsoluteUri);
@ -341,8 +338,6 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
#if SUPPORTS_ANDROID && SUPPORTS_ANDROIDX_BROWSER #if SUPPORTS_ANDROID && SUPPORTS_ANDROIDX_BROWSER
if (string.IsNullOrEmpty(context.PostLogoutRedirectUri)) if (string.IsNullOrEmpty(context.PostLogoutRedirectUri))
{ {
@ -369,7 +364,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// custom activity responsible for handling callback URIs pointing to a custom scheme. // custom activity responsible for handling callback URIs pointing to a custom scheme.
intent.LaunchUrl(Application.Context, NativeUri.Parse(OpenIddictHelpers.AddQueryStringParameters( intent.LaunchUrl(Application.Context, NativeUri.Parse(OpenIddictHelpers.AddQueryStringParameters(
uri: new Uri(context.EndSessionEndpoint, UriKind.Absolute), uri: new Uri(context.EndSessionEndpoint, UriKind.Absolute),
parameters: context.Transaction.Request.GetParameters().ToDictionary( parameters: context.Request.GetParameters().ToDictionary(
parameter => parameter.Key, parameter => parameter.Key,
parameter => new StringValues((string?[]?) parameter.Value))).AbsoluteUri)!); parameter => new StringValues((string?[]?) parameter.Value))).AbsoluteUri)!);
@ -414,8 +409,6 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
#if SUPPORTS_WINDOWS_RUNTIME #if SUPPORTS_WINDOWS_RUNTIME
if (string.IsNullOrEmpty(context.PostLogoutRedirectUri)) if (string.IsNullOrEmpty(context.PostLogoutRedirectUri))
{ {
@ -454,7 +447,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
options : WebAuthenticationOptions.None, options : WebAuthenticationOptions.None,
requestUri : OpenIddictHelpers.AddQueryStringParameters( requestUri : OpenIddictHelpers.AddQueryStringParameters(
uri: new Uri(context.EndSessionEndpoint, UriKind.Absolute), uri: new Uri(context.EndSessionEndpoint, UriKind.Absolute),
parameters: context.Transaction.Request.GetParameters().ToDictionary( parameters: context.Request.GetParameters().ToDictionary(
parameter => parameter.Key, parameter => parameter.Key,
parameter => new StringValues((string?[]?) parameter.Value))), parameter => new StringValues((string?[]?) parameter.Value))),
callbackUri: new Uri(context.PostLogoutRedirectUri, UriKind.Absolute))) callbackUri: new Uri(context.PostLogoutRedirectUri, UriKind.Absolute)))
@ -571,11 +564,9 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
var uri = OpenIddictHelpers.AddQueryStringParameters( var uri = OpenIddictHelpers.AddQueryStringParameters(
uri: new Uri(context.EndSessionEndpoint, UriKind.Absolute), uri: new Uri(context.EndSessionEndpoint, UriKind.Absolute),
parameters: context.Transaction.Request.GetParameters().ToDictionary( parameters: context.Request.GetParameters().ToDictionary(
parameter => parameter.Key, parameter => parameter.Key,
parameter => new StringValues((string?[]?) parameter.Value))); parameter => new StringValues((string?[]?) parameter.Value)));
@ -614,6 +605,8 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
} }
} }
// On Android, iOS and Mac Catalyst, Process.Start() is not supported and
// OS-specific/non-portable APIs must be used to launch the system browser.
#if SUPPORTS_ANDROID #if SUPPORTS_ANDROID
if (OperatingSystem.IsAndroid() && TryLaunchBrowserWithGenericIntent(uri)) if (OperatingSystem.IsAndroid() && TryLaunchBrowserWithGenericIntent(uri))
{ {
@ -621,20 +614,26 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
return; return;
} }
#endif #endif
#if SUPPORTS_UIKIT #if SUPPORTS_UIKIT
if ((OperatingSystem.IsIOS() || OperatingSystem.IsMacCatalyst()) && await TryLaunchBrowserWithUIApplicationAsync(uri)) if ((OperatingSystem.IsIOS() || OperatingSystem.IsMacCatalyst()) && await TryLaunchBrowserWithUIApplicationAsync(uri))
{ {
context.HandleRequest(); context.HandleRequest();
return; return;
} }
#elif SUPPORTS_APPKIT #endif
#if SUPPORTS_APPKIT
if (OperatingSystem.IsMacOS() && TryLaunchBrowserWithNSWorkspace(uri)) if (OperatingSystem.IsMacOS() && TryLaunchBrowserWithNSWorkspace(uri))
{ {
context.HandleRequest(); context.HandleRequest();
return; return;
} }
#endif #endif
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && await TryLaunchBrowserWithOpenAsync(uri))
{
context.HandleRequest();
return;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && await TryLaunchBrowserWithXdgOpenAsync(uri)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && await TryLaunchBrowserWithXdgOpenAsync(uri))
{ {
context.HandleRequest(); context.HandleRequest();
@ -670,8 +669,6 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007));
// This handler only applies to HTTP listener requests. If the HTTP context cannot be resolved, // This handler only applies to HTTP listener requests. If the HTTP context cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack. // this may indicate that the request was incorrectly processed by another server stack.
var response = context.Transaction.GetHttpListenerContext()?.Response ?? var response = context.Transaction.GetHttpListenerContext()?.Response ??
@ -683,7 +680,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// Return a message indicating whether the sign-out process // Return a message indicating whether the sign-out process
// succeeded or failed and that will be visible by the user. // succeeded or failed and that will be visible by the user.
var buffer = Encoding.UTF8.GetBytes(context.Transaction.Response.Error switch var buffer = Encoding.UTF8.GetBytes(context.Response.Error switch
{ {
null or { Length: 0 } => "Logout completed. Please return to the application.", null or { Length: 0 } => "Logout completed. Please return to the application.",
Errors.AccessDenied => "Logout denied. Please return to the application.", Errors.AccessDenied => "Logout denied. Please return to the application.",

57
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHelpers.cs

@ -109,7 +109,7 @@ public static class OpenIddictClientSystemIntegrationHelpers
[SupportedOSPlatformGuard("maccatalyst13.1")] [SupportedOSPlatformGuard("maccatalyst13.1")]
[SupportedOSPlatformGuard("macos10.15")] [SupportedOSPlatformGuard("macos10.15")]
internal static bool IsASWebAuthenticationSessionSupported() internal static bool IsASWebAuthenticationSessionSupported()
#if SUPPORTS_OPERATING_SYSTEM_VERSIONS_COMPARISON #if SUPPORTS_AUTHENTICATION_SERVICES && SUPPORTS_OPERATING_SYSTEM_VERSIONS_COMPARISON
=> OperatingSystem.IsIOSVersionAtLeast(12) || => OperatingSystem.IsIOSVersionAtLeast(12) ||
OperatingSystem.IsMacCatalystVersionAtLeast(13) || OperatingSystem.IsMacCatalystVersionAtLeast(13) ||
OperatingSystem.IsMacOSVersionAtLeast(10, 15); OperatingSystem.IsMacOSVersionAtLeast(10, 15);
@ -124,7 +124,7 @@ public static class OpenIddictClientSystemIntegrationHelpers
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[SupportedOSPlatformGuard("android21.0")] [SupportedOSPlatformGuard("android21.0")]
internal static bool IsCustomTabsIntentSupported() internal static bool IsCustomTabsIntentSupported()
#if SUPPORTS_OPERATING_SYSTEM_VERSIONS_COMPARISON #if SUPPORTS_ANDROIDX_BROWSER && SUPPORTS_OPERATING_SYSTEM_VERSIONS_COMPARISON
=> OperatingSystem.IsAndroidVersionAtLeast(21); => OperatingSystem.IsAndroidVersionAtLeast(21);
#else #else
=> false; => false;
@ -142,7 +142,12 @@ public static class OpenIddictClientSystemIntegrationHelpers
// guard that will prevent the WinRT projections from being loaded by the runtime on // guard that will prevent the WinRT projections from being loaded by the runtime on
// platforms that don't support it. Since OpenIddict declares Windows 10 1809 as the // platforms that don't support it. Since OpenIddict declares Windows 10 1809 as the
// oldest supported version in the package, it is also used for the runtime check. // oldest supported version in the package, it is also used for the runtime check.
internal static bool IsWindowsRuntimeSupported() => IsWindowsVersionAtLeast(10, 0, 17763); internal static bool IsWindowsRuntimeSupported()
#if SUPPORTS_WINDOWS_RUNTIME
=> IsWindowsVersionAtLeast(10, 0, 17763);
#else
=> false;
#endif
/// <summary> /// <summary>
/// Determines whether WinRT app instance activation is supported on this platform. /// Determines whether WinRT app instance activation is supported on this platform.
@ -394,11 +399,10 @@ public static class OpenIddictClientSystemIntegrationHelpers
}; };
/// <summary> /// <summary>
/// Starts the system browser using ShellExecute. /// Starts the system browser using the operating system shell.
/// </summary> /// </summary>
/// <param name="uri">The <see cref="Uri"/> to use.</param> /// <param name="uri">The <see cref="Uri"/> to use.</param>
/// <returns><see langword="true"/> if the browser could be started, <see langword="false"/> otherwise.</returns> /// <returns><see langword="true"/> if the browser could be started, <see langword="false"/> otherwise.</returns>
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
internal static async ValueTask<bool> TryLaunchBrowserWithShellExecuteAsync(Uri uri) internal static async ValueTask<bool> TryLaunchBrowserWithShellExecuteAsync(Uri uri)
{ {
@ -495,7 +499,40 @@ public static class OpenIddictClientSystemIntegrationHelpers
#endif #endif
/// <summary> /// <summary>
/// Starts the system browser using xdg-open. /// Starts the system browser using the "open" executable.
/// </summary>
/// <param name="uri">The <see cref="Uri"/> to use.</param>
/// <returns><see langword="true"/> if the browser could be started, <see langword="false"/> otherwise.</returns>
[SupportedOSPlatform("macos")]
internal static async ValueTask<bool> TryLaunchBrowserWithOpenAsync(Uri uri)
{
try
{
await Task.Run(() => Process.Start(new ProcessStartInfo
{
FileName = "/usr/bin/open",
Arguments = uri.AbsoluteUri,
UseShellExecute = false,
// Note: children processes always inherit the standard input/output/error handles of the
// parent process by default. To ensure the messages logged by the system browser are not
// written to the stdio/stderr of the current process, the streams are always redirected.
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true
}));
return true;
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
return false;
}
}
/// <summary>
/// Starts the system browser using the "xdg-open" executable.
/// </summary> /// </summary>
/// <param name="uri">The <see cref="Uri"/> to use.</param> /// <param name="uri">The <see cref="Uri"/> to use.</param>
/// <returns><see langword="true"/> if the browser could be started, <see langword="false"/> otherwise.</returns> /// <returns><see langword="true"/> if the browser could be started, <see langword="false"/> otherwise.</returns>
@ -510,9 +547,9 @@ public static class OpenIddictClientSystemIntegrationHelpers
Arguments = uri.AbsoluteUri, Arguments = uri.AbsoluteUri,
UseShellExecute = false, UseShellExecute = false,
// Note: on some Linux distributions, xdg-open is known to propagate errors // Note: children processes always inherit the standard input/output/error handles of the
// and warnings written to the standard error stream to the parent process. // parent process by default. To ensure the messages logged by the system browser are not
// To avoid that, the streams are redirected to this instance and ignored. // written to the stdio/stderr of the current process, the streams are always redirected.
RedirectStandardError = true, RedirectStandardError = true,
RedirectStandardInput = true, RedirectStandardInput = true,
RedirectStandardOutput = true RedirectStandardOutput = true
@ -521,7 +558,7 @@ public static class OpenIddictClientSystemIntegrationHelpers
return true; return true;
} }
catch (UnauthorizedAccessException) catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{ {
return false; return false;
} }

2
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHttpListener.cs

@ -116,7 +116,7 @@ public sealed class OpenIddictClientSystemIntegrationHttpListener : BackgroundSe
Stack<Exception>? exceptions = null; Stack<Exception>? exceptions = null;
for (var port = IPEndPoint.MinPort; port <= IPEndPoint.MaxPort; port++) for (var port = IPEndPoint.MinPort; port is <= IPEndPoint.MaxPort; port++)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();

Loading…
Cancel
Save