1
0
Форкнуть 0

Merge pull request #653 from mminns/issue/651

Issue/651
This commit is contained in:
J Wyman 2018-06-07 13:38:44 -04:00 коммит произвёл GitHub
Родитель 2b480067ac a21ec78592
Коммит dea7b5cb9e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 234 добавлений и 12 удалений

Просмотреть файл

@ -25,6 +25,7 @@
using System;
using System.Threading.Tasks;
using Atlassian.Bitbucket.Authentication.BasicAuth;
using Microsoft.Alm.Authentication;
namespace Atlassian.Bitbucket.Authentication
@ -53,7 +54,8 @@ namespace Atlassian.Bitbucket.Authentication
RuntimeContext context,
ICredentialStore personalAccessTokenStore,
AcquireCredentialsDelegate acquireCredentialsCallback,
AcquireAuthenticationOAuthDelegate acquireAuthenticationOAuthCallback)
AcquireAuthenticationOAuthDelegate acquireAuthenticationOAuthCallback,
IAuthority authority = null)
: base(context)
{
if (personalAccessTokenStore == null)
@ -61,7 +63,7 @@ namespace Atlassian.Bitbucket.Authentication
PersonalAccessTokenStore = personalAccessTokenStore;
BitbucketAuthority = new Authority(context);
BitbucketAuthority = authority ?? new Authority(context);
TokenScope = TokenScope.SnippetWrite | TokenScope.RepositoryWrite;
AcquireCredentialsCallback = acquireCredentialsCallback;

Просмотреть файл

@ -7,7 +7,10 @@ using Microsoft.Alm.Authentication.Git;
namespace Atlassian.Bitbucket.Authentication.BasicAuth
{
internal class BasicAuthAuthenticator : Base
/// <summary>
/// Provides the functionality for validating basic auth credentials with Bitbucket.org
/// </summary>
public class BasicAuthAuthenticator : Base
{
public BasicAuthAuthenticator(RuntimeContext context)
: base(context)

Просмотреть файл

@ -5,7 +5,7 @@
<PropertyGroup>
<AppDesignerFolder>Properties</AppDesignerFolder>
<AssemblyName>Bitbucket.Authentication</AssemblyName>
<NuGetPackageImportStamp/>
<NuGetPackageImportStamp />
<OutputType>Library</OutputType>
<ProjectGuid>{EE663736-5BAD-4CA6-A4F8-99978925AD8A}</ProjectGuid>
<ProjectName>Bitbucket.Authentication</ProjectName>

Просмотреть файл

@ -31,7 +31,7 @@ namespace Atlassian.Bitbucket.Authentication
/// <summary>
/// Defines the interactions with the Authority capable of providing and validating Bitbucket credentials.
/// </summary>
internal interface IAuthority
public interface IAuthority
{
/// <summary>
/// Use the provided credentials, username and password, to request an access token from the Authority.

Просмотреть файл

@ -1,4 +1,5 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following set of attributes.
@ -27,3 +28,5 @@ using System.Runtime.InteropServices;
// as shown below: [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.0.0.0")]
[assembly: AssemblyFileVersion("2.0.0.0")]
[assembly: InternalsVisibleTo("Bitbucket.Authentication.Test")]

Просмотреть файл

@ -3,13 +3,27 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Atlassian.Bitbucket.Authentication.BasicAuth;
using Microsoft.Alm.Authentication;
using Moq;
using Xunit;
namespace Atlassian.Bitbucket.Authentication.Test
{
public class AuthenticationTest
{
/// <summary>
/// used to populate delegates;
/// </summary>
private const string _validUsername = "john";
private const string _validPassword = "squire";
private Credential _validBasicAuthCredentials = new Credential(_validUsername, _validPassword);
private const string _invalidUsername = "invalid_username";
private const string _invalidPassword = "invalid_password";
private Credential _invalidBasicAuthCredentials = new Credential(_invalidUsername, _invalidPassword);
[Fact]
public void VerifyBitbucketOrgIsIdentified()
{
@ -188,7 +202,7 @@ namespace Atlassian.Bitbucket.Authentication.Test
{
var credentialStore = new MockCredentialStore();
// Add a stored basic authentication credential to delete.
credentialStore.Credentials.Add("https://example.com/", new Credential("john", "squire"));
credentialStore.Credentials.Add("https://example.com/", _validBasicAuthCredentials);
var bbAuth = new Authentication(RuntimeContext.Default, credentialStore, null, null);
@ -219,7 +233,7 @@ namespace Atlassian.Bitbucket.Authentication.Test
{
var credentialStore = new MockCredentialStore();
// add a stored basic auth credential to delete.
credentialStore.Credentials.Add("https://example.com/", new Credential("john", "a1b2c3"));
credentialStore.Credentials.Add("https://example.com/", _validBasicAuthCredentials);
credentialStore.Credentials.Add("https://example.com/refresh_token", new Credential("john", "d4e5f6"));
var bbAuth = new Authentication(RuntimeContext.Default, credentialStore, null, null);
@ -252,9 +266,9 @@ namespace Atlassian.Bitbucket.Authentication.Test
var credentialStore = new MockCredentialStore();
// add a stored basic auth credential to delete.
// per host credentials
credentialStore.Credentials.Add("https://example.com/", new Credential("john", "squire"));
credentialStore.Credentials.Add("https://example.com/", _validBasicAuthCredentials);
// per user credentials
credentialStore.Credentials.Add("https://john@example.com/", new Credential("john", "squire"));
credentialStore.Credentials.Add("https://john@example.com/", _validBasicAuthCredentials);
var bbAuth = new Authentication(RuntimeContext.Default, credentialStore, null, null);
@ -298,7 +312,7 @@ namespace Atlassian.Bitbucket.Authentication.Test
// per host credentials
credentialStore.Credentials.Add("https://example.com/", new Credential("ian", "brown"));
// per user credentials
credentialStore.Credentials.Add("https://john@example.com/", new Credential("john", "squire"));
credentialStore.Credentials.Add("https://john@example.com/", _validBasicAuthCredentials);
var bbAuth = new Authentication(RuntimeContext.Default, credentialStore, null, null);
@ -401,9 +415,152 @@ namespace Atlassian.Bitbucket.Authentication.Test
Assert.Equal("https", resultUri.Scheme);
Assert.Equal("https://example.com/", resultUri.ToString(false, true, true));
}
[Fact]
public async void VerifyInteractiveLoginAquiresAndStoresValidBasicAuthCredentials()
{
var bitbucketUrl = "https://bitbucket.org";
var credentialStore = new Mock<ICredentialStore>();
var targetUri = new TargetUri(bitbucketUrl);
// Mock the behaviour of IAuthority.AcquireToken() to basically mimic BasicAuthAuthenticator.GetAuthAsync() validating the useername/password
var authority = new Mock<IAuthority>();
authority
.Setup(a => a.AcquireToken(It.IsAny<TargetUri>(), It.IsAny<Credential>(), It.IsAny<AuthenticationResultType>(), It.IsAny<TokenScope>()))
// return 'success' with the validated credentials
.Returns(Task.FromResult<AuthenticationResult>(new AuthenticationResult(AuthenticationResultType.Success, new Token(_validBasicAuthCredentials.Password, TokenType.Personal))));
var bbAuth = new Authentication(RuntimeContext.Default, credentialStore.Object, MockValidBasicAuthCredentialsAquireCredentialsCallback, MockValidAquireAuthenticationOAuthCallback, authority.Object);
var credentials = await bbAuth.InteractiveLogon(targetUri);
Assert.NotNull(credentials);
// attempted to validate credentials
authority.Verify(a => a.AcquireToken(targetUri, _validBasicAuthCredentials, AuthenticationResultType.None, TokenScope.SnippetWrite | TokenScope.RepositoryWrite), Times.Once);
// valid credentials stored
credentialStore.Verify(c => c.WriteCredentials(targetUri, _validBasicAuthCredentials), Times.Once);
}
[Fact]
public async void VerifyInteractiveLoginDoesNotAquireInvalidBasicAuthCredentials()
{
var bitbucketUrl = "https://bitbucket.org";
var credentialStore = new Mock<ICredentialStore>();
var targetUri = new TargetUri(bitbucketUrl);
// Mock the behaviour of IAuthority.AcquireToken() to basically mimic BasicAuthAuthenticator.GetAuthAsync() validating the useername/password
var authority = new Mock<IAuthority>();
authority
.Setup(a => a.AcquireToken(It.IsAny<TargetUri>(), It.IsAny<Credential>(), It.IsAny<AuthenticationResultType>(), It.IsAny<TokenScope>()))
// return 'failure' with the validated credentials
.Returns(Task.FromResult<AuthenticationResult>(new AuthenticationResult(AuthenticationResultType.Failure)));
var bbAuth = new Authentication(RuntimeContext.Default, credentialStore.Object, MockInvalidBasicAuthCredentialsAquireCredentialsCallback, MockValidAquireAuthenticationOAuthCallback, authority.Object);
var credentials = await bbAuth.InteractiveLogon(targetUri);
Assert.NotNull(credentials);
Assert.Equal(_invalidUsername, credentials.Username);
Assert.Equal(_invalidPassword, credentials.Password);
// attempted to validate credentials
authority.Verify(a => a.AcquireToken(It.IsAny<TargetUri>(), It.IsAny<Credential>(), It.IsAny<AuthenticationResultType>(), It.IsAny<TokenScope>()), Times.Once);
// no attempt to store invalid credentials
credentialStore.Verify(c => c.WriteCredentials(It.IsAny<TargetUri>(), It.IsAny<Credential>()), Times.Never);
}
[Fact]
public async void VerifyInteractiveLoginDoesNothingIfUserDoesNotEnterCredentials()
{
var bitbucketUrl = "https://bitbucket.org";
var credentialStore = new Mock<ICredentialStore>();
var targetUri = new TargetUri(bitbucketUrl);
// Mock the behaviour of IAuthority.AcquireToken() to basically mimic BasicAuthAuthenticator.GetAuthAsync() validating the useername/password
var authority = new Mock<IAuthority>();
authority
.Setup(a => a.AcquireToken(It.IsAny<TargetUri>(), It.IsAny<Credential>(), It.IsAny<AuthenticationResultType>(), It.IsAny<TokenScope>()))
// return 'failure' with the validated credentials
.Returns(Task.FromResult<AuthenticationResult>(new AuthenticationResult(AuthenticationResultType.Failure)));
var bbAuth = new Authentication(RuntimeContext.Default, credentialStore.Object, MockNoCredentialsAquireCredentialsCallback, MockValidAquireAuthenticationOAuthCallback, authority.Object);
var credentials = await bbAuth.InteractiveLogon(targetUri);
Assert.Null(credentials);
// no attempted to validate credentials
authority.Verify(a => a.AcquireToken(It.IsAny<TargetUri>(), It.IsAny<Credential>(), It.IsAny<AuthenticationResultType>(), It.IsAny<TokenScope>()), Times.Never);
// no attempt to store invalid credentials
credentialStore.Verify(c => c.WriteCredentials(It.IsAny<TargetUri>(), It.IsAny<Credential>()), Times.Never);
}
[Fact]
public async void VerifyInteractiveLoginAquiresAndStoresValidOAuthCredentials()
{
var bitbucketUrl = "https://bitbucket.org";
var credentialStore = new Mock<ICredentialStore>();
var targetUri = new TargetUri(bitbucketUrl);
// Mock the behaviour of IAuthority.AcquireToken() to basically mimic BasicAuthAuthenticator.GetAuthAsync() validating the useername/password
var authority = new Mock<IAuthority>();
authority
.Setup(a => a.AcquireToken(It.IsAny<TargetUri>(), It.IsAny<Credential>(), AuthenticationResultType.None, It.IsAny<TokenScope>()))
// return 'twofactor' with the validated credentials to indicate 2FAOAuth
.Returns(Task.FromResult<AuthenticationResult>(new AuthenticationResult(AuthenticationResultType.TwoFactor)));
authority
.Setup(a => a.AcquireToken(It.IsAny<TargetUri>(), It.IsAny<Credential>(), AuthenticationResultType.TwoFactor, It.IsAny<TokenScope>()))
// return 'twofactor' with the validated credentials to indicate 2FA/OAuth
.Returns(Task.FromResult<AuthenticationResult>(new AuthenticationResult(AuthenticationResultType.Success, new Token("access_token", TokenType.Personal), new Token("refresh_token", TokenType.BitbucketRefresh))));
var bbAuth = new Authentication(RuntimeContext.Default, credentialStore.Object, MockValidBasicAuthCredentialsAquireCredentialsCallback, MockValidAquireAuthenticationOAuthCallback, authority.Object);
var credentials = await bbAuth.InteractiveLogon(targetUri);
Assert.NotNull(credentials);
// attempted to validate credentials
authority.Verify(a => a.AcquireToken(targetUri, _validBasicAuthCredentials, AuthenticationResultType.None, TokenScope.SnippetWrite | TokenScope.RepositoryWrite), Times.Once);
authority.Verify(a => a.AcquireToken(targetUri, _validBasicAuthCredentials, AuthenticationResultType.TwoFactor, TokenScope.SnippetWrite | TokenScope.RepositoryWrite), Times.Once);
// valid access token + refresh token stored for the per user and per host urls so 2 x 2 calls
credentialStore.Verify(c => c.WriteCredentials(It.IsAny<TargetUri>(), It.IsAny<Credential>()), Times.Exactly(4));
}
private bool MockValidAquireAuthenticationOAuthCallback(string title, TargetUri targetUri, AuthenticationResultType resultType, string username)
{
return true;
}
private bool MockValidBasicAuthCredentialsAquireCredentialsCallback(string titleMessage, TargetUri targetUri, out string username, out string password)
{
username = _validBasicAuthCredentials.Username;
password = _validBasicAuthCredentials.Password;
return true;
}
private bool MockInvalidBasicAuthCredentialsAquireCredentialsCallback(string titleMessage, TargetUri targetUri, out string username, out string password)
{
username = _invalidUsername;
password = _invalidPassword;
return true;
}
private bool MockNoCredentialsAquireCredentialsCallback(string titleMessage, TargetUri targetUri, out string username, out string password)
{
username = null;
password = null;
return false;
}
}
public class MockCredentialStore : ICredentialStore
public class MockCredentialStore : ICredentialStore
{
public Dictionary<string, Dictionary<List<string>, int>> MethodCalls =
new Dictionary<string, Dictionary<List<string>, int>>();

Просмотреть файл

@ -0,0 +1,35 @@
using Atlassian.Bitbucket.Authentication;
using GitHub.Authentication;
using Microsoft.Alm.Authentication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Bitbucket.Authentication.Test
{
public class AuthorityTest
{
[Fact]
public async void VerifyAcquireTokenAcceptsValidAuthenticationResultTypes()
{
var context = RuntimeContext.Default;
var authority = new Authority(context);
var targetUri = new TargetUri("https://bitbucket.org");
var credentials = new Credential("a", "b");
var resultType = AuthenticationResultType.None;
var tokenScope = Atlassian.Bitbucket.Authentication.TokenScope.None;
var values = Enum.GetValues(typeof(AuthenticationResultType)).Cast<AuthenticationResultType>();
values.ToList().ForEach(async _ =>
{
var token = await authority.AcquireToken(targetUri, credentials, resultType, tokenScope);
Assert.NotNull(token);
});
}
}
}

Просмотреть файл

@ -14,14 +14,27 @@
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<NuGetPackageImportStamp/>
<NuGetPackageImportStamp />
</PropertyGroup>
<Import Project="..\..\test.props" />
<PropertyGroup>
<RunCodeAnalysis>false</RunCodeAnalysis>
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.8.0.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\..\packages\Moq.4.8.2\lib\net45\Moq.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Threading.Tasks.Extensions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll</HintPath>
</Reference>
@ -36,11 +49,16 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AuthorityTest.cs" />
<Compile Include="BitbucketAuthTests.cs" />
<Compile Include="AuthenticationTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\GitHub.Authentication\Src\GitHub.Authentication.csproj">
<Project>{CF306116-BBF0-4CC7-AFCE-A506AC4752CB}</Project>
<Name>GitHub.Authentication</Name>
</ProjectReference>
<ProjectReference Include="..\Src\Bitbucket.Authentication.csproj">
<Project>{ee663736-5bad-4ca6-a4f8-99978925ad8a}</Project>
<Name>Bitbucket.Authentication</Name>

Просмотреть файл

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Castle.Core" version="4.2.1" targetFramework="net462" />
<package id="Microsoft.Net.Compilers" version="2.8.0" targetFramework="net462" developmentDependency="true" />
<package id="Moq" version="4.8.2" targetFramework="net462" />
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net462" />
<package id="System.ValueTuple" version="4.4.0" targetFramework="net462" />
<package id="xunit" version="2.3.1" targetFramework="net452" />
<package id="xunit.abstractions" version="2.0.1" targetFramework="net452" />
<package id="xunit.analyzers" version="0.8.0" targetFramework="net462" />