Adding WindowsProvider project

This commit is contained in:
Shane Weaver 2021-03-25 13:50:14 -07:00
Родитель 035e1b6dff
Коммит d36ef91426
3 изменённых файлов: 356 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,38 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFramework>uap10.0.17763</TargetFramework>
<Title>Windows Community Toolkit Graph Uwp Authentication Provider</Title>
<PackageId>CommunityToolkit.Uwp.Authentication</PackageId>
<Description>
This library provides an authentication provider based on the native Windows dialogues. It is part of the Windows Community Toolkit.
Classes:
- WindowsProvider:
</Description>
<PackageTags>UWP Toolkit Windows Microsoft Graph AadLogin Authentication Login</PackageTags>
<SignAssembly>false</SignAssembly>
<GenerateLibraryLayout>true</GenerateLibraryLayout>
<LangVersion>8.0</LangVersion>
<Configurations>Debug;Release;CI</Configurations>
<Platforms>AnyCPU;ARM;ARM64;x64;x86</Platforms>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\CommunityToolkit.Net.Authentication\CommunityToolkit.Net.Authentication.csproj" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0.17763'">
<DefineConstants Condition="'$(DisableImplicitFrameworkDefines)' != 'true'">$(DefineConstants);WINRT</DefineConstants>
</PropertyGroup>
<Import Project="$(MSBuildSDKExtrasTargets)" Condition="Exists('$(MSBuildSDKExtrasTargets)')" />
<!-- https://weblogs.asp.net/rweigelt/disable-warnings-in-generated-c-files-of-uwp-app -->
<Target Name="PragmaWarningDisablePrefixer" AfterTargets="MarkupCompilePass2">
<ItemGroup>
<GeneratedCSFiles Include="**\*.g.cs;**\*.g.i.cs" />
</ItemGroup>
<Message Text="CSFiles: @(GeneratedCSFiles->'&quot;%(Identity)&quot;')" />
<Exec Command="for %%f in (@(GeneratedCSFiles->'&quot;%(Identity)&quot;')) do echo #pragma warning disable &gt; %%f.temp &amp;&amp; type %%f &gt;&gt; %%f.temp &amp;&amp; move /y %%f.temp %%f &gt; NUL" />
</Target>
</Project>

Просмотреть файл

