Skip to content
Open
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 @@ -6,5 +6,29 @@ namespace TfsCmdlets.SourceGenerators.Generators.Cmdlets
public class CmdletGenerator : BaseGenerator<Filter, TypeProcessor>
{
protected override string GeneratorName => nameof(CmdletGenerator);

protected override void GenerateCmdletParameters(CmdletInfo cmdletInfo)
{
base.GenerateCmdletParameters(cmdletInfo);

if (cmdletInfo.Name.StartsWith("Connect-"))
{
cmdletInfo.Parameters.Add(new CmdletParameter
{
Name = "AzCli",
Type = "SwitchParameter",
Mandatory = false,
Position = -1
});

cmdletInfo.Parameters.Add(new CmdletParameter
{
Name = "UseMSI",
Type = "SwitchParameter",
Mandatory = false,
Position = -1
});
}
}
}
}
}
45 changes: 42 additions & 3 deletions CSharp/TfsCmdlets/Cmdlets/Credential/NewCredential.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Management.Automation;
using System.Management.Automation;
using Microsoft.VisualStudio.Services.Common;
using System.Net;
using Microsoft.VisualStudio.Services.Client;
Expand All @@ -19,6 +19,18 @@ partial class NewCredential
/// </summary>
[Parameter(Position = 0, Mandatory = true)]
public Uri Url { get; set; }

/// <summary>
/// Specifies that the credentials should be obtained from the currently logged in Azure CLI user.
/// </summary>
[Parameter(Mandatory = false)]
public SwitchParameter AzCli { get; set; }

/// <summary>
/// Specifies that the credentials should be obtained from the Azure Managed Identity present in the current script context.
/// </summary>
[Parameter(Mandatory = false)]
public SwitchParameter UseMSI { get; set; }
}

[CmdletController(typeof(VssCredentials), CustomCmdletName = "NewCredential")]
Expand All @@ -27,6 +39,9 @@ partial class GetCredentialController
[Import]
private IInteractiveAuthentication InteractiveAuthentication { get; }

[Import]
private IAzCliAuthentication AzCliAuthentication { get; }

protected override IEnumerable Run()
{
var connectionMode = ConnectionMode.CachedCredentials;
Expand All @@ -39,6 +54,10 @@ protected override IEnumerable Run()
connectionMode = ConnectionMode.AccessToken;
else if (Interactive)
connectionMode = ConnectionMode.Interactive;
else if (AzCli)
connectionMode = ConnectionMode.AzCli;
else if (UseMSI)
connectionMode = ConnectionMode.UseMSI;

NetworkCredential netCred = null;

Expand Down Expand Up @@ -124,6 +143,24 @@ protected override IEnumerable Run()
throw new Exception("Interactive authentication is not supported for TFS / Azure DevOps Server in PowerShell Core. Please use either a username/password credential or a Personal Access Token.");
}

case ConnectionMode.AzCli:
{
Logger.Log("Using Azure CLI credential");

yield return new VssCredentials(
new VssOAuthAccessTokenCredential(AzCliAuthentication.GetToken(Url)));
break;
}

case ConnectionMode.UseMSI:
{
Logger.Log("Using Managed Identity credential");

yield return new VssCredentials(
new VssOAuthAccessTokenCredential(AzCliAuthentication.GetToken(Url, useMsi: true)));
break;
}

default:
{
throw new Exception($"Invalid parameter set '{connectionMode}'");
Expand All @@ -150,7 +187,9 @@ private enum ConnectionMode
CredentialObject,
UserNamePassword,
AccessToken,
Interactive
Interactive,
AzCli,
UseMSI
}
}
}
}
14 changes: 13 additions & 1 deletion CSharp/TfsCmdlets/Cmdlets/Organization/ConnectOrganization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,17 @@ partial class ConnectOrganization
[Alias("Collection")]
[ValidateNotNull]
public object Organization { get; set; }

/// <summary>
/// Specifies that the credentials should be obtained from the currently logged in Azure CLI user.
/// </summary>
[Parameter(Mandatory = false)]
public SwitchParameter AzCli { get; set; }

/// <summary>
/// Specifies that the credentials should be obtained from the Azure Managed Identity present in the current script context.
/// </summary>
[Parameter(Mandatory = false)]
public SwitchParameter UseMSI { get; set; }
}
}
}
14 changes: 13 additions & 1 deletion CSharp/TfsCmdlets/Cmdlets/Team/ConnectTeam.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ partial class ConnectTeam
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)]
[ValidateNotNull()]
public object Team { get; set; }

/// <summary>
/// Specifies that the credentials should be obtained from the currently logged in Azure CLI user.
/// </summary>
[Parameter(Mandatory = false)]
public SwitchParameter AzCli { get; set; }

/// <summary>
/// Specifies that the credentials should be obtained from the Azure Managed Identity present in the current script context.
/// </summary>
[Parameter(Mandatory = false)]
public SwitchParameter UseMSI { get; set; }
}

[CmdletController(typeof(Models.Team))]
Expand All @@ -36,4 +48,4 @@ protected override IEnumerable Run()
yield return Team;
}
}
}
}
14 changes: 13 additions & 1 deletion CSharp/TfsCmdlets/Cmdlets/TeamProject/ConnectTeamProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ partial class ConnectTeamProject
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)]
[ValidateNotNull()]
public object Project { get; set; }

/// <summary>
/// Specifies that the credentials should be obtained from the currently logged in Azure CLI user.
/// </summary>
[Parameter(Mandatory = false)]
public SwitchParameter AzCli { get; set; }

/// <summary>
/// Specifies that the credentials should be obtained from the Azure Managed Identity present in the current script context.
/// </summary>
[Parameter(Mandatory = false)]
public SwitchParameter UseMSI { get; set; }
}

[CmdletController(typeof(WebApiTeamProject))]
Expand All @@ -35,4 +47,4 @@ protected override IEnumerable Run()
[Import]
private ICurrentConnections CurrentConnections { get; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ partial class ConnectTeamProjectCollection
[Alias("Organization")]
[ValidateNotNull]
public object Collection { get; set; }

/// <summary>
/// Specifies that the credentials should be obtained from the currently logged in Azure CLI user.
/// </summary>
[Parameter(Mandatory = false)]
public SwitchParameter AzCli { get; set; }

/// <summary>
/// Specifies that the credentials should be obtained from the Azure Managed Identity present in the current script context.
/// </summary>
[Parameter(Mandatory = false)]
public SwitchParameter UseMSI { get; set; }
}

[CmdletController(typeof(Connection))]
Expand All @@ -57,6 +69,9 @@ partial class ConnectTeamProjectCollectionController
[Import]
private ICurrentConnections CurrentConnections { get; }

[Import]
private IAzCliAuthentication AzCliAuthentication { get; }

protected override IEnumerable Run()
{
var tpc = Data.GetCollection(new { Collection = Collection ?? Parameters.Get<object>("Organization") });
Expand All @@ -75,4 +90,4 @@ protected override IEnumerable Run()
yield return tpc;
}
}
}
}
7 changes: 7 additions & 0 deletions CSharp/TfsCmdlets/Services/IAzCliAuthentication.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TfsCmdlets.Services
{
public interface IAzCliAuthentication
{
string GetToken(Uri uri, bool useMsi = false);
}
}
35 changes: 35 additions & 0 deletions CSharp/TfsCmdlets/Services/Impl/AzCliAuthenticationImpl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Identity;

namespace TfsCmdlets.Services.Impl
{
[Export(typeof(IAzCliAuthentication)), Shared]
public class AzCliAuthenticationImpl : IAzCliAuthentication
{
private const string AzureDevOpsResourceId = "499b84ac-1321-427f-aa17-267ca6975798";

public string GetToken(Uri uri, bool useMsi = false)
{
var token = useMsi ? GetMsiTokenAsync().Result : GetAzCliTokenAsync().Result;
return token;
}

private async Task<string> GetAzCliTokenAsync()
{
var credential = new DefaultAzureCredential();
var tokenRequestContext = new TokenRequestContext(new[] { $"{AzureDevOpsResourceId}/.default" });
var token = await credential.GetTokenAsync(tokenRequestContext);
return token.Token;
}

private async Task<string> GetMsiTokenAsync()
{
var credential = new ManagedIdentityCredential();
var tokenRequestContext = new TokenRequestContext(new[] { $"{AzureDevOpsResourceId}/.default" });
var token = await credential.GetTokenAsync(tokenRequestContext);
return token.Token;
}
}
}
Loading