Skip to content

Commit

Permalink
feat: add web account manager (WAM) support (#436)
Browse files Browse the repository at this point in the history
chore: remove redundant attribute

fix: add default scopes to solve login error when no scopes are provided

ci: fixes test failure

chore: remove redundant SupportedOSPlatform attribute
  • Loading branch information
calebkiage committed Aug 9, 2024
1 parent e7d7682 commit a5fe538
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public void Parses_No_Scopes()
var scopes = result.FindResultFor(command.Options[0])?.Tokens.Select(t => t.Value);

// Then
Assert.Null(scopes);
Assert.NotNull(scopes);
Assert.Empty(scopes);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
using System.Threading;
using System.Threading.Tasks;

#if OS_WINDOWS
using System.Diagnostics;
using Azure.Identity.Broker;
using Microsoft.Graph.Cli.Core.utils;
#endif

namespace Microsoft.Graph.Cli.Core.Authentication;

/// <summary>
Expand Down Expand Up @@ -120,13 +126,17 @@ private async Task<DeviceCodeCredential> GetDeviceCodeCredentialAsync(string? te

private async Task<InteractiveBrowserCredential> GetInteractiveBrowserCredentialAsync(string? tenantId, string? clientId, Uri authorityHost, CancellationToken cancellationToken = default)
{
InteractiveBrowserCredentialOptions credOptions = new()
{
ClientId = clientId ?? Constants.DefaultAppId,
TenantId = tenantId ?? Constants.DefaultTenant,
DisableAutomaticAuthentication = true,
AuthorityHost = authorityHost
};
#if OS_WINDOWS
Debug.Assert(OperatingSystem.IsWindows());
InteractiveBrowserCredentialBrokerOptions credOptions = new(WindowUtils.GetConsoleOrTerminalWindow());
#else
InteractiveBrowserCredentialOptions credOptions = new();
#endif

credOptions.ClientId = clientId ?? Constants.DefaultAppId;
credOptions.TenantId = tenantId ?? Constants.DefaultTenant;
credOptions.DisableAutomaticAuthentication = true;
credOptions.AuthorityHost = authorityHost;

TokenCachePersistenceOptions tokenCacheOptions = new() { Name = Constants.TokenCacheName };
credOptions.TokenCachePersistenceOptions = tokenCacheOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ public async Task LoginAsync(string[] scopes, CancellationToken cancellationToke
{
if (record is null) return;
var recordPath = Path.Combine(pathUtility.GetApplicationDataDirectory(), Constants.AuthRecordPath);
using var authRecordStream = new FileStream(recordPath, FileMode.Create, FileAccess.Write);
await record.SerializeAsync(authRecordStream, cancellationToken);
var authRecordStream = new FileStream(recordPath, FileMode.Create, FileAccess.Write);
await using (authRecordStream.ConfigureAwait(false))
{
await record.SerializeAsync(authRecordStream, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Microsoft.Graph.Cli.Core.Commands.Authentication;
/// </summary>
public sealed class LoginCommand : Command
{
private Option<string[]> scopesOption = new("--scopes", "The login scopes e.g. User.Read. Required scopes can be found in the docs linked against each verb (get, list, create...) command.")
private Option<string[]> scopesOption = new("--scopes",() => ["User.Read"], "The login scopes e.g. User.Read. Required scopes can be found in the docs linked against each verb (get, list, create...) command.")

Check warning on line 16 in src/Microsoft.Graph.Cli.Core/Commands/Authentication/LoginCommand.cs

View workflow job for this annotation

GitHub Actions / Build

Make 'scopesOption' 'readonly'. (https://rules.sonarsource.com/csharp/RSPEC-2933)
{
Arity = ArgumentArity.ZeroOrMore,
AllowMultipleArgumentsPerToken = true,
Expand Down
9 changes: 8 additions & 1 deletion src/Microsoft.Graph.Cli.Core/Microsoft.Graph.Cli.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
<IsTrimmable>true</IsTrimmable>

<PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion>

<!-- Needed to use LibraryImport -->
<!-- see: https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke-source-generation#differences-from-dllimport -->
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup>
Expand Down Expand Up @@ -39,9 +43,12 @@
<PropertyGroup Condition="'$(TF_BUILD)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

<PropertyGroup Condition="'$(OS)' == 'Windows_NT'">
<DefineConstants>OS_WINDOWS</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.12.0"/>
<PackageReference Include="Azure.Identity.Broker" Version="1.1.0" Condition="'$(OS)' == 'Windows_NT'" />
<PackageReference Include="JmesPath.Net" Version="1.0.330"/>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0"/>
Expand Down
40 changes: 40 additions & 0 deletions src/Microsoft.Graph.Cli.Core/utils/WindowUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

namespace Microsoft.Graph.Cli.Core.utils;

[SupportedOSPlatform("Windows")]
internal partial class WindowUtils
{
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.
/// </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>
[LibraryImport("user32.dll")]
private static partial IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags flags);

[LibraryImport("kernel32.dll")]
private static partial IntPtr GetConsoleWindow();

// https://learn.microsoft.com/en-us/entra/msal/dotnet/acquiring-tokens/desktop-mobile/wam#parent-window-handles
internal static IntPtr GetConsoleOrTerminalWindow()
{
var consoleHandle = GetConsoleWindow();
var handle = GetAncestor(consoleHandle, GetAncestorFlags.GetRootOwner);
return handle;
}
}

0 comments on commit a5fe538

Please sign in to comment.