Add ability to register and retrieve auth providers to/from IApplicationEnvironment (#360)

* Add ability to register and retrieve auth providers to/from IApplicationEnvironment

* Fix docstring

* Keep AppEnv as non-abstract
This commit is contained in:
Luke Bordonaro 2024-06-14 08:50:22 -07:00 коммит произвёл GitHub
Родитель 7da00b7bd9
Коммит 4f3c59d935
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
12 изменённых файлов: 354 добавлений и 2 удалений

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
using Microsoft.Performance.SDK.Auth;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
@ -122,6 +123,14 @@ namespace Microsoft.Performance.SDK.Runtime
args);
}
/// <inheritdoc />
public virtual bool TryGetAuthProvider<TAuth, TResult>(out IAuthProvider<TAuth, TResult> provider)
where TAuth : IAuthMethod<TResult>
{
provider = null;
return false;
}
private static MessageBoxIcon MapToImage(MessageType messageType)
{
switch (messageType)

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
using Microsoft.Performance.SDK.Auth;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
@ -35,5 +36,12 @@ namespace Microsoft.Performance.SDK.Tests.TestClasses
{
return ButtonResult.None;
}
public bool TryGetAuthProvider<TAuth, TResult>(out IAuthProvider<TAuth, TResult> provider)
where TAuth : IAuthMethod<TResult>
{
provider = null;
return false;
}
}
}

Просмотреть файл

@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
namespace Microsoft.Performance.SDK.Auth
{
/// <summary>
/// An <see cref="IAuthMethod{TResult}"/> that acquires bearer tokens for accessing Azure services.
/// </summary>
public sealed class AzureBearerTokenAuth
: IAuthMethod<string>
{
/// <summary>
/// Initializes a new instance of the <see cref="AzureBearerTokenAuth"/> class.
/// </summary>
/// <param name="appId">
/// The <see cref="Guid"/> of the Microsoft Entra ID (formerly Azure Active Directory) of the
/// application to authenticate to.
/// </param>
/// <param name="scopes">
/// The required scopes that the returned bearer token must have.
/// </param>
/// <param name="allowCache">
/// A value indicating whether or not to allow the acquired token to come from a cache. This value may
/// be <c>false</c> if a token for these scopes was previously acquired, but the token does not have access
/// to the required resources. In this case, a different token (e.g. a token for a different user) may be
/// required.
/// </param>
/// <param name="appName">
/// A human-readable display name for the application the user is logging into.
/// </param>
/// <param name="authReason">
/// An optional human-readable description to describe why the authentication is needed. For example,
/// "retrieve data from Azure DevOps".
/// </param>
/// <param name="tenant">
/// The optional tenant the authenticated principal must be logged into. May be <c>null</c>.
/// </param>
/// <param name="preferredAccountDomain">
/// The optional domain name of accounts which should be preferred for this authentication request.
/// May be <c>null</c>.
/// </param>
public AzureBearerTokenAuth(
Guid appId,
string[] scopes,
bool allowCache,
string appName,
string authReason,
Guid? tenant,
string preferredAccountDomain)
{
AppId = appId;
Scopes = scopes;
AllowCache = allowCache;
AppName = appName;
AuthReason = authReason;
Tenant = tenant;
PreferredAccountDomain = preferredAccountDomain;
}
/// <summary>
/// Gets the <see cref="Guid"/> of the Microsoft Entra ID (formerly Azure Active Directory) of the
/// application to authenticate to.
/// </summary>
public Guid AppId { get; }
/// <summary>
/// Gets the required scopes that the returned bearer token must have.
/// </summary>
public string[] Scopes { get; }
/// <summary>
/// Gets a value indicating whether or not to allow the acquired token to come from a cache. This value may
/// be <c>false</c> if a token for these scopes was previously acquired, but the token does not have access
/// to the required resources. In this case, a different token (e.g. a token for a different user) may be
/// required.
/// </summary>
public bool AllowCache { get; }
/// <summary>
/// Gets a human-readable display name for the application the user is logging into.
/// </summary>
public string AppName { get; }
/// <summary>
/// Gets an optional human-readable description to describe why the authentication is needed. For example,
/// "retrieve data from Azure DevOps".
/// </summary>
public string AuthReason { get; }
/// <summary>
/// Gets the optional tenant the authenticated principal must be logged into. May be <c>null</c>.
/// </summary>
public Guid? Tenant { get; }
/// <summary>
/// Gets the optional domain name of accounts which should be preferred for this authentication request.
/// May be <c>null</c>.
/// </summary>
public string PreferredAccountDomain { get; }
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.SDK.Auth
{
/// <summary>
/// Represents an authentication method that can be used to authenticate to a service.
/// </summary>
/// <typeparam name="TResult">
/// The type of the result of a successful authentication.
/// </typeparam>
public interface IAuthMethod<TResult>
{
}
}

Просмотреть файл

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Threading.Tasks;
namespace Microsoft.Performance.SDK.Auth
{
/// <summary>
/// Provides authentication for a given <see cref="IAuthMethod{TResult}"/>.
/// </summary>
/// <typeparam name="TAuth">
/// The type of the <see cref="IAuthMethod{TResult}"/> this provider can authenticate.
/// </typeparam>
/// <typeparam name="TResult">
/// The type of the result of a successful authentication.
/// </typeparam>
public interface IAuthProvider<in TAuth, TResult>
where TAuth : IAuthMethod<TResult>
{
/// <summary>
/// Attempts to authenticate the given <see cref="IAuthMethod{TResult}"/>.
/// </summary>
/// <param name="authRequest">
/// The <see cref="IAuthMethod{TResult}"/> to authenticate.
/// </param>
/// <returns>
/// An awaitable task that, upon completion, will contain the result of the authentication request.
/// The result of the authentication will either be an instance of <typeparamref name="TResult"/> if
/// the authentication was successful, or <c>null</c> if the authentication failed.
/// </returns>
Task<TResult> TryGetAuth(TAuth authRequest);
}
}

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
using Microsoft.Performance.SDK.Auth;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
@ -117,5 +118,26 @@ namespace Microsoft.Performance.SDK.Processing
string caption,
string format,
params object[] args);
/// <summary>
/// Attempts to get an <see cref="IAuthProvider{TAuth, TResult}"/> that can provide authentication
/// for <see cref="IAuthMethod{TResult}"/> of type <typeparamref name="TAuth"/>.
/// </summary>
/// <param name="provider">
/// The found provider, or <c>null</c> if no registered provider can provide authentication for
/// <typeparamref name="TAuth"/>.
/// </param>
/// <typeparam name="TAuth">
/// The type of the <see cref="IAuthMethod{TResult}"/> for which to attempt to get a provider.
/// </typeparam>
/// <typeparam name="TResult">
/// The type of the result of a successful authentication for <typeparamref name="TAuth"/>.
/// </typeparam>
/// <returns>
/// <c>true</c> if a provider was found; <c>false</c> otherwise. If <c>false</c> is returned,
/// <paramref name="provider"/> will be <c>null</c>.
/// </returns>
bool TryGetAuthProvider<TAuth, TResult>(out IAuthProvider<TAuth, TResult> provider)
where TAuth : IAuthMethod<TResult>;
}
}

Просмотреть файл

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Auth;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
@ -35,5 +36,12 @@ namespace Microsoft.Performance.Testing.SDK
{
return ButtonResult.None;
}
public bool TryGetAuthProvider<TAuth, TResult>(out IAuthProvider<TAuth, TResult> provider)
where TAuth : IAuthMethod<TResult>
{
provider = null;
return false;
}
}
}

Просмотреть файл

@ -43,6 +43,8 @@ namespace Microsoft.Performance.Toolkit.Engine.Tests.TestCookers.Source123
public bool IsDisposed => this.disposedValue;
internal IApplicationEnvironment ApplicationEnvironmentSpy => this.ApplicationEnvironment;
protected override ICustomDataProcessor CreateProcessorCore(
IEnumerable<IDataSource> dataSources,
IProcessorEnvironment processorEnvironment,

Просмотреть файл

@ -7,6 +7,8 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Performance.SDK.Auth;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.SDK.Runtime;
@ -1131,6 +1133,55 @@ namespace Microsoft.Performance.Toolkit.Engine.Tests
#endregion
#region Auth
[TestMethod]
[UnitTest]
public void ProcessingSources_AreGiven_SpecifiedAuthProviders()
{
var givenProvider = new StubAuthProvider();
var info = new EngineCreateInfo(this.DefaultSet.AsReadOnly());
info.WithAuthProvider(givenProvider);
using var sut = Engine.Create(info);
var spy = sut.Plugins.ProcessingSourceReferences.First(psr => psr.Guid == Source123DataSource.Guid).Instance as Source123DataSource;
var success = spy.ApplicationEnvironmentSpy.TryGetAuthProvider<StubAuthMethod, int>(out var foundProvider);
Assert.IsTrue(success);
Assert.AreEqual(givenProvider, foundProvider);
}
[TestMethod]
[UnitTest]
public void TryGetAuthProvider_Fails_WhenNoProviderIsRegistered()
{
var info = new EngineCreateInfo(this.DefaultSet.AsReadOnly());
using var sut = Engine.Create(info);
var spy = sut.Plugins.ProcessingSourceReferences.First(psr => psr.Guid == Source123DataSource.Guid).Instance as Source123DataSource;
var success = spy.ApplicationEnvironmentSpy.TryGetAuthProvider<StubAuthMethod, int>(out var foundProvider);
Assert.IsFalse(success);
Assert.IsNull(foundProvider);
}
private class StubAuthMethod
: IAuthMethod<int>
{
}
private class StubAuthProvider
: IAuthProvider<StubAuthMethod, int>
{
public Task<int> TryGetAuth(StubAuthMethod authRequest)
{
throw new NotImplementedException();
}
}
#endregion
public static DataOutputPath Parse(string dataCookerOutputPath)
{
var split = dataCookerOutputPath.Split('/');

Просмотреть файл

@ -770,7 +770,7 @@ namespace Microsoft.Performance.Toolkit.Engine
? createInfo.ApplicationName
: string.Empty;
instance.applicationEnvironment = new ApplicationEnvironment(
instance.applicationEnvironment = new EngineApplicationEnvironment(
applicationName: applicationName,
runtimeName: runtimeName,
new RuntimeTableSynchronizer(),
@ -778,7 +778,8 @@ namespace Microsoft.Performance.Toolkit.Engine
instance.Factory.CreateSourceSessionFactory(),
createInfo.IsInteractive
? (IMessageBox)new InteractiveRuntimeMessageBox(instance.logger)
: (IMessageBox)new NonInteractiveMessageBox(instance.logger))
: (IMessageBox)new NonInteractiveMessageBox(instance.logger),
createInfo.AuthProviders)
{
IsInteractive = createInfo.IsInteractive,
};

Просмотреть файл

@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Microsoft.Performance.SDK.Auth;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.SDK.Runtime;
namespace Microsoft.Performance.Toolkit.Engine
{
internal sealed class EngineApplicationEnvironment
: ApplicationEnvironment
{
private readonly ReadOnlyDictionary<Type, object> authProviders;
public EngineApplicationEnvironment(
string applicationName,
string runtimeName,
ITableDataSynchronization tableDataSynchronizer,
ISourceDataCookerFactoryRetrieval sourceDataCookerFactory,
ISourceSessionFactory sourceSessionFactory,
IMessageBox messageBox,
ReadOnlyDictionary<Type, object> authProviders)
: base(
applicationName,
runtimeName,
tableDataSynchronizer,
sourceDataCookerFactory,
sourceSessionFactory,
messageBox)
{
this.authProviders = authProviders;
}
/// <inheritdoc />
public override bool TryGetAuthProvider<TAuth, TResult>(out IAuthProvider<TAuth, TResult> provider)
{
if (this.authProviders.TryGetValue(typeof(TAuth), out var authProvider))
{
var authProviderTyped = authProvider as IAuthProvider<TAuth, TResult>;
if (authProviderTyped == null)
{
Debug.Fail($"Expected registered {nameof(authProvider)} for type {typeof(TAuth)} to be of type {nameof(IAuthProvider<TAuth, TResult>)}");
provider = null;
return false;
}
provider = authProviderTyped;
return true;
}
return base.TryGetAuthProvider(out provider);
}
}
}

Просмотреть файл

@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Auth;
using Microsoft.Performance.SDK.Processing;
namespace Microsoft.Performance.Toolkit.Engine
@ -16,6 +18,8 @@ namespace Microsoft.Performance.Toolkit.Engine
{
private static string DefaultRuntimeName;
private readonly Dictionary<Type, object> authProviders = new Dictionary<Type, object>();
/// <summary>
/// Initializes the statc members of the <see cref="EngineCreateInfo"/>
/// class.
@ -97,6 +101,39 @@ namespace Microsoft.Performance.Toolkit.Engine
return this;
}
/// <summary>
/// Registers an <see cref="IAuthProvider{TAuth, TResult}"/> of type <typeparamref name="TAuth"/> to be available to plugins
/// via <see cref="IApplicationEnvironment.TryGetAuthProvider{TAuth, TResult}"/>.
/// </summary>
/// <param name="provider">
/// The <see cref="IAuthProvider{TAuth, TResult}"/> to register.
/// </param>
/// <typeparam name="TAuth">
/// The type of <see cref="IAuthMethod{TResult}"/> for which the <see cref="IAuthProvider{TAuth, TResult}"/> is being registered.
/// </typeparam>
/// <typeparam name="TResult">
/// The type of the result of successful authentication requests for <typeparamref name="TAuth"/>.
/// </typeparam>
/// <returns>
/// The instance of <see cref="EngineCreateInfo"/>.
/// </returns>
/// <exception cref="InvalidOperationException">
/// An <see cref="IAuthProvider{TAuth, TResult}"/> for the specified type <typeparamref name="TAuth"/> already exists.
/// </exception>
public EngineCreateInfo WithAuthProvider<TAuth, TResult>(IAuthProvider<TAuth, TResult> provider)
where TAuth : IAuthMethod<TResult>
{
Guard.NotNull(provider, nameof(provider));
var toRegister = typeof(TAuth);
if (!this.authProviders.TryAdd(toRegister, provider))
{
throw new InvalidOperationException($"An auth provider for the specified type {toRegister} already exists.");
}
return this;
}
/// <summary>
/// Gets or sets the name of the runtime on which the application is built.
/// </summary>
@ -136,5 +173,7 @@ namespace Microsoft.Performance.Toolkit.Engine
/// will fail. By default, this property is <c>false</c>.
/// </summary>
public bool IsInteractive { get; set; }
internal ReadOnlyDictionary<Type, object> AuthProviders => new ReadOnlyDictionary<Type, object>(this.authProviders);
}
}