Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cut new release #105

Merged
merged 29 commits into from
Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7c26c5f
Merge pull request #87 from microsoft/release
mjcheetham Dec 13, 2019
5b2b752
Add GCM_INTERACTIVE/credential.interactive setting
mjcheetham Jan 31, 2020
21abaa0
Merge pull request #91 from mjcheetham/nointeractive
mjcheetham Jan 31, 2020
3c183f9
Create and publish zip & tar.gz archives of app and symbols
mjcheetham Feb 13, 2020
be7feb7
Merge pull request #96 from microsoft/zippy
mjcheetham Feb 13, 2020
92e1d9e
Add ISystemPrompts component and impl for Windows
mjcheetham May 24, 2019
22da69c
Set parent window handle based on GCM_MODAL_PARENTHWD
mjcheetham Feb 12, 2020
8ef6914
Merge pull request #92 from mjcheetham/basic-nativeui
mjcheetham Feb 14, 2020
7a003ac
Add debug trace listener when debugger is attached
mjcheetham Feb 7, 2020
f64d3e9
Merge pull request #94 from mjcheetham/debug-trace
mjcheetham Apr 6, 2020
60fc632
build: update macOS build images
mjcheetham Apr 6, 2020
fca0adc
oauth: add an OAuth2 client implementation
mjcheetham Feb 4, 2020
637a056
oauth: add unit tests for OAuth2Client
mjcheetham Apr 14, 2020
0b0752d
Merge pull request #99 from mjcheetham/oauth2
mjcheetham Apr 16, 2020
bac4884
terminal: add a simple TTY-based menu system
mjcheetham Apr 8, 2020
e12fd6a
settings: pull generic 'get' methods up to interface
mjcheetham Apr 16, 2020
d5975df
github: use OAuth2/web flow for GitHub
mjcheetham Apr 8, 2020
d8cc644
github-ui: use new GitHub UI on Windows
mjcheetham Mar 30, 2020
7283e81
build: use latest .NET Core SDK 3.1.201 to build
mjcheetham Apr 17, 2020
98bde60
netcore: upgrade to .NET Core 3.1
mjcheetham Apr 17, 2020
5f9d449
build: ignore Windows projects in Mac sln configs
mjcheetham Apr 17, 2020
9ffdc5e
Merge pull request #101 from mjcheetham/github-oauth-ui
mjcheetham Apr 22, 2020
5f13ab2
oauth: implement RFC 7636 PKCE in OAuth client
mjcheetham Apr 21, 2020
adccfaf
oauth: ensure auth grant redirect URI matches token req
mjcheetham Apr 21, 2020
7efe852
Merge pull request #102 from mjcheetham/oauth2-pkce
mjcheetham Apr 23, 2020
e2d7b80
github: allow OAuth params to be overridden at runtime
mjcheetham Apr 24, 2020
9d17ca2
Merge pull request #103 from mjcheetham/gh-oauthenv
mjcheetham Apr 28, 2020
fcaec85
build: workaround Azure Pipelines build bug
mjcheetham Apr 28, 2020
ba53205
Merge pull request #106 from mjcheetham/fix-winbuild
mjcheetham Apr 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add ISystemPrompts component and impl for Windows
Introduce the `ISystemPrompts` component which provides native/system UI
prompts, starting with basic credential prompts. Include a basic
implementation for Windows.
  • Loading branch information
mjcheetham committed Feb 14, 2020
commit 92e1d9e668c1d4a33108e46da73b2f61c8e00af5
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using Microsoft.Git.CredentialManager.Authentication;
using Microsoft.Git.CredentialManager.Tests.Objects;
using Moq;
using Xunit;

namespace Microsoft.Git.CredentialManager.Tests.Authentication
Expand All @@ -19,49 +20,50 @@ public void BasicAuthentication_GetCredentials_NullResource_ThrowsException()
}

[Fact]
public void BasicAuthentication_GetCredentials_ResourceAndUserName_PasswordPromptReturnsCredentials()
public void BasicAuthentication_GetCredentials_NonDesktopSession_ResourceAndUserName_PasswordPromptReturnsCredentials()
{
const string testResource = "https://example.com";
const string testUserName = "john.doe";
const string testPassword = "letmein123";

var context = new TestCommandContext();
var context = new TestCommandContext {IsDesktopSession = false};
context.Terminal.SecretPrompts["Password"] = testPassword;

var basicAuth = new BasicAuthentication(context);

GitCredential credential = basicAuth.GetCredentials(testResource, testUserName);
ICredential credential = basicAuth.GetCredentials(testResource, testUserName);

Assert.Equal(testUserName, credential.UserName);
Assert.Equal(testPassword, credential.Password);
}

