[v9.4.x] Auth: Fix orgrole picker disabled if isSynced user (#65553)

Auth: Fix orgrole picker disabled if isSynced user (#64033)

* fix: disable orgrolepicker if externaluser is synced

* add disable to role picker

* just took me 2 hours to center the icon

* wip

* fix: check externallySyncedUser for API call

* remove check from store

* add: tests

* refactor authproxy and made tests run

* add: feature toggle

* set feature toggle for tests

* add: IsProviderEnabled

* refactor: featuretoggle name

* IsProviderEnabled tests

* add specific tests for isProviderEnabled

* fix: org_user tests

* add: owner to featuretoggle

* add missing authlabels

* remove fmt

* feature toggle

* change config

* add test for a different authmodule

* test refactor

* gen feature toggle again

* fix basic auth user able to change the org role

* test for basic auth role

* make err.base to error

* lowered lvl of log and input mesg

(cherry picked from commit 3cd952b8ba)
This commit is contained in:
Eric Leijonmarck
2023-04-05 09:55:43 +01:00
committed by GitHub
parent f9f0dd8b1b
commit d37fa06f05
27 changed files with 618 additions and 89 deletions
@@ -98,6 +98,7 @@ Alpha features might be changed or removed without prior notice.
| `alertingBacktesting` | Rule backtesting API for alerting |
| `editPanelCSVDragAndDrop` | Enables drag and drop for CSV and Excel files |
| `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view |
| `onlyExternalOrgRoleSync` | Prohibits a user from changing organization roles synced with external auth providers |
| `prometheusMetricEncyclopedia` | Replaces the Prometheus query builder metric select option with a paginated and filterable component |
| `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy |
| `alertStateHistoryLokiSecondary` | Enable Grafana to write alert state history to an external Loki instance in addition to Grafana annotations. |
@@ -91,6 +91,7 @@ export interface FeatureToggles {
topNavCommandPalette?: boolean;
logsSampleInExplore?: boolean;
logsContextDatasourceUi?: boolean;
onlyExternalOrgRoleSync?: boolean;
prometheusMetricEncyclopedia?: boolean;
influxdbBackendMigration?: boolean;
alertStateHistoryLokiSecondary?: boolean;
+2 -2
View File
@@ -105,7 +105,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *contextmodel.ReqContext) (map[st
"appSubUrl": hs.Cfg.AppSubURL,
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
"authProxyEnabled": setting.AuthProxyEnabled,
"ldapEnabled": hs.Cfg.LDAPEnabled,
"ldapEnabled": hs.Cfg.LDAPAuthEnabled,
"jwtHeaderName": hs.Cfg.JWTAuthHeaderName,
"jwtUrlLogin": hs.Cfg.JWTAuthURLLogin,
"alertingEnabled": setting.AlertingEnabled,
@@ -148,7 +148,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *contextmodel.ReqContext) (map[st
"OAuthSkipOrgRoleUpdateSync": hs.Cfg.OAuthSkipOrgRoleUpdateSync,
"SAMLSkipOrgRoleSync": hs.Cfg.SectionWithEnvOverrides("auth.saml").Key("skip_org_role_sync").MustBool(false),
"LDAPSkipOrgRoleSync": hs.Cfg.LDAPSkipOrgRoleSync,
"GithubSkipOrgRoleSync": hs.Cfg.GithubSkipOrgRoleSync,
"GithubSkipOrgRoleSync": hs.Cfg.GitHubSkipOrgRoleSync,
"GoogleSkipOrgRoleSync": hs.Cfg.GoogleSkipOrgRoleSync,
"JWTAuthSkipOrgRoleSync": hs.Cfg.JWTAuthSkipOrgRoleSync,
"GrafanaComSkipOrgRoleSync": hs.Cfg.GrafanaComSkipOrgRoleSync,
+13 -13
View File
@@ -66,9 +66,9 @@ func getUserFromLDAPContext(t *testing.T, requestURL string, searchOrgRst []*org
sc := setupScenarioContext(t, requestURL)
origLDAP := setting.LDAPEnabled
setting.LDAPEnabled = true
t.Cleanup(func() { setting.LDAPEnabled = origLDAP })
origLDAP := setting.LDAPAuthEnabled
setting.LDAPAuthEnabled = true
t.Cleanup(func() { setting.LDAPAuthEnabled = origLDAP })
hs := &HTTPServer{Cfg: setting.NewCfg(), ldapGroups: ldap.ProvideGroupsService(), orgService: &orgtest.FakeOrgService{ExpectedOrgs: searchOrgRst}}
@@ -313,9 +313,9 @@ func getLDAPStatusContext(t *testing.T) *scenarioContext {
requestURL := "/api/admin/ldap/status"
sc := setupScenarioContext(t, requestURL)
ldap := setting.LDAPEnabled
setting.LDAPEnabled = true
t.Cleanup(func() { setting.LDAPEnabled = ldap })
ldap := setting.LDAPAuthEnabled
setting.LDAPAuthEnabled = true
t.Cleanup(func() { setting.LDAPAuthEnabled = ldap })
hs := &HTTPServer{Cfg: setting.NewCfg()}
@@ -373,11 +373,11 @@ func postSyncUserWithLDAPContext(t *testing.T, requestURL string, preHook func(*
sc := setupScenarioContext(t, requestURL)
sc.authInfoService = &logintest.AuthInfoServiceFake{}
ldap := setting.LDAPEnabled
ldap := setting.LDAPAuthEnabled
t.Cleanup(func() {
setting.LDAPEnabled = ldap
setting.LDAPAuthEnabled = ldap
})
setting.LDAPEnabled = true
setting.LDAPAuthEnabled = true
hs := &HTTPServer{
Cfg: sc.cfg,
@@ -600,22 +600,22 @@ func TestLDAP_AccessControl(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
enabled := setting.LDAPEnabled
enabled := setting.LDAPAuthEnabled
configFile := setting.LDAPConfigFile
t.Cleanup(func() {
setting.LDAPEnabled = enabled
setting.LDAPAuthEnabled = enabled
setting.LDAPConfigFile = configFile
})
setting.LDAPEnabled = true
setting.LDAPAuthEnabled = true
path, err := filepath.Abs("../../conf/ldap.toml")
assert.NoError(t, err)
setting.LDAPConfigFile = path
server := SetupAPITestServer(t, func(hs *HTTPServer) {
cfg := setting.NewCfg()
cfg.LDAPEnabled = true
cfg.LDAPAuthEnabled = true
hs.Cfg = cfg
hs.SQLStore = dbtest.NewFakeDB()
hs.orgService = orgtest.NewOrgServiceFake()
+19
View File
@@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/services/accesscontrol"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
@@ -296,6 +297,7 @@ func (hs *HTTPServer) searchOrgUsersHelper(c *contextmodel.ReqContext, query *or
userIDs[fmt.Sprint(user.UserID)] = true
authLabelsUserIDs = append(authLabelsUserIDs, user.UserID)
filteredUsers = append(filteredUsers, user)
}
@@ -313,6 +315,7 @@ func (hs *HTTPServer) searchOrgUsersHelper(c *contextmodel.ReqContext, query *or
filteredUsers[i].AccessControl = accessControlMetadata[fmt.Sprint(filteredUsers[i].UserID)]
if module, ok := modules[filteredUsers[i].UserID]; ok {
filteredUsers[i].AuthLabels = []string{login.GetAuthProviderLabel(module)}
filteredUsers[i].IsExternallySynced = login.IsExternallySynced(hs.Cfg, module)
}
}
@@ -386,6 +389,22 @@ func (hs *HTTPServer) updateOrgUserHelper(c *contextmodel.ReqContext, cmd org.Up
if !c.OrgRole.Includes(cmd.Role) && !c.IsGrafanaAdmin {
return response.Error(http.StatusForbidden, "Cannot assign a role higher than user's role", nil)
}
if hs.Features.IsEnabled(featuremgmt.FlagOnlyExternalOrgRoleSync) {
// we do not allow to change role for external synced users
qAuth := login.GetAuthInfoQuery{UserId: cmd.UserID}
err := hs.authInfoService.GetAuthInfo(c.Req.Context(), &qAuth)
if err != nil {
if errors.Is(err, user.ErrUserNotFound) {
hs.log.Debug("Failed to get user auth info for basic auth user", cmd.UserID, nil)
} else {
hs.log.Error("Failed to get user auth info for external sync check", cmd.UserID, err)
return response.Error(http.StatusInternalServerError, "Failed to get user auth info", nil)
}
}
if qAuth.Result != nil && qAuth.Result.AuthModule != "" && login.IsExternallySynced(hs.Cfg, qAuth.Result.AuthModule) {
return response.Err(org.ErrCannotChangeRoleForExternallySyncedUser.Errorf("Cannot change role for externally synced user"))
}
}
if err := hs.orgService.UpdateOrgUser(c.Req.Context(), &cmd); err != nil {
if errors.Is(err, org.ErrLastOrgAdmin) {
return response.Error(400, "Cannot change role so that there is no organization admin left", nil)
+99
View File
@@ -17,9 +17,13 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/db/dbtest"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/models/roletype"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/login/logintest"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/org/orgtest"
@@ -30,8 +34,10 @@ import (
"github.com/grafana/grafana/pkg/services/temp_user/tempuserimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/userimpl"
"github.com/grafana/grafana/pkg/services/user/usertest"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web/webtest"
)
func setUpGetOrgUsersDB(t *testing.T, sqlStore *sqlstore.SQLStore) {
@@ -200,6 +206,99 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) {
})
}
func TestOrgUsersAPIEndpoint_updateOrgRole(t *testing.T) {
type testCase struct {
desc string
SkipOrgRoleSync bool
AuthEnabled bool
AuthModule string
expectedCode int
}
permissions := []accesscontrol.Permission{
{Action: accesscontrol.ActionOrgUsersRead, Scope: "users:*"},
{Action: accesscontrol.ActionOrgUsersWrite, Scope: "users:*"},
{Action: accesscontrol.ActionOrgUsersAdd, Scope: "users:*"},
{Action: accesscontrol.ActionOrgUsersRemove, Scope: "users:*"},
}
tests := []testCase{
{
desc: "should be able to change basicRole when skip_org_role_sync true",
SkipOrgRoleSync: true,
AuthEnabled: true,
AuthModule: login.LDAPAuthModule,
expectedCode: http.StatusOK,
},
{
desc: "should not be able to change basicRole when skip_org_role_sync false",
SkipOrgRoleSync: false,
AuthEnabled: true,
AuthModule: login.LDAPAuthModule,
expectedCode: http.StatusForbidden,
},
{
desc: "should not be able to change basicRole with a different provider",
SkipOrgRoleSync: false,
AuthEnabled: true,
AuthModule: login.GenericOAuthModule,
expectedCode: http.StatusForbidden,
},
{
desc: "should be able to change basicRole with a basic Auth",
SkipOrgRoleSync: false,
AuthEnabled: false,
AuthModule: "",
expectedCode: http.StatusOK,
},
{
desc: "should be able to change basicRole with a basic Auth",
SkipOrgRoleSync: true,
AuthEnabled: true,
AuthModule: "",
expectedCode: http.StatusOK,
},
}
userWithPermissions := userWithPermissions(1, permissions)
userRequesting := &user.User{ID: 2, OrgID: 1}
reqBody := `{"userId": "1", "role": "Admin", "orgId": "1"}`
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
hs.Cfg.LDAPAuthEnabled = tt.AuthEnabled
if tt.AuthModule == login.LDAPAuthModule {
hs.Cfg.LDAPAuthEnabled = tt.AuthEnabled
hs.Cfg.LDAPSkipOrgRoleSync = tt.SkipOrgRoleSync
} else if tt.AuthModule == login.GenericOAuthModule {
hs.Cfg.GenericOAuthAuthEnabled = tt.AuthEnabled
hs.Cfg.GenericOAuthSkipOrgRoleSync = tt.SkipOrgRoleSync
} else if tt.AuthModule == "" {
// authmodule empty means basic auth
} else {
t.Errorf("invalid auth module for test: %s", tt.AuthModule)
}
hs.authInfoService = &logintest.AuthInfoServiceFake{
ExpectedUserAuth: &login.UserAuth{AuthModule: tt.AuthModule},
}
hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagOnlyExternalOrgRoleSync, true)
hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: userWithPermissions}
hs.orgService = &orgtest.FakeOrgService{}
hs.accesscontrolService = &actest.FakeService{
ExpectedPermissions: permissions,
}
})
req := server.NewRequest(http.MethodPatch, fmt.Sprintf("/api/orgs/%d/users/%d", userRequesting.OrgID, userRequesting.ID), strings.NewReader(reqBody))
req.Header.Set("Content-Type", "application/json")
userWithPermissions.OrgRole = roletype.RoleAdmin
res, err := server.Send(webtest.RequestWithSignedInUser(req, userWithPermissions))
require.NoError(t, err)
assert.Equal(t, tt.expectedCode, res.StatusCode)
require.NoError(t, res.Body.Close())
})
}
}
func TestOrgUsersAPIEndpoint_LegacyAccessControl_FolderAdmin(t *testing.T) {
cfg := setting.NewCfg()
cfg.RBACEnabled = false
+1
View File
@@ -69,6 +69,7 @@ func (hs *HTTPServer) getUserUserProfile(c *contextmodel.ReqContext, userID int6
authLabel := login.GetAuthProviderLabel(getAuthQuery.Result.AuthModule)
userProfile.AuthLabels = append(userProfile.AuthLabels, authLabel)
userProfile.IsExternal = true
userProfile.IsExternallySynced = login.IsExternallySynced(hs.Cfg, getAuthQuery.Result.AuthModule)
}
userProfile.AccessControl = hs.getAccessControlMetadata(c, c.OrgID, "global.users:id:", strconv.FormatInt(userID, 10))
@@ -78,7 +78,7 @@ func TestMetrics(t *testing.T) {
BuildVersion: "5.0.0",
AnonymousEnabled: true,
BasicAuthEnabled: true,
LDAPEnabled: true,
LDAPAuthEnabled: true,
AuthProxyEnabled: true,
Packaging: "deb",
ReportingDistributor: "hosted-grafana",
@@ -148,7 +148,7 @@ func TestCollectingUsageStats(t *testing.T) {
BuildVersion: "5.0.0",
AnonymousEnabled: true,
BasicAuthEnabled: true,
LDAPEnabled: true,
LDAPAuthEnabled: true,
AuthProxyEnabled: true,
Packaging: "deb",
ReportingDistributor: "hosted-grafana",
@@ -210,7 +210,7 @@ func TestElasticStats(t *testing.T) {
BuildVersion: "5.0.0",
AnonymousEnabled: true,
BasicAuthEnabled: true,
LDAPEnabled: true,
LDAPAuthEnabled: true,
AuthProxyEnabled: true,
Packaging: "deb",
ReportingDistributor: "hosted-grafana",
+4 -4
View File
@@ -19,7 +19,7 @@ var errTest = errors.New("test error")
func TestLoginUsingLDAP(t *testing.T) {
LDAPLoginScenario(t, "When LDAP enabled and no server configured", func(sc *LDAPLoginScenarioContext) {
setting.LDAPEnabled = true
setting.LDAPAuthEnabled = true
sc.withLoginResult(false)
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
@@ -39,7 +39,7 @@ func TestLoginUsingLDAP(t *testing.T) {
})
LDAPLoginScenario(t, "When LDAP disabled", func(sc *LDAPLoginScenarioContext) {
setting.LDAPEnabled = false
setting.LDAPAuthEnabled = false
sc.withLoginResult(false)
loginService := &logintest.LoginServiceFake{}
@@ -135,11 +135,11 @@ func LDAPLoginScenario(t *testing.T, desc string, fn LDAPLoginScenarioFunc) {
origNewLDAP := newLDAP
origGetLDAPConfig := getLDAPConfig
origLDAPEnabled := setting.LDAPEnabled
origLDAPEnabled := setting.LDAPAuthEnabled
t.Cleanup(func() {
newLDAP = origNewLDAP
getLDAPConfig = origGetLDAPConfig
setting.LDAPEnabled = origLDAPEnabled
setting.LDAPAuthEnabled = origLDAPEnabled
})
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
+1 -1
View File
@@ -151,7 +151,7 @@ func ProvideService(cfg *setting.Cfg,
apiUrl: info.ApiUrl,
teamIds: sec.Key("team_ids").Ints(","),
allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()),
skipOrgRoleSync: cfg.GithubSkipOrgRoleSync,
skipOrgRoleSync: cfg.GitHubSkipOrgRoleSync,
}
}
+9 -9
View File
@@ -583,7 +583,7 @@ func TestMiddlewareContext(t *testing.T) {
configure := func(cfg *setting.Cfg) {
cfg.AuthProxyEnabled = true
cfg.AuthProxyAutoSignUp = true
cfg.LDAPEnabled = true
cfg.LDAPAuthEnabled = true
cfg.AuthProxyHeaderName = "X-WEBAUTH-USER"
cfg.AuthProxyHeaderProperty = "username"
cfg.AuthProxyHeaders = map[string]string{"Groups": "X-WEBAUTH-GROUPS", "Role": "X-WEBAUTH-ROLE"}
@@ -627,7 +627,7 @@ func TestMiddlewareContext(t *testing.T) {
assert.Nil(t, sc.context)
}, func(cfg *setting.Cfg) {
configure(cfg)
cfg.LDAPEnabled = false
cfg.LDAPAuthEnabled = false
cfg.AuthProxyAutoSignUp = false
})
@@ -648,7 +648,7 @@ func TestMiddlewareContext(t *testing.T) {
require.Contains(t, list.Items, "X-WEBAUTH-ROLE")
}, func(cfg *setting.Cfg) {
configure(cfg)
cfg.LDAPEnabled = false
cfg.LDAPAuthEnabled = false
cfg.AuthProxyAutoSignUp = true
})
@@ -671,7 +671,7 @@ func TestMiddlewareContext(t *testing.T) {
assert.Equal(t, orgRole, string(sc.context.OrgRole))
}, func(cfg *setting.Cfg) {
configure(cfg)
cfg.LDAPEnabled = false
cfg.LDAPAuthEnabled = false
cfg.AuthProxyAutoSignUp = true
})
@@ -697,7 +697,7 @@ func TestMiddlewareContext(t *testing.T) {
assert.Equal(t, "", string(sc.context.OrgRole))
}, func(cfg *setting.Cfg) {
configure(cfg)
cfg.LDAPEnabled = false
cfg.LDAPAuthEnabled = false
cfg.AuthProxyAutoSignUp = true
})
@@ -715,7 +715,7 @@ func TestMiddlewareContext(t *testing.T) {
assert.Equal(t, targetOrgID, sc.context.OrgID)
}, func(cfg *setting.Cfg) {
configure(cfg)
cfg.LDAPEnabled = false
cfg.LDAPAuthEnabled = false
cfg.AuthProxyAutoSignUp = true
})
@@ -789,7 +789,7 @@ func TestMiddlewareContext(t *testing.T) {
assert.Equal(t, orgID, sc.context.OrgID)
}, func(cfg *setting.Cfg) {
configure(cfg)
cfg.LDAPEnabled = false
cfg.LDAPAuthEnabled = false
})
middlewareScenario(t, "Should allow the request from whitelist IP", func(t *testing.T, sc *scenarioContext) {
@@ -807,7 +807,7 @@ func TestMiddlewareContext(t *testing.T) {
}, func(cfg *setting.Cfg) {
configure(cfg)
cfg.AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120"
cfg.LDAPEnabled = false
cfg.LDAPAuthEnabled = false
})
middlewareScenario(t, "Should not allow the request from whitelisted IP", func(t *testing.T, sc *scenarioContext) {
@@ -823,7 +823,7 @@ func TestMiddlewareContext(t *testing.T) {
}, func(cfg *setting.Cfg) {
configure(cfg)
cfg.AuthProxyWhitelist = "8.8.8.8"
cfg.LDAPEnabled = false
cfg.LDAPAuthEnabled = false
})
middlewareScenario(t, "Should return 407 status code if LDAP says no", func(t *testing.T, sc *scenarioContext) {
+1 -1
View File
@@ -85,7 +85,7 @@ func ProvideService(
var proxyClients []authn.ProxyClient
var passwordClients []authn.PasswordClient
if s.cfg.LDAPEnabled {
if s.cfg.LDAPAuthEnabled {
ldap := clients.ProvideLDAP(cfg)
proxyClients = append(proxyClients, ldap)
passwordClients = append(passwordClients, ldap)
+1 -1
View File
@@ -13,7 +13,7 @@ func (s *Service) getUsageStats(ctx context.Context) (map[string]interface{}, er
// Add stats about auth configuration
authTypes := map[string]bool{}
authTypes["basic_auth"] = s.cfg.BasicAuthEnabled
authTypes["ldap"] = s.cfg.LDAPEnabled
authTypes["ldap"] = s.cfg.LDAPAuthEnabled
authTypes["auth_proxy"] = s.cfg.AuthProxyEnabled
authTypes["anonymous"] = s.cfg.AnonymousEnabled
@@ -38,10 +38,10 @@ var getLDAPConfig = ldap.GetConfig
// isLDAPEnabled checks if LDAP is enabled
var isLDAPEnabled = func(cfg *setting.Cfg) bool {
if cfg != nil {
return cfg.LDAPEnabled
return cfg.LDAPAuthEnabled
}
return setting.LDAPEnabled
return setting.LDAPAuthEnabled
}
// newLDAP creates multiple LDAP instance
+5
View File
@@ -423,6 +423,11 @@ var (
State: FeatureStateAlpha,
FrontendOnly: true,
},
{
Name: "onlyExternalOrgRoleSync",
Description: "Prohibits a user from changing organization roles synced with external auth providers",
State: FeatureStateAlpha,
},
{
Name: "prometheusMetricEncyclopedia",
Description: "Replaces the Prometheus query builder metric select option with a paginated and filterable component",
+4
View File
@@ -307,6 +307,10 @@ const (
// Allow datasource to provide custom UI for context view
FlagLogsContextDatasourceUi = "logsContextDatasourceUi"
// FlagOnlyExternalOrgRoleSync
// Prohibits a user from changing organization roles synced with external auth providers
FlagOnlyExternalOrgRoleSync = "onlyExternalOrgRoleSync"
// FlagPrometheusMetricEncyclopedia
// Replaces the Prometheus query builder metric select option with a paginated and filterable component
FlagPrometheusMetricEncyclopedia = "prometheusMetricEncyclopedia"
+2 -2
View File
@@ -154,7 +154,7 @@ func TestServer_validateGrafanaUser(t *testing.T) {
Config: &ServerConfig{
Groups: []*GroupToOrgRole{},
},
log: logger.New("test"),
log: log.New("test"),
}
user := &login.ExternalUserInfo{
@@ -174,7 +174,7 @@ func TestServer_validateGrafanaUser(t *testing.T) {
},
},
},
log: logger.New("test"),
log: log.New("test"),
}
user := &login.ExternalUserInfo{
+2 -2
View File
@@ -73,7 +73,7 @@ var loadingMutex = &sync.Mutex{}
// IsEnabled checks if ldap is enabled
func IsEnabled() bool {
return setting.LDAPEnabled
return setting.LDAPAuthEnabled
}
func SkipOrgRoleSync() bool {
@@ -102,7 +102,7 @@ var config *Config
// the config or it reads it and caches it first.
func GetConfig(cfg *setting.Cfg) (*Config, error) {
if cfg != nil {
if !cfg.LDAPEnabled {
if !cfg.LDAPAuthEnabled {
return nil, nil
}
} else if !IsEnabled() {
+99 -1
View File
@@ -4,6 +4,7 @@ import (
"context"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
)
type AuthInfoService interface {
@@ -22,8 +23,103 @@ const (
AuthProxyAuthModule = "authproxy"
JWTModule = "jwt"
RenderModule = "render"
// OAuth provider modules
AzureADAuthModule = "oauth_azuread"
GoogleAuthModule = "oauth_google"
GitLabAuthModule = "oauth_gitlab"
GithubAuthModule = "oauth_github"
GenericOAuthModule = "oauth_generic_oauth"
GrafanaComAuthModule = "oauth_grafana_com"
GrafanaNetAuthModule = "oauth_grafananet"
OktaAuthModule = "oauth_okta"
// labels
SAMLLabel = "SAML"
LDAPLabel = "LDAP"
JWTLabel = "JWT"
// OAuth provider labels
AuthProxyLabel = "Auth Proxy"
AzureADLabel = "AzureAD"
GoogleLabel = "Google"
GenericOAuthLabel = "Generic OAuth"
GitLabLabel = "GitLab"
GithubLabel = "GitHub"
GrafanaComLabel = "grafana.com"
OktaLabel = "Okta"
)
// IsExternnalySynced is used to tell if the user roles are externally synced
// true means that the org role sync is handled by Grafana
// Note: currently the users authinfo is overridden each time the user logs in
// https://github.com/grafana/grafana/blob/4181acec72f76df7ad02badce13769bae4a1f840/pkg/services/login/authinfoservice/database/database.go#L61
// this means that if the user has multiple auth providers and one of them is set to sync org roles
// then IsExternallySynced will be true for this one provider and false for the others
func IsExternallySynced(cfg *setting.Cfg, authModule string) bool {
// provider enabled in config
if !IsProviderEnabled(cfg, authModule) {
return false
}
// first check SAML, LDAP and JWT
switch authModule {
case SAMLAuthModule:
return !cfg.SAMLSkipOrgRoleSync
case LDAPAuthModule:
return !cfg.LDAPSkipOrgRoleSync
case JWTModule:
return !cfg.JWTAuthSkipOrgRoleSync
}
// then check the rest of the oauth providers
// FIXME: remove this once we remove the setting
// is a deprecated setting that is used to skip org role sync for all external oauth providers
if cfg.OAuthSkipOrgRoleUpdateSync {
return false
}
switch authModule {
case GoogleAuthModule:
return !cfg.GoogleSkipOrgRoleSync
case OktaAuthModule:
return !cfg.OktaSkipOrgRoleSync
case AzureADAuthModule:
return !cfg.AzureADSkipOrgRoleSync
case GitLabAuthModule:
return !cfg.GitLabSkipOrgRoleSync
case GithubAuthModule:
return !cfg.GitHubSkipOrgRoleSync
case GrafanaComAuthModule:
return !cfg.GrafanaComSkipOrgRoleSync
case GenericOAuthModule:
return !cfg.GenericOAuthSkipOrgRoleSync
}
return true
}
func IsProviderEnabled(cfg *setting.Cfg, authModule string) bool {
switch authModule {
case SAMLAuthModule:
return cfg.SAMLAuthEnabled
case LDAPAuthModule:
return cfg.LDAPAuthEnabled
case JWTModule:
return cfg.JWTAuthEnabled
case GoogleAuthModule:
return cfg.GoogleAuthEnabled
case OktaAuthModule:
return cfg.OktaAuthEnabled
case AzureADAuthModule:
return cfg.AzureADEnabled
case GitLabAuthModule:
return cfg.GitLabAuthEnabled
case GithubAuthModule:
return cfg.GitHubAuthEnabled
case GrafanaComAuthModule:
return cfg.GrafanaComAuthEnabled
case GenericOAuthModule:
return cfg.GenericOAuthAuthEnabled
}
return false
}
// used for frontend to display a more user friendly label
func GetAuthProviderLabel(authModule string) string {
switch authModule {
case "oauth_github":
@@ -45,7 +141,9 @@ func GetAuthProviderLabel(authModule string) string {
case JWTModule:
return "JWT"
case AuthProxyAuthModule:
return "Auth Proxy"
return AuthProxyLabel
case GenericOAuthModule:
return GenericOAuthLabel
default:
return "OAuth" // FIXME: replace with "Unknown" and handle generic oauth as a case
}
+248
View File
@@ -0,0 +1,248 @@
package login
import (
"testing"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
)
func TestIsExternallySynced(t *testing.T) {
testcases := []struct {
name string
cfg *setting.Cfg
provider string
expected bool
}{
// azure
{
name: "AzureAD synced user should return that it is externally synced",
cfg: &setting.Cfg{AzureADEnabled: true, AzureADSkipOrgRoleSync: false},
provider: AzureADAuthModule,
expected: true,
},
{
name: "AzureAD synced user should return that it is not externally synced when org role sync is set",
cfg: &setting.Cfg{AzureADEnabled: true, AzureADSkipOrgRoleSync: true},
provider: AzureADAuthModule,
expected: false,
},
// FIXME: remove this test as soon as we remove the deprecated setting for skipping org role sync for all external oauth providers
{
name: "azuread external user should return that it is not externally synced when oauth org role sync is set",
cfg: &setting.Cfg{AzureADEnabled: true, AzureADSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
provider: AzureADAuthModule,
expected: false,
},
// google
{
name: "Google synced user should return that it is externally synced",
cfg: &setting.Cfg{GoogleAuthEnabled: true, GoogleSkipOrgRoleSync: false},
provider: GoogleAuthModule,
expected: true,
},
{
name: "Google synced user should return that it is not externally synced when org role sync is set",
cfg: &setting.Cfg{GoogleAuthEnabled: true, GoogleSkipOrgRoleSync: true},
provider: GoogleAuthModule,
expected: false,
},
// FIXME: remove this test as soon as we remove the deprecated setting for skipping org role sync for all external oauth providers
{
name: "google external user should return that it is not externally synced when oauth org role sync is set",
cfg: &setting.Cfg{GoogleAuthEnabled: true, GoogleSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
provider: GoogleAuthModule,
expected: false,
},
{
name: "external user should return that it is not externally synced when oauth org role sync is set and google skip org role sync set",
cfg: &setting.Cfg{GoogleAuthEnabled: true, GoogleSkipOrgRoleSync: true, OAuthSkipOrgRoleUpdateSync: true},
provider: GoogleAuthModule,
expected: false,
},
// okta
{
name: "Okta synced user should return that it is externally synced",
cfg: &setting.Cfg{OktaAuthEnabled: true, OktaSkipOrgRoleSync: false},
provider: OktaAuthModule,
expected: true,
},
{
name: "Okta synced user should return that it is not externally synced when org role sync is set",
cfg: &setting.Cfg{OktaAuthEnabled: true, OktaSkipOrgRoleSync: true},
provider: OktaAuthModule,
expected: false,
},
// FIXME: remove this test as soon as we remove the deprecated setting for skipping org role sync for all external oauth providers
{
name: "okta external user should return that it is not externally synced when oauth org role sync is set",
cfg: &setting.Cfg{OktaAuthEnabled: true, OktaSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
provider: OktaAuthModule,
expected: false,
},
// github
{
name: "Github synced user should return that it is externally synced",
cfg: &setting.Cfg{GitHubAuthEnabled: true, GitHubSkipOrgRoleSync: false},
provider: GithubAuthModule,
expected: true,
},
{
name: "Github synced user should return that it is not externally synced when org role sync is set",
cfg: &setting.Cfg{GitHubAuthEnabled: true, GitHubSkipOrgRoleSync: true},
provider: GithubAuthModule,
expected: false,
},
// FIXME: remove this test as soon as we remove the deprecated setting for skipping org role sync for all external oauth providers
{
name: "github external user should return that it is not externally synced when oauth org role sync is set",
cfg: &setting.Cfg{GitHubAuthEnabled: true, GitHubSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
provider: GithubAuthModule,
expected: false,
},
// gitlab
{
name: "Gitlab synced user should return that it is externally synced",
cfg: &setting.Cfg{GitLabAuthEnabled: true, GitLabSkipOrgRoleSync: false},
provider: GitLabAuthModule,
expected: true,
},
{
name: "Gitlab synced user should return that it is not externally synced when org role sync is set",
cfg: &setting.Cfg{GitLabAuthEnabled: true, GitLabSkipOrgRoleSync: true},
provider: GitLabAuthModule,
expected: false,
},
// FIXME: remove this test as soon as we remove the deprecated setting for skipping org role sync for all external oauth providers
{
name: "gitlab external user should return that it is not externally synced when oauth org role sync is set",
cfg: &setting.Cfg{GitLabAuthEnabled: true, GitLabSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
provider: GitLabAuthModule,
expected: false,
},
// grafana.com
{
name: "Grafana.com synced user should return that it is externally synced",
cfg: &setting.Cfg{GrafanaComAuthEnabled: true, GrafanaComSkipOrgRoleSync: false},
provider: GrafanaComAuthModule,
expected: true,
},
{
name: "Grafana.com synced user should return that it is not externally synced when org role sync is set",
cfg: &setting.Cfg{GrafanaComAuthEnabled: true, GrafanaComSkipOrgRoleSync: true},
provider: GrafanaComAuthModule,
expected: false,
},
// FIXME: remove this test as soon as we remove the deprecated setting for skipping org role sync for all external oauth providers
{
name: "grafanacom external user should return that it is not externally synced when oauth org role sync is set",
cfg: &setting.Cfg{GrafanaComAuthEnabled: true, GrafanaComSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
provider: GrafanaComAuthModule,
expected: false,
},
// generic oauth
{
name: "OAuth synced user should return that it is externally synced",
cfg: &setting.Cfg{GenericOAuthAuthEnabled: true, OAuthSkipOrgRoleUpdateSync: false},
// this could be any of the external oauth providers
provider: GenericOAuthModule,
expected: true,
},
{
name: "OAuth synced user should return that it is not externally synced when org role sync is set",
cfg: &setting.Cfg{GenericOAuthAuthEnabled: true, OAuthSkipOrgRoleUpdateSync: true},
// this could be any of the external oauth providers
provider: GenericOAuthModule,
expected: false,
},
// FIXME: remove this test as soon as we remove the deprecated setting for skipping org role sync for all external oauth providers
{
name: "generic oauth external user should return that it is not externally synced when oauth org role sync is set",
cfg: &setting.Cfg{GenericOAuthAuthEnabled: true, GenericOAuthSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
provider: GenericOAuthModule,
expected: false,
},
// saml
{
name: "SAML synced user should return that it is externally synced",
cfg: &setting.Cfg{SAMLAuthEnabled: true, SAMLSkipOrgRoleSync: false},
provider: SAMLAuthModule,
expected: true,
},
{
name: "SAML synced user should return that it is not externally synced when org role sync is set",
cfg: &setting.Cfg{SAMLAuthEnabled: true, SAMLSkipOrgRoleSync: true},
provider: SAMLAuthModule,
expected: false,
},
// ldap
{
name: "LDAP synced user should return that it is externally synced",
cfg: &setting.Cfg{LDAPAuthEnabled: true, LDAPSkipOrgRoleSync: false},
provider: LDAPAuthModule,
expected: true,
},
{
name: "LDAP synced user should return that it is not externally synced when org role sync is set",
cfg: &setting.Cfg{LDAPAuthEnabled: true, LDAPSkipOrgRoleSync: true},
provider: LDAPAuthModule,
expected: false,
},
// jwt
{
name: "JWT synced user should return that it is externally synced",
cfg: &setting.Cfg{JWTAuthEnabled: true, JWTAuthSkipOrgRoleSync: false},
provider: JWTModule,
expected: true,
},
{
name: "JWT synced user should return that it is not externally synced when org role sync is set",
cfg: &setting.Cfg{JWTAuthEnabled: true, JWTAuthSkipOrgRoleSync: true},
provider: JWTModule,
expected: false,
},
// IsProvider test
{
name: "If no provider enabled should return false",
cfg: &setting.Cfg{JWTAuthSkipOrgRoleSync: true},
provider: JWTModule,
expected: false,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, IsExternallySynced(tc.cfg, tc.provider))
})
}
}
func TestIsProviderEnabled(t *testing.T) {
testcases := []struct {
name string
cfg *setting.Cfg
provider string
expected bool
}{
// github
{
name: "Github should return true if enabled",
cfg: &setting.Cfg{GitHubAuthEnabled: true},
provider: GithubAuthModule,
expected: true,
},
{
name: "Github should return false if not enabled",
cfg: &setting.Cfg{},
provider: GithubAuthModule,
expected: false,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, IsProviderEnabled(tc.cfg, tc.provider))
})
}
}
+1 -1
View File
@@ -182,7 +182,7 @@ func (s *ServiceImpl) getServerAdminNode(c *contextmodel.ReqContext) *navtree.Na
}
}
if s.cfg.LDAPEnabled && hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)) {
if s.cfg.LDAPAuthEnabled && hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)) {
adminNavLinks = append(adminNavLinks, &navtree.NavLink{
Text: "LDAP", Id: "ldap", Url: s.cfg.AppSubURL + "/admin/ldap", Icon: "book",
})
+22 -19
View File
@@ -7,15 +7,17 @@ import (
"github.com/grafana/grafana/pkg/models/roletype"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util/errutil"
)
// Typed errors
var (
ErrOrgNotFound = errors.New("organization not found")
ErrOrgNameTaken = errors.New("organization name is taken")
ErrLastOrgAdmin = errors.New("cannot remove last organization admin")
ErrOrgUserNotFound = errors.New("cannot find the organization user")
ErrOrgUserAlreadyAdded = errors.New("user is already added to organization")
ErrOrgNameTaken = errors.New("organization name is taken")
ErrLastOrgAdmin = errors.New("cannot remove last organization admin")
ErrOrgUserNotFound = errors.New("cannot find the organization user")
ErrOrgUserAlreadyAdded = errors.New("user is already added to organization")
ErrOrgNotFound = errors.New("organization not found")
ErrCannotChangeRoleForExternallySyncedUser = errutil.NewBase(errutil.StatusForbidden, "org.externallySynced", errutil.WithPublicMessage("cannot change role for externally synced user"))
)
type Org struct {
@@ -138,20 +140,21 @@ type UpdateOrgUserCommand struct {
}
type OrgUserDTO struct {
OrgID int64 `json:"orgId" xorm:"org_id"`
UserID int64 `json:"userId" xorm:"user_id"`
Email string `json:"email"`
Name string `json:"name"`
AvatarURL string `json:"avatarUrl" xorm:"avatar_url"`
Login string `json:"login"`
Role string `json:"role"`
LastSeenAt time.Time `json:"lastSeenAt"`
Updated time.Time `json:"-"`
Created time.Time `json:"-"`
LastSeenAtAge string `json:"lastSeenAtAge"`
AccessControl map[string]bool `json:"accessControl,omitempty"`
IsDisabled bool `json:"isDisabled"`
AuthLabels []string `json:"authLabels" xorm:"-"`
OrgID int64 `json:"orgId" xorm:"org_id"`
UserID int64 `json:"userId" xorm:"user_id"`
Email string `json:"email"`
Name string `json:"name"`
AvatarURL string `json:"avatarUrl" xorm:"avatar_url"`
Login string `json:"login"`
Role string `json:"role"`
LastSeenAt time.Time `json:"lastSeenAt"`
Updated time.Time `json:"-"`
Created time.Time `json:"-"`
LastSeenAtAge string `json:"lastSeenAtAge"`
AccessControl map[string]bool `json:"accessControl,omitempty"`
IsDisabled bool `json:"isDisabled"`
AuthLabels []string `json:"authLabels" xorm:"-"`
IsExternallySynced bool `json:"isExternallySynced"`
}
type RemoveOrgUserCommand struct {
+15 -14
View File
@@ -140,20 +140,21 @@ type GetUserProfileQuery struct {
}
type UserProfileDTO struct {
ID int64 `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Login string `json:"login"`
Theme string `json:"theme"`
OrgID int64 `json:"orgId,omitempty"`
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
IsDisabled bool `json:"isDisabled"`
IsExternal bool `json:"isExternal"`
AuthLabels []string `json:"authLabels"`
UpdatedAt time.Time `json:"updatedAt"`
CreatedAt time.Time `json:"createdAt"`
AvatarUrl string `json:"avatarUrl"`
AccessControl map[string]bool `json:"accessControl,omitempty"`
ID int64 `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Login string `json:"login"`
Theme string `json:"theme"`
OrgID int64 `json:"orgId,omitempty"`
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
IsDisabled bool `json:"isDisabled"`
IsExternal bool `json:"isExternal"`
IsExternallySynced bool `json:"isExternallySynced"`
AuthLabels []string `json:"authLabels"`
UpdatedAt time.Time `json:"updatedAt"`
CreatedAt time.Time `json:"createdAt"`
AvatarUrl string `json:"avatarUrl"`
AccessControl map[string]bool `json:"accessControl,omitempty"`
}
// implement Conversion interface to define custom field mapping (xorm feature)
+53 -11
View File
@@ -140,7 +140,7 @@ var (
RudderstackConfigUrl string
// LDAP
LDAPEnabled bool
LDAPAuthEnabled bool
LDAPSkipOrgRoleSync bool
LDAPConfigFile string
LDAPSyncCron string
@@ -428,18 +428,28 @@ type Cfg struct {
// Frontend analytics
IntercomSecret string
// AzureAD
AzureADEnabled bool
AzureADSkipOrgRoleSync bool
// Google
GoogleAuthEnabled bool
GoogleSkipOrgRoleSync bool
// Gitlab
GitLabAuthEnabled bool
GitLabSkipOrgRoleSync bool
// Generic OAuth
GenericOAuthAuthEnabled bool
GenericOAuthSkipOrgRoleSync bool
// LDAP
LDAPEnabled bool
LDAPSkipOrgRoleSync bool
LDAPAllowSignup bool
LDAPAuthEnabled bool
LDAPSkipOrgRoleSync bool
LDAPConfigFilePath string
LDAPAllowSignup bool
LDAPActiveSyncEnabled bool
LDAPSyncCron string
DefaultTheme string
DefaultLanguage string
@@ -470,8 +480,9 @@ type Cfg struct {
// then Live uses AppURL as the only allowed origin.
LiveAllowedOrigins []string
// Github OAuth
GithubSkipOrgRoleSync bool
// GitHub OAuth
GitHubAuthEnabled bool
GitHubSkipOrgRoleSync bool
// Grafana.com URL, used for OAuth redirect.
GrafanaComURL string
@@ -479,6 +490,8 @@ type Cfg struct {
// in case API is not publicly accessible.
// Defaults to GrafanaComURL setting + "/api" if unset.
GrafanaComAPIURL string
// Grafana.com Auth enabled
GrafanaComAuthEnabled bool
// GrafanaComSkipOrgRoleSync can be set for
// letting users set org roles from within Grafana and
// skip the org roles coming from GrafanaCom
@@ -502,7 +515,12 @@ type Cfg struct {
SecureSocksDSProxy SecureSocksDSProxySettings
// SAML Auth
SAMLAuthEnabled bool
SAMLSkipOrgRoleSync bool
// Okta OAuth
OktaAuthEnabled bool
OktaSkipOrgRoleSync bool
// Access Control
@@ -1094,6 +1112,7 @@ func (cfg *Cfg) Load(args CommandLineArgs) error {
}
cfg.readLDAPConfig()
cfg.readSAMLConfig()
cfg.handleAWSConfig()
cfg.readAzureSettings()
cfg.readSessionConfig()
@@ -1192,17 +1211,26 @@ type RemoteCacheOptions struct {
Encryption bool
}
func (cfg *Cfg) readSAMLConfig() {
samlSec := cfg.Raw.Section("auth.saml")
cfg.SAMLAuthEnabled = samlSec.Key("enabled").MustBool(false)
cfg.SAMLSkipOrgRoleSync = samlSec.Key("skip_org_role_sync").MustBool(false)
}
func (cfg *Cfg) readLDAPConfig() {
ldapSec := cfg.Raw.Section("auth.ldap")
LDAPConfigFile = ldapSec.Key("config_file").String()
LDAPSyncCron = ldapSec.Key("sync_cron").String()
LDAPEnabled = ldapSec.Key("enabled").MustBool(false)
cfg.LDAPEnabled = LDAPEnabled
LDAPAuthEnabled = ldapSec.Key("enabled").MustBool(false)
cfg.LDAPConfigFilePath = ldapSec.Key("config_file").String()
cfg.LDAPSyncCron = ldapSec.Key("sync_cron").String()
cfg.LDAPAuthEnabled = ldapSec.Key("enabled").MustBool(false)
LDAPSkipOrgRoleSync = ldapSec.Key("skip_org_role_sync").MustBool(false)
cfg.LDAPSkipOrgRoleSync = LDAPSkipOrgRoleSync
cfg.LDAPSkipOrgRoleSync = ldapSec.Key("skip_org_role_sync").MustBool(false)
LDAPActiveSyncEnabled = ldapSec.Key("active_sync_enabled").MustBool(false)
cfg.LDAPActiveSyncEnabled = ldapSec.Key("active_sync_enabled").MustBool(false)
LDAPAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true)
cfg.LDAPAllowSignup = LDAPAllowSignup
cfg.LDAPAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true)
}
func (cfg *Cfg) handleAWSConfig() {
@@ -1377,31 +1405,43 @@ func readSecuritySettings(iniFile *ini.File, cfg *Cfg) error {
}
func readAuthAzureADSettings(iniFile *ini.File, cfg *Cfg) {
sec := iniFile.Section("auth.azuread")
cfg.AzureADEnabled = sec.Key("enabled").MustBool(false)
cfg.AzureADSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false)
}
func readAuthGrafanaComSettings(iniFile *ini.File, cfg *Cfg) {
sec := iniFile.Section("auth.grafana_com")
cfg.GrafanaComAuthEnabled = sec.Key("enabled").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)
cfg.GitHubAuthEnabled = sec.Key("enabled").MustBool(false)
cfg.GitHubSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false)
}
func readAuthGoogleSettings(iniFile *ini.File, cfg *Cfg) {
sec := iniFile.Section("auth.google")
cfg.GoogleAuthEnabled = sec.Key("enabled").MustBool(false)
cfg.GoogleSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false)
}
func readAuthGitlabSettings(iniFile *ini.File, cfg *Cfg) {
sec := iniFile.Section("auth.gitlab")
cfg.GitLabAuthEnabled = sec.Key("enabled").MustBool(false)
cfg.GitLabSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false)
}
func readGenericOAuthSettings(iniFile *ini.File, cfg *Cfg) {
sec := iniFile.Section("auth.generic_oauth")
cfg.GenericOAuthAuthEnabled = sec.Key("enabled").MustBool(false)
cfg.GenericOAuthSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false)
}
func readAuthOktaSettings(iniFile *ini.File, cfg *Cfg) {
sec := iniFile.Section("auth.okta")
cfg.OktaAuthEnabled = sec.Key("enabled").MustBool(false)
cfg.OktaSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false)
}
@@ -1464,6 +1504,8 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
// GitLab Auth
readAuthGitlabSettings(iniFile, cfg)
// genericOAuth
readGenericOAuthSettings(iniFile, cfg)
// Okta Auth
readAuthOktaSettings(iniFile, cfg)
+9 -3
View File
@@ -5,6 +5,7 @@ import { Button, ConfirmModal } from '@grafana/ui';
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
import { fetchRoleOptions } from 'app/core/components/RolePicker/api';
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
import config from 'app/core/config';
import { contextSrv } from 'app/core/core';
import { AccessControlAction, OrgUser, Role } from 'app/types';
@@ -49,12 +50,17 @@ export const UsersTable = ({ users, orgId, onRoleChange, onRemoveUser }: Props)
<th>Seen</th>
<th>Role</th>
<th style={{ width: '34px' }} />
<th></th>
<th>Origin</th>
<th></th>
</tr>
</thead>
<tbody>
{users.map((user, index) => {
let basicRoleDisabled = !contextSrv.hasPermissionInMetadata(AccessControlAction.OrgUsersWrite, user);
if (config.featureToggles.onlyExternalOrgRoleSync) {
const isUserSynced = user?.isExternallySynced;
basicRoleDisabled = isUserSynced || basicRoleDisabled;
}
return (
<tr key={`${user.userId}-${index}`}>
<td className="width-2 text-center">
@@ -86,13 +92,13 @@ export const UsersTable = ({ users, orgId, onRoleChange, onRemoveUser }: Props)
roleOptions={roleOptions}
basicRole={user.role}
onBasicRoleChange={(newRole) => onRoleChange(newRole, user)}
basicRoleDisabled={!contextSrv.hasPermissionInMetadata(AccessControlAction.OrgUsersWrite, user)}
basicRoleDisabled={basicRoleDisabled}
/>
) : (
<OrgRolePicker
aria-label="Role"
value={user.role}
disabled={!contextSrv.hasPermissionInMetadata(AccessControlAction.OrgUsersWrite, user)}
disabled={basicRoleDisabled}
onChange={(newRole) => onRoleChange(newRole, user)}
/>
)}
+1
View File
@@ -13,6 +13,7 @@ export interface OrgUser extends WithAccessControlMetadata {
userId: number;
isDisabled: boolean;
authLabels?: string[];
isExternallySynced?: boolean;
}
export interface User {