mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Auth: Add skip_org_role_sync setting for github (#61673)
* add: skip_org_role_sync setting for github * fix: frontend * rearranged tests * refactor: assignGrafanaAdmin skip also * Add: tests for allowGrafanaAdmin - both for the case when both settings are set and the setting for only allowGrafanaAdmin * Apply suggestions from code review Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * Update pkg/login/social/github_oauth.go Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * added vairable inside scope * Update docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md * Update docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
This commit is contained in:
@@ -904,6 +904,21 @@ The following table shows the OAuth provider's setting with the default value an
|
|||||||
| Google | false | true | User organization roles are set with `defaultRole` and the org role can be changed for Google synced users. |
|
| Google | false | true | User organization roles are set with `defaultRole` and the org role can be changed for Google synced users. |
|
||||||
| Google | true | true | User organization roles are set with `defaultRole` for Google. For other providers, the synchronization will be skipped, and the org role can be changed, along with other OAuth provider users' org roles. |
|
| Google | true | true | User organization roles are set with `defaultRole` for Google. For other providers, the synchronization will be skipped, and the org role can be changed, along with other OAuth provider users' org roles. |
|
||||||
|
|
||||||
|
### [auth.github] skip_org_role_sync
|
||||||
|
|
||||||
|
When a user logs in the first time, Grafana sets the organization role based on the value specified in `AutoAssignOrgRole`. If you want to manage organization roles, set the `skip_org_role_sync` option to `true`. GitHub syncs organization roles and sets Grafana Admins.
|
||||||
|
This also impacts `allow_assign_grafana_admin` setting, by not syncing the grafana admin role from GitHub.
|
||||||
|
|
||||||
|
> **Note:** There is a separate setting called `oauth_skip_org_role_update_sync` which has a different scope. While `skip_org_role_sync` only applies to the specific OAuth provider, `oauth_skip_org_role_update_sync` is a generic setting that affects all configured OAuth providers.
|
||||||
|
|
||||||
|
The following table shows the OAuth provider's setting with the default value and the skip org role sync setting.
|
||||||
|
| OAuth Provider | `oauth_skip_org_role_sync_update` | `skip_org_role_sync` | Behavior |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| GitHub | false | false | User organization roles are set with `defaultRole` and cannot be changed |
|
||||||
|
| Github | true | false | User organization roles are set with `defaultRole` for GitHub, and Grafana Admins are set. For other providers, the synchronization is skipped, and the org role can be changed, along with other OAuth provider users' org roles. |
|
||||||
|
| GitHub | false | true | User organization roles are set with `defaultRole`, and the organization role can be changed for GitHub synced users. |
|
||||||
|
| GitHub | true | true | User organization roles are set with `defaultRole` for Google. For other providers, the synchronization is skipped, and the org role can be changed, along with other OAuth provider users' org roles. |
|
||||||
|
|
||||||
### [auth.gitlab] skip_org_role_sync
|
### [auth.gitlab] skip_org_role_sync
|
||||||
|
|
||||||
When a user logs in the first time, Grafana sets the organization role based on the value specified in `AutoAssignOrgRole`. If you want to manage organization roles, set the `skip_org_role_sync` option to `true`. GitLab syncs organization roles and sets Grafana Admins.
|
When a user logs in the first time, Grafana sets the organization role based on the value specified in `AutoAssignOrgRole`. If you want to manage organization roles, set the `skip_org_role_sync` option to `true`. GitLab syncs organization roles and sets Grafana Admins.
|
||||||
|
|||||||
@@ -203,3 +203,16 @@ Your GitHub teams can be referenced in two ways:
|
|||||||
Example: `@grafana/developers`
|
Example: `@grafana/developers`
|
||||||
|
|
||||||
[Learn more about Team Sync]({{< relref "../../configure-team-sync/" >}})
|
[Learn more about Team Sync]({{< relref "../../configure-team-sync/" >}})
|
||||||
|
|
||||||
|
## Skip organization role sync
|
||||||
|
|
||||||
|
To prevent the sync of organization roles from GitHub, set `skip_org_role_sync` to `true`. This is useful if you want to manage the organization roles for your users from within Grafana.
|
||||||
|
This also impacts the `allow_assign_grafana_admin` setting by not syncing the Grafana admin role from GitHub.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[auth.github]
|
||||||
|
# ..
|
||||||
|
# prevents the sync of org roles from Github
|
||||||
|
skip_org_role_sync = true
|
||||||
|
``
|
||||||
|
```
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ export interface AuthSettings {
|
|||||||
LDAPSkipOrgRoleSync?: boolean;
|
LDAPSkipOrgRoleSync?: boolean;
|
||||||
JWTAuthSkipOrgRoleSync?: boolean;
|
JWTAuthSkipOrgRoleSync?: boolean;
|
||||||
GrafanaComSkipOrgRoleSync?: boolean;
|
GrafanaComSkipOrgRoleSync?: boolean;
|
||||||
|
GithubSkipOrgRoleSync?: boolean;
|
||||||
GitLabSkipOrgRoleSync?: boolean;
|
GitLabSkipOrgRoleSync?: boolean;
|
||||||
AzureADSkipOrgRoleSync?: boolean;
|
AzureADSkipOrgRoleSync?: boolean;
|
||||||
GoogleSkipOrgRoleSync?: boolean;
|
GoogleSkipOrgRoleSync?: boolean;
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
|
|||||||
"OAuthSkipOrgRoleUpdateSync": hs.Cfg.OAuthSkipOrgRoleUpdateSync,
|
"OAuthSkipOrgRoleUpdateSync": hs.Cfg.OAuthSkipOrgRoleUpdateSync,
|
||||||
"SAMLSkipOrgRoleSync": hs.Cfg.SectionWithEnvOverrides("auth.saml").Key("skip_org_role_sync").MustBool(false),
|
"SAMLSkipOrgRoleSync": hs.Cfg.SectionWithEnvOverrides("auth.saml").Key("skip_org_role_sync").MustBool(false),
|
||||||
"LDAPSkipOrgRoleSync": hs.Cfg.LDAPSkipOrgRoleSync,
|
"LDAPSkipOrgRoleSync": hs.Cfg.LDAPSkipOrgRoleSync,
|
||||||
|
"GithubSkipOrgRoleSync": hs.Cfg.GithubSkipOrgRoleSync,
|
||||||
"GoogleSkipOrgRoleSync": hs.Cfg.GoogleSkipOrgRoleSync,
|
"GoogleSkipOrgRoleSync": hs.Cfg.GoogleSkipOrgRoleSync,
|
||||||
"JWTAuthSkipOrgRoleSync": hs.Cfg.JWTAuthSkipOrgRoleSync,
|
"JWTAuthSkipOrgRoleSync": hs.Cfg.JWTAuthSkipOrgRoleSync,
|
||||||
"GrafanaComSkipOrgRoleSync": hs.Cfg.GrafanaComSkipOrgRoleSync,
|
"GrafanaComSkipOrgRoleSync": hs.Cfg.GrafanaComSkipOrgRoleSync,
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/models/roletype"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SocialGithub struct {
|
type SocialGithub struct {
|
||||||
@@ -15,6 +17,7 @@ type SocialGithub struct {
|
|||||||
allowedOrganizations []string
|
allowedOrganizations []string
|
||||||
apiUrl string
|
apiUrl string
|
||||||
teamIds []int
|
teamIds []int
|
||||||
|
skipOrgRoleSync bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type GithubTeam struct {
|
type GithubTeam struct {
|
||||||
@@ -201,14 +204,25 @@ func (s *SocialGithub) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
|
|||||||
|
|
||||||
teams := convertToGroupList(teamMemberships)
|
teams := convertToGroupList(teamMemberships)
|
||||||
|
|
||||||
role, grafanaAdmin := s.extractRoleAndAdmin(response.Body, teams, true)
|
var role roletype.RoleType
|
||||||
if s.roleAttributeStrict && !role.IsValid() {
|
var isGrafanaAdmin *bool = nil
|
||||||
return nil, &InvalidBasicRoleError{idP: "Github", assignedRole: string(role)}
|
|
||||||
|
if !s.skipOrgRoleSync {
|
||||||
|
var grafanaAdmin bool
|
||||||
|
role, grafanaAdmin = s.extractRoleAndAdmin(response.Body, teams, true)
|
||||||
|
|
||||||
|
if s.roleAttributeStrict && !role.IsValid() {
|
||||||
|
return nil, &InvalidBasicRoleError{idP: "Github", assignedRole: string(role)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.allowAssignGrafanaAdmin {
|
||||||
|
isGrafanaAdmin = &grafanaAdmin
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isGrafanaAdmin *bool = nil
|
// we skip allowing assignment of GrafanaAdmin if skipOrgRoleSync is present
|
||||||
if s.allowAssignGrafanaAdmin {
|
if s.allowAssignGrafanaAdmin && s.skipOrgRoleSync {
|
||||||
isGrafanaAdmin = &grafanaAdmin
|
s.log.Debug("allowAssignGrafanaAdmin and skipOrgRoleSync are both set, Grafana Admin role will not be synced, consider setting one or the other")
|
||||||
}
|
}
|
||||||
|
|
||||||
userInfo := &BasicUserInfo{
|
userInfo := &BasicUserInfo{
|
||||||
|
|||||||
@@ -112,11 +112,14 @@ const testGHUserJSON = `{
|
|||||||
}`
|
}`
|
||||||
|
|
||||||
func TestSocialGitHub_UserInfo(t *testing.T) {
|
func TestSocialGitHub_UserInfo(t *testing.T) {
|
||||||
|
var boolPointer *bool
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
userRawJSON string
|
userRawJSON string
|
||||||
userTeamsRawJSON string
|
userTeamsRawJSON string
|
||||||
settingAutoAssignOrgRole string
|
settingAutoAssignOrgRole string
|
||||||
|
settingAllowGrafanaAdmin bool
|
||||||
|
settingSkipOrgRoleSync bool
|
||||||
roleAttributePath string
|
roleAttributePath string
|
||||||
autoAssignOrgRole string
|
autoAssignOrgRole string
|
||||||
want *BasicUserInfo
|
want *BasicUserInfo
|
||||||
@@ -167,6 +170,38 @@ func TestSocialGitHub_UserInfo(t *testing.T) {
|
|||||||
Groups: []string{"https://github.com/orgs/github/teams/justice-league", "@github/justice-league"},
|
Groups: []string{"https://github.com/orgs/github/teams/justice-league", "@github/justice-league"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Should be empty role if setting skipOrgRoleSync is set to true",
|
||||||
|
roleAttributePath: "contains(groups[*], '@github/justice-league') && 'Editor' || 'Viewer'",
|
||||||
|
settingSkipOrgRoleSync: true,
|
||||||
|
userRawJSON: testGHUserJSON,
|
||||||
|
userTeamsRawJSON: testGHUserTeamsJSON,
|
||||||
|
want: &BasicUserInfo{
|
||||||
|
Id: "1",
|
||||||
|
Name: "monalisa octocat",
|
||||||
|
Email: "octocat@github.com",
|
||||||
|
Login: "octocat",
|
||||||
|
Role: "",
|
||||||
|
Groups: []string{"https://github.com/orgs/github/teams/justice-league", "@github/justice-league"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should return nil pointer if allowGrafanaAdmin and skipOrgRoleSync setting is set to true",
|
||||||
|
roleAttributePath: "contains(groups[*], '@github/justice-league') && 'Editor' || 'Viewer'",
|
||||||
|
settingSkipOrgRoleSync: true,
|
||||||
|
settingAllowGrafanaAdmin: true,
|
||||||
|
userRawJSON: testGHUserJSON,
|
||||||
|
userTeamsRawJSON: testGHUserTeamsJSON,
|
||||||
|
want: &BasicUserInfo{
|
||||||
|
Id: "1",
|
||||||
|
Name: "monalisa octocat",
|
||||||
|
Email: "octocat@github.com",
|
||||||
|
Login: "octocat",
|
||||||
|
Role: "",
|
||||||
|
Groups: []string{"https://github.com/orgs/github/teams/justice-league", "@github/justice-league"},
|
||||||
|
IsGrafanaAdmin: boolPointer,
|
||||||
|
},
|
||||||
|
},
|
||||||
{ // Case that's going to change with Grafana 10
|
{ // Case that's going to change with Grafana 10
|
||||||
name: "No fallback to default org role (will change in Grafana 10)",
|
name: "No fallback to default org role (will change in Grafana 10)",
|
||||||
roleAttributePath: "",
|
roleAttributePath: "",
|
||||||
@@ -208,6 +243,7 @@ func TestSocialGitHub_UserInfo(t *testing.T) {
|
|||||||
allowedOrganizations: []string{},
|
allowedOrganizations: []string{},
|
||||||
apiUrl: server.URL + "/user",
|
apiUrl: server.URL + "/user",
|
||||||
teamIds: []int{},
|
teamIds: []int{},
|
||||||
|
skipOrgRoleSync: tt.settingSkipOrgRoleSync,
|
||||||
}
|
}
|
||||||
|
|
||||||
token := &oauth2.Token{
|
token := &oauth2.Token{
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ func ProvideService(cfg *setting.Cfg, features *featuremgmt.FeatureManager) *Soc
|
|||||||
apiUrl: info.ApiUrl,
|
apiUrl: info.ApiUrl,
|
||||||
teamIds: sec.Key("team_ids").Ints(","),
|
teamIds: sec.Key("team_ids").Ints(","),
|
||||||
allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()),
|
allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()),
|
||||||
|
skipOrgRoleSync: cfg.GithubSkipOrgRoleSync,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -467,6 +467,9 @@ type Cfg struct {
|
|||||||
// then Live uses AppURL as the only allowed origin.
|
// then Live uses AppURL as the only allowed origin.
|
||||||
LiveAllowedOrigins []string
|
LiveAllowedOrigins []string
|
||||||
|
|
||||||
|
// Github OAuth
|
||||||
|
GithubSkipOrgRoleSync bool
|
||||||
|
|
||||||
// Grafana.com URL, used for OAuth redirect.
|
// Grafana.com URL, used for OAuth redirect.
|
||||||
GrafanaComURL string
|
GrafanaComURL string
|
||||||
// Grafana.com API URL. Can be set separately to GrafanaComURL
|
// Grafana.com API URL. Can be set separately to GrafanaComURL
|
||||||
@@ -1375,6 +1378,11 @@ func readAuthGrafanaComSettings(iniFile *ini.File, cfg *Cfg) {
|
|||||||
cfg.GrafanaComSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false)
|
cfg.GrafanaComSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readAuthGithubSettings(iniFile *ini.File, cfg *Cfg) {
|
||||||
|
sec := iniFile.Section("auth.github")
|
||||||
|
cfg.GithubSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false)
|
||||||
|
}
|
||||||
|
|
||||||
func readAuthGoogleSettings(iniFile *ini.File, cfg *Cfg) {
|
func readAuthGoogleSettings(iniFile *ini.File, cfg *Cfg) {
|
||||||
sec := iniFile.Section("auth.google")
|
sec := iniFile.Section("auth.google")
|
||||||
cfg.GoogleSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false)
|
cfg.GoogleSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false)
|
||||||
@@ -1501,7 +1509,11 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
|
|||||||
|
|
||||||
cfg.AuthProxyHeadersEncoded = authProxy.Key("headers_encoded").MustBool(false)
|
cfg.AuthProxyHeadersEncoded = authProxy.Key("headers_encoded").MustBool(false)
|
||||||
|
|
||||||
|
// GrafanaCom
|
||||||
readAuthGrafanaComSettings(iniFile, cfg)
|
readAuthGrafanaComSettings(iniFile, cfg)
|
||||||
|
|
||||||
|
// Github
|
||||||
|
readAuthGithubSettings(iniFile, cfg)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ interface OwnProps extends GrafanaRouteComponentProps<{ id: string }> {
|
|||||||
error?: UserAdminError;
|
error?: UserAdminError;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SyncedOAuthLabels: string[] = ['GitHub', 'OAuth'];
|
const SyncedOAuthLabels: string[] = ['OAuth'];
|
||||||
|
|
||||||
export class UserAdminPage extends PureComponent<Props> {
|
export class UserAdminPage extends PureComponent<Props> {
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
@@ -113,6 +113,7 @@ export class UserAdminPage extends PureComponent<Props> {
|
|||||||
user?.isExternal && user?.authLabels?.some((r) => SyncedOAuthLabels.includes(r));
|
user?.isExternal && user?.authLabels?.some((r) => SyncedOAuthLabels.includes(r));
|
||||||
const isSAMLUser = user?.isExternal && user?.authLabels?.includes('SAML');
|
const isSAMLUser = user?.isExternal && user?.authLabels?.includes('SAML');
|
||||||
const isGoogleUser = user?.isExternal && user?.authLabels?.includes('Google');
|
const isGoogleUser = user?.isExternal && user?.authLabels?.includes('Google');
|
||||||
|
const isGithubUser = user?.isExternal && user?.authLabels?.includes('GitHub');
|
||||||
const isGitLabUser = user?.isExternal && user?.authLabels?.includes('GitLab');
|
const isGitLabUser = user?.isExternal && user?.authLabels?.includes('GitLab');
|
||||||
const isAuthProxyUser = user?.isExternal && user?.authLabels?.includes('Auth Proxy');
|
const isAuthProxyUser = user?.isExternal && user?.authLabels?.includes('Auth Proxy');
|
||||||
const isAzureADUser = user?.isExternal && user?.authLabels?.includes('AzureAD');
|
const isAzureADUser = user?.isExternal && user?.authLabels?.includes('AzureAD');
|
||||||
@@ -127,6 +128,7 @@ export class UserAdminPage extends PureComponent<Props> {
|
|||||||
isOAuthUserWithSkippableSync ||
|
isOAuthUserWithSkippableSync ||
|
||||||
isSAMLUser ||
|
isSAMLUser ||
|
||||||
isLDAPUser ||
|
isLDAPUser ||
|
||||||
|
isGithubUser ||
|
||||||
isAzureADUser ||
|
isAzureADUser ||
|
||||||
isJWTUser ||
|
isJWTUser ||
|
||||||
isGrafanaComUser
|
isGrafanaComUser
|
||||||
@@ -137,6 +139,7 @@ export class UserAdminPage extends PureComponent<Props> {
|
|||||||
(!config.auth.JWTAuthSkipOrgRoleSync && isJWTUser) ||
|
(!config.auth.JWTAuthSkipOrgRoleSync && isJWTUser) ||
|
||||||
// both OAuthSkipOrgRoleUpdateSync and specific provider settings needs to be false for a user to be synced
|
// both OAuthSkipOrgRoleUpdateSync and specific provider settings needs to be false for a user to be synced
|
||||||
(!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.GrafanaComSkipOrgRoleSync && isGrafanaComUser) ||
|
(!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.GrafanaComSkipOrgRoleSync && isGrafanaComUser) ||
|
||||||
|
(!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.GithubSkipOrgRoleSync && isGithubUser) ||
|
||||||
(!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.AzureADSkipOrgRoleSync && isAzureADUser) ||
|
(!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.AzureADSkipOrgRoleSync && isAzureADUser) ||
|
||||||
(!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.GitLabSkipOrgRoleSync && isGitLabUser) ||
|
(!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.GitLabSkipOrgRoleSync && isGitLabUser) ||
|
||||||
(!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.GoogleSkipOrgRoleSync && isGoogleUser));
|
(!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.GoogleSkipOrgRoleSync && isGoogleUser));
|
||||||
|
|||||||
Reference in New Issue
Block a user