using System; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using Microsoft.Graph; using Microsoft.Identity.Client; using Logger = OwlCore.Services.Logger; namespace StrixMusic.Cores.OneDrive.Services { /// /// Manages MSAL authentication. /// public class AuthenticationManager { private readonly string _authorityUri = "https://login.microsoftonline.com/consumers"; private readonly string[] _scopes = { "Files.Read.All", "User.Read", "Files.ReadWrite" }; private readonly string _clientId; private readonly string _tenantId; private readonly string? _redirectUri; /// /// Creates a new instance of . /// /// Client ID of a registered MS Graph application that the user will be authenticated against. /// Tenant ID for authenticating the user against a registered MS Graph application /// The redirect URI to use with the connected application, if any. public AuthenticationManager(string clientId, string tenantId, string? redirectUri = null) { _clientId = clientId; _tenantId = tenantId; _redirectUri = redirectUri; } /// /// A custom message handler to use for network requests during authentication. /// public HttpMessageHandler HttpMessageHandler { get; set; } = new HttpClientHandler(); /// public event EventHandler? MsalPublicClientApplicationBuilderCreated; /// public event EventHandler? MsalAcquireTokenInteractiveParameterBuilderCreated; public GraphServiceClient CreateGraphClient(string accessToken) { Logger.LogInformation($"Creating graph client"); var authProvider = new DelegateAuthenticationProvider(requestMessage => { requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); return Task.CompletedTask; }); var handlers = GraphClientFactory.CreateDefaultHandlers(authProvider); var httpClient = GraphClientFactory.Create(handlers, finalHandler: HttpMessageHandler); return new GraphServiceClient(httpClient); } public async Task GetDisplayNameAsync(GraphServiceClient graphClient) { try { Logger.LogInformation($"Getting user"); var user = await graphClient.Users.Request().GetAsync(); if (user.Count == 0) { Logger.LogInformation($"No available users"); return string.Empty; } Logger.LogInformation($"Got user. Display name {user.FirstOrDefault()?.DisplayName}"); return user[0].DisplayName; } catch (Exception ex) { Logger.LogError($"Failed to get user: {ex}"); return string.Empty; } } public async Task TryAcquireCachedTokenAsync(string accountIdentifier, CancellationToken cancellationToken = default) { var clientApp = BuildPublicClientApplication(); Logger.LogInformation($"Acquiring token from cache."); if (string.IsNullOrWhiteSpace(accountIdentifier)) return null; try { Logger.LogInformation($"Getting accounts"); var account = await clientApp.GetAccountAsync(accountIdentifier); Logger.LogInformation($"Executing via {nameof(clientApp.AcquireTokenSilent)}"); return await clientApp.AcquireTokenSilent(_scopes, account).ExecuteAsync(cancellationToken); } catch (MsalUiRequiredException) { return null; } } public Task TryAcquireTokenViaInteractiveLoginAsync(CancellationToken cancellationToken = default) { var clientApp = BuildPublicClientApplication(); Logger.LogInformation($"Acquiring token via interactive login"); Logger.LogInformation($"Building via {nameof(clientApp.AcquireTokenInteractive)}"); var builder = clientApp.AcquireTokenInteractive(_scopes) .WithPrompt(Microsoft.Identity.Client.Prompt.SelectAccount); var createdArgs = new AcquireTokenInteractiveParameterBuilderCreatedEventArgs(builder); MsalAcquireTokenInteractiveParameterBuilderCreated?.Invoke(this, createdArgs); builder = createdArgs.Builder; Logger.LogInformation($"Executing builder"); return builder.ExecuteAsync(cancellationToken); } public async Task TryAcquireTokenViaDeviceCodeLoginAsync(Func deviceCodeResultCallback, CancellationToken cancellationToken = default) { var clientApp = BuildPublicClientApplication(); Logger.LogInformation($"Acquiring token via device code"); Logger.LogInformation($"Building via {nameof(clientApp.AcquireTokenWithDeviceCode)}"); var builder = clientApp.AcquireTokenWithDeviceCode(_scopes, deviceCodeResultCallback); Logger.LogInformation($"Executing builder"); try { return await builder.ExecuteAsync(cancellationToken); } catch (MsalServiceException) { return null; } } private IPublicClientApplication BuildPublicClientApplication() { var authority = new Uri($"{_authorityUri}/{_tenantId}"); var builder = PublicClientApplicationBuilder .Create(_clientId) .WithAuthority(authority, false); if (!string.IsNullOrWhiteSpace(_redirectUri)) builder.WithRedirectUri(_redirectUri); var createdArgs = new MsalPublicClientApplicationBuilderCreatedEventArgs(builder); MsalPublicClientApplicationBuilderCreated?.Invoke(this, createdArgs); return createdArgs.Builder.Build(); } } }