feat(bitbucketdatacenter): Implement missing OrgMembership method (#5476)

This commit is contained in:
Henrik Huitti
2025-09-21 10:51:30 +03:00
committed by GitHub
parent fd38197b32
commit 275c8fee1c
5 changed files with 127 additions and 30 deletions

View File

@@ -536,6 +536,11 @@ var flags = append([]cli.Flag{
TrimSpace: true,
},
},
&cli.BoolFlag{ // TODO: Remove this feature flag in next major version
Sources: cli.EnvVars("WOODPECKER_BITBUCKET_DC_ENABLE_OAUTH2_SCOPE_PROJECT_ADMIN"),
Name: "bitbucket-dc-oauth-enable-oauth2-scope-project-admin",
Usage: "Bitbucket DataCenter/Server oauth2 scope should be configured to include PROJECT_ADMIN configuration.",
},
//
// development flags
//

View File

@@ -22,6 +22,7 @@ To enable Bitbucket Server you should configure the Woodpecker container using t
+ - WOODPECKER_BITBUCKET_DC_CLIENT_ID=xxx
+ - WOODPECKER_BITBUCKET_DC_CLIENT_SECRET=yyy
+ - WOODPECKER_BITBUCKET_DC_URL=http://stash.mycompany.com
+ - WOODPECKER_BITBUCKET_DC_ENABLE_OAUTH2_SCOPE_PROJECT_ADMIN=true
woodpecker-agent:
[...]
@@ -124,3 +125,12 @@ Read the value for `WOODPECKER_BITBUCKET_DC_GIT_PASSWORD` from the specified fil
- Default: `false`
Configure if SSL verification should be skipped.
---
### BITBUCKET_DC_ENABLE_OAUTH2_SCOPE_PROJECT_ADMIN
- Name: `WOODPECKER_BITBUCKET_DC_ENABLE_OAUTH2_SCOPE_PROJECT_ADMIN`
- Default: `false`
When enabled, the Bitbucket Application Link for Woodpecker should include the `PROJECT_ADMIN` scope. Enabling this feature flag will allow the users of Bitbucket Datacenter to use organization secrets and properly list repositories within the organization.

View File

@@ -38,35 +38,38 @@ const listLimit = 250
// Opts defines configuration options.
type Opts struct {
URL string // Bitbucket server url for API access.
Username string // Git machine account username.
Password string // Git machine account password.
OAuthClientID string // OAuth 2.0 client id
OAuthClientSecret string // OAuth 2.0 client secret
OAuthHost string // OAuth 2.0 host
URL string // Bitbucket server url for API access.
Username string // Git machine account username.
Password string // Git machine account password.
OAuthClientID string // OAuth 2.0 client id
OAuthClientSecret string // OAuth 2.0 client secret
OAuthHost string // OAuth 2.0 host
OAuthEnableProjectAdminScope bool // Whether to enable project admin scope. Should be set as default in the next major version.
}
type client struct {
url string
urlAPI string
clientID string
clientSecret string
oauthHost string
username string
password string
url string
urlAPI string
clientID string
clientSecret string
oauthHost string
username string
password string
oauthEnableProjectAdminScope bool
}
// New returns a Forge implementation that integrates with Bitbucket DataCenter/Server,
// the on-premise edition of Bitbucket Cloud, formerly known as Stash.
func New(opts Opts) (forge.Forge, error) {
config := &client{
url: opts.URL,
urlAPI: fmt.Sprintf("%s/rest", opts.URL),
clientID: opts.OAuthClientID,
clientSecret: opts.OAuthClientSecret,
oauthHost: opts.OAuthHost,
username: opts.Username,
password: opts.Password,
url: opts.URL,
urlAPI: fmt.Sprintf("%s/rest", opts.URL),
clientID: opts.OAuthClientID,
clientSecret: opts.OAuthClientSecret,
oauthHost: opts.OAuthHost,
username: opts.Username,
password: opts.Password,
oauthEnableProjectAdminScope: opts.OAuthEnableProjectAdminScope,
}
switch {
@@ -638,9 +641,69 @@ func (*client) TeamPerm(_ *model.User, _ string) (*model.Perm, error) {
// OrgMembership returns if user is member of organization and if user
// is admin/owner in this organization.
func (c *client) OrgMembership(_ context.Context, _ *model.User, _ string) (*model.OrgPerm, error) {
// TODO: Not implemented currently
return nil, nil
func (c *client) OrgMembership(ctx context.Context, u *model.User, org string) (*model.OrgPerm, error) {
if !c.oauthEnableProjectAdminScope {
// This method cannot be implemented without the PROJECT_ADMIN scope included in the OAuth2 configuration
return nil, nil
}
bc, err := c.newClient(ctx, u)
if err != nil {
return nil, fmt.Errorf("unable to create bitbucket client: %w", err)
}
// Check if the user is Bitbucket project admin
if c.hasProjectAdminAccess(ctx, bc, org) {
return &model.OrgPerm{Member: true, Admin: true}, nil
}
// User is not Bitbucket project admin, check if they have write access to any repositories in the Bitbucket project.
// If they have, they are considered to be an organization member.
hasMembership, err := c.hasRepositoryWriteAccess(ctx, org, bc)
if err != nil {
return nil, fmt.Errorf("failed to check repository access: %w", err)
}
if hasMembership {
return &model.OrgPerm{Member: true, Admin: false}, nil
}
return &model.OrgPerm{Member: false, Admin: false}, nil
}
func (c *client) hasProjectAdminAccess(ctx context.Context, client *bb.Client, org string) bool {
// If the user can access project permissions, the user has project admin access in the Bitbucket
perms, _, err := client.Projects.SearchProjectPermissions(ctx, org, &bb.ProjectPermissionSearchOptions{})
if err == nil && len(perms) > 0 {
return true
}
return false
}
func (c *client) hasRepositoryWriteAccess(ctx context.Context, org string, client *bb.Client) (bool, error) {
opts := &bb.RepositorySearchOptions{
Archived: "ACTIVE",
ProjectKey: org,
Permission: bb.PermissionRepoWrite,
}
for {
repos, resp, err := client.Projects.SearchRepositories(ctx, opts)
if err != nil {
return false, fmt.Errorf("failed to search repositories: %w", err)
}
// If we find any repositories with write access, user has membership
if len(repos) > 0 {
return true, nil
}
if resp.LastPage {
break
}
opts.Start = resp.NextPageStart
}
return false, nil
}
// Org fetches the organization from the forge by name. If the name is a user an org with type user is returned.
@@ -663,6 +726,17 @@ func (c *client) newOAuth2Config() *oauth2.Config {
publicOAuthURL = c.urlAPI
}
scopes := []string{
string(bb.PermissionRepoRead),
string(bb.PermissionRepoWrite),
string(bb.PermissionRepoAdmin),
}
// TODO: Remove this feature flag in the next major version and always include project admin scope
if c.oauthEnableProjectAdminScope {
scopes = append(scopes, string(bb.PermissionProjectAdmin))
}
return &oauth2.Config{
ClientID: c.clientID,
ClientSecret: c.clientSecret,
@@ -670,7 +744,7 @@ func (c *client) newOAuth2Config() *oauth2.Config {
AuthURL: fmt.Sprintf("%s/oauth2/latest/authorize", publicOAuthURL),
TokenURL: fmt.Sprintf("%s/oauth2/latest/token", c.urlAPI),
},
Scopes: []string{string(bb.PermissionRepoRead), string(bb.PermissionRepoWrite), string(bb.PermissionRepoAdmin)},
Scopes: scopes,
RedirectURL: fmt.Sprintf("%s/authorize", server.Config.Server.OAuthHost),
}
}

View File

@@ -169,13 +169,19 @@ func setupBitbucketDatacenter(forge *model.Forge) (forge.Forge, error) {
return nil, fmt.Errorf("missing git-password")
}
enableProjectAdminScope, ok := forge.AdditionalOptions["oauth-enable-project-admin-scope"].(bool)
if !ok {
return nil, fmt.Errorf("incorrect type for oauth-enable-project-admin-scope value")
}
opts := bitbucketdatacenter.Opts{
URL: forge.URL,
OAuthClientID: forge.OAuthClientID,
OAuthClientSecret: forge.OAuthClientSecret,
Username: gitUsername,
Password: gitPassword,
OAuthHost: forge.OAuthHost,
URL: forge.URL,
OAuthClientID: forge.OAuthClientID,
OAuthClientSecret: forge.OAuthClientSecret,
Username: gitUsername,
Password: gitPassword,
OAuthHost: forge.OAuthHost,
OAuthEnableProjectAdminScope: enableProjectAdminScope,
}
log.Debug().
Str("url", opts.URL).
@@ -183,6 +189,7 @@ func setupBitbucketDatacenter(forge *model.Forge) (forge.Forge, error) {
Bool("oauth-client-id-set", opts.OAuthClientID != "").
Bool("oauth-client-secret-set", opts.OAuthClientSecret != "").
Str("type", string(forge.Type)).
Bool("oauth-enable-project-admin-scope", opts.OAuthEnableProjectAdminScope).
Msg("setting up forge")
return bitbucketdatacenter.New(opts)
}

View File

@@ -154,6 +154,7 @@ func setupForgeService(c *cli.Command, _store store.Store) error {
_forge.Type = model.ForgeTypeBitbucketDatacenter
_forge.AdditionalOptions["git-username"] = c.String("bitbucket-dc-git-username")
_forge.AdditionalOptions["git-password"] = c.String("bitbucket-dc-git-password")
_forge.AdditionalOptions["oauth-enable-project-admin-scope"] = c.Bool("bitbucket-dc-oauth-enable-oauth2-scope-project-admin")
default:
return errors.New("forge not configured")
}