Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions config/ModuleMetadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@
"versions": {
"authentication": {
"prerelease": "",
"version": "2.40.0"
"version": "2.40.1"
},
"beta": {
"prerelease": "",
"version": "2.40.0"
"version": "2.40.1"
},
"v1.0": {
"prerelease": "",
"version": "2.40.0"
"version": "2.40.1"
}
}
}
9 changes: 9 additions & 0 deletions src/Authentication/Authentication.Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ public static class Constants
internal const string AuthRecordName = "mg.authrecord.json";
internal const int MaxAuthRetry = 2;
internal static readonly string AuthRecordPath = Path.Combine(GraphDirectoryPath, AuthRecordName);

/// <summary>
/// Header names mirrored from Microsoft.Graph.Core's CoreConstants so callers do not need a reference to that assembly.
/// </summary>
public static class Headers
{
public const string SdkVersionHeaderName = "SdkVersion";
public const string ClientRequestId = "client-request-id";
}
}

internal static class EnvironmentVariables
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using System.IO;
using System.Net.Http;
using System.Threading.Tasks;

namespace Microsoft.Graph.PowerShell.Authentication.Extensions
namespace Microsoft.Graph.PowerShell.Authentication.Core.Extensions
{
internal static class HttpRequestMessageExtensions
{
Expand Down Expand Up @@ -67,4 +67,4 @@ internal static bool IsBuffered(this HttpRequestMessage httpRequestMessage)
return true;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------


using Microsoft.Graph.Authentication;
using Microsoft.Graph.PowerShell.Authentication.Extensions;
using Microsoft.Graph.PowerShell.Authentication.Core.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -15,8 +14,12 @@
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Graph.PowerShell.Authentication.Handlers
namespace Microsoft.Graph.PowerShell.Authentication.Core.Http
{
/// <summary>
/// A <see cref="DelegatingHandler"/> that authenticates outgoing requests and retries once on 401 with CAE claims.
/// This type lives in Authentication.Core so that Microsoft.Graph.Core / Azure.Identity types never leak into the cmdlet assembly.
/// </summary>
internal class AuthenticationHandler : DelegatingHandler
{
private const string ClaimsKey = "claims";
Expand Down Expand Up @@ -129,4 +132,4 @@ private static async Task DrainAsync(HttpResponseMessage response)
response.Dispose();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using Microsoft.Graph.PowerShell.Authentication.Core.Interfaces;
using Microsoft.Graph.PowerShell.Authentication.Core.Utilities;
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware;
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware.Options;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Net.Http;

namespace Microsoft.Graph.PowerShell.Authentication.Core.Http
{
/// <summary>
/// Builds the Microsoft Graph <see cref="HttpClient"/> pipeline.
/// The public surface of this type intentionally only exposes BCL types (<see cref="HttpClient"/>, <see cref="DelegatingHandler"/>)
/// and types owned by this assembly, so that callers living in the default <c>AssemblyLoadContext</c> never bind to
/// Microsoft.Graph.Core, Microsoft.Kiota.* or Azure.* directly.
/// </summary>
public static class GraphHttpClientFactory
{
/// <summary>
/// Creates a pre-configured Microsoft Graph <see cref="HttpClient"/> for the provided <see cref="IAuthContext"/>.
/// </summary>
/// <param name="authContext">The authentication context used to acquire tokens.</param>
/// <param name="requestContext">Retry/timeout settings.</param>
/// <param name="customHandlers">
/// Optional handlers supplied by the caller. They are inserted after authentication and before the retry/redirect
/// handlers, in the order provided.
/// </param>
/// <param name="trailingHandlers">
/// Optional handlers supplied by the caller that are appended after the retry/redirect handlers, in the order provided.
/// </param>
/// <param name="useLegacyClientHandler">
/// When true a <see cref="HttpClientHandler"/> with auto-redirect disabled and GZip/Deflate decompression is used
/// as the final handler (required on .NET Framework / Windows PowerShell).
/// </param>
/// <returns>A configured <see cref="HttpClient"/>.</returns>
public static HttpClient Create(
IAuthContext authContext,
IRequestContext requestContext,
IEnumerable<DelegatingHandler> customHandlers = null,
IEnumerable<DelegatingHandler> trailingHandlers = null,
bool useLegacyClientHandler = false)
{
if (authContext is null)
throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext);
if (requestContext is null)
throw new AuthenticationException(string.Format(CultureInfo.InvariantCulture, ErrorConstants.Message.MissingSessionProperty, nameof(requestContext)));

var authProvider = AuthenticationHelpers.GetAuthenticationProviderAsync(authContext).ConfigureAwait(false).GetAwaiter().GetResult();

var delegatingHandlers = new List<DelegatingHandler>
{
new AuthenticationHandler(authProvider)
};

if (customHandlers != null)
delegatingHandlers.AddRange(customHandlers);

delegatingHandlers.Add(new RetryHandler(new RetryHandlerOption
{
Delay = requestContext.RetryDelay,
MaxRetry = requestContext.MaxRetry,
RetriesTimeLimit = requestContext.RetriesTimeLimit
}));
delegatingHandlers.Add(new RedirectHandler());

if (trailingHandlers != null)
delegatingHandlers.AddRange(trailingHandlers);

HttpClient httpClient = useLegacyClientHandler
? GraphClientFactory.Create(delegatingHandlers, finalHandler: new HttpClientHandler
{
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
})
: GraphClientFactory.Create(delegatingHandlers);

httpClient.Timeout = requestContext.ClientTimeout;
return httpClient;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<LangVersion>9.0</LangVersion>
<TargetFrameworks>netstandard2.0;net6.0;net472</TargetFrameworks>
<RootNamespace>Microsoft.Graph.PowerShell.Authentication.Core</RootNamespace>
<Version>2.38.1</Version>
<Version>2.40.1</Version>
<!-- Suppress .NET Target Framework Moniker (TFM) Support Build Warnings -->
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
</PropertyGroup>
Expand All @@ -19,11 +19,11 @@
<PackageReference Include="Azure.Identity" Version="1.18.0" />
<PackageReference Include="Azure.Identity.Broker" Version="1.4.0" />
<!-- Explicitly reference Microsoft.Identity.Client to ensure form_post support -->
<PackageReference Include="Microsoft.Identity.Client" Version="4.83.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.88.0" />
<!-- Explicitly reference the MSAL broker to clear WAM/broker cached accounts on disconnect -->
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.83.1" />
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.88.0" />
<!-- Explicitly reference MSAL Extensions to clear persisted token cache on disconnect -->
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.83.1" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.88.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.3" />
<PackageReference Include="Microsoft.Graph.Core" Version="4.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
Expand All @@ -32,4 +32,4 @@
<Target Name="CopyFiles" AfterTargets="Build">
<Copy SourceFiles="@(PreLoadAssemblies)" DestinationFolder="$(OutputPath)/publish" />
</Target>
</Project>
</Project>
147 changes: 147 additions & 0 deletions src/Authentication/Authentication.Loader/GraphAssemblyLoadContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;

namespace Microsoft.Graph.PowerShell.Authentication.Loader
{
/// <summary>
/// A private <see cref="AssemblyLoadContext"/> that hosts Microsoft.Graph.Authentication.Core and all of its
/// third-party dependencies (Azure.Identity, Microsoft.Identity.Client, Microsoft.Kiota.*, Microsoft.Graph.Core, ...)
/// so that they never collide with copies loaded by other PowerShell modules in the default context.
/// </summary>
public sealed class GraphAssemblyLoadContext : AssemblyLoadContext
{
/// <summary>
/// Assemblies that must always be resolved from the default load context because their types are shared
/// with the cmdlet assembly, the PowerShell engine or the generated service modules.
/// </summary>
private static readonly HashSet<string> s_sharedAssemblyNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Newtonsoft.Json",
"System.Management.Automation",
"Microsoft.PowerShell.Commands.Utility",
"Microsoft.PowerShell.Commands.Management",
"Microsoft.PowerShell.Security",
"Microsoft.PowerShell.ConsoleHost",
"Microsoft.Graph.Authentication",
"Microsoft.Graph.Authentication.Loader",
};

/// <summary>
/// Simple names of the assemblies that make up the shared framework (Trusted Platform Assemblies). These must always
/// come from the runtime, never from the module's Dependencies folder: loading e.g. the netstandard build of
/// System.Memory.dll into this context would create a second, incompatible ReadOnlyMemory&lt;T&gt; type.
/// </summary>
private static readonly HashSet<string> s_trustedPlatformAssemblies = GetTrustedPlatformAssemblies();

private readonly string _dependencyFolder;
private readonly string _psEditionDependencyFolder;
private readonly string _nativeFolder;

public GraphAssemblyLoadContext(string dependencyFolder, string psEditionDependencyFolder)
: base(name: "Microsoft.Graph.Authentication", isCollectible: false)
{
_dependencyFolder = dependencyFolder ?? throw new ArgumentNullException(nameof(dependencyFolder));
_psEditionDependencyFolder = psEditionDependencyFolder ?? throw new ArgumentNullException(nameof(psEditionDependencyFolder));
_nativeFolder = Path.Combine(_dependencyFolder, "runtimes", GetRuntimeIdentifier(), "native");
}

/// <summary>
/// Gets the folder containing shared managed dependencies.
/// </summary>
public string DependencyFolder => _dependencyFolder;

/// <summary>
/// Gets the folder containing PowerShell edition specific managed dependencies.
/// </summary>
public string PSEditionDependencyFolder => _psEditionDependencyFolder;

/// <inheritdoc/>
protected override Assembly Load(AssemblyName assemblyName)
{
if (s_sharedAssemblyNames.Contains(assemblyName.Name) || s_trustedPlatformAssemblies.Contains(assemblyName.Name))
{
// Defer to the default context so type identity is preserved across the boundary.
return null;
}

string path = ResolveManagedPath(assemblyName.Name);
return path != null ? LoadFromAssemblyPath(path) : null;
}

/// <inheritdoc/>
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
foreach (string candidate in GetNativeCandidates(unmanagedDllName))
{
if (File.Exists(candidate))
{
return LoadUnmanagedDllFromPath(candidate);
}
}
return IntPtr.Zero;
}

/// <summary>
/// Attempts to resolve a managed assembly file for the given simple name from the module's dependency folders.
/// </summary>
/// <param name="simpleName">Simple assembly name (without extension).</param>
/// <returns>The full path when found; otherwise null.</returns>
public string ResolveManagedPath(string simpleName)
{
string fileName = simpleName + ".dll";

string path = Path.Combine(_psEditionDependencyFolder, fileName);
if (File.Exists(path))
return path;

path = Path.Combine(_dependencyFolder, fileName);
return File.Exists(path) ? path : null;
}

private IEnumerable<string> GetNativeCandidates(string unmanagedDllName)
{
string fileName = unmanagedDllName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) ? unmanagedDllName : unmanagedDllName + ".dll";
yield return Path.Combine(_nativeFolder, fileName);
yield return Path.Combine(_psEditionDependencyFolder, "runtimes", GetRuntimeIdentifier(), "native", fileName);
yield return Path.Combine(_psEditionDependencyFolder, fileName);
yield return Path.Combine(_dependencyFolder, fileName);
}

private static HashSet<string> GetTrustedPlatformAssemblies()
{
var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") is string tpa)
{
foreach (string path in tpa.Split(Path.PathSeparator))
{
if (!string.IsNullOrEmpty(path))
result.Add(Path.GetFileNameWithoutExtension(path));
}
}
return result;
}

private static string GetRuntimeIdentifier()
{
string os = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "win"
: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "osx"
: "linux";
string arch = RuntimeInformation.ProcessArchitecture switch
{
Architecture.X86 => "x86",
Architecture.Arm64 => "arm64",
Architecture.Arm => "arm",
_ => "x64",
};
return $"{os}-{arch}";
}
}
}
Loading