Skip to content

Commit

Permalink
Merge pull request #76 from windows-toolkit/shweaver/bad-behaviors
Browse files Browse the repository at this point in the history
Restructuring package layout, removing behaviors
  • Loading branch information
nmetulev authored Mar 24, 2021
2 parents 7a148f7 + 0ce9faf commit 035e1b6
Show file tree
Hide file tree
Showing 85 changed files with 560 additions and 1,812 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>

<Title>Windows Community Toolkit .NET Standard Auth Services</Title>
<Description>
This package includes .NET Standard authentication helpers such as:
- MsalProvider:
</Description>
<PackageTags>Community Toolkit Provider Authentication Auth Msal</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Identity.Client" Version="4.28.0" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="2.18.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CommunityToolkit.Net.Authentication\CommunityToolkit.Net.Authentication.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -6,102 +6,70 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Graph;
using Microsoft.Identity.Client;
using Microsoft.Toolkit.Graph.Extensions;

namespace Microsoft.Toolkit.Graph.Providers
namespace CommunityToolkit.Net.Authentication
{
//// TODO: Move some of this to a simple base-class for non-MSAL parts related to Provider only and properties?

/// <summary>
/// <a href="https://github.com/AzureAD/microsoft-authentication-library-for-dotnet">MSAL.NET</a> provider helper for tracking authentication state using an <see cref="IAuthenticationProvider"/> class.
/// <a href="https://github.com/AzureAD/microsoft-authentication-library-for-dotnet">MSAL.NET</a> provider helper for tracking authentication state.
/// </summary>
public class MsalProvider : IProvider
public class MsalProvider : BaseProvider
{
/// <summary>
/// Gets or sets the MSAL.NET Client used to authenticate the user.
/// </summary>
protected IPublicClientApplication Client { get; set; }

/// <summary>
/// Gets or sets the provider used by the graph to manage requests.
/// Gets the MSAL.NET Client used to authenticate the user.
/// </summary>
protected IAuthenticationProvider Provider { get; set; }

private ProviderState _state = ProviderState.Loading;

/// <inheritdoc/>
public ProviderState State
{
get
{
return _state;
}

private set
{
var current = _state;
_state = value;

StateChanged?.Invoke(this, new StateChangedEventArgs(current, _state));
}
}

/// <inheritdoc/>
public GraphServiceClient Graph { get; private set; }

/// <inheritdoc/>
public event EventHandler<StateChangedEventArgs> StateChanged;
protected IPublicClientApplication Client { get; private set; }

/// <summary>
/// Initializes a new instance of the <see cref="MsalProvider"/> class. <see cref="CreateAsync"/>.
/// Gets an array of scopes to use for accessing Graph resources.
/// </summary>
private MsalProvider()
{
}
protected string[] Scopes { get; private set; }

/// <summary>
/// Initializes a new instance of the <see cref="MsalProvider"/> class.
/// </summary>
/// <param name="client">Existing <see cref="IPublicClientApplication"/> instance.</param>
/// <param name="provider">Existing <see cref="IAuthenticationProvider"/> instance.</param>
/// <returns>A <see cref="Task"/> returning a <see cref="MsalProvider"/> instance.</returns>
public static async Task<MsalProvider> CreateAsync(IPublicClientApplication client, IAuthenticationProvider provider)
/// <param name="clientId">Registered ClientId.</param>
/// <param name="redirectUri">RedirectUri for auth response.</param>
/// <param name="scopes">List of Scopes to initially request.</param>
public MsalProvider(string clientId, string[] scopes = null, string redirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient")
{
//// TODO: Check all config provided

var msal = new MsalProvider
{
Client = client,
Provider = provider,
};
var client = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(AzureCloudInstance.AzurePublic, AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount)
.WithRedirectUri(redirectUri)
.WithClientName(ProviderManager.ClientName)
.WithClientVersion(Assembly.GetExecutingAssembly().GetName().Version.ToString())
.Build();

msal.Graph = new GraphServiceClient(msal);
Scopes = scopes ?? new string[] { string.Empty };

await msal.TrySilentSignInAsync();
Client = client;

return msal;
_ = TrySilentSignInAsync();
}

/// <inheritdoc/>
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
public override async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
request.AddSdkVersion();
IEnumerable<IAccount> accounts = await Client.GetAccountsAsync();
AuthenticationResult authResult;

try
{
await Provider.AuthenticateRequestAsync(request);
authResult = await Client.AcquireTokenSilent(Scopes, accounts.FirstOrDefault()).ExecuteAsync();
}
catch (Exception)
catch (MsalUiRequiredException)
{
// TODO: Catch different types of errors and try and re-auth? Should be handled by Graph Auth Providers.
// Assume we're signed-out on error?
State = ProviderState.SignedOut;
authResult = await Client.AcquireTokenInteractive(Scopes).ExecuteAsync();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
}

return;
if (authResult != null)
{
AddSdkVersion(request);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
}

// Check state after request to see if we're now signed-in.
Expand Down Expand Up @@ -159,14 +127,14 @@ public async Task TrySilentSignInAsync()
}

/// <inheritdoc/>
public async Task LoginAsync()
public override async Task LoginAsync()
{
// Force fake request to start auth process
await AuthenticateRequestAsync(new System.Net.Http.HttpRequestMessage());
}

/// <inheritdoc/>
public async Task LogoutAsync()
public override async Task LogoutAsync()
{
// Forcibly remove each user.
foreach (var user in await Client.GetAccountsAsync())
Expand Down
68 changes: 68 additions & 0 deletions CommunityToolkit.Net.Authentication/BaseProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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.Net.Http;
using System.Threading.Tasks;
using CommunityToolkit.Net.Authentication.Extensions;

namespace CommunityToolkit.Net.Authentication
{
/// <summary>
/// A base construct for building Graph Providers on top of.
/// </summary>
public abstract class BaseProvider : IProvider
{
private ProviderState _state;

/// <summary>
/// Gets or sets the current state of the provider.
/// </summary>
public ProviderState State
{
get => _state;
protected set
{
var oldState = _state;
var newState = value;
if (oldState != newState)
{
_state = newState;
StateChanged?.Invoke(this, new ProviderStateChangedEventArgs(oldState, newState));
}
}
}

/// <inheritdoc/>
public event EventHandler<ProviderStateChangedEventArgs> StateChanged;

/// <summary>
/// Initializes a new instance of the <see cref="BaseProvider"/> class.
/// </summary>
public BaseProvider()
{
_state = ProviderState.Loading;
}

/// <inheritdoc />
public abstract Task LoginAsync();

/// <inheritdoc />
public abstract Task LogoutAsync();

/// <inheritdoc />
public abstract Task AuthenticateRequestAsync(HttpRequestMessage request);

/// <summary>
/// Append the Sdk version to the request headers.
/// </summary>
/// <param name="request">
/// The request to append the header to.
/// </param>
protected void AddSdkVersion(HttpRequestMessage request)
{
request.AddSdkVersion();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>

<Title>Windows Community Toolkit .NET Standard Auth Services</Title>
<Description>
This package includes .NET Standard authentication helpers such as:
- BaseProvider:
- IProvider:
- MockPRovider:
- ProviderManager:
- ProviderManagerChangedState:
- ProviderState:
- ProviderStateChangedEventArgs:
- ProviderUpdatedEventArgs:
</Description>
<PackageTags>Community Toolkit Provider Authentication Auth</PackageTags>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,24 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.Toolkit.Graph.Extensions
namespace CommunityToolkit.Net.Authentication.Extensions
{
/// <summary>
/// Helpers for Graph related HTTP Headers.
/// </summary>
internal static class HttpRequestMessageExtensions
public static class HttpRequestMessageExtensions
{
private const string SdkVersion = "SdkVersion";
private const string LibraryName = "wct";
private const string Bearer = "Bearer";
private const string MockGraphToken = "{token:https://graph.microsoft.com/}";

public static void AddSdkVersion(this HttpRequestMessage request)
internal static void AddSdkVersion(this HttpRequestMessage request)
{
if (request == null || request.Headers == null)
{
Expand All @@ -39,11 +37,26 @@ public static void AddSdkVersion(this HttpRequestMessage request)
}
}

public static void AddMockProviderToken(this HttpRequestMessage request)
internal static void AddMockProviderToken(this HttpRequestMessage request)
{
request
.Headers
.Authorization = new AuthenticationHeaderValue(Bearer, MockGraphToken);
}

/// <summary>
/// Helper method for authenticating an http request using the GlobalProvider instance.
/// </summary>
/// <param name="request">The request to authenticate.</param>
/// <returns>A task upon completion.</returns>
public static async Task AuthenticateAsync(this HttpRequestMessage request)
{
if (ProviderManager.Instance.GlobalProvider == null)
{
throw new InvalidOperationException("The request cannot be authenticated. The GlobalProvider is null.");
}

await ProviderManager.Instance.GlobalProvider.AuthenticateRequestAsync(request);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,32 @@
// See the LICENSE file in the project root for more information.

using System;
using System.ComponentModel;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Graph;

namespace Microsoft.Toolkit.Graph.Providers
namespace CommunityToolkit.Net.Authentication
{
/// <summary>
/// <see cref="IAuthenticationProvider"/> helper wrapper to expose more states around the authentication process for graph controls.
/// Authentication provider to expose more states around the authentication process for graph controls.
/// </summary>
public interface IProvider : IAuthenticationProvider
public interface IProvider
{
/// <summary>
/// Gets the current login state of the provider.
/// </summary>
ProviderState State { get; }

/// <summary>
/// Gets the <see cref="GraphServiceClient"/> object to access the Microsoft Graph APIs.
/// Event called when the login <see cref="State"/> changes.
/// </summary>
GraphServiceClient Graph { get; }
event EventHandler<ProviderStateChangedEventArgs> StateChanged;

/// <summary>
/// Event called when the login <see cref="State"/> changes.
/// Authenticate an outgoing request.
/// </summary>
event EventHandler<StateChangedEventArgs> StateChanged;
/// <param name="request">The request to authenticate.</param>
/// <returns>A task upon completion.</returns>
Task AuthenticateRequestAsync(HttpRequestMessage request);

/// <summary>
/// Login the user.
Expand Down
Loading

0 comments on commit 035e1b6

Please sign in to comment.