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
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public class GraphSession : IGraphSession
/// </summary>
public IRequestContext RequestContext { get; set; }

/// <summary>
/// Stores the user's Graph options.
/// </summary>
public IGraphOption GraphOption { get; set; }

/// <summary>
/// Represents a collection of Microsoft Graph PowerShell meta-info.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// ------------------------------------------------------------------------------
// 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.Security;
using System.Security.Cryptography.X509Certificates;

namespace Microsoft.Graph.PowerShell.Authentication
{
public interface IGraphOption
{
bool EnableWAMForMSGraph { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public interface IGraphSession
IAuthContext AuthContext { get; set; }
IDataStore DataStore { get; set; }
IRequestContext RequestContext { get; set; }
IGraphOption GraphOption { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(MSBuildThisFileDirectory)..\..\..\Repo.props" />
<PropertyGroup>
<LangVersion>9.0</LangVersion>
<TargetFrameworks>netstandard2.0;net6.0;net472</TargetFrameworks>
<RootNamespace>Microsoft.Graph.PowerShell.Authentication.Core</RootNamespace>
<VersionPrefix>2.0.0</VersionPrefix>
Expand All @@ -12,6 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.9.0" />
<PackageReference Include="Azure.Identity.BrokeredAuthentication" Version="1.0.0-beta.3" />
<PackageReference Include="Microsoft.Graph.Core" Version="3.0.7" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Azure.Core;
using Azure.Core.Diagnostics;
using Azure.Identity;
using Azure.Identity.BrokeredAuthentication;
using Microsoft.Graph.Authentication;
using Microsoft.Graph.PowerShell.Authentication.Core.Extensions;
using Microsoft.Identity.Client;
Expand Down Expand Up @@ -41,10 +42,9 @@ public static async Task<TokenCredential> GetTokenCredentialAsync(IAuthContext a
return await GetInteractiveBrowserCredentialAsync(authContext, cancellationToken).ConfigureAwait(false);
return await GetDeviceCodeCredentialAsync(authContext, cancellationToken).ConfigureAwait(false);
case AuthenticationType.AppOnly:
if (authContext.TokenCredentialType == TokenCredentialType.ClientCertificate)
return await GetClientCertificateCredentialAsync(authContext).ConfigureAwait(false);
else
return await GetClientSecretCredentialAsync(authContext).ConfigureAwait(false);
return authContext.TokenCredentialType == TokenCredentialType.ClientCertificate
? await GetClientCertificateCredentialAsync(authContext).ConfigureAwait(false)
: await GetClientSecretCredentialAsync(authContext).ConfigureAwait(false);
case AuthenticationType.ManagedIdentity:
return await GetManagedIdentityCredentialAsync(authContext).ConfigureAwait(false);
case AuthenticationType.EnvironmentVariable:
Expand Down Expand Up @@ -81,6 +81,11 @@ private static bool IsAuthFlowNotSupported()
&& (string.IsNullOrEmpty(EnvironmentVariables.ClientSecret) && string.IsNullOrEmpty(EnvironmentVariables.ClientCertificatePath)));
}

private static bool IsWamSupported()
{
return GraphSession.Instance.GraphOption.EnableWAMForMSGraph && SharedUtilities.IsWindowsPlatform();
}

private static async Task<TokenCredential> GetClientSecretCredentialAsync(IAuthContext authContext)
{
if (authContext is null)
Expand Down Expand Up @@ -108,19 +113,28 @@ private static async Task<InteractiveBrowserCredential> GetInteractiveBrowserCre
{
if (authContext is null)
throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext);

var interactiveOptions = new InteractiveBrowserCredentialOptions
{
ClientId = authContext.ClientId,
TenantId = authContext.TenantId,
AuthorityHost = new Uri(GetAuthorityUrl(authContext)),
TokenCachePersistenceOptions = GetTokenCachePersistenceOptions(authContext)
};
var interactiveOptions = IsWamSupported() ? new InteractiveBrowserCredentialBrokerOptions(WindowHandleUtlities.GetConsoleOrTerminalWindow()) : new InteractiveBrowserCredentialOptions();
interactiveOptions.ClientId = authContext.ClientId;
interactiveOptions.TenantId = authContext.TenantId ?? "common";
interactiveOptions.AuthorityHost = new Uri(GetAuthorityUrl(authContext));
interactiveOptions.TokenCachePersistenceOptions = GetTokenCachePersistenceOptions(authContext);

if (!File.Exists(Constants.AuthRecordPath))
{
AuthenticationRecord authRecord;
var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveOptions);
var authRecord = await interactiveBrowserCredential.AuthenticateAsync(new TokenRequestContext(authContext.Scopes), cancellationToken).ConfigureAwait(false);
if (IsWamSupported())
{
authRecord = await Task.Run(() =>
{
// Run the thread in MTA.
return interactiveBrowserCredential.Authenticate(new TokenRequestContext(authContext.Scopes), cancellationToken);
});
}
else
{
authRecord = await interactiveBrowserCredential.AuthenticateAsync(new TokenRequestContext(authContext.Scopes), cancellationToken).ConfigureAwait(false);
}
await WriteAuthRecordAsync(authRecord).ConfigureAwait(false);
return interactiveBrowserCredential;
}
Expand Down Expand Up @@ -174,10 +188,9 @@ private static async Task<ClientCertificateCredential> GetClientCertificateCrede

private static TokenCachePersistenceOptions GetTokenCachePersistenceOptions(IAuthContext authContext)
{
if (authContext.ContextScope == ContextScope.Process)
return GraphSession.Instance.InMemoryTokenCache.GetTokenCachePersistenceOptions();

return new TokenCachePersistenceOptions { Name = Constants.CacheName };
return authContext.ContextScope == ContextScope.Process
? GraphSession.Instance.InMemoryTokenCache.GetTokenCachePersistenceOptions()
: new TokenCachePersistenceOptions { Name = Constants.CacheName };
}

/// <summary>
Expand Down Expand Up @@ -291,10 +304,9 @@ private static string GetAuthorityUrl(IAuthContext authContext)
if (authContext is null)
throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext);
string audience = authContext.TenantId ?? Constants.DefaultTenant;
if (GraphSession.Instance.Environment != null)
return $"{GraphSession.Instance.Environment.AzureADEndpoint}/{audience}";

return $"{Constants.DefaultAzureADEndpoint}/{audience}";
return GraphSession.Instance.Environment != null
? $"{GraphSession.Instance.Environment.AzureADEndpoint}/{audience}"
: $"{Constants.DefaultAzureADEndpoint}/{audience}";
}

/// <summary>
Expand All @@ -303,6 +315,7 @@ private static string GetAuthorityUrl(IAuthContext authContext)
/// </summary>
/// <param name="authContext">Current <see cref="IAuthContext"/> context</param>
/// <returns>A <see cref="X509Certificate2"/> based on provided <see cref="IAuthContext"/> context</returns>
/// <returns>A <see cref="X509Certificate2"/> based on provided <see cref="IAuthContext"/> context</returns>
private static X509Certificate2 GetCertificate(IAuthContext authContext)
{
if (authContext is null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using Microsoft.Identity.Client.Extensions.Msal;
using System;
using System.Runtime.InteropServices;

namespace Microsoft.Graph.PowerShell.Authentication.Core.Utilities
{
internal static class WindowHandleUtlities
{
enum GetAncestorFlags
{
GetParent = 1,
GetRoot = 2,
/// <summary>
/// Retrieves the owned root window by walking the chain of parent and owner windows returned by GetParent.
/// </summary>
GetRootOwner = 3
}

/// <summary>
/// Retrieves the handle to the ancestor of the specified window.
/// See https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-acquire-token-wam#console-applications.
/// </summary>
/// <param name="hwnd">A handle to the window whose ancestor is to be retrieved.
/// If this parameter is the desktop window, the function returns NULL. </param>
/// <param name="flags">The ancestor to be retrieved.</param>
/// <returns>The return value is the handle to the ancestor window.</returns>
[DllImport("user32.dll", ExactSpelling = true)]
static extern IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags flags);

[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();

public static IntPtr GetConsoleOrTerminalWindow()
{
if (SharedUtilities.IsWindowsPlatform())
{
IntPtr consoleHandle = GetConsoleWindow();
IntPtr handle = GetAncestor(consoleHandle, GetAncestorFlags.GetRootOwner);
return handle;
}
else
{
// can't call Windows native APIs
return (IntPtr)0;
}
}
}
}
44 changes: 44 additions & 0 deletions src/Authentication/Authentication/Cmdlets/SetMgGraphOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using Newtonsoft.Json;
using System.IO;
using System.Management.Automation;

namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets
{
[Cmdlet(VerbsCommon.Set, "MgGraphOption", HelpUri = "")]
public class SetMgGraphOption : PSCmdlet
{
[Parameter]
public bool EnableLoginByWAM { get; set; }

protected override void BeginProcessing()
{
base.BeginProcessing();
}

protected override void ProcessRecord()
{
base.ProcessRecord();
if (this.IsParameterBound(nameof(EnableLoginByWAM)))
{
GraphSession.Instance.GraphOption.EnableWAMForMSGraph = EnableLoginByWAM;
var message = $"Signin by Web Account Manager (WAM) is {(EnableLoginByWAM ? "enabled" : "disabled")}.";
WriteObject(message);
}
File.WriteAllText(Constants.GraphOptionsFilePath, JsonConvert.SerializeObject(GraphSession.Instance.GraphOption, Formatting.Indented));
}

protected override void EndProcessing()
{
base.EndProcessing();
}

protected override void StopProcessing()
{
base.StopProcessing();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
using Microsoft.Graph.PowerShell.Authentication.Helpers;
using Microsoft.Graph.PowerShell.Authentication.Interfaces;
using Microsoft.Graph.PowerShell.Authentication.Models;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Management.Automation;
using RequestContext = Microsoft.Graph.PowerShell.Authentication.Models.RequestContext;

Expand All @@ -26,10 +28,18 @@ public static void InitializeSession()
/// <returns><see cref="GraphSession"/></returns>
internal static GraphSession CreateInstance(IDataStore dataStore = null)
{
IGraphOption graphOptions = null;
if (File.Exists(Constants.GraphOptionsFilePath))
{
// Deserialize the JSON into the GraphOption instance
graphOptions = JsonConvert.DeserializeObject<GraphOption>(File.ReadAllText(Constants.GraphOptionsFilePath));
}

return new GraphSession
{
DataStore = dataStore ?? new DiskDataStore(),
RequestContext = new RequestContext()
RequestContext = new RequestContext(),
GraphOption = graphOptions ?? new GraphOption()
};
}
/// <summary>
Expand Down
1 change: 1 addition & 0 deletions src/Authentication/Authentication/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public static class Constants
internal const int MAX_NUMBER_OF_RETRY = 10;
internal const int DEFAULT_RETRY_DELAY = 3;
internal const int DEFAULT_MAX_RETRY = 3;
internal static readonly string GraphOptionsFilePath = Path.Combine(Core.Constants.GraphDirectoryPath, "mg.graphoptions.json");

public static class HelpMessages
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<package>
<metadata>
<version>2.0.0-rc3</version>
<version>2.0.0-rc4</version>
<id>Microsoft.Graph.Authentication</id>
<description>Microsoft Graph PowerShell authentication module</description>
<authors>Microsoft</authors>
Expand All @@ -27,6 +27,9 @@
<!-- Copy framework dependent assemblies to respective framework directory. -->
<!-- Shared -->
<file src="artifacts\Dependencies\Azure.Identity.dll" target="Dependencies" />
<file src="artifacts\Dependencies\Azure.Identity.BrokeredAuthentication.dll" target="Dependencies" />
<file src="artifacts\Dependencies\Microsoft.Identity.Client.Broker.dll" target="Dependencies" />
<file src="artifacts\Dependencies\Microsoft.Identity.Client.NativeInterop.dll" target="Dependencies" />
<file src="artifacts\Dependencies\Microsoft.Bcl.AsyncInterfaces.dll" target="Dependencies" />
<file src="artifacts\Dependencies\Microsoft.IdentityModel.Abstractions.dll" target="Dependencies" />
<file src="artifacts\Dependencies\System.Buffers.dll" target="Dependencies" />
Expand All @@ -39,6 +42,9 @@
<file src="artifacts\Dependencies\Microsoft.Kiota.Serialization.Form.dll" target="Dependencies" />
<file src="artifacts\Dependencies\Microsoft.Kiota.Serialization.Json.dll" target="Dependencies" />
<file src="artifacts\Dependencies\Microsoft.Kiota.Serialization.Text.dll" target="Dependencies" />
<file src="artifacts\Dependencies\Core\runtimes\win-x64\native\msalruntime.dll" target="Dependencies" />
<file src="artifacts\Dependencies\Core\runtimes\win-x86\native\msalruntime_x86.dll" target="Dependencies" />
<file src="artifacts\Dependencies\Core\runtimes\win-arm64\native\msalruntime_arm64.dll" target="Dependencies" />
<!-- Core-->
<file src="artifacts\Dependencies\Core\Azure.Core.dll" target="Dependencies\Core" />
<file src="artifacts\Dependencies\Core\Microsoft.Graph.Core.dll" target="Dependencies\Core" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ FunctionsToExport = 'Find-MgGraphCommand', 'Find-MgGraphPermission'
CmdletsToExport = 'Connect-MgGraph', 'Disconnect-MgGraph', 'Get-MgContext',
'Invoke-MgGraphRequest', 'Add-MgEnvironment', 'Get-MgEnvironment',
'Remove-MgEnvironment', 'Set-MgEnvironment', 'Get-MgRequestContext',
'Set-MgRequestContext'
'Set-MgRequestContext', 'Set-MgGraphOption'

# Variables to export from this module
VariablesToExport = '*'
Expand Down
14 changes: 14 additions & 0 deletions src/Authentication/Authentication/Models/GraphOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using System.IO;

namespace Microsoft.Graph.PowerShell.Authentication
{
internal class GraphOption : IGraphOption
{
public bool EnableWAMForMSGraph { get; set; }
}

}
Loading