1
1
using System ;
2
2
using System . Collections . Generic ;
3
3
using System . IO ;
4
+ using System . Linq ;
4
5
using System . Net . Http ;
5
6
using System . Threading . Tasks ;
6
7
using GitCredentialManager . Interop . Windows . Native ;
@@ -23,7 +24,7 @@ namespace GitCredentialManager.Authentication
23
24
public interface IMicrosoftAuthentication
24
25
{
25
26
Task < IMicrosoftAuthenticationResult > GetTokenAsync ( string authority , string clientId , Uri redirectUri ,
26
- string [ ] scopes , string userName ) ;
27
+ string [ ] scopes , string userName , bool msaPt = false ) ;
27
28
}
28
29
29
30
public interface IMicrosoftAuthenticationResult
@@ -59,26 +60,31 @@ public MicrosoftAuthentication(ICommandContext context)
59
60
#region IMicrosoftAuthentication
60
61
61
62
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 )
63
64
{
64
65
// Check if we can and should use OS broker authentication
65
66
bool useBroker = CanUseBroker ( ) ;
66
67
Context . Trace . WriteLine ( useBroker
67
68
? "OS broker is available and enabled."
68
69
: "OS broker is not available or enabled." ) ;
69
70
71
+ if ( msaPt )
72
+ {
73
+ Context . Trace . WriteLine ( "MSA passthrough is enabled." ) ;
74
+ }
75
+
70
76
try
71
77
{
72
78
// 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 ) ;
74
80
75
81
AuthenticationResult result = null ;
76
82
77
83
// Try silent authentication first if we know about an existing user
78
84
bool hasExistingUser = ! string . IsNullOrWhiteSpace ( userName ) ;
79
85
if ( hasExistingUser )
80
86
{
81
- result = await GetAccessTokenSilentlyAsync ( app , scopes , userName ) ;
87
+ result = await GetAccessTokenSilentlyAsync ( app , scopes , userName , msaPt ) ;
82
88
}
83
89
84
90
//
@@ -116,7 +122,7 @@ public async Task<IMicrosoftAuthenticationResult> GetTokenAsync(
116
122
// account then the user may become stuck in a loop of authentication failures.
117
123
if ( ! hasExistingUser && Context . Settings . UseMsAuthDefaultAccount )
118
124
{
119
- result = await GetAccessTokenSilentlyAsync ( app , scopes , null ) ;
125
+ result = await GetAccessTokenSilentlyAsync ( app , scopes , null , msaPt ) ;
120
126
121
127
if ( result is null || ! await UseDefaultAccountAsync ( result . Account . Username ) )
122
128
{
@@ -281,34 +287,62 @@ internal MicrosoftAuthenticationFlowType GetFlowType()
281
287
/// <summary>
282
288
/// Obtain an access token without showing UI or prompts.
283
289
/// </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 )
285
292
{
286
293
try
287
294
{
288
295
if ( userName is null )
289
296
{
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..." ) ;
291
299
292
- return await app . AcquireTokenSilent ( scopes , PublicClientApplication . OperatingSystemAccount ) . ExecuteAsync ( ) ;
300
+ return await app . AcquireTokenSilent ( scopes , PublicClientApplication . OperatingSystemAccount )
301
+ . ExecuteAsync ( ) ;
293
302
}
294
303
else
295
304
{
296
305
Context . Trace . WriteLine ( $ "Attempting to acquire token silently for user '{ userName } '...") ;
297
306
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 ( ) ;
301
329
}
302
330
}
303
331
catch ( MsalUiRequiredException )
304
332
{
305
333
Context . Trace . WriteLine ( "Failed to acquire token silently; user interaction is required." ) ;
306
334
return null ;
307
335
}
336
+ catch ( Exception ex )
337
+ {
338
+ Context . Trace . WriteLine ( "Failed to acquire token silently." ) ;
339
+ Context . Trace . WriteException ( ex ) ;
340
+ return null ;
341
+ }
308
342
}
309
343
310
344
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 )
312
346
{
313
347
var httpFactoryAdaptor = new MsalHttpClientFactoryAdaptor ( Context . HttpClientFactory ) ;
314
348
@@ -370,7 +404,7 @@ private async Task<IPublicClientApplication> CreatePublicClientApplicationAsync(
370
404
new BrokerOptions ( BrokerOptions . OperatingSystems . Windows )
371
405
{
372
406
Title = "Git Credential Manager" ,
373
- MsaPassthrough = true ,
407
+ MsaPassthrough = msaPt ,
374
408
}
375
409
) ;
376
410
#endif
0 commit comments