Коммит
dea7b5cb9e
|
@ -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" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче