Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit a9f01eb

Browse files
committed
Store scopes in the Connection.
1 parent a8a1bb5 commit a9f01eb

File tree

10 files changed

+158
-93
lines changed

10 files changed

+158
-93
lines changed

src/GitHub.Api/LoginManager.cs

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading.Tasks;
77
using GitHub.Extensions;
88
using GitHub.Logging;
9+
using GitHub.Models;
910
using GitHub.Primitives;
1011
using Octokit;
1112
using Serilog;
@@ -200,40 +201,6 @@ public async Task Logout(HostAddress hostAddress, IGitHubClient client)
200201
await keychain.Delete(hostAddress).ConfigureAwait(false);
201202
}
202203

203-
/// <summary>
204-
/// Tests if received API scopes match the required API scopes.
205-
/// </summary>
206-
/// <param name="required">The required API scopes.</param>
207-
/// <param name="received">The received API scopes.</param>
208-
/// <returns>True if all required scopes are present, otherwise false.</returns>
209-
public static bool ScopesMatch(IReadOnlyList<string> required, IReadOnlyList<string> received)
210-
{
211-
foreach (var scope in required)
212-
{
213-
var found = received.Contains(scope);
214-
215-
if (!found &&
216-
(scope.StartsWith("read:", StringComparison.Ordinal) ||
217-
scope.StartsWith("write:", StringComparison.Ordinal)))
218-
{
219-
// NOTE: Scopes are actually more complex than this, for example
220-
// `user` encompasses `read:user` and `user:email` but just use
221-
// this simple rule for now as it works for the scopes we require.
222-
var adminScope = scope
223-
.Replace("read:", "admin:")
224-
.Replace("write:", "admin:");
225-
found = received.Contains(adminScope);
226-
}
227-
228-
if (!found)
229-
{
230-
return false;
231-
}
232-
}
233-
234-
return true;
235-
}
236-
237204
async Task<ApplicationAuthorization> CreateAndDeleteExistingApplicationAuthorization(
238205
IGitHubClient client,
239206
NewAuthorization newAuth,
@@ -377,12 +344,12 @@ async Task<LoginResult> GetUserAndCheckScopes(IGitHubClient client)
377344

378345
if (response.HttpResponse.Headers.ContainsKey(ScopesHeader))
379346
{
380-
var returnedScopes = response.HttpResponse.Headers[ScopesHeader]
347+
var returnedScopes = new ScopesCollection(response.HttpResponse.Headers[ScopesHeader]
381348
.Split(',')
382349
.Select(x => x.Trim())
383-
.ToArray();
350+
.ToArray());
384351

385-
if (ScopesMatch(minimumScopes, returnedScopes))
352+
if (returnedScopes.Matches(minimumScopes))
386353
{
387354
return new LoginResult(response.Body, returnedScopes);
388355
}

src/GitHub.Api/LoginResult.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using GitHub.Models;
34
using Octokit;
45

56
namespace GitHub.Api
@@ -14,7 +15,7 @@ public class LoginResult
1415
/// </summary>
1516
/// <param name="user">The logged-in user.</param>
1617
/// <param name="scopes">The login scopes.</param>
17-
public LoginResult(User user, IReadOnlyList<string> scopes)
18+
public LoginResult(User user, ScopesCollection scopes)
1819
{
1920
User = user;
2021
Scopes = scopes;
@@ -23,7 +24,7 @@ public LoginResult(User user, IReadOnlyList<string> scopes)
2324
/// <summary>
2425
/// Gets the login scopes.
2526
/// </summary>
26-
public IReadOnlyList<string> Scopes { get; }
27+
public ScopesCollection Scopes { get; }
2728

2829
/// <summary>
2930
/// Gets the logged-in user.

src/GitHub.App/SampleData/SampleViewModels.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,8 @@ class Conn : IConnection
199199
public HostAddress HostAddress { get; set; }
200200

201201
public string Username { get; set; }
202-
public ObservableCollection<ILocalRepositoryModel> Repositories { get; set; }
203-
204202
public Octokit.User User => null;
203+
public ScopesCollection Scopes => null;
205204
public bool IsLoggedIn => true;
206205
public bool IsLoggingIn => false;
207206

src/GitHub.Exports/Models/IConnection.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.ComponentModel;
3-
using System.Threading.Tasks;
43
using GitHub.Primitives;
54
using Octokit;
65

@@ -29,6 +28,11 @@ public interface IConnection : INotifyPropertyChanged
2928
/// </remarks>
3029
User User { get; }
3130

31+
/// <summary>
32+
/// Gets the login scopes.
33+
/// </summary>
34+
ScopesCollection Scopes { get; }
35+
3236
/// <summary>
3337
/// Gets a value indicating whether the login of the account succeeded.
3438
/// </summary>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
6+
namespace GitHub.Models
7+
{
8+
/// <summary>
9+
/// A collection of OAuth scopes.
10+
/// </summary>
11+
public class ScopesCollection : IReadOnlyList<string>
12+
{
13+
readonly IReadOnlyList<string> inner;
14+
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="Scopes"/> class.
17+
/// </summary>
18+
/// <param name="scopes">The scopes.</param>
19+
public ScopesCollection(IReadOnlyList<string> scopes)
20+
{
21+
inner = scopes;
22+
}
23+
24+
/// <inheritdoc/>
25+
public string this[int index] => inner[index];
26+
27+
/// <inheritdoc/>
28+
public int Count => inner.Count;
29+
30+
/// <inheritdoc/>
31+
public IEnumerator<string> GetEnumerator() => inner.GetEnumerator();
32+
33+
/// <inheritdoc/>
34+
IEnumerator IEnumerable.GetEnumerator() => inner.GetEnumerator();
35+
36+
/// <summary>
37+
/// Tests if received API scopes match the required API scopes.
38+
/// </summary>
39+
/// <param name="required">The required API scopes.</param>
40+
/// <returns>True if all required scopes are present, otherwise false.</returns>
41+
public bool Matches(IReadOnlyList<string> required)
42+
{
43+
foreach (var scope in required)
44+
{
45+
var found = inner.Contains(scope);
46+
47+
if (!found &&
48+
(scope.StartsWith("read:", StringComparison.Ordinal) ||
49+
scope.StartsWith("write:", StringComparison.Ordinal)))
50+
{
51+
// NOTE: Scopes are actually more complex than this, for example
52+
// `user` encompasses `read:user` and `user:email` but just use
53+
// this simple rule for now as it works for the scopes we require.
54+
var adminScope = scope
55+
.Replace("read:", "admin:")
56+
.Replace("write:", "admin:");
57+
found = inner.Contains(adminScope);
58+
}
59+
60+
if (!found)
61+
{
62+
return false;
63+
}
64+
}
65+
66+
return true;
67+
}
68+
69+
/// <inheritdoc/>
70+
public override string ToString() => string.Join(",", inner);
71+
}
72+
}

src/GitHub.Exports/Services/Connection.cs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.ComponentModel;
34
using System.Runtime.CompilerServices;
45
using GitHub.Models;
@@ -13,25 +14,29 @@ public class Connection : IConnection
1314
{
1415
string username;
1516
Octokit.User user;
17+
ScopesCollection scopes;
1618
bool isLoggedIn;
1719
bool isLoggingIn;
1820
Exception connectionError;
1921

20-
public Connection(HostAddress hostAddress)
22+
public Connection(
23+
HostAddress hostAddress,
24+
string username)
2125
{
26+
this.username = username;
2227
HostAddress = hostAddress;
2328
isLoggedIn = false;
2429
isLoggingIn = true;
2530
}
2631

2732
public Connection(
2833
HostAddress hostAddress,
29-
string username,
30-
Octokit.User user)
34+
Octokit.User user,
35+
ScopesCollection scopes)
3136
{
3237
HostAddress = hostAddress;
33-
this.username = username;
3438
this.user = user;
39+
this.scopes = scopes;
3540
isLoggedIn = true;
3641
}
3742

@@ -44,14 +49,20 @@ public string Username
4449
get => username;
4550
private set => RaiseAndSetIfChanged(ref username, value);
4651
}
47-
4852
/// <inheritdoc/>
4953
public Octokit.User User
5054
{
5155
get => user;
5256
private set => RaiseAndSetIfChanged(ref user, value);
5357
}
5458

59+
/// <inheritdoc/>
60+
public ScopesCollection Scopes
61+
{
62+
get => scopes;
63+
private set => RaiseAndSetIfChanged(ref scopes, value);
64+
}
65+
5566
/// <inheritdoc/>
5667
public bool IsLoggedIn
5768
{
@@ -82,20 +93,21 @@ public void SetLoggingIn()
8293
IsLoggedIn = false;
8394
IsLoggingIn = true;
8495
User = null;
85-
Username = null;
96+
Scopes = null;
8697
}
8798

8899
public void SetError(Exception e)
89100
{
90101
ConnectionError = e;
91102
IsLoggingIn = false;
92103
IsLoggedIn = false;
104+
Scopes = null;
93105
}
94106

95-
public void SetSuccess(Octokit.User user)
107+
public void SetSuccess(Octokit.User user, ScopesCollection scopes)
96108
{
97109
User = user;
98-
Username = user.Login;
110+
Scopes = scopes;
99111
IsLoggingIn = false;
100112
IsLoggedIn = true;
101113
}

src/GitHub.VisualStudio/Services/ConnectionManager.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public async Task<IConnection> LogIn(HostAddress address, string userName, strin
8080

8181
var client = CreateClient(address);
8282
var login = await loginManager.Login(address, client, userName, password);
83-
var connection = new Connection(address, userName, login.User);
83+
var connection = new Connection(address, login.User, login.Scopes);
8484

8585
conns.Add(connection);
8686
await SaveConnections();
@@ -101,7 +101,7 @@ public async Task<IConnection> LogInViaOAuth(HostAddress address, CancellationTo
101101
var client = CreateClient(address);
102102
var oauthClient = new OauthClient(client.Connection);
103103
var login = await loginManager.LoginViaOAuth(address, client, oauthClient, OpenBrowser, cancel);
104-
var connection = new Connection(address, login.User.Login, login.User);
104+
var connection = new Connection(address, login.User, login.Scopes);
105105

106106
conns.Add(connection);
107107
await SaveConnections();
@@ -122,7 +122,7 @@ public async Task<IConnection> LogInWithToken(HostAddress address, string token)
122122

123123
var client = CreateClient(address);
124124
var login = await loginManager.LoginWithToken(address, client, token);
125-
var connection = new Connection(address, login.User.Login, login.User);
125+
var connection = new Connection(address, login.User, login.Scopes);
126126

127127
conns.Add(connection);
128128
await SaveConnections();
@@ -157,7 +157,7 @@ public async Task Retry(IConnection connection)
157157
{
158158
var client = CreateClient(c.HostAddress);
159159
var login = await loginManager.LoginFromCache(connection.HostAddress, client);
160-
c.SetSuccess(login.User);
160+
c.SetSuccess(login.User, login.Scopes);
161161
await usageTracker.IncrementCounter(x => x.NumberOfLogins);
162162
}
163163
catch (Exception e)
@@ -194,7 +194,7 @@ async Task LoadConnections(ObservableCollection<IConnection> result)
194194
{
195195
foreach (var c in await cache.Load())
196196
{
197-
var connection = new Connection(c.HostAddress);
197+
var connection = new Connection(c.HostAddress, c.UserName);
198198
result.Add(connection);
199199
}
200200

@@ -206,7 +206,7 @@ async Task LoadConnections(ObservableCollection<IConnection> result)
206206
try
207207
{
208208
login = await loginManager.LoginFromCache(connection.HostAddress, client);
209-
connection.SetSuccess(login.User);
209+
connection.SetSuccess(login.User, login.Scopes);
210210
await usageTracker.IncrementCounter(x => x.NumberOfLogins);
211211
}
212212
catch (Exception e)

test/GitHub.Api.UnitTests/LoginManagerTests.cs

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -311,39 +311,4 @@ IGitHubClient CreateClient(User user = null, string[] responseScopes = null)
311311
return result;
312312
}
313313
}
314-
315-
public class TheScopesMatchMethod
316-
{
317-
[Test]
318-
public void ReturnsFalseWhenMissingScopes()
319-
{
320-
var received = new[] { "user", "repo", "write:public_key" };
321-
322-
Assert.False(LoginManager.ScopesMatch(scopes, received));
323-
}
324-
325-
[Test]
326-
public void ReturnsTrueWhenScopesEqual()
327-
{
328-
var received = new[] { "user", "repo", "gist", "write:public_key" };
329-
330-
Assert.True(LoginManager.ScopesMatch(scopes, received));
331-
}
332-
333-
[Test]
334-
public void ReturnsTrueWhenExtraScopesReturned()
335-
{
336-
var received = new[] { "user", "repo", "gist", "foo", "write:public_key" };
337-
338-
Assert.True(LoginManager.ScopesMatch(scopes, received));
339-
}
340-
341-
[Test]
342-
public void ReturnsTrueWhenAdminScopeReturnedInsteadOfWrite()
343-
{
344-
var received = new[] { "user", "repo", "gist", "foo", "admin:public_key" };
345-
346-
Assert.True(LoginManager.ScopesMatch(scopes, received));
347-
}
348-
}
349314
}

0 commit comments

Comments
 (0)