11using System ;
2+ using System . Collections . Generic ;
3+ using System . Linq ;
24using System . Net ;
35using System . Threading ;
46using System . Threading . Tasks ;
57using GitHub . Extensions ;
8+ using GitHub . Logging ;
69using GitHub . Primitives ;
710using Octokit ;
11+ using Serilog ;
812
913namespace GitHub . Api
1014{
@@ -13,10 +17,14 @@ namespace GitHub.Api
1317 /// </summary>
1418 public class LoginManager : ILoginManager
1519 {
20+ const string ScopesHeader = "X-OAuth-Scopes" ;
21+ static readonly ILogger log = LogManager . ForContext < LoginManager > ( ) ;
22+ static readonly Uri UserEndpoint = new Uri ( "user" , UriKind . Relative ) ;
1623 readonly IKeychain keychain ;
1724 readonly ITwoFactorChallengeHandler twoFactorChallengeHandler ;
1825 readonly string clientId ;
1926 readonly string clientSecret ;
27+ readonly IReadOnlyList < string > scopes ;
2028 readonly string authorizationNote ;
2129 readonly string fingerprint ;
2230 IOAuthCallbackListener oauthListener ;
@@ -36,6 +44,7 @@ public LoginManager(
3644 IOAuthCallbackListener oauthListener ,
3745 string clientId ,
3846 string clientSecret ,
47+ IReadOnlyList < string > scopes ,
3948 string authorizationNote = null ,
4049 string fingerprint = null )
4150 {
@@ -49,6 +58,7 @@ public LoginManager(
4958 this . oauthListener = oauthListener ;
5059 this . clientId = clientId ;
5160 this . clientSecret = clientSecret ;
61+ this . scopes = scopes ;
5262 this . authorizationNote = authorizationNote ;
5363 this . fingerprint = fingerprint ;
5464 }
@@ -71,7 +81,7 @@ public async Task<User> Login(
7181
7282 var newAuth = new NewAuthorization
7383 {
74- Scopes = ApiClientConfiguration . Scopes ,
84+ Scopes = scopes ,
7585 Note = authorizationNote ,
7686 Fingerprint = fingerprint ,
7787 } ;
@@ -147,7 +157,7 @@ public Task<User> LoginFromCache(HostAddress hostAddress, IGitHubClient client)
147157 Guard . ArgumentNotNull ( hostAddress , nameof ( hostAddress ) ) ;
148158 Guard . ArgumentNotNull ( client , nameof ( client ) ) ;
149159
150- return client . User . Current ( ) ;
160+ return ReadUserWithRetry ( client ) ;
151161 }
152162
153163 /// <inheritdoc/>
@@ -282,7 +292,7 @@ async Task<User> ReadUserWithRetry(IGitHubClient client)
282292 {
283293 try
284294 {
285- return await client . User . Current ( ) . ConfigureAwait ( false ) ;
295+ return await GetUserAndCheckScopes ( client ) . ConfigureAwait ( false ) ;
286296 }
287297 catch ( AuthorizationException )
288298 {
@@ -295,13 +305,42 @@ async Task<User> ReadUserWithRetry(IGitHubClient client)
295305 }
296306 }
297307
298- static Uri GetLoginUrl ( IOauthClient client , string state )
308+ async Task < User > GetUserAndCheckScopes ( IGitHubClient client )
309+ {
310+ var response = await client . Connection . Get < User > (
311+ UserEndpoint , null , null ) . ConfigureAwait ( false ) ;
312+
313+ if ( response . HttpResponse . Headers . ContainsKey ( ScopesHeader ) )
314+ {
315+ var returnedScopes = response . HttpResponse . Headers [ ScopesHeader ]
316+ . Split ( ',' )
317+ . Select ( x => x . Trim ( ) )
318+ . ToArray ( ) ;
319+
320+ if ( scopes . Except ( returnedScopes ) . Count ( ) == 0 )
321+ {
322+ return response . Body ;
323+ }
324+ else
325+ {
326+ log . Error ( "Incorrect API scopes: require {RequiredScopes} but got {Scopes}" , scopes , returnedScopes ) ;
327+ }
328+ }
329+ else
330+ {
331+ log . Error ( "Error reading scopes: /user succeeded but scopes header was not present" ) ;
332+ }
333+
334+ throw new IncorrectScopesException ( ) ;
335+ }
336+
337+ Uri GetLoginUrl ( IOauthClient client , string state )
299338 {
300339 var request = new OauthLoginRequest ( ApiClientConfiguration . ClientId ) ;
301340
302341 request . State = state ;
303342
304- foreach ( var scope in ApiClientConfiguration . Scopes )
343+ foreach ( var scope in scopes )
305344 {
306345 request . Scopes . Add ( scope ) ;
307346 }
0 commit comments