@ -0,0 +1,276 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using CommunityToolkit.Net.Authentication;
using Windows.Security.Authentication.Web;
using Windows.Security.Authentication.Web.Core;
using Windows.UI.ApplicationSettings;
namespace Microsoft.Toolkit.Graph.Providers.Uwp
{
/// <summary>
/// A provider for leveraging Windows system authentication.
/// </summary>
public class WindowsProvider : BaseProvider
{
private struct AuthenticatedUser
{
public Windows.Security.Credentials.PasswordCredential TokenCredential { get; private set; }
public string GetUserName()
{
return TokenCredential?.UserName;
}
public string GetToken()
{
return TokenCredential?.Password;
}
public AuthenticatedUser(Windows.Security.Credentials.PasswordCredential tokenCredential)
{
TokenCredential = tokenCredential;
}
}
private const string TokenCredentialResourceName = "WindowsProviderToken";
private const string WebAccountProviderId = "https://login.microsoft.com";
private static readonly string[] DefaultScopes = new string[] { "user.read" };
private static readonly string GraphResourceProperty = "https://graph.microsoft.com";
/// <summary>
/// Gets the redirect uri value based on the current app callback uri.
/// </summary>
public static string RedirectUri => string.Format("ms-appx-web://Microsoft.AAD.BrokerPlugIn/{0}", WebAuthenticationBroker.GetCurrentApplicationCallbackUri().Host.ToUpper());
private AccountsSettingsPane _currentPane;
private AuthenticatedUser? _currentUser;
private string[] _scopes;
private string _clientId;
/// <summary>
/// Initializes a new instance of the <see cref="WindowsProvider"/> class.
/// </summary>
/// <param name="clientId">The clientId for the app registration.</param>
/// <param name="scopes">The security scopes used to access specific workloads.</param>
public WindowsProvider(string clientId, string[] scopes = null)
{
_clientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
_currentPane = null;
_currentUser = null;
_scopes = scopes ?? DefaultScopes;
State = ProviderState.SignedOut;
_ = TrySilentSignInAsync();
}
/// <summary>
/// Attempts to sign in the logged in user automatically.
/// </summary>
/// <returns>Success boolean.</returns>
public async Task<bool> TrySilentSignInAsync()
{
if (State == ProviderState.SignedIn)
{
return false;
}
State = ProviderState.Loading;
var tokenCredential = GetCredentialFromLocker();
if (tokenCredential == null)
{
// There is no credential stored in the locker.
State = ProviderState.SignedOut;
return false;
}
// Populate the password (aka token).
tokenCredential.RetrievePassword();
// Log the user in by storing the credential in memory.
_currentUser = new AuthenticatedUser(tokenCredential);
try
{
var testRequest = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1/me");
await AuthenticateRequestAsync(testRequest);
await new HttpClient().SendAsync(testRequest);
// Update the state to be signed in.
State = ProviderState.SignedIn;
return true;
}
catch
{
// Update the state to be signed in.
State = ProviderState.SignedOut;
return false;
}
}
/// <inheritdoc />
public override Task LoginAsync()
{
if (State == ProviderState.SignedIn)
{
return Task.CompletedTask;
}
State = ProviderState.Loading;
if (_currentPane != null)
{
_currentPane.AccountCommandsRequested -= BuildPaneAsync;
}
_currentPane = AccountsSettingsPane.GetForCurrentView();
_currentPane.AccountCommandsRequested += BuildPaneAsync;
AccountsSettingsPane.Show();
return Task.CompletedTask;
}
/// <inheritdoc />
public override Task LogoutAsync()
{
if (State == ProviderState.SignedOut)
{
return Task.CompletedTask;
}
State = ProviderState.Loading;
if (_currentPane != null)
{
_currentPane.AccountCommandsRequested -= BuildPaneAsync;
_currentPane = null;
}
if (_currentUser != null)
{
// Remove the user info from the PaasswordVault
var vault = new Windows.Security.Credentials.PasswordVault();
vault.Remove(_currentUser?.TokenCredential);
_currentUser = null;
}
State = ProviderState.SignedOut;
return Task.CompletedTask;
}
/// <inheritdoc />
public override Task AuthenticateRequestAsync(HttpRequestMessage request)
{
// Append the token to the authorization header of any outgoing Graph requests.
var token = _currentUser?.GetToken();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return Task.CompletedTask;
}
/// <summary>
/// https://docs.microsoft.com/en-us/windows/uwp/security/web-account-manager#build-the-account-settings-pane.
/// </summary>
private async void BuildPaneAsync(AccountsSettingsPane sender, AccountsSettingsPaneCommandsRequestedEventArgs args)
{
var deferral = args.GetDeferral();
try
{
// Providing nothing shows all accounts, providing authority shows only aad
var msaProvider = await WebAuthenticationCoreManager.FindAccountProviderAsync(WebAccountProviderId);
if (msaProvider == null)
{
State = ProviderState.SignedOut;
return;
}
var command = new WebAccountProviderCommand(msaProvider, GetTokenAsync);
args.WebAccountProviderCommands.Add(command);
}
catch
{
State = ProviderState.SignedOut;
}
finally
{
deferral.Complete();
}
}
private async void GetTokenAsync(WebAccountProviderCommand command)
{
// Build the token request
WebTokenRequest request = new WebTokenRequest(command.WebAccountProvider, string.Join(',', _scopes), _clientId);
request.Properties.Add("resource", GraphResourceProperty);
// Get the results
WebTokenRequestResult result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
// Handle user cancellation
if (result.ResponseStatus == WebTokenRequestStatus.UserCancel)
{
State = ProviderState.SignedOut;
return;
}
// Handle any errors
if (result.ResponseStatus != WebTokenRequestStatus.Success)
{
Debug.WriteLine(result.ResponseError.ErrorMessage);
State = ProviderState.SignedOut;
return;
}
// Extract values from the results
var token = result.ResponseData[0].Token;
var account = result.ResponseData[0].WebAccount;
// The UserName value may be null, but the Id is always present.
var userName = account.Id;
// Save the user info to the PaasswordVault
var vault = new Windows.Security.Credentials.PasswordVault();
var tokenCredential = new Windows.Security.Credentials.PasswordCredential(TokenCredentialResourceName, userName, token);
vault.Add(tokenCredential);
// Set the current user object
_currentUser = new AuthenticatedUser(tokenCredential);
// Update the state to be signed in.
State = ProviderState.SignedIn;
}
private Windows.Security.Credentials.PasswordCredential GetCredentialFromLocker()
{
Windows.Security.Credentials.PasswordCredential credential = null;
try
{
var vault = new Windows.Security.Credentials.PasswordVault();
var credentialList = vault.FindAllByResource(TokenCredentialResourceName);
if (credentialList.Count > 0)
{
// We delete the credential upon logout, so only one user can be stored in the vault at a time.
credential = credentialList.First();
}
}
catch
{
// FindAllByResource will throw an exception if the resource isn't found.
}
return credential;
}
}
}

