mirror of https://github.com/grafana/grafana.git
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
This commit is contained in:
parent
13af5afaf3
commit
3cd952b8ba
|
|
@ -89,6 +89,7 @@ Alpha features might be changed or removed without prior notice.
|
|||
| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals |
|
||||
| `lokiQuerySplittingConfig` | Give users the option to configure split durations for Loki queries |
|
||||
| `individualCookiePreferences` | Support overriding cookie preferences per user |
|
||||
| `onlyExternalOrgRoleSync` | Prohibits a user from changing organization roles synced with external auth providers |
|
||||
| `drawerDataSourcePicker` | Changes the user experience for data source selection to a drawer. |
|
||||
| `traceqlSearch` | Enables the 'TraceQL Search' tab for the Tempo datasource which provides a UI to generate TraceQL queries |
|
||||
| `prometheusMetricEncyclopedia` | Replaces the Prometheus query builder metric select option with a paginated and filterable component |
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ export interface FeatureToggles {
|
|||
lokiQuerySplitting?: boolean;
|
||||
lokiQuerySplittingConfig?: boolean;
|
||||
individualCookiePreferences?: boolean;
|
||||
onlyExternalOrgRoleSync?: boolean;
|
||||
drawerDataSourcePicker?: boolean;
|
||||
traceqlSearch?: boolean;
|
||||
prometheusMetricEncyclopedia?: boolean;
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
|||
AppSubUrl: hs.Cfg.AppSubURL,
|
||||
AllowOrgCreate: (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
|
||||
AuthProxyEnabled: hs.Cfg.AuthProxyEnabled,
|
||||
LdapEnabled: hs.Cfg.LDAPEnabled,
|
||||
LdapEnabled: hs.Cfg.LDAPAuthEnabled,
|
||||
JwtHeaderName: hs.Cfg.JWTAuthHeaderName,
|
||||
JwtUrlLogin: hs.Cfg.JWTAuthURLLogin,
|
||||
AlertingErrorOrTimeout: setting.AlertingErrorOrTimeout,
|
||||
|
|
@ -148,7 +148,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
|||
GrafanaComSkipOrgRoleSync: hs.Cfg.GrafanaComSkipOrgRoleSync,
|
||||
GenericOAuthSkipOrgRoleSync: hs.Cfg.GenericOAuthSkipOrgRoleSync,
|
||||
AzureADSkipOrgRoleSync: hs.Cfg.AzureADSkipOrgRoleSync,
|
||||
GithubSkipOrgRoleSync: hs.Cfg.GithubSkipOrgRoleSync,
|
||||
GithubSkipOrgRoleSync: hs.Cfg.GitHubSkipOrgRoleSync,
|
||||
GitLabSkipOrgRoleSync: hs.Cfg.GitLabSkipOrgRoleSync,
|
||||
OktaSkipOrgRoleSync: hs.Cfg.OktaSkipOrgRoleSync,
|
||||
DisableSyncLock: hs.Cfg.DisableSyncLock,
|
||||
|
|
|
|||
|
|
@ -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(http.StatusBadRequest, "Cannot change role so that there is no organization admin left", nil)
|
||||
|
|
|
|||
|
|
@ -16,10 +16,12 @@ import (
|
|||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/db/dbtest"
|
||||
"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"
|
||||
|
|
@ -201,6 +203,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(t *testing.T) {
|
||||
type testCase struct {
|
||||
desc string
|
||||
|
|
@ -630,7 +725,11 @@ func TestOrgUsersAPIEndpointWithSetPerms_AccessControl(t *testing.T) {
|
|||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.orgService = &orgtest.FakeOrgService{}
|
||||
hs.authInfoService = &logintest.AuthInfoServiceFake{}
|
||||
hs.authInfoService = &logintest.AuthInfoServiceFake{
|
||||
ExpectedUserAuth: &login.UserAuth{
|
||||
AuthModule: "",
|
||||
},
|
||||
}
|
||||
hs.userService = &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{},
|
||||
ExpectedSignedInUser: userWithPermissions(1, tt.permissions),
|
||||
|
|
@ -708,7 +807,11 @@ func TestPatchOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
|
|||
hs.Cfg = setting.NewCfg()
|
||||
hs.Cfg.RBACEnabled = tt.enableAccessControl
|
||||
hs.orgService = &orgtest.FakeOrgService{}
|
||||
hs.authInfoService = &logintest.AuthInfoServiceFake{}
|
||||
hs.authInfoService = &logintest.AuthInfoServiceFake{
|
||||
ExpectedUserAuth: &login.UserAuth{
|
||||
AuthModule: "",
|
||||
},
|
||||
}
|
||||
hs.accesscontrolService = &actest.FakeService{}
|
||||
hs.userService = &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{},
|
||||
|
|
|
|||
|
|
@ -67,7 +67,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, authLabel)
|
||||
userProfile.IsExternallySynced = login.IsExternallySynced(hs.Cfg, getAuthQuery.Result.AuthModule)
|
||||
}
|
||||
|
||||
userProfile.AccessControl = hs.getAccessControlMetadata(c, c.OrgID, "global.users:id:", strconv.FormatInt(userID, 10))
|
||||
|
|
|
|||
|
|
@ -79,7 +79,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",
|
||||
|
|
@ -213,7 +213,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",
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ func TestAuthenticateUser(t *testing.T) {
|
|||
|
||||
authScenario(t, "When a non-existing grafana user authenticate and invalid ldap credentials", func(sc *authScenarioContext) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
||||
mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc)
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ func TestAuthenticateUser(t *testing.T) {
|
|||
|
||||
authScenario(t, "When a non-existing grafana user authenticate and valid ldap credentials", func(sc *authScenarioContext) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
||||
mockLoginUsingLDAP(true, nil, sc)
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ func TestAuthenticateUser(t *testing.T) {
|
|||
|
||||
authScenario(t, "When a non-existing grafana user authenticate and ldap returns unexpected error", func(sc *authScenarioContext) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
customErr := errors.New("custom")
|
||||
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
||||
mockLoginUsingLDAP(true, customErr, sc)
|
||||
|
|
@ -177,7 +177,7 @@ func TestAuthenticateUser(t *testing.T) {
|
|||
|
||||
authScenario(t, "When grafana user authenticate with invalid credentials and invalid ldap credentials", func(sc *authScenarioContext) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
mockLoginUsingGrafanaDB(ErrInvalidCredentials, sc)
|
||||
mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ var ldapLogger = log.New("login.ldap")
|
|||
// populated with the logged in user if successful.
|
||||
var loginUsingLDAP = func(ctx context.Context, query *login.LoginUserQuery,
|
||||
loginService login.Service, cfg *setting.Cfg) (bool, error) {
|
||||
if !cfg.LDAPEnabled {
|
||||
if !cfg.LDAPAuthEnabled {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ var errTest = errors.New("test error")
|
|||
func TestLoginUsingLDAP(t *testing.T) {
|
||||
LDAPLoginScenario(t, "When LDAP enabled and no server configured", func(sc *LDAPLoginScenarioContext) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
sc.withLoginResult(false)
|
||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||
|
|
@ -41,7 +41,7 @@ func TestLoginUsingLDAP(t *testing.T) {
|
|||
|
||||
LDAPLoginScenario(t, "When LDAP disabled", func(sc *LDAPLoginScenarioContext) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = false
|
||||
cfg.LDAPAuthEnabled = false
|
||||
|
||||
sc.withLoginResult(false)
|
||||
loginService := &logintest.LoginServiceFake{}
|
||||
|
|
|
|||
|
|
@ -156,7 +156,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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -600,7 +600,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"}
|
||||
|
|
@ -645,7 +645,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
|
||||
})
|
||||
|
||||
|
|
@ -666,7 +666,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
|
||||
})
|
||||
|
||||
|
|
@ -689,7 +689,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
|
||||
})
|
||||
|
||||
|
|
@ -715,7 +715,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
|
||||
})
|
||||
|
||||
|
|
@ -733,7 +733,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
|
||||
})
|
||||
|
||||
|
|
@ -807,7 +807,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) {
|
||||
|
|
@ -825,7 +825,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) {
|
||||
|
|
@ -841,7 +841,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) {
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ func ProvideService(
|
|||
|
||||
var proxyClients []authn.ProxyClient
|
||||
var passwordClients []authn.PasswordClient
|
||||
if s.cfg.LDAPEnabled {
|
||||
if s.cfg.LDAPAuthEnabled {
|
||||
ldap := clients.ProvideLDAP(cfg, ldapService)
|
||||
proxyClients = append(proxyClients, ldap)
|
||||
passwordClients = append(passwordClients, ldap)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,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
|
||||
authTypes["jwt"] = s.cfg.JWTAuthEnabled
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ func TestService_getUsageStats(t *testing.T) {
|
|||
svc.cfg.BasicAuthEnabled = true
|
||||
svc.cfg.AuthProxyEnabled = true
|
||||
svc.cfg.JWTAuthEnabled = true
|
||||
svc.cfg.LDAPEnabled = true
|
||||
svc.cfg.LDAPAuthEnabled = true
|
||||
svc.cfg.EditorsCanAdmin = true
|
||||
svc.cfg.ViewersCanEdit = true
|
||||
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ func (auth *AuthProxy) Login(reqCtx *contextmodel.ReqContext, ignoreCache bool)
|
|||
}
|
||||
}
|
||||
|
||||
if auth.cfg.LDAPEnabled {
|
||||
if auth.cfg.LDAPAuthEnabled {
|
||||
id, err := auth.LoginViaLDAP(reqCtx)
|
||||
if err != nil {
|
||||
if errors.Is(err, ldap.ErrInvalidCredentials) {
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ func TestMiddlewareContext_ldap(t *testing.T) {
|
|||
cache := remotecache.NewFakeStore(t)
|
||||
|
||||
auth, reqCtx := prepareMiddleware(t, cache, nil)
|
||||
auth.cfg.LDAPEnabled = true
|
||||
auth.cfg.LDAPAuthEnabled = true
|
||||
ldapFake := &service.LDAPFakeService{
|
||||
ExpectedUser: &login.ExternalUserInfo{UserId: id},
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ func TestMiddlewareContext_ldap(t *testing.T) {
|
|||
cache := remotecache.NewFakeStore(t)
|
||||
|
||||
auth, reqCtx := prepareMiddleware(t, cache, nil)
|
||||
auth.cfg.LDAPEnabled = true
|
||||
auth.cfg.LDAPAuthEnabled = true
|
||||
ldapFake := &service.LDAPFakeService{
|
||||
ExpectedUser: nil,
|
||||
ExpectedError: service.ErrUnableToCreateLDAPClient,
|
||||
|
|
|
|||
|
|
@ -393,6 +393,12 @@ var (
|
|||
State: FeatureStateAlpha,
|
||||
Owner: grafanaBackendPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "onlyExternalOrgRoleSync",
|
||||
Description: "Prohibits a user from changing organization roles synced with external auth providers",
|
||||
State: FeatureStateAlpha,
|
||||
Owner: grafanaAuthnzSquad,
|
||||
},
|
||||
{
|
||||
Name: "drawerDataSourcePicker",
|
||||
Description: "Changes the user experience for data source selection to a drawer.",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ logsContextDatasourceUi,alpha,@grafana/observability-logs,false,false,false,true
|
|||
lokiQuerySplitting,alpha,@grafana/observability-logs,false,false,false,true
|
||||
lokiQuerySplittingConfig,alpha,@grafana/observability-logs,false,false,false,true
|
||||
individualCookiePreferences,alpha,@grafana/backend-platform,false,false,false,false
|
||||
onlyExternalOrgRoleSync,alpha,@grafana/grafana-authnz-team,false,false,false,false
|
||||
drawerDataSourcePicker,alpha,@grafana/grafana-bi-squad,false,false,false,true
|
||||
traceqlSearch,alpha,@grafana/observability-traces-and-profiling,false,false,false,true
|
||||
prometheusMetricEncyclopedia,alpha,@grafana/observability-metrics,false,false,false,true
|
||||
|
|
|
|||
|
|
|
@ -243,6 +243,10 @@ const (
|
|||
// Support overriding cookie preferences per user
|
||||
FlagIndividualCookiePreferences = "individualCookiePreferences"
|
||||
|
||||
// FlagOnlyExternalOrgRoleSync
|
||||
// Prohibits a user from changing organization roles synced with external auth providers
|
||||
FlagOnlyExternalOrgRoleSync = "onlyExternalOrgRoleSync"
|
||||
|
||||
// FlagDrawerDataSourcePicker
|
||||
// Changes the user experience for data source selection to a drawer.
|
||||
FlagDrawerDataSourcePicker = "drawerDataSourcePicker"
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ func ProvideService(cfg *setting.Cfg, router routing.RouteRegister, accessContro
|
|||
adminRoute.Get("/ldap/status", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)), routing.Wrap(s.GetLDAPStatus))
|
||||
}, middleware.ReqSignedIn)
|
||||
|
||||
if cfg.LDAPEnabled {
|
||||
if cfg.LDAPAuthEnabled {
|
||||
bundleRegistry.RegisterSupportItemCollector(supportbundles.Collector{
|
||||
UID: "auth-ldap",
|
||||
DisplayName: "LDAP",
|
||||
|
|
@ -94,7 +94,7 @@ func ProvideService(cfg *setting.Cfg, router routing.RouteRegister, accessContro
|
|||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (s *Service) ReloadLDAPCfg(c *contextmodel.ReqContext) response.Response {
|
||||
if !s.cfg.LDAPEnabled {
|
||||
if !s.cfg.LDAPAuthEnabled {
|
||||
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ func (s *Service) ReloadLDAPCfg(c *contextmodel.ReqContext) response.Response {
|
|||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (s *Service) GetLDAPStatus(c *contextmodel.ReqContext) response.Response {
|
||||
if !s.cfg.LDAPEnabled {
|
||||
if !s.cfg.LDAPAuthEnabled {
|
||||
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ func (s *Service) GetLDAPStatus(c *contextmodel.ReqContext) response.Response {
|
|||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (s *Service) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.Response {
|
||||
if !s.cfg.LDAPEnabled {
|
||||
if !s.cfg.LDAPAuthEnabled {
|
||||
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
||||
}
|
||||
|
||||
|
|
@ -262,7 +262,7 @@ func (s *Service) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.Resp
|
|||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (s *Service) GetUserFromLDAP(c *contextmodel.ReqContext) response.Response {
|
||||
if !s.cfg.LDAPEnabled {
|
||||
if !s.cfg.LDAPAuthEnabled {
|
||||
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ func setupAPITest(t *testing.T, opts ...func(a *Service)) (*Service, *webtest.Se
|
|||
t.Helper()
|
||||
router := routing.NewRouteRegister()
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
a := ProvideService(cfg,
|
||||
router,
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ func (s *Service) supportBundleCollector(context.Context) (*supportbundles.Suppo
|
|||
|
||||
bWriter.WriteString("```ini\n")
|
||||
|
||||
bWriter.WriteString(fmt.Sprintf("enabled = %v\n", s.cfg.LDAPEnabled))
|
||||
bWriter.WriteString(fmt.Sprintf("enabled = %v\n", s.cfg.LDAPAuthEnabled))
|
||||
bWriter.WriteString(fmt.Sprintf("config_file = %s\n", s.cfg.LDAPConfigFilePath))
|
||||
bWriter.WriteString(fmt.Sprintf("allow_sign_up = %v\n", s.cfg.LDAPAllowSignup))
|
||||
bWriter.WriteString(fmt.Sprintf("sync_cron = %s\n", s.cfg.LDAPSyncCron))
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ func TestServer_Login_UserBind_Fail(t *testing.T) {
|
|||
}
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
Config: &ServerConfig{
|
||||
|
|
@ -106,7 +106,7 @@ func TestServer_Login_ValidCredentials(t *testing.T) {
|
|||
}
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
|
@ -143,7 +143,7 @@ func TestServer_Login_UnauthenticatedBind(t *testing.T) {
|
|||
}
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
|
@ -190,7 +190,7 @@ func TestServer_Login_AuthenticatedBind(t *testing.T) {
|
|||
}
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
|
@ -233,7 +233,7 @@ func TestServer_Login_UserWildcardBind(t *testing.T) {
|
|||
}
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ func TestServer_getSearchRequest(t *testing.T) {
|
|||
func TestSerializeUsers(t *testing.T) {
|
||||
t.Run("simple case", func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
|
@ -93,7 +93,7 @@ func TestSerializeUsers(t *testing.T) {
|
|||
|
||||
t.Run("without lastname", func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
|
@ -130,7 +130,7 @@ func TestSerializeUsers(t *testing.T) {
|
|||
|
||||
t.Run("mark user without matching group as disabled", func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
|
@ -164,7 +164,7 @@ func TestSerializeUsers(t *testing.T) {
|
|||
func TestServer_validateGrafanaUser(t *testing.T) {
|
||||
t.Run("no group config", func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
|
@ -184,7 +184,7 @@ func TestServer_validateGrafanaUser(t *testing.T) {
|
|||
|
||||
t.Run("user in group", func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
|
@ -211,7 +211,7 @@ func TestServer_validateGrafanaUser(t *testing.T) {
|
|||
|
||||
t.Run("user not in group", func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ func TestServer_Users(t *testing.T) {
|
|||
|
||||
// Set up attribute map without surname and email
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
|
@ -212,7 +212,7 @@ func TestServer_Users(t *testing.T) {
|
|||
})
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
|
@ -289,7 +289,7 @@ func TestServer_Users(t *testing.T) {
|
|||
})
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ func ProvideService(cfg *setting.Cfg) *LDAPImpl {
|
|||
loadingMutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
if !cfg.LDAPEnabled {
|
||||
if !cfg.LDAPAuthEnabled {
|
||||
return s
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ func ProvideService(cfg *setting.Cfg) *LDAPImpl {
|
|||
}
|
||||
|
||||
func (s *LDAPImpl) ReloadConfig() error {
|
||||
if !s.cfg.LDAPEnabled {
|
||||
if !s.cfg.LDAPAuthEnabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ func (s *LDAPImpl) Config() *ldap.Config {
|
|||
}
|
||||
|
||||
func (s *LDAPImpl) Login(query *login.LoginUserQuery) (*login.ExternalUserInfo, error) {
|
||||
if !s.cfg.LDAPEnabled {
|
||||
if !s.cfg.LDAPAuthEnabled {
|
||||
return nil, ErrLDAPNotEnabled
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +108,7 @@ func (s *LDAPImpl) Login(query *login.LoginUserQuery) (*login.ExternalUserInfo,
|
|||
}
|
||||
|
||||
func (s *LDAPImpl) User(username string) (*login.ExternalUserInfo, error) {
|
||||
if !s.cfg.LDAPEnabled {
|
||||
if !s.cfg.LDAPAuthEnabled {
|
||||
return nil, ErrLDAPNotEnabled
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,10 +87,10 @@ 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 !cfg.LDAPEnabled {
|
||||
} else if !cfg.LDAPAuthEnabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ const (
|
|||
LDAPLabel = "LDAP"
|
||||
JWTLabel = "JWT"
|
||||
// OAuth provider labels
|
||||
AuthProxtLabel = "Auth Proxy"
|
||||
AuthProxyLabel = "Auth Proxy"
|
||||
AzureADLabel = "AzureAD"
|
||||
GoogleLabel = "Google"
|
||||
GenericOAuthLabel = "Generic OAuth"
|
||||
|
|
@ -72,14 +72,18 @@ const (
|
|||
// 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, autoProviderLabel string) bool {
|
||||
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 autoProviderLabel {
|
||||
case SAMLLabel:
|
||||
switch authModule {
|
||||
case SAMLAuthModule:
|
||||
return !cfg.SAMLSkipOrgRoleSync
|
||||
case LDAPLabel:
|
||||
case LDAPAuthModule:
|
||||
return !cfg.LDAPSkipOrgRoleSync
|
||||
case JWTLabel:
|
||||
case JWTModule:
|
||||
return !cfg.JWTAuthSkipOrgRoleSync
|
||||
}
|
||||
// then check the rest of the oauth providers
|
||||
|
|
@ -88,25 +92,52 @@ func IsExternallySynced(cfg *setting.Cfg, autoProviderLabel string) bool {
|
|||
if cfg.OAuthSkipOrgRoleUpdateSync {
|
||||
return false
|
||||
}
|
||||
switch autoProviderLabel {
|
||||
case GoogleLabel:
|
||||
switch authModule {
|
||||
case GoogleAuthModule:
|
||||
return !cfg.GoogleSkipOrgRoleSync
|
||||
case OktaLabel:
|
||||
case OktaAuthModule:
|
||||
return !cfg.OktaSkipOrgRoleSync
|
||||
case AzureADLabel:
|
||||
case AzureADAuthModule:
|
||||
return !cfg.AzureADSkipOrgRoleSync
|
||||
case GitLabLabel:
|
||||
case GitLabAuthModule:
|
||||
return !cfg.GitLabSkipOrgRoleSync
|
||||
case GithubLabel:
|
||||
return !cfg.GithubSkipOrgRoleSync
|
||||
case GrafanaComLabel:
|
||||
case GithubAuthModule:
|
||||
return !cfg.GitHubSkipOrgRoleSync
|
||||
case GrafanaComAuthModule:
|
||||
return !cfg.GrafanaComSkipOrgRoleSync
|
||||
case GenericOAuthLabel:
|
||||
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 GithubAuthModule:
|
||||
|
|
@ -128,7 +159,7 @@ func GetAuthProviderLabel(authModule string) string {
|
|||
case JWTModule:
|
||||
return JWTLabel
|
||||
case AuthProxyAuthModule:
|
||||
return AuthProxtLabel
|
||||
return AuthProxyLabel
|
||||
case GenericOAuthModule:
|
||||
return GenericOAuthLabel
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -14,182 +14,199 @@ func TestIsExternallySynced(t *testing.T) {
|
|||
provider string
|
||||
expected bool
|
||||
}{
|
||||
// azure
|
||||
{
|
||||
name: "AzureAD synced user should return that it is externally synced",
|
||||
cfg: &setting.Cfg{AzureADSkipOrgRoleSync: false},
|
||||
provider: AzureADLabel,
|
||||
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{AzureADSkipOrgRoleSync: true},
|
||||
provider: AzureADLabel,
|
||||
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{AzureADSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
|
||||
provider: AzureADLabel,
|
||||
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{GoogleSkipOrgRoleSync: false},
|
||||
provider: GoogleLabel,
|
||||
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{GoogleSkipOrgRoleSync: true},
|
||||
provider: GoogleLabel,
|
||||
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{GoogleSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
|
||||
provider: GoogleLabel,
|
||||
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{GoogleSkipOrgRoleSync: true, OAuthSkipOrgRoleUpdateSync: true},
|
||||
provider: GoogleLabel,
|
||||
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{OktaSkipOrgRoleSync: false},
|
||||
provider: OktaLabel,
|
||||
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{OktaSkipOrgRoleSync: true},
|
||||
cfg: &setting.Cfg{OktaAuthEnabled: true, OktaSkipOrgRoleSync: true},
|
||||
|
||||
provider: OktaLabel,
|
||||
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{OktaSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
|
||||
provider: OktaLabel,
|
||||
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{GithubSkipOrgRoleSync: false},
|
||||
provider: GithubLabel,
|
||||
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{GithubSkipOrgRoleSync: true},
|
||||
provider: GithubLabel,
|
||||
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{GithubSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
|
||||
provider: GithubLabel,
|
||||
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{GitLabSkipOrgRoleSync: false},
|
||||
provider: GitLabLabel,
|
||||
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{GitLabSkipOrgRoleSync: true},
|
||||
provider: GitLabLabel,
|
||||
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{GitLabSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
|
||||
provider: GitLabLabel,
|
||||
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{GrafanaComSkipOrgRoleSync: false},
|
||||
provider: GrafanaComLabel,
|
||||
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{GrafanaComSkipOrgRoleSync: true},
|
||||
provider: GrafanaComLabel,
|
||||
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{GrafanaComSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
|
||||
provider: GrafanaComLabel,
|
||||
cfg: &setting.Cfg{GrafanaComAuthEnabled: true, GrafanaComSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
|
||||
provider: GrafanaComAuthModule,
|
||||
expected: false,
|
||||
},
|
||||
// generic oauth
|
||||
{
|
||||
name: "Generic OAuth synced user should return that it is externally synced",
|
||||
cfg: &setting.Cfg{OAuthSkipOrgRoleUpdateSync: false},
|
||||
provider: GenericOAuthLabel,
|
||||
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: "Generic OAuth synced user should return that it is not externally synced when org role sync is set",
|
||||
cfg: &setting.Cfg{OAuthSkipOrgRoleUpdateSync: true},
|
||||
provider: GenericOAuthLabel,
|
||||
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{GenericOAuthSkipOrgRoleSync: false, OAuthSkipOrgRoleUpdateSync: true},
|
||||
provider: GenericOAuthLabel,
|
||||
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{SAMLSkipOrgRoleSync: false},
|
||||
provider: SAMLLabel,
|
||||
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{SAMLSkipOrgRoleSync: true},
|
||||
provider: SAMLLabel,
|
||||
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{LDAPSkipOrgRoleSync: false},
|
||||
provider: LDAPLabel,
|
||||
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{LDAPSkipOrgRoleSync: true},
|
||||
provider: LDAPLabel,
|
||||
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{JWTAuthSkipOrgRoleSync: false},
|
||||
provider: JWTLabel,
|
||||
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: JWTLabel,
|
||||
provider: JWTModule,
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
|
@ -200,3 +217,32 @@ func TestIsExternallySynced(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ func (s *ServiceImpl) getServerAdminNode(c *contextmodel.ReqContext) *navtree.Na
|
|||
adminNavLinks = append(adminNavLinks, storage)
|
||||
}
|
||||
|
||||
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",
|
||||
})
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ import (
|
|||
|
||||
// Typed errors
|
||||
var (
|
||||
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 = errutil.NewBase(errutil.StatusNotFound, "org.notFound", errutil.WithPublicMessage("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")
|
||||
ErrOrgNotFound = errutil.NewBase(errutil.StatusNotFound, "org.notFound", errutil.WithPublicMessage("organization not found"))
|
||||
ErrCannotChangeRoleForExternallySyncedUser = errutil.NewBase(errutil.StatusForbidden, "org.externallySynced", errutil.WithPublicMessage("cannot change role for externally synced user"))
|
||||
)
|
||||
|
||||
type Org struct {
|
||||
|
|
@ -139,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 {
|
||||
|
|
|
|||
|
|
@ -405,19 +405,23 @@ type Cfg struct {
|
|||
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
|
||||
LDAPAuthEnabled bool
|
||||
LDAPSkipOrgRoleSync bool
|
||||
LDAPConfigFilePath string
|
||||
LDAPAllowSignup bool
|
||||
|
|
@ -453,8 +457,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
|
||||
|
|
@ -462,6 +467,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
|
||||
|
|
@ -486,9 +493,11 @@ type Cfg struct {
|
|||
SecureSocksDSProxy SecureSocksDSProxySettings
|
||||
|
||||
// SAML Auth
|
||||
SAMLAuthEnabled bool
|
||||
SAMLSkipOrgRoleSync bool
|
||||
|
||||
// Okta OAuth
|
||||
OktaAuthEnabled bool
|
||||
OktaSkipOrgRoleSync bool
|
||||
|
||||
// Access Control
|
||||
|
|
@ -1190,6 +1199,7 @@ type RemoteCacheOptions struct {
|
|||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -1197,7 +1207,7 @@ func (cfg *Cfg) readLDAPConfig() {
|
|||
ldapSec := cfg.Raw.Section("auth.ldap")
|
||||
cfg.LDAPConfigFilePath = ldapSec.Key("config_file").String()
|
||||
cfg.LDAPSyncCron = ldapSec.Key("sync_cron").String()
|
||||
cfg.LDAPEnabled = ldapSec.Key("enabled").MustBool(false)
|
||||
cfg.LDAPAuthEnabled = ldapSec.Key("enabled").MustBool(false)
|
||||
cfg.LDAPSkipOrgRoleSync = ldapSec.Key("skip_org_role_sync").MustBool(false)
|
||||
cfg.LDAPActiveSyncEnabled = ldapSec.Key("active_sync_enabled").MustBool(false)
|
||||
cfg.LDAPAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true)
|
||||
|
|
@ -1376,36 +1386,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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ const UserListAdminPageUnConnected = ({
|
|||
<Icon name="question-circle" />
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th style={{ width: '1%' }}>Synced from</th>
|
||||
<th style={{ width: '1%' }}>Origin</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
|||
|
|
@ -426,6 +426,9 @@ const getChangeOrgButtonTheme = (theme: GrafanaTheme2) => ({
|
|||
margin-left: 1.8rem;
|
||||
margin-right: 0.6rem;
|
||||
`,
|
||||
icon: css`
|
||||
line-height: 2;
|
||||
`,
|
||||
});
|
||||
|
||||
export function ChangeOrgButton({
|
||||
|
|
@ -443,6 +446,7 @@ export function ChangeOrgButton({
|
|||
<span className={styles.lockMessageClass}>{lockMessage}</span>
|
||||
<Tooltip
|
||||
placement="right-end"
|
||||
interactive={true}
|
||||
content={
|
||||
<div>
|
||||
This user's role is not editable because it is synchronized from your auth provider. Refer to
|
||||
|
|
@ -459,7 +463,9 @@ export function ChangeOrgButton({
|
|||
</div>
|
||||
}
|
||||
>
|
||||
<Icon name="question-circle" />
|
||||
<div className={styles.icon}>
|
||||
<Icon name="question-circle" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue