Skip to content

Commit

Permalink
Merge pull request git-ecosystem#141 from itofinity/issue/support-bbs…
Browse files Browse the repository at this point in the history
…-basicauth

Update support for Bitbucket Server
  • Loading branch information
mjcheetham authored Jun 29, 2020
2 parents 9032ca6 + 4513acb commit 262ad1a
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 7 deletions.
12 changes: 12 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@
"console": "integratedTerminal",
"stopAtEntry": false,
},
{
"name": "Git Credential Manager (store)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/out/shared/Git-Credential-Manager/bin/Debug/netcoreapp3.1/git-credential-manager-core.dll",
"args": ["store"],
"cwd": "${workspaceFolder}/out/shared/Git-Credential-Manager",
"console": "integratedTerminal",
"stopAtEntry": false,
},
{
"name": ".NET Core Attach",
"type": "coreclr",
Expand Down
2 changes: 2 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ ID|Provider
`auto` _(default)_|_\[automatic\]_
`azure-repos`|Azure Repos
`github`|GitHub
`bitbucket`|Bitbucket
`generic`|Generic (any other provider not listed above)

Automatic provider selection is based on the remote URL.
Expand Down Expand Up @@ -91,6 +92,7 @@ Authority|Provider(s)
`auto` _(default)_|_\[automatic\]_
`msa`, `microsoft`, `microsoftaccount`,<br/>`aad`, `azure`, `azuredirectory`,</br>`live`, `liveconnect`, `liveid`|Azure Repos<br/>_(supports Microsoft Authentication)_
`github`|GitHub<br/>_(supports GitHub Authentication)_
`bitbucket`|Bitbucket.org<br/>_(supports Basic Authentication and OAuth)_<br/>Bitbucket Server<br/>_(supports Basic Authentication)_
`basic`, `integrated`, `windows`, `kerberos`, `ntlm`,<br/>`tfs`, `sso`|Generic<br/>_(supports Basic and Windows Integrated Authentication)_

#### Example
Expand Down
66 changes: 59 additions & 7 deletions src/shared/Atlassian.Bitbucket/BitbucketHostProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,16 @@ public bool IsSupported(InputArguments input)

public async Task<ICredential> GetCredentialAsync(InputArguments input)
{
// Compute the target URI
Uri targetUri = GetTargetUri(input);

// We should not allow unencrypted communication and should inform the user
if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http"))
if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http")
&& !IsBitbucketServer(targetUri))
{
throw new Exception("Unencrypted HTTP is not supported for Bitbucket. Ensure the repository remote URL is using HTTPS.");
throw new Exception("Unencrypted HTTP is not supported for Bitbucket.org. Ensure the repository remote URL is using HTTPS.");
}

// Compute the target URI
Uri targetUri = GetTargetUri(input);

// Try and get the username specified in the remote URL if any
string targetUriUser = targetUri.GetUserName();

Expand Down Expand Up @@ -93,7 +94,7 @@ public async Task<ICredential> GetCredentialAsync(InputArguments input)
// or we have a freshly captured user/pass. Regardless, we must check if these credentials
// pass and two-factor requirement on the account.
_context.Trace.WriteLine("Checking if two-factor requirements for stored credentials...");
bool requires2Fa = await RequiresTwoFactorAuthenticationAsync(credential);
bool requires2Fa = await RequiresTwoFactorAuthenticationAsync(credential, targetUri);
if (!requires2Fa)
{
_context.Trace.WriteLine("Two-factor requirement passed with stored credentials");
Expand Down Expand Up @@ -183,6 +184,19 @@ public Task StoreCredentialAsync(InputArguments input)
_context.CredentialStore.AddOrUpdate(credentialKey, credential);
_context.Trace.WriteLine("Credential was successfully stored.");

Uri targetUri = GetTargetUri(input);
if (IsBitbucketServer(targetUri))
{
// BBS doesn't usually include the username in the urls which means they aren't included in the GET call,
// which means if we store only with the username the credentials are never found again ...
// This does have the potential to overwrite itself for different BbS accounts,
// but typically BbS doesn't encourage multiple user accounts
string bbsCredentialKey = GetBitbucketServerCredentialKey(input);
_context.Trace.WriteLine($"Storing Bitbucket Server credential with key '{bbsCredentialKey}'...");
_context.CredentialStore.AddOrUpdate(bbsCredentialKey, credential);
_context.Trace.WriteLine("Bitbucket Server Credential was successfully stored.");
}

return Task.CompletedTask;
}

Expand Down Expand Up @@ -220,8 +234,14 @@ private async Task<string> ResolveOAuthUserNameAsync(string accessToken)
throw new Exception($"Failed to resolve username. HTTP: {result.StatusCode}");
}

private async Task<bool> RequiresTwoFactorAuthenticationAsync(ICredential credentials)
private async Task<bool> RequiresTwoFactorAuthenticationAsync(ICredential credentials, Uri targetUri)
{
if (IsBitbucketServer(targetUri))
{
// BBS does not support 2FA out of the box so neither does GCM
return false;
}

RestApiResult<UserInfo> result = await _bitbucketApi.GetUserInformationAsync(credentials.UserName, credentials.Password, false);
switch (result.StatusCode)
{
Expand Down Expand Up @@ -257,6 +277,21 @@ private string GetCredentialKey(InputArguments input)
return $"git:{url}";
}

private string GetBitbucketServerCredentialKey(InputArguments input)
{
// The credential (user/pass or an OAuth access token) key is the full target URI.
// If the full path is included (credential.useHttpPath = true) then respect that.
string url = GetBitbucketServerTargetUri(input).AbsoluteUri;

// Trim trailing slash
if (url.EndsWith("/"))
{
url = url.Substring(0, url.Length - 1);
}

return $"git:{url}";
}

private string GetRefreshTokenKey(InputArguments input)
{
Uri targetUri = GetTargetUri(input);
Expand Down Expand Up @@ -297,6 +332,23 @@ private static Uri GetTargetUri(InputArguments input)
return uri;
}

private static Uri GetBitbucketServerTargetUri(InputArguments input)
{
Uri uri = new UriBuilder
{
Scheme = input.Protocol,
Host = input.Host,
Path = input.Path
}.Uri;

return uri;
}

private bool IsBitbucketServer(Uri targetUri)
{
return !targetUri.Host.Equals(BitbucketConstants.BitbucketBaseUrlHost);
}

#endregion

public void Dispose()
Expand Down

0 comments on commit 262ad1a

Please sign in to comment.