diff --git a/src/Microsoft.Performance.SDK.Runtime/ApplicationEnvironment.cs b/src/Microsoft.Performance.SDK.Runtime/ApplicationEnvironment.cs
index 1c54e9c..319c76d 100644
--- a/src/Microsoft.Performance.SDK.Runtime/ApplicationEnvironment.cs
+++ b/src/Microsoft.Performance.SDK.Runtime/ApplicationEnvironment.cs
@@ -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);
}
+ ///
+ public virtual bool TryGetAuthProvider(out IAuthProvider provider)
+ where TAuth : IAuthMethod
+ {
+ provider = null;
+ return false;
+ }
+
private static MessageBoxIcon MapToImage(MessageType messageType)
{
switch (messageType)
diff --git a/src/Microsoft.Performance.SDK.Tests/TestClasses/TestApplicationEnvironment.cs b/src/Microsoft.Performance.SDK.Tests/TestClasses/TestApplicationEnvironment.cs
index 6c89acd..b68a987 100644
--- a/src/Microsoft.Performance.SDK.Tests/TestClasses/TestApplicationEnvironment.cs
+++ b/src/Microsoft.Performance.SDK.Tests/TestClasses/TestApplicationEnvironment.cs
@@ -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(out IAuthProvider provider)
+ where TAuth : IAuthMethod
+ {
+ provider = null;
+ return false;
+ }
}
}
diff --git a/src/Microsoft.Performance.SDK/Auth/AzureAuthRequest.cs b/src/Microsoft.Performance.SDK/Auth/AzureAuthRequest.cs
new file mode 100644
index 0000000..6e155e6
--- /dev/null
+++ b/src/Microsoft.Performance.SDK/Auth/AzureAuthRequest.cs
@@ -0,0 +1,103 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+
+namespace Microsoft.Performance.SDK.Auth
+{
+ ///
+ /// An that acquires bearer tokens for accessing Azure services.
+ ///
+ public sealed class AzureBearerTokenAuth
+ : IAuthMethod
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The of the Microsoft Entra ID (formerly Azure Active Directory) of the
+ /// application to authenticate to.
+ ///
+ ///
+ /// The required scopes that the returned bearer token must have.
+ ///
+ ///
+ /// A value indicating whether or not to allow the acquired token to come from a cache. This value may
+ /// be false 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.
+ ///
+ ///
+ /// A human-readable display name for the application the user is logging into.
+ ///
+ ///
+ /// An optional human-readable description to describe why the authentication is needed. For example,
+ /// "retrieve data from Azure DevOps".
+ ///
+ ///
+ /// The optional tenant the authenticated principal must be logged into. May be null.
+ ///
+ ///
+ /// The optional domain name of accounts which should be preferred for this authentication request.
+ /// May be null.
+ ///
+ 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;
+ }
+
+ ///
+ /// Gets the of the Microsoft Entra ID (formerly Azure Active Directory) of the
+ /// application to authenticate to.
+ ///
+ public Guid AppId { get; }
+
+ ///
+ /// Gets the required scopes that the returned bearer token must have.
+ ///
+ public string[] Scopes { get; }
+
+ ///
+ /// Gets a value indicating whether or not to allow the acquired token to come from a cache. This value may
+ /// be false 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.
+ ///
+ public bool AllowCache { get; }
+
+ ///
+ /// Gets a human-readable display name for the application the user is logging into.
+ ///
+ public string AppName { get; }
+
+ ///
+ /// Gets an optional human-readable description to describe why the authentication is needed. For example,
+ /// "retrieve data from Azure DevOps".
+ ///
+ public string AuthReason { get; }
+
+ ///
+ /// Gets the optional tenant the authenticated principal must be logged into. May be null.
+ ///
+ public Guid? Tenant { get; }
+
+ ///
+ /// Gets the optional domain name of accounts which should be preferred for this authentication request.
+ /// May be null.
+ ///
+ public string PreferredAccountDomain { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Performance.SDK/Auth/IAuthMethod.cs b/src/Microsoft.Performance.SDK/Auth/IAuthMethod.cs
new file mode 100644
index 0000000..d062520
--- /dev/null
+++ b/src/Microsoft.Performance.SDK/Auth/IAuthMethod.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.Performance.SDK.Auth
+{
+ ///
+ /// Represents an authentication method that can be used to authenticate to a service.
+ ///
+ ///
+ /// The type of the result of a successful authentication.
+ ///
+ public interface IAuthMethod
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Performance.SDK/Auth/IAuthProvider`1.cs b/src/Microsoft.Performance.SDK/Auth/IAuthProvider`1.cs
new file mode 100644
index 0000000..6ce45cf
--- /dev/null
+++ b/src/Microsoft.Performance.SDK/Auth/IAuthProvider`1.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Threading.Tasks;
+
+namespace Microsoft.Performance.SDK.Auth
+{
+ ///
+ /// Provides authentication for a given .
+ ///
+ ///
+ /// The type of the this provider can authenticate.
+ ///
+ ///
+ /// The type of the result of a successful authentication.
+ ///
+ public interface IAuthProvider
+ where TAuth : IAuthMethod
+ {
+ ///
+ /// Attempts to authenticate the given .
+ ///
+ ///
+ /// The to authenticate.
+ ///
+ ///
+ /// 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 if
+ /// the authentication was successful, or null if the authentication failed.
+ ///
+ Task TryGetAuth(TAuth authRequest);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Performance.SDK/Processing/IApplicationEnvironment.cs b/src/Microsoft.Performance.SDK/Processing/IApplicationEnvironment.cs
index c1e58b7..887d7fd 100644
--- a/src/Microsoft.Performance.SDK/Processing/IApplicationEnvironment.cs
+++ b/src/Microsoft.Performance.SDK/Processing/IApplicationEnvironment.cs
@@ -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);
+
+ ///
+ /// Attempts to get an that can provide authentication
+ /// for of type .
+ ///
+ ///
+ /// The found provider, or null if no registered provider can provide authentication for
+ /// .
+ ///
+ ///
+ /// The type of the for which to attempt to get a provider.
+ ///
+ ///
+ /// The type of the result of a successful authentication for .
+ ///
+ ///
+ /// true if a provider was found; false otherwise. If false is returned,
+ /// will be null.
+ ///
+ bool TryGetAuthProvider(out IAuthProvider provider)
+ where TAuth : IAuthMethod;
}
}
diff --git a/src/Microsoft.Performance.Testing.SDK/StubApplicationEnvironment.cs b/src/Microsoft.Performance.Testing.SDK/StubApplicationEnvironment.cs
index 552e18b..f5d0879 100644
--- a/src/Microsoft.Performance.Testing.SDK/StubApplicationEnvironment.cs
+++ b/src/Microsoft.Performance.Testing.SDK/StubApplicationEnvironment.cs
@@ -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(out IAuthProvider provider)
+ where TAuth : IAuthMethod
+ {
+ provider = null;
+ return false;
+ }
}
}
diff --git a/src/Microsoft.Performance.Toolkit.Engine.Tests/TestCookers/Source123/Source123DataSource.cs b/src/Microsoft.Performance.Toolkit.Engine.Tests/TestCookers/Source123/Source123DataSource.cs
index 21370d5..21fe862 100644
--- a/src/Microsoft.Performance.Toolkit.Engine.Tests/TestCookers/Source123/Source123DataSource.cs
+++ b/src/Microsoft.Performance.Toolkit.Engine.Tests/TestCookers/Source123/Source123DataSource.cs
@@ -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 dataSources,
IProcessorEnvironment processorEnvironment,
diff --git a/src/Microsoft.Performance.Toolkit.Engine.Tests/ToolkitEngineTests.cs b/src/Microsoft.Performance.Toolkit.Engine.Tests/ToolkitEngineTests.cs
index f199deb..18f8faa 100644
--- a/src/Microsoft.Performance.Toolkit.Engine.Tests/ToolkitEngineTests.cs
+++ b/src/Microsoft.Performance.Toolkit.Engine.Tests/ToolkitEngineTests.cs
@@ -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(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(out var foundProvider);
+
+ Assert.IsFalse(success);
+ Assert.IsNull(foundProvider);
+ }
+
+ private class StubAuthMethod
+ : IAuthMethod
+ {
+ }
+
+ private class StubAuthProvider
+ : IAuthProvider
+ {
+ public Task TryGetAuth(StubAuthMethod authRequest)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ #endregion
+
public static DataOutputPath Parse(string dataCookerOutputPath)
{
var split = dataCookerOutputPath.Split('/');
diff --git a/src/Microsoft.Performance.Toolkit.Engine/Engine.cs b/src/Microsoft.Performance.Toolkit.Engine/Engine.cs
index 2011390..544edcb 100644
--- a/src/Microsoft.Performance.Toolkit.Engine/Engine.cs
+++ b/src/Microsoft.Performance.Toolkit.Engine/Engine.cs
@@ -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,
};
diff --git a/src/Microsoft.Performance.Toolkit.Engine/EngineApplicationEnvironment.cs b/src/Microsoft.Performance.Toolkit.Engine/EngineApplicationEnvironment.cs
new file mode 100644
index 0000000..a919c0f
--- /dev/null
+++ b/src/Microsoft.Performance.Toolkit.Engine/EngineApplicationEnvironment.cs
@@ -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 authProviders;
+
+ public EngineApplicationEnvironment(
+ string applicationName,
+ string runtimeName,
+ ITableDataSynchronization tableDataSynchronizer,
+ ISourceDataCookerFactoryRetrieval sourceDataCookerFactory,
+ ISourceSessionFactory sourceSessionFactory,
+ IMessageBox messageBox,
+ ReadOnlyDictionary authProviders)
+ : base(
+ applicationName,
+ runtimeName,
+ tableDataSynchronizer,
+ sourceDataCookerFactory,
+ sourceSessionFactory,
+ messageBox)
+ {
+ this.authProviders = authProviders;
+ }
+
+ ///
+ public override bool TryGetAuthProvider(out IAuthProvider provider)
+ {
+ if (this.authProviders.TryGetValue(typeof(TAuth), out var authProvider))
+ {
+ var authProviderTyped = authProvider as IAuthProvider;
+
+ if (authProviderTyped == null)
+ {
+ Debug.Fail($"Expected registered {nameof(authProvider)} for type {typeof(TAuth)} to be of type {nameof(IAuthProvider)}");
+
+ provider = null;
+ return false;
+ }
+
+ provider = authProviderTyped;
+ return true;
+ }
+
+ return base.TryGetAuthProvider(out provider);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Performance.Toolkit.Engine/EngineCreateInfo.cs b/src/Microsoft.Performance.Toolkit.Engine/EngineCreateInfo.cs
index 2162b2a..0735b73 100644
--- a/src/Microsoft.Performance.Toolkit.Engine/EngineCreateInfo.cs
+++ b/src/Microsoft.Performance.Toolkit.Engine/EngineCreateInfo.cs
@@ -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 authProviders = new Dictionary();
+
///
/// Initializes the statc members of the
/// class.
@@ -97,6 +101,39 @@ namespace Microsoft.Performance.Toolkit.Engine
return this;
}
+ ///
+ /// Registers an of type to be available to plugins
+ /// via .
+ ///
+ ///
+ /// The to register.
+ ///
+ ///
+ /// The type of for which the is being registered.
+ ///
+ ///
+ /// The type of the result of successful authentication requests for .
+ ///
+ ///
+ /// The instance of .
+ ///
+ ///
+ /// An for the specified type already exists.
+ ///
+ public EngineCreateInfo WithAuthProvider(IAuthProvider provider)
+ where TAuth : IAuthMethod
+ {
+ 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;
+ }
+
///
/// Gets or sets the name of the runtime on which the application is built.
///
@@ -136,5 +173,7 @@ namespace Microsoft.Performance.Toolkit.Engine
/// will fail. By default, this property is false.
///
public bool IsInteractive { get; set; }
+
+ internal ReadOnlyDictionary AuthProviders => new ReadOnlyDictionary(this.authProviders);
}
}