[Fact]
public void BasicAuthentication_GetCredentials_Resource_UserPassPromptReturnsCredentials()
public void BasicAuthentication_GetCredentials_NonDesktopSession_Resource_UserPassPromptReturnsCredentials()
{
const string testResource = "https://example.com";
const string testUserName = "john.doe";
const string testPassword = "letmein123";

var context = new TestCommandContext();
var context = new TestCommandContext {IsDesktopSession = false};
context.Terminal.Prompts["Username"] = testUserName;
context.Terminal.SecretPrompts["Password"] = testPassword;

var basicAuth = new BasicAuthentication(context);

GitCredential credential = basicAuth.GetCredentials(testResource);
ICredential credential = basicAuth.GetCredentials(testResource);

Assert.Equal(testUserName, credential.UserName);
Assert.Equal(testPassword, credential.Password);
}

[Fact]
public void BasicAuthentication_GetCredentials_NoInteraction_ThrowsException()
public void BasicAuthentication_GetCredentials_NonDesktopSession_NoTerminalPrompts_ThrowsException()
{
const string testResource = "https://example.com";

var context = new TestCommandContext
{
IsDesktopSession = false,
Settings = {IsInteractionAllowed = false},
};

Expand All @@ -70,19 +72,98 @@ public void BasicAuthentication_GetCredentials_NoInteraction_ThrowsException()
Assert.Throws<InvalidOperationException>(() => basicAuth.GetCredentials(testResource));
}

[Fact]
public void BasicAuthentication_GetCredentials_NoTerminalPrompts_ThrowsException()
[PlatformFact(Platform.Windows)]
public void BasicAuthentication_GetCredentials_DesktopSession_Resource_UserPassPromptReturnsCredentials()
{
const string testResource = "https://example.com";
const string testUserName = "john.doe";
const string testPassword = "letmein123";

var context = new TestCommandContext
{
Settings = {IsTerminalPromptsEnabled = false},
IsDesktopSession = true,
SystemPrompts =
{
CredentialPrompt = (resource, userName) =>
{
Assert.Equal(testResource, resource);
Assert.Null(userName);

return new GitCredential(testUserName, testPassword);
}
}
};

var basicAuth = new BasicAuthentication(context);

Assert.Throws<InvalidOperationException>(() => basicAuth.GetCredentials(testResource));
ICredential credential = basicAuth.GetCredentials(testResource);

Assert.NotNull(credential);
Assert.Equal(testUserName, credential.UserName);
Assert.Equal(testPassword, credential.Password);
}

[PlatformFact(Platform.Windows)]
public void BasicAuthentication_GetCredentials_DesktopSession_ResourceAndUser_PassPromptReturnsCredentials()
{
const string testResource = "https://example.com";
const string testUserName = "john.doe";
const string testPassword = "letmein123";

var context = new TestCommandContext
{
IsDesktopSession = true,
SystemPrompts =
{
CredentialPrompt = (resource, userName) =>
{
Assert.Equal(testResource, resource);
Assert.Equal(testUserName, userName);

return new GitCredential(testUserName, testPassword);
}
}
};

var basicAuth = new BasicAuthentication(context);

ICredential credential = basicAuth.GetCredentials(testResource, testUserName);

Assert.NotNull(credential);
Assert.Equal(testUserName, credential.UserName);
Assert.Equal(testPassword, credential.Password);
}

