From 3a4b4127f0249852e152374bde6947294c171c5f Mon Sep 17 00:00:00 2001 From: frederikprijck Date: Fri, 13 Oct 2023 14:52:48 +0200 Subject: [PATCH] Change Windows implementation with our own solution --- .github/workflows/publish.yml | 1 + Auth0.OidcClient.All.sln | 517 +++++++++++------- nuget/Auth0.OidcClient.MAUI.nuspec | 50 ++ .../Activator.cs | 59 ++ .../AppActivationArguments.cs | 15 + .../AppInstanceProxy.cs | 100 ++++ ...0.OidcClient.MAUI.Platforms.Windows.csproj | 16 + .../Helpers.cs | 101 ++++ .../RedirectionContext.cs | 53 ++ .../RedirectionContextManager.cs | 55 ++ .../StateModifier.cs | 94 ++++ .../TasksManager.cs | 44 ++ .../WebAuthenticator.cs | 99 ++++ .../WebAuthenticatorResult.cs | 34 ++ .../Auth0.OidcClient.MAUI.csproj | 36 +- .../WebAuthenticatorBrowser.cs | 2 +- ...nt.MAUI.Platforms.Windows.UnitTests.csproj | 31 ++ .../GlobalUsings.cs | 1 + .../RedirectionContextManagerTests.cs | 112 ++++ .../StateModifiedTests.cs | 114 ++++ .../TasksManagerTests.cs | 41 ++ .../WebAuthenticatorTests.cs | 288 ++++++++++ 22 files changed, 1630 insertions(+), 233 deletions(-) create mode 100644 nuget/Auth0.OidcClient.MAUI.nuspec create mode 100644 src/Auth0.OidcClient.MAUI.Platforms.Windows/Activator.cs create mode 100644 src/Auth0.OidcClient.MAUI.Platforms.Windows/AppActivationArguments.cs create mode 100644 src/Auth0.OidcClient.MAUI.Platforms.Windows/AppInstanceProxy.cs create mode 100644 src/Auth0.OidcClient.MAUI.Platforms.Windows/Auth0.OidcClient.MAUI.Platforms.Windows.csproj create mode 100644 src/Auth0.OidcClient.MAUI.Platforms.Windows/Helpers.cs create mode 100644 src/Auth0.OidcClient.MAUI.Platforms.Windows/RedirectionContext.cs create mode 100644 src/Auth0.OidcClient.MAUI.Platforms.Windows/RedirectionContextManager.cs create mode 100644 src/Auth0.OidcClient.MAUI.Platforms.Windows/StateModifier.cs create mode 100644 src/Auth0.OidcClient.MAUI.Platforms.Windows/TasksManager.cs create mode 100644 src/Auth0.OidcClient.MAUI.Platforms.Windows/WebAuthenticator.cs create mode 100644 src/Auth0.OidcClient.MAUI.Platforms.Windows/WebAuthenticatorResult.cs create mode 100644 test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests.csproj create mode 100644 test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/GlobalUsings.cs create mode 100644 test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/RedirectionContextManagerTests.cs create mode 100644 test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/StateModifiedTests.cs create mode 100644 test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/TasksManagerTests.cs create mode 100644 test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/WebAuthenticatorTests.cs diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6b55731c..377cd823 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -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 diff --git a/Auth0.OidcClient.All.sln b/Auth0.OidcClient.All.sln index 24bd6e97..5de1d953 100644 --- a/Auth0.OidcClient.All.sln +++ b/Auth0.OidcClient.All.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29411.108 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34031.279 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interactive Test Apps", "Interactive Test Apps", "{F4F201D8-5C51-4060-8D16-D4515DB497F8}" EndProject @@ -16,9 +16,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuGet Packaging", "NuGet Pa nuget\Auth0.OidcClient.WPF.nuspec = nuget\Auth0.OidcClient.WPF.nuspec EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Auth0.OidcClient.Android", "src\Auth0.OidcClient.Android\Auth0.OidcClient.Android.csproj", "{A18A146B-FB5A-4AB8-8F1C-501E58AA7D4D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auth0.OidcClient.Android", "src\Auth0.OidcClient.Android\Auth0.OidcClient.Android.csproj", "{A18A146B-FB5A-4AB8-8F1C-501E58AA7D4D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Auth0.OidcClient.iOS", "src\Auth0.OidcClient.iOS\Auth0.OidcClient.iOS.csproj", "{D6CD228A-D8FA-4250-B770-CCC9CCD5698B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auth0.OidcClient.iOS", "src\Auth0.OidcClient.iOS\Auth0.OidcClient.iOS.csproj", "{D6CD228A-D8FA-4250-B770-CCC9CCD5698B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android", "test\Android\Android.csproj", "{59F6811C-4F4F-4EFD-B46D-F1434830200C}" EndProject @@ -28,23 +28,27 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Unit Tests", "Unit Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auth0.OidcClient.Core.UnitTests", "test\Auth0.OidcClient.Core.UnitTests\Auth0.OidcClient.Core.UnitTests.csproj", "{4AC8498B-95D0-45AD-96E9-EB4388FA7582}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Auth0.OidcClient.AndroidX", "src\Auth0.OidcClient.AndroidX\Auth0.OidcClient.AndroidX.csproj", "{AD54C508-C0A1-4BF7-BA63-D21F078665F6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auth0.OidcClient.AndroidX", "src\Auth0.OidcClient.AndroidX\Auth0.OidcClient.AndroidX.csproj", "{AD54C508-C0A1-4BF7-BA63-D21F078665F6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Auth0.OidcClient.MAUI", "src\Auth0.OidcClient.MAUI\Auth0.OidcClient.MAUI.csproj", "{18757108-68F4-4B5E-B153-4F3E02D33572}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auth0.OidcClient.MAUI", "src\Auth0.OidcClient.MAUI\Auth0.OidcClient.MAUI.csproj", "{18757108-68F4-4B5E-B153-4F3E02D33572}" EndProject -Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "iOS", "test\iOS\iOS.csproj", "{91A68228-855B-4A46-97A3-8CA81DBE81E3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS", "test\iOS\iOS.csproj", "{A47E458A-DF25-4FAC-9B80-5AA2B1870F22}" EndProject -Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "UWP", "test\UWP\UWP.csproj", "{6DA90A36-98C7-4CCF-9A32-3F030E395F1B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UWP", "test\UWP\UWP.csproj", "{E5110B30-EEBA-4514-9E55-EB0C2F00BA12}" EndProject -Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "WinForms", "test\WinForms\WinForms.csproj", "{8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinForms", "test\WinForms\WinForms.csproj", "{A800695B-C5D8-4331-A80F-CE1CE8D842B3}" EndProject -Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "WPF", "test\WPF\WPF.csproj", "{FA2B650D-C89D-4DF8-B24D-09FCACB48533}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPF", "test\WPF\WPF.csproj", "{004EB46A-7001-4158-BEA4-40C21F863BBF}" EndProject -Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "Auth0.OidcClient.UWP", "src\Auth0.OidcClient.UWP\Auth0.OidcClient.UWP.csproj", "{B5885356-0EA1-4E6F-864F-1625E29A658F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auth0.OidcClient.UWP", "src\Auth0.OidcClient.UWP\Auth0.OidcClient.UWP.csproj", "{B5885356-0EA1-4E6F-864F-1625E29A658F}" EndProject -Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "Auth0.OidcClient.WinForms", "src\Auth0.OidcClient.WinForms\Auth0.OidcClient.WinForms.csproj", "{C3EB229E-14FB-4004-AD7C-480EDFAFD843}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auth0.OidcClient.WinForms", "src\Auth0.OidcClient.WinForms\Auth0.OidcClient.WinForms.csproj", "{C3EB229E-14FB-4004-AD7C-480EDFAFD843}" EndProject -Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "Auth0.OidcClient.WPF", "src\Auth0.OidcClient.WPF\Auth0.OidcClient.WPF.csproj", "{C3030559-6BD9-408C-BB21-6637D9883188}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auth0.OidcClient.WPF", "src\Auth0.OidcClient.WPF\Auth0.OidcClient.WPF.csproj", "{C3030559-6BD9-408C-BB21-6637D9883188}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests", "test\Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests\Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests.csproj", "{5DB6D1AB-3252-4833-B97F-1194502F01BF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Auth0.OidcClient.MAUI.Platforms.Windows", "src\Auth0.OidcClient.MAUI.Platforms.Windows\Auth0.OidcClient.MAUI.Platforms.Windows.csproj", "{5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -432,197 +436,197 @@ Global {18757108-68F4-4B5E-B153-4F3E02D33572}.Release|x64.Build.0 = Release|Any CPU {18757108-68F4-4B5E-B153-4F3E02D33572}.Release|x86.ActiveCfg = Release|Any CPU {18757108-68F4-4B5E-B153-4F3E02D33572}.Release|x86.Build.0 = Release|Any CPU - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.AppStore|ARM.ActiveCfg = AppStore|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.AppStore|iPhone.ActiveCfg = AppStore|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.AppStore|iPhone.Build.0 = AppStore|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.AppStore|x64.ActiveCfg = AppStore|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.AppStore|x86.ActiveCfg = AppStore|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Debug|ARM.ActiveCfg = Debug|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Debug|iPhone.ActiveCfg = Debug|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Debug|iPhone.Build.0 = Debug|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Debug|x64.ActiveCfg = Debug|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Debug|x86.ActiveCfg = Debug|iPhoneSimulator - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Debug|x86.Build.0 = Debug|iPhoneSimulator - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Release|Any CPU.ActiveCfg = Release|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Release|ARM.ActiveCfg = Release|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Release|iPhone.ActiveCfg = Release|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Release|iPhone.Build.0 = Release|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Release|x64.ActiveCfg = Release|iPhone - {91A68228-855B-4A46-97A3-8CA81DBE81E3}.Release|x86.ActiveCfg = Release|iPhone - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|Any CPU.ActiveCfg = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|Any CPU.Build.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|Any CPU.Deploy.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|ARM.ActiveCfg = Release|ARM - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|ARM.Build.0 = Release|ARM - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|ARM.Deploy.0 = Release|ARM - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|iPhone.ActiveCfg = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|iPhone.Build.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|iPhone.Deploy.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|x64.ActiveCfg = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|x64.Build.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|x64.Deploy.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|x86.ActiveCfg = Release|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|x86.Build.0 = Release|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Ad-Hoc|x86.Deploy.0 = Release|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|Any CPU.ActiveCfg = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|Any CPU.Build.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|Any CPU.Deploy.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|ARM.ActiveCfg = Release|ARM - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|ARM.Build.0 = Release|ARM - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|ARM.Deploy.0 = Release|ARM - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|iPhone.ActiveCfg = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|iPhone.Build.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|iPhone.Deploy.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|iPhoneSimulator.ActiveCfg = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|iPhoneSimulator.Build.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|iPhoneSimulator.Deploy.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|x64.ActiveCfg = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|x64.Build.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|x64.Deploy.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|x86.ActiveCfg = Release|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|x86.Build.0 = Release|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.AppStore|x86.Deploy.0 = Release|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|Any CPU.ActiveCfg = Debug|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|Any CPU.Build.0 = Debug|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|Any CPU.Deploy.0 = Debug|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|ARM.ActiveCfg = Debug|ARM - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|ARM.Build.0 = Debug|ARM - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|ARM.Deploy.0 = Debug|ARM - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|iPhone.ActiveCfg = Debug|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|iPhoneSimulator.ActiveCfg = Debug|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|x64.ActiveCfg = Debug|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|x64.Build.0 = Debug|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|x64.Deploy.0 = Debug|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|x86.ActiveCfg = Debug|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|x86.Build.0 = Debug|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Debug|x86.Deploy.0 = Debug|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Release|Any CPU.ActiveCfg = Release|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Release|ARM.ActiveCfg = Release|ARM - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Release|ARM.Build.0 = Release|ARM - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Release|ARM.Deploy.0 = Release|ARM - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Release|iPhone.ActiveCfg = Release|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Release|iPhoneSimulator.ActiveCfg = Release|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Release|x64.ActiveCfg = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Release|x64.Build.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Release|x64.Deploy.0 = Release|x64 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Release|x86.ActiveCfg = Release|x86 - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B}.Release|x86.Build.0 = Release|x86 - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Ad-Hoc|ARM.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Ad-Hoc|x64.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Ad-Hoc|x86.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.AppStore|Any CPU.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.AppStore|Any CPU.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.AppStore|ARM.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.AppStore|ARM.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.AppStore|iPhone.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.AppStore|iPhone.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.AppStore|x64.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.AppStore|x64.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.AppStore|x86.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.AppStore|x86.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Debug|ARM.ActiveCfg = Debug|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Debug|ARM.Build.0 = Debug|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Debug|iPhone.Build.0 = Debug|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Debug|x64.ActiveCfg = Debug|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Debug|x64.Build.0 = Debug|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Debug|x86.ActiveCfg = Debug|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Debug|x86.Build.0 = Debug|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Release|Any CPU.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Release|ARM.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Release|ARM.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Release|iPhone.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Release|iPhone.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Release|x64.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Release|x64.Build.0 = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Release|x86.ActiveCfg = Release|Any CPU - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5}.Release|x86.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Ad-Hoc|ARM.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Ad-Hoc|x64.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Ad-Hoc|x86.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.AppStore|Any CPU.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.AppStore|Any CPU.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.AppStore|ARM.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.AppStore|ARM.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.AppStore|iPhone.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.AppStore|iPhone.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.AppStore|x64.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.AppStore|x64.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.AppStore|x86.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.AppStore|x86.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Debug|ARM.ActiveCfg = Debug|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Debug|ARM.Build.0 = Debug|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Debug|iPhone.Build.0 = Debug|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Debug|x64.ActiveCfg = Debug|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Debug|x64.Build.0 = Debug|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Debug|x86.ActiveCfg = Debug|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Debug|x86.Build.0 = Debug|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Release|Any CPU.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Release|ARM.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Release|ARM.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Release|iPhone.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Release|iPhone.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Release|x64.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Release|x64.Build.0 = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Release|x86.ActiveCfg = Release|Any CPU - {FA2B650D-C89D-4DF8-B24D-09FCACB48533}.Release|x86.Build.0 = Release|Any CPU + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.AppStore|ARM.ActiveCfg = AppStore|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.AppStore|iPhone.ActiveCfg = AppStore|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.AppStore|iPhone.Build.0 = AppStore|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.AppStore|x64.ActiveCfg = AppStore|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.AppStore|x86.ActiveCfg = AppStore|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Debug|ARM.ActiveCfg = Debug|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Debug|iPhone.ActiveCfg = Debug|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Debug|iPhone.Build.0 = Debug|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Debug|x64.ActiveCfg = Debug|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Debug|x86.ActiveCfg = Debug|iPhoneSimulator + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Debug|x86.Build.0 = Debug|iPhoneSimulator + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Release|Any CPU.ActiveCfg = Release|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Release|ARM.ActiveCfg = Release|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Release|iPhone.ActiveCfg = Release|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Release|iPhone.Build.0 = Release|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Release|x64.ActiveCfg = Release|iPhone + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22}.Release|x86.ActiveCfg = Release|iPhone + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|Any CPU.ActiveCfg = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|Any CPU.Build.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|Any CPU.Deploy.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|ARM.ActiveCfg = Release|ARM + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|ARM.Build.0 = Release|ARM + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|ARM.Deploy.0 = Release|ARM + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|iPhone.ActiveCfg = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|iPhone.Build.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|iPhone.Deploy.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|x64.ActiveCfg = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|x64.Build.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|x64.Deploy.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|x86.ActiveCfg = Release|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|x86.Build.0 = Release|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Ad-Hoc|x86.Deploy.0 = Release|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|Any CPU.ActiveCfg = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|Any CPU.Build.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|Any CPU.Deploy.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|ARM.ActiveCfg = Release|ARM + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|ARM.Build.0 = Release|ARM + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|ARM.Deploy.0 = Release|ARM + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|iPhone.ActiveCfg = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|iPhone.Build.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|iPhone.Deploy.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|iPhoneSimulator.ActiveCfg = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|iPhoneSimulator.Build.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|iPhoneSimulator.Deploy.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|x64.ActiveCfg = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|x64.Build.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|x64.Deploy.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|x86.ActiveCfg = Release|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|x86.Build.0 = Release|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.AppStore|x86.Deploy.0 = Release|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|Any CPU.ActiveCfg = Debug|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|Any CPU.Build.0 = Debug|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|Any CPU.Deploy.0 = Debug|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|ARM.ActiveCfg = Debug|ARM + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|ARM.Build.0 = Debug|ARM + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|ARM.Deploy.0 = Debug|ARM + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|iPhone.ActiveCfg = Debug|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|iPhoneSimulator.ActiveCfg = Debug|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|x64.ActiveCfg = Debug|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|x64.Build.0 = Debug|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|x64.Deploy.0 = Debug|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|x86.ActiveCfg = Debug|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|x86.Build.0 = Debug|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Debug|x86.Deploy.0 = Debug|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Release|Any CPU.ActiveCfg = Release|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Release|ARM.ActiveCfg = Release|ARM + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Release|ARM.Build.0 = Release|ARM + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Release|ARM.Deploy.0 = Release|ARM + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Release|iPhone.ActiveCfg = Release|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Release|iPhoneSimulator.ActiveCfg = Release|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Release|x64.ActiveCfg = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Release|x64.Build.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Release|x64.Deploy.0 = Release|x64 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Release|x86.ActiveCfg = Release|x86 + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12}.Release|x86.Build.0 = Release|x86 + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Ad-Hoc|ARM.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Ad-Hoc|x64.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.AppStore|Any CPU.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.AppStore|ARM.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.AppStore|ARM.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.AppStore|iPhone.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.AppStore|x64.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.AppStore|x64.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.AppStore|x86.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.AppStore|x86.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Debug|ARM.ActiveCfg = Debug|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Debug|ARM.Build.0 = Debug|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Debug|iPhone.Build.0 = Debug|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Debug|x64.ActiveCfg = Debug|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Debug|x64.Build.0 = Debug|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Debug|x86.ActiveCfg = Debug|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Debug|x86.Build.0 = Debug|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Release|Any CPU.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Release|ARM.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Release|ARM.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Release|iPhone.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Release|iPhone.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Release|x64.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Release|x64.Build.0 = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Release|x86.ActiveCfg = Release|Any CPU + {A800695B-C5D8-4331-A80F-CE1CE8D842B3}.Release|x86.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Ad-Hoc|ARM.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Ad-Hoc|x64.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.AppStore|Any CPU.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.AppStore|ARM.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.AppStore|ARM.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.AppStore|iPhone.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.AppStore|x64.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.AppStore|x64.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.AppStore|x86.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.AppStore|x86.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Debug|ARM.ActiveCfg = Debug|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Debug|ARM.Build.0 = Debug|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Debug|iPhone.Build.0 = Debug|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Debug|x64.ActiveCfg = Debug|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Debug|x64.Build.0 = Debug|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Debug|x86.ActiveCfg = Debug|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Debug|x86.Build.0 = Debug|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Release|Any CPU.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Release|ARM.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Release|ARM.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Release|iPhone.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Release|iPhone.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Release|x64.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Release|x64.Build.0 = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Release|x86.ActiveCfg = Release|Any CPU + {004EB46A-7001-4158-BEA4-40C21F863BBF}.Release|x86.Build.0 = Release|Any CPU {B5885356-0EA1-4E6F-864F-1625E29A658F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {B5885356-0EA1-4E6F-864F-1625E29A658F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {B5885356-0EA1-4E6F-864F-1625E29A658F}.Ad-Hoc|ARM.ActiveCfg = Release|ARM @@ -767,6 +771,102 @@ Global {C3030559-6BD9-408C-BB21-6637D9883188}.Release|x64.Build.0 = Release|Any CPU {C3030559-6BD9-408C-BB21-6637D9883188}.Release|x86.ActiveCfg = Release|Any CPU {C3030559-6BD9-408C-BB21-6637D9883188}.Release|x86.Build.0 = Release|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.AppStore|ARM.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.AppStore|iPhone.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.AppStore|x64.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.AppStore|x64.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.AppStore|x86.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.AppStore|x86.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Debug|ARM.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Debug|ARM.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Debug|iPhone.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Debug|x64.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Debug|x64.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Debug|x86.ActiveCfg = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Debug|x86.Build.0 = Debug|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Release|Any CPU.Build.0 = Release|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Release|ARM.ActiveCfg = Release|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Release|ARM.Build.0 = Release|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Release|iPhone.ActiveCfg = Release|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Release|iPhone.Build.0 = Release|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Release|x64.ActiveCfg = Release|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Release|x64.Build.0 = Release|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Release|x86.ActiveCfg = Release|Any CPU + {5DB6D1AB-3252-4833-B97F-1194502F01BF}.Release|x86.Build.0 = Release|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.AppStore|ARM.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.AppStore|iPhone.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.AppStore|x64.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.AppStore|x64.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.AppStore|x86.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.AppStore|x86.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Debug|ARM.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Debug|ARM.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Debug|iPhone.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Debug|x64.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Debug|x64.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Debug|x86.ActiveCfg = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Debug|x86.Build.0 = Debug|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Release|Any CPU.Build.0 = Release|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Release|ARM.ActiveCfg = Release|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Release|ARM.Build.0 = Release|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Release|iPhone.ActiveCfg = Release|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Release|iPhone.Build.0 = Release|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Release|x64.ActiveCfg = Release|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Release|x64.Build.0 = Release|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Release|x86.ActiveCfg = Release|Any CPU + {5B9F23A7-E4B7-4FB7-B7DC-F208176799D1}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -774,10 +874,11 @@ Global GlobalSection(NestedProjects) = preSolution {59F6811C-4F4F-4EFD-B46D-F1434830200C} = {F4F201D8-5C51-4060-8D16-D4515DB497F8} {4AC8498B-95D0-45AD-96E9-EB4388FA7582} = {2C45E37C-F5A4-4A65-A3C9-BC03AAE66527} - {91A68228-855B-4A46-97A3-8CA81DBE81E3} = {F4F201D8-5C51-4060-8D16-D4515DB497F8} - {6DA90A36-98C7-4CCF-9A32-3F030E395F1B} = {F4F201D8-5C51-4060-8D16-D4515DB497F8} - {8851DCAE-F6B7-4C0C-850F-5C80CBC080F5} = {F4F201D8-5C51-4060-8D16-D4515DB497F8} - {FA2B650D-C89D-4DF8-B24D-09FCACB48533} = {F4F201D8-5C51-4060-8D16-D4515DB497F8} + {A47E458A-DF25-4FAC-9B80-5AA2B1870F22} = {F4F201D8-5C51-4060-8D16-D4515DB497F8} + {E5110B30-EEBA-4514-9E55-EB0C2F00BA12} = {F4F201D8-5C51-4060-8D16-D4515DB497F8} + {A800695B-C5D8-4331-A80F-CE1CE8D842B3} = {F4F201D8-5C51-4060-8D16-D4515DB497F8} + {004EB46A-7001-4158-BEA4-40C21F863BBF} = {F4F201D8-5C51-4060-8D16-D4515DB497F8} + {5DB6D1AB-3252-4833-B97F-1194502F01BF} = {2C45E37C-F5A4-4A65-A3C9-BC03AAE66527} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {12B26EAA-80AA-4E1B-8C76-A4747C72C804} diff --git a/nuget/Auth0.OidcClient.MAUI.nuspec b/nuget/Auth0.OidcClient.MAUI.nuspec new file mode 100644 index 00000000..8ec77973 --- /dev/null +++ b/nuget/Auth0.OidcClient.MAUI.nuspec @@ -0,0 +1,50 @@ + + + + Auth0.OidcClient.MAUI + 1.0.0-beta.0 + Auth0 + Auth0 + Apache-2.0 + https://github.com/auth0/auth0-oidc-client-net + Auth0Icon.png + false + Auth0 OIDC Client for MAUI apps + + Copyright 2017-2023 Auth0, Inc. + Auth0 OIDC MAUI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Auth0.OidcClient.MAUI.Platforms.Windows/Activator.cs b/src/Auth0.OidcClient.MAUI.Platforms.Windows/Activator.cs new file mode 100644 index 00000000..05495923 --- /dev/null +++ b/src/Auth0.OidcClient.MAUI.Platforms.Windows/Activator.cs @@ -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(); + } + + /// + /// Activator class used to enable protocol activation check and redirects activation to the correct application instance + /// + public sealed class Activator : IActivator + { + private readonly IAppInstanceProxy _appInstanceProxy; + + public static readonly Activator Default = new Activator(new AppInstanceProxy()); + + internal Activator(IAppInstanceProxy appInstanceProxy) + { + _appInstanceProxy = appInstanceProxy; + } + + /// + /// Boolean indication the redirect activation was checked + /// + public bool RedirectActivationChecked { get; internal set; } + + /// + /// Performs a protocol activation check and redirects activation to the correct application instance. + /// + 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; + } + + } +} \ No newline at end of file diff --git a/src/Auth0.OidcClient.MAUI.Platforms.Windows/AppActivationArguments.cs b/src/Auth0.OidcClient.MAUI.Platforms.Windows/AppActivationArguments.cs new file mode 100644 index 00000000..936d4aca --- /dev/null +++ b/src/Auth0.OidcClient.MAUI.Platforms.Windows/AppActivationArguments.cs @@ -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; } +} \ No newline at end of file diff --git a/src/Auth0.OidcClient.MAUI.Platforms.Windows/AppInstanceProxy.cs b/src/Auth0.OidcClient.MAUI.Platforms.Windows/AppInstanceProxy.cs new file mode 100644 index 00000000..075bd000 --- /dev/null +++ b/src/Auth0.OidcClient.MAUI.Platforms.Windows/AppInstanceProxy.cs @@ -0,0 +1,100 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Windows.AppLifecycle; + +namespace Auth0.OidcClient.Platforms.Windows; + +internal interface IAppInstanceProxy +{ + event EventHandler Activated; + string GetCurrentAppKey(); + Microsoft.Windows.AppLifecycle.AppActivationArguments GetCurrentActivatedEventArgs(); + + bool RedirectActivationToAsync(string key, + Microsoft.Windows.AppLifecycle.AppActivationArguments activatedEventArgs); + + void FindOrRegisterForKey(); +} + +/// +/// Excludes from Code Coverage because of the integration with AppInstance.GetCurrent() +/// +[ExcludeFromCodeCoverage] +internal class AppInstanceProxy : IAppInstanceProxy +{ + public AppInstanceProxy() + { + AppInstance.GetCurrent().Activated += OnActivated; + } + + public event EventHandler Activated; + + protected virtual void OnActivated(object? sender, Microsoft.Windows.AppLifecycle.AppActivationArguments e) + { + Activated?.Invoke(this, new AppActivationArguments + { + Kind = e.Kind, + Data = e.Data + }); + } + + /// + /// Get the current application key. + /// + /// + /// Proxy call to AppInstance.GetCurrent().Key. + /// Used because AppInstance is complicated to use in tests. + /// + /// The key for the current application. + public virtual string GetCurrentAppKey() + { + return AppInstance.GetCurrent().Key; + } + + /// + /// Get the current application + /// + /// + /// Proxy call to AppInstance.GetCurrent().GetActivatedEventArgs(). + /// Used because AppInstance is complicated to use in tests. + /// + /// Null if no current application instance is found, or the corresponding . + public virtual Microsoft.Windows.AppLifecycle.AppActivationArguments GetCurrentActivatedEventArgs() + { + return AppInstance.GetCurrent()?.GetActivatedEventArgs(); + } + + /// + /// Redirect the activation to the correct application instance and kill the current process. + /// + /// Key of the application to activated + /// to pass to the application. + /// Boolean indicating an application instance was activated. + 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; + } + + /// + /// Registers the current application using a new key. + /// + public virtual void FindOrRegisterForKey() + { + var instance = AppInstance.GetCurrent(); + + if (string.IsNullOrEmpty(instance.Key)) + { + AppInstance.FindOrRegisterForKey(Guid.NewGuid().ToString()); + } + } +} \ No newline at end of file diff --git a/src/Auth0.OidcClient.MAUI.Platforms.Windows/Auth0.OidcClient.MAUI.Platforms.Windows.csproj b/src/Auth0.OidcClient.MAUI.Platforms.Windows/Auth0.OidcClient.MAUI.Platforms.Windows.csproj new file mode 100644 index 00000000..a0258066 --- /dev/null +++ b/src/Auth0.OidcClient.MAUI.Platforms.Windows/Auth0.OidcClient.MAUI.Platforms.Windows.csproj @@ -0,0 +1,16 @@ + + + + net6.0-windows10.0.19041.0 + + + true + true + enable + + 10.0.17763.0 + 10.0.17763.0 + ..\..\build\Auth0OidcClientStrongName.snk + + + diff --git a/src/Auth0.OidcClient.MAUI.Platforms.Windows/Helpers.cs b/src/Auth0.OidcClient.MAUI.Platforms.Windows/Helpers.cs new file mode 100644 index 00000000..8e7aa153 --- /dev/null +++ b/src/Auth0.OidcClient.MAUI.Platforms.Windows/Helpers.cs @@ -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); + + /// + /// Helper property to verify the application is packaged. + /// + /// + /// Original source: https://github.com/dotMorten/WinUIEx + /// + /// A boolean indicate whether or not the app is packaged. + 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; + } + } + } + + /// + /// Helper method to verify the scheme is defined as a protocol in the AppxManifest.xml files + /// + /// + /// Original source: https://github.com/dotMorten/WinUIEx + /// + /// The scheme expected to be declared. + /// A boolean indicate whether or not the scheme is declared as an Uri protocol. + 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(); + } + + /// + /// Helper method to open the browser through the url.dll. + /// + /// The Uri to open + 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); + } + } +} \ No newline at end of file diff --git a/src/Auth0.OidcClient.MAUI.Platforms.Windows/RedirectionContext.cs b/src/Auth0.OidcClient.MAUI.Platforms.Windows/RedirectionContext.cs new file mode 100644 index 00000000..d930075d --- /dev/null +++ b/src/Auth0.OidcClient.MAUI.Platforms.Windows/RedirectionContext.cs @@ -0,0 +1,53 @@ +using System.Collections.Specialized; +using System.Text.Json.Nodes; + +namespace Auth0.OidcClient.Platforms.Windows +{ + internal class RedirectionContext + { + /// + /// The id of the task associated with the current redirect. + /// + internal string TaskId { get; set; } + + /// + /// The key of the application associated with the current redirect. + /// + internal string AppInstanceKey { get; set; } + + /// + /// Generate a new based on the application instance. + /// + /// The current application instance. + /// The newly created + internal static RedirectionContext New(IAppInstanceProxy appInstanceProxy) + { + return new RedirectionContext + { + TaskId = Guid.NewGuid().ToString(), + AppInstanceKey = appInstanceProxy.GetCurrentAppKey() + }; + } + + /// + /// Converts the to a for serialization. + /// + /// A holding an optional state property to incorporate in the . + /// + internal JsonObject ToJsonObject(NameValueCollection query) + { + var jsonObject = new JsonObject + { + { "appInstanceKey", AppInstanceKey }, + { "taskId", TaskId } + }; + + if (query["state"] is string oldState && !string.IsNullOrEmpty(oldState)) + { + jsonObject["state"] = oldState; + } + + return jsonObject; + } + } +} \ No newline at end of file diff --git a/src/Auth0.OidcClient.MAUI.Platforms.Windows/RedirectionContextManager.cs b/src/Auth0.OidcClient.MAUI.Platforms.Windows/RedirectionContextManager.cs new file mode 100644 index 00000000..17fc0e30 --- /dev/null +++ b/src/Auth0.OidcClient.MAUI.Platforms.Windows/RedirectionContextManager.cs @@ -0,0 +1,55 @@ +using System.Runtime.CompilerServices; +using System.Text.Json.Nodes; +using Windows.ApplicationModel.Activation; + +[assembly: InternalsVisibleTo("Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests")] +namespace Auth0.OidcClient.Platforms.Windows +{ + internal class RedirectionContextManager + { + /// + /// Gets the from the provided + /// + /// The event arguments associated with the corresponding protocol activation. + /// The newly created . + internal static RedirectionContext? GetRedirectionContext(IProtocolActivatedEventArgs protocolArgs) + { + var query = System.Web.HttpUtility.ParseQueryString(protocolArgs.Uri.Query); + var state = query["state"]; + JsonObject jsonObject = null; + + if (!string.IsNullOrEmpty(state)) + { + jsonObject = JsonNode.Parse(Helpers.Decode(state)) as JsonObject; + } + + if (jsonObject is not null) + { + return new RedirectionContext + { + AppInstanceKey = TryGetJsonValue(jsonObject, "appInstanceKey"), + TaskId = TryGetJsonValue(jsonObject, "taskId") + }; + } + + return null; + } + + + /// + /// Helper method to try and get a value from a . + /// + /// The corresponding . + /// The key for the value to be retrieve from the + /// The value from the provided key, or null if not found. + private static string TryGetJsonValue(JsonObject jsonObject, string key) + { + if (jsonObject.ContainsKey(key) && jsonObject[key] is JsonValue jValue && jValue.TryGetValue(out string value)) + { + return value; + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Auth0.OidcClient.MAUI.Platforms.Windows/StateModifier.cs b/src/Auth0.OidcClient.MAUI.Platforms.Windows/StateModifier.cs new file mode 100644 index 00000000..3bc56ea0 --- /dev/null +++ b/src/Auth0.OidcClient.MAUI.Platforms.Windows/StateModifier.cs @@ -0,0 +1,94 @@ +using System.Text.Json.Nodes; + +namespace Auth0.OidcClient.Platforms.Windows; + +internal class StateModifier +{ + /// + /// Takes the state query param, and moves it to be a query param on the URL passed to returnTo + /// + /// The Uri instance that contains both a state and returnTo query parameter. + /// + internal static Uri MoveStateToReturnTo(Uri uri) + { + var query = System.Web.HttpUtility.ParseQueryString(uri.Query); + // The state QueryString + var state = query["state"]; + // The original returnTo as configured externally + var returnTo = query["returnTo"]; + + UriBuilder returnToBuilder = new UriBuilder(returnTo!); + + // Get the original returnTo querystring params, so we can append state to it + var returnToQuery = System.Web.HttpUtility.ParseQueryString(new Uri(returnTo).Query); + // Append state as a querystring parameter to returnTo + // We need to escape it for it to be accepted + returnToQuery["state"] = state; + // Set the query again on the returnTo url + returnToBuilder.Query = returnToQuery.ToString() ?? string.Empty; + + // Update returnTo in the original query so that it now includes state + query["returnTo"] = returnToBuilder.Uri.ToString(); + // Remove original state + query.Remove("state"); + + UriBuilder uriBuilder = new UriBuilder(uri); + // Set the query again on the logout url + uriBuilder.Query = query.ToString() ?? string.Empty; + + // Return the Uri + return uriBuilder.Uri; + } + + /// + /// Wraps the state query parameter with the redirection context. + /// + /// The uri containing a state parameter. + /// The used to wrap the state query parameter. + /// + internal static Uri WrapStateWithRedirectionContext(Uri uri, RedirectionContext redirectContext) + { + var query = System.Web.HttpUtility.ParseQueryString(uri.Query); + var redirectContextJson = redirectContext.ToJsonObject(query); + + query["state"] = Helpers.Encode(redirectContextJson.ToJsonString()); + + UriBuilder authorizeUriBuilder = new UriBuilder(uri) + { + Query = query.ToString() ?? string.Empty + }; + + return authorizeUriBuilder.Uri; + } + + /// + /// Unwraps the state and removes the redirection context from it. + /// + /// The uri containing a wrapped state query parameter. + /// The uri contained an unwrapped state query parameter. + internal static Uri UnwrapRedirectionContextFromState(Uri uri) + { + var query = System.Web.HttpUtility.ParseQueryString(uri.Query); + + var state = Helpers.Decode(query["state"]); + + JsonObject jsonObject = JsonNode.Parse(state ?? "{}") as JsonObject; + + var originalState = jsonObject["state"]; + + if (originalState is not null) + { + query["state"] = originalState.ToString(); + } + else + { + query.Remove("state"); + } + + + UriBuilder uriBuilder = new UriBuilder(uri); + uriBuilder.Query = query.ToString(); + return uriBuilder.Uri; + + } +} \ No newline at end of file diff --git a/src/Auth0.OidcClient.MAUI.Platforms.Windows/TasksManager.cs b/src/Auth0.OidcClient.MAUI.Platforms.Windows/TasksManager.cs new file mode 100644 index 00000000..4a13f02a --- /dev/null +++ b/src/Auth0.OidcClient.MAUI.Platforms.Windows/TasksManager.cs @@ -0,0 +1,44 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests")] +namespace Auth0.OidcClient.Platforms.Windows +{ + interface ITasksManager + { + void Remove(string taskId); + void Add(string taskId, TaskCompletionSource tcs); + void ResumeTask(Uri callbackUri, string taskId); + } + + internal sealed class TasksManager : ITasksManager + { + public static readonly TasksManager Default = new TasksManager(); + + private readonly Dictionary> _tasks = new Dictionary>(); + private TasksManager() { } + + public void Remove(string taskId) + { + if (_tasks.ContainsKey(taskId)) + { + _tasks.Remove(taskId); + } + } + + public void Add(string taskId, TaskCompletionSource tcs) + { + _tasks.Add(taskId, tcs); + } + + public void ResumeTask(Uri callbackUri, string taskId) + { + if (taskId != null && _tasks.ContainsKey(taskId)) + { + var task = _tasks[taskId]; + _tasks.Remove(taskId); + task.TrySetResult(callbackUri); + } + } + + } +} \ No newline at end of file diff --git a/src/Auth0.OidcClient.MAUI.Platforms.Windows/WebAuthenticator.cs b/src/Auth0.OidcClient.MAUI.Platforms.Windows/WebAuthenticator.cs new file mode 100644 index 00000000..279f1e6e --- /dev/null +++ b/src/Auth0.OidcClient.MAUI.Platforms.Windows/WebAuthenticator.cs @@ -0,0 +1,99 @@ +using Microsoft.Windows.AppLifecycle; +using System.Runtime.CompilerServices; +using Windows.ApplicationModel.Activation; + +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +namespace Auth0.OidcClient.Platforms.Windows +{ + public sealed class WebAuthenticator + { + private readonly IHelpers _helpers; + private readonly IAppInstanceProxy _appInstanceProxy; + private readonly ITasksManager _tasksManager; + private readonly IActivator _activator; + + public static readonly WebAuthenticator Default = new WebAuthenticator(new AppInstanceProxy(), new Helpers(), TasksManager.Default, Activator.Default); + + internal WebAuthenticator(IAppInstanceProxy appInstanceProxy, IHelpers helpers, ITasksManager tasksManager, IActivator activator) + { + _helpers = helpers; + _appInstanceProxy = appInstanceProxy; + _tasksManager = tasksManager; + _activator = activator; + appInstanceProxy.Activated += CurrentAppInstance_Activated; + } + + /// + /// When the current application is activated, we want to see if there is a corresponding task that needs to be resumed. + /// + /// + /// The event. + private void CurrentAppInstance_Activated(object? sender, IAppActivationArguments e) + { + if (e.Kind == ExtendedActivationKind.Protocol) + { + if (e.Data is IProtocolActivatedEventArgs protocolArgs) + { + var ctx = RedirectionContextManager.GetRedirectionContext(protocolArgs); + + if (ctx is not null && ctx.TaskId is not null) + { + _tasksManager.ResumeTask(protocolArgs.Uri, ctx.TaskId); + } + } + } + } + + /// + /// Begin an authentication flow by navigating to the specified url and waiting for a callback/redirect to the callbackUrl scheme. + /// + /// Url to navigate to, beginning the authentication flow. + /// Expected callback url that the navigation flow will eventually redirect to. + /// Cancellation token. + /// Returns a result parsed out from the callback url. + /// Prior to calling this, a call to must be made during application startup. + /// + public async Task AuthenticateAsync(Uri authorizeUri, Uri callbackUri, CancellationToken cancellationToken = default(CancellationToken)) + { + if (!_activator.RedirectActivationChecked) + { + throw new InvalidOperationException("The redirection check on app activation was not detected. Please make sure a call to Activator.CheckRedirectionActivation was made during App creation."); + } + if (!_helpers.IsAppPackaged) + { + throw new InvalidOperationException("The WebAuthenticator requires a packaged app with an AppxManifest."); + } + if (!_helpers.IsUriProtocolDeclared(callbackUri.Scheme)) + { + throw new InvalidOperationException($"The URI Scheme {callbackUri.Scheme} is not declared in AppxManifest.xml."); + } + + var redirectContext = RedirectionContext.New(_appInstanceProxy); + + authorizeUri = StateModifier.WrapStateWithRedirectionContext(authorizeUri, redirectContext); + + var tcs = new TaskCompletionSource(); + + if (cancellationToken.CanBeCanceled) + { + // When the cancellationToken is used to trigger the cancellation + // we need to cancel the TaskCompletionSource and remove the taskid from the tasks. + cancellationToken.Register(() => + { + tcs.TrySetCanceled(); + _tasksManager.Remove(redirectContext.TaskId); + }); + + cancellationToken.ThrowIfCancellationRequested(); + } + + var newUri = authorizeUri.ToString().IndexOf("logout", StringComparison.CurrentCultureIgnoreCase) > -1 ? StateModifier.MoveStateToReturnTo(authorizeUri) : authorizeUri; + + _helpers.OpenBrowser(newUri); + + _tasksManager.Add(redirectContext.TaskId, tcs); + var uri = await tcs.Task.ConfigureAwait(false); + return new WebAuthenticatorResult(StateModifier.UnwrapRedirectionContextFromState(uri)); + } + } +} \ No newline at end of file diff --git a/src/Auth0.OidcClient.MAUI.Platforms.Windows/WebAuthenticatorResult.cs b/src/Auth0.OidcClient.MAUI.Platforms.Windows/WebAuthenticatorResult.cs new file mode 100644 index 00000000..af5714f4 --- /dev/null +++ b/src/Auth0.OidcClient.MAUI.Platforms.Windows/WebAuthenticatorResult.cs @@ -0,0 +1,34 @@ +namespace Auth0.OidcClient.Platforms.Windows; + +/// +/// Represents a Web Authenticator Result object parsed from the callback Url. +/// +/// +/// All of the query string or url fragment properties are parsed into a dictionary and can be accessed by their key. +/// +public class WebAuthenticatorResult +{ + /// + /// Initializes a new instance of the class by parsing a URI's query string parameters. + /// + /// The callback uri that was used to end the authentication sequence. + public WebAuthenticatorResult(Uri uri) + { + CallbackUri = uri; + var properties = System.Web.HttpUtility.ParseQueryString(uri.Query); + foreach (var key in properties.Keys) + { + Properties[(string) key] = properties[(string)key]; + } + } + + /// + /// The uri that was used to call back. + /// + public Uri CallbackUri { get; } + + /// + /// The dictionary of key/value pairs parsed form the callback URI's query string. + /// + public Dictionary Properties { get; } = new(StringComparer.Ordinal); +} \ No newline at end of file diff --git a/src/Auth0.OidcClient.MAUI/Auth0.OidcClient.MAUI.csproj b/src/Auth0.OidcClient.MAUI/Auth0.OidcClient.MAUI.csproj index 1d33d977..00fe8142 100644 --- a/src/Auth0.OidcClient.MAUI/Auth0.OidcClient.MAUI.csproj +++ b/src/Auth0.OidcClient.MAUI/Auth0.OidcClient.MAUI.csproj @@ -1,4 +1,4 @@ - + net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst @@ -7,41 +7,29 @@ true enable - 14.2 + 13.0 14.0 - 21.0 + 29.0 10.0.17763.0 10.0.17763.0 - README.md + Auth0.OidcClient Auth0.OidcClient - Auth0.OidcClient.MAUI - Auth0.OidcClient - Auth0.OidcClient.MAUI - 1.0.0-beta.0 - Auth0 OIDC Client for MAUI apps - Auth0 - Apache-2.0 - https://auth0.github.io/auth0-oidc-client-net/ - Auth0Icon.png - false - Copyright 2023 Auth0, Inc. - Auth0 OIDC MAUI - https://github.com/auth0/auth0-oidc-client-net - git - true - + + + false + + + true + - - 2.2.0 - - + diff --git a/src/Auth0.OidcClient.MAUI/WebAuthenticatorBrowser.cs b/src/Auth0.OidcClient.MAUI/WebAuthenticatorBrowser.cs index 335fef65..be5aeb08 100644 --- a/src/Auth0.OidcClient.MAUI/WebAuthenticatorBrowser.cs +++ b/src/Auth0.OidcClient.MAUI/WebAuthenticatorBrowser.cs @@ -14,7 +14,7 @@ public async Task InvokeAsync(BrowserOptions options, Cancellatio try { #if WINDOWS - var result = await WinUIEx.WebAuthenticator.AuthenticateAsync(new Uri(options.StartUrl), new Uri(options.EndUrl)); + var result = await Auth0.OidcClient.Platforms.Windows.WebAuthenticator.Default.AuthenticateAsync(new Uri(options.StartUrl), new Uri(options.EndUrl)); #else var result = await WebAuthenticator.Default.AuthenticateAsync(new Uri(options.StartUrl), new Uri(options.EndUrl)); #endif diff --git a/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests.csproj b/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests.csproj new file mode 100644 index 00000000..1bc87ee1 --- /dev/null +++ b/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests.csproj @@ -0,0 +1,31 @@ + + + + net6.0-windows10.0.19041.0 + enable + enable + + false + true + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/GlobalUsings.cs b/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/GlobalUsings.cs new file mode 100644 index 00000000..8c927eb7 --- /dev/null +++ b/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/RedirectionContextManagerTests.cs b/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/RedirectionContextManagerTests.cs new file mode 100644 index 00000000..79be48a8 --- /dev/null +++ b/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/RedirectionContextManagerTests.cs @@ -0,0 +1,112 @@ +using System.Text.Json.Nodes; +using Windows.ApplicationModel.Activation; +using Microsoft.Windows.AppLifecycle; +using Moq; +using WinRT; +using Auth0.OidcClient.Platforms.Windows; + +namespace Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests +{ + public class RedirectionContextManagerTests + { + [Fact] + public void Should_Get_Redirection_Context() + { + var eventArgsMock = new Mock(); + + var jsonObject = new JsonObject + { + { "appInstanceKey", "abc" }, + { "taskId", "def" } + }; + + var query = System.Web.HttpUtility.ParseQueryString(""); + + query["state"] = Helpers.Encode(jsonObject.ToJsonString()); + + UriBuilder uriBuilder = new UriBuilder("http://localhost"); + uriBuilder.Query = query.ToString(); + + eventArgsMock.SetupGet(x => x.Uri).Returns(uriBuilder.Uri); + + var ctx = OidcClient.Platforms.Windows.RedirectionContextManager.GetRedirectionContext(eventArgsMock.Object); + + Assert.Equal("abc", ctx.AppInstanceKey); + Assert.Equal("def", ctx.TaskId); + } + + [Fact] + public void Should_Get_Redirection_Context_When_Escaped() + { + var eventArgsMock = new Mock(); + + var jsonObject = new JsonObject + { + { "appInstanceKey", "abc" }, + { "taskId", "def" } + }; + + + var query = System.Web.HttpUtility.ParseQueryString(""); + + query["state"] = Helpers.Encode(jsonObject.ToJsonString()); + + + UriBuilder uriBuilder = new UriBuilder("http://localhost") + { + Query = query.ToString() + }; + + eventArgsMock.SetupGet(x => x.Uri).Returns(uriBuilder.Uri); + + var ctx = OidcClient.Platforms.Windows.RedirectionContextManager.GetRedirectionContext(eventArgsMock.Object); + + Assert.Equal("abc", ctx.AppInstanceKey); + Assert.Equal("def", ctx.TaskId); + } + + [Fact] + public void Should_Get_Redirection_Context_Without_Values() + { + var eventArgsMock = new Mock(); + + var jsonObject = new JsonObject { }; + + var query = System.Web.HttpUtility.ParseQueryString(""); + + query["state"] = Helpers.Encode(jsonObject.ToJsonString()); + + + UriBuilder uriBuilder = new UriBuilder("http://localhost") + { + Query = query.ToString() + }; + + eventArgsMock.SetupGet(x => x.Uri).Returns(uriBuilder.Uri); + + var ctx = OidcClient.Platforms.Windows.RedirectionContextManager.GetRedirectionContext(eventArgsMock.Object); + + Assert.Null(ctx.AppInstanceKey); + Assert.Null(ctx.TaskId); + } + + [Fact] + public void Should_Get_null() + { + var eventArgsMock = new Mock(); + + var query = System.Web.HttpUtility.ParseQueryString(""); + + UriBuilder uriBuilder = new UriBuilder("http://localhost") + { + Query = query.ToString() + }; + + eventArgsMock.SetupGet(x => x.Uri).Returns(uriBuilder.Uri); + + var ctx = OidcClient.Platforms.Windows.RedirectionContextManager.GetRedirectionContext(eventArgsMock.Object); + + Assert.Null(ctx); + } + } +} \ No newline at end of file diff --git a/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/StateModifiedTests.cs b/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/StateModifiedTests.cs new file mode 100644 index 00000000..06b2a518 --- /dev/null +++ b/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/StateModifiedTests.cs @@ -0,0 +1,114 @@ +using System.Collections.Specialized; +using Auth0.OidcClient.Platforms.Windows; +using System.Text.Json.Nodes; + +namespace Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests; + +public class StateModifiedTests +{ + [Fact] + public void Should_move_state_to_returnTo() + { + var originalUri = new Uri("http://www.my-idp.com?state=abc&returnTo=myapp://callback"); + + var newUri = StateModifier.MoveStateToReturnTo(originalUri); + + var newQuery = System.Web.HttpUtility.ParseQueryString(newUri.Query); + var newReturnToUri = new Uri(newQuery["returnTo"] ?? string.Empty); + + var newReturnToQuery = System.Web.HttpUtility.ParseQueryString(newReturnToUri.Query); + + Assert.Equal(newReturnToQuery["state"], "abc"); + Assert.Null(newQuery["state"]); + } + + [Fact] + public void Should_reset_raw_state() + { + var jsonObject = new JsonObject + { + { "appInstanceKey", "123" }, + { "taskId", "456" } + }; + + jsonObject["state"] = "abc"; + + var originalUriBuilder = new UriBuilder("http://www.my-idp.com"); + + var query = System.Web.HttpUtility.ParseQueryString(new Uri("http://www.my-idp.com").Query); + + query["state"] = Helpers.Encode(jsonObject.ToJsonString()); + + + originalUriBuilder.Query = query.ToString(); + + + + var newUri = StateModifier.UnwrapRedirectionContextFromState(originalUriBuilder.Uri); + + var newQuery = System.Web.HttpUtility.ParseQueryString(newUri.Query); + var newState = newQuery["state"]; + + Assert.Equal(newState, "abc"); + } + + [Fact] + public void Should_reset_raw_state_when_escaped() + { + var jsonObject = new JsonObject + { + { "appInstanceKey", "123" }, + { "taskId", "456" } + }; + + jsonObject["state"] = "abc"; + + var originalUriBuilder = new UriBuilder("http://www.my-idp.com"); + + var query = System.Web.HttpUtility.ParseQueryString(new Uri("http://www.my-idp.com").Query); + + query["state"] = Helpers.Encode(jsonObject.ToJsonString()); + + + originalUriBuilder.Query = query.ToString(); + + + + var newUri = StateModifier.UnwrapRedirectionContextFromState(originalUriBuilder.Uri); + + var newQuery = System.Web.HttpUtility.ParseQueryString(newUri.Query); + var newState = newQuery["state"]; + + Assert.Equal(newState, "abc"); + } + + [Fact] + public void Should_remove_state_when_no_original_state() + { + var jsonObject = new JsonObject + { + { "appInstanceKey", "123" }, + { "taskId", "456" } + }; + + + var originalUriBuilder = new UriBuilder("http://www.my-idp.com"); + + var query = System.Web.HttpUtility.ParseQueryString(new Uri("http://www.my-idp.com").Query); + + query["state"] = Helpers.Encode(jsonObject.ToJsonString()); + + + originalUriBuilder.Query = query.ToString(); + + + + var newUri = StateModifier.UnwrapRedirectionContextFromState(originalUriBuilder.Uri); + + var newQuery = System.Web.HttpUtility.ParseQueryString(newUri.Query); + var newState = newQuery["state"]; + + Assert.Null(newState); + } + +} \ No newline at end of file diff --git a/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/TasksManagerTests.cs b/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/TasksManagerTests.cs new file mode 100644 index 00000000..82f7344c --- /dev/null +++ b/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/TasksManagerTests.cs @@ -0,0 +1,41 @@ +using Auth0.OidcClient.Platforms.Windows; + +namespace Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests +{ + public class TasksManagerTests + { + [Fact] + public void Should_add_and_resume_task() + { + var taskId = Guid.NewGuid().ToString(); + + var tcs = new TaskCompletionSource(); + + TasksManager.Default.Add(taskId, tcs); + + var callbackUri = new Uri("myapp://callback"); + + TasksManager.Default.ResumeTask(callbackUri, taskId); + + Assert.True(tcs.Task.IsCompleted); + Assert.Equal(tcs.Task.Result, callbackUri); + } + + [Fact] + public void Should_not_resume_task_when_removed() + { + var taskId = Guid.NewGuid().ToString(); + + var tcs = new TaskCompletionSource(); + + TasksManager.Default.Add(taskId, tcs); + TasksManager.Default.Remove(taskId); + + var callbackUri = new Uri("myapp://callback"); + + TasksManager.Default.ResumeTask(callbackUri, "123"); + + Assert.False(tcs.Task.IsCompleted); + } + } +} \ No newline at end of file diff --git a/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/WebAuthenticatorTests.cs b/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/WebAuthenticatorTests.cs new file mode 100644 index 00000000..992e20ef --- /dev/null +++ b/test/Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests/WebAuthenticatorTests.cs @@ -0,0 +1,288 @@ +using Auth0.OidcClient.Platforms.Windows; +using Microsoft.Windows.AppLifecycle; +using Moq; +using System.Text.Json.Nodes; +using Windows.ApplicationModel.Activation; +using Activator = Auth0.OidcClient.Platforms.Windows.Activator; +using AppActivationArguments = Auth0.OidcClient.Platforms.Windows.AppActivationArguments; + +namespace Auth0.OidcClient.MAUI.Platforms.Windows.UnitTests; + +public class WebAuthenticatorTests { + + [Fact] + public async void Should_Throw_With_Default() + { + await Assert.ThrowsAsync(() => + WebAuthenticator.Default.AuthenticateAsync(new Uri("http://www.idp.com"), new Uri("nyapp://callback"))); + + } + + [Fact] + public async void Should_Throw_When_Not_Checked_For_Redirection_Activation() + { + var mockAppInstance = new Mock(); + var mockHelpers = new Mock(); + var mockTasksManager = new Mock(); + var mockActivator = new Mock(); + + mockActivator.SetupGet(h => h.RedirectActivationChecked).Returns(false); + + var exception = await Assert.ThrowsAsync(() => + new WebAuthenticator(mockAppInstance.Object, mockHelpers.Object, mockTasksManager.Object, mockActivator.Object).AuthenticateAsync(new Uri("http://www.idp.com"), new Uri("nyapp://callback"))); + + Assert.Equal("The redirection check on app activation was not detected. Please make sure a call to Activator.CheckRedirectionActivation was made during App creation.", exception.Message); + } + + [Fact] + public async void Should_Not_Throw_When_Checked_For_Redirection_Activation() + { + var mockAppInstance = new Mock(); + var mockHelpers = new Mock(); + var mockTasksManager = new Mock(); + var mockActivator = new Mock(); + + mockActivator.SetupGet(h => h.RedirectActivationChecked).Returns(true); + + var exception = await Assert.ThrowsAsync(() => + new WebAuthenticator(mockAppInstance.Object, mockHelpers.Object, mockTasksManager.Object, mockActivator.Object).AuthenticateAsync(new Uri("http://www.idp.com"), new Uri("nyapp://callback"))); + + Assert.NotEqual("The redirection check on app activation was not detected. Please make sure a call to Activator.CheckRedirectionActivation was made during App creation.", exception.Message); + } + + [Fact] + public async void Should_Throw_When_App_Not_Packaged() + { + var mockAppInstance = new Mock(); + var mockHelpers = new Mock(); + var mockTasksManager = new Mock(); + var mockActivator = new Mock(); + + mockActivator.SetupGet(h => h.RedirectActivationChecked).Returns(true); + + mockHelpers.SetupGet(h => h.IsAppPackaged).Returns(false); + + var exception = await Assert.ThrowsAsync(() => + new WebAuthenticator(mockAppInstance.Object, mockHelpers.Object, mockTasksManager.Object, mockActivator.Object).AuthenticateAsync(new Uri("http://www.idp.com"), new Uri("nyapp://callback"))); + + Assert.Equal("The WebAuthenticator requires a packaged app with an AppxManifest.", exception.Message); + } + + [Fact] + public async void Should_Throw_When_Uri_Protocol_Not_Declared() + { + var mockAppInstance = new Mock(); + var mockHelpers = new Mock(); + var mockTasksManager = new Mock(); + var mockActivator = new Mock(); + + mockActivator.SetupGet(h => h.RedirectActivationChecked).Returns(true); + mockHelpers.SetupGet(h => h.IsAppPackaged).Returns(true); + mockHelpers.Setup(h => h.IsUriProtocolDeclared("myapp")).Returns(false); + + var exception = await Assert.ThrowsAsync(() => + new WebAuthenticator(mockAppInstance.Object, mockHelpers.Object, mockTasksManager.Object, mockActivator.Object).AuthenticateAsync(new Uri("http://www.idp.com"), new Uri("myapp://callback"))); + + Assert.Equal($"The URI Scheme myapp is not declared in AppxManifest.xml.", exception.Message); + } + + [Fact] + public void Should_Open_Browser_With_State() + { + var mockAppInstance = new Mock(); + var mockHelpers = new Mock(); + var mockTasksManager = new Mock(); + var mockActivator = new Mock(); + + mockActivator.SetupGet(h => h.RedirectActivationChecked).Returns(true); + mockHelpers.SetupGet(h => h.IsAppPackaged).Returns(true); + mockHelpers.Setup(h => h.IsUriProtocolDeclared("myapp")).Returns(true); + mockHelpers.Setup(h => h.OpenBrowser(It.IsAny())); + mockAppInstance.Setup(a => a.GetCurrentAppKey()).Returns("test"); + + var webAuthenticator = new WebAuthenticator(mockAppInstance.Object, mockHelpers.Object, mockTasksManager.Object, mockActivator.Object); + + // Do no await so we can leave the method again +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + webAuthenticator.AuthenticateAsync(new Uri("http://www.idp.com"), new Uri("myapp://callback")); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + + + mockHelpers.Verify(d => d.OpenBrowser(It.Is(uri => HasStateParam(uri, "appInstanceKey") && HasStateParam(uri, "taskId") ))); + } + + [Fact] + public async void Should_return_WebAuthenticatorResult_With_Original_State() + { + var mockAppInstance = new Mock(); + var mockHelpers = new Mock(); + var mockTasksManager = new Mock(); + Uri authorizeUri = new Uri("https://www.idp.com"); + var mockActivator = new Mock(); + + mockActivator.SetupGet(h => h.RedirectActivationChecked).Returns(true); + mockHelpers.SetupGet(h => h.IsAppPackaged).Returns(true); + mockHelpers.Setup(h => h.IsUriProtocolDeclared("myapp")).Returns(true); + mockHelpers.Setup(h => h.OpenBrowser(It.IsAny())).Callback((Uri uri) => + { + authorizeUri = uri; + }); + mockAppInstance.Setup(a => a.GetCurrentAppKey()).Returns("test"); + + mockTasksManager.Setup(t => t.Add(It.IsAny(), It.IsAny>())).Callback((string taskId, TaskCompletionSource task) => + { + var query = System.Web.HttpUtility.ParseQueryString(authorizeUri.Query); + var state = query["state"]; + task.TrySetResult(new Uri($"myapp://callback?state={state}")); + }); + + var webAuthenticator = new WebAuthenticator(mockAppInstance.Object, mockHelpers.Object, mockTasksManager.Object, mockActivator.Object); + var result = await webAuthenticator.AuthenticateAsync(new Uri("http://www.idp.com/?state=abc"), new Uri("myapp://callback")); + + Assert.NotNull(result); + Assert.Equal("myapp://callback/?state=abc", result.CallbackUri.ToString()); + } + + [Fact] + public void Should_Remove_Task_On_Cancel() + { + var mockAppInstance = new Mock(); + var mockHelpers = new Mock(); + var mockTasksManager = new Mock(); + var mockActivator = new Mock(); + + mockActivator.SetupGet(h => h.RedirectActivationChecked).Returns(true); + mockHelpers.SetupGet(h => h.IsAppPackaged).Returns(true); + mockHelpers.Setup(h => h.IsUriProtocolDeclared("myapp")).Returns(true); + mockHelpers.Setup(h => h.OpenBrowser(It.IsAny())); + mockAppInstance.Setup(a => a.GetCurrentAppKey()).Returns("test"); + + var cancellationTokenSource = new CancellationTokenSource(); + var webAuthenticator = new WebAuthenticator(mockAppInstance.Object, mockHelpers.Object, mockTasksManager.Object, mockActivator.Object); + + webAuthenticator.AuthenticateAsync(new Uri("http://www.idp.com"), new Uri("myapp://callback"), cancellationTokenSource.Token); + + cancellationTokenSource.Cancel(); + + mockTasksManager.Verify(d => d.Remove(It.IsAny())); + } + + [Fact] + public async void Should_Throw_If_Cancelled_Before() + { + var mockAppInstance = new Mock(); + var mockHelpers = new Mock(); + var mockTasksManager = new Mock(); + var mockActivator = new Mock(); + + mockActivator.SetupGet(h => h.RedirectActivationChecked).Returns(true); + mockHelpers.SetupGet(h => h.IsAppPackaged).Returns(true); + mockHelpers.Setup(h => h.IsUriProtocolDeclared("myapp")).Returns(true); + mockHelpers.Setup(h => h.OpenBrowser(It.IsAny())); + mockAppInstance.Setup(a => a.GetCurrentAppKey()).Returns("test"); + + var cancellationTokenSource = new CancellationTokenSource(); + var webAuthenticator = new WebAuthenticator(mockAppInstance.Object, mockHelpers.Object, mockTasksManager.Object, mockActivator.Object); + + cancellationTokenSource.Cancel(); + + await Assert.ThrowsAsync(() => + webAuthenticator.AuthenticateAsync(new Uri("http://www.idp.com"), new Uri("myapp://callback"), + cancellationTokenSource.Token)); + } + + [Fact] + public void Should_Resume_On_Activated() + { + var mockAppInstance = new MockAppInstanceProxy(); + var mockHelpers = new Mock(); + var mockTasksManager = new Mock(); + var mockActivator = new Mock(); + + mockActivator.SetupGet(h => h.RedirectActivationChecked).Returns(true); + mockHelpers.SetupGet(h => h.IsAppPackaged).Returns(true); + mockHelpers.Setup(h => h.IsUriProtocolDeclared("myapp")).Returns(true); + mockHelpers.Setup(h => h.OpenBrowser(It.IsAny())); + + new WebAuthenticator(mockAppInstance, mockHelpers.Object, mockTasksManager.Object, mockActivator.Object); + + var jsonObject = new JsonObject + { + { "appInstanceKey", "abc" }, + { "taskId", "def" } + }; + + var query = System.Web.HttpUtility.ParseQueryString(""); + + query["state"] = Helpers.Encode(jsonObject.ToJsonString()); + + UriBuilder uriBuilder = new UriBuilder("myapp://callback") + { + Query = query.ToString() + }; + + mockAppInstance.OnActivated(null, new AppActivationArguments() + { + Data = new MockProtocolActivatedEventArgs() + { + Uri = uriBuilder.Uri, + }, + Kind = ExtendedActivationKind.Protocol + }); + + mockTasksManager.Verify(d => d.ResumeTask(It.IsAny(), "def")); + } + + private bool HasStateParam(Uri uri, string paramName) + { + var query = System.Web.HttpUtility.ParseQueryString(uri.Query); + + var state = Helpers.Decode(query["state"]); + var jsonObject = JsonNode.Parse(state) as JsonObject; + + return jsonObject[paramName] != null; + + } +} + +internal class MockAppInstanceProxy : IAppInstanceProxy +{ + public MockAppInstanceProxy() + { + } + + public event EventHandler Activated; + + public void OnActivated(object? sender, AppActivationArguments e) + { + Activated?.Invoke(this, e); + } + + public virtual string GetCurrentAppKey() + { + return AppInstance.GetCurrent().Key; + } + + public Microsoft.Windows.AppLifecycle.AppActivationArguments GetCurrentActivatedEventArgs() + { + return null; + } + + public bool RedirectActivationToAsync(string key, Microsoft.Windows.AppLifecycle.AppActivationArguments activatedEventArgs) + { + return true; + } + + public void FindOrRegisterForKey() + { + + } +} + +public class MockProtocolActivatedEventArgs : IProtocolActivatedEventArgs +{ + public ActivationKind Kind { get; } + public ApplicationExecutionState PreviousExecutionState { get; } + public SplashScreen SplashScreen { get; } + public Uri Uri { get; set; } +} \ No newline at end of file