Skip to content

Commit

Permalink
feat: Add team groups support to bitbucket connector
Browse files Browse the repository at this point in the history
Signed-off-by: m.nabokikh <[email protected]>
  • Loading branch information
nabokihms committed Oct 4, 2020
1 parent 3e5ff2f commit ec66ced
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 13 deletions.
4 changes: 4 additions & 0 deletions Documentation/connectors/bitbucketcloud.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ connectors:
# If `teams` is provided, this acts as a whitelist - only the user's Bitbucket teams that are in the configured `teams` below will go into the groups claim. Conversely, if the user is not in any of the configured `teams`, the user will not be authenticated.
teams:
- my-team
# Optional parameter to include team groups.
# If enabled, the groups claim of dex id_token will looks like this:
# ["my_team", "my_team/administrators", "my_team/members"]
includeTeamGroups: true
```
61 changes: 49 additions & 12 deletions connector/bitbucketcloud/bitbucketcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import (

const (
apiURL = "https://api.bitbucket.org/2.0"

// Switch to API v2.0 when the Atlassian platform services are fully available in Bitbucket
legacyAPIURL = "https://api.bitbucket.org/1.0"
// Bitbucket requires this scope to access '/user' API endpoints.
scopeAccount = "account"
// Bitbucket requires this scope to access '/user/emails' API endpoints.
Expand All @@ -33,21 +34,24 @@ const (

// Config holds configuration options for Bitbucket logins.
type Config struct {
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
RedirectURI string `json:"redirectURI"`
Teams []string `json:"teams"`
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
RedirectURI string `json:"redirectURI"`
Teams []string `json:"teams"`
IncludeTeamGroups bool `json:"includeTeamGroups,omitempty"`
}

// Open returns a strategy for logging in through Bitbucket.
func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) {
func (c *Config) Open(_ string, logger log.Logger) (connector.Connector, error) {
b := bitbucketConnector{
redirectURI: c.RedirectURI,
teams: c.Teams,
clientID: c.ClientID,
clientSecret: c.ClientSecret,
apiURL: apiURL,
logger: logger,
redirectURI: c.RedirectURI,
teams: c.Teams,
clientID: c.ClientID,
clientSecret: c.ClientSecret,
includeTeamGroups: c.IncludeTeamGroups,
apiURL: apiURL,
legacyAPIURL: legacyAPIURL,
logger: logger,
}

return &b, nil
Expand All @@ -71,10 +75,13 @@ type bitbucketConnector struct {
clientSecret string
logger log.Logger
apiURL string
legacyAPIURL string

// the following are used only for tests
hostName string
httpClient *http.Client

includeTeamGroups bool
}

// groupsRequired returns whether dex requires Bitbucket's 'team' scope.
Expand Down Expand Up @@ -396,9 +403,39 @@ func (b *bitbucketConnector) userTeams(ctx context.Context, client *http.Client)
}
}

if b.includeTeamGroups {
for _, team := range teams {
teamGroups, err := b.userTeamGroups(ctx, client, team)
if err != nil {
return nil, fmt.Errorf("bitbucket: %v", err)
}
teams = append(teams, teamGroups...)
}
}

return teams, nil
}

type group struct {
Slug string `json:"slug"`
}

func (b *bitbucketConnector) userTeamGroups(ctx context.Context, client *http.Client, teamName string) ([]string, error) {
var teamGroups []string
apiURL := b.legacyAPIURL + "/groups/" + teamName

var response []group
if err := get(ctx, client, apiURL, &response); err != nil {
return nil, fmt.Errorf("get user team %q groups: %v", teamName, err)
}

for _, group := range response {
teamGroups = append(teamGroups, teamName+"/"+group.Slug)
}

return teamGroups, nil
}

// get creates a "GET `apiURL`" request with context, sends the request using
// the client, and decodes the resulting response body into v.
// Any errors encountered when building requests, sending requests, and
Expand Down
18 changes: 17 additions & 1 deletion connector/bitbucketcloud/bitbucketcloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ func TestUserGroups(t *testing.T) {

s := newTestServer(map[string]interface{}{
"/user/permissions/teams": teamsResponse,
"/groups/team-1": []group{{Slug: "administrators"}, {Slug: "members"}},
"/groups/team-2": []group{{Slug: "everyone"}},
"/groups/team-3": []group{},
})

connector := bitbucketConnector{apiURL: s.URL}
connector := bitbucketConnector{apiURL: s.URL, legacyAPIURL: s.URL}
groups, err := connector.userTeams(context.Background(), newClient())

expectNil(t, err)
Expand All @@ -41,6 +44,19 @@ func TestUserGroups(t *testing.T) {
"team-3",
})

connector.includeTeamGroups = true
groups, err = connector.userTeams(context.Background(), newClient())

expectNil(t, err)
expectEquals(t, groups, []string{
"team-1",
"team-2",
"team-3",
"team-1/administrators",
"team-1/members",
"team-2/everyone",
})

s.Close()
}

Expand Down

0 comments on commit ec66ced

Please sign in to comment.