Skip to content

Commit 9ec6714

Browse files
authored
feat(bitbucket): add API token authentication support and deprecate A… (#8604)
* feat(bitbucket): add API token authentication support and deprecate App passwords - Updated Bitbucket connection model to include `UsesApiToken` field for API token support. - Modified connection handling in the Bitbucket API to use API tokens. - Added migration script to update existing connections for backward compatibility. - Updated UI to reflect changes in authentication method and provide guidance on API token usage. - Updated documentation to inform users about the deprecation of App passwords. * test(bitbucket): add unit tests for API token authentication and connection handling - Introduced tests for Bitbucket connection API, validating API token and app password authentication methods. - Added tests for connection sanitization to ensure sensitive data is handled correctly. - Implemented tests for connection status code handling and deprecation warnings for app passwords. - Enhanced coverage for connection merging logic and authentication setup. Addresses #8520 * fix(lint): resolve multiple linting issues The following issues were resolved: - SA1006 in `server/api/shared/api_output.go`: Changed `fmt.Errorf` to `errors.Default.New` and removed unused `fmt` import. - gofmt in `plugins/bitbucket/models/migrationscripts/20251001_add_api_token_auth.go`: Fixed trailing blank line. - gofmt in `plugins/bitbucket/api/connection_api.go`: Corrected inconsistent tab spacing. - confusing-results in `server/services/remote/plugin/plugin_impl.go`: Added named return parameters and resolved variable shadowing. - ST1016 in `plugins/bitbucket/models/connection.go`: Standardized receiver name from `connection` to `bc`. - S1009 in `plugins/github_graphql/tasks/issue_extractor.go`: Removed redundant nil check. - superfluous-else in `impls/logruslog/init.go`: Refactored code to eliminate unnecessary else block. - superfluous-else in `helpers/srvhelper/scope_service_helper.go`: Refactored code to eliminate unnecessary else block. Fixes #8520
1 parent 794f8ef commit 9ec6714

File tree

22 files changed

+993
-102
lines changed

22 files changed

+993
-102
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ _debug_bin
121121
postgres-data/
122122
mysql-data/
123123
.docker/
124+
docker-compose.override.yml
124125

125126
# config files
126127
local.js

backend/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ all: build
2929
go-dep:
3030
go install github.com/vektra/mockery/[email protected]
3131
go install github.com/swaggo/swag/cmd/[email protected]
32-
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
32+
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4
3333

3434
go-dev-tools:
3535
# go install github.com/atombender/go-jsonschema/cmd/gojsonschema@latest

backend/helpers/srvhelper/scope_service_helper.go

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -268,39 +268,39 @@ func (scopeSrv *ScopeSrvHelper[C, S, SC]) getAffectedTables() ([]string, errors.
268268
if err != nil {
269269
return nil, err
270270
}
271-
if pluginModel, ok := meta.(plugin.PluginModel); !ok {
271+
pluginModel, ok := meta.(plugin.PluginModel)
272+
if !ok {
272273
panic(errors.Default.New(fmt.Sprintf("plugin \"%s\" does not implement listing its tables", scopeSrv.pluginName)))
273-
} else {
274-
// Unfortunately, can't cache the tables because Python creates some tables on a per-demand basis, so such a cache would possibly get outdated.
275-
// It's a rare scenario in practice, but might as well play it safe and sacrifice some performance here
276-
var allTables []string
277-
if allTables, err = scopeSrv.db.AllTables(); err != nil {
278-
return nil, err
279-
}
280-
// collect raw tables
281-
for _, table := range allTables {
282-
if strings.HasPrefix(table, "_raw_"+scopeSrv.pluginName) {
283-
tables = append(tables, table)
284-
}
274+
}
275+
// Unfortunately, can't cache the tables because Python creates some tables on a per-demand basis, so such a cache would possibly get outdated.
276+
// It's a rare scenario in practice, but might as well play it safe and sacrifice some performance here
277+
var allTables []string
278+
if allTables, err = scopeSrv.db.AllTables(); err != nil {
279+
return nil, err
280+
}
281+
// collect raw tables
282+
for _, table := range allTables {
283+
if strings.HasPrefix(table, "_raw_"+scopeSrv.pluginName) {
284+
tables = append(tables, table)
285285
}
286-
// collect tool tables
287-
toolModels := pluginModel.GetTablesInfo()
288-
for _, toolModel := range toolModels {
289-
if !isScopeModel(toolModel) && hasField(toolModel, "RawDataParams") {
290-
tables = append(tables, toolModel.TableName())
291-
}
286+
}
287+
// collect tool tables
288+
toolModels := pluginModel.GetTablesInfo()
289+
for _, toolModel := range toolModels {
290+
if !isScopeModel(toolModel) && hasField(toolModel, "RawDataParams") {
291+
tables = append(tables, toolModel.TableName())
292292
}
293-
// collect domain tables
294-
for _, domainModel := range domaininfo.GetDomainTablesInfo() {
295-
// we only care about tables with RawOrigin
296-
ok = hasField(domainModel, "RawDataParams")
297-
if ok {
298-
tables = append(tables, domainModel.TableName())
299-
}
293+
}
294+
// collect domain tables
295+
for _, domainModel := range domaininfo.GetDomainTablesInfo() {
296+
// we only care about tables with RawOrigin
297+
ok = hasField(domainModel, "RawDataParams")
298+
if ok {
299+
tables = append(tables, domainModel.TableName())
300300
}
301-
// additional tables
302-
tables = append(tables, models.CollectorLatestState{}.TableName())
303301
}
302+
// additional tables
303+
tables = append(tables, models.CollectorLatestState{}.TableName())
304304
scopeSrv.log.Debug("Discovered %d tables used by plugin \"%s\": %v", len(tables), scopeSrv.pluginName, tables)
305305
return tables, nil
306306
}

backend/impls/logruslog/init.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@ func init() {
7070
if basePath == "" {
7171
basePath = "./logs"
7272
}
73-
if abs, err := filepath.Abs(basePath); err != nil {
74-
panic(err)
75-
} else {
76-
basePath = filepath.Join(abs, "devlake.log")
73+
abs, absErr := filepath.Abs(basePath)
74+
if absErr != nil {
75+
panic(absErr)
7776
}
77+
basePath = filepath.Join(abs, "devlake.log")
7878
var err errors.Error
7979
Global, err = NewDefaultLogger(inner)
8080
if err != nil {

backend/plugins/bitbucket/api/blueprint_v200.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,13 @@ func makeDataSourcePipelinePlanV200(
120120
if err != nil {
121121
return nil, err
122122
}
123-
cloneUrl.User = url.UserPassword(connection.Username, connection.Password)
123+
// For Bitbucket API tokens, use x-token-auth as username per Bitbucket docs
124+
// https://support.atlassian.com/bitbucket-cloud/docs/using-api-tokens/
125+
gitUsername := connection.Username
126+
if connection.UsesApiToken {
127+
gitUsername = "x-bitbucket-api-token-auth"
128+
}
129+
cloneUrl.User = url.UserPassword(gitUsername, connection.Password)
124130
stage = append(stage, &coreModels.PipelineTask{
125131
Plugin: "gitextractor",
126132
Options: map[string]interface{}{

backend/plugins/bitbucket/api/connection_api.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,18 @@ func testConnection(ctx context.Context, connection models.BitbucketConn) (*BitB
5252
}
5353

5454
if res.StatusCode == http.StatusUnauthorized {
55-
return nil, errors.HttpStatus(http.StatusBadRequest).New("StatusUnauthorized error when testing connection")
55+
return nil, errors.HttpStatus(http.StatusBadRequest).New("StatusUnauthorized error when testing connection. Please check your credentials.")
5656
}
5757

5858
if res.StatusCode != http.StatusOK {
5959
return nil, errors.HttpStatus(res.StatusCode).New("unexpected status code when testing connection")
6060
}
61+
62+
// Log deprecation warning if using App Password (not API token)
63+
if !connection.UsesApiToken {
64+
basicRes.GetLogger().Warn(nil, "Bitbucket App passwords are deprecated and will be deactivated on June 9, 2026. Please migrate to API tokens.")
65+
}
66+
6167
connection = connection.Sanitize()
6268
body := BitBucketTestConnResponse{}
6369
body.Success = true

0 commit comments

Comments
 (0)