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
|
||||
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
|
||||
|
|
Загрузка…
Ссылка в новой задаче