Просмотреть файл

@ -30,6 +30,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Net.Authen
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Net.Authentication.Msal", "CommunityToolkit.Net.Authentication.Msal\CommunityToolkit.Net.Authentication.Msal.csproj", "{CA4042D2-33A2-450B-8B9D-C286B9F3F3F4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Uwp.Authentication", "CommunityToolkit.Uwp.Authentication\CommunityToolkit.Uwp.Authentication.csproj", "{2E4A708A-DF53-4863-B797-E14CDC6B90FA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
CI|Any CPU = CI|Any CPU
@ -252,6 +254,46 @@ Global
{CA4042D2-33A2-450B-8B9D-C286B9F3F3F4}.Release|x64.Build.0 = Release|x64
{CA4042D2-33A2-450B-8B9D-C286B9F3F3F4}.Release|x86.ActiveCfg = Release|x86
{CA4042D2-33A2-450B-8B9D-C286B9F3F3F4}.Release|x86.Build.0 = Release|x86
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.CI|Any CPU.ActiveCfg = CI|Any CPU
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.CI|Any CPU.Build.0 = CI|Any CPU
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.CI|ARM.ActiveCfg = CI|ARM
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.CI|ARM.Build.0 = CI|ARM
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.CI|ARM64.ActiveCfg = CI|ARM64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.CI|ARM64.Build.0 = CI|ARM64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.CI|x64.ActiveCfg = CI|x64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.CI|x64.Build.0 = CI|x64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.CI|x86.ActiveCfg = CI|x86
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.CI|x86.Build.0 = CI|x86
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Debug|ARM.ActiveCfg = Debug|ARM
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Debug|ARM.Build.0 = Debug|ARM
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Debug|ARM64.ActiveCfg = Debug|ARM64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Debug|ARM64.Build.0 = Debug|ARM64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Debug|x64.ActiveCfg = Debug|x64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Debug|x64.Build.0 = Debug|x64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Debug|x86.ActiveCfg = Debug|x86
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Debug|x86.Build.0 = Debug|x86
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Native|Any CPU.ActiveCfg = Debug|Any CPU
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Native|Any CPU.Build.0 = Debug|Any CPU
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Native|ARM.ActiveCfg = Debug|ARM
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Native|ARM.Build.0 = Debug|ARM
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Native|ARM64.ActiveCfg = Debug|ARM64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Native|ARM64.Build.0 = Debug|ARM64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Native|x64.ActiveCfg = Debug|x64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Native|x64.Build.0 = Debug|x64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Native|x86.ActiveCfg = Debug|x86
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Native|x86.Build.0 = Debug|x86
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Release|Any CPU.Build.0 = Release|Any CPU
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Release|ARM.ActiveCfg = Release|ARM
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Release|ARM.Build.0 = Release|ARM
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Release|ARM64.ActiveCfg = Release|ARM64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Release|ARM64.Build.0 = Release|ARM64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Release|x64.ActiveCfg = Release|x64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Release|x64.Build.0 = Release|x64
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Release|x86.ActiveCfg = Release|x86
{2E4A708A-DF53-4863-B797-E14CDC6B90FA}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE