[Backport 5.2] [bitbucket cloud] Support workspace access tokens (#58551)

Co-authored-by: Petri-Johan Last <petri.last@sourcegraph.com>
This commit is contained in:
sourcegraph-release-guild-bot 2023-11-28 16:34:06 -05:00 committed by GitHub
parent d5e7e5d5a3
commit 84feb578d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 296 additions and 20 deletions

View File

@ -38,7 +38,30 @@ All notable changes to Sourcegraph are documented in this file.
- The feature-flag `search-ranking`, which allowed to disable the improved ranking introduced in 5.1, is now deprecated and will not be read anymore. [#57468](https://github.com/sourcegraph/sourcegraph/pull/57468)
- The GitHub Proxy service is no longer required and has been removed from deployment options. [#55290](https://github.com/sourcegraph/sourcegraph/issues/55290)
## Unreleased 5.2.2
## Unreleased 5.2.4
### Added
### Fixed
- Fixed two issues in Zoekt that could cause out of memory errors during search indexing. [sourcegraph/zoekt#686](https://github.com/sourcegraph/zoekt/pull/686), [sourcegraph/zoekt#689](https://github.com/sourcegraph/zoekt/pull/689)
### Changed
### Removed
## 5.2.3
### Added
- Added configurable GraphQL query cost limitations to prevent unintended resource exhaustion. Default values are now provided and enforced, replacing the previously unlimited behaviour. For more information, please refer to: [GraphQL Cost Limits Documentation](https://docs.sourcegraph.com/api/graphql#cost-limits). See details at [#58346](https://github.com/sourcegraph/sourcegraph/pull/58346).
- Sourcegraph now supports connecting to Bitbucket Cloud using Workspace Access Tokens. [#58465](https://github.com/sourcegraph/sourcegraph/pull/58465).
### Fixed
- Defining file filters for embeddings jobs no longer causes all files to be skipped if `MaxFileSizeBytes` isn't defined. [#58262](https://github.com/sourcegraph/sourcegraph/pull/58262)
## 5.2.2
### Added

View File

@ -41,7 +41,7 @@ func TestAddRepoToExclude(t *testing.T) {
kind: extsvc.KindBitbucketCloud,
repo: makeBitbucketCloudRepo(),
initialConfig: `{"appPassword":"","url":"https://bitbucket.org","username":""}`,
expectedConfig: `{"appPassword":"","exclude":[{"name":"sg/sourcegraph"}],"url":"https://bitbucket.org","username":""}`,
expectedConfig: `{"exclude":[{"name":"sg/sourcegraph"}],"url":"https://bitbucket.org"}`,
},
{
name: "second attempt of excluding same repo is ignored for BitbucketServer schema",

View File

@ -18,6 +18,49 @@ In addition, there is one more field for configuring which repositories are mirr
- [`teams`](bitbucket_cloud.md#configuration)<br>A list of teams (workspaces) that the configured user has access to whose repositories should be synced.
- [`exclude`](bitbucket_cloud.md#configuration)<br>A list of repositories to exclude, which takes precedence over the `teams` field.
## Configuration options
Bitbucket Cloud code host connections can be configured with either a username and app password combination, or with workspace access tokens.
### Username and app password
1. Visit your [Bitbucket account settings page](https://bitbucket.org/account/settings).
2. Navigate to **App passwords**.
3. Select **Create app password**.
4. Give your app password a label.
5. Select the `Projects: Read` permission. `Repositories: Read` should automatically be selected.
6. Press **Create**.
Use the newly created app password and your username to configure the Bitbucket Cloud connection:
```json
{
"url": "https://bitbucket.org",
"username": "USERNAME",
"appPassword": "<PASSWORD>",
// ... other settings
}
```
### Workspace access token
1. Visit the Bitbucket Cloud workspace settings page of the workspace you want to create an access token for.
2. Navigate to **Security > Access tokens**.
3. Press **Create workspace access token**.
4. Give your access token a name.
5. Select the `Projects: Read` permission. `Repositories: Read` should automatically be selected.
6. Press **Create**.
Use the newly created access token to configure the Bitbucket Cloud connection:
```json
{
"url": "https://bitbucket.org",
"accessToken": "ACCESS_TOKEN",
// ... other settings
}
```
### HTTPS cloning
Sourcegraph clones repositories from your Bitbucket Cloud via HTTP(S), using the [`username`](bitbucket_cloud.md#configuration) and [`appPassword`](bitbucket_cloud.md#configuration) required fields you provide in the configuration.

View File

@ -1100,6 +1100,16 @@ func TestValidateExternalServiceConfig(t *testing.T) {
}`,
assert: equals("<nil>"),
},
{
kind: extsvc.KindBitbucketCloud,
desc: "valid with url, accessToken",
config: `
{
"url": "https://bitbucket.org/",
"accessToken": "access-token"
}`,
assert: equals("<nil>"),
},
{
kind: extsvc.KindBitbucketCloud,
desc: "valid with url, username, appPassword, teams",
@ -1114,12 +1124,10 @@ func TestValidateExternalServiceConfig(t *testing.T) {
},
{
kind: extsvc.KindBitbucketCloud,
desc: "without url, username nor appPassword",
desc: "without url",
config: `{}`,
assert: includes(
"url is required",
"username is required",
"appPassword is required",
),
},
{

View File

@ -23,9 +23,7 @@ type BitbucketCloudSource struct {
client bitbucketcloud.Client
}
var (
_ ForkableChangesetSource = BitbucketCloudSource{}
)
var _ ForkableChangesetSource = BitbucketCloudSource{}
func NewBitbucketCloudSource(ctx context.Context, svc *types.ExternalService, cf *httpcli.Factory) (*BitbucketCloudSource, error) {
rawConfig, err := svc.Config.Decrypt(ctx)

View File

@ -103,13 +103,20 @@ func newClient(urn string, config *schema.BitbucketCloudConnection, httpClient h
return nil, err
}
var auther auth.Authenticator
if config.AccessToken != "" {
auther = &auth.OAuthBearerToken{Token: config.AccessToken}
} else {
auther = &auth.BasicAuth{
Username: config.Username,
Password: config.AppPassword,
}
}
return &client{
httpClient: httpClient,
URL: extsvc.NormalizeBaseURL(apiURL),
Auth: &auth.BasicAuth{
Username: config.Username,
Password: config.AppPassword,
},
Auth: auther,
// Default limits are defined in extsvc.GetLimitFromConfig
rateLimit: ratelimit.NewInstrumentedLimiter(urn, ratelimit.NewGlobalRateLimiter(log.Scoped("BitbucketCloudClient", ""), urn)),
}, nil

View File

@ -238,7 +238,6 @@ func (s *BitbucketCloudSource) WithAuthenticator(a auth.Authenticator) (Source,
sc.client = sc.client.WithAuthenticator(a)
return &sc, nil
}
// ValidateAuthenticator validates the currently set authenticator is usable.

View File

@ -79,6 +79,20 @@ func TestBitbucketCloudSource_ListRepos(t *testing.T) {
},
err: "<nil>",
},
{
name: "with access token",
assert: assertAllReposListed([]string{
"/sourcegraph-source/src-cli",
"/sourcegraph-source/source-test",
}),
conf: &schema.BitbucketCloudConnection{
AccessToken: os.Getenv("BITBUCKET_CLOUD_ACCESS_TOKEN"),
Teams: []string{
"sourcegraph-source",
},
},
err: "<nil>",
},
}
for _, tc := range testCases {

View File

@ -163,7 +163,7 @@ func bitbucketServerCloneURL(repo *bitbucketserver.Repo, cfg *schema.BitbucketSe
}
// bitbucketCloudCloneURL returns the repository's Git remote URL with the configured
// Bitbucket Cloud app password inserted in the URL userinfo.
// Bitbucket Cloud app password or workspace access token inserted in the URL userinfo.
func bitbucketCloudCloneURL(logger log.Logger, repo *bitbucketcloud.Repo, cfg *schema.BitbucketCloudConnection) string {
if cfg.GitURLType == "ssh" {
return fmt.Sprintf("git@%s:%s.git", cfg.Url, repo.FullName)
@ -186,7 +186,11 @@ func bitbucketCloudCloneURL(logger log.Logger, repo *bitbucketcloud.Repo, cfg *s
return fallbackURL
}
u.User = url.UserPassword(cfg.Username, cfg.AppPassword)
if cfg.AccessToken != "" {
u.User = url.UserPassword("x-token-auth", cfg.AccessToken)
} else {
u.User = url.UserPassword(cfg.Username, cfg.AppPassword)
}
return u.String()
}

View File

@ -0,0 +1,143 @@
---
version: 1
interactions:
- request:
body: ""
form: {}
headers: {}
url: https://api.bitbucket.org/2.0/repositories/sourcegraph-source?pagelen=100
method: GET
response:
body: '{"values": [{"type": "repository", "full_name": "sourcegraph-source/src-cli",
"links": {"self": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/src-cli"},
"html": {"href": "https://bitbucket.org/sourcegraph-source/src-cli"}, "avatar":
{"href": "https://bytebucket.org/ravatar/%7B22bdacfc-eae7-4dcb-81c5-af096a93bd9b%7D?ts=default"},
"pullrequests": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/src-cli/pullrequests"},
"commits": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/src-cli/commits"},
"forks": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/src-cli/forks"},
"watchers": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/src-cli/watchers"},
"branches": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/src-cli/refs/branches"},
"tags": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/src-cli/refs/tags"},
"downloads": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/src-cli/downloads"},
"source": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/src-cli/src"},
"clone": [{"name": "https", "href": "https://2h1ehnddx9o2mwnng8plaqp56mb7aq@bitbucket.org/sourcegraph-source/src-cli.git"},
{"name": "ssh", "href": "git@bitbucket.org:sourcegraph-source/src-cli.git"}],
"hooks": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/src-cli/hooks"}},
"name": "src-cli", "slug": "src-cli", "description": "", "scm": "git", "website":
null, "owner": {"display_name": "sourcegraph-source", "links": {"self": {"href":
"https://api.bitbucket.org/2.0/workspaces/%7B790592f7-4e5e-44a8-9ec6-8745876fe1c3%7D"},
"avatar": {"href": "https://bitbucket.org/account/sourcegraph-source/avatar/"},
"html": {"href": "https://bitbucket.org/%7B790592f7-4e5e-44a8-9ec6-8745876fe1c3%7D/"}},
"type": "team", "uuid": "{790592f7-4e5e-44a8-9ec6-8745876fe1c3}", "username":
"sourcegraph-source"}, "workspace": {"type": "workspace", "uuid": "{790592f7-4e5e-44a8-9ec6-8745876fe1c3}",
"name": "sourcegraph-source", "slug": "sourcegraph-source", "links": {"avatar":
{"href": "https://bitbucket.org/workspaces/sourcegraph-source/avatar/?ts=1700076275"},
"html": {"href": "https://bitbucket.org/sourcegraph-source/"}, "self": {"href":
"https://api.bitbucket.org/2.0/workspaces/sourcegraph-source"}}}, "is_private":
true, "project": {"type": "project", "key": "SOUR", "uuid": "{d4cb2804-9d30-4b8d-b880-8097b111375a}",
"name": "source", "links": {"self": {"href": "https://api.bitbucket.org/2.0/workspaces/sourcegraph-source/projects/SOUR"},
"html": {"href": "https://bitbucket.org/sourcegraph-source/workspace/projects/SOUR"},
"avatar": {"href": "https://bitbucket.org/account/user/sourcegraph-source/projects/SOUR/avatar/32?ts=1700076320"}}},
"fork_policy": "no_public_forks", "created_on": "2023-11-15T19:25:20.076854+00:00",
"updated_on": "2023-11-15T19:25:20.911042+00:00", "size": 22185443, "language":
"", "uuid": "{22bdacfc-eae7-4dcb-81c5-af096a93bd9b}", "mainbranch": {"name":
"master", "type": "branch"}, "override_settings": {"default_merge_strategy":
true, "branching_model": true}, "parent": null, "has_issues": false, "has_wiki":
false}, {"type": "repository", "full_name": "sourcegraph-source/source-test",
"links": {"self": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/source-test"},
"html": {"href": "https://bitbucket.org/sourcegraph-source/source-test"}, "avatar":
{"href": "https://bytebucket.org/ravatar/%7Bdfeaae25-8168-466f-ade4-d07e6837dedf%7D?ts=default"},
"pullrequests": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/source-test/pullrequests"},
"commits": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/source-test/commits"},
"forks": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/source-test/forks"},
"watchers": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/source-test/watchers"},
"branches": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/source-test/refs/branches"},
"tags": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/source-test/refs/tags"},
"downloads": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/source-test/downloads"},
"source": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/source-test/src"},
"clone": [{"name": "https", "href": "https://2h1ehnddx9o2mwnng8plaqp56mb7aq@bitbucket.org/sourcegraph-source/source-test.git"},
{"name": "ssh", "href": "git@bitbucket.org:sourcegraph-source/source-test.git"}],
"hooks": {"href": "https://api.bitbucket.org/2.0/repositories/sourcegraph-source/source-test/hooks"}},
"name": "source-test", "slug": "source-test", "description": "", "scm": "git",
"website": null, "owner": {"display_name": "sourcegraph-source", "links": {"self":
{"href": "https://api.bitbucket.org/2.0/workspaces/%7B790592f7-4e5e-44a8-9ec6-8745876fe1c3%7D"},
"avatar": {"href": "https://bitbucket.org/account/sourcegraph-source/avatar/"},
"html": {"href": "https://bitbucket.org/%7B790592f7-4e5e-44a8-9ec6-8745876fe1c3%7D/"}},
"type": "team", "uuid": "{790592f7-4e5e-44a8-9ec6-8745876fe1c3}", "username":
"sourcegraph-source"}, "workspace": {"type": "workspace", "uuid": "{790592f7-4e5e-44a8-9ec6-8745876fe1c3}",
"name": "sourcegraph-source", "slug": "sourcegraph-source", "links": {"avatar":
{"href": "https://bitbucket.org/workspaces/sourcegraph-source/avatar/?ts=1700076275"},
"html": {"href": "https://bitbucket.org/sourcegraph-source/"}, "self": {"href":
"https://api.bitbucket.org/2.0/workspaces/sourcegraph-source"}}}, "is_private":
true, "project": {"type": "project", "key": "SOUR", "uuid": "{d4cb2804-9d30-4b8d-b880-8097b111375a}",
"name": "source", "links": {"self": {"href": "https://api.bitbucket.org/2.0/workspaces/sourcegraph-source/projects/SOUR"},
"html": {"href": "https://bitbucket.org/sourcegraph-source/workspace/projects/SOUR"},
"avatar": {"href": "https://bitbucket.org/account/user/sourcegraph-source/projects/SOUR/avatar/32?ts=1700076320"}}},
"fork_policy": "no_public_forks", "created_on": "2023-11-21T13:39:54.600952+00:00",
"updated_on": "2023-11-21T13:39:57.299319+00:00", "size": 54954, "language":
"", "uuid": "{dfeaae25-8168-466f-ade4-d07e6837dedf}", "mainbranch": {"name":
"master", "type": "branch"}, "override_settings": {"default_merge_strategy":
true, "branching_model": true}, "parent": null, "has_issues": false, "has_wiki":
false}], "pagelen": 100, "size": 2, "page": 1}'
headers:
Cache-Control:
- private
Content-Type:
- application/json; charset=utf-8
Date:
- Tue, 21 Nov 2023 14:28:09 GMT
Etag:
- '"81bd5365c10a63e8359344c6deaad885"'
Server:
- envoy
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Vary:
- Authorization, Origin, cookie, user-context
X-Asap-Succeeded:
- "True"
X-B3-Spanid:
- 57e688568582f1b8
X-B3-Traceid:
- 1d016bef27946a4e
X-Content-Type-Options:
- nosniff
X-Credential-Type:
- workspace_access_token
X-Dc-Location:
- Micros-3
X-Envoy-Upstream-Service-Time:
- "250"
X-Frame-Options:
- SAMEORIGIN
X-Render-Time:
- "0.23119592666625977"
X-Request-Count:
- "1601"
X-Request-Id:
- 1d016bef27946a4e
X-Served-By:
- 3ce7dc13e690
X-Static-Version:
- ba8afb91e693
X-Trace-Id:
- 1d016bef27946a4e
X-Usage-Input-Ops:
- "0"
X-Usage-Output-Ops:
- "0"
X-Usage-System-Time:
- "0.004212"
X-Usage-User-Time:
- "0.125926"
X-Used-Mesh:
- "False"
X-Version:
- ba8afb91e693
X-View-Name:
- bitbucket.apps.repo2.api.v20.repo.RepositoriesHandler
X-Xss-Protection:
- 1; mode=block
status: 200 OK
code: 200
duration: ""

View File

@ -66,6 +66,7 @@ func (e *ExternalService) RedactedConfig(ctx context.Context) (string, error) {
es.redactString(c.Token, "token")
case *schema.BitbucketCloudConnection:
es.redactString(c.AppPassword, "appPassword")
es.redactString(c.AccessToken, "accessToken")
case *schema.AWSCodeCommitConnection:
es.redactString(c.SecretAccessKey, "secretAccessKey")
es.redactString(c.GitCredentials.Password, "gitCredentials", "password")
@ -184,10 +185,21 @@ func (e *ExternalService) UnredactConfig(ctx context.Context, old *ExternalServi
es.unredactString(c.Token, o.Token, "token")
case *schema.BitbucketCloudConnection:
o := oldCfg.(*schema.BitbucketCloudConnection)
es.unredactString(c.AppPassword, o.AppPassword, "appPassword")
if c.Url != o.Url {
return errCodeHostIdentityChanged{"apiUrl", "appPassword"}
var redactedProperty string
if c.AppPassword == RedactedSecret {
redactedProperty = "appPassword"
}
if c.AccessToken == RedactedSecret {
redactedProperty = "accessToken"
}
if redactedProperty != "" {
return errCodeHostIdentityChanged{"apiUrl", redactedProperty}
}
}
es.unredactString(c.AppPassword, o.AppPassword, "appPassword")
es.unredactString(c.AccessToken, o.AccessToken, "accessToken")
case *schema.AWSCodeCommitConnection:
o := oldCfg.(*schema.AWSCodeCommitConnection)
es.unredactString(c.SecretAccessKey, o.SecretAccessKey, "secretAccessKey")

View File

@ -6,7 +6,26 @@
"allowComments": true,
"type": "object",
"additionalProperties": false,
"required": ["url", "username", "appPassword"],
"required": ["url"],
"oneOf": [
{
"allOf": [
{
"required": ["accessToken"]
},
{
"not": { "required": ["username"] }
},
{
"not": { "required": ["appPassword"] }
}
]
},
{
"required": ["username", "appPassword"],
"not": { "required": ["accessToken"] }
}
],
"properties": {
"url": {
"description": "URL of Bitbucket Cloud, such as https://bitbucket.org. Generally, admin should not modify the value of this option because Bitbucket Cloud is a public hosting platform.",
@ -72,6 +91,10 @@
"description": "The app password to use when authenticating to the Bitbucket Cloud. Also set the corresponding \"username\" field.",
"type": "string"
},
"accessToken": {
"description": "The workspace access token to use when authenticating with Bitbucket Cloud.",
"type": "string"
},
"gitURLType": {
"description": "The type of Git URLs to use for cloning and fetching Git repositories on this Bitbucket Cloud.\n\nIf \"http\", Sourcegraph will access Bitbucket Cloud repositories using Git URLs of the form https://bitbucket.org/myteam/myproject.git.\n\nIf \"ssh\", Sourcegraph will access Bitbucket Cloud repositories using Git URLs of the form git@bitbucket.org:myteam/myproject.git. See the documentation for how to provide SSH private keys and known_hosts: https://docs.sourcegraph.com/admin/repo/auth#repositories-that-need-http-s-or-ssh-authentication.",
"type": "string",

View File

@ -301,10 +301,12 @@ type BitbucketCloudAuthorization struct {
// BitbucketCloudConnection description: Configuration for a connection to Bitbucket Cloud.
type BitbucketCloudConnection struct {
// AccessToken description: The workspace access token to use when authenticating with Bitbucket Cloud.
AccessToken string `json:"accessToken,omitempty"`
// ApiURL description: The API URL of Bitbucket Cloud, such as https://api.bitbucket.org. Generally, admin should not modify the value of this option because Bitbucket Cloud is a public hosting platform.
ApiURL string `json:"apiURL,omitempty"`
// AppPassword description: The app password to use when authenticating to the Bitbucket Cloud. Also set the corresponding "username" field.
AppPassword string `json:"appPassword"`
AppPassword string `json:"appPassword,omitempty"`
// Authorization description: If non-null, enforces Bitbucket Cloud repository permissions. This requires that there is an item in the [site configuration json](https://docs.sourcegraph.com/admin/config/site_config#auth-providers) `auth.providers` field, of type "bitbucketcloud" with the same `url` field as specified in this `BitbucketCloudConnection`.
Authorization *BitbucketCloudAuthorization `json:"authorization,omitempty"`
// Exclude description: A list of repositories to never mirror from Bitbucket Cloud. Takes precedence over "teams" configuration.
@ -332,7 +334,7 @@ type BitbucketCloudConnection struct {
// Url description: URL of Bitbucket Cloud, such as https://bitbucket.org. Generally, admin should not modify the value of this option because Bitbucket Cloud is a public hosting platform.
Url string `json:"url"`
// Username description: The username to use when authenticating to the Bitbucket Cloud. Also set the corresponding "appPassword" field.
Username string `json:"username"`
Username string `json:"username,omitempty"`
// WebhookSecret description: A shared secret used to authenticate incoming webhooks (minimum 12 characters).
WebhookSecret string `json:"webhookSecret,omitempty"`
}