11using System ;
22using System . Collections . Generic ;
33using System . Linq ;
4+ using System . Net . Http ;
5+ using System . Threading ;
46using System . Threading . Tasks ;
57using GitCredentialManager . Authentication ;
8+ using GitCredentialManager . Authentication . OAuth ;
69
710namespace GitCredentialManager
811{
912 public class GenericHostProvider : HostProvider
1013 {
1114 private readonly IBasicAuthentication _basicAuth ;
1215 private readonly IWindowsIntegratedAuthentication _winAuth ;
16+ private readonly IOAuthAuthentication _oauth ;
1317
1418 public GenericHostProvider ( ICommandContext context )
15- : this ( context , new BasicAuthentication ( context ) , new WindowsIntegratedAuthentication ( context ) ) { }
19+ : this ( context , new BasicAuthentication ( context ) , new WindowsIntegratedAuthentication ( context ) ,
20+ new OAuthAuthentication ( context ) ) { }
1621
1722 public GenericHostProvider ( ICommandContext context ,
1823 IBasicAuthentication basicAuth ,
19- IWindowsIntegratedAuthentication winAuth )
24+ IWindowsIntegratedAuthentication winAuth ,
25+ IOAuthAuthentication oauth )
2026 : base ( context )
2127 {
2228 EnsureArgument . NotNull ( basicAuth , nameof ( basicAuth ) ) ;
2329 EnsureArgument . NotNull ( winAuth , nameof ( winAuth ) ) ;
30+ EnsureArgument . NotNull ( oauth , nameof ( oauth ) ) ;
2431
2532 _basicAuth = basicAuth ;
2633 _winAuth = winAuth ;
34+ _oauth = oauth ;
2735 }
2836
2937 public override string Id => "generic" ;
@@ -68,7 +76,7 @@ public override async Task<ICredential> GenerateCredentialAsync(InputArguments i
6876 Context . Trace . WriteLine ( $ "\t UseAuthHeader = { oauthConfig . UseAuthHeader } ") ;
6977 Context . Trace . WriteLine ( $ "\t DefaultUserName = { oauthConfig . DefaultUserName } ") ;
7078
71- throw new NotImplementedException ( ) ;
79+ return await GetOAuthAccessToken ( uri , input . UserName , oauthConfig ) ;
7280 }
7381 // Try detecting WIA for this remote, if permitted
7482 else if ( IsWindowsAuthAllowed )
@@ -106,6 +114,65 @@ public override async Task<ICredential> GenerateCredentialAsync(InputArguments i
106114 return await _basicAuth . GetCredentialsAsync ( uri . AbsoluteUri , input . UserName ) ;
107115 }
108116
117+ private async Task < ICredential > GetOAuthAccessToken ( Uri remoteUri , string userName , GenericOAuthConfig config )
118+ {
119+ // TODO: Determined user info from a webcall? ID token? Need OIDC support
120+ string oauthUser = userName ?? config . DefaultUserName ;
121+
122+ var client = new OAuth2Client (
123+ HttpClient ,
124+ config . Endpoints ,
125+ config . ClientId ,
126+ config . RedirectUri ,
127+ config . ClientSecret ,
128+ Context . Trace ,
129+ config . UseAuthHeader ) ;
130+
131+ // Determine which interactive OAuth mode to use. Start by checking for mode preference in config
132+ var supportedModes = OAuthAuthenticationModes . All ;
133+ if ( Context . Settings . TryGetSetting (
134+ Constants . EnvironmentVariables . OAuthAuthenticationModes ,
135+ Constants . GitConfiguration . Credential . SectionName ,
136+ Constants . GitConfiguration . Credential . OAuthAuthenticationModes ,
137+ out string authModesStr ) )
138+ {
139+ if ( Enum . TryParse ( authModesStr , true , out supportedModes ) && supportedModes != OAuthAuthenticationModes . None )
140+ {
141+ Context . Trace . WriteLine ( $ "Supported authentication modes override present: { supportedModes } ") ;
142+ }
143+ else
144+ {
145+ Context . Trace . WriteLine ( $ "Invalid value for supported authentication modes override setting: '{ authModesStr } '") ;
146+ }
147+ }
148+
149+ // If the server doesn't support device code we need to remove it as an option here
150+ if ( ! config . SupportsDeviceCode )
151+ {
152+ supportedModes &= ~ OAuthAuthenticationModes . DeviceCode ;
153+ }
154+
155+ // Prompt the user to select a mode
156+ OAuthAuthenticationModes mode = await _oauth . GetAuthenticationModeAsync ( remoteUri . ToString ( ) , supportedModes ) ;
157+
158+ OAuth2TokenResult tokenResult ;
159+ switch ( mode )
160+ {
161+ case OAuthAuthenticationModes . Browser :
162+ tokenResult = await _oauth . GetTokenByBrowserAsync ( client , config . Scopes ) ;
163+ break ;
164+
165+ case OAuthAuthenticationModes . DeviceCode :
166+ tokenResult = await _oauth . GetTokenByDeviceCodeAsync ( client , config . Scopes ) ;
167+ break ;
168+
169+ default :
170+ throw new Exception ( "No authentication mode selected!" ) ;
171+ }
172+
173+ return new GitCredential ( oauthUser , tokenResult . AccessToken ) ;
174+ }
175+
109176 /// <summary>
110177 /// Check if the user permits checking for Windows Integrated Authentication.
111178 /// </summary>
@@ -131,9 +198,13 @@ private bool IsWindowsAuthAllowed
131198 }
132199 }
133200
201+ private HttpClient _httpClient ;
202+ private HttpClient HttpClient => _httpClient ?? ( _httpClient = Context . HttpClientFactory . CreateClient ( ) ) ;
203+
134204 protected override void ReleaseManagedResources ( )
135205 {
136206 _winAuth . Dispose ( ) ;
207+ _httpClient ? . Dispose ( ) ;
137208 base . ReleaseManagedResources ( ) ;
138209 }
139210 }
0 commit comments