From 9c479dbdde23ac0c23729ca2fd11938a20cd2fb3 Mon Sep 17 00:00:00 2001 From: Linda Chen Date: Tue, 12 Nov 2024 16:19:56 -0500 Subject: [PATCH 1/4] add support for bitbucket workspace token authentication --- cmd/other.go | 4 ++- cmd/platform.go | 6 ++-- docs/README.template.md | 16 ++++++--- .../scm/bitbucketcloud/bitbucket_cloud.go | 35 +++++++++++++++---- 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/cmd/other.go b/cmd/other.go index 6f1041fb..05e7f543 100644 --- a/cmd/other.go +++ b/cmd/other.go @@ -35,11 +35,13 @@ func getToken(flag *flag.FlagSet) (string, error) { token = ght } else if ght := os.Getenv("BITBUCKET_CLOUD_APP_PASSWORD"); ght != "" { token = ght + } else if ght := os.Getenv("BITBUCKET_CLOUD_WORKSPACE_TOKEN"); ght != "" { + token = ght } } if token == "" { - return "", errors.New("either the --token flag or the GITHUB_TOKEN/GITLAB_TOKEN/GITEA_TOKEN/BITBUCKET_SERVER_TOKEN/BITBUCKET_CLOUD_APP_PASSWORD environment variable has to be set") + return "", errors.New("either the --token flag or the GITHUB_TOKEN/GITLAB_TOKEN/GITEA_TOKEN/BITBUCKET_SERVER_TOKEN/BITBUCKET_CLOUD_APP_PASSWORD/BITBUCKET_CLOUD_WORKSPACE_TOKEN environment variable has to be set") } return token, nil diff --git a/cmd/platform.go b/cmd/platform.go index 9f6d1df9..1fc6984b 100644 --- a/cmd/platform.go +++ b/cmd/platform.go @@ -23,7 +23,8 @@ func configurePlatform(cmd *cobra.Command) { flags.StringP("base-url", "g", "", "Base URL of the target platform, needs to be changed for GitHub enterprise, a self-hosted GitLab instance, Gitea or BitBucket.") flags.BoolP("insecure", "", false, "Insecure controls whether a client verifies the server certificate chain and host name. Used only for Bitbucket server.") flags.StringP("username", "u", "", "The Bitbucket server username.") - flags.StringP("token", "T", "", "The personal access token for the targeting platform. Can also be set using the GITHUB_TOKEN/GITLAB_TOKEN/GITEA_TOKEN/BITBUCKET_SERVER_TOKEN/BITBUCKET_CLOUD_APP_PASSWORD environment variable.") + flags.StringP("token", "T", "", "The personal access token for the targeting platform. Can also be set using the GITHUB_TOKEN/GITLAB_TOKEN/GITEA_TOKEN/BITBUCKET_SERVER_TOKEN/BITBUCKET_CLOUD_APP_PASSWORD/BITBUCKET_CLOUD_WORKSPACE_TOKEN environment variable.") + flags.StringP("authtype", "", "", "The authentication type. Either app-password or workspace-token. Used only for Bitbucket cloud.") flags.StringSliceP("org", "O", nil, "The name of a GitHub organization. All repositories in that organization will be used.") flags.StringSliceP("group", "G", nil, "The name of a GitLab organization. All repositories in that group will be used.") @@ -294,6 +295,7 @@ func createBitbucketCloudClient(flag *flag.FlagSet, verifyFlags bool) (multigitt sshAuth, _ := flag.GetBool("ssh-auth") fork, _ := flag.GetBool("fork") newOwner, _ := flag.GetString("fork-owner") + authType, _ := flag.GetString("authtype") if verifyFlags && len(workspaces) == 0 && len(users) == 0 && len(repos) == 0 { return nil, errors.New("no workspace, user or repository set") @@ -308,7 +310,7 @@ func createBitbucketCloudClient(flag *flag.FlagSet, verifyFlags bool) (multigitt return nil, err } - vc, err := bitbucketcloud.New(username, token, repos, workspaces, users, fork, sshAuth, newOwner) + vc, err := bitbucketcloud.New(username, token, repos, workspaces, users, fork, sshAuth, newOwner, authType) if err != nil { return nil, err } diff --git a/docs/README.template.md b/docs/README.template.md index 23a3072e..5480d561 100755 --- a/docs/README.template.md +++ b/docs/README.template.md @@ -141,15 +141,21 @@ Do you have a nice script that might be useful to others? Please create a PR tha _note: bitbucket cloud support is currently in Beta_ -In order to use bitbucket cloud you will need to create and use an [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/). The app password you create needs sufficient permissions so ensure you grant it Read and Write access to projects, repositories and pull requests and at least Read access to your account and workspace membership. +In order to use bitbucket cloud you will need to create and use an [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/) or [Workspace Token](https://support.atlassian.com/bitbucket-cloud/docs/access-tokens/). The app password or workspace token you create needs sufficient permissions so ensure you grant it Read and Write access to projects, repositories and pull requests and at least Read access to your account and workspace membership. -You will need to configure the bitbucket workspace using the `org` option for multi-gitter for the repositories you want to make changes to e.g. `multi-gitter run examples/go/upgrade-go-version.sh -u your_username --org "your_workspace"` +You will need to configure the bitbucket workspace using the `org` option for multi-gitter for the repositories you want to make changes to. You will also need to configure the authentication method using the `authtype` flag e.g. `multi-gitter run examples/go/upgrade-go-version.sh -u your_username --org "your_workspace" --authtype app-password` ### Example -Here is an example of using the command line options to run a script from the `examples/` directory and make pull-requests for a few repositories in a specified workspace. +Here is an example of using the command line options to run a script from the `examples/` directory and make pull-requests for a few repositories in a specified workspace, using app password authentication. ```shell export BITBUCKET_CLOUD_APP_PASSWORD="your_app_password" -multi-gitter run examples/go/upgrade-go-version.sh -u your_username --org "your_workspace" --repo "your_first_repository,your_second_repository" --platform bitbucket_cloud -m "your_commit_message" -B your_branch_name +multi-gitter run examples/go/upgrade-go-version.sh -u your_username --org "your_workspace" --repo "your_first_repository,your_second_repository" --platform bitbucket_cloud -m "your_commit_message" -B your_branch_name --authtype app-password +``` + +Here is an example of running the script using workspace token authentication. +```shell +export BITBUCKET_CLOUD_WORKSPACE_TOKEN="your_workspace_token" +multi-gitter run examples/go/upgrade-go-version.sh -u your_username --org "your_workspace" --repo "your_first_repository,your_second_repository" --platform bitbucket_cloud -m "your_commit_message" -B your_branch_name --authtype workspace-token ``` ### Bitbucket Cloud Limitations @@ -165,4 +171,4 @@ We also only support modifying a single workspace, any additional workspaces pas We also have noticed the performance is slower with larger workspaces and we expect to resolve this when we add support for projects to make filtering repositories by project faster. - \ No newline at end of file + diff --git a/internal/scm/bitbucketcloud/bitbucket_cloud.go b/internal/scm/bitbucketcloud/bitbucket_cloud.go index 35628c68..1f84df60 100644 --- a/internal/scm/bitbucketcloud/bitbucket_cloud.go +++ b/internal/scm/bitbucketcloud/bitbucket_cloud.go @@ -25,12 +25,13 @@ type BitbucketCloud struct { token string sshAuth bool newOwner string + authType string httpClient *http.Client bbClient *bitbucket.Client } func New(username string, token string, repositories []string, workspaces []string, users []string, fork bool, sshAuth bool, - newOwner string) (*BitbucketCloud, error) { + newOwner string, authType string) (*BitbucketCloud, error) { if strings.TrimSpace(token) == "" { return nil, errors.New("bearer token is empty") } @@ -44,10 +45,20 @@ func New(username string, token string, repositories []string, workspaces []stri bitbucketCloud.token = token bitbucketCloud.sshAuth = sshAuth bitbucketCloud.newOwner = newOwner + bitbucketCloud.authType = authType bitbucketCloud.httpClient = &http.Client{ Transport: internalHTTP.LoggingRoundTripper{}, } - bitbucketCloud.bbClient = bitbucket.NewBasicAuth(username, token) + + if authType == "app-password" { + // Authenticate using app password + bitbucketCloud.bbClient = bitbucket.NewBasicAuth(username, token) + } else if authType == "workspace-token" { + // Authenticate using workspace token + bitbucketCloud.bbClient = bitbucket.NewOAuthbearerToken(token) + } else { + return nil, errors.New("Please enter a valid value for authtype (app-password or workspace-token)") + } return bitbucketCloud, nil } @@ -59,16 +70,21 @@ func (bbc *BitbucketCloud) CreatePullRequest(_ context.Context, _ scm.Repository Owner: bbc.workspaces[0], RepoSlug: bbcRepo.name, } - currentUser, err := bbc.bbClient.User.Profile() - if err != nil { - return nil, err + var currentUserUUID string + if bbc.authType == "app-password" { + currentUser, err := bbc.bbClient.User.Profile() + if err != nil { + return nil, err + } + currentUserUUID = currentUser.Uuid } + defaultReviewers, err := bbc.bbClient.Repositories.Repository.ListEffectiveDefaultReviewers(repoOptions) if err != nil { return nil, err } for _, reviewer := range defaultReviewers.EffectiveDefaultReviewers { - if currentUser.Uuid != reviewer.User.Uuid { + if currentUserUUID != reviewer.User.Uuid { newPR.Reviewers = append(newPR.Reviewers, reviewer.User.Uuid) } } @@ -351,7 +367,12 @@ func (bbc *BitbucketCloud) convertRepository(repo bitbucket.Repository) (*reposi return nil, err } - parsedURL.User = url.UserPassword(bbc.username, bbc.token) + if bbc.authType == "app-password" { + parsedURL.User = url.UserPassword(bbc.username, bbc.token) + } else if bbc.authType == "workspace-token" { + parsedURL.User = url.UserPassword("x-token-auth", bbc.token) + } + cloneURL = parsedURL.String() } From be03b2956ae65358f349fdab7d9d2cb26b6ded53 Mon Sep 17 00:00:00 2001 From: Linda Chen Date: Fri, 15 Nov 2024 13:39:33 -0500 Subject: [PATCH 2/4] updates to authtype flag --- cmd/platform.go | 16 +++++++- docs/README.template.md | 6 +-- .../scm/bitbucketcloud/bitbucket_cloud.go | 38 ++++++++++++++----- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/cmd/platform.go b/cmd/platform.go index 1fc6984b..c983782c 100644 --- a/cmd/platform.go +++ b/cmd/platform.go @@ -24,7 +24,14 @@ func configurePlatform(cmd *cobra.Command) { flags.BoolP("insecure", "", false, "Insecure controls whether a client verifies the server certificate chain and host name. Used only for Bitbucket server.") flags.StringP("username", "u", "", "The Bitbucket server username.") flags.StringP("token", "T", "", "The personal access token for the targeting platform. Can also be set using the GITHUB_TOKEN/GITLAB_TOKEN/GITEA_TOKEN/BITBUCKET_SERVER_TOKEN/BITBUCKET_CLOUD_APP_PASSWORD/BITBUCKET_CLOUD_WORKSPACE_TOKEN environment variable.") - flags.StringP("authtype", "", "", "The authentication type. Either app-password or workspace-token. Used only for Bitbucket cloud.") + flags.StringP("auth-type", "", "app-password", `The authentication type. Used only for Bitbucket cloud. +Available values: + app-password: authenticate using an app password + workspace-token: authenticate using a workspace token + `) + _ = cmd.RegisterFlagCompletionFunc("auth-type", func(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{"app-password", "workspace-token"}, cobra.ShellCompDirectiveNoFileComp + }) flags.StringSliceP("org", "O", nil, "The name of a GitHub organization. All repositories in that organization will be used.") flags.StringSliceP("group", "G", nil, "The name of a GitLab organization. All repositories in that group will be used.") @@ -295,7 +302,7 @@ func createBitbucketCloudClient(flag *flag.FlagSet, verifyFlags bool) (multigitt sshAuth, _ := flag.GetBool("ssh-auth") fork, _ := flag.GetBool("fork") newOwner, _ := flag.GetString("fork-owner") - authType, _ := flag.GetString("authtype") + authTypeStr, _ := flag.GetString("auth-type") if verifyFlags && len(workspaces) == 0 && len(users) == 0 && len(repos) == 0 { return nil, errors.New("no workspace, user or repository set") @@ -310,6 +317,11 @@ func createBitbucketCloudClient(flag *flag.FlagSet, verifyFlags bool) (multigitt return nil, err } + authType, err := bitbucketcloud.ParseAuthType(authTypeStr) + if err != nil { + return nil, err + } + vc, err := bitbucketcloud.New(username, token, repos, workspaces, users, fork, sshAuth, newOwner, authType) if err != nil { return nil, err diff --git a/docs/README.template.md b/docs/README.template.md index 5480d561..de27fd9a 100755 --- a/docs/README.template.md +++ b/docs/README.template.md @@ -143,19 +143,19 @@ _note: bitbucket cloud support is currently in Beta_ In order to use bitbucket cloud you will need to create and use an [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/) or [Workspace Token](https://support.atlassian.com/bitbucket-cloud/docs/access-tokens/). The app password or workspace token you create needs sufficient permissions so ensure you grant it Read and Write access to projects, repositories and pull requests and at least Read access to your account and workspace membership. -You will need to configure the bitbucket workspace using the `org` option for multi-gitter for the repositories you want to make changes to. You will also need to configure the authentication method using the `authtype` flag e.g. `multi-gitter run examples/go/upgrade-go-version.sh -u your_username --org "your_workspace" --authtype app-password` +You will need to configure the bitbucket workspace using the `org` option for multi-gitter for the repositories you want to make changes to. You will also need to configure the authentication method using the `auth-type` flag e.g. `multi-gitter run examples/go/upgrade-go-version.sh -u your_username --org "your_workspace" --auth-type app-password` ### Example Here is an example of using the command line options to run a script from the `examples/` directory and make pull-requests for a few repositories in a specified workspace, using app password authentication. ```shell export BITBUCKET_CLOUD_APP_PASSWORD="your_app_password" -multi-gitter run examples/go/upgrade-go-version.sh -u your_username --org "your_workspace" --repo "your_first_repository,your_second_repository" --platform bitbucket_cloud -m "your_commit_message" -B your_branch_name --authtype app-password +multi-gitter run examples/go/upgrade-go-version.sh -u your_username --org "your_workspace" --repo "your_first_repository,your_second_repository" --platform bitbucket_cloud -m "your_commit_message" -B your_branch_name --auth-type app-password ``` Here is an example of running the script using workspace token authentication. ```shell export BITBUCKET_CLOUD_WORKSPACE_TOKEN="your_workspace_token" -multi-gitter run examples/go/upgrade-go-version.sh -u your_username --org "your_workspace" --repo "your_first_repository,your_second_repository" --platform bitbucket_cloud -m "your_commit_message" -B your_branch_name --authtype workspace-token +multi-gitter run examples/go/upgrade-go-version.sh -u your_username --org "your_workspace" --repo "your_first_repository,your_second_repository" --platform bitbucket_cloud -m "your_commit_message" -B your_branch_name --auth-type workspace-token ``` ### Bitbucket Cloud Limitations diff --git a/internal/scm/bitbucketcloud/bitbucket_cloud.go b/internal/scm/bitbucketcloud/bitbucket_cloud.go index 1f84df60..09c683bf 100644 --- a/internal/scm/bitbucketcloud/bitbucket_cloud.go +++ b/internal/scm/bitbucketcloud/bitbucket_cloud.go @@ -25,13 +25,13 @@ type BitbucketCloud struct { token string sshAuth bool newOwner string - authType string + authType AuthType httpClient *http.Client bbClient *bitbucket.Client } func New(username string, token string, repositories []string, workspaces []string, users []string, fork bool, sshAuth bool, - newOwner string, authType string) (*BitbucketCloud, error) { + newOwner string, authType AuthType) (*BitbucketCloud, error) { if strings.TrimSpace(token) == "" { return nil, errors.New("bearer token is empty") } @@ -50,14 +50,12 @@ func New(username string, token string, repositories []string, workspaces []stri Transport: internalHTTP.LoggingRoundTripper{}, } - if authType == "app-password" { + if authType == AuthTypeAppPassword { // Authenticate using app password bitbucketCloud.bbClient = bitbucket.NewBasicAuth(username, token) - } else if authType == "workspace-token" { + } else if authType == AuthTypeWorkspaceToken { // Authenticate using workspace token bitbucketCloud.bbClient = bitbucket.NewOAuthbearerToken(token) - } else { - return nil, errors.New("Please enter a valid value for authtype (app-password or workspace-token)") } return bitbucketCloud, nil @@ -71,7 +69,7 @@ func (bbc *BitbucketCloud) CreatePullRequest(_ context.Context, _ scm.Repository RepoSlug: bbcRepo.name, } var currentUserUUID string - if bbc.authType == "app-password" { + if bbc.authType == AuthTypeAppPassword { currentUser, err := bbc.bbClient.User.Profile() if err != nil { return nil, err @@ -367,9 +365,9 @@ func (bbc *BitbucketCloud) convertRepository(repo bitbucket.Repository) (*reposi return nil, err } - if bbc.authType == "app-password" { + if bbc.authType == AuthTypeAppPassword { parsedURL.User = url.UserPassword(bbc.username, bbc.token) - } else if bbc.authType == "workspace-token" { + } else if bbc.authType == AuthTypeWorkspaceToken { parsedURL.User = url.UserPassword("x-token-auth", bbc.token) } @@ -393,3 +391,25 @@ func findLinkType(cloneLinks []hrefLink, cloneType string, repoName string) (str return "", errors.Errorf("unable to find clone url for repository %s using clone type %s", repoName, cloneType) } + +// AuthType defines the authentication method for Bitbucket Cloud +type AuthType int + +const ( + // AuthTypeAppPassword will use app password authentication + AuthTypeAppPassword AuthType = iota + 1 + // AuthTypeWorkspaceToken will use workspace token authentication + AuthTypeWorkspaceToken +) + +// ParseAuthType parses an auth type from a string +func ParseAuthType(str string) (AuthType, error) { + switch str { + default: + return AuthType(0), fmt.Errorf("could not parse \"%s\" as auth type", str) + case "app-password": + return AuthTypeAppPassword, nil + case "workspace-token": + return AuthTypeWorkspaceToken, nil + } +} From 653a3f916d2749b951144f41cdf573d98b88ba16 Mon Sep 17 00:00:00 2001 From: Linda Chen Date: Mon, 18 Nov 2024 16:48:08 -0500 Subject: [PATCH 3/4] moved available values to the same line in usage description --- cmd/platform.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/platform.go b/cmd/platform.go index c983782c..ffb679da 100644 --- a/cmd/platform.go +++ b/cmd/platform.go @@ -24,10 +24,7 @@ func configurePlatform(cmd *cobra.Command) { flags.BoolP("insecure", "", false, "Insecure controls whether a client verifies the server certificate chain and host name. Used only for Bitbucket server.") flags.StringP("username", "u", "", "The Bitbucket server username.") flags.StringP("token", "T", "", "The personal access token for the targeting platform. Can also be set using the GITHUB_TOKEN/GITLAB_TOKEN/GITEA_TOKEN/BITBUCKET_SERVER_TOKEN/BITBUCKET_CLOUD_APP_PASSWORD/BITBUCKET_CLOUD_WORKSPACE_TOKEN environment variable.") - flags.StringP("auth-type", "", "app-password", `The authentication type. Used only for Bitbucket cloud. -Available values: - app-password: authenticate using an app password - workspace-token: authenticate using a workspace token + flags.StringP("auth-type", "", "app-password", `The authentication type. Used only for Bitbucket cloud. Available values: app-password, workspace-token. `) _ = cmd.RegisterFlagCompletionFunc("auth-type", func(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { return []string{"app-password", "workspace-token"}, cobra.ShellCompDirectiveNoFileComp From 3cbad41a779521e641f67d5c465ab3cca595a37c Mon Sep 17 00:00:00 2001 From: Linda Chen Date: Mon, 18 Nov 2024 16:50:23 -0500 Subject: [PATCH 4/4] small format typo --- cmd/platform.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/platform.go b/cmd/platform.go index ffb679da..bc21bcf9 100644 --- a/cmd/platform.go +++ b/cmd/platform.go @@ -24,8 +24,7 @@ func configurePlatform(cmd *cobra.Command) { flags.BoolP("insecure", "", false, "Insecure controls whether a client verifies the server certificate chain and host name. Used only for Bitbucket server.") flags.StringP("username", "u", "", "The Bitbucket server username.") flags.StringP("token", "T", "", "The personal access token for the targeting platform. Can also be set using the GITHUB_TOKEN/GITLAB_TOKEN/GITEA_TOKEN/BITBUCKET_SERVER_TOKEN/BITBUCKET_CLOUD_APP_PASSWORD/BITBUCKET_CLOUD_WORKSPACE_TOKEN environment variable.") - flags.StringP("auth-type", "", "app-password", `The authentication type. Used only for Bitbucket cloud. Available values: app-password, workspace-token. - `) + flags.StringP("auth-type", "", "app-password", `The authentication type. Used only for Bitbucket cloud. Available values: app-password, workspace-token.`) _ = cmd.RegisterFlagCompletionFunc("auth-type", func(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { return []string{"app-password", "workspace-token"}, cobra.ShellCompDirectiveNoFileComp })