diff --git a/CommunityToolkit.Authentication.Msal/MsalProvider.cs b/CommunityToolkit.Authentication.Msal/MsalProvider.cs index 9b90884..5a51f8b 100644 --- a/CommunityToolkit.Authentication.Msal/MsalProvider.cs +++ b/CommunityToolkit.Authentication.Msal/MsalProvider.cs @@ -80,9 +80,10 @@ public MsalProvider(IPublicClientApplication client, string[] scopes = null, boo /// Determines whether the provider attempts to silently log in upon creation. /// Determines if organizational accounts should be enabled/disabled. /// Registered tenant id in Azure Active Directory. - public MsalProvider(string clientId, string[] scopes = null, string redirectUri = null, bool autoSignIn = true, bool listWindowsWorkAndSchoolAccounts = true, string tenantId = null) + /// Output logs. + public MsalProvider(string clientId, string[] scopes = null, string redirectUri = null, bool autoSignIn = true, bool listWindowsWorkAndSchoolAccounts = true, string tenantId = null, bool withLogging = false) { - Client = CreatePublicClientApplication(clientId, tenantId, redirectUri, listWindowsWorkAndSchoolAccounts); + Client = CreatePublicClientApplication(clientId, tenantId, redirectUri, listWindowsWorkAndSchoolAccounts, withLogging); Scopes = scopes.Select(s => s.ToLower()).ToArray() ?? new string[] { string.Empty }; if (autoSignIn) @@ -184,13 +185,28 @@ public override Task GetTokenAsync(bool silentOnly = false, string[] sco /// An optional tenant id. /// Redirect uri for auth response. /// Determines if organizational accounts should be supported. + /// Output logs. /// A new instance of . - protected IPublicClientApplication CreatePublicClientApplication(string clientId, string tenantId, string redirectUri, bool listWindowsWorkAndSchoolAccounts) + protected IPublicClientApplication CreatePublicClientApplication(string clientId, string tenantId, string redirectUri, bool listWindowsWorkAndSchoolAccounts, bool withLogging) { var clientBuilder = PublicClientApplicationBuilder.Create(clientId) .WithClientName(ProviderManager.ClientName) .WithClientVersion(Assembly.GetExecutingAssembly().GetName().Version.ToString()); + if (withLogging) + { + clientBuilder = clientBuilder.WithLogging((level, message, containsPii) => + { + if (containsPii) + { + // Add a PII warning to messages containing any. + message = $"[CONTAINS PII] {message}"; + } + + EventLogger.Log($"{level}: {message}"); + }); + } + if (tenantId != null) { clientBuilder = clientBuilder.WithTenantId(tenantId); @@ -237,13 +253,15 @@ protected async Task GetTokenWithScopesAsync(string[] scopes, bool silen authResult = await Client.AcquireTokenSilent(scopes, account).ExecuteAsync(); } } - catch (MsalUiRequiredException) + catch (MsalUiRequiredException e) { + EventLogger.Log(e.Message); } - catch + catch (Exception e) { // Unexpected exception - // TODO: Send exception to a logger. + EventLogger.Log(e.Message); + EventLogger.Log(e.StackTrace); } if (authResult == null && !silentOnly) @@ -271,10 +289,11 @@ protected async Task GetTokenWithScopesAsync(string[] scopes, bool silen authResult = await paramBuilder.ExecuteAsync(); } - catch + catch (Exception e) { // Unexpected exception - // TODO: Send exception to a logger. + EventLogger.Log(e.Message); + EventLogger.Log(e.StackTrace); } } diff --git a/CommunityToolkit.Authentication.Uwp/WindowsProvider.cs b/CommunityToolkit.Authentication.Uwp/WindowsProvider.cs index 2a5434a..3e149f4 100644 --- a/CommunityToolkit.Authentication.Uwp/WindowsProvider.cs +++ b/CommunityToolkit.Authentication.Uwp/WindowsProvider.cs @@ -172,9 +172,11 @@ public override async Task SignOutAsync() { await _webAccount.SignOutAsync(); } - catch + catch (Exception e) { // Failed to remove an account. + EventLogger.Log(e.Message); + EventLogger.Log(e.StackTrace); } _webAccount = null; @@ -233,7 +235,8 @@ public override async Task GetTokenAsync(bool silentOnly = false, string } catch (Exception e) { - // TODO: Log failure + EventLogger.Log(e.Message); + EventLogger.Log(e.StackTrace); throw e; } finally @@ -364,36 +367,29 @@ private async Task SetAccountAsync(WebAccount account) private async Task AuthenticateSilentAsync(string[] scopes) { - try - { - WebTokenRequestResult authResult = null; + WebTokenRequestResult authResult = null; - var account = _webAccount; - if (account == null) - { - // Check the cache for an existing user - if (Settings[SettingsKeyAccountId] is string savedAccountId && - Settings[SettingsKeyProviderId] is string savedProviderId && - Settings[SettingsKeyProviderAuthority] is string savedProviderAuthority) - { - var savedProvider = await WebAuthenticationCoreManager.FindAccountProviderAsync(savedProviderId, savedProviderAuthority); - account = await WebAuthenticationCoreManager.FindAccountAsync(savedProvider, savedAccountId); - } - } - - if (account != null) + var account = _webAccount; + if (account == null) + { + // Check the cache for an existing user + if (Settings[SettingsKeyAccountId] is string savedAccountId && + Settings[SettingsKeyProviderId] is string savedProviderId && + Settings[SettingsKeyProviderAuthority] is string savedProviderAuthority) { - // Prepare a request to get a token. - var webTokenRequest = GetWebTokenRequest(account.WebAccountProvider, _webAccountProviderConfig.ClientId, scopes); - authResult = await WebAuthenticationCoreManager.GetTokenSilentlyAsync(webTokenRequest, account); + var savedProvider = await WebAuthenticationCoreManager.FindAccountProviderAsync(savedProviderId, savedProviderAuthority); + account = await WebAuthenticationCoreManager.FindAccountAsync(savedProvider, savedAccountId); } - - return authResult; } - catch (HttpRequestException) + + if (account != null) { - throw; /* probably offline, no point continuing to interactive auth */ + // Prepare a request to get a token. + var webTokenRequest = GetWebTokenRequest(account.WebAccountProvider, _webAccountProviderConfig.ClientId, scopes); + authResult = await WebAuthenticationCoreManager.GetTokenSilentlyAsync(webTokenRequest, account); } + + return authResult; } private Task AuthenticateInteractiveAsync(string[] scopes) @@ -528,9 +524,10 @@ async void OnAccountCommandsRequested(AccountsSettingsPane sender, AccountsSetti var webAccountProvider = webAccountProviderCommandWasInvoked ? await webAccountProviderTaskCompletionSource.Task : null; return webAccountProvider; } - catch (TaskCanceledException) + catch (TaskCanceledException e) { // The task was cancelled. No provider was chosen. + EventLogger.Log(e.Message); return null; } finally diff --git a/CommunityToolkit.Authentication/BaseProvider.cs b/CommunityToolkit.Authentication/BaseProvider.cs index 4a0c5d5..665159d 100644 --- a/CommunityToolkit.Authentication/BaseProvider.cs +++ b/CommunityToolkit.Authentication/BaseProvider.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Threading.Tasks; using CommunityToolkit.Authentication.Extensions; +using CommunityToolkit.Authentication.Logging; namespace CommunityToolkit.Authentication { @@ -34,6 +35,11 @@ protected set } } + /// + /// Gets or sets the logger used to log exceptions and event messages. + /// + public ILogger EventLogger { get; set; } = DebugLogger.Instance; + /// public abstract string CurrentAccountId { get; } diff --git a/CommunityToolkit.Authentication/Logging/DebugLogger.cs b/CommunityToolkit.Authentication/Logging/DebugLogger.cs new file mode 100644 index 0000000..8f0b515 --- /dev/null +++ b/CommunityToolkit.Authentication/Logging/DebugLogger.cs @@ -0,0 +1,25 @@ +// 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.Diagnostics; + +namespace CommunityToolkit.Authentication.Logging +{ + /// + /// Logs messages using the API. + /// + public class DebugLogger : ILogger + { + /// + /// Singleton instance. + /// + public static readonly DebugLogger Instance = new DebugLogger(); + + /// + public void Log(string message) + { + Debug.WriteLine(message); + } + } +} diff --git a/CommunityToolkit.Authentication/Logging/ILogger.cs b/CommunityToolkit.Authentication/Logging/ILogger.cs new file mode 100644 index 0000000..47d7ac8 --- /dev/null +++ b/CommunityToolkit.Authentication/Logging/ILogger.cs @@ -0,0 +1,18 @@ +// 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. + +namespace CommunityToolkit.Authentication.Logging +{ + /// + /// Defines a simple logger for handling messages during runtime. + /// + public interface ILogger + { + /// + /// Log a message. + /// + /// The message to log. + void Log(string message); + } +}