Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.

Commit bae9cfd

Browse files
committed
External tool for logging in
1 parent 634ae54 commit bae9cfd

35 files changed

+3239
-26
lines changed

GitHub.Unity.sln

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TaskSystem", "src\tests\Tas
2727
EndProject
2828
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "src\tests\TestApp\TestApp.csproj", "{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}"
2929
EndProject
30+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OctoRun", "src\OctoRun\OctoRun.csproj", "{127F84FE-DB89-4543-9A83-74DB4E751061}"
31+
EndProject
3032
Global
3133
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3234
Debug|Any CPU = Debug|Any CPU
@@ -94,6 +96,12 @@ Global
9496
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.dev|Any CPU.Build.0 = Debug|Any CPU
9597
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
9698
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.Release|Any CPU.Build.0 = Release|Any CPU
99+
{127F84FE-DB89-4543-9A83-74DB4E751061}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
100+
{127F84FE-DB89-4543-9A83-74DB4E751061}.Debug|Any CPU.Build.0 = Debug|Any CPU
101+
{127F84FE-DB89-4543-9A83-74DB4E751061}.dev|Any CPU.ActiveCfg = Debug|Any CPU
102+
{127F84FE-DB89-4543-9A83-74DB4E751061}.dev|Any CPU.Build.0 = Debug|Any CPU
103+
{127F84FE-DB89-4543-9A83-74DB4E751061}.Release|Any CPU.ActiveCfg = Release|Any CPU
104+
{127F84FE-DB89-4543-9A83-74DB4E751061}.Release|Any CPU.Build.0 = Release|Any CPU
97105
EndGlobalSection
98106
GlobalSection(SolutionProperties) = preSolution
99107
HideSolutionNode = FALSE

src/GitHub.Api/Application/ApiClient.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ namespace GitHub.Unity
88
{
99
class ApiClient : IApiClient
1010
{
11-
public static IApiClient Create(UriString repositoryUrl, IKeychain keychain)
11+
public static IApiClient Create(UriString repositoryUrl, IKeychain keychain, IProcessManager processManager, ITaskManager taskManager, NPath loginTool)
1212
{
1313
logger.Trace("Creating ApiClient: {0}", repositoryUrl);
1414

1515
var credentialStore = keychain.Connect(repositoryUrl);
1616
var hostAddress = HostAddress.Create(repositoryUrl);
1717

1818
return new ApiClient(repositoryUrl, keychain,
19-
new GitHubClient(AppConfiguration.ProductHeader, credentialStore, hostAddress.ApiUri));
19+
new GitHubClient(AppConfiguration.ProductHeader, credentialStore, hostAddress.ApiUri),
20+
processManager, taskManager, loginTool);
2021
}
2122

2223
private static readonly ILogging logger = Logging.GetLogger<ApiClient>();
@@ -25,9 +26,12 @@ public static IApiClient Create(UriString repositoryUrl, IKeychain keychain)
2526

2627
private readonly IKeychain keychain;
2728
private readonly IGitHubClient githubClient;
29+
private readonly IProcessManager processManager;
30+
private readonly ITaskManager taskManager;
31+
private readonly NPath loginTool;
2832
private readonly ILoginManager loginManager;
2933

30-
public ApiClient(UriString hostUrl, IKeychain keychain, IGitHubClient githubClient)
34+
public ApiClient(UriString hostUrl, IKeychain keychain, IGitHubClient githubClient, IProcessManager processManager, ITaskManager taskManager, NPath loginTool)
3135
{
3236
Guard.ArgumentNotNull(hostUrl, nameof(hostUrl));
3337
Guard.ArgumentNotNull(keychain, nameof(keychain));
@@ -37,7 +41,13 @@ public ApiClient(UriString hostUrl, IKeychain keychain, IGitHubClient githubClie
3741
OriginalUrl = hostUrl;
3842
this.keychain = keychain;
3943
this.githubClient = githubClient;
40-
loginManager = new LoginManager(keychain, ApplicationInfo.ClientId, ApplicationInfo.ClientSecret);
44+
this.processManager = processManager;
45+
this.taskManager = taskManager;
46+
this.loginTool = loginTool;
47+
loginManager = new LoginManager(keychain, ApplicationInfo.ClientId, ApplicationInfo.ClientSecret,
48+
processManager: processManager,
49+
taskManager: taskManager,
50+
loginTool: loginTool);
4151
}
4252

4353
public async Task Logout(UriString host)

src/GitHub.Api/Application/ApplicationManagerBase.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ protected virtual void Dispose(bool disposing)
199199
}
200200
}
201201

202+
public virtual NPath GetTool(string tool)
203+
{
204+
return null;
205+
}
206+
202207
public void Dispose()
203208
{
204209
Dispose(true);
@@ -216,6 +221,7 @@ public void Dispose()
216221
public ISettings SystemSettings { get; protected set; }
217222
public ISettings UserSettings { get; protected set; }
218223
public IUsageTracker UsageTracker { get; protected set; }
224+
public NPath LoginTool => GetTool("octorun.exe");
219225
protected TaskScheduler UIScheduler { get; private set; }
220226
protected SynchronizationContext SynchronizationContext { get; private set; }
221227
protected IRepositoryManager RepositoryManager { get { return repositoryManager; } }

src/GitHub.Api/Application/IApplicationManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ public interface IApplicationManager : IDisposable
1717
ITaskManager TaskManager { get; }
1818
IGitClient GitClient { get; }
1919
IUsageTracker UsageTracker { get; }
20+
NPath LoginTool { get; }
2021

2122
void Run(bool firstRun);
2223
void RestartRepository();
2324
ITask InitializeRepository();
25+
NPath GetTool(string tool);
2426
}
2527
}

src/GitHub.Api/Authentication/LoginManager.cs

Lines changed: 117 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,30 @@ class LoginManager : ILoginManager
2727
private readonly string clientSecret;
2828
private readonly string authorizationNote;
2929
private readonly string fingerprint;
30+
private readonly IProcessManager processManager;
31+
private readonly ITaskManager taskManager;
32+
private readonly NPath loginTool;
3033

3134
/// <summary>
3235
/// Initializes a new instance of the <see cref="LoginManager"/> class.
3336
/// </summary>
34-
/// <param name="loginCache">The cache in which to store login details.</param>
35-
/// <param name="twoFactorChallengeHandler">The handler for 2FA challenges.</param>
37+
/// <param name="keychain"></param>
3638
/// <param name="clientId">The application's client API ID.</param>
3739
/// <param name="clientSecret">The application's client API secret.</param>
3840
/// <param name="authorizationNote">An note to store with the authorization.</param>
3941
/// <param name="fingerprint">The machine fingerprint.</param>
42+
/// <param name="processManager"></param>
43+
/// <param name="taskManager"></param>
44+
/// <param name="loginTool"></param>
45+
/// <param name="loginCache">The cache in which to store login details.</param>
46+
/// <param name="twoFactorChallengeHandler">The handler for 2FA challenges.</param>
4047
public LoginManager(
4148
IKeychain keychain,
4249
string clientId,
4350
string clientSecret,
4451
string authorizationNote = null,
45-
string fingerprint = null)
52+
string fingerprint = null,
53+
IProcessManager processManager = null, ITaskManager taskManager = null, NPath loginTool = null)
4654
{
4755
Guard.ArgumentNotNull(keychain, nameof(keychain));
4856
Guard.ArgumentNotNullOrWhiteSpace(clientId, nameof(clientId));
@@ -53,6 +61,9 @@ public LoginManager(
5361
this.clientSecret = clientSecret;
5462
this.authorizationNote = authorizationNote;
5563
this.fingerprint = fingerprint;
64+
this.processManager = processManager;
65+
this.taskManager = taskManager;
66+
this.loginTool = loginTool;
5667
}
5768

5869
/// <inheritdoc/>
@@ -83,9 +94,8 @@ public async Task<LoginResultData> Login(
8394

8495
try
8596
{
86-
logger.Info("Login Username:{0}", username);
87-
88-
auth = await CreateAndDeleteExistingApplicationAuthorization(client, newAuth, null);
97+
//auth = await CreateAndDeleteExistingApplicationAuthorization(client, newAuth, null);
98+
auth = await TryLogin(client, host, username, password);
8999
EnsureNonNullAuthorization(auth);
90100
}
91101
catch (TwoFactorAuthorizationException e)
@@ -148,15 +158,17 @@ public async Task<LoginResultData> ContinueLogin(LoginResultData loginResultData
148158
var client = loginResultData.Client;
149159
var newAuth = loginResultData.NewAuth;
150160
var host = loginResultData.Host;
151-
161+
var k = keychain.Connect(host);
162+
var username = k.Credential.Username;
163+
var password = k.Credential.Token;
152164
try
153165
{
154166
logger.Trace("2FA Continue");
155-
156-
var auth = await CreateAndDeleteExistingApplicationAuthorization(
157-
client,
158-
newAuth,
159-
twofacode);
167+
var auth = await TryContinueLogin(client, host, username, password, twofacode);
168+
//var auth = await CreateAndDeleteExistingApplicationAuthorization(
169+
// client,
170+
// newAuth,
171+
// twofacode);
160172
EnsureNonNullAuthorization(auth);
161173

162174
keychain.SetToken(host, auth.Token);
@@ -207,6 +219,7 @@ private async Task<ApplicationAuthorization> CreateAndDeleteExistingApplicationA
207219
{
208220
if (twoFactorAuthenticationCode == null)
209221
{
222+
210223
result = await client.Authorization.GetOrCreateApplicationAuthentication(
211224
clientId,
212225
clientSecret,
@@ -237,6 +250,98 @@ private async Task<ApplicationAuthorization> CreateAndDeleteExistingApplicationA
237250
return result;
238251
}
239252

253+
private async Task<ApplicationAuthorization> TryLogin(
254+
IGitHubClient client,
255+
UriString host,
256+
string username,
257+
string password
258+
)
259+
{
260+
logger.Info("Login Username:{0}", username);
261+
262+
ApplicationAuthorization auth = null;
263+
var loginTask = new SimpleListProcessTask(taskManager.Token, loginTool, $"login --host={host}");
264+
loginTask.Configure(processManager, true);
265+
loginTask.OnStartProcess += proc =>
266+
{
267+
proc.StandardInput.WriteLine(username);
268+
proc.StandardInput.WriteLine(password);
269+
};
270+
var ret = await loginTask.StartAwait();
271+
if (ret.Count == 0)
272+
{
273+
throw new Exception("Authentication failed");
274+
}
275+
// success
276+
else if (ret.Count == 1)
277+
{
278+
auth = new ApplicationAuthorization(ret[0]);
279+
}
280+
else
281+
{
282+
if (ret[0] == "2fa")
283+
{
284+
keychain.SetToken(host, ret[1]);
285+
await keychain.Save(host);
286+
throw new TwoFactorRequiredException(TwoFactorType.Unknown);
287+
}
288+
else if (ret[0] == "locked")
289+
{
290+
throw new LoginAttemptsExceededException(null, null);
291+
}
292+
else
293+
throw new Exception("Authentication failed");
294+
}
295+
return auth;
296+
}
297+
298+
private async Task<ApplicationAuthorization> TryContinueLogin(
299+
IGitHubClient client,
300+
UriString host,
301+
string username,
302+
string password,
303+
string code
304+
)
305+
{
306+
logger.Info("Continue Username:{0}", username);
307+
308+
ApplicationAuthorization auth = null;
309+
var loginTask = new SimpleListProcessTask(taskManager.Token, loginTool, $"login --host={host} --2fa");
310+
loginTask.Configure(processManager, true);
311+
loginTask.OnStartProcess += proc =>
312+
{
313+
proc.StandardInput.WriteLine(username);
314+
proc.StandardInput.WriteLine(password);
315+
proc.StandardInput.WriteLine(code);
316+
};
317+
var ret = await loginTask.StartAwait();
318+
if (ret.Count == 0)
319+
{
320+
throw new Exception("Authentication failed");
321+
}
322+
// success
323+
else if (ret.Count == 1)
324+
{
325+
auth = new ApplicationAuthorization(ret[0]);
326+
}
327+
else
328+
{
329+
if (ret[0] == "2fa")
330+
{
331+
keychain.SetToken(host, ret[1]);
332+
await keychain.Save(host);
333+
throw new TwoFactorRequiredException(TwoFactorType.Unknown);
334+
}
335+
else if (ret[0] == "locked")
336+
{
337+
throw new LoginAttemptsExceededException(null, null);
338+
}
339+
else
340+
throw new Exception("Authentication failed");
341+
}
342+
return auth;
343+
}
344+
240345
ApplicationAuthorization EnsureNonNullAuthorization(ApplicationAuthorization auth)
241346
{
242347
// If a mock IGitHubClient is not set up correctly, it can return null from

src/GitHub.Api/GitHub.Api.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
<AssemblyName>GitHub.Api</AssemblyName>
1212
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
1313
<FileAlignment>512</FileAlignment>
14-
<OutputPath>bin\$(Configuration)\</OutputPath>
1514
<TargetFrameworkProfile />
1615
<LangVersion>6</LangVersion>
1716
<NuGetPackageImportStamp>

src/GitHub.Api/NewTaskSystem/BaseOutputProcessor.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,28 @@ protected override bool ProcessLine(string line, out NPath result)
125125
return true;
126126
}
127127
}
128+
129+
class LastResultOutputProcessor<T> : BaseOutputProcessor<T>
130+
{
131+
public override void LineReceived(string line)
132+
{
133+
T res;
134+
if (ProcessLine(line, out res))
135+
{
136+
Result = res;
137+
RaiseOnEntry(res);
138+
}
139+
}
140+
141+
protected bool ProcessLine(string line, out T result)
142+
{
143+
result = default(T);
144+
if (typeof(T) == typeof(string))
145+
{
146+
result = (T)(object)line;
147+
return true;
148+
}
149+
return false;
150+
}
151+
}
128152
}

src/GitHub.Api/NewTaskSystem/ProcessTask.cs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -468,20 +468,41 @@ class SimpleProcessTask : ProcessTask<string>
468468
private readonly NPath fullPathToExecutable;
469469
private readonly string arguments;
470470

471-
public SimpleProcessTask(CancellationToken token, NPath fullPathToExecutable, string arguments)
472-
: base(token, new SimpleOutputProcessor())
471+
public SimpleProcessTask(CancellationToken token, NPath fullPathToExecutable, string arguments, IOutputProcessor<string> processor = null)
472+
: base(token, processor ?? new SimpleOutputProcessor())
473473
{
474474
this.fullPathToExecutable = fullPathToExecutable;
475475
this.arguments = arguments;
476476
}
477477

478-
public SimpleProcessTask(CancellationToken token, string arguments)
479-
: base(token, new SimpleOutputProcessor())
478+
public SimpleProcessTask(CancellationToken token, string arguments, IOutputProcessor<string> processor = null)
479+
: base(token, processor ?? new SimpleOutputProcessor())
480480
{
481481
this.arguments = arguments;
482482
}
483483

484484
public override string ProcessName => fullPathToExecutable?.FileName;
485485
public override string ProcessArguments => arguments;
486486
}
487-
}
487+
488+
class SimpleListProcessTask : ProcessTaskWithListOutput<string>
489+
{
490+
private readonly NPath fullPathToExecutable;
491+
private readonly string arguments;
492+
493+
public SimpleListProcessTask(CancellationToken token, NPath fullPathToExecutable, string arguments, IOutputProcessor<string, List<string>> processor = null)
494+
: base(token, processor ?? new SimpleListOutputProcessor())
495+
{
496+
this.fullPathToExecutable = fullPathToExecutable;
497+
this.arguments = arguments;
498+
}
499+
500+
public SimpleListProcessTask(CancellationToken token, string arguments, IOutputProcessor<string, List<string>> processor = null)
501+
: base(token, processor ?? new SimpleListOutputProcessor())
502+
{
503+
this.arguments = arguments;
504+
}
505+
506+
public override string ProcessName => fullPathToExecutable?.FileName;
507+
public override string ProcessArguments => arguments;
508+
}}

0 commit comments

Comments
 (0)