Auth: Add skip_org_role_sync setting for GrafanaCom (#60553)

* add frontend settings and setting for grafanacom

* removed println

* add skip-org-role-sync on login

* add deprecation notice for this field

* remove println

* remove newline

* change and renamed variables

* fix for reconfiguring the settings for grafanacom

* add documentationf or grafanacom setup

* WIP tests

* added tests

* Update docs/sources/setup-grafana/configure-security/configure-authentication/grafana-com/index.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/setup-grafana/configure-security/configure-authentication/grafana-com/index.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/setup-grafana/configure-security/configure-authentication/grafana-com/index.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/setup-grafana/configure-security/configure-authentication/grafana-com/index.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/setup-grafana/configure-security/configure-authentication/grafana-com/index.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/setup-grafana/configure-security/configure-authentication/grafana-com/index.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/setup-grafana/configure-security/configure-authentication/grafana-com/index.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/setup-grafana/configure-security/configure-authentication/grafana-com/index.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* updated steps

* Update docs/sources/setup-grafana/configure-security/configure-authentication/grafana-com/index.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/setup-grafana/configure-security/configure-authentication/grafana-com/index.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* doc: updated the docs to reflect what happens to grafana.com users

* Update docs/sources/setup-grafana/configure-grafana/_index.md

Co-authored-by: Jo <joao.guerreiro@grafana.com>

* Update docs/sources/setup-grafana/configure-security/configure-authentication/grafana-com/index.md

Co-authored-by: Jo <joao.guerreiro@grafana.com>

* Update docs/sources/setup-grafana/configure-security/configure-authentication/grafana-com/index.md

Co-authored-by: Jo <joao.guerreiro@grafana.com>

* add blankline

* rephrase of doc improvements for explaing of the settings

* Update docs/sources/setup-grafana/configure-grafana/_index.md

Co-authored-by: Jo <joao.guerreiro@grafana.com>

* add frontend setting for grafanacom.

* WIP tests

* refactor docs

* frontend to adhere to skipping org role sync for GrafanaCom users

* update docs to reflect desired behavior

* tests: added test for skip and nonskip

* Update docs/sources/setup-grafana/configure-grafana/_index.md

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>

* Update docs/sources/setup-grafana/configure-grafana/_index.md

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>

* Update docs/sources/setup-grafana/configure-grafana/_index.md

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>

* Update docs/sources/setup-grafana/configure-grafana/_index.md

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>

* Update docs/sources/setup-grafana/configure-grafana/_index.md

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
Co-authored-by: Jo <joao.guerreiro@grafana.com>
Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
This commit is contained in:
Eric Leijonmarck
2023-01-12 16:44:08 +01:00
committed by GitHub
co-authored by Christopher Moyer Jo Ieva
parent e7b8b82c14
commit 91322bebb5
12 changed files with 278 additions and 16 deletions
+1
View File
@@ -148,6 +148,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
"OAuthSkipOrgRoleUpdateSync": hs.Cfg.OAuthSkipOrgRoleUpdateSync,
"SAMLSkipOrgRoleSync": hs.Cfg.SectionWithEnvOverrides("auth.saml").Key("skip_org_role_sync").MustBool(false),
"LDAPSkipOrgRoleSync": hs.Cfg.LDAPSkipOrgRoleSync,
"GrafanaComSkipOrgRoleSync": hs.Cfg.GrafanaComSkipOrgRoleSync,
"DisableSyncLock": hs.Cfg.DisableSyncLock,
},
"buildInfo": map[string]interface{}{
+82 -11
View File
@@ -14,14 +14,29 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/models/roletype"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/hooks"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/secrets/fakes"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
)
func setupSocialHTTPServerWithConfig(t *testing.T, cfg *setting.Cfg) *HTTPServer {
sqlStore := db.InitTestDB(t)
return &HTTPServer{
Cfg: cfg,
License: &licensing.OSSLicensingService{Cfg: cfg},
SQLStore: sqlStore,
SocialService: social.ProvideService(cfg, featuremgmt.WithFeatures()),
HooksService: hooks.ProvideService(),
SecretsService: fakes.NewFakeSecretsService(),
}
}
func setupOAuthTest(t *testing.T, cfg *setting.Cfg) *web.Mux {
t.Helper()
@@ -29,17 +44,7 @@ func setupOAuthTest(t *testing.T, cfg *setting.Cfg) *web.Mux {
cfg = setting.NewCfg()
}
cfg.ErrTemplateName = "error-template"
sqlStore := db.InitTestDB(t)
hs := &HTTPServer{
Cfg: cfg,
License: &licensing.OSSLicensingService{Cfg: cfg},
SQLStore: sqlStore,
SocialService: social.ProvideService(cfg, featuremgmt.WithFeatures()),
HooksService: hooks.ProvideService(),
SecretsService: fakes.NewFakeSecretsService(),
}
hs := setupSocialHTTPServerWithConfig(t, cfg)
m := web.New()
m.Use(getContextHandler(t, cfg).Middleware)
@@ -159,3 +164,69 @@ func TestOAuthLogin_UsePKCE(t *testing.T) {
base64.RawURLEncoding.EncodeToString(shasum[:]),
)
}
func TestOAuthLogin_BuildExternalUserInfo(t *testing.T) {
t.Helper()
cfgOAuthSkipRoleSync := setting.NewCfg()
authOAuthSec := cfgOAuthSkipRoleSync.Raw.Section("auth")
_, err := authOAuthSec.NewKey("oauth_skip_org_role_update_sync", "true")
require.NoError(t, err)
cfgOAuthSkipRoleSync.ErrTemplateName = "error-template"
cfgOAuthOrgRoleSync := setting.NewCfg()
authOAutoWithoutSec := cfgOAuthOrgRoleSync.Raw.Section("auth")
_, err = authOAutoWithoutSec.NewKey("oauth_skip_org_role_update_sync", "false")
require.NoError(t, err)
cfgOAuthOrgRoleSync.ErrTemplateName = "error-template"
testcases := []struct {
name string
cfg *setting.Cfg
basicUser *social.BasicUserInfo
expectedOrgRoles map[int64]org.RoleType
}{
{
name: "should return empty map of org role mapping if the role for the basic info is empty",
cfg: cfgOAuthOrgRoleSync,
basicUser: &social.BasicUserInfo{
Id: "1",
Name: "first lastname",
Email: "example@github.com",
Login: "example",
Role: "",
},
expectedOrgRoles: map[int64]org.RoleType{},
},
{
name: "should set internal role if role exists and we are skipping org role sync",
cfg: cfgOAuthSkipRoleSync,
basicUser: &social.BasicUserInfo{
Id: "1",
Name: "first lastname",
Email: "example@github.com",
Login: "example",
Role: roletype.RoleAdmin,
},
expectedOrgRoles: map[int64]org.RoleType{1: roletype.RoleAdmin},
},
{
name: "should return empty external role, if the role for the basic info is empty",
cfg: cfgOAuthSkipRoleSync,
basicUser: &social.BasicUserInfo{
Id: "1",
Name: "first lastname",
Email: "example@github.com",
Login: "example",
Role: "",
},
expectedOrgRoles: map[int64]org.RoleType{},
},
}
for _, tc := range testcases {
t.Logf("%s", tc.name)
cfg := tc.cfg
hs := setupSocialHTTPServerWithConfig(t, cfg)
externalUser := hs.buildExternalUserInfo(nil, tc.basicUser, "")
require.Equal(t, tc.expectedOrgRoles, externalUser.OrgRoles)
}
}
+9 -1
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"github.com/grafana/grafana/pkg/models/roletype"
"github.com/grafana/grafana/pkg/services/org"
"golang.org/x/oauth2"
@@ -14,6 +15,7 @@ type SocialGrafanaCom struct {
*SocialBase
url string
allowedOrganizations []string
skipOrgRoleSync bool
}
type OrgRecord struct {
@@ -40,6 +42,7 @@ func (s *SocialGrafanaCom) IsOrganizationMember(organizations []OrgRecord) bool
return false
}
// UserInfo is used for login credentials for the user
func (s *SocialGrafanaCom) UserInfo(client *http.Client, _ *oauth2.Token) (*BasicUserInfo, error) {
var data struct {
Id int `json:"id"`
@@ -60,12 +63,17 @@ func (s *SocialGrafanaCom) UserInfo(client *http.Client, _ *oauth2.Token) (*Basi
return nil, fmt.Errorf("Error getting user info: %s", err)
}
// on login we do not want to display the role from the external provider
var role roletype.RoleType
if !s.skipOrgRoleSync {
role = org.RoleType(data.Role)
}
userInfo := &BasicUserInfo{
Id: fmt.Sprintf("%d", data.Id),
Name: data.Name,
Login: data.Login,
Email: data.Email,
Role: org.RoleType(data.Role),
Role: role,
}
if !s.IsOrganizationMember(data.Orgs) {
@@ -0,0 +1,94 @@
package social
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
)
const (
UserInfoURL = "/api/oauth2/user"
userResponse = `{
"id": 123,
"name": "grafana name",
"login": "grafanalogin",
"username": "grafanalogin",
"email": "grafana@grafana.com",
"role": "Admin",
"orgs": [ { "login": "grafana", "role": "Admin" } ]
}`
)
func TestSocialGrafanaCom_UserInfo(t *testing.T) {
provider := SocialGrafanaCom{
SocialBase: &SocialBase{
log: newLogger("grafana_com_oauth_test", "debug"),
},
}
type conf struct {
skipOrgRoleSync bool
}
tests := []struct {
Name string
Cfg conf
userInfoResp string
want *BasicUserInfo
ExpectedError error
}{
{
Name: "should return empty role as userInfo when Skip Org Role Sync Enabled",
userInfoResp: userResponse,
Cfg: conf{skipOrgRoleSync: true},
want: &BasicUserInfo{
Id: "1",
Name: "Eric Leijonmarck",
Email: "octocat@github.com",
Login: "octocat",
Role: "",
},
},
{
Name: "should return role as userInfo when Skip Org Role Sync Enabled",
userInfoResp: userResponse,
Cfg: conf{skipOrgRoleSync: false},
want: &BasicUserInfo{
Id: "1",
Name: "Eric Leijonmarck",
Email: "octocat@github.com",
Login: "octocat",
Role: "Admin",
},
},
}
for _, test := range tests {
provider.skipOrgRoleSync = test.Cfg.skipOrgRoleSync
t.Run(test.Name, func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
switch r.RequestURI {
case UserInfoURL:
_, err := w.Write([]byte(test.userInfoResp))
require.NoError(t, err)
default:
w.WriteHeader(http.StatusNotFound)
}
}))
provider.url = ts.URL
actualResult, err := provider.UserInfo(ts.Client(), nil)
if test.ExpectedError != nil {
require.Equal(t, err, test.ExpectedError)
return
}
require.NoError(t, err)
require.Equal(t, test.want.Role, actualResult.Role)
})
}
}
+1
View File
@@ -218,6 +218,7 @@ func ProvideService(cfg *setting.Cfg, features *featuremgmt.FeatureManager) *Soc
SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole, cfg.OAuthSkipOrgRoleUpdateSync, *features),
url: cfg.GrafanaComURL,
allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()),
skipOrgRoleSync: cfg.GrafanaComSkipOrgRoleSync,
}
}
}
+10
View File
@@ -462,6 +462,10 @@ type Cfg struct {
// in case API is not publicly accessible.
// Defaults to GrafanaComURL setting + "/api" if unset.
GrafanaComAPIURL string
// GrafanaComSkipOrgRoleSync can be set for
// letting users set org roles from within Grafana and
// skip the org roles coming from GrafanaCom
GrafanaComSkipOrgRoleSync bool
// Geomap base layer config
GeomapDefaultBaseLayerConfig map[string]interface{}
@@ -1351,6 +1355,11 @@ func readSecuritySettings(iniFile *ini.File, cfg *Cfg) error {
return nil
}
func readAuthGrafanaComSettings(iniFile *ini.File, cfg *Cfg) {
sec := iniFile.Section("auth.grafana_com")
cfg.GrafanaComSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false)
}
func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
auth := iniFile.Section("auth")
@@ -1453,6 +1462,7 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
cfg.AuthProxyHeadersEncoded = authProxy.Key("headers_encoded").MustBool(false)
readAuthGrafanaComSettings(iniFile, cfg)
return nil
}