11using System ;
22using System . Collections . Generic ;
33using System . IO ;
4+ using System . Linq ;
45using System . Net . Http ;
56using System . Threading . Tasks ;
67using GitCredentialManager . Interop . Windows . Native ;
@@ -23,7 +24,7 @@ namespace GitCredentialManager.Authentication
2324 public interface IMicrosoftAuthentication
2425 {
2526 Task < IMicrosoftAuthenticationResult > GetTokenAsync ( string authority , string clientId , Uri redirectUri ,
26- string [ ] scopes , string userName ) ;
27+ string [ ] scopes , string userName , bool msaPt = false ) ;
2728 }
2829
2930 public interface IMicrosoftAuthenticationResult
@@ -59,26 +60,31 @@ public MicrosoftAuthentication(ICommandContext context)
5960 #region IMicrosoftAuthentication
6061
6162 public async Task < IMicrosoftAuthenticationResult > GetTokenAsync (
62- string authority , string clientId , Uri redirectUri , string [ ] scopes , string userName )
63+ string authority , string clientId , Uri redirectUri , string [ ] scopes , string userName , bool msaPt )
6364 {
6465 // Check if we can and should use OS broker authentication
6566 bool useBroker = CanUseBroker ( ) ;
6667 Context . Trace . WriteLine ( useBroker
6768 ? "OS broker is available and enabled."
6869 : "OS broker is not available or enabled." ) ;
6970
71+ if ( msaPt )
72+ {
73+ Context . Trace . WriteLine ( "MSA passthrough is enabled." ) ;
74+ }
75+
7076 try
7177 {
7278 // Create the public client application for authentication
73- IPublicClientApplication app = await CreatePublicClientApplicationAsync ( authority , clientId , redirectUri , useBroker ) ;
79+ IPublicClientApplication app = await CreatePublicClientApplicationAsync ( authority , clientId , redirectUri , useBroker , msaPt ) ;
7480
7581 AuthenticationResult result = null ;
7682
7783 // Try silent authentication first if we know about an existing user
7884 bool hasExistingUser = ! string . IsNullOrWhiteSpace ( userName ) ;
7985 if ( hasExistingUser )
8086 {
81- result = await GetAccessTokenSilentlyAsync ( app , scopes , userName ) ;
87+ result = await GetAccessTokenSilentlyAsync ( app , scopes , userName , msaPt ) ;
8288 }
8389
8490 //
@@ -116,7 +122,7 @@ public async Task<IMicrosoftAuthenticationResult> GetTokenAsync(
116122 // account then the user may become stuck in a loop of authentication failures.
117123 if ( ! hasExistingUser && Context . Settings . UseMsAuthDefaultAccount )
118124 {
119- result = await GetAccessTokenSilentlyAsync ( app , scopes , null ) ;
125+ result = await GetAccessTokenSilentlyAsync ( app , scopes , null , msaPt ) ;
120126
121127 if ( result is null || ! await UseDefaultAccountAsync ( result . Account . Username ) )
122128 {
@@ -281,34 +287,62 @@ internal MicrosoftAuthenticationFlowType GetFlowType()
281287 /// <summary>
282288 /// Obtain an access token without showing UI or prompts.
283289 /// </summary>
284- private async Task < AuthenticationResult > GetAccessTokenSilentlyAsync ( IPublicClientApplication app , string [ ] scopes , string userName )
290+ private async Task < AuthenticationResult > GetAccessTokenSilentlyAsync (
291+ IPublicClientApplication app , string [ ] scopes , string userName , bool msaPt )
285292 {
286293 try
287294 {
288295 if ( userName is null )
289296 {
290- Context . Trace . WriteLine ( "Attempting to acquire token silently for current operating system account..." ) ;
297+ Context . Trace . WriteLine (
298+ "Attempting to acquire token silently for current operating system account..." ) ;
291299
292- return await app . AcquireTokenSilent ( scopes , PublicClientApplication . OperatingSystemAccount ) . ExecuteAsync ( ) ;
300+ return await app . AcquireTokenSilent ( scopes , PublicClientApplication . OperatingSystemAccount )
301+ . ExecuteAsync ( ) ;
293302 }
294303 else
295304 {
296305 Context . Trace . WriteLine ( $ "Attempting to acquire token silently for user '{ userName } '...") ;
297306
298- // We can either call `app.GetAccountsAsync` and filter through the IAccount objects for the instance with the correct user name,
299- // or we can just pass the user name string we have as the `loginHint` and let MSAL do exactly that for us instead!
300- return await app . AcquireTokenSilent ( scopes , loginHint : userName ) . ExecuteAsync ( ) ;
307+ // Enumerate all accounts and find the one matching the user name
308+ IEnumerable < IAccount > accounts = await app . GetAccountsAsync ( ) ;
309+ IAccount account = accounts . FirstOrDefault ( x =>
310+ StringComparer . OrdinalIgnoreCase . Equals ( x . Username , userName ) ) ;
311+ if ( account is null )
312+ {
313+ Context . Trace . WriteLine ( $ "No cached account found for user '{ userName } '...") ;
314+ return null ;
315+ }
316+
317+ var atsBuilder = app . AcquireTokenSilent ( scopes , account ) ;
318+
319+ // Is we are operating with an MSA passthrough app we need to ensure that we target the
320+ // special MSA 'transfer' tenant explicitly. This is a workaround for MSAL issue:
321+ // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/3077
322+ if ( msaPt && Guid . TryParse ( account . HomeAccountId . TenantId , out Guid homeTenantId ) &&
323+ homeTenantId == Constants . MsaHomeTenantId )
324+ {
325+ atsBuilder = atsBuilder . WithTenantId ( Constants . MsaTransferTenantId . ToString ( "D" ) ) ;
326+ }
327+
328+ return await atsBuilder . ExecuteAsync ( ) ;
301329 }
302330 }
303331 catch ( MsalUiRequiredException )
304332 {
305333 Context . Trace . WriteLine ( "Failed to acquire token silently; user interaction is required." ) ;
306334 return null ;
307335 }
336+ catch ( Exception ex )
337+ {
338+ Context . Trace . WriteLine ( "Failed to acquire token silently." ) ;
339+ Context . Trace . WriteException ( ex ) ;
340+ return null ;
341+ }
308342 }
309343
310344 private async Task < IPublicClientApplication > CreatePublicClientApplicationAsync (
311- string authority , string clientId , Uri redirectUri , bool enableBroker )
345+ string authority , string clientId , Uri redirectUri , bool enableBroker , bool msaPt )
312346 {
313347 var httpFactoryAdaptor = new MsalHttpClientFactoryAdaptor ( Context . HttpClientFactory ) ;
314348
@@ -370,7 +404,7 @@ private async Task<IPublicClientApplication> CreatePublicClientApplicationAsync(
370404 new BrokerOptions ( BrokerOptions . OperatingSystems . Windows )
371405 {
372406 Title = "Git Credential Manager" ,
373- MsaPassthrough = true ,
407+ MsaPassthrough = msaPt ,
374408 }
375409 ) ;
376410#endif
0 commit comments