Skip to content

Commit 262ad1a

Browse files
authored
Merge pull request #141 from itofinity/issue/support-bbs-basicauth
Update support for Bitbucket Server
2 parents 9032ca6 + 4513acb commit 262ad1a

File tree

3 files changed

+73
-7
lines changed

3 files changed

+73
-7
lines changed

.vscode/launch.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@
1616
"console": "integratedTerminal",
1717
"stopAtEntry": false,
1818
},
19+
{
20+
"name": "Git Credential Manager (store)",
21+
"type": "coreclr",
22+
"request": "launch",
23+
"preLaunchTask": "build",
24+
// If you have changed target frameworks, make sure to update the program path.
25+
"program": "${workspaceFolder}/out/shared/Git-Credential-Manager/bin/Debug/netcoreapp3.1/git-credential-manager-core.dll",
26+
"args": ["store"],
27+
"cwd": "${workspaceFolder}/out/shared/Git-Credential-Manager",
28+
"console": "integratedTerminal",
29+
"stopAtEntry": false,
30+
},
1931
{
2032
"name": ".NET Core Attach",
2133
"type": "coreclr",

docs/configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ ID|Provider
6262
`auto` _(default)_|_\[automatic\]_
6363
`azure-repos`|Azure Repos
6464
`github`|GitHub
65+
`bitbucket`|Bitbucket
6566
`generic`|Generic (any other provider not listed above)
6667

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

9698
#### Example

src/shared/Atlassian.Bitbucket/BitbucketHostProvider.cs

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,16 @@ public bool IsSupported(InputArguments input)
5151

5252
public async Task<ICredential> GetCredentialAsync(InputArguments input)
5353
{
54+
// Compute the target URI
55+
Uri targetUri = GetTargetUri(input);
56+
5457
// We should not allow unencrypted communication and should inform the user
55-
if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http"))
58+
if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http")
59+
&& !IsBitbucketServer(targetUri))
5660
{
57-
throw new Exception("Unencrypted HTTP is not supported for Bitbucket. Ensure the repository remote URL is using HTTPS.");
61+
throw new Exception("Unencrypted HTTP is not supported for Bitbucket.org. Ensure the repository remote URL is using HTTPS.");
5862
}
5963

60-
// Compute the target URI
61-
Uri targetUri = GetTargetUri(input);
62-
6364
// Try and get the username specified in the remote URL if any
6465
string targetUriUser = targetUri.GetUserName();
6566

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

187+
Uri targetUri = GetTargetUri(input);
188+
if (IsBitbucketServer(targetUri))
189+
{
190+
// BBS doesn't usually include the username in the urls which means they aren't included in the GET call,
191+
// which means if we store only with the username the credentials are never found again ...
192+
// This does have the potential to overwrite itself for different BbS accounts,
193+
// but typically BbS doesn't encourage multiple user accounts
194+
string bbsCredentialKey = GetBitbucketServerCredentialKey(input);
195+
_context.Trace.WriteLine($"Storing Bitbucket Server credential with key '{bbsCredentialKey}'...");
196+
_context.CredentialStore.AddOrUpdate(bbsCredentialKey, credential);
197+
_context.Trace.WriteLine("Bitbucket Server Credential was successfully stored.");
198+
}
199+
186200
return Task.CompletedTask;
187201
}
188202

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

223-
private async Task<bool> RequiresTwoFactorAuthenticationAsync(ICredential credentials)
237+
private async Task<bool> RequiresTwoFactorAuthenticationAsync(ICredential credentials, Uri targetUri)
224238
{
239+
if (IsBitbucketServer(targetUri))
240+
{
241+
// BBS does not support 2FA out of the box so neither does GCM
242+
return false;
243+
}
244+
225245
RestApiResult<UserInfo> result = await _bitbucketApi.GetUserInformationAsync(credentials.UserName, credentials.Password, false);
226246
switch (result.StatusCode)
227247
{
@@ -257,6 +277,21 @@ private string GetCredentialKey(InputArguments input)
257277
return $"git:{url}";
258278
}
259279

280+
private string GetBitbucketServerCredentialKey(InputArguments input)
281+
{
282+
// The credential (user/pass or an OAuth access token) key is the full target URI.
283+
// If the full path is included (credential.useHttpPath = true) then respect that.
284+
string url = GetBitbucketServerTargetUri(input).AbsoluteUri;
285+
286+
// Trim trailing slash
287+
if (url.EndsWith("/"))
288+
{
289+
url = url.Substring(0, url.Length - 1);
290+
}
291+
292+
return $"git:{url}";
293+
}
294+
260295
private string GetRefreshTokenKey(InputArguments input)
261296
{
262297
Uri targetUri = GetTargetUri(input);
@@ -297,6 +332,23 @@ private static Uri GetTargetUri(InputArguments input)
297332
return uri;
298333
}
299334

335+
private static Uri GetBitbucketServerTargetUri(InputArguments input)
336+
{
337+
Uri uri = new UriBuilder
338+
{
339+
Scheme = input.Protocol,
340+
Host = input.Host,
341+
Path = input.Path
342+
}.Uri;
343+
344+
return uri;
345+
}
346+
347+
private bool IsBitbucketServer(Uri targetUri)
348+
{
349+
return !targetUri.Host.Equals(BitbucketConstants.BitbucketBaseUrlHost);
350+
}
351+
300352
#endregion
301353

302354
public void Dispose()

0 commit comments

Comments
 (0)