Auth: Add org to role mappings support to Gitlab integration (#88751)

* Conf: Add org_mapping and org_attribute_path to github and gitlab conf

* Gitlab: Implement org role mapping

* Update docs
---------

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
This commit is contained in:
Karl Persson 2024-06-05 16:15:53 +02:00 committed by GitHub
parent 1ceb9e8e9d
commit f28905f8c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 129 additions and 90 deletions

View File

@ -647,6 +647,7 @@ team_ids =
allowed_organizations = allowed_organizations =
role_attribute_path = role_attribute_path =
role_attribute_strict = false role_attribute_strict = false
org_mapping =
allow_assign_grafana_admin = false allow_assign_grafana_admin = false
skip_org_role_sync = false skip_org_role_sync = false
tls_skip_verify_insecure = false tls_skip_verify_insecure = false
@ -674,6 +675,7 @@ allowed_domains =
allowed_groups = allowed_groups =
role_attribute_path = role_attribute_path =
role_attribute_strict = false role_attribute_strict = false
org_mapping =
allow_assign_grafana_admin = false allow_assign_grafana_admin = false
skip_org_role_sync = false skip_org_role_sync = false
tls_skip_verify_insecure = false tls_skip_verify_insecure = false

View File

@ -608,6 +608,7 @@
;allowed_organizations = ;allowed_organizations =
;role_attribute_path = ;role_attribute_path =
;role_attribute_strict = false ;role_attribute_strict = false
;org_mapping =
;allow_assign_grafana_admin = false ;allow_assign_grafana_admin = false
;skip_org_role_sync = false ;skip_org_role_sync = false
@ -629,6 +630,7 @@
;allowed_groups = ;allowed_groups =
;role_attribute_path = ;role_attribute_path =
;role_attribute_strict = false ;role_attribute_strict = false
;org_mapping =
;allow_assign_grafana_admin = false ;allow_assign_grafana_admin = false
;skip_org_role_sync = false ;skip_org_role_sync = false
;tls_skip_verify_insecure = false ;tls_skip_verify_insecure = false

View File

@ -149,8 +149,7 @@ To map the server administrator role, use the `allow_assign_grafana_admin` confi
Refer to [configuration options]({{< relref "#configuration-options" >}}) for more information. Refer to [configuration options]({{< relref "#configuration-options" >}}) for more information.
If no valid role is found, the user is assigned the role specified by [the `auto_assign_org_role` option]({{< relref "../../../configure-grafana#auto_assign_org_role" >}}). If no valid role is found, the user is assigned the role specified by [the `auto_assign_org_role` option]({{< relref "../../../configure-grafana#auto_assign_org_role" >}}).
You can disable this default role assignment by setting `role_attribute_strict = true`. You can disable this default role assignment by setting `role_attribute_strict = true`. This setting denies user access if no role or an invalid role is returned after evaluating the `role_attribute_path` and the `org_mapping` expressions.
This setting denies user access if no role or an invalid role is returned.
To ease configuration of a proper JMESPath expression, go to [JMESPath](http://jmespath.org/) to test and evaluate expressions with custom payloads. To ease configuration of a proper JMESPath expression, go to [JMESPath](http://jmespath.org/) to test and evaluate expressions with custom payloads.
@ -158,6 +157,20 @@ To ease configuration of a proper JMESPath expression, go to [JMESPath](http://j
This section includes examples of JMESPath expressions used for role mapping. This section includes examples of JMESPath expressions used for role mapping.
##### Org roles mapping example
The GitLab integration uses the external users' groups in the `org_mapping` configuration to map organizations and roles based on their GitLab group membership.
In this example, the user has been granted the role of a `Viewer` in the `org_foo` organization, and the role of an `Editor` in the `org_bar` and `org_baz` orgs.
The external user is part of the following GitLab groups: `groupd-1` and `group-2`.
Config:
```ini
org_mapping = group-1:org_foo:Viewer groupd-1:org_bar:Editor *:org_baz:Editor
```
#### Map roles using user information from OAuth token #### Map roles using user information from OAuth token
In this example, the user with email `admin@company.com` has been granted the `Admin` role. In this example, the user with email `admin@company.com` has been granted the `Admin` role.
@ -251,6 +264,7 @@ The table below describes all GitLab OAuth configuration options. Like any other
| `auto_login` | No | Set to `true` to enable users to bypass the login screen and automatically log in. This setting is ignored if you configure multiple auth providers to use auto-login. | `false` | | `auto_login` | No | Set to `true` to enable users to bypass the login screen and automatically log in. This setting is ignored if you configure multiple auth providers to use auto-login. | `false` |
| `role_attribute_path` | No | [JMESPath](http://jmespath.org/examples.html) expression to use for Grafana role lookup. Grafana will first evaluate the expression using the GitLab OAuth token. If no role is found, Grafana creates a JSON data with `groups` key that maps to groups obtained from GitLab's `/oauth/userinfo` endpoint, and evaluates the expression using this data. Finally, if a valid role is still not found, the expression is evaluated against the user information retrieved from `api_url/users` endpoint and groups retrieved from `api_url/groups` endpoint. The result of the evaluation should be a valid Grafana role (`None`, `Viewer`, `Editor`, `Admin` or `GrafanaAdmin`). For more information on user role mapping, refer to [Configure role mapping]({{< relref "#configure-role-mapping" >}}). | | | `role_attribute_path` | No | [JMESPath](http://jmespath.org/examples.html) expression to use for Grafana role lookup. Grafana will first evaluate the expression using the GitLab OAuth token. If no role is found, Grafana creates a JSON data with `groups` key that maps to groups obtained from GitLab's `/oauth/userinfo` endpoint, and evaluates the expression using this data. Finally, if a valid role is still not found, the expression is evaluated against the user information retrieved from `api_url/users` endpoint and groups retrieved from `api_url/groups` endpoint. The result of the evaluation should be a valid Grafana role (`None`, `Viewer`, `Editor`, `Admin` or `GrafanaAdmin`). For more information on user role mapping, refer to [Configure role mapping]({{< relref "#configure-role-mapping" >}}). | |
| `role_attribute_strict` | No | Set to `true` to deny user login if the Grafana role cannot be extracted using `role_attribute_path`. For more information on user role mapping, refer to [Configure role mapping]({{< relref "#configure-role-mapping" >}}). | `false` | | `role_attribute_strict` | No | Set to `true` to deny user login if the Grafana role cannot be extracted using `role_attribute_path`. For more information on user role mapping, refer to [Configure role mapping]({{< relref "#configure-role-mapping" >}}). | `false` |
| `org_mapping` | No | List of comma- or space-separated `<ExternalGitlabGroupName>:<OrgIdOrName>:<Role>` mappings. Value can be `*` meaning "All users". Role is optional and can have the following values: `None`, `Viewer`, `Editor` or `Admin`. For more information on external organization to role mapping, refer to [Org roles mapping example](#org-roles-mapping-example). | |
| `allow_assign_grafana_admin` | No | Set to `true` to enable automatic sync of the Grafana server administrator role. If this option is set to `true` and the result of evaluating `role_attribute_path` for a user is `GrafanaAdmin`, Grafana grants the user the server administrator privileges and organization administrator role. If this option is set to `false` and the result of evaluating `role_attribute_path` for a user is `GrafanaAdmin`, Grafana grants the user only organization administrator role. For more information on user role mapping, refer to [Configure role mapping]({{< relref "#configure-role-mapping" >}}). | `false` | | `allow_assign_grafana_admin` | No | Set to `true` to enable automatic sync of the Grafana server administrator role. If this option is set to `true` and the result of evaluating `role_attribute_path` for a user is `GrafanaAdmin`, Grafana grants the user the server administrator privileges and organization administrator role. If this option is set to `false` and the result of evaluating `role_attribute_path` for a user is `GrafanaAdmin`, Grafana grants the user only organization administrator role. For more information on user role mapping, refer to [Configure role mapping]({{< relref "#configure-role-mapping" >}}). | `false` |
| `skip_org_role_sync` | No | Set to `true` to stop automatically syncing user roles. | `false` | | `skip_org_role_sync` | No | Set to `true` to stop automatically syncing user roles. | `false` |
| `allowed_domains` | No | List of comma or space-separated domains. User must belong to at least one domain to log in. | | | `allowed_domains` | No | List of comma or space-separated domains. User must belong to at least one domain to log in. | |

View File

@ -12,7 +12,6 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
"github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/models/roletype"
"github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/ssosettings" "github.com/grafana/grafana/pkg/services/ssosettings"
@ -49,9 +48,8 @@ type userData struct {
Name string `json:"name"` Name string `json:"name"`
Groups []string `json:"groups_direct"` Groups []string `json:"groups_direct"`
EmailVerified bool `json:"email_verified"` EmailVerified bool `json:"email_verified"`
Role roletype.RoleType `json:"-"` raw []byte
IsGrafanaAdmin *bool `json:"-"`
} }
func NewGitLabProvider(info *social.OAuthInfo, cfg *setting.Cfg, orgRoleMapper *OrgRoleMapper, ssoSettings ssosettings.Service, features featuremgmt.FeatureToggles) *SocialGitlab { func NewGitLabProvider(info *social.OAuthInfo, cfg *setting.Cfg, orgRoleMapper *OrgRoleMapper, ssoSettings ssosettings.Service, features featuremgmt.FeatureToggles) *SocialGitlab {
@ -195,23 +193,37 @@ func (s *SocialGitlab) UserInfo(ctx context.Context, client *http.Client, token
} }
userInfo := &social.BasicUserInfo{ userInfo := &social.BasicUserInfo{
Id: data.ID, Id: data.ID,
Name: data.Name, Name: data.Name,
Login: data.Login, Login: data.Login,
Email: data.Email, Email: data.Email,
Groups: data.Groups, Groups: data.Groups,
Role: data.Role,
IsGrafanaAdmin: data.IsGrafanaAdmin,
}
if !s.isGroupMember(data.Groups) {
return nil, errMissingGroupMembership
} }
if s.info.AllowAssignGrafanaAdmin && s.info.SkipOrgRoleSync { if s.info.AllowAssignGrafanaAdmin && s.info.SkipOrgRoleSync {
s.log.Debug("AllowAssignGrafanaAdmin and skipOrgRoleSync are both set, Grafana Admin role will not be synced, consider setting one or the other") s.log.Debug("AllowAssignGrafanaAdmin and skipOrgRoleSync are both set, Grafana Admin role will not be synced, consider setting one or the other")
} }
if !s.info.SkipOrgRoleSync {
directlyMappedRole, grafanaAdmin, err := s.extractRoleAndAdminOptional(data.raw, userInfo.Groups)
if err != nil {
s.log.Warn("Failed to extract role", "err", err)
}
if s.info.AllowAssignGrafanaAdmin {
userInfo.IsGrafanaAdmin = &grafanaAdmin
}
userInfo.OrgRoles = s.orgRoleMapper.MapOrgRoles(s.orgMappingCfg, userInfo.Groups, directlyMappedRole)
if s.info.RoleAttributeStrict && len(userInfo.OrgRoles) == 0 {
return nil, errRoleAttributeStrictViolation.Errorf("could not evaluate any valid roles using IdP provided data")
}
}
if !s.isGroupMember(data.Groups) {
return nil, errMissingGroupMembership
}
return userInfo, nil return userInfo, nil
} }
@ -241,20 +253,7 @@ func (s *SocialGitlab) extractFromAPI(ctx context.Context, client *http.Client,
Email: apiResp.Email, Email: apiResp.Email,
Name: apiResp.Name, Name: apiResp.Name,
Groups: s.getGroups(ctx, client), Groups: s.getGroups(ctx, client),
} raw: response.Body,
if !s.info.SkipOrgRoleSync {
var grafanaAdmin bool
role, grafanaAdmin, err := s.extractRoleAndAdmin(response.Body, idData.Groups)
if err != nil {
return nil, err
}
if s.info.AllowAssignGrafanaAdmin {
idData.IsGrafanaAdmin = &grafanaAdmin
}
idData.Role = role
} }
if s.cfg.Env == setting.Dev { if s.cfg.Env == setting.Dev {
@ -300,19 +299,6 @@ func (s *SocialGitlab) extractFromToken(ctx context.Context, client *http.Client
data.Groups = userInfo.Groups data.Groups = userInfo.Groups
} }
if !s.info.SkipOrgRoleSync {
role, grafanaAdmin, errRole := s.extractRoleAndAdmin(rawJSON, data.Groups)
if errRole != nil {
return nil, errRole
}
if s.info.AllowAssignGrafanaAdmin {
data.IsGrafanaAdmin = &grafanaAdmin
}
data.Role = role
}
s.log.Debug("Resolved user data", "data", fmt.Sprintf("%+v", data)) s.log.Debug("Resolved user data", "data", fmt.Sprintf("%+v", data))
return &data, nil return &data, nil
} }

View File

@ -19,6 +19,7 @@ import (
"github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgtest"
"github.com/grafana/grafana/pkg/services/ssosettings" "github.com/grafana/grafana/pkg/services/ssosettings"
ssoModels "github.com/grafana/grafana/pkg/services/ssosettings/models" ssoModels "github.com/grafana/grafana/pkg/services/ssosettings/models"
"github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests" "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests"
@ -45,13 +46,12 @@ const (
func TestSocialGitlab_UserInfo(t *testing.T) { func TestSocialGitlab_UserInfo(t *testing.T) {
var nilPointer *bool var nilPointer *bool
provider := NewGitLabProvider(&social.OAuthInfo{SkipOrgRoleSync: false}, &setting.Cfg{}, nil, &ssosettingstests.MockService{}, featuremgmt.WithFeatures())
type conf struct { type conf struct {
AllowAssignGrafanaAdmin bool AllowAssignGrafanaAdmin bool
RoleAttributeStrict bool RoleAttributeStrict bool
AutoAssignOrgRole org.RoleType AutoAssignOrgRole org.RoleType
SkipOrgRoleSync bool SkipOrgRoleSync bool
OrgMapping []string
} }
tests := []struct { tests := []struct {
@ -63,7 +63,7 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
RoleAttributePath string RoleAttributePath string
ExpectedLogin string ExpectedLogin string
ExpectedEmail string ExpectedEmail string
ExpectedRole org.RoleType ExpectedRoles map[int64]org.RoleType
ExpectedGrafanaAdmin *bool ExpectedGrafanaAdmin *bool
ExpectedError error ExpectedError error
}{ }{
@ -81,7 +81,7 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
RoleAttributePath: gitlabAttrPath, RoleAttributePath: gitlabAttrPath,
ExpectedLogin: "root", ExpectedLogin: "root",
ExpectedEmail: "root@example.org", ExpectedEmail: "root@example.org",
ExpectedRole: "Admin", ExpectedRoles: map[int64]org.RoleType{1: "Admin"},
ExpectedGrafanaAdmin: trueBoolPtr(), ExpectedGrafanaAdmin: trueBoolPtr(),
}, },
{ // Edge case, user in Viewer Group, Server Admin disabled but attribute path contains a condition for Server Admin => User has the Admin role { // Edge case, user in Viewer Group, Server Admin disabled but attribute path contains a condition for Server Admin => User has the Admin role
@ -98,7 +98,7 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
RoleAttributePath: gitlabAttrPath, RoleAttributePath: gitlabAttrPath,
ExpectedLogin: "root", ExpectedLogin: "root",
ExpectedEmail: "root@example.org", ExpectedEmail: "root@example.org",
ExpectedRole: "Admin", ExpectedRoles: map[int64]org.RoleType{1: "Admin"},
ExpectedGrafanaAdmin: nil, ExpectedGrafanaAdmin: nil,
}, },
{ {
@ -109,7 +109,7 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
RoleAttributePath: gitlabAttrPath, RoleAttributePath: gitlabAttrPath,
ExpectedLogin: "gitlab-editor", ExpectedLogin: "gitlab-editor",
ExpectedEmail: "gitlab-editor@example.org", ExpectedEmail: "gitlab-editor@example.org",
ExpectedRole: "Editor", ExpectedRoles: map[int64]org.RoleType{1: "Editor"},
ExpectedGrafanaAdmin: falseBoolPtr(), ExpectedGrafanaAdmin: falseBoolPtr(),
GroupHeaders: map[string]string{ GroupHeaders: map[string]string{
// All headers omitted to test that the provider does not make a second request // All headers omitted to test that the provider does not make a second request
@ -123,7 +123,7 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
RoleAttributePath: gitlabAttrPath, RoleAttributePath: gitlabAttrPath,
ExpectedLogin: "gitlab-editor", ExpectedLogin: "gitlab-editor",
ExpectedEmail: "gitlab-editor@example.org", ExpectedEmail: "gitlab-editor@example.org",
ExpectedRole: "", ExpectedRoles: nil,
ExpectedGrafanaAdmin: nilPointer, ExpectedGrafanaAdmin: nilPointer,
}, },
{ // Fallback to autoAssignOrgRole { // Fallback to autoAssignOrgRole
@ -134,7 +134,7 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
RoleAttributePath: gitlabAttrPath, RoleAttributePath: gitlabAttrPath,
ExpectedLogin: "gitlab-editor", ExpectedLogin: "gitlab-editor",
ExpectedEmail: "gitlab-editor@example.org", ExpectedEmail: "gitlab-editor@example.org",
ExpectedRole: "Admin", ExpectedRoles: map[int64]org.RoleType{1: "Admin"},
}, },
{ {
Name: "Strict mode prevents fallback to default", Name: "Strict mode prevents fallback to default",
@ -146,13 +146,13 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
}, },
{ // Edge case, no match, no strict mode and no fallback => User has the Viewer role (hard coded) { // Edge case, no match, no strict mode and no fallback => User has the Viewer role (hard coded)
Name: "Fallback with no default will create a user with a default role", Name: "Fallback with no default will create a user with a default role",
Cfg: conf{}, Cfg: conf{AutoAssignOrgRole: org.RoleViewer},
UserRespBody: editorUserRespBody, UserRespBody: editorUserRespBody,
GroupsRespBody: "[]", GroupsRespBody: "[]",
RoleAttributePath: gitlabAttrPath, RoleAttributePath: gitlabAttrPath,
ExpectedLogin: "gitlab-editor", ExpectedLogin: "gitlab-editor",
ExpectedEmail: "gitlab-editor@example.org", ExpectedEmail: "gitlab-editor@example.org",
ExpectedRole: "Viewer", ExpectedRoles: map[int64]org.RoleType{1: "Viewer"},
}, },
{ // Edge case, no attribute path with strict mode => Error { // Edge case, no attribute path with strict mode => Error
Name: "Strict mode with no attribute path", Name: "Strict mode with no attribute path",
@ -160,49 +160,88 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
UserRespBody: editorUserRespBody, UserRespBody: editorUserRespBody,
GroupsRespBody: "[" + strings.Join([]string{editorGroup}, ",") + "]", GroupsRespBody: "[" + strings.Join([]string{editorGroup}, ",") + "]",
RoleAttributePath: "", RoleAttributePath: "",
ExpectedError: errRoleAttributePathNotSet, ExpectedError: errRoleAttributeStrictViolation,
},
{
Name: "Should map role when only org mapping is set",
Cfg: conf{OrgMapping: []string{"editors:Org4:Editor", "*:Org5:Viewer"}},
UserRespBody: editorUserRespBody,
GroupsRespBody: "[" + strings.Join([]string{editorGroup}, ",") + "]",
ExpectedLogin: "gitlab-editor",
ExpectedEmail: "gitlab-editor@example.org",
ExpectedRoles: map[int64]org.RoleType{4: "Editor", 5: "Viewer"},
},
{
Name: "Should map role when only org mapping is set and role attribute strict is enabled",
Cfg: conf{OrgMapping: []string{"editors:Org4:Editor", "*:Org5:Viewer"}, RoleAttributeStrict: true},
UserRespBody: editorUserRespBody,
GroupsRespBody: "[" + strings.Join([]string{editorGroup}, ",") + "]",
ExpectedLogin: "gitlab-editor",
ExpectedEmail: "gitlab-editor@example.org",
ExpectedRoles: map[int64]org.RoleType{4: "Editor", 5: "Viewer"},
},
{
Name: "Should return error when neither role attribute path nor org mapping evaluates to a role and role attribute strict is enabled",
Cfg: conf{RoleAttributeStrict: true, OrgMapping: []string{"other:Org4:Editor"}},
UserRespBody: editorUserRespBody,
GroupsRespBody: "[" + strings.Join([]string{editorGroup}, ",") + "]",
ExpectedError: errRoleAttributeStrictViolation,
},
{
Name: "should return error when neither role attribute path nor org mapping is set and role attribute strict is enabled",
Cfg: conf{RoleAttributeStrict: true},
UserRespBody: editorUserRespBody,
GroupsRespBody: "[" + strings.Join([]string{editorGroup}, ",") + "]",
ExpectedError: errRoleAttributeStrictViolation,
}, },
} }
for _, test := range tests { for _, tt := range tests {
provider.info.RoleAttributePath = test.RoleAttributePath t.Run(tt.Name, func(t *testing.T) {
provider.info.AllowAssignGrafanaAdmin = test.Cfg.AllowAssignGrafanaAdmin cfg := setting.NewCfg()
provider.cfg.AutoAssignOrgRole = string(test.Cfg.AutoAssignOrgRole) cfg.AutoAssignOrgRole = string(tt.Cfg.AutoAssignOrgRole)
provider.info.RoleAttributeStrict = test.Cfg.RoleAttributeStrict
provider.info.SkipOrgRoleSync = test.Cfg.SkipOrgRoleSync orgMapper := ProvideOrgRoleMapper(cfg, &orgtest.FakeOrgService{ExpectedOrgs: []*org.OrgDTO{{ID: 4, Name: "Org4"}, {ID: 5, Name: "Org5"}}})
provider := NewGitLabProvider(&social.OAuthInfo{
RoleAttributePath: tt.RoleAttributePath,
RoleAttributeStrict: tt.Cfg.RoleAttributeStrict,
AllowAssignGrafanaAdmin: tt.Cfg.AllowAssignGrafanaAdmin,
SkipOrgRoleSync: tt.Cfg.SkipOrgRoleSync,
OrgMapping: tt.Cfg.OrgMapping,
// OrgAttributePath: "",
}, cfg, orgMapper, &ssosettingstests.MockService{}, featuremgmt.WithFeatures())
t.Run(test.Name, func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
switch r.RequestURI { switch r.RequestURI {
case userURI: case userURI:
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(test.UserRespBody)) _, err := w.Write([]byte(tt.UserRespBody))
require.NoError(t, err) require.NoError(t, err)
case groupsURI: case groupsURI:
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
for k, v := range test.GroupHeaders { for k, v := range tt.GroupHeaders {
w.Header().Set(k, v) w.Header().Set(k, v)
} }
_, err := w.Write([]byte(test.GroupsRespBody)) _, err := w.Write([]byte(tt.GroupsRespBody))
require.NoError(t, err) require.NoError(t, err)
default: default:
w.WriteHeader(http.StatusOK)
require.Fail(t, "unexpected request URI: "+r.RequestURI) require.Fail(t, "unexpected request URI: "+r.RequestURI)
} }
})) }))
provider.info.ApiUrl = ts.URL + apiURI provider.info.ApiUrl = ts.URL + apiURI
actualResult, err := provider.UserInfo(context.Background(), ts.Client(), &oauth2.Token{}) actualResult, err := provider.UserInfo(context.Background(), ts.Client(), &oauth2.Token{})
if test.ExpectedError != nil { if tt.ExpectedError != nil {
require.ErrorIs(t, err, test.ExpectedError) require.ErrorIs(t, err, tt.ExpectedError)
return return
} }
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, test.ExpectedEmail, actualResult.Email) require.Equal(t, tt.ExpectedEmail, actualResult.Email)
require.Equal(t, test.ExpectedLogin, actualResult.Login) require.Equal(t, tt.ExpectedLogin, actualResult.Login)
require.Equal(t, test.ExpectedRole, actualResult.Role) require.Equal(t, tt.ExpectedRoles, actualResult.OrgRoles)
require.Equal(t, test.ExpectedGrafanaAdmin, actualResult.IsGrafanaAdmin) require.Equal(t, tt.ExpectedGrafanaAdmin, actualResult.IsGrafanaAdmin)
}) })
} }
} }
@ -267,14 +306,12 @@ func TestSocialGitlab_extractFromToken(t *testing.T) {
}, },
}, },
wantUser: &userData{ wantUser: &userData{
ID: "12345678", ID: "12345678",
Login: "johndoe", Login: "johndoe",
Email: "johndoe@example.com", Email: "johndoe@example.com",
Name: "John Doe", Name: "John Doe",
Groups: []string{"admins", "editors", "viewers"}, Groups: []string{"admins", "editors", "viewers"},
EmailVerified: true, EmailVerified: true,
Role: "Viewer",
IsGrafanaAdmin: nil,
}, },
}, },
{ {
@ -334,14 +371,12 @@ func TestSocialGitlab_extractFromToken(t *testing.T) {
}, },
}, },
wantUser: &userData{ wantUser: &userData{
ID: "12345678", ID: "12345678",
Login: "johndoe", Login: "johndoe",
Email: "johndoe@example.com", Email: "johndoe@example.com",
Name: "John Doe", Name: "John Doe",
Groups: []string{"admins"}, Groups: []string{"admins"},
EmailVerified: true, EmailVerified: true,
Role: "Viewer",
IsGrafanaAdmin: nil,
}, },
}, },
} }

View File

@ -168,8 +168,8 @@ func (c *OAuth) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
// This is required to implement OrgRole mapping for OAuth providers step by step // This is required to implement OrgRole mapping for OAuth providers step by step
switch c.providerName { switch c.providerName {
case social.GenericOAuthProviderName, social.GitHubProviderName: case social.GenericOAuthProviderName, social.GitHubProviderName, social.GitlabProviderName:
// Do nothing, GenericOAuthProvider and GitHub already supports OrgRole mapping // Do nothing, these providers already supports OrgRole mapping
default: default:
userInfo.OrgRoles, userInfo.IsGrafanaAdmin, _ = getRoles(c.cfg, func() (org.RoleType, *bool, error) { userInfo.OrgRoles, userInfo.IsGrafanaAdmin, _ = getRoles(c.cfg, func() (org.RoleType, *bool, error) {
return userInfo.Role, userInfo.IsGrafanaAdmin, nil return userInfo.Role, userInfo.IsGrafanaAdmin, nil