2
2
// Licensed under the MIT license.
3
3
using System ;
4
4
using System . Collections . Generic ;
5
- using System . Net ;
6
5
using System . Net . Http ;
7
6
using System . Net . Http . Headers ;
8
- using System . Text ;
9
7
using System . Threading ;
10
8
using System . Threading . Tasks ;
11
9
using Microsoft . Git . CredentialManager . Authentication . OAuth . Json ;
@@ -26,7 +24,7 @@ public interface IOAuth2Client
26
24
/// <param name="browser">User agent to use to start the authorization code grant flow.</param>
27
25
/// <param name="ct">Token to cancel the operation.</param>
28
26
/// <returns>Authorization code.</returns>
29
- Task < string > GetAuthorizationCodeAsync ( IEnumerable < string > scopes , IOAuth2WebBrowser browser , CancellationToken ct ) ;
27
+ Task < OAuth2AuthorizationCodeResult > GetAuthorizationCodeAsync ( IEnumerable < string > scopes , IOAuth2WebBrowser browser , CancellationToken ct ) ;
30
28
31
29
/// <summary>
32
30
/// Retrieve a device code grant.
@@ -40,10 +38,10 @@ public interface IOAuth2Client
40
38
/// <summary>
41
39
/// Exchange an authorization code acquired from <see cref="GetAuthorizationCodeAsync"/> for an access token.
42
40
/// </summary>
43
- /// <param name="authorizationCode ">Authorization code.</param>
41
+ /// <param name="authorizationCodeResult ">Authorization code grant result .</param>
44
42
/// <param name="ct">Token to cancel the operation.</param>
45
43
/// <returns>Token result.</returns>
46
- Task < OAuth2TokenResult > GetTokenByAuthorizationCodeAsync ( string authorizationCode , CancellationToken ct ) ;
44
+ Task < OAuth2TokenResult > GetTokenByAuthorizationCodeAsync ( OAuth2AuthorizationCodeResult authorizationCodeResult , CancellationToken ct ) ;
47
45
48
46
/// <summary>
49
47
/// Use a refresh token to get a new access token.
@@ -70,7 +68,7 @@ public class OAuth2Client : IOAuth2Client
70
68
private readonly string _clientId ;
71
69
private readonly string _clientSecret ;
72
70
73
- private IOAuth2NonceGenerator _nonceGenerator ;
71
+ private IOAuth2CodeGenerator _codeGenerator ;
74
72
75
73
public OAuth2Client ( HttpClient httpClient , OAuth2ServerEndpoints endpoints , string clientId , Uri redirectUri = null , string clientSecret = null )
76
74
{
@@ -81,31 +79,39 @@ public OAuth2Client(HttpClient httpClient, OAuth2ServerEndpoints endpoints, stri
81
79
_clientSecret = clientSecret ;
82
80
}
83
81
84
- public IOAuth2NonceGenerator NonceGenerator
82
+ public IOAuth2CodeGenerator CodeGenerator
85
83
{
86
- get => _nonceGenerator ?? ( _nonceGenerator = new OAuth2NonceGenerator ( ) ) ;
87
- set => _nonceGenerator = value ;
84
+ get => _codeGenerator ?? ( _codeGenerator = new OAuth2CryptographicCodeGenerator ( ) ) ;
85
+ set => _codeGenerator = value ;
88
86
}
89
87
90
88
#region IOAuth2Client
91
89
92
- public async Task < string > GetAuthorizationCodeAsync ( IEnumerable < string > scopes , IOAuth2WebBrowser browser , CancellationToken ct )
90
+ public async Task < OAuth2AuthorizationCodeResult > GetAuthorizationCodeAsync ( IEnumerable < string > scopes , IOAuth2WebBrowser browser , CancellationToken ct )
93
91
{
94
- string state = NonceGenerator . CreateNonce ( ) ;
95
- string scopesStr = string . Join ( " " , scopes ) ;
92
+ string state = CodeGenerator . CreateNonce ( ) ;
93
+ string codeVerifier = CodeGenerator . CreatePkceCodeVerifier ( ) ;
94
+ string codeChallenge = CodeGenerator . CreatePkceCodeChallenge ( OAuth2PkceChallengeMethod . Sha256 , codeVerifier ) ;
96
95
97
96
var queryParams = new Dictionary < string , string >
98
97
{
99
- [ OAuth2Constants . AuthorizationEndpoint . ResponseTypeParameter ] = OAuth2Constants . AuthorizationEndpoint . AuthorizationCodeResponseType ,
98
+ [ OAuth2Constants . AuthorizationEndpoint . ResponseTypeParameter ] =
99
+ OAuth2Constants . AuthorizationEndpoint . AuthorizationCodeResponseType ,
100
100
[ OAuth2Constants . ClientIdParameter ] = _clientId ,
101
101
[ OAuth2Constants . AuthorizationEndpoint . StateParameter ] = state ,
102
+ [ OAuth2Constants . AuthorizationEndpoint . PkceChallengeMethodParameter ] =
103
+ OAuth2Constants . AuthorizationEndpoint . PkceChallengeMethodS256 ,
104
+ [ OAuth2Constants . AuthorizationEndpoint . PkceChallengeParameter ] = codeChallenge
102
105
} ;
103
106
107
+ Uri redirectUri = null ;
104
108
if ( _redirectUri != null )
105
109
{
106
- queryParams [ OAuth2Constants . RedirectUriParameter ] = _redirectUri . ToString ( ) ;
110
+ redirectUri = browser . UpdateRedirectUri ( _redirectUri ) ;
111
+ queryParams [ OAuth2Constants . RedirectUriParameter ] = redirectUri . ToString ( ) ;
107
112
}
108
113
114
+ string scopesStr = string . Join ( " " , scopes ) ;
109
115
if ( ! string . IsNullOrWhiteSpace ( scopesStr ) )
110
116
{
111
117
queryParams [ OAuth2Constants . ScopeParameter ] = scopesStr ;
@@ -119,12 +125,12 @@ public async Task<string> GetAuthorizationCodeAsync(IEnumerable<string> scopes,
119
125
Uri authorizationUri = authorizationUriBuilder . Uri ;
120
126
121
127
// Open the browser at the request URI to start the authorization code grant flow.
122
- Uri redirectUri = await browser . GetAuthenticationCodeAsync ( authorizationUri , _redirectUri , ct ) ;
128
+ Uri finalUri = await browser . GetAuthenticationCodeAsync ( authorizationUri , redirectUri , ct ) ;
123
129
124
130
// Check for errors serious enough we should terminate the flow, such as if the state value returned does
125
131
// not match the one we passed. This indicates a badly implemented Authorization Server, or worse, some
126
132
// form of failed MITM or replay attack.
127
- IDictionary < string , string > redirectQueryParams = redirectUri . GetQueryParameters ( ) ;
133
+ IDictionary < string , string > redirectQueryParams = finalUri . GetQueryParameters ( ) ;
128
134
if ( ! redirectQueryParams . TryGetValue ( OAuth2Constants . AuthorizationGrantResponse . StateParameter , out string replyState ) )
129
135
{
130
136
throw new OAuth2Exception ( $ "Missing '{ OAuth2Constants . AuthorizationGrantResponse . StateParameter } ' in response.") ;
@@ -140,7 +146,7 @@ public async Task<string> GetAuthorizationCodeAsync(IEnumerable<string> scopes,
140
146
throw new OAuth2Exception ( $ "Missing '{ OAuth2Constants . AuthorizationGrantResponse . AuthorizationCodeParameter } ' in response.") ;
141
147
}
142
148
143
- return authCode ;
149
+ return new OAuth2AuthorizationCodeResult ( authCode , redirectUri , codeVerifier ) ;
144
150
}
145
151
146
152
public async Task < OAuth2DeviceCodeResult > GetDeviceCodeAsync ( IEnumerable < string > scopes , CancellationToken ct )
@@ -177,18 +183,24 @@ public async Task<OAuth2DeviceCodeResult> GetDeviceCodeAsync(IEnumerable<string>
177
183
}
178
184
}
179
185
180
- public async Task < OAuth2TokenResult > GetTokenByAuthorizationCodeAsync ( string authorizationCode , CancellationToken ct )
186
+ public async Task < OAuth2TokenResult > GetTokenByAuthorizationCodeAsync ( OAuth2AuthorizationCodeResult authorizationCodeResult , CancellationToken ct )
181
187
{
182
188
var formData = new Dictionary < string , string >
183
189
{
184
190
[ OAuth2Constants . TokenEndpoint . GrantTypeParameter ] = OAuth2Constants . TokenEndpoint . AuthorizationCodeGrantType ,
185
- [ OAuth2Constants . TokenEndpoint . AuthorizationCodeParameter ] = authorizationCode ,
186
- [ OAuth2Constants . ClientIdParameter ] = _clientId ,
191
+ [ OAuth2Constants . TokenEndpoint . AuthorizationCodeParameter ] = authorizationCodeResult . Code ,
192
+ [ OAuth2Constants . TokenEndpoint . PkceVerifierParameter ] = authorizationCodeResult . CodeVerifier ,
193
+ [ OAuth2Constants . ClientIdParameter ] = _clientId
187
194
} ;
188
195
189
- if ( _redirectUri != null )
196
+ if ( authorizationCodeResult . RedirectUri != null )
190
197
{
191
- formData [ OAuth2Constants . RedirectUriParameter ] = _redirectUri . ToString ( ) ;
198
+ formData [ OAuth2Constants . RedirectUriParameter ] = authorizationCodeResult . RedirectUri . ToString ( ) ;
199
+ }
200
+
201
+ if ( authorizationCodeResult . CodeVerifier != null )
202
+ {
203
+ formData [ OAuth2Constants . TokenEndpoint . PkceVerifierParameter ] = authorizationCodeResult . CodeVerifier ;
192
204
}
193
205
194
206
using ( HttpContent requestContent = new FormUrlEncodedContent ( formData ) )
0 commit comments