-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change Windows implementation with our own solution #287
Changes from all commits
6f15b4c
8d01c71
6417081
09e08bb
ffdd000
977f15b
9078a78
1af30f2
d819e8c
5a65812
27b69fa
4008bd7
3a4b412
ff6e143
4298b00
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?xml version="1.0"?> | ||
<package> | ||
<metadata> | ||
<id>Auth0.OidcClient.MAUI</id> | ||
<version>1.0.0-beta.0</version> | ||
<authors>Auth0</authors> | ||
<owners>Auth0</owners> | ||
<license type="expression">Apache-2.0</license> | ||
<projectUrl>https://github.com/auth0/auth0-oidc-client-net</projectUrl> | ||
<icon>Auth0Icon.png</icon> | ||
<requireLicenseAcceptance>false</requireLicenseAcceptance> | ||
<description>Auth0 OIDC Client for MAUI apps</description> | ||
<releaseNotes></releaseNotes> | ||
<copyright>Copyright 2017-2023 Auth0, Inc.</copyright> | ||
<tags>Auth0 OIDC MAUI</tags> | ||
<dependencies> | ||
<group targetFramework="net6.0-android29.0"> | ||
<dependency id="Auth0.OidcClient.Core" version="3.4.1" /> | ||
<dependency id="IdentityModel.OidcClient" version="5.2.1" /> | ||
</group> | ||
<group targetFramework="net6.0-ios13.0"> | ||
<dependency id="Auth0.OidcClient.Core" version="3.4.1" /> | ||
<dependency id="IdentityModel.OidcClient" version="5.2.1" /> | ||
<dependency id="System.Runtime.InteropServices.NFloat.Internal" version="6.0.1" /> | ||
</group> | ||
<group targetFramework="net6.0-maccatalyst14.0"> | ||
<dependency id="Auth0.OidcClient.Core" version="3.4.1" /> | ||
<dependency id="IdentityModel.OidcClient" version="5.2.1" /> | ||
<dependency id="System.Runtime.InteropServices.NFloat.Internal" version="6.0.1" /> | ||
</group> | ||
<group targetFramework="net6.0-windows10.0.19041"> | ||
<dependency id="Auth0.OidcClient.Core" version="3.4.1" /> | ||
<dependency id="IdentityModel.OidcClient" version="5.2.1" /> | ||
<dependency id="Microsoft.WindowsAppSDK" version="1.2.221209.1" /> | ||
</group> | ||
</dependencies> | ||
</metadata> | ||
<files> | ||
<file src="..\src\Auth0.OidcClient.MAUI\bin\Release\net6.0-android\Auth0.OidcClient.dll" target="lib\net6.0-android29.0" /> | ||
<file src="..\src\Auth0.OidcClient.MAUI\bin\Release\net6.0-android\Auth0.OidcClient.xml" target="lib\net6.0-android29.0" /> | ||
<file src="..\src\Auth0.OidcClient.MAUI\bin\Release\net6.0-ios\Auth0.OidcClient.dll" target="lib\net6.0-ios13.0" /> | ||
<file src="..\src\Auth0.OidcClient.MAUI\bin\Release\net6.0-ios\Auth0.OidcClient.xml" target="lib\net6.0-ios13.0" /> | ||
<file src="..\src\Auth0.OidcClient.MAUI\bin\Release\net6.0-maccatalyst\Auth0.OidcClient.dll" target="lib\net6.0-maccatalyst14.0" /> | ||
<file src="..\src\Auth0.OidcClient.MAUI\bin\Release\net6.0-maccatalyst\Auth0.OidcClient.xml" target="lib\net6.0-maccatalyst14.0" /> | ||
<file src="..\src\Auth0.OidcClient.MAUI\bin\Release\net6.0-windows10.0.19041.0\Auth0.OidcClient.dll" target="lib\net6.0-windows10.0.19041" /> | ||
<file src="..\src\Auth0.OidcClient.MAUI\bin\Release\net6.0-windows10.0.19041.0\Auth0.OidcClient.xml" target="lib\net6.0-windows10.0.19041" /> | ||
<file src="..\src\Auth0.OidcClient.MAUI\bin\Release\net6.0-windows10.0.19041.0\Auth0.OidcClient.MAUI.Platforms.Windows.dll" target="lib\net6.0-windows10.0.19041" /> | ||
<file src="..\build\Auth0Icon.png" /> | ||
</files> | ||
</package> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
using Microsoft.Windows.AppLifecycle; | ||
using Windows.ApplicationModel.Activation; | ||
|
||
namespace Auth0.OidcClient.Platforms.Windows | ||
{ | ||
public interface IActivator | ||
{ | ||
bool RedirectActivationChecked { get; } | ||
bool CheckRedirectionActivation(); | ||
} | ||
|
||
/// <summary> | ||
/// Activator class used to enable protocol activation check and redirects activation to the correct application instance | ||
/// </summary> | ||
public sealed class Activator : IActivator | ||
{ | ||
private readonly IAppInstanceProxy _appInstanceProxy; | ||
|
||
public static readonly Activator Default = new Activator(new AppInstanceProxy()); | ||
|
||
internal Activator(IAppInstanceProxy appInstanceProxy) | ||
{ | ||
_appInstanceProxy = appInstanceProxy; | ||
} | ||
|
||
/// <summary> | ||
/// Boolean indication the redirect activation was checked | ||
/// </summary> | ||
public bool RedirectActivationChecked { get; internal set; } | ||
|
||
/// <summary> | ||
/// Performs a protocol activation check and redirects activation to the correct application instance. | ||
/// </summary> | ||
public bool CheckRedirectionActivation() | ||
{ | ||
var activatedEventArgs = _appInstanceProxy.GetCurrentActivatedEventArgs(); | ||
|
||
RedirectActivationChecked = true; | ||
|
||
if (activatedEventArgs is null || activatedEventArgs.Kind != ExtendedActivationKind.Protocol || activatedEventArgs.Data is not IProtocolActivatedEventArgs protocolArgs) | ||
{ | ||
return false; | ||
} | ||
|
||
var ctx = RedirectionContextManager.GetRedirectionContext(protocolArgs); | ||
|
||
if (ctx is not null && ctx.AppInstanceKey is not null && ctx.TaskId is not null) | ||
{ | ||
return _appInstanceProxy.RedirectActivationToAsync(ctx.AppInstanceKey, activatedEventArgs); | ||
} | ||
else | ||
{ | ||
_appInstanceProxy.FindOrRegisterForKey(); | ||
} | ||
return false; | ||
} | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using Microsoft.Windows.AppLifecycle; | ||
|
||
namespace Auth0.OidcClient.Platforms.Windows; | ||
|
||
internal interface IAppActivationArguments | ||
{ | ||
ExtendedActivationKind Kind { get; set; } | ||
object Data { get; set; } | ||
} | ||
|
||
internal class AppActivationArguments : IAppActivationArguments | ||
{ | ||
public ExtendedActivationKind Kind { get; set; } | ||
public object Data { get; set; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
using Microsoft.Windows.AppLifecycle; | ||
|
||
namespace Auth0.OidcClient.Platforms.Windows; | ||
|
||
internal interface IAppInstanceProxy | ||
{ | ||
event EventHandler<IAppActivationArguments> Activated; | ||
string GetCurrentAppKey(); | ||
Microsoft.Windows.AppLifecycle.AppActivationArguments GetCurrentActivatedEventArgs(); | ||
|
||
bool RedirectActivationToAsync(string key, | ||
Microsoft.Windows.AppLifecycle.AppActivationArguments activatedEventArgs); | ||
|
||
void FindOrRegisterForKey(); | ||
} | ||
|
||
/// <summary> | ||
/// Excludes from Code Coverage because of the integration with AppInstance.GetCurrent() | ||
/// </summary> | ||
[ExcludeFromCodeCoverage] | ||
internal class AppInstanceProxy : IAppInstanceProxy | ||
{ | ||
public AppInstanceProxy() | ||
{ | ||
AppInstance.GetCurrent().Activated += OnActivated; | ||
} | ||
|
||
public event EventHandler<IAppActivationArguments> Activated; | ||
|
||
protected virtual void OnActivated(object? sender, Microsoft.Windows.AppLifecycle.AppActivationArguments e) | ||
{ | ||
Activated?.Invoke(this, new AppActivationArguments | ||
{ | ||
Kind = e.Kind, | ||
Data = e.Data | ||
}); | ||
} | ||
|
||
/// <summary> | ||
/// Get the current application key. | ||
/// </summary> | ||
/// <remarks> | ||
/// Proxy call to AppInstance.GetCurrent().Key. | ||
/// Used because AppInstance is complicated to use in tests. | ||
/// </remarks> | ||
/// <returns>The key for the current application.</returns> | ||
public virtual string GetCurrentAppKey() | ||
{ | ||
return AppInstance.GetCurrent().Key; | ||
} | ||
|
||
/// <summary> | ||
/// Get the current application <see cref="AppActivationArguments"/> | ||
/// </summary> | ||
/// <remarks> | ||
/// Proxy call to AppInstance.GetCurrent().GetActivatedEventArgs(). | ||
/// Used because AppInstance is complicated to use in tests. | ||
/// </remarks> | ||
/// <returns>Null if no current application instance is found, or the corresponding <see cref="AppActivationArguments"/>.</returns> | ||
public virtual Microsoft.Windows.AppLifecycle.AppActivationArguments GetCurrentActivatedEventArgs() | ||
{ | ||
return AppInstance.GetCurrent()?.GetActivatedEventArgs(); | ||
} | ||
|
||
/// <summary> | ||
/// Redirect the activation to the correct application instance and kill the current process. | ||
/// </summary> | ||
/// <param name="key">Key of the application to activated</param> | ||
/// <param name="activatedEventArgs"><see cref="AppActivationArguments"/> to pass to the application.</param> | ||
/// <returns>Boolean indicating an application instance was activated.</returns> | ||
public virtual bool RedirectActivationToAsync(string key, Microsoft.Windows.AppLifecycle.AppActivationArguments activatedEventArgs) | ||
{ | ||
var instance = AppInstance.GetInstances().FirstOrDefault(i => i.Key == key); | ||
|
||
if (instance is not null && !instance.IsCurrent) | ||
{ | ||
instance.RedirectActivationToAsync(activatedEventArgs).AsTask().Wait(); | ||
|
||
System.Diagnostics.Process.GetCurrentProcess().Kill(); | ||
|
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/// <summary> | ||
/// Registers the current application using a new key. | ||
/// </summary> | ||
public virtual void FindOrRegisterForKey() | ||
{ | ||
var instance = AppInstance.GetCurrent(); | ||
|
||
if (string.IsNullOrEmpty(instance.Key)) | ||
{ | ||
AppInstance.FindOrRegisterForKey(Guid.NewGuid().ToString()); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFrameworks>net6.0-windows10.0.19041.0</TargetFrameworks> | ||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET --> | ||
<!-- <TargetFrameworks>$(TargetFrameworks);net7.0-tizen</TargetFrameworks> --> | ||
<UseMaui>true</UseMaui> | ||
<SingleProject>true</SingleProject> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
|
||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion> | ||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion> | ||
<AssemblyOriginatorKeyFile>..\..\build\Auth0OidcClientStrongName.snk</AssemblyOriginatorKeyFile> | ||
</PropertyGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
using System.Runtime.InteropServices; | ||
using System.Text; | ||
using System.Xml; | ||
using System.Xml.Linq; | ||
using System.Xml.XPath; | ||
|
||
namespace Auth0.OidcClient.Platforms.Windows | ||
{ | ||
internal interface IHelpers | ||
{ | ||
bool IsAppPackaged { get; } | ||
bool IsUriProtocolDeclared(string scheme); | ||
void OpenBrowser(Uri uri); | ||
} | ||
|
||
internal class Helpers : IHelpers | ||
{ | ||
#pragma warning disable SA1203 // Constants should appear before fields | ||
private const long AppModelErrorNoPackage = 15700L; | ||
#pragma warning restore SA1203 // Constants should appear before fields | ||
|
||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | ||
private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, System.Text.StringBuilder packageFullName); | ||
|
||
/// <summary> | ||
/// Helper property to verify the application is packaged. | ||
/// </summary> | ||
/// <remarks> | ||
/// Original source: https://github.com/dotMorten/WinUIEx | ||
/// </remarks> | ||
/// <returns>A boolean indicate whether or not the app is packaged.</returns> | ||
public bool IsAppPackaged | ||
{ | ||
get | ||
{ | ||
try | ||
{ | ||
// Application is MSIX packaged if it has an identity: https://learn.microsoft.com/en-us/windows/msix/detect-package-identity | ||
int length = 0; | ||
var sb = new StringBuilder(0); | ||
int result = GetCurrentPackageFullName(ref length, sb); | ||
return result != AppModelErrorNoPackage; | ||
} | ||
catch | ||
{ | ||
return false; | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Helper method to verify the scheme is defined as a protocol in the AppxManifest.xml files | ||
/// </summary> | ||
/// <remarks> | ||
/// Original source: https://github.com/dotMorten/WinUIEx | ||
/// </remarks> | ||
/// <param name="scheme">The scheme expected to be declared.</param> | ||
/// <returns>A boolean indicate whether or not the scheme is declared as an Uri protocol.</returns> | ||
public bool IsUriProtocolDeclared(string scheme) | ||
{ | ||
if (global::Windows.ApplicationModel.Package.Current is null) | ||
return false; | ||
var docPath = Path.Combine(global::Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "AppxManifest.xml"); | ||
var doc = XDocument.Load(docPath, LoadOptions.None); | ||
var reader = doc.CreateReader(); | ||
var namespaceManager = new XmlNamespaceManager(reader.NameTable); | ||
namespaceManager.AddNamespace("x", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); | ||
namespaceManager.AddNamespace("uap", "http://schemas.microsoft.com/appx/manifest/uap/windows10"); | ||
|
||
// Check if the protocol was declared | ||
var decl = doc.Root?.XPathSelectElements($"//uap:Extension[@Category='windows.protocol']/uap:Protocol[@Name='{scheme}']", namespaceManager); | ||
|
||
return decl != null && decl.Any(); | ||
} | ||
|
||
/// <summary> | ||
/// Helper method to open the browser through the url.dll. | ||
/// </summary> | ||
/// <param name="uri">The Uri to open</param> | ||
public void OpenBrowser(Uri uri) | ||
{ | ||
var process = new System.Diagnostics.Process(); | ||
process.StartInfo.FileName = "rundll32.exe"; | ||
process.StartInfo.Arguments = $"url.dll,FileProtocolHandler \"{uri.ToString().Replace("\"", "%22")}\""; | ||
process.StartInfo.UseShellExecute = true; | ||
process.Start(); | ||
} | ||
Comment on lines
+80
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before going to GA, we will want to have a decent security review on this. It works as expected, and is also used in other packages, but does look a bit funky. If this turns out to be an issue before we go GA, we can swap for an embedded webview implementation. |
||
|
||
public static string Encode(string value) | ||
{ | ||
var bytes = Encoding.UTF8.GetBytes(value); | ||
return Convert.ToBase64String(bytes); | ||
} | ||
|
||
public static string Decode(string value) | ||
{ | ||
var bytes = Convert.FromBase64String(value); | ||
return Encoding.UTF8.GetString(bytes); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are moving MAUI packaging to follow the same pattern as we do with other SDKs throuhg a nuspec file to better control platform specific dependencies.