From 4f70113ea012895bf8539e96e21e2c58f17060ac Mon Sep 17 00:00:00 2001 From: Peter Leitzen Date: Mon, 13 Sep 2021 18:44:37 +0200 Subject: [PATCH] OAuth: Support role mapping for GitLab OAuth (#30025) * Support `role_attribute_path` for GitLab OAuth Allow role mapping for GitLab accounts. Example: [auth.gitlab] role_attribute_path = is_admin && 'Admin' || 'Viewer' * Support `role_attribute_path` for GitLab OAuth Allow role mapping for GitLab accounts. Example: [auth.gitlab] role_attribute_path = is_admin && 'Admin' || 'Viewer' * docs: add docs for role_attribute_path * Apply suggestions from code review Co-authored-by: Peter Leitzen * docs: update example example should suggest a full configuration * Apply suggestions from code review Co-authored-by: Marcus Efraimsson * Apply suggestions from code review Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> * docs: add suggestions from tech writers Co-authored-by: Henry Sachs Co-authored-by: Henry Sachs Co-authored-by: Marcus Efraimsson Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> --- docs/sources/auth/gitlab.md | 27 +++++++++++++++++++++------ pkg/login/social/gitlab_oauth.go | 24 ++++++++++++++++++++++-- pkg/login/social/social.go | 7 ++++--- 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/docs/sources/auth/gitlab.md b/docs/sources/auth/gitlab.md index 56d4e5accbf..84ac2c24eb4 100644 --- a/docs/sources/auth/gitlab.md +++ b/docs/sources/auth/gitlab.md @@ -26,10 +26,10 @@ instance, if you access Grafana at `http://203.0.113.31:3000`, you should use http://203.0.113.31:3000/login/gitlab ``` -Finally, select _read_api_ as the _Scope_ and submit the form. Note that if you're +Finally, select _read_api_as the_Scope_and submit the form. Note that if you're not going to use GitLab groups for authorization (i.e. not setting -`allowed_groups`, see below), you can select _read_user_ instead of _read_api_ as -the _Scope_, thus giving a more restricted access to your GitLab API. +`allowed_groups`, see below), you can select_read_user_ instead of _read_api_as +the_Scope_, thus giving a more restricted access to your GitLab API. You'll get an _Application Id_ and a _Secret_ in return; we'll call them `GITLAB_APPLICATION_ID` and `GITLAB_SECRET` respectively for the rest of this @@ -94,8 +94,8 @@ display name, especially if the display name contains spaces or special characters. Make sure you always use the group or subgroup name as it appears in the URL of the group or subgroup. -Here's a complete example with `allow_sign_up` enabled, and access limited to -the `example` and `foo/bar` groups: +Here's a complete example with `allow_sign_up` enabled, with access limited to +the `example` and `foo/bar` groups. The example also promotes all GitLab Admins to Grafana Admins: ```ini [auth.gitlab] @@ -103,13 +103,28 @@ enabled = true allow_sign_up = true client_id = GITLAB_APPLICATION_ID client_secret = GITLAB_SECRET -scopes = api +scopes = read_api auth_url = https://gitlab.com/oauth/authorize token_url = https://gitlab.com/oauth/token api_url = https://gitlab.com/api/v4 allowed_groups = example, foo/bar +role_attribute_path = is_admin && 'Admin' || 'Viewer' ``` +### Map roles + +You can use GitLab OAuth to map roles. During mapping, Grafana checks for the presence of a role using the [JMESPath](http://jmespath.org/examples.html) specified via the `role_attribute_path` configuration option. + +For the path lookup, Grafana uses JSON obtained from querying GitLab's API [`/api/v4/user`](https://docs.gitlab.com/ee/api/users.html#list-current-user-for-normal-users) endpoint. The result of evaluating the `role_attribute_path` JMESPath expression must be a valid Grafana role, for example, `Viewer`, `Editor` or `Admin`. For more information about roles and permissions in Grafana, refer to [Organization roles]({{< relref "../permissions/organization_roles.md" >}}). + +An example Query could look like the following: + +```bash +role_attribute_path = is_admin && 'Admin' || 'Viewer' +``` + +This allows every GitLab Admin to be an Admin in Grafana. + ### Team Sync (Enterprise only) > Only available in Grafana Enterprise v6.4+ diff --git a/pkg/login/social/gitlab_oauth.go b/pkg/login/social/gitlab_oauth.go index afb8731c629..17bd231e1a4 100644 --- a/pkg/login/social/gitlab_oauth.go +++ b/pkg/login/social/gitlab_oauth.go @@ -13,8 +13,9 @@ import ( type SocialGitlab struct { *SocialBase - allowedGroups []string - apiUrl string + allowedGroups []string + apiUrl string + roleAttributePath string } func (s *SocialGitlab) Type() int { @@ -114,12 +115,18 @@ func (s *SocialGitlab) UserInfo(client *http.Client, token *oauth2.Token) (*Basi groups := s.GetGroups(client) + role, err := s.extractRole(response.Body) + if err != nil { + s.log.Error("Failed to extract role", "error", err) + } + userInfo := &BasicUserInfo{ Id: fmt.Sprintf("%d", data.Id), Name: data.Name, Login: data.Username, Email: data.Email, Groups: groups, + Role: role, } if !s.IsGroupMember(groups) { @@ -128,3 +135,16 @@ func (s *SocialGitlab) UserInfo(client *http.Client, token *oauth2.Token) (*Basi return userInfo, nil } + +func (s *SocialGitlab) extractRole(rawJSON []byte) (string, error) { + if s.roleAttributePath == "" { + return "", nil + } + + role, err := s.searchJSONForStringAttr(s.roleAttributePath, rawJSON) + + if err != nil { + return "", err + } + return role, nil +} diff --git a/pkg/login/social/social.go b/pkg/login/social/social.go index f63f1ccfc26..948d4b1a72b 100644 --- a/pkg/login/social/social.go +++ b/pkg/login/social/social.go @@ -126,9 +126,10 @@ func ProvideService(cfg *setting.Cfg) *SocialService { // GitLab. if name == "gitlab" { ss.socialMap["gitlab"] = &SocialGitlab{ - SocialBase: newSocialBase(name, &config, info), - apiUrl: info.ApiUrl, - allowedGroups: util.SplitString(sec.Key("allowed_groups").String()), + SocialBase: newSocialBase(name, &config, info), + apiUrl: info.ApiUrl, + allowedGroups: util.SplitString(sec.Key("allowed_groups").String()), + roleAttributePath: info.RoleAttributePath, } }