mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-02-13 21:00:00 +00:00
feat(bitbucketdatacenter): Implement missing OrgMembership method (#5476)
This commit is contained in:
@@ -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
|
||||
//
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user