diff --git a/Documentation/connectors/gitlab.md b/Documentation/connectors/gitlab.md index e22d0b46..f85e24c7 100644 --- a/Documentation/connectors/gitlab.md +++ b/Documentation/connectors/gitlab.md @@ -28,4 +28,9 @@ connectors: clientID: $GITLAB_APPLICATION_ID clientSecret: $GITLAB_CLIENT_SECRET redirectURI: http://127.0.0.1:5556/dex/callback + # Optional groups whitelist, communicated through the "groups" scope. + # If `groups` is omitted, all of the user's GitLab groups are returned when the groups scope is present. + # If `groups` is provided, this acts as a whitelist - only the user's GitLab groups that are in the configured `groups` below will go into the groups claim. Conversely, if the user is not in any of the configured `groups`, the user will not be authenticated. + groups: + - my-group ``` diff --git a/connector/gitlab/gitlab.go b/connector/gitlab/gitlab.go index 58ecba17..fd932142 100644 --- a/connector/gitlab/gitlab.go +++ b/connector/gitlab/gitlab.go @@ -26,10 +26,11 @@ const ( // Config holds configuration options for gilab logins. type Config struct { - BaseURL string `json:"baseURL"` - ClientID string `json:"clientID"` - ClientSecret string `json:"clientSecret"` - RedirectURI string `json:"redirectURI"` + BaseURL string `json:"baseURL"` + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` + RedirectURI string `json:"redirectURI"` + Groups []string `json:"groups"` } type gitlabUser struct { @@ -52,6 +53,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) clientID: c.ClientID, clientSecret: c.ClientSecret, logger: logger, + groups: c.Groups, }, nil } @@ -68,7 +70,7 @@ var ( type gitlabConnector struct { baseURL string redirectURI string - org string + groups []string clientID string clientSecret string logger log.Logger @@ -142,7 +144,7 @@ func (c *gitlabConnector) HandleCallback(s connector.Scopes, r *http.Request) (i } if s.Groups { - groups, err := c.groups(ctx, client) + groups, err := c.getGroups(ctx, client, s.Groups, user.Username) if err != nil { return identity, fmt.Errorf("gitlab: get groups: %v", err) } @@ -185,7 +187,7 @@ func (c *gitlabConnector) Refresh(ctx context.Context, s connector.Scopes, ident ident.Email = user.Email if s.Groups { - groups, err := c.groups(ctx, client) + groups, err := c.getGroups(ctx, client, s.Groups, user.Username) if err != nil { return ident, fmt.Errorf("gitlab: get groups: %v", err) } @@ -224,11 +226,11 @@ func (c *gitlabConnector) user(ctx context.Context, client *http.Client) (gitlab return u, nil } -// groups queries the GitLab API for group membership. +// userGroups queries the GitLab API for group membership. // // The HTTP passed client is expected to be constructed by the golang.org/x/oauth2 package, // which inserts a bearer token as part of the request. -func (c *gitlabConnector) groups(ctx context.Context, client *http.Client) ([]string, error) { +func (c *gitlabConnector) userGroups(ctx context.Context, client *http.Client) ([]string, error) { req, err := http.NewRequest("GET", c.baseURL+"/oauth/userinfo", nil) if err != nil { return nil, fmt.Errorf("gitlab: new req: %v", err) @@ -256,3 +258,37 @@ func (c *gitlabConnector) groups(ctx context.Context, client *http.Client) ([]st return u.Groups, nil } + +func (c *gitlabConnector) getGroups(ctx context.Context, client *http.Client, groupScope bool, userLogin string) ([]string, error) { + gitlabGroups, err := c.userGroups(ctx, client) + if err != nil { + return nil, err + } + + if len(c.groups) > 0 { + filteredGroups := filterGroups(gitlabGroups, c.groups) + if len(filteredGroups) == 0 { + return nil, fmt.Errorf("gitlab: user %q is not in any of the required groups", userLogin) + } + return filteredGroups, nil + } else if groupScope { + return gitlabGroups, nil + } + + return nil, nil +} + +// Filter the users' group memberships by 'groups' from config. +func filterGroups(userGroups, configGroups []string) []string { + groups := []string{} + groupFilter := make(map[string]struct{}) + for _, group := range configGroups { + groupFilter[group] = struct{}{} + } + for _, group := range userGroups { + if _, ok := groupFilter[group]; ok { + groups = append(groups, group) + } + } + return groups +}