Skip to content

Commit

Permalink
Change Windows implementation with our own solution
Browse files Browse the repository at this point in the history
  • Loading branch information
frederikprijck committed Oct 26, 2023
1 parent 47ef0f9 commit 3a4b412
Show file tree
Hide file tree
Showing 22 changed files with 1,630 additions and 233 deletions.
1 change: 1 addition & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ jobs:
nuget pack nuget/Auth0.OidcClient.AndroidX.nuspec
nuget pack nuget/Auth0.OidcClient.Core.nuspec
nuget pack nuget/Auth0.OidcClient.iOS.nuspec
nuget pack nuget/Auth0.OidcClient.MAUI.nuspec
nuget pack nuget/Auth0.OidcClient.UWP.nuspec
nuget pack nuget/Auth0.OidcClient.WinForms.nuspec
nuget pack nuget/Auth0.OidcClient.WPF.nuspec
Expand Down
517 changes: 309 additions & 208 deletions Auth0.OidcClient.All.sln

Large diffs are not rendered by default.

50 changes: 50 additions & 0 deletions nuget/Auth0.OidcClient.MAUI.nuspec
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>
59 changes: 59 additions & 0 deletions src/Auth0.OidcClient.MAUI.Platforms.Windows/Activator.cs
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; }
}
100 changes: 100 additions & 0 deletions src/Auth0.OidcClient.MAUI.Platforms.Windows/AppInstanceProxy.cs
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)

Check warning on line 31 in src/Auth0.OidcClient.MAUI.Platforms.Windows/AppInstanceProxy.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
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>
101 changes: 101 additions & 0 deletions src/Auth0.OidcClient.MAUI.Platforms.Windows/Helpers.cs
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();
}

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);
}
}
}
Loading

0 comments on commit 3a4b412

Please sign in to comment.