(cherry picked from commit 5caf97be40)
Co-authored-by: Karl Persson <kalle.persson@grafana.com>
This commit is contained in:
co-authored by
Karl Persson
parent
1d4e2d12da
commit
4bd6f62dfd
+2
-2
@@ -250,15 +250,15 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
// current org without requirement of user to be org admin
|
// current org without requirement of user to be org admin
|
||||||
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
|
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
|
||||||
lookupEvaluator := func() ac.Evaluator {
|
lookupEvaluator := func() ac.Evaluator {
|
||||||
if hs.Cfg.IsEnterprise {
|
if hs.License.FeatureEnabled("accesscontrol.enforcement") {
|
||||||
return ac.EvalPermission(ac.ActionOrgUsersRead)
|
return ac.EvalPermission(ac.ActionOrgUsersRead)
|
||||||
}
|
}
|
||||||
// For oss we allow users with access to update permissions on either folders, teams or dashboards to perform the lookup
|
// For oss we allow users with access to update permissions on either folders, teams or dashboards to perform the lookup
|
||||||
return ac.EvalAny(
|
return ac.EvalAny(
|
||||||
ac.EvalPermission(ac.ActionOrgUsersRead),
|
ac.EvalPermission(ac.ActionOrgUsersRead),
|
||||||
ac.EvalPermission(ac.ActionTeamsPermissionsWrite),
|
ac.EvalPermission(ac.ActionTeamsPermissionsWrite),
|
||||||
ac.EvalPermission(dashboards.ActionDashboardsPermissionsWrite),
|
|
||||||
ac.EvalPermission(dashboards.ActionFoldersPermissionsWrite),
|
ac.EvalPermission(dashboards.ActionFoldersPermissionsWrite),
|
||||||
|
ac.EvalPermission(dashboards.ActionDashboardsPermissionsWrite),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
orgRoute.Get("/users/lookup", authorize(reqOrgAdminFolderAdminOrTeamAdmin, lookupEvaluator()), routing.Wrap(hs.GetOrgUsersForCurrentOrgLookup))
|
orgRoute.Get("/users/lookup", authorize(reqOrgAdminFolderAdminOrTeamAdmin, lookupEvaluator()), routing.Wrap(hs.GetOrgUsersForCurrentOrgLookup))
|
||||||
|
|||||||
@@ -236,6 +236,7 @@ func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url strin
|
|||||||
hs := &HTTPServer{
|
hs := &HTTPServer{
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
Live: newTestLive(t, store),
|
Live: newTestLive(t, store),
|
||||||
|
License: &licensing.OSSLicensingService{},
|
||||||
Features: featuremgmt.WithFeatures(),
|
Features: featuremgmt.WithFeatures(),
|
||||||
QuotaService: "a.QuotaService{Cfg: cfg},
|
QuotaService: "a.QuotaService{Cfg: cfg},
|
||||||
RouteRegister: routing.NewRouteRegister(),
|
RouteRegister: routing.NewRouteRegister(),
|
||||||
@@ -325,6 +326,7 @@ func setupSimpleHTTPServer(features *featuremgmt.FeatureManager) *HTTPServer {
|
|||||||
return &HTTPServer{
|
return &HTTPServer{
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
Features: features,
|
Features: features,
|
||||||
|
License: &licensing.OSSLicensingService{},
|
||||||
AccessControl: accesscontrolmock.New().WithDisabled(),
|
AccessControl: accesscontrolmock.New().WithDisabled(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,7 +390,7 @@ func setupHTTPServerWithCfgDb(t *testing.T, useFakeAccessControl, enableAccessCo
|
|||||||
acmock = acmock.WithDisabled()
|
acmock = acmock.WithDisabled()
|
||||||
}
|
}
|
||||||
hs.AccessControl = acmock
|
hs.AccessControl = acmock
|
||||||
teamPermissionService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, routeRegister, db, acmock, database.ProvideService(db))
|
teamPermissionService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, routeRegister, db, acmock, database.ProvideService(db), hs.License)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
hs.teamPermissionsService = teamPermissionService
|
hs.teamPermissionsService = teamPermissionService
|
||||||
} else {
|
} else {
|
||||||
@@ -400,7 +402,7 @@ func setupHTTPServerWithCfgDb(t *testing.T, useFakeAccessControl, enableAccessCo
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = ac.RegisterFixedRoles(context.Background())
|
err = ac.RegisterFixedRoles(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
teamPermissionService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, routeRegister, db, ac, database.ProvideService(db))
|
teamPermissionService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, routeRegister, db, ac, database.ProvideService(db), hs.License)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
hs.teamPermissionsService = teamPermissionService
|
hs.teamPermissionsService = teamPermissionService
|
||||||
}
|
}
|
||||||
@@ -464,6 +466,7 @@ func SetupAPITestServer(t *testing.T, opts ...APITestServerOption) *webtest.Serv
|
|||||||
hs := &HTTPServer{
|
hs := &HTTPServer{
|
||||||
RouteRegister: routing.NewRouteRegister(),
|
RouteRegister: routing.NewRouteRegister(),
|
||||||
Cfg: setting.NewCfg(),
|
Cfg: setting.NewCfg(),
|
||||||
|
License: &licensing.OSSLicensingService{},
|
||||||
AccessControl: accesscontrolmock.New().WithDisabled(),
|
AccessControl: accesscontrolmock.New().WithDisabled(),
|
||||||
Features: featuremgmt.WithFeatures(),
|
Features: featuremgmt.WithFeatures(),
|
||||||
searchUsersService: &searchusers.OSSService{},
|
searchUsersService: &searchusers.OSSService{},
|
||||||
|
|||||||
@@ -89,10 +89,11 @@ func (hs *HTTPServer) GetOrgUsersForCurrentOrg(c *models.ReqContext) response.Re
|
|||||||
// GET /api/org/users/lookup
|
// GET /api/org/users/lookup
|
||||||
func (hs *HTTPServer) GetOrgUsersForCurrentOrgLookup(c *models.ReqContext) response.Response {
|
func (hs *HTTPServer) GetOrgUsersForCurrentOrgLookup(c *models.ReqContext) response.Response {
|
||||||
orgUsers, err := hs.getOrgUsersHelper(c, &models.GetOrgUsersQuery{
|
orgUsers, err := hs.getOrgUsersHelper(c, &models.GetOrgUsersQuery{
|
||||||
OrgId: c.OrgId,
|
OrgId: c.OrgId,
|
||||||
Query: c.Query("query"),
|
Query: c.Query("query"),
|
||||||
Limit: c.QueryInt("limit"),
|
Limit: c.QueryInt("limit"),
|
||||||
User: c.SignedInUser,
|
User: c.SignedInUser,
|
||||||
|
DontEnforceAccessControl: !hs.License.FeatureEnabled("accesscontrol.enforcement"),
|
||||||
}, c.SignedInUser)
|
}, c.SignedInUser)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ type GetOrgUsersQuery struct {
|
|||||||
OrgId int64
|
OrgId int64
|
||||||
Query string
|
Query string
|
||||||
Limit int
|
Limit int
|
||||||
|
// Flag used to allow oss edition to query users without access control
|
||||||
|
DontEnforceAccessControl bool
|
||||||
|
|
||||||
User *SignedInUser
|
User *SignedInUser
|
||||||
Result []*OrgUserDTO
|
Result []*OrgUserDTO
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ var (
|
|||||||
|
|
||||||
func ProvideTeamPermissions(
|
func ProvideTeamPermissions(
|
||||||
cfg *setting.Cfg, router routing.RouteRegister, sql *sqlstore.SQLStore,
|
cfg *setting.Cfg, router routing.RouteRegister, sql *sqlstore.SQLStore,
|
||||||
ac accesscontrol.AccessControl, store resourcepermissions.Store,
|
ac accesscontrol.AccessControl, store resourcepermissions.Store, license models.Licensing,
|
||||||
) (*TeamPermissionsService, error) {
|
) (*TeamPermissionsService, error) {
|
||||||
options := resourcepermissions.Options{
|
options := resourcepermissions.Options{
|
||||||
Resource: "teams",
|
Resource: "teams",
|
||||||
@@ -91,7 +91,7 @@ func ProvideTeamPermissions(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
srv, err := resourcepermissions.New(options, cfg, router, ac, store, sql)
|
srv, err := resourcepermissions.New(options, cfg, router, license, ac, store, sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ var DashboardAdminActions = append(DashboardEditActions, []string{dashboards.Act
|
|||||||
func ProvideDashboardPermissions(
|
func ProvideDashboardPermissions(
|
||||||
cfg *setting.Cfg, router routing.RouteRegister, sql *sqlstore.SQLStore,
|
cfg *setting.Cfg, router routing.RouteRegister, sql *sqlstore.SQLStore,
|
||||||
ac accesscontrol.AccessControl, store resourcepermissions.Store,
|
ac accesscontrol.AccessControl, store resourcepermissions.Store,
|
||||||
dashboardStore dashboards.Store,
|
license models.Licensing, dashboardStore dashboards.Store,
|
||||||
) (*DashboardPermissionsService, error) {
|
) (*DashboardPermissionsService, error) {
|
||||||
getDashboard := func(ctx context.Context, orgID int64, resourceID string) (*models.Dashboard, error) {
|
getDashboard := func(ctx context.Context, orgID int64, resourceID string) (*models.Dashboard, error) {
|
||||||
query := &models.GetDashboardQuery{Uid: resourceID, OrgId: orgID}
|
query := &models.GetDashboardQuery{Uid: resourceID, OrgId: orgID}
|
||||||
@@ -164,7 +164,7 @@ func ProvideDashboardPermissions(
|
|||||||
RoleGroup: "Dashboards",
|
RoleGroup: "Dashboards",
|
||||||
}
|
}
|
||||||
|
|
||||||
srv, err := resourcepermissions.New(options, cfg, router, ac, store, sql)
|
srv, err := resourcepermissions.New(options, cfg, router, license, ac, store, sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -182,7 +182,7 @@ var FolderAdminActions = append(FolderEditActions, []string{dashboards.ActionFol
|
|||||||
func ProvideFolderPermissions(
|
func ProvideFolderPermissions(
|
||||||
cfg *setting.Cfg, router routing.RouteRegister, sql *sqlstore.SQLStore,
|
cfg *setting.Cfg, router routing.RouteRegister, sql *sqlstore.SQLStore,
|
||||||
accesscontrol accesscontrol.AccessControl, store resourcepermissions.Store,
|
accesscontrol accesscontrol.AccessControl, store resourcepermissions.Store,
|
||||||
dashboardStore dashboards.Store,
|
license models.Licensing, dashboardStore dashboards.Store,
|
||||||
) (*FolderPermissionsService, error) {
|
) (*FolderPermissionsService, error) {
|
||||||
options := resourcepermissions.Options{
|
options := resourcepermissions.Options{
|
||||||
Resource: "folders",
|
Resource: "folders",
|
||||||
@@ -213,7 +213,7 @@ func ProvideFolderPermissions(
|
|||||||
WriterRoleName: "Folder permission writer",
|
WriterRoleName: "Folder permission writer",
|
||||||
RoleGroup: "Folders",
|
RoleGroup: "Folders",
|
||||||
}
|
}
|
||||||
srv, err := resourcepermissions.New(options, cfg, router, accesscontrol, store, sql)
|
srv, err := resourcepermissions.New(options, cfg, router, license, accesscontrol, store, sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ func (a *api) getPermissions(c *models.ReqContext) response.Response {
|
|||||||
return response.Error(http.StatusInternalServerError, "failed to get permissions", err)
|
return response.Error(http.StatusInternalServerError, "failed to get permissions", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.service.options.Assignments.BuiltInRoles && !a.service.cfg.IsEnterprise {
|
if a.service.options.Assignments.BuiltInRoles && !a.service.license.FeatureEnabled("accesscontrol.enforcement") {
|
||||||
permissions = append(permissions, accesscontrol.ResourcePermission{
|
permissions = append(permissions, accesscontrol.ResourcePermission{
|
||||||
Actions: a.service.actions,
|
Actions: a.service.actions,
|
||||||
Scope: "*",
|
Scope: "*",
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ type Store interface {
|
|||||||
GetResourcePermissions(ctx context.Context, orgID int64, query types.GetResourcePermissionsQuery) ([]accesscontrol.ResourcePermission, error)
|
GetResourcePermissions(ctx context.Context, orgID int64, query types.GetResourcePermissionsQuery) ([]accesscontrol.ResourcePermission, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(options Options, cfg *setting.Cfg, router routing.RouteRegister, ac accesscontrol.AccessControl, store Store, sqlStore *sqlstore.SQLStore) (*Service, error) {
|
func New(
|
||||||
|
options Options, cfg *setting.Cfg, router routing.RouteRegister, license models.Licensing,
|
||||||
|
ac accesscontrol.AccessControl, store Store, sqlStore *sqlstore.SQLStore,
|
||||||
|
) (*Service, error) {
|
||||||
var permissions []string
|
var permissions []string
|
||||||
actionSet := make(map[string]struct{})
|
actionSet := make(map[string]struct{})
|
||||||
for permission, actions := range options.PermissionsToActions {
|
for permission, actions := range options.PermissionsToActions {
|
||||||
@@ -71,6 +74,7 @@ func New(options Options, cfg *setting.Cfg, router routing.RouteRegister, ac acc
|
|||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
store: store,
|
store: store,
|
||||||
options: options,
|
options: options,
|
||||||
|
license: license,
|
||||||
permissions: permissions,
|
permissions: permissions,
|
||||||
actions: actions,
|
actions: actions,
|
||||||
sqlStore: sqlStore,
|
sqlStore: sqlStore,
|
||||||
@@ -89,10 +93,11 @@ func New(options Options, cfg *setting.Cfg, router routing.RouteRegister, ac acc
|
|||||||
|
|
||||||
// Service is used to create access control sub system including api / and service for managed resource permission
|
// Service is used to create access control sub system including api / and service for managed resource permission
|
||||||
type Service struct {
|
type Service struct {
|
||||||
cfg *setting.Cfg
|
cfg *setting.Cfg
|
||||||
ac accesscontrol.AccessControl
|
ac accesscontrol.AccessControl
|
||||||
store Store
|
store Store
|
||||||
api *api
|
api *api
|
||||||
|
license models.Licensing
|
||||||
|
|
||||||
options Options
|
options Options
|
||||||
permissions []string
|
permissions []string
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
||||||
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||||
|
"github.com/grafana/grafana/pkg/services/licensing/licensingtest"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
@@ -221,8 +222,12 @@ func setupTestEnvironment(t *testing.T, permissions []*accesscontrol.Permission,
|
|||||||
sql := sqlstore.InitTestDB(t)
|
sql := sqlstore.InitTestDB(t)
|
||||||
store := database.ProvideService(sql)
|
store := database.ProvideService(sql)
|
||||||
cfg := setting.NewCfg()
|
cfg := setting.NewCfg()
|
||||||
cfg.IsEnterprise = true
|
license := licensingtest.NewFakeLicensing()
|
||||||
service, err := New(ops, cfg, routing.NewRouteRegister(), accesscontrolmock.New().WithPermissions(permissions), store, sql)
|
license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe()
|
||||||
|
service, err := New(
|
||||||
|
ops, cfg, routing.NewRouteRegister(), license,
|
||||||
|
accesscontrolmock.New().WithPermissions(permissions), store, sql,
|
||||||
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return service, sql
|
return service, sql
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
dashdb "github.com/grafana/grafana/pkg/services/dashboards/database"
|
dashdb "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||||
|
"github.com/grafana/grafana/pkg/services/licensing/licensingtest"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
@@ -594,11 +595,14 @@ func setupAccessControlGuardianTest(t *testing.T, uid string, permissions []*acc
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
ac := accesscontrolmock.New().WithPermissions(permissions)
|
ac := accesscontrolmock.New().WithPermissions(permissions)
|
||||||
|
license := licensingtest.NewFakeLicensing()
|
||||||
|
license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe()
|
||||||
|
|
||||||
folderPermissions, err := ossaccesscontrol.ProvideFolderPermissions(
|
folderPermissions, err := ossaccesscontrol.ProvideFolderPermissions(
|
||||||
setting.NewCfg(), routing.NewRouteRegister(), store, ac, database.ProvideService(store), &dashboards.FakeDashboardStore{})
|
setting.NewCfg(), routing.NewRouteRegister(), store, ac, database.ProvideService(store), license, &dashboards.FakeDashboardStore{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
dashboardPermissions, err := ossaccesscontrol.ProvideDashboardPermissions(
|
dashboardPermissions, err := ossaccesscontrol.ProvideDashboardPermissions(
|
||||||
setting.NewCfg(), routing.NewRouteRegister(), store, ac, database.ProvideService(store), &dashboards.FakeDashboardStore{})
|
setting.NewCfg(), routing.NewRouteRegister(), store, ac, database.ProvideService(store), license, &dashboards.FakeDashboardStore{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if dashboardSvc == nil {
|
if dashboardSvc == nil {
|
||||||
dashboardSvc = &dashboards.FakeDashboardService{}
|
dashboardSvc = &dashboards.FakeDashboardService{}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package licensingtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ models.Licensing = new(FakeLicensing)
|
||||||
|
|
||||||
|
func NewFakeLicensing() *FakeLicensing {
|
||||||
|
return &FakeLicensing{&mock.Mock{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FakeLicensing struct {
|
||||||
|
*mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeLicensing) Expiry() int64 {
|
||||||
|
mockedArgs := f.Called()
|
||||||
|
return mockedArgs.Get(0).(int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeLicensing) Edition() string {
|
||||||
|
mockedArgs := f.Called()
|
||||||
|
return mockedArgs.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeLicensing) ContentDeliveryPrefix() string {
|
||||||
|
mockedArgs := f.Called()
|
||||||
|
return mockedArgs.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeLicensing) LicenseURL(showAdminLicensingPage bool) string {
|
||||||
|
mockedArgs := f.Called(showAdminLicensingPage)
|
||||||
|
return mockedArgs.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeLicensing) StateInfo() string {
|
||||||
|
mockedArgs := f.Called()
|
||||||
|
return mockedArgs.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeLicensing) EnabledFeatures() map[string]bool {
|
||||||
|
mockedArgs := f.Called()
|
||||||
|
return mockedArgs.Get(0).(map[string]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeLicensing) FeatureEnabled(feature string) bool {
|
||||||
|
mockedArgs := f.Called(feature)
|
||||||
|
return mockedArgs.Get(0).(bool)
|
||||||
|
}
|
||||||
@@ -112,7 +112,8 @@ func (ss *SQLStore) GetOrgUsers(ctx context.Context, query *models.GetOrgUsersQu
|
|||||||
if query.User == nil {
|
if query.User == nil {
|
||||||
ss.log.Warn("Query user not set for filtering.")
|
ss.log.Warn("Query user not set for filtering.")
|
||||||
}
|
}
|
||||||
if ss.Cfg.IsEnterprise && !accesscontrol.IsDisabled(ss.Cfg) {
|
|
||||||
|
if !query.DontEnforceAccessControl && !accesscontrol.IsDisabled(ss.Cfg) {
|
||||||
acFilter, err := accesscontrol.Filter(query.User, "org_user.user_id", "users:id:", accesscontrol.ActionOrgUsersRead)
|
acFilter, err := accesscontrol.Filter(query.User, "org_user.user_id", "users:id:", accesscontrol.ActionOrgUsersRead)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user