Adding WindowsProvider project
This commit is contained in:
Родитель
035e1b6dff
Коммит
d36ef91426
|
@ -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->'"%(Identity)"')" />
|
||||||
|
<Exec Command="for %%f in (@(GeneratedCSFiles->'"%(Identity)"')) do echo #pragma warning disable > %%f.temp && type %%f >> %%f.temp && move /y %%f.temp %%f > 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
|
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}"
|
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
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Uwp.Authentication", "CommunityToolkit.Uwp.Authentication\CommunityToolkit.Uwp.Authentication.csproj", "{2E4A708A-DF53-4863-B797-E14CDC6B90FA}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
CI|Any CPU = CI|Any CPU
|
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|x64.Build.0 = Release|x64
|
||||||
{CA4042D2-33A2-450B-8B9D-C286B9F3F3F4}.Release|x86.ActiveCfg = Release|x86
|
{CA4042D2-33A2-450B-8B9D-C286B9F3F3F4}.Release|x86.ActiveCfg = Release|x86
|
||||||
{CA4042D2-33A2-450B-8B9D-C286B9F3F3F4}.Release|x86.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
Загрузка…
Ссылка в новой задаче