[PlatformFact(Platform.Windows)]
public void BasicAuthentication_GetCredentials_DesktopSession_ResourceAndUser_PassPromptDiffUserReturnsCredentials()
{
const string testResource = "https://example.com";
const string testUserName = "john.doe";
const string newUserName = "jane.doe";
const string testPassword = "letmein123";

var context = new TestCommandContext
{
IsDesktopSession = true,
SystemPrompts =
{
CredentialPrompt = (resource, userName) =>
{
Assert.Equal(testResource, resource);
Assert.Equal(testUserName, userName);

return new GitCredential(newUserName, testPassword);
}
}
};

var basicAuth = new BasicAuthentication(context);

ICredential credential = basicAuth.GetCredentials(testResource, testUserName);

Assert.NotNull(credential);
Assert.Equal(newUserName, credential.UserName);
Assert.Equal(testPassword, credential.Password);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using Microsoft.Git.CredentialManager.Interop.Windows;
using Microsoft.Git.CredentialManager.Tests.Objects;
using Xunit;

namespace Microsoft.Git.CredentialManager.Tests.Interop.Windows
{
public class WindowsSystemPromptsTests
{
[Fact]
public void WindowsSystemPrompts_ShowCredentialPrompt_NullResource_ThrowsException()
{
var sysPrompts = new WindowsSystemPrompts();
Assert.Throws<ArgumentNullException>(() => sysPrompts.ShowCredentialPrompt(null, null, out _));
}

[Fact]
public void WindowsSystemPrompts_ShowCredentialPrompt_EmptyResource_ThrowsException()
{
var sysPrompts = new WindowsSystemPrompts();
Assert.Throws<ArgumentException>(() => sysPrompts.ShowCredentialPrompt(string.Empty, null, out _));
}

[Fact]
public void WindowsSystemPrompts_ShowCredentialPrompt_WhiteSpaceResource_ThrowsException()
{
var sysPrompts = new WindowsSystemPrompts();
Assert.Throws<ArgumentException>(() => sysPrompts.ShowCredentialPrompt(" ", null, out _));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;

namespace Microsoft.Git.CredentialManager.Authentication
{
public interface IBasicAuthentication
{
GitCredential GetCredentials(string resource, string userName);
ICredential GetCredentials(string resource, string userName);
}

public static class BasicAuthenticationExtensions
{
public static GitCredential GetCredentials(this IBasicAuthentication basicAuth, string resource)
public static ICredential GetCredentials(this IBasicAuthentication basicAuth, string resource)
{
return basicAuth.GetCredentials(resource, null);
}
Expand All @@ -26,13 +27,25 @@ public class BasicAuthentication : AuthenticationBase, IBasicAuthentication
public BasicAuthentication(ICommandContext context)
: base (context) { }

public GitCredential GetCredentials(string resource, string userName)
public ICredential GetCredentials(string resource, string userName)
{
EnsureArgument.NotNullOrWhiteSpace(resource, nameof(resource));

ThrowIfUserInteractionDisabled();

// TODO: we only support system GUI prompts on Windows currently
if (Context.IsDesktopSession && PlatformUtils.IsWindows())
{
return GetCredentialsByUi(resource, userName);
}

ThrowIfTerminalPromptsDisabled();

return GetCredentialsByTty(resource, userName);
}

private ICredential GetCredentialsByTty(string resource, string userName)
{
Context.Terminal.WriteLine("Enter basic credentials for '{0}':", resource);

if (!string.IsNullOrWhiteSpace(userName))
Expand All @@ -51,5 +64,15 @@ public GitCredential GetCredentials(string resource, string userName)

return new GitCredential(userName, password);
}

private ICredential GetCredentialsByUi(string resource, string userName)
{
if (!Context.SystemPrompts.ShowCredentialPrompt(resource, userName, out ICredential credential))
{
throw new Exception("User cancelled the authentication prompt.");
}

return credential;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ private async Task<JsonWebToken> GetAccessTokenInProcAsync(string authority, str
}
#elif NETSTANDARD
// MSAL requires the application redirect URI is a loopback address to use the System WebView
if (PlatformUtils.IsDesktopSession() && app.IsSystemWebViewAvailable && redirectUri.IsLoopback)
if (Context.IsDesktopSession && app.IsSystemWebViewAvailable && redirectUri.IsLoopback)
{
result = await app.AcquireTokenInteractive(scopes)
.WithPrompt(Prompt.SelectAccount)
Expand Down
17 changes: 17 additions & 0 deletions src/shared/Microsoft.Git.CredentialManager/CommandContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public interface ICommandContext : IDisposable
/// </summary>
ITerminal Terminal { get; }

/// <summary>
/// Returns true if in a GUI session/desktop is available, false otherwise.
/// </summary>
bool IsDesktopSession { get; }

/// <summary>
/// Application tracing system.
/// </summary>
Expand Down Expand Up @@ -57,6 +62,11 @@ public interface ICommandContext : IDisposable
/// The current process environment.
/// </summary>
IEnvironment Environment { get; }

/// <summary>
/// Native UI prompts.
/// </summary>
ISystemPrompts SystemPrompts { get; }
}

/// <summary>
Expand All @@ -76,13 +86,15 @@ public CommandContext()
Environment = new WindowsEnvironment(FileSystem);
Terminal = new WindowsTerminal(Trace);
CredentialStore = WindowsCredentialManager.Open();
SystemPrompts = new WindowsSystemPrompts();
}
else if (PlatformUtils.IsPosix())
{
if (PlatformUtils.IsMacOS())
{
FileSystem = new MacOSFileSystem();
CredentialStore = MacOSKeychain.Open();
SystemPrompts = new MacOSSystemPrompts();
}
else if (PlatformUtils.IsLinux())
{
Expand All @@ -96,6 +108,7 @@ public CommandContext()
string repoPath = Git.GetRepositoryPath(FileSystem.GetCurrentDirectory());
Settings = new Settings(Environment, Git, repoPath);
HttpClientFactory = new HttpClientFactory(Trace, Settings, Streams);
IsDesktopSession = PlatformUtils.IsDesktopSession();
}

#region ICommandContext
Expand All @@ -106,6 +119,8 @@ public CommandContext()

public ITerminal Terminal { get; }

public bool IsDesktopSession { get; }

public ITrace Trace { get; }

public IFileSystem FileSystem { get; }
Expand All @@ -118,6 +133,8 @@ public CommandContext()

public IEnvironment Environment { get; }

public ISystemPrompts SystemPrompts { get; }

#endregion

#region IDisposable
Expand Down
10 changes: 10 additions & 0 deletions src/shared/Microsoft.Git.CredentialManager/ISystemPrompts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

namespace Microsoft.Git.CredentialManager
{
public interface ISystemPrompts
{
bool ShowCredentialPrompt(string resource, string userName, out ICredential credential);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

namespace Microsoft.Git.CredentialManager.Interop.MacOS
{
public class MacOSSystemPrompts : ISystemPrompts
{
public bool ShowCredentialPrompt(string resource, string userName, out ICredential credential)
{
throw new System.NotImplementedException();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
Expand Down
Loading