Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.
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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @calebkiage @baywet @zengin @MichaelMainer @andrueastman @peombwa @jobala @samwelkanda
* @calebkiage @baywet @zengin @MichaelMainer @andrueastman @peombwa @samwelkanda
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.Graph.Cli.Core.Authentication;
using Microsoft.Graph.Cli.Core.Configuration;
using Microsoft.Graph.Cli.Core.IO;
using Microsoft.Graph.Cli.Core.Utils;
using System.CommandLine;

Expand All @@ -18,20 +20,28 @@ public LoginCommand(AuthenticationServiceFactory authenticationServiceFactory) {

public Command Build() {
var loginCommand = new Command("login", "Login and store the session for use in subsequent commands");
var scopes = new Option<string[]>("--scopes", "The login scopes e.g. User.Read") {
var scopesOption = new Option<string[]>("--scopes", "The login scopes e.g. User.Read") {
Arity = ArgumentArity.OneOrMore
};
scopes.IsRequired = true;
loginCommand.AddOption(scopes);
scopesOption.IsRequired = true;
loginCommand.AddOption(scopesOption);

var strategy = new Option<AuthenticationStrategy>("--strategy", () => Constants.defaultAuthStrategy, "The authentication strategy to use.");
loginCommand.AddOption(strategy);
loginCommand.SetHandler<string[], AuthenticationStrategy, IHost, CancellationToken>(async (scopes, strategy, host, cancellationToken) =>
var clientIdOption = new Option<string>("--client-id", "The client id");
loginCommand.AddOption(clientIdOption);

var tenantIdOption = new Option<string>("--tenant-id", "The tenant id");
loginCommand.AddOption(tenantIdOption);

var strategyOption = new Option<AuthenticationStrategy>("--strategy", () => Constants.defaultAuthStrategy, "The authentication strategy to use.");
loginCommand.AddOption(strategyOption);

loginCommand.SetHandler<string[], string?, string?, AuthenticationStrategy, IHost, CancellationToken>(async (scopes, clientId, tenantId, strategy, host, cancellationToken) =>
{
var options = host.Services.GetRequiredService<IOptionsMonitor<AuthenticationOptions>>().CurrentValue;
var authService = await this.authenticationServiceFactory.GetAuthenticationServiceAsync(strategy, options?.TenantId, options?.ClientId, cancellationToken);
var authUtil = host.Services.GetRequiredService<IAuthenticationCacheUtility>();
var authService = await this.authenticationServiceFactory.GetAuthenticationServiceAsync(strategy, tenantId, clientId, cancellationToken);
await authService.LoginAsync(scopes, cancellationToken);
}, scopes, strategy);
await authUtil.SaveAuthenticationIdentifiersAsync(clientId, tenantId, cancellationToken);
}, scopesOption, clientIdOption, tenantIdOption, strategyOption);

return loginCommand;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Microsoft.Graph.Cli.Core.Configuration;

public class ConfigurationRoot {
public AuthenticationOptions AuthenticationOptions { get; set; } = new AuthenticationOptions();
}
63 changes: 63 additions & 0 deletions src/Microsoft.Graph.Cli.Core/IO/AuthenticationCacheUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Text;
using System.Text.Json;
using Microsoft.Graph.Cli.Core.Configuration;
using Microsoft.Graph.Cli.Core.Utils;

namespace Microsoft.Graph.Cli.Core.IO;

public class AuthenticationCacheUtility : IAuthenticationCacheUtility {
private readonly IPathUtility pathUtility;

public AuthenticationCacheUtility(IPathUtility pathUtility)
{
this.pathUtility = pathUtility;
}

public string GetAuthenticationCacheFilePath()
{
return Path.Join(pathUtility.GetApplicationDataDirectory(), Constants.AuthenticationIdCachePath);
}

public async Task<AuthenticationOptions> ReadAuthenticationIdentifiersAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var path = this.GetAuthenticationCacheFilePath();
if (!File.Exists(path)) {
throw new FileNotFoundException();
}

using var fileStream = File.OpenRead(path);
var configRoot = await JsonSerializer.DeserializeAsync<Configuration.ConfigurationRoot>(fileStream, cancellationToken: cancellationToken);
if (configRoot?.AuthenticationOptions is null) throw new AuthenticationIdentifierException("Cannot find cached authentication identifiers.");

return configRoot.AuthenticationOptions;
}

public async Task SaveAuthenticationIdentifiersAsync(string? clientId, string? tenantId, CancellationToken cancellationToken = default) {
cancellationToken.ThrowIfCancellationRequested();
var path = this.GetAuthenticationCacheFilePath();
var configuration = new Configuration.ConfigurationRoot {
AuthenticationOptions = new AuthenticationOptions {
ClientId = clientId,
TenantId = tenantId
}
};
using FileStream fileStream = File.OpenWrite(path);
await JsonSerializer.SerializeAsync(fileStream, configuration, cancellationToken: cancellationToken);
}

public class AuthenticationIdentifierException : Exception
{
public AuthenticationIdentifierException()
{
}

public AuthenticationIdentifierException(string? message) : base(message)
{
}

public AuthenticationIdentifierException(string? message, Exception? innerException) : base(message, innerException)
{
}
}
}
11 changes: 11 additions & 0 deletions src/Microsoft.Graph.Cli.Core/IO/IAuthenticationCacheUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Microsoft.Graph.Cli.Core.Configuration;

namespace Microsoft.Graph.Cli.Core.IO;

public interface IAuthenticationCacheUtility {
string GetAuthenticationCacheFilePath();

Task SaveAuthenticationIdentifiersAsync(string? clientId, string? tenantId, CancellationToken cancellationToken = default(CancellationToken));

Task<AuthenticationOptions> ReadAuthenticationIdentifiersAsync(CancellationToken cancellationToken = default(CancellationToken));
}
4 changes: 2 additions & 2 deletions src/Microsoft.Graph.Cli.Core/utils/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ public class Constants

public const string AuthRecordPath = "authRecord";

public const string TokenCacheName = "MicrosoftGraph";
public const string AuthenticationIdCachePath = "authentication-id-cache.json";

public const string AuthenticationSection = "Authentication";
public const string TokenCacheName = "MicrosoftGraph";

public const AuthenticationStrategy defaultAuthStrategy = AuthenticationStrategy.DeviceCode;
}
Expand Down