1
1
using System ;
2
2
using System . Collections . Generic ;
3
3
using System . Linq ;
4
+ using System . Net . Http ;
5
+ using System . Threading ;
4
6
using System . Threading . Tasks ;
5
7
using GitCredentialManager . Authentication ;
8
+ using GitCredentialManager . Authentication . OAuth ;
6
9
7
10
namespace GitCredentialManager
8
11
{
9
12
public class GenericHostProvider : HostProvider
10
13
{
11
14
private readonly IBasicAuthentication _basicAuth ;
12
15
private readonly IWindowsIntegratedAuthentication _winAuth ;
16
+ private readonly IOAuthAuthentication _oauth ;
13
17
14
18
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 ) ) { }
16
21
17
22
public GenericHostProvider ( ICommandContext context ,
18
23
IBasicAuthentication basicAuth ,
19
- IWindowsIntegratedAuthentication winAuth )
24
+ IWindowsIntegratedAuthentication winAuth ,
25
+ IOAuthAuthentication oauth )
20
26
: base ( context )
21
27
{
22
28
EnsureArgument . NotNull ( basicAuth , nameof ( basicAuth ) ) ;
23
29
EnsureArgument . NotNull ( winAuth , nameof ( winAuth ) ) ;
30
+ EnsureArgument . NotNull ( oauth , nameof ( oauth ) ) ;
24
31
25
32
_basicAuth = basicAuth ;
26
33
_winAuth = winAuth ;
34
+ _oauth = oauth ;
27
35
}
28
36
29
37
public override string Id => "generic" ;
@@ -68,7 +76,7 @@ public override async Task<ICredential> GenerateCredentialAsync(InputArguments i
68
76
Context . Trace . WriteLine ( $ "\t UseAuthHeader = { oauthConfig . UseAuthHeader } ") ;
69
77
Context . Trace . WriteLine ( $ "\t DefaultUserName = { oauthConfig . DefaultUserName } ") ;
70
78
71
- throw new NotImplementedException ( ) ;
79
+ return await GetOAuthAccessToken ( uri , input . UserName , oauthConfig ) ;
72
80
}
73
81
// Try detecting WIA for this remote, if permitted
74
82
else if ( IsWindowsAuthAllowed )
@@ -106,6 +114,65 @@ public override async Task<ICredential> GenerateCredentialAsync(InputArguments i
106
114
return await _basicAuth . GetCredentialsAsync ( uri . AbsoluteUri , input . UserName ) ;
107
115
}
108
116
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
+
109
176
/// <summary>
110
177
/// Check if the user permits checking for Windows Integrated Authentication.
111
178
/// </summary>
@@ -131,9 +198,13 @@ private bool IsWindowsAuthAllowed
131
198
}
132
199
}
133
200
201
+ private HttpClient _httpClient ;
202
+ private HttpClient HttpClient => _httpClient ?? ( _httpClient = Context . HttpClientFactory . CreateClient ( ) ) ;
203
+
134
204
protected override void ReleaseManagedResources ( )
135
205
{
136
206
_winAuth . Dispose ( ) ;
207
+ _httpClient ? . Dispose ( ) ;
137
208
base . ReleaseManagedResources ( ) ;
138
209
}
139
210
}
0 commit comments