[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:
@@ -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;
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface OrgUser extends WithAccessControlMetadata {
|
||||
userId: number;
|
||||
isDisabled: boolean;
|
||||
authLabels?: string[];
|
||||
isExternallySynced?: boolean;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
|
||||
Reference in New Issue
Block a user