diff --git a/conf/defaults.ini b/conf/defaults.ini index 1e3feb44292..6712b15c734 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -644,7 +644,11 @@ token_url = https://oauth2.googleapis.com/token api_url = https://openidconnect.googleapis.com/v1/userinfo allowed_domains = hosted_domain = -skip_org_role_sync = false +allowed_groups = +role_attribute_path = +role_attribute_strict = false +allow_assign_grafana_admin = false +skip_org_role_sync = true tls_skip_verify_insecure = false tls_client_cert = tls_client_key = @@ -828,7 +832,7 @@ assume_role_enabled = true list_metrics_page_limit = 500 # Experimental, for use in Grafana Cloud only. Please do not set. -external_id = +external_id = #################################### Azure ############################### [azure] @@ -1226,7 +1230,7 @@ url = # Tenant ID to use in requests to the Alertmanager. # It will also be used for the basic auth username. -tenant = +tenant = # Optional password for basic authentication. # If not present, the tenant ID will be set in the X-Scope-OrgID header. diff --git a/conf/sample.ini b/conf/sample.ini index e58ab27a662..6972722789c 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -626,6 +626,10 @@ ;api_url = https://openidconnect.googleapis.com/v1/userinfo ;allowed_domains = ;hosted_domain = +;allowed_groups = +;role_attribute_path = +;role_attribute_strict = false +;allow_assign_grafana_admin = false ;skip_org_role_sync = false ;use_pkce = true @@ -781,7 +785,7 @@ ; list_metrics_page_limit = 500 # Experimental, for use in Grafana Cloud only. Please do not set. -; external_id = +; external_id = #################################### Azure ############################### [azure] diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md index 3c649fa07f9..3b0a69f3481 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md @@ -79,15 +79,15 @@ The table below describes all GitLab OAuth configuration options. Like any other | `api_url` | No | Grafana uses `/user` endpoint to obtain GitLab user information compatible with [OpenID UserInfo](https://connect2id.com/products/server/docs/api/userinfo). | `https://gitlab.com/api/v4` | | `name` | No | Name used to refer to the GitLab authentication in the Grafana user interface. | `GitLab` | | `icon` | No | Icon used for GitLab authentication in the Grafana user interface. | `gitlab` | -| `scopes` | No | List of comma- or space-separated GitLab OAuth scopes. | `openid email profile` | +| `scopes` | No | List of comma or space-separated GitLab OAuth scopes. | `openid email profile` | | `allow_sign_up` | No | Whether to allow new Grafana user creation through GitLab login. If set to `false`, then only existing Grafana users can log in with GitLab OAuth. | `true` | | `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 (`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` | | `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` | -| `allowed_domains` | No | List of comma- or space-separated domains. User must belong to at least one domain to log in. | | -| `allowed_groups` | No | List of comma- or space-separated groups. The user should be a member of at least one group to log in. | | +| `allowed_domains` | No | List of comma or space-separated domains. User must belong to at least one domain to log in. | | +| `allowed_groups` | No | List of comma or space-separated groups. The user should be a member of at least one group to log in. | | | `tls_skip_verify_insecure` | No | If set to `true`, the client accepts any certificate presented by the server and any host name in that certificate. _You should only use this for testing_, because this mode leaves SSL/TLS susceptible to man-in-the-middle attacks. | `false` | | `tls_client_cert` | No | The path to the certificate. | | | `tls_client_key` | No | The path to the key. | | @@ -115,7 +115,7 @@ Refresh token fetching and access token expiration check is enabled by default f To limit access to authenticated users that are members of one or more [GitLab groups](https://docs.gitlab.com/ce/user/group/index.html), set `allowed_groups` -to a comma- or space-separated list of groups. +to a comma or space-separated list of groups. GitLab's groups are referenced by the group name. For example, `developers`. To reference a subgroup `frontend`, use `developers/frontend`. Note that in GitLab, the group or subgroup name does not always match its display name, especially if the display name contains spaces or special characters. diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md index 0a0e14234fa..2c0b586c523 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md @@ -100,16 +100,6 @@ This setting is ignored if multiple auth providers are configured to use auto lo auto_login = true ``` -## Skip organization role sync - -We do not currently sync roles from Google and instead set the AutoAssigned role to the user at first login. The default setting for `skip_org_role_sync` is `true`, which means that role modifications can still be made through the user interface. - -```ini -[auth.google] -# .. -skip_org_role_sync = true -``` - ### Configure team sync for Google OAuth > Available in Grafana v10.1.0 and later versions. @@ -132,3 +122,68 @@ With team sync, you can easily add users to teams by utilizing their Google grou The external group ID for a Google group is the group's email address, such as `dev@grafana.com`. To learn more about Team Sync, refer to [Configure Team Sync]({{< relref "../../configure-team-sync" >}}). + +### Configure allowed groups + +> Available in Grafana v10.2.0 and later versions. + +To limit access to authenticated users that are members of one or more groups, set `allowed_groups` +to a comma or space separated list of groups. + +Google groups are referenced by the group email key. For example, `developers@google.com`. + +> Note: Add the `https://www.googleapis.com/auth/cloud-identity.groups.readonly` scope to your Grafana `[auth.google]` scopes configuration to retrieve groups + +## Configure role mapping + +> Available in Grafana v10.2.0 and later versions. + +Unless `skip_org_role_sync` option is enabled, the user's role will be set to the role mapped from Google upon user login. If no mapping is set the default instance role is used. + +The user's role is retrieved using a [JMESPath](http://jmespath.org/examples.html) expression from the `role_attribute_path` configuration option. +To map the server administrator role, use the `allow_assign_grafana_admin` configuration option. + +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`. +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. + +> By default skip_org_role_sync is enabled. skip_org_role_sync will default to false in Grafana v10.3.0 and later versions. + +### Role mapping examples + +This section includes examples of JMESPath expressions used for role mapping. + +#### Map roles using user information from OAuth token + +In this example, the user with email `admin@company.com` has been granted the `Admin` role. +All other users are granted the `Viewer` role. + +```ini +role_attribute_path = email=='admin@company.com' && 'Admin' || 'Viewer' +skip_org_role_sync = false +``` + +#### Map roles using groups + +In this example, the user from Google group 'example-group@google.com' have been granted the `Editor` role. +All other users are granted the `Viewer` role. + +```ini +role_attribute_path = contains(groups[*], 'example-group@google.com') && 'Editor' || 'Viewer' +skip_org_role_sync = false +``` + +> Note: Add the `https://www.googleapis.com/auth/cloud-identity.groups.readonly` scope to your Grafana `[auth.google]` scopes configuration to retrieve groups + +#### Map server administrator role + +In this example, the user with email `admin@company.com` has been granted the `Admin` organization role as well as the Grafana server admin role. +All other users are granted the `Viewer` role. + +```ini +allow_assign_grafana_admin = true +skip_org_role_sync = false +role_attribute_path = email=='admin@company.com' && 'GrafanaAdmin' || 'Viewer' +``` diff --git a/pkg/login/social/azuread_oauth.go b/pkg/login/social/azuread_oauth.go index e7dc8b11b63..640552e9aae 100644 --- a/pkg/login/social/azuread_oauth.go +++ b/pkg/login/social/azuread_oauth.go @@ -23,7 +23,6 @@ type SocialAzureAD struct { *SocialBase cache remotecache.CacheStorage allowedOrganizations []string - allowedGroups []string forceUseGraphAPI bool skipOrgRoleSync bool } @@ -99,7 +98,7 @@ func (s *SocialAzureAD) UserInfo(ctx context.Context, client *http.Client, token return nil, fmt.Errorf("failed to extract groups: %w", err) } s.log.Debug("AzureAD OAuth: extracted groups", "email", email, "groups", fmt.Sprintf("%v", groups)) - if !s.IsGroupMember(groups) { + if !s.isGroupMember(groups) { return nil, errMissingGroupMembership } @@ -182,22 +181,6 @@ func (s *SocialAzureAD) validateIDTokenSignature(ctx context.Context, client *ht return nil, &Error{"AzureAD OAuth: signing key not found"} } -func (s *SocialAzureAD) IsGroupMember(groups []string) bool { - if len(s.allowedGroups) == 0 { - return true - } - - for _, allowedGroup := range s.allowedGroups { - for _, group := range groups { - if group == allowedGroup { - return true - } - } - } - - return false -} - func (claims *azureClaims) extractEmail() string { if claims.Email == "" { if claims.PreferredUsername != "" { diff --git a/pkg/login/social/azuread_oauth_test.go b/pkg/login/social/azuread_oauth_test.go index 5818293d200..d8819629c4b 100644 --- a/pkg/login/social/azuread_oauth_test.go +++ b/pkg/login/social/azuread_oauth_test.go @@ -530,7 +530,6 @@ func TestSocialAzureAD_UserInfo(t *testing.T) { t.Run(tt.name, func(t *testing.T) { s := &SocialAzureAD{ SocialBase: tt.fields.SocialBase, - allowedGroups: tt.fields.allowedGroups, allowedOrganizations: tt.fields.allowedOrganizations, forceUseGraphAPI: tt.fields.forceUseGraphAPI, cache: cache, @@ -540,6 +539,10 @@ func TestSocialAzureAD_UserInfo(t *testing.T) { s.SocialBase = newSocialBase("azuread", &oauth2.Config{ClientID: "client-id-example"}, &OAuthInfo{}, "", false, *featuremgmt.WithFeatures()) } + if tt.fields.allowedGroups != nil { + s.allowedGroups = tt.fields.allowedGroups + } + if tt.fields.usGovURL { s.SocialBase.Endpoint.AuthURL = usGovAuthURL } else { @@ -710,14 +713,15 @@ func TestSocialAzureAD_SkipOrgRole(t *testing.T) { t.Run(tt.name, func(t *testing.T) { s := &SocialAzureAD{ SocialBase: tt.fields.SocialBase, - allowedGroups: tt.fields.allowedGroups, forceUseGraphAPI: tt.fields.forceUseGraphAPI, skipOrgRoleSync: tt.fields.skipOrgRoleSync, cache: cache, } if tt.fields.SocialBase == nil { - s.SocialBase = newSocialBase("azuread", &oauth2.Config{ClientID: "client-id-example"}, &OAuthInfo{}, "", false, *featuremgmt.WithFeatures()) + s.SocialBase = newSocialBase("azuread", &oauth2.Config{ClientID: "client-id-example"}, &OAuthInfo{ + AllowedGroups: tt.fields.allowedGroups, + }, "", false, *featuremgmt.WithFeatures()) } s.SocialBase.Endpoint.AuthURL = authURL diff --git a/pkg/login/social/gitlab_oauth.go b/pkg/login/social/gitlab_oauth.go index 0bf22db646b..70d2eb23730 100644 --- a/pkg/login/social/gitlab_oauth.go +++ b/pkg/login/social/gitlab_oauth.go @@ -22,7 +22,6 @@ const ( type SocialGitlab struct { *SocialBase - allowedGroups []string apiUrl string skipOrgRoleSync bool } @@ -48,22 +47,6 @@ type userData struct { IsGrafanaAdmin *bool `json:"-"` } -func (s *SocialGitlab) isGroupMember(groups []string) bool { - if len(s.allowedGroups) == 0 { - return true - } - - for _, allowedGroup := range s.allowedGroups { - for _, group := range groups { - if group == allowedGroup { - return true - } - } - } - - return false -} - func (s *SocialGitlab) getGroups(ctx context.Context, client *http.Client) []string { groups := make([]string, 0) nextPage := new(int) diff --git a/pkg/login/social/google_oauth.go b/pkg/login/social/google_oauth.go index 21beaccf449..f4957eb4096 100644 --- a/pkg/login/social/google_oauth.go +++ b/pkg/login/social/google_oauth.go @@ -30,6 +30,7 @@ type googleUserData struct { Email string `json:"email"` Name string `json:"name"` EmailVerified bool `json:"email_verified"` + rawJSON []byte `json:"-"` } func (s *SocialGoogle) UserInfo(ctx context.Context, client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) { @@ -59,6 +60,10 @@ func (s *SocialGoogle) UserInfo(ctx context.Context, client *http.Client, token s.log.Warn("Error retrieving groups", "error", errPage) } + if !s.isGroupMember(groups) { + return nil, errMissingGroupMembership + } + userInfo := &BasicUserInfo{ Id: data.ID, Name: data.Name, @@ -69,6 +74,19 @@ func (s *SocialGoogle) UserInfo(ctx context.Context, client *http.Client, token Groups: groups, } + if !s.skipOrgRoleSync { + role, grafanaAdmin, errRole := s.extractRoleAndAdmin(data.rawJSON, groups) + if errRole != nil { + return nil, errRole + } + + if s.allowAssignGrafanaAdmin { + userInfo.IsGrafanaAdmin = &grafanaAdmin + } + + userInfo.Role = role + } + s.log.Debug("Resolved user info", "data", fmt.Sprintf("%+v", userInfo)) return userInfo, nil @@ -98,6 +116,7 @@ func (s *SocialGoogle) extractFromAPI(ctx context.Context, client *http.Client) Name: data.Name, Email: data.Email, EmailVerified: data.EmailVerified, + rawJSON: response.Body, }, nil } @@ -145,6 +164,8 @@ func (s *SocialGoogle) extractFromToken(ctx context.Context, client *http.Client return nil, fmt.Errorf("Error getting user info: %s", err) } + data.rawJSON = rawJSON + return &data, nil } diff --git a/pkg/login/social/google_oauth_test.go b/pkg/login/social/google_oauth_test.go index 783125d0f45..96aa302d490 100644 --- a/pkg/login/social/google_oauth_test.go +++ b/pkg/login/social/google_oauth_test.go @@ -15,6 +15,7 @@ import ( "golang.org/x/oauth2" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models/roletype" ) func TestSocialGoogle_retrieveGroups(t *testing.T) { @@ -239,8 +240,13 @@ func TestSocialGoogle_UserInfo(t *testing.T) { tokenWithoutID := &oauth2.Token{} type fields struct { - Scopes []string - apiURL string + Scopes []string + apiURL string + allowedGroups []string + roleAttributePath string + roleAttributeStrict bool + allowAssignGrafanaAdmin bool + skipOrgRoleSync bool } type args struct { client *http.Client @@ -257,7 +263,8 @@ func TestSocialGoogle_UserInfo(t *testing.T) { { name: "Success id_token", fields: fields{ - Scopes: []string{}, + Scopes: []string{}, + skipOrgRoleSync: true, }, args: args{ token: tokenWithID, @@ -273,7 +280,8 @@ func TestSocialGoogle_UserInfo(t *testing.T) { { name: "Success id_token - groups requested", fields: fields{ - Scopes: []string{"https://www.googleapis.com/auth/cloud-identity.groups.readonly"}, + Scopes: []string{"https://www.googleapis.com/auth/cloud-identity.groups.readonly"}, + skipOrgRoleSync: true, }, args: args{ token: tokenWithID, @@ -310,7 +318,8 @@ func TestSocialGoogle_UserInfo(t *testing.T) { { name: "Legacy API URL", fields: fields{ - apiURL: legacyAPIURL, + apiURL: legacyAPIURL, + skipOrgRoleSync: true, }, args: args{ token: tokenWithoutID, @@ -340,7 +349,8 @@ func TestSocialGoogle_UserInfo(t *testing.T) { { name: "Legacy API URL - no id provided", fields: fields{ - apiURL: legacyAPIURL, + apiURL: legacyAPIURL, + skipOrgRoleSync: true, }, args: args{ token: tokenWithoutID, @@ -426,7 +436,8 @@ func TestSocialGoogle_UserInfo(t *testing.T) { { name: "Success", fields: fields{ - apiURL: "https://openidconnect.googleapis.com/v1/userinfo", + apiURL: "https://openidconnect.googleapis.com/v1/userinfo", + skipOrgRoleSync: true, }, args: args{ token: tokenWithoutID, @@ -478,6 +489,145 @@ func TestSocialGoogle_UserInfo(t *testing.T) { wantErr: true, wantErrMsg: "email is not verified", }, + { + name: "not in allowed Groups", + fields: fields{ + Scopes: []string{"https://www.googleapis.com/auth/cloud-identity.groups.readonly"}, + allowedGroups: []string{"not-that-one"}, + }, + args: args{ + token: tokenWithID, + client: &http.Client{ + Transport: &roundTripperFunc{ + fn: func(req *http.Request) (*http.Response, error) { + resp := httptest.NewRecorder() + _, _ = resp.WriteString(`{ + "memberships": [ + { + "group": "test-group", + "groupKey": { + "id": "test-group@google.com" + }, + "displayName": "Test Group" + } + ], + "nextPageToken": "" + }`) + return resp.Result(), nil + }, + }, + }, + }, + wantData: &BasicUserInfo{ + Id: "88888888888888", + Login: "test@example.com", + Email: "test@example.com", + Name: "Test User", + Groups: []string{"test-group@google.com"}, + }, + wantErr: true, + wantErrMsg: "user not a member of one of the required groups", + }, + { + name: "Role mapping - strict", + fields: fields{ + Scopes: []string{}, + allowedGroups: []string{}, + roleAttributePath: "this", + roleAttributeStrict: true, + }, + args: args{ + token: tokenWithID, + }, + wantData: &BasicUserInfo{ + Id: "88888888888888", + Login: "test@example.com", + Email: "test@example.com", + Name: "Test User", + Groups: []string{"test-group@google.com"}, + }, + wantErr: true, + wantErrMsg: "idP did not return a role attribute, but role_attribute_strict is set", + }, + { + name: "role mapping from id_token - no allowed assign Grafana Admin", + fields: fields{ + Scopes: []string{}, + allowAssignGrafanaAdmin: false, + roleAttributePath: "email_verified && 'GrafanaAdmin'", + }, + args: args{ + token: tokenWithID, + }, + wantData: &BasicUserInfo{ + Id: "88888888888888", + Login: "test@example.com", + Email: "test@example.com", + Name: "Test User", + Role: roletype.RoleAdmin, + IsGrafanaAdmin: nil, + }, + wantErr: false, + }, + { + name: "role mapping from id_token - allowed assign Grafana Admin", + fields: fields{ + Scopes: []string{}, + allowAssignGrafanaAdmin: true, + roleAttributePath: "email_verified && 'GrafanaAdmin'", + }, + args: args{ + token: tokenWithID, + }, + wantData: &BasicUserInfo{ + Id: "88888888888888", + Login: "test@example.com", + Email: "test@example.com", + Name: "Test User", + Role: roletype.RoleAdmin, + IsGrafanaAdmin: trueBoolPtr(), + }, + wantErr: false, + }, + { + name: "mapping from groups", + fields: fields{ + Scopes: []string{"https://www.googleapis.com/auth/cloud-identity.groups.readonly"}, + roleAttributePath: "contains(groups[*], 'test-group@google.com') && 'Editor'", + }, + args: args{ + token: tokenWithID, + client: &http.Client{ + Transport: &roundTripperFunc{ + fn: func(req *http.Request) (*http.Response, error) { + resp := httptest.NewRecorder() + _, _ = resp.WriteString(`{ + "memberships": [ + { + "group": "test-group", + "groupKey": { + "id": "test-group@google.com" + }, + "displayName": "Test Group" + } + ], + "nextPageToken": "" + }`) + return resp.Result(), nil + }, + }, + }, + }, + wantData: &BasicUserInfo{ + Id: "88888888888888", + Login: "test@example.com", + Email: "test@example.com", + Name: "Test User", + Role: "Editor", + Groups: []string{"test-group@google.com"}, + }, + wantErr: false, + }, } for _, tt := range tests { @@ -485,10 +635,15 @@ func TestSocialGoogle_UserInfo(t *testing.T) { s := &SocialGoogle{ apiUrl: tt.fields.apiURL, SocialBase: &SocialBase{ - Config: &oauth2.Config{Scopes: tt.fields.Scopes}, - log: log.NewNopLogger(), - allowSignup: false, + Config: &oauth2.Config{Scopes: tt.fields.Scopes}, + log: log.NewNopLogger(), + allowSignup: false, + allowedGroups: tt.fields.allowedGroups, + roleAttributePath: tt.fields.roleAttributePath, + roleAttributeStrict: tt.fields.roleAttributeStrict, + allowAssignGrafanaAdmin: tt.fields.allowAssignGrafanaAdmin, }, + skipOrgRoleSync: tt.fields.skipOrgRoleSync, } gotData, err := s.UserInfo(context.Background(), tt.args.client, tt.args.token) diff --git a/pkg/login/social/social.go b/pkg/login/social/social.go index 044e4f5a40a..2d81fd8a97c 100644 --- a/pkg/login/social/social.go +++ b/pkg/login/social/social.go @@ -63,6 +63,7 @@ type OAuthInfo struct { TlsClientKey string `toml:"tls_client_key"` TokenUrl string `toml:"token_url"` AllowedDomains []string `toml:"allowed_domains"` + AllowedGroups []string `toml:"allowed_groups"` Scopes []string `toml:"scopes"` AllowAssignGrafanaAdmin bool `toml:"allow_assign_grafana_admin"` AllowSignup bool `toml:"allow_signup"` @@ -120,6 +121,7 @@ func ProvideService(cfg *setting.Cfg, UseRefreshToken: sec.Key("use_refresh_token").MustBool(false), AllowAssignGrafanaAdmin: sec.Key("allow_assign_grafana_admin").MustBool(false), AutoLogin: sec.Key("auto_login").MustBool(false), + AllowedGroups: util.SplitString(sec.Key("allowed_groups").String()), } // when empty_scopes parameter exists and is true, overwrite scope with empty value @@ -178,7 +180,6 @@ func ProvideService(cfg *setting.Cfg, ss.socialMap["gitlab"] = &SocialGitlab{ SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole, cfg.OAuthSkipOrgRoleUpdateSync, *features), apiUrl: info.ApiUrl, - allowedGroups: util.SplitString(sec.Key("allowed_groups").String()), skipOrgRoleSync: cfg.GitLabSkipOrgRoleSync, } } @@ -202,7 +203,6 @@ func ProvideService(cfg *setting.Cfg, SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole, cfg.OAuthSkipOrgRoleUpdateSync, *features), cache: cache, allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()), - allowedGroups: util.SplitString(sec.Key("allowed_groups").String()), forceUseGraphAPI: sec.Key("force_use_graph_api").MustBool(false), skipOrgRoleSync: cfg.AzureADSkipOrgRoleSync, } @@ -305,6 +305,7 @@ type SocialBase struct { allowSignup bool allowAssignGrafanaAdmin bool allowedDomains []string + allowedGroups []string roleAttributePath string roleAttributeStrict bool @@ -356,9 +357,10 @@ func newSocialBase(name string, allowSignup: info.AllowSignup, allowAssignGrafanaAdmin: info.AllowAssignGrafanaAdmin, allowedDomains: info.AllowedDomains, - autoAssignOrgRole: autoAssignOrgRole, + allowedGroups: info.AllowedGroups, roleAttributePath: info.RoleAttributePath, roleAttributeStrict: info.RoleAttributeStrict, + autoAssignOrgRole: autoAssignOrgRole, skipOrgRoleSync: skipOrgRoleSync, features: features, useRefreshToken: info.UseRefreshToken, @@ -571,6 +573,22 @@ func (ss *SocialService) getUsageStats(ctx context.Context) (map[string]interfac return m, nil } +func (s *SocialBase) isGroupMember(groups []string) bool { + if len(s.allowedGroups) == 0 { + return true + } + + for _, allowedGroup := range s.allowedGroups { + for _, group := range groups { + if group == allowedGroup { + return true + } + } + } + + return false +} + func (s *SocialBase) retrieveRawIDToken(idToken interface{}) ([]byte, error) { tokenString, ok := idToken.(string) if !ok { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 42260e9eae7..5c86c56fc4d 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -1497,9 +1497,7 @@ func readAuthGithubSettings(cfg *Cfg) { func readAuthGoogleSettings(cfg *Cfg) { sec := cfg.SectionWithEnvOverrides("auth.google") cfg.GoogleAuthEnabled = sec.Key("enabled").MustBool(false) - // FIXME: for now we skip org role sync for google auth - // as we do not sync organization roles from Google - cfg.GoogleSkipOrgRoleSync = true + cfg.GoogleSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(true) } func readAuthGitlabSettings(cfg *Cfg) {