Authz: Remove use of SignedInUser copy for permission evaluation (#78448)

* remove use of SignedInUserCopies

* add extra safety to not cross assign permissions

unwind circular dependency

dashboardacl->dashboardaccess

fix missing import

* correctly set teams for permissions

* fix missing inits

* nit: check err

* exit early for api keys
This commit is contained in:
Jo
2023-11-22 14:20:22 +01:00
committed by GitHub
parent 392a4342a8
commit 0de66a8099
44 changed files with 422 additions and 337 deletions

View File

@ -13,6 +13,7 @@ import (
alertmodels "github.com/grafana/grafana/pkg/services/alerting/models"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
@ -113,7 +114,7 @@ func (hs *HTTPServer) GetAlerts(c *contextmodel.ReqContext) response.Response {
DashboardIds: dashboardIDs,
Type: string(model.DashHitDB),
FolderIds: folderIDs, // nolint:staticcheck
Permission: dashboards.PERMISSION_VIEW,
Permission: dashboardaccess.PERMISSION_VIEW,
}
hits, err := hs.SearchService.SearchHandler(c.Req.Context(), &searchQuery)

View File

@ -64,7 +64,7 @@ func (hs *HTTPServer) registerRoutes() {
reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg)
redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg)
authorize := ac.Middleware(hs.AccessControl)
authorizeInOrg := ac.AuthorizeInOrgMiddleware(hs.AccessControl, hs.accesscontrolService, hs.userService)
authorizeInOrg := ac.AuthorizeInOrgMiddleware(hs.AccessControl, hs.accesscontrolService, hs.userService, hs.teamService)
quota := middleware.Quota(hs.QuotaService)
r := hs.RouteRegister

View File

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/web"
)
@ -166,10 +167,10 @@ func (hs *HTTPServer) UpdateDashboardPermissions(c *contextmodel.ReqContext) res
return response.Success("Dashboard permissions updated")
}
var dashboardPermissionMap = map[string]dashboards.PermissionType{
"View": dashboards.PERMISSION_VIEW,
"Edit": dashboards.PERMISSION_EDIT,
"Admin": dashboards.PERMISSION_ADMIN,
var dashboardPermissionMap = map[string]dashboardaccess.PermissionType{
"View": dashboardaccess.PERMISSION_VIEW,
"Edit": dashboardaccess.PERMISSION_EDIT,
"Admin": dashboardaccess.PERMISSION_ADMIN,
}
func (hs *HTTPServer) getDashboardACL(ctx context.Context, user identity.Requester, dashboard *dashboards.Dashboard) ([]*dashboards.DashboardACLInfoDTO, error) {
@ -313,11 +314,11 @@ func (hs *HTTPServer) updateDashboardAccessControl(ctx context.Context, orgID in
func validatePermissionsUpdate(apiCmd dtos.UpdateDashboardACLCommand) error {
for _, item := range apiCmd.Items {
if item.UserID > 0 && item.TeamID > 0 {
return dashboards.ErrPermissionsWithUserAndTeamNotAllowed
return dashboardaccess.ErrPermissionsWithUserAndTeamNotAllowed
}
if (item.UserID > 0 || item.TeamID > 0) && item.Role != nil {
return dashboards.ErrPermissionsWithRoleNotAllowed
return dashboardaccess.ErrPermissionsWithRoleNotAllowed
}
}
return nil

View File

@ -1,7 +1,7 @@
package dtos
import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/org"
)
@ -21,5 +21,5 @@ type DashboardACLUpdateItem struct {
// * `2` - Edit
// * `4` - Admin
// Enum: 1,2,4
Permission dashboards.PermissionType `json:"permission"`
Permission dashboardaccess.PermissionType `json:"permission"`
}

View File

@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian"
@ -186,15 +187,15 @@ func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int
}
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
UserID: userID, Permission: dashboards.PERMISSION_ADMIN.String(),
UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(),
})
}
isNested := folder.ParentUID != ""
if !isNested || !hs.Features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
}...)
}
@ -449,7 +450,7 @@ func (hs *HTTPServer) searchFolders(c *contextmodel.ReqContext) ([]*folder.Folde
Limit: c.QueryInt64("limit"),
OrgId: c.SignedInUser.GetOrgID(),
Type: "dash-folder",
Permission: dashboards.PERMISSION_VIEW,
Permission: dashboardaccess.PERMISSION_VIEW,
Page: c.QueryInt64("page"),
}

View File

@ -27,6 +27,7 @@ import (
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/dashboards/database"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@ -270,7 +271,7 @@ func setupDB(b testing.TB) benchScenario {
UserID: userID,
TeamID: teamID,
OrgID: orgID,
Permission: dashboards.PERMISSION_VIEW,
Permission: dashboardaccess.PERMISSION_VIEW,
Created: now,
Updated: now,
})

View File

@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/web"
@ -118,10 +119,10 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *contextmodel.ReqContext) respon
return response.Success("Folder permissions updated")
}
var folderPermissionMap = map[string]dashboards.PermissionType{
"View": dashboards.PERMISSION_VIEW,
"Edit": dashboards.PERMISSION_EDIT,
"Admin": dashboards.PERMISSION_ADMIN,
var folderPermissionMap = map[string]dashboardaccess.PermissionType{
"View": dashboardaccess.PERMISSION_VIEW,
"Edit": dashboardaccess.PERMISSION_EDIT,
"Admin": dashboardaccess.PERMISSION_ADMIN,
}
func (hs *HTTPServer) getFolderACL(ctx context.Context, user identity.Requester, folder *folder.Folder) ([]*dashboards.DashboardACLInfoDTO, error) {

View File

@ -437,6 +437,7 @@ func TestGetOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
}
hs.authInfoService = &authinfotest.FakeService{}
hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: userWithPermissions(1, tc.permissions)}
hs.accesscontrolService = &actest.FakeService{}
})
u := userWithPermissions(1, tc.permissions)
@ -482,6 +483,7 @@ func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
ExpectedUser: &user.User{},
ExpectedSignedInUser: userWithPermissions(1, tt.permissions),
}
hs.accesscontrolService = &actest.FakeService{}
})
u := userWithPermissions(1, tt.permissions)

View File

@ -7,7 +7,7 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/metrics"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/util"
@ -28,14 +28,14 @@ func (hs *HTTPServer) Search(c *contextmodel.ReqContext) response.Response {
page := c.QueryInt64("page")
dashboardType := c.Query("type")
sort := c.Query("sort")
permission := dashboards.PERMISSION_VIEW
permission := dashboardaccess.PERMISSION_VIEW
if limit > 5000 {
return response.Error(422, "Limit is above maximum allowed (5000), use page parameter to access hits beyond limit", nil)
}
if c.Query("permission") == "Edit" {
permission = dashboards.PERMISSION_EDIT
permission = dashboardaccess.PERMISSION_EDIT
}
dbIDs := make([]int64, 0)

View File

@ -4,7 +4,7 @@ import (
"bytes"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
@ -63,7 +63,7 @@ func (sb *SQLBuilder) AddParams(params ...any) {
sb.params = append(sb.params, params...)
}
func (sb *SQLBuilder) WriteDashboardPermissionFilter(user identity.Requester, permission dashboards.PermissionType, queryType string) {
func (sb *SQLBuilder) WriteDashboardPermissionFilter(user identity.Requester, permission dashboardaccess.PermissionType, queryType string) {
var (
sql string
params []any

View File

@ -104,26 +104,20 @@ type User struct {
// HasGlobalAccess checks user access with globally assigned permissions only
func HasGlobalAccess(ac AccessControl, service Service, c *contextmodel.ReqContext) func(evaluator Evaluator) bool {
return func(evaluator Evaluator) bool {
userCopy := *c.SignedInUser
userCopy.OrgID = GlobalOrgID
userCopy.OrgRole = ""
userCopy.OrgName = ""
if userCopy.Permissions[GlobalOrgID] == nil {
permissions, err := service.GetUserPermissions(c.Req.Context(), &userCopy, Options{})
var targetOrgID int64 = GlobalOrgID
tmpUser, err := makeTmpUser(c.Req.Context(), service, nil, nil, c.SignedInUser, targetOrgID)
if err != nil {
c.Logger.Error("Failed fetching permissions for user", "userID", userCopy.UserID, "error", err)
}
userCopy.Permissions[GlobalOrgID] = GroupScopesByAction(permissions)
deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err))
}
hasAccess, err := ac.Evaluate(c.Req.Context(), &userCopy, evaluator)
hasAccess, err := ac.Evaluate(c.Req.Context(), tmpUser, evaluator)
if err != nil {
c.Logger.Error("Error from access control system", "error", err)
return false
}
// set on user so we don't fetch global permissions every time this is called
c.SignedInUser.Permissions[GlobalOrgID] = userCopy.Permissions[GlobalOrgID]
c.SignedInUser.Permissions[tmpUser.GetOrgID()] = tmpUser.GetPermissions()
return hasAccess
}

View File

@ -22,7 +22,6 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/migrator"
"github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
@ -116,17 +115,10 @@ func (s *Service) getUserPermissions(ctx context.Context, user identity.Requeste
}
}
namespace, identifier := user.GetNamespacedID()
var userID int64
switch namespace {
case authn.NamespaceUser, authn.NamespaceServiceAccount:
var err error
userID, err = strconv.ParseInt(identifier, 10, 64)
userID, err := identity.UserIdentifier(user.GetNamespacedID())
if err != nil {
return nil, err
}
}
dbPermissions, err := s.store.GetUserPermissions(ctx, accesscontrol.GetUserPermissionsQuery{
OrgID: user.GetOrgID(),

View File

@ -13,6 +13,8 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/team/teamtest"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/usertest"
"github.com/grafana/grafana/pkg/setting"
@ -32,6 +34,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
acService accesscontrol.Service
userCache user.Service
ctxSignedInUser *user.SignedInUser
teamService team.Service
expectedStatus int
}{
{
@ -46,6 +49,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
acService: &actest.FakeService{
ExpectedPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
},
teamService: &teamtest.FakeService{},
expectedStatus: http.StatusOK,
},
{
@ -58,6 +62,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
acService: &actest.FakeService{},
userCache: &usertest.FakeUserService{},
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
teamService: &teamtest.FakeService{},
expectedStatus: http.StatusOK,
},
{
@ -69,6 +74,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
accessControl: ac,
userCache: &usertest.FakeUserService{},
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{}},
teamService: &teamtest.FakeService{},
acService: &actest.FakeService{},
expectedStatus: http.StatusForbidden,
},
@ -80,8 +86,11 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
accessControl: ac,
userCache: &usertest.FakeUserService{},
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{accesscontrol.GlobalOrgID: {"users:read": {"users:*"}}}},
acService: &actest.FakeService{},
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
teamService: &teamtest.FakeService{},
acService: &actest.FakeService{
ExpectedPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
},
expectedStatus: http.StatusOK,
},
{
@ -93,6 +102,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
accessControl: ac,
userCache: &usertest.FakeUserService{},
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
teamService: &teamtest.FakeService{},
acService: &actest.FakeService{},
expectedStatus: http.StatusForbidden,
},
@ -106,6 +116,20 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
userCache: &usertest.FakeUserService{ExpectedError: fmt.Errorf("user not found")},
acService: &actest.FakeService{},
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
teamService: &teamtest.FakeService{},
expectedStatus: http.StatusForbidden,
},
{
name: "should return 403 early when api key org ID doesn't match",
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) {
return 2, nil
},
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
accessControl: ac,
userCache: &usertest.FakeUserService{},
acService: &actest.FakeService{},
ctxSignedInUser: &user.SignedInUser{ApiKeyID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
teamService: &teamtest.FakeService{},
expectedStatus: http.StatusForbidden,
},
{
@ -118,6 +142,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
acService: &actest.FakeService{
ExpectedPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
},
teamService: &teamtest.FakeService{},
userCache: &usertest.FakeUserService{
GetSignedInUserFn: func(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) {
return &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: nil}, nil
@ -133,6 +158,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
},
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
accessControl: ac,
teamService: &teamtest.FakeService{},
acService: &actest.FakeService{
ExpectedPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
},
@ -151,6 +177,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
},
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
accessControl: ac,
teamService: &teamtest.FakeService{},
acService: &actest.FakeService{
ExpectedErr: fmt.Errorf("failed to get user permissions"),
},
@ -172,6 +199,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
acService: &actest.FakeService{},
userCache: &usertest.FakeUserService{},
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
teamService: &teamtest.FakeService{},
expectedStatus: http.StatusForbidden,
},
}
@ -189,6 +217,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
tc.accessControl,
service,
tc.userCache,
tc.teamService,
)(tc.orgIDGetter, tc.evaluator)
// Create test server

View File

@ -13,7 +13,7 @@ import (
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/services/accesscontrol"
rs "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
@ -248,7 +248,7 @@ func createUserAndTeam(t *testing.T, userSrv user.Service, teamSvc team.Service,
team, err := teamSvc.CreateTeam("team", "", orgID)
require.NoError(t, err)
err = teamSvc.AddTeamMember(user.ID, orgID, team.ID, false, dashboards.PERMISSION_VIEW)
err = teamSvc.AddTeamMember(user.ID, orgID, team.ID, false, dashboardaccess.PERMISSION_VIEW)
require.NoError(t, err)
return user, team
@ -296,7 +296,7 @@ func createUsersAndTeams(t *testing.T, svcs helperServices, orgID int64, users [
team, err := svcs.teamSvc.CreateTeam(fmt.Sprintf("team%v", i+1), "", orgID)
require.NoError(t, err)
err = svcs.teamSvc.AddTeamMember(user.ID, orgID, team.ID, false, dashboards.PERMISSION_VIEW)
err = svcs.teamSvc.AddTeamMember(user.ID, orgID, team.ID, false, dashboardaccess.PERMISSION_VIEW)
require.NoError(t, err)
err = svcs.orgSvc.UpdateOrgUser(context.Background(),

View File

@ -19,6 +19,7 @@ import (
"github.com/grafana/grafana/pkg/services/authn"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@ -179,60 +180,108 @@ type userCache interface {
GetSignedInUserWithCacheCtx(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error)
}
func AuthorizeInOrgMiddleware(ac AccessControl, service Service, cache userCache) func(OrgIDGetter, Evaluator) web.Handler {
type teamService interface {
GetTeamIDsByUser(ctx context.Context, query *team.GetTeamIDsByUserQuery) ([]int64, error)
}
func AuthorizeInOrgMiddleware(ac AccessControl, service Service, userService userCache, teamService teamService) func(OrgIDGetter, Evaluator) web.Handler {
return func(getTargetOrg OrgIDGetter, evaluator Evaluator) web.Handler {
return func(c *contextmodel.ReqContext) {
// We need to copy the user here because we're going to mutate it
userCopy := *(c.SignedInUser)
targetOrgID, err := getTargetOrg(c)
if err != nil {
deny(c, nil, fmt.Errorf("failed to get target org: %w", err))
return
}
if userCopy.OrgID != targetOrgID {
switch targetOrgID {
case GlobalOrgID:
userCopy.OrgID = GlobalOrgID
userCopy.OrgRole = org.RoleNone
userCopy.OrgName = ""
default:
query := user.GetSignedInUserQuery{UserID: c.UserID, OrgID: targetOrgID}
queryResult, err := cache.GetSignedInUserWithCacheCtx(c.Req.Context(), &query)
tmpUser, err := makeTmpUser(c.Req.Context(), service, userService, teamService, c.SignedInUser, targetOrgID)
if err != nil {
deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err))
return
}
userCopy.OrgID = queryResult.OrgID
userCopy.OrgName = queryResult.OrgName
userCopy.OrgRole = queryResult.OrgRole
}
}
if userCopy.Permissions[targetOrgID] == nil {
permissions, err := service.GetUserPermissions(c.Req.Context(), &userCopy, Options{})
if err != nil {
deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err))
}
// guard against nil map
if userCopy.Permissions == nil {
userCopy.Permissions = make(map[int64]map[string][]string)
}
userCopy.Permissions[targetOrgID] = GroupScopesByAction(permissions)
}
authorize(c, ac, &userCopy, evaluator)
authorize(c, ac, tmpUser, evaluator)
// guard against nil map
if c.SignedInUser.Permissions == nil {
c.SignedInUser.Permissions = make(map[int64]map[string][]string)
}
c.SignedInUser.Permissions[targetOrgID] = userCopy.Permissions[targetOrgID]
c.SignedInUser.Permissions[tmpUser.GetOrgID()] = tmpUser.GetPermissions()
}
}
}
// makeTmpUser creates a temporary user that can be used to evaluate access across orgs.
func makeTmpUser(ctx context.Context, service Service, cache userCache,
teamService teamService, reqUser identity.Requester, targetOrgID int64) (identity.Requester, error) {
tmpUser := &user.SignedInUser{
OrgID: reqUser.GetOrgID(),
OrgName: reqUser.GetOrgName(),
OrgRole: reqUser.GetOrgRole(),
IsGrafanaAdmin: reqUser.GetIsGrafanaAdmin(),
Login: reqUser.GetLogin(),
Teams: reqUser.GetTeams(),
Permissions: map[int64]map[string][]string{
reqUser.GetOrgID(): reqUser.GetPermissions(),
},
}
namespace, identifier := reqUser.GetNamespacedID()
id, _ := identity.IntIdentifier(namespace, identifier)
switch namespace {
case identity.NamespaceUser:
tmpUser.UserID = id
case identity.NamespaceAPIKey:
tmpUser.ApiKeyID = id
if tmpUser.OrgID != targetOrgID {
return nil, errors.New("API key does not belong to target org")
}
case identity.NamespaceServiceAccount:
tmpUser.UserID = id
tmpUser.IsServiceAccount = true
}
if tmpUser.OrgID != targetOrgID {
switch targetOrgID {
case GlobalOrgID:
tmpUser.OrgID = GlobalOrgID
tmpUser.OrgRole = org.RoleNone
tmpUser.OrgName = ""
tmpUser.Teams = []int64{}
default:
if cache == nil {
return nil, errors.New("user cache is nil")
}
query := user.GetSignedInUserQuery{UserID: tmpUser.UserID, OrgID: targetOrgID}
queryResult, err := cache.GetSignedInUserWithCacheCtx(ctx, &query)
if err != nil {
return nil, err
}
tmpUser.OrgID = queryResult.OrgID
tmpUser.OrgName = queryResult.OrgName
tmpUser.OrgRole = queryResult.OrgRole
if teamService != nil {
teamIDs, err := teamService.GetTeamIDsByUser(ctx, &team.GetTeamIDsByUserQuery{OrgID: targetOrgID, UserID: tmpUser.UserID})
if err != nil {
return nil, err
}
tmpUser.Teams = teamIDs
}
}
}
if tmpUser.Permissions[targetOrgID] == nil || len(tmpUser.Permissions[targetOrgID]) == 0 {
permissions, err := service.GetUserPermissions(ctx, tmpUser, Options{})
if err != nil {
return nil, err
}
tmpUser.Permissions[targetOrgID] = GroupScopesByAction(permissions)
}
return tmpUser, nil
}
func UseOrgFromContextParams(c *contextmodel.ReqContext) (int64, error) {
orgID, err := strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64)

View File

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/libraryelements"
@ -87,7 +88,7 @@ func ProvideTeamPermissions(
case "Member":
return teamimpl.AddOrUpdateTeamMemberHook(session, user.ID, orgID, teamId, user.IsExternal, 0)
case "Admin":
return teamimpl.AddOrUpdateTeamMemberHook(session, user.ID, orgID, teamId, user.IsExternal, dashboards.PERMISSION_ADMIN)
return teamimpl.AddOrUpdateTeamMemberHook(session, user.ID, orgID, teamId, user.IsExternal, dashboardaccess.PERMISSION_ADMIN)
case "":
return teamimpl.RemoveTeamMemberHook(session, &team.RemoveTeamMemberCommand{
OrgID: orgID,

View File

@ -10,7 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
alertmodels "github.com/grafana/grafana/pkg/services/alerting/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/tag"
"github.com/grafana/grafana/pkg/setting"
@ -167,7 +167,7 @@ func (ss *sqlStore) HandleAlertsQuery(ctx context.Context, query *alertmodels.Ge
builder.Write(")")
}
builder.WriteDashboardPermissionFilter(query.User, dashboards.PERMISSION_VIEW, "")
builder.WriteDashboardPermissionFilter(query.User, dashboardaccess.PERMISSION_VIEW, "")
builder.Write(" ORDER BY name ASC")

View File

@ -7,7 +7,7 @@ import (
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
@ -74,7 +74,7 @@ func (authz *AuthService) userVisibleDashboards(ctx context.Context, user identi
}
filters := []any{
permissions.NewAccessControlDashboardPermissionFilter(user, dashboards.PERMISSION_VIEW, searchstore.TypeDashboard, authz.features, recursiveQueriesSupported),
permissions.NewAccessControlDashboardPermissionFilter(user, dashboardaccess.PERMISSION_VIEW, searchstore.TypeDashboard, authz.features, recursiveQueriesSupported),
searchstore.OrgFilter{OrgId: orgID},
}

View File

@ -1,4 +1,4 @@
package dashboards
package dashboardaccess
import (
"errors"

View File

@ -1,4 +1,4 @@
package dashboards
package dashboardaccess
import (
"fmt"

View File

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/kinds/dashboard"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/quota"
@ -419,7 +420,7 @@ type DashboardACL struct {
UserID int64 `xorm:"user_id"`
TeamID int64 `xorm:"team_id"`
Role *org.RoleType // pointer to be nullable
Permission PermissionType
Permission dashboardaccess.PermissionType
Created time.Time
Updated time.Time
@ -446,7 +447,7 @@ type DashboardACLInfoDTO struct {
TeamAvatarURL string `json:"teamAvatarUrl" xorm:"team_avatar_url"`
Team string `json:"team"`
Role *org.RoleType `json:"role,omitempty"`
Permission PermissionType `json:"permission"`
Permission dashboardaccess.PermissionType `json:"permission"`
PermissionName string `json:"permissionName"`
UID string `json:"uid" xorm:"uid"`
Title string `json:"title"`
@ -490,7 +491,7 @@ type FindPersistedDashboardsQuery struct {
Tags []string
Limit int64
Page int64
Permission PermissionType
Permission dashboardaccess.PermissionType
Sort model.SortOption
Filters []any

View File

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
@ -502,15 +503,15 @@ func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto *
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "namespaceID", namespaceID, "userID", userID, "error", err)
} else if namespaceID == identity.NamespaceUser && userID > 0 {
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
UserID: userID, Permission: dashboards.PERMISSION_ADMIN.String(),
UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(),
})
}
}
if !inFolder {
permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
}...)
}

View File

@ -14,6 +14,7 @@ import (
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/org"
@ -288,7 +289,7 @@ func (l *LibraryElementService) getLibraryElements(c context.Context, store db.D
builder.Write(getFromLibraryElementDTOWithMeta(store.GetDialect()))
builder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id AND le.folder_id <> 0")
writeParamSelectorSQL(&builder, params...)
builder.WriteDashboardPermissionFilter(signedInUser, dashboards.PERMISSION_VIEW, "")
builder.WriteDashboardPermissionFilter(signedInUser, dashboardaccess.PERMISSION_VIEW, "")
builder.Write(` OR dashboard.id=0`)
if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&libraryElements); err != nil {
return err
@ -421,7 +422,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI
return err
}
if !signedInUser.HasRole(org.RoleAdmin) {
builder.WriteDashboardPermissionFilter(signedInUser, dashboards.PERMISSION_VIEW, "")
builder.WriteDashboardPermissionFilter(signedInUser, dashboardaccess.PERMISSION_VIEW, "")
}
if query.SortDirection == search.SortAlphaDesc.Name {
builder.Write(" ORDER BY 1 DESC")
@ -665,7 +666,7 @@ func (l *LibraryElementService) getConnections(c context.Context, signedInUser i
builder.Write(" INNER JOIN dashboard AS dashboard on lec.connection_id = dashboard.id")
builder.Write(` WHERE lec.element_id=?`, element.ID)
if signedInUser.GetOrgRole() != org.RoleAdmin {
builder.WriteDashboardPermissionFilter(signedInUser, dashboards.PERMISSION_VIEW, "")
builder.WriteDashboardPermissionFilter(signedInUser, dashboardaccess.PERMISSION_VIEW, "")
}
if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&libraryElementConnections); err != nil {
return err

View File

@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/folder"
migmodels "github.com/grafana/grafana/pkg/services/ngalert/migration/models"
@ -42,12 +43,12 @@ var (
// generalAlertingFolderTitle is the title of the general alerting folder. This is used for dashboard alerts in the general folder.
generalAlertingFolderTitle = "General Alerting"
// permissionMap maps the "friendly" permission name for a ResourcePermissions actions to the dashboards.PermissionType.
// permissionMap maps the "friendly" permission name for a ResourcePermissions actions to the dashboardaccess.PermissionType.
// A sort of reverse accesscontrol Service.MapActions similar to api.dashboardPermissionMap.
permissionMap = map[string]dashboards.PermissionType{
"View": dashboards.PERMISSION_VIEW,
"Edit": dashboards.PERMISSION_EDIT,
"Admin": dashboards.PERMISSION_ADMIN,
permissionMap = map[string]dashboardaccess.PermissionType{
"View": dashboardaccess.PERMISSION_VIEW,
"Edit": dashboardaccess.PERMISSION_EDIT,
"Admin": dashboardaccess.PERMISSION_ADMIN,
}
)
@ -186,11 +187,11 @@ func isBasic(roleName string) bool {
// There are two role types that we consider:
// - managed (ex. managed:users:1:permissions, managed:builtins:editor:permissions, managed:teams:1:permissions):
// These are the only roles that exist in OSS. For each of these roles, we add the actions of the highest
// dashboards.PermissionType between the folder and the dashboard. Permissions from the folder are inherited.
// dashboardaccess.PermissionType between the folder and the dashboard. Permissions from the folder are inherited.
// The added actions should have scope=folder:uid:xxxxxx, where xxxxxx is the new folder uid.
// - basic (ex. basic:admin, basic:editor):
// These are roles used in enterprise. Every user should have one of these roles. They should be considered
// equivalent to managed:builtins. The highest dashboards.PermissionType between the two should be used.
// equivalent to managed:builtins. The highest dashboardaccess.PermissionType between the two should be used.
//
// There are two role types that we do not consider:
// - fixed: (ex. fixed:dashboards:reader, fixed:dashboards:writer):
@ -205,7 +206,7 @@ func isBasic(roleName string) bool {
// For now, we choose the simpler approach of handling managed and basic roles. Fixed and custom roles will not
// be taken into account, but we will log a warning if they had the potential to override the folder permissions.
func (om *OrgMigration) convertResourcePerms(rperms []accesscontrol.ResourcePermission) ([]accesscontrol.SetResourcePermissionCommand, []accesscontrol.ResourcePermission) {
keep := make(map[accesscontrol.SetResourcePermissionCommand]dashboards.PermissionType)
keep := make(map[accesscontrol.SetResourcePermissionCommand]dashboardaccess.PermissionType)
unusedPerms := make([]accesscontrol.ResourcePermission, 0)
for _, p := range rperms {
if p.IsManaged || p.IsInherited || isBasic(p.RoleName) {
@ -254,8 +255,8 @@ func (om *OrgMigration) convertResourcePerms(rperms []accesscontrol.ResourcePerm
// potentialOverrides returns a map of roles from unusedOldPerms that have dashboard permissions that could potentially
// override the given folder permissions in newPerms. These overrides are always to increase permissions not decrease them.
func potentialOverrides(unusedOldPerms []accesscontrol.ResourcePermission, newPerms []accesscontrol.SetResourcePermissionCommand) map[string]dashboards.PermissionType {
var lowestPermission dashboards.PermissionType
func potentialOverrides(unusedOldPerms []accesscontrol.ResourcePermission, newPerms []accesscontrol.SetResourcePermissionCommand) map[string]dashboardaccess.PermissionType {
var lowestPermission dashboardaccess.PermissionType
for _, p := range newPerms {
if p.BuiltinRole == string(org.RoleEditor) || p.BuiltinRole == string(org.RoleViewer) {
pType := permissionMap[p.Permission]
@ -265,18 +266,18 @@ func potentialOverrides(unusedOldPerms []accesscontrol.ResourcePermission, newPe
}
}
nonManagedPermissionTypes := make(map[string]dashboards.PermissionType)
nonManagedPermissionTypes := make(map[string]dashboardaccess.PermissionType)
for _, p := range unusedOldPerms {
existing, ok := nonManagedPermissionTypes[p.RoleName]
if ok && existing == dashboards.PERMISSION_EDIT {
if ok && existing == dashboardaccess.PERMISSION_EDIT {
// We've already handled the highest permission we care about, no need to check this role anymore.
continue
}
if p.Contains([]string{dashboards.ActionDashboardsWrite}) {
existing = dashboards.PERMISSION_EDIT
existing = dashboardaccess.PERMISSION_EDIT
} else if p.Contains([]string{dashboards.ActionDashboardsRead}) {
existing = dashboards.PERMISSION_VIEW
existing = dashboardaccess.PERMISSION_VIEW
}
if existing > lowestPermission && existing > nonManagedPermissionTypes[p.RoleName] {

View File

@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
"github.com/grafana/grafana/pkg/services/alerting/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
ngModels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/team"
@ -245,8 +246,8 @@ func TestDashAlertPermissionMigration(t *testing.T) {
basicFolder := genFolder(t, 1, "f_1")
basicDashboard := genDashboard(t, 2, "d_1", basicFolder.ID)
defaultPerms := genPerms(
accesscontrol.SetResourcePermissionCommand{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
accesscontrol.SetResourcePermissionCommand{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()},
accesscontrol.SetResourcePermissionCommand{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
accesscontrol.SetResourcePermissionCommand{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
)
basicAlert1 := genLegacyAlert("alert1", basicDashboard.ID, func(a *models.Alert) { a.PanelID = 1 })
@ -304,8 +305,8 @@ func TestDashAlertPermissionMigration(t *testing.T) {
dashboards: []*dashboards.Dashboard{basicDashboard},
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
basicDashboard.UID: {
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_VIEW.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_VIEW.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
},
},
alerts: []*models.Alert{basicAlert1},
@ -314,8 +315,8 @@ func TestDashAlertPermissionMigration(t *testing.T) {
Alert: genAlert(basicAlert1.Name, basicFolder.UID, basicDashboard.UID),
Folder: basicFolder,
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()}, // Inherits from Folder.
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Inherits from Folder.
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
},
},
},
@ -333,24 +334,24 @@ func TestDashAlertPermissionMigration(t *testing.T) {
},
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
"d_1": {
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_EDIT.String()}, // Change.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Change.
},
"d_2": {
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
},
"d_3": {
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_EDIT.String()}, // Change.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Change.
},
"d_4": {
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
},
"d_5": {
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
},
},
alerts: []*models.Alert{genLegacyAlert("alert1", 2), genLegacyAlert("alert2", 3), genLegacyAlert("alert3", 4), genLegacyAlert("alert4", 5), genLegacyAlert("alert5", 6)},
@ -359,40 +360,40 @@ func TestDashAlertPermissionMigration(t *testing.T) {
Alert: genAlert("alert1", "", "d_1"),
Folder: genCreatedFolder(t, "Original Folder f_1 Alerts - %s"),
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_EDIT.String()}, // Change.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Change.
},
},
{
Alert: genAlert("alert2", "", "d_2"),
Folder: genCreatedFolder(t, "Original Folder f_1 Alerts - %s"),
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
},
},
{
Alert: genAlert("alert3", "", "d_3"),
Folder: genCreatedFolder(t, "Original Folder f_1 Alerts - %s"),
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_EDIT.String()}, // Change.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Change.
},
},
{
Alert: genAlert("alert4", "", "d_4"),
Folder: genCreatedFolder(t, "Original Folder f_1 Alerts - %s"),
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
},
},
{
Alert: genAlert("alert5", "", "d_5"),
Folder: genCreatedFolder(t, "Original Folder f_1 Alerts - %s"),
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
},
},
},
@ -402,12 +403,12 @@ func TestDashAlertPermissionMigration(t *testing.T) {
folders: []*dashboards.Dashboard{genFolder(t, 1, "f_1"), genFolder(t, 2, "f_2")},
folderPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
"f_1": {
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_ADMIN.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_ADMIN.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()},
},
"f_2": {
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_VIEW.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_VIEW.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
},
},
dashboards: []*dashboards.Dashboard{
@ -418,16 +419,16 @@ func TestDashAlertPermissionMigration(t *testing.T) {
},
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
"d_1": {
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
},
"d_2": {
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
},
"d_3": {
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
},
"d_4": {
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
},
},
alerts: []*models.Alert{genLegacyAlert("alert1", 3), genLegacyAlert("alert2", 4), genLegacyAlert("alert3", 5), genLegacyAlert("alert4", 6)},
@ -436,32 +437,32 @@ func TestDashAlertPermissionMigration(t *testing.T) {
Alert: genAlert("alert1", "f_1", "d_1"),
Folder: genFolder(t, 1, "f_1"), // Original folder since the perms didn't change.
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_ADMIN.String()}, // Inherits from Folder.
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_ADMIN.String()}, // Overrides from Folder.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Inherits from Folder.
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Overrides from Folder.
},
},
{
Alert: genAlert("alert2", "f_1", "d_2"),
Folder: genFolder(t, 1, "f_1"), // Original folder since the perms didn't change.
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_ADMIN.String()}, // Overrides from Folder.
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_ADMIN.String()}, // Inherits from Folder.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Overrides from Folder.
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Inherits from Folder.
},
},
{
Alert: genAlert("alert3", "f_2", "d_3"),
Folder: genFolder(t, 2, "f_2"), // Original folder since the perms didn't change.
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_VIEW.String()}, // Inherits from Folder.
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_VIEW.String()}, // Inherits from Folder.
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
},
},
{
Alert: genAlert("alert4", "", "d_4"),
Folder: genCreatedFolder(t, "Original Folder f_2 Alerts - %s"),
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()}, // Inherits from Folder.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, // Inherits from Folder.
},
},
},
@ -471,13 +472,13 @@ func TestDashAlertPermissionMigration(t *testing.T) {
folders: []*dashboards.Dashboard{basicFolder},
folderPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
basicFolder.UID: {
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
},
},
dashboards: []*dashboards.Dashboard{basicDashboard},
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
basicDashboard.UID: {
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_VIEW.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_VIEW.String()},
},
},
alerts: []*models.Alert{basicAlert1},
@ -486,7 +487,7 @@ func TestDashAlertPermissionMigration(t *testing.T) {
Alert: genAlert(basicAlert1.Name, basicFolder.UID, basicDashboard.UID),
Folder: basicFolder,
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
},
},
},
@ -505,8 +506,8 @@ func TestDashAlertPermissionMigration(t *testing.T) {
Alert: genAlert("alert1", "f_1", "d_1"),
Folder: genCreatedFolder(t, "General Alerting Alerts - %s"),
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()}, // From Dashboard.
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()}, // From Dashboard.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // From Dashboard.
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, // From Dashboard.
},
},
},
@ -516,7 +517,7 @@ func TestDashAlertPermissionMigration(t *testing.T) {
dashboards: []*dashboards.Dashboard{genDashboard(t, 1, "d_1", 0)}, // Dashboard in general folder.
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
"d_1": { // Missing viewer.
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
},
},
alerts: []*models.Alert{genLegacyAlert("alert1", 1)},
@ -525,7 +526,7 @@ func TestDashAlertPermissionMigration(t *testing.T) {
Alert: genAlert("alert1", "f_1", "d_1"),
Folder: genCreatedFolder(t, "General Alerting Alerts - %s"),
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()}, // From Dashboard.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // From Dashboard.
},
},
},
@ -555,8 +556,8 @@ func TestDashAlertPermissionMigration(t *testing.T) {
},
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
"d_1": {
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_EDIT.String()}, // Change.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Change.
},
},
alerts: []*models.Alert{genLegacyAlert("alert1", 2)},
@ -565,9 +566,9 @@ func TestDashAlertPermissionMigration(t *testing.T) {
Alert: genAlert("alert1", "", "d_1"),
Folder: genCreatedFolder(t, "Original Folder f_1 Alerts - %s"),
Perms: []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleAdmin), Permission: dashboards.PERMISSION_ADMIN.String()}, // From basic:admin.
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_EDIT.String()}, // Change.
{BuiltinRole: string(org.RoleAdmin), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // From basic:admin.
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Change.
},
},
},
@ -699,7 +700,7 @@ func TestDashAlertPermissionMigration(t *testing.T) {
expected.Alert.NamespaceUID = ""
}
keep := make(map[accesscontrol.SetResourcePermissionCommand]dashboards.PermissionType)
keep := make(map[accesscontrol.SetResourcePermissionCommand]dashboardaccess.PermissionType)
for _, p := range rperms {
if permission := service.migrationStore.MapActions(p); permission != "" {
sp := accesscontrol.SetResourcePermissionCommand{

View File

@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/folder"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/search/model"
@ -433,7 +434,7 @@ func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, use
SignedInUser: user,
Type: searchstore.TypeAlertFolder,
Limit: -1,
Permission: dashboards.PERMISSION_VIEW,
Permission: dashboardaccess.PERMISSION_VIEW,
Sort: model.SortOption{},
Filters: []any{
searchstore.FolderWithAlertsFilter{},

View File

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/publicdashboards"
@ -59,7 +60,7 @@ func (d *PublicDashboardStoreImpl) FindAllWithPagination(ctx context.Context, qu
pubdashBuilder.Write(" JOIN dashboard ON dashboard.uid = dashboard_public.dashboard_uid AND dashboard.org_id = dashboard_public.org_id")
pubdashBuilder.Write(` WHERE dashboard_public.org_id = ?`, query.OrgID)
if query.User.OrgRole != org.RoleAdmin {
pubdashBuilder.WriteDashboardPermissionFilter(query.User, dashboards.PERMISSION_VIEW, searchstore.TypeDashboard)
pubdashBuilder.WriteDashboardPermissionFilter(query.User, dashboardaccess.PERMISSION_VIEW, searchstore.TypeDashboard)
}
pubdashBuilder.Write(" ORDER BY dashboard.title")
pubdashBuilder.Write(d.sqlStore.GetDialect().LimitOffset(int64(query.Limit), int64(query.Offset)))
@ -70,7 +71,7 @@ func (d *PublicDashboardStoreImpl) FindAllWithPagination(ctx context.Context, qu
counterBuilder.Write(" JOIN dashboard ON dashboard.uid = dashboard_public.dashboard_uid AND dashboard.org_id = dashboard_public.org_id")
counterBuilder.Write(` WHERE dashboard_public.org_id = ?`, query.OrgID)
if query.User.OrgRole != org.RoleAdmin {
counterBuilder.WriteDashboardPermissionFilter(query.User, dashboards.PERMISSION_VIEW, searchstore.TypeDashboard)
counterBuilder.WriteDashboardPermissionFilter(query.User, dashboardaccess.PERMISSION_VIEW, searchstore.TypeDashboard)
}
err = d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {

View File

@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/star"
"github.com/grafana/grafana/pkg/services/user"
@ -40,7 +41,7 @@ type Query struct {
// Deprecated: use FolderUID instead
FolderIds []int64
FolderUIDs []string
Permission dashboards.PermissionType
Permission dashboardaccess.PermissionType
Sort string
}

View File

@ -10,19 +10,20 @@ import (
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)
var dashboardPermissionTranslation = map[dashboards.PermissionType][]string{
dashboards.PERMISSION_VIEW: {
var dashboardPermissionTranslation = map[dashboardaccess.PermissionType][]string{
dashboardaccess.PERMISSION_VIEW: {
dashboards.ActionDashboardsRead,
},
dashboards.PERMISSION_EDIT: {
dashboardaccess.PERMISSION_EDIT: {
dashboards.ActionDashboardsRead,
dashboards.ActionDashboardsWrite,
dashboards.ActionDashboardsDelete,
},
dashboards.PERMISSION_ADMIN: {
dashboardaccess.PERMISSION_ADMIN: {
dashboards.ActionDashboardsRead,
dashboards.ActionDashboardsWrite,
dashboards.ActionDashboardsCreate,
@ -32,17 +33,17 @@ var dashboardPermissionTranslation = map[dashboards.PermissionType][]string{
},
}
var folderPermissionTranslation = map[dashboards.PermissionType][]string{
dashboards.PERMISSION_VIEW: append(dashboardPermissionTranslation[dashboards.PERMISSION_VIEW], []string{
var folderPermissionTranslation = map[dashboardaccess.PermissionType][]string{
dashboardaccess.PERMISSION_VIEW: append(dashboardPermissionTranslation[dashboardaccess.PERMISSION_VIEW], []string{
dashboards.ActionFoldersRead,
}...),
dashboards.PERMISSION_EDIT: append(dashboardPermissionTranslation[dashboards.PERMISSION_EDIT], []string{
dashboardaccess.PERMISSION_EDIT: append(dashboardPermissionTranslation[dashboardaccess.PERMISSION_EDIT], []string{
dashboards.ActionDashboardsCreate,
dashboards.ActionFoldersRead,
dashboards.ActionFoldersWrite,
dashboards.ActionFoldersDelete,
}...),
dashboards.PERMISSION_ADMIN: append(dashboardPermissionTranslation[dashboards.PERMISSION_ADMIN], []string{
dashboardaccess.PERMISSION_ADMIN: append(dashboardPermissionTranslation[dashboardaccess.PERMISSION_ADMIN], []string{
dashboards.ActionFoldersRead,
dashboards.ActionFoldersWrite,
dashboards.ActionFoldersDelete,
@ -111,11 +112,11 @@ func (m dashboardPermissionsMigrator) migratePermissions(dashes []dashboard, acl
if (d.IsFolder || d.FolderID == 0) && len(acls) == 0 && !d.HasAcl {
permissionMap[d.OrgID]["managed:builtins:editor:permissions"] = append(
permissionMap[d.OrgID]["managed:builtins:editor:permissions"],
m.mapPermission(d.ID, dashboards.PERMISSION_EDIT, d.IsFolder)...,
m.mapPermission(d.ID, dashboardaccess.PERMISSION_EDIT, d.IsFolder)...,
)
permissionMap[d.OrgID]["managed:builtins:viewer:permissions"] = append(
permissionMap[d.OrgID]["managed:builtins:viewer:permissions"],
m.mapPermission(d.ID, dashboards.PERMISSION_VIEW, d.IsFolder)...,
m.mapPermission(d.ID, dashboardaccess.PERMISSION_VIEW, d.IsFolder)...,
)
} else {
for _, a := range deduplicateAcl(acls) {
@ -192,7 +193,7 @@ func (m dashboardPermissionsMigrator) setPermissions(allRoles []*ac.Role, permis
return nil
}
func (m dashboardPermissionsMigrator) mapPermission(id int64, p dashboards.PermissionType, isFolder bool) []*ac.Permission {
func (m dashboardPermissionsMigrator) mapPermission(id int64, p dashboardaccess.PermissionType, isFolder bool) []*ac.Permission {
if isFolder {
actions := folderPermissionTranslation[p]
scope := dashboards.ScopeFoldersProvider.GetResourceScope(strconv.FormatInt(id, 10))
@ -661,15 +662,15 @@ func (m *managedFolderLibraryPanelActionsMigrator) Exec(sess *xorm.Session, mg *
}
func hasFolderAdmin(permissions []ac.Permission) bool {
return hasActions(folderPermissionTranslation[dashboards.PERMISSION_ADMIN], permissions)
return hasActions(folderPermissionTranslation[dashboardaccess.PERMISSION_ADMIN], permissions)
}
func hasFolderEdit(permissions []ac.Permission) bool {
return hasActions(folderPermissionTranslation[dashboards.PERMISSION_EDIT], permissions)
return hasActions(folderPermissionTranslation[dashboardaccess.PERMISSION_EDIT], permissions)
}
func hasFolderView(permissions []ac.Permission) bool {
return hasActions(folderPermissionTranslation[dashboards.PERMISSION_VIEW], permissions)
return hasActions(folderPermissionTranslation[dashboardaccess.PERMISSION_VIEW], permissions)
}
func hasActions(actions []string, permissions []ac.Permission) bool {

View File

@ -8,7 +8,7 @@ import (
"xorm.io/xorm"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/team"
@ -64,12 +64,12 @@ func (p *teamPermissionMigrator) setRolePermissions(roleID int64, permissions []
}
// mapPermissionToRBAC translates the legacy membership (Member or Admin) into RBAC permissions
func (p *teamPermissionMigrator) mapPermissionToRBAC(permission dashboards.PermissionType, teamID int64) []accesscontrol.Permission {
func (p *teamPermissionMigrator) mapPermissionToRBAC(permission dashboardaccess.PermissionType, teamID int64) []accesscontrol.Permission {
teamIDScope := accesscontrol.Scope("teams", "id", strconv.FormatInt(teamID, 10))
switch permission {
case 0:
return []accesscontrol.Permission{{Action: "teams:read", Scope: teamIDScope}}
case dashboards.PERMISSION_ADMIN:
case dashboardaccess.PERMISSION_ADMIN:
return []accesscontrol.Permission{
{Action: "teams:delete", Scope: teamIDScope},
{Action: "teams:read", Scope: teamIDScope},
@ -210,7 +210,7 @@ func (p *teamPermissionMigrator) generateAssociatedPermissions(teamMemberships [
// Downgrade team permissions if needed:
// only admins or editors (when editorsCanAdmin option is enabled)
// can access team administration endpoints
if m.Permission == dashboards.PERMISSION_ADMIN {
if m.Permission == dashboardaccess.PERMISSION_ADMIN {
if userRolesByOrg[m.OrgID][m.UserID] == string(org.RoleViewer) || (userRolesByOrg[m.OrgID][m.UserID] == string(org.RoleEditor) && !p.editorsCanAdmin) {
m.Permission = 0

View File

@ -13,7 +13,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
@ -358,7 +358,7 @@ func setupTeams(t *testing.T, x *xorm.Engine) {
TeamID: 1,
UserID: 2,
External: false,
Permission: dashboards.PERMISSION_ADMIN,
Permission: dashboardaccess.PERMISSION_ADMIN,
Created: now,
Updated: now,
},
@ -368,7 +368,7 @@ func setupTeams(t *testing.T, x *xorm.Engine) {
TeamID: 1,
UserID: 3,
External: false,
Permission: dashboards.PERMISSION_ADMIN,
Permission: dashboardaccess.PERMISSION_ADMIN,
Created: now,
Updated: now,
},
@ -378,7 +378,7 @@ func setupTeams(t *testing.T, x *xorm.Engine) {
TeamID: 1,
UserID: 4,
External: false,
Permission: dashboards.PERMISSION_ADMIN,
Permission: dashboardaccess.PERMISSION_ADMIN,
Created: now,
Updated: now,
},

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/login"
@ -44,11 +45,11 @@ type PermissionsFilter interface {
nestedFoldersSelectors(permSelector string, permSelectorArgs []any, leftTableCol string, rightTableCol string, orgID int64) (string, []any)
}
// NewAccessControlDashboardPermissionFilter creates a new AccessControlDashboardPermissionFilter that is configured with specific actions calculated based on the dashboards.PermissionType and query type
// NewAccessControlDashboardPermissionFilter creates a new AccessControlDashboardPermissionFilter that is configured with specific actions calculated based on the dashboardaccess.PermissionType and query type
// The filter is configured to use the new permissions filter (without subqueries) if the feature flag is enabled
// The filter is configured to use the old permissions filter (with subqueries) if the feature flag is disabled
func NewAccessControlDashboardPermissionFilter(user identity.Requester, permissionLevel dashboards.PermissionType, queryType string, features featuremgmt.FeatureToggles, recursiveQueriesAreSupported bool) PermissionsFilter {
needEdit := permissionLevel > dashboards.PERMISSION_VIEW
func NewAccessControlDashboardPermissionFilter(user identity.Requester, permissionLevel dashboardaccess.PermissionType, queryType string, features featuremgmt.FeatureToggles, recursiveQueriesAreSupported bool) PermissionsFilter {
needEdit := permissionLevel > dashboardaccess.PERMISSION_VIEW
var folderActions []string
var dashboardActions []string

View File

@ -17,6 +17,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
@ -40,7 +41,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
type testCase struct {
desc string
queryType string
permission dashboards.PermissionType
permission dashboardaccess.PermissionType
permissions []accesscontrol.Permission
expectedResult int
}
@ -48,7 +49,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
tests := []testCase{
{
desc: "Should be able to view all dashboards with wildcard scope",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeDashboardsAll},
},
@ -56,7 +57,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
},
{
desc: "Should be able to view all dashboards with folder wildcard scope",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
},
@ -64,7 +65,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
},
{
desc: "Should be able to view dashboards under the root with folders:uid:general scope",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
},
@ -72,7 +73,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
},
{
desc: "Should not be able to view editable dashboards under the root with folders:uid:general scope if missing write action",
permission: dashboards.PERMISSION_EDIT,
permission: dashboardaccess.PERMISSION_EDIT,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
},
@ -80,7 +81,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
},
{
desc: "Should be able to view editable dashboards under the root with folders:uid:general scope if has write action",
permission: dashboards.PERMISSION_EDIT,
permission: dashboardaccess.PERMISSION_EDIT,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
{Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
@ -89,7 +90,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
},
{
desc: "Should be able to view a subset of dashboards with dashboard scopes",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:110"},
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:40"},
@ -102,7 +103,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
},
{
desc: "Should be able to view a subset of dashboards with dashboard action and folder scope",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:8"},
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:10"},
@ -111,7 +112,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
},
{
desc: "Should be able to view all folders with folder wildcard",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:*"},
},
@ -119,7 +120,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
},
{
desc: "Should be able to view a subset folders",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:3"},
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:6"},
@ -129,7 +130,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
},
{
desc: "Should return folders and dashboard with 'edit' permission",
permission: dashboards.PERMISSION_EDIT,
permission: dashboardaccess.PERMISSION_EDIT,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:3"},
{Action: dashboards.ActionDashboardsCreate, Scope: "folders:uid:3"},
@ -140,7 +141,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
},
{
desc: "Should return the dashboards that the User has dashboards:write permission on in case of 'edit' permission",
permission: dashboards.PERMISSION_EDIT,
permission: dashboardaccess.PERMISSION_EDIT,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:3"},
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:31"},
@ -152,7 +153,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
},
{
desc: "Should return the folders that the User has dashboards:create permission on in case of 'edit' permission",
permission: dashboards.PERMISSION_EDIT,
permission: dashboardaccess.PERMISSION_EDIT,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:3"},
{Action: dashboards.ActionDashboardsCreate, Scope: "folders:uid:3"},
@ -164,7 +165,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
},
{
desc: "Should return folders that users can read alerts from",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
queryType: searchstore.TypeAlertFolder,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:3"},
@ -176,7 +177,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
},
{
desc: "Should return folders that users can read alerts when user has read wildcard",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
queryType: searchstore.TypeAlertFolder,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "*"},
@ -232,7 +233,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
type testCase struct {
desc string
queryType string
permission dashboards.PermissionType
permission dashboardaccess.PermissionType
signedInUserPermissions []accesscontrol.Permission
expectedResult int
}
@ -240,7 +241,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
tests := []testCase{
{
desc: "Should be able to view all dashboards with wildcard scope",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeDashboardsAll},
},
@ -248,7 +249,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
},
{
desc: "Should be able to view all dashboards with folder wildcard scope",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
},
@ -256,13 +257,13 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
},
{
desc: "Should not be able to view any dashboards or folders without any permissions",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{},
expectedResult: 0,
},
{
desc: "Should be able to view a subset of dashboards with dashboard scopes",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:110"},
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:40"},
@ -275,7 +276,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
},
{
desc: "Should be able to view a subset of dashboards with dashboard action and folder scope",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:8"},
@ -285,7 +286,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
},
{
desc: "Should be able to view dashboards under the root with folders:uid:general scope",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
},
@ -293,7 +294,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
},
{
desc: "Should not be able to view editable dashboards under the root with folders:uid:general scope if missing write action",
permission: dashboards.PERMISSION_EDIT,
permission: dashboardaccess.PERMISSION_EDIT,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
},
@ -301,7 +302,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
},
{
desc: "Should be able to view editable dashboards under the root with folders:uid:general scope if has write action",
permission: dashboards.PERMISSION_EDIT,
permission: dashboardaccess.PERMISSION_EDIT,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
{Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
@ -310,7 +311,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
},
{
desc: "Should be able to view all folders with folder wildcard",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:*"},
},
@ -318,7 +319,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
},
{
desc: "Should be able to view a subset folders",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:3"},
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:6"},
@ -328,7 +329,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
},
{
desc: "Should return folders and dashboard with 'edit' permission",
permission: dashboards.PERMISSION_EDIT,
permission: dashboardaccess.PERMISSION_EDIT,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:3"},
{Action: dashboards.ActionDashboardsCreate, Scope: "folders:uid:3"},
@ -339,7 +340,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
},
{
desc: "Should return the dashboards that the User has dashboards:write permission on in case of 'edit' permission",
permission: dashboards.PERMISSION_EDIT,
permission: dashboardaccess.PERMISSION_EDIT,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:3"},
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:31"},
@ -351,7 +352,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
},
{
desc: "Should return the folders that the User has dashboards:create permission on in case of 'edit' permission",
permission: dashboards.PERMISSION_EDIT,
permission: dashboardaccess.PERMISSION_EDIT,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:3"},
{Action: dashboards.ActionDashboardsCreate, Scope: "folders:uid:3"},
@ -363,7 +364,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
},
{
desc: "Should return folders that users can read alerts from",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
queryType: searchstore.TypeAlertFolder,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:3"},
@ -375,7 +376,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
},
{
desc: "Should return folders that users can read alerts when user has read wildcard",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
queryType: searchstore.TypeAlertFolder,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "*"},
@ -427,7 +428,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
testCases := []struct {
desc string
queryType string
permission dashboards.PermissionType
permission dashboardaccess.PermissionType
permissions []accesscontrol.Permission
expectedResult []string
features []any
@ -435,7 +436,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
{
desc: "Should not be able to view dashboards under inherited folders with no permissions if nested folders are enabled",
queryType: searchstore.TypeDashboard,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: nil,
features: []any{featuremgmt.FlagNestedFolders},
expectedResult: nil,
@ -443,14 +444,14 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
{
desc: "Should not be able to view inherited folders with no permissions if nested folders are enabled",
queryType: searchstore.TypeFolder,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: nil,
features: []any{featuremgmt.FlagNestedFolders},
expectedResult: nil,
},
{
desc: "Should not be able to view inherited dashboards and folders with no permissions if nested folders are enabled",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: nil,
features: []any{featuremgmt.FlagNestedFolders},
expectedResult: nil,
@ -458,7 +459,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
{
desc: "Should be able to view dashboards under inherited folders with wildcard scope if nested folders are enabled",
queryType: searchstore.TypeDashboard,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
},
@ -468,7 +469,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
{
desc: "Should be able to view dashboards under inherited folders if nested folders are enabled",
queryType: searchstore.TypeDashboard,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"},
},
@ -478,7 +479,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
{
desc: "Should not be able to view dashboards under inherited folders if nested folders are not enabled",
queryType: searchstore.TypeDashboard,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"},
},
@ -488,7 +489,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
{
desc: "Should be able to view inherited folders if nested folders are enabled",
queryType: searchstore.TypeFolder,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
},
@ -498,7 +499,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
{
desc: "Should not be able to view inherited folders if nested folders are not enabled",
queryType: searchstore.TypeFolder,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
},
@ -507,7 +508,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
},
{
desc: "Should be able to view inherited dashboards and folders if nested folders are enabled",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"},
@ -517,7 +518,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
},
{
desc: "Should not be able to view inherited dashboards and folders if nested folders are not enabled",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"},
@ -580,7 +581,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
testCases := []struct {
desc string
queryType string
permission dashboards.PermissionType
permission dashboardaccess.PermissionType
signedInUserPermissions []accesscontrol.Permission
expectedResult []string
features []any
@ -588,7 +589,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
{
desc: "Should not be able to view dashboards under inherited folders with no permissions if nested folders are enabled",
queryType: searchstore.TypeDashboard,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: nil,
features: []any{featuremgmt.FlagNestedFolders},
expectedResult: nil,
@ -596,14 +597,14 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
{
desc: "Should not be able to view inherited folders with no permissions if nested folders are enabled",
queryType: searchstore.TypeFolder,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: nil,
features: []any{featuremgmt.FlagNestedFolders},
expectedResult: nil,
},
{
desc: "Should not be able to view inherited dashboards and folders with no permissions if nested folders are enabled",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: nil,
features: []any{featuremgmt.FlagNestedFolders},
expectedResult: nil,
@ -611,7 +612,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
{
desc: "Should be able to view dashboards under inherited folders with wildcard scope if nested folders are enabled",
queryType: searchstore.TypeDashboard,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
},
@ -621,7 +622,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
{
desc: "Should be able to view dashboards under inherited folders if nested folders are enabled",
queryType: searchstore.TypeDashboard,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"},
},
@ -631,7 +632,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
{
desc: "Should not be able to view dashboards under inherited folders if nested folders are not enabled",
queryType: searchstore.TypeDashboard,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"},
},
@ -641,7 +642,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
{
desc: "Should be able to view inherited folders if nested folders are enabled",
queryType: searchstore.TypeFolder,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
},
@ -651,7 +652,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
{
desc: "Should not be able to view inherited folders if nested folders are not enabled",
queryType: searchstore.TypeFolder,
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
},
@ -660,7 +661,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
},
{
desc: "Should be able to view inherited dashboards and folders if nested folders are enabled",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"},
@ -670,7 +671,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
},
{
desc: "Should not be able to view inherited dashboards and folders if nested folders are not enabled",
permission: dashboards.PERMISSION_VIEW,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"},
@ -680,7 +681,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
},
{
desc: "Should be able to edit inherited dashboards and folders if nested folders are enabled",
permission: dashboards.PERMISSION_EDIT,
permission: dashboardaccess.PERMISSION_EDIT,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:subfolder"},
{Action: dashboards.ActionDashboardsCreate, Scope: "folders:uid:subfolder"},

View File

@ -17,6 +17,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
@ -56,7 +57,7 @@ func benchmarkDashboardPermissionFilter(b *testing.B, numUsers, numDashboards, n
b.ResetTimer()
for i := 0; i < b.N; i++ {
filter := permissions.NewAccessControlDashboardPermissionFilter(&usr, dashboards.PERMISSION_VIEW, "", features, recursiveQueriesAreSupported)
filter := permissions.NewAccessControlDashboardPermissionFilter(&usr, dashboardaccess.PERMISSION_VIEW, "", features, recursiveQueriesAreSupported)
var result int
err := store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
q, params := filter.Where()

View File

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
@ -299,7 +300,7 @@ func TestBuilder_RBAC(t *testing.T) {
user.Permissions = map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tc.userPermissions)}
}
level := dashboards.PERMISSION_EDIT
level := dashboardaccess.PERMISSION_EDIT
builder := &searchstore.Builder{
Filters: []any{

View File

@ -7,7 +7,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
@ -109,10 +109,10 @@ func (ss *sqlStatsService) GetSystemStats(ctx context.Context, query *stats.GetS
WHERE d.is_folder = ?
) AS folder_permissions,`, dialect.BooleanStr(true))
sb.Write(viewersPermissionsCounterSQL(ss.db, "dashboards_viewers_can_edit", false, dashboards.PERMISSION_EDIT))
sb.Write(viewersPermissionsCounterSQL(ss.db, "dashboards_viewers_can_admin", false, dashboards.PERMISSION_ADMIN))
sb.Write(viewersPermissionsCounterSQL(ss.db, "folders_viewers_can_edit", true, dashboards.PERMISSION_EDIT))
sb.Write(viewersPermissionsCounterSQL(ss.db, "folders_viewers_can_admin", true, dashboards.PERMISSION_ADMIN))
sb.Write(viewersPermissionsCounterSQL(ss.db, "dashboards_viewers_can_edit", false, dashboardaccess.PERMISSION_EDIT))
sb.Write(viewersPermissionsCounterSQL(ss.db, "dashboards_viewers_can_admin", false, dashboardaccess.PERMISSION_ADMIN))
sb.Write(viewersPermissionsCounterSQL(ss.db, "folders_viewers_can_edit", true, dashboardaccess.PERMISSION_EDIT))
sb.Write(viewersPermissionsCounterSQL(ss.db, "folders_viewers_can_admin", true, dashboardaccess.PERMISSION_ADMIN))
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_provisioning") + `) AS provisioned_dashboards,`)
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_snapshot") + `) AS snapshots,`)
@ -166,7 +166,7 @@ func (ss *sqlStatsService) roleCounterSQL(ctx context.Context) string {
return sqlQuery
}
func viewersPermissionsCounterSQL(db db.DB, statName string, isFolder bool, permission dashboards.PermissionType) string {
func viewersPermissionsCounterSQL(db db.DB, statName string, isFolder bool, permission dashboardaccess.PermissionType) string {
dialect := db.GetDialect()
return `(
SELECT COUNT(*)

View File

@ -8,7 +8,7 @@ import (
"github.com/grafana/grafana/pkg/kinds/team"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/search/model"
)
@ -110,7 +110,7 @@ type TeamDTO struct {
Email string `json:"email"`
AvatarURL string `json:"avatarUrl"`
MemberCount int64 `json:"memberCount"`
Permission dashboards.PermissionType `json:"permission"`
Permission dashboardaccess.PermissionType `json:"permission"`
AccessControl map[string]bool `json:"accessControl"`
}
@ -128,7 +128,7 @@ type TeamMember struct {
TeamID int64 `xorm:"team_id"`
UserID int64 `xorm:"user_id"`
External bool // Signals that the membership has been created by an external systems, such as LDAP
Permission dashboards.PermissionType
Permission dashboardaccess.PermissionType
Created time.Time
Updated time.Time
@ -142,14 +142,14 @@ type AddTeamMemberCommand struct {
OrgID int64 `json:"-"`
TeamID int64 `json:"-"`
External bool `json:"-"`
Permission dashboards.PermissionType `json:"-"`
Permission dashboardaccess.PermissionType `json:"-"`
}
type UpdateTeamMemberCommand struct {
UserID int64 `json:"-"`
OrgID int64 `json:"-"`
TeamID int64 `json:"-"`
Permission dashboards.PermissionType `json:"permission"`
Permission dashboardaccess.PermissionType `json:"permission"`
}
type RemoveTeamMemberCommand struct {
@ -185,5 +185,5 @@ type TeamMemberDTO struct {
Login string `json:"login"`
AvatarURL string `json:"avatarUrl" xorm:"avatar_url"`
Labels []string `json:"labels"`
Permission dashboards.PermissionType `json:"permission"`
Permission dashboardaccess.PermissionType `json:"permission"`
}

View File

@ -3,7 +3,7 @@ package team
import (
"context"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
)
type Service interface {
@ -14,7 +14,7 @@ type Service interface {
GetTeamByID(ctx context.Context, query *GetTeamByIDQuery) (*TeamDTO, error)
GetTeamsByUser(ctx context.Context, query *GetTeamsByUserQuery) ([]*TeamDTO, error)
GetTeamIDsByUser(ctx context.Context, query *GetTeamIDsByUserQuery) ([]int64, error)
AddTeamMember(userID, orgID, teamID int64, isExternal bool, permission dashboards.PermissionType) error
AddTeamMember(userID, orgID, teamID int64, isExternal bool, permission dashboardaccess.PermissionType) error
UpdateTeamMember(ctx context.Context, cmd *UpdateTeamMemberCommand) error
IsTeamMember(orgId int64, teamId int64, userId int64) (bool, error)
RemoveTeamMember(ctx context.Context, cmd *RemoveTeamMemberCommand) error

View File

@ -10,7 +10,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/preference/prefapi"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/team/sortopts"
@ -58,7 +58,7 @@ func (tapi *TeamAPI) createTeam(c *contextmodel.ReqContext) response.Response {
break
}
if err := addOrUpdateTeamMember(c.Req.Context(), tapi.teamPermissionsService, userID, c.SignedInUser.GetOrgID(),
t.ID, dashboards.PERMISSION_ADMIN.String()); err != nil {
t.ID, dashboardaccess.PERMISSION_ADMIN.String()); err != nil {
c.Logger.Error("Could not add creator to team", "error", err)
}
default:

View File

@ -11,7 +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/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/util"
@ -141,7 +141,7 @@ func (tapi *TeamAPI) updateTeamMember(c *contextmodel.ReqContext) response.Respo
return response.Success("Team member updated")
}
func getPermissionName(permission dashboards.PermissionType) string {
func getPermissionName(permission dashboardaccess.PermissionType) string {
permissionName := permission.String()
// Team member permission is 0, which maps to an empty string.
// However, we want the team permission service to display "Member" for team members. This is a hack to make it work.

View File

@ -10,7 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@ -25,7 +25,7 @@ type store interface {
GetByUser(ctx context.Context, query *team.GetTeamsByUserQuery) ([]*team.TeamDTO, error)
GetIDsByUser(ctx context.Context, query *team.GetTeamIDsByUserQuery) ([]int64, error)
RemoveUsersMemberships(ctx context.Context, userID int64) error
AddMember(userID, orgID, teamID int64, isExternal bool, permission dashboards.PermissionType) error
AddMember(userID, orgID, teamID int64, isExternal bool, permission dashboardaccess.PermissionType) error
UpdateMember(ctx context.Context, cmd *team.UpdateTeamMemberCommand) error
IsMember(orgId int64, teamId int64, userId int64) (bool, error)
RemoveMember(ctx context.Context, cmd *team.RemoveTeamMemberCommand) error
@ -354,7 +354,7 @@ WHERE tm.user_id=? AND tm.org_id=?;`, query.UserID, query.OrgID).Find(&queryResu
}
// AddTeamMember adds a user to a team
func (ss *xormStore) AddMember(userID, orgID, teamID int64, isExternal bool, permission dashboards.PermissionType) error {
func (ss *xormStore) AddMember(userID, orgID, teamID int64, isExternal bool, permission dashboardaccess.PermissionType) error {
return ss.db.WithTransactionalDbSession(context.Background(), func(sess *db.Session) error {
if isMember, err := isTeamMember(sess, orgID, teamID, userID); err != nil {
return err
@ -412,7 +412,7 @@ func isTeamMember(sess *db.Session, orgId int64, teamId int64, userId int64) (bo
// AddOrUpdateTeamMemberHook is called from team resource permission service
// it adds user to a team or updates user permissions in a team within the given transaction session
func AddOrUpdateTeamMemberHook(sess *db.Session, userID, orgID, teamID int64, isExternal bool, permission dashboards.PermissionType) error {
func AddOrUpdateTeamMemberHook(sess *db.Session, userID, orgID, teamID int64, isExternal bool, permission dashboardaccess.PermissionType) error {
isMember, err := isTeamMember(sess, orgID, teamID, userID)
if err != nil {
return err
@ -427,7 +427,7 @@ func AddOrUpdateTeamMemberHook(sess *db.Session, userID, orgID, teamID int64, is
return err
}
func addTeamMember(sess *db.Session, orgID, teamID, userID int64, isExternal bool, permission dashboards.PermissionType) error {
func addTeamMember(sess *db.Session, orgID, teamID, userID int64, isExternal bool, permission dashboardaccess.PermissionType) error {
if _, err := teamExists(orgID, teamID, sess); err != nil {
return err
}
@ -446,13 +446,13 @@ func addTeamMember(sess *db.Session, orgID, teamID, userID int64, isExternal boo
return err
}
func updateTeamMember(sess *db.Session, orgID, teamID, userID int64, permission dashboards.PermissionType) error {
func updateTeamMember(sess *db.Session, orgID, teamID, userID int64, permission dashboardaccess.PermissionType) error {
member, err := getTeamMember(sess, orgID, teamID, userID)
if err != nil {
return err
}
if permission != dashboards.PERMISSION_ADMIN {
if permission != dashboardaccess.PERMISSION_ADMIN {
permission = 0 // make sure we don't get invalid permission levels in store
}

View File

@ -12,7 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
@ -174,7 +174,7 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) {
UserID: userId,
OrgID: testOrgID,
TeamID: team1.ID,
Permission: dashboards.PERMISSION_ADMIN,
Permission: dashboardaccess.PERMISSION_ADMIN,
})
require.NoError(t, err)
@ -182,7 +182,7 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) {
qAfterUpdate := &team.GetTeamMembersQuery{OrgID: testOrgID, TeamID: team1.ID, SignedInUser: testUser}
qAfterUpdateResult, err := teamSvc.GetTeamMembers(context.Background(), qAfterUpdate)
require.NoError(t, err)
require.Equal(t, qAfterUpdateResult[0].Permission, dashboards.PERMISSION_ADMIN)
require.Equal(t, qAfterUpdateResult[0].Permission, dashboardaccess.PERMISSION_ADMIN)
})
t.Run("Should default to member permission level when updating a user with invalid permission level", func(t *testing.T) {
@ -197,7 +197,7 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) {
require.NoError(t, err)
require.EqualValues(t, qBeforeUpdateResult[0].Permission, 0)
invalidPermissionLevel := dashboards.PERMISSION_EDIT
invalidPermissionLevel := dashboardaccess.PERMISSION_EDIT
err = teamSvc.UpdateTeamMember(context.Background(), &team.UpdateTeamMemberCommand{
UserID: userID,
OrgID: testOrgID,
@ -220,7 +220,7 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) {
UserID: 1,
OrgID: testOrgID,
TeamID: team1.ID,
Permission: dashboards.PERMISSION_ADMIN,
Permission: dashboardaccess.PERMISSION_ADMIN,
})
require.Error(t, err, team.ErrTeamMemberNotFound)
@ -330,7 +330,7 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) {
})
t.Run("Should have empty teams", func(t *testing.T) {
err = teamSvc.AddTeamMember(userIds[0], testOrgID, team1.ID, false, dashboards.PERMISSION_ADMIN)
err = teamSvc.AddTeamMember(userIds[0], testOrgID, team1.ID, false, dashboardaccess.PERMISSION_ADMIN)
require.NoError(t, err)
t.Run("A user should be able to remove the admin permission for the last admin", func(t *testing.T) {
@ -347,10 +347,10 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) {
sqlStore = db.InitTestDB(t)
setup()
err = teamSvc.AddTeamMember(userIds[0], testOrgID, team1.ID, false, dashboards.PERMISSION_ADMIN)
err = teamSvc.AddTeamMember(userIds[0], testOrgID, team1.ID, false, dashboardaccess.PERMISSION_ADMIN)
require.NoError(t, err)
err = teamSvc.AddTeamMember(userIds[1], testOrgID, team1.ID, false, dashboards.PERMISSION_ADMIN)
err = teamSvc.AddTeamMember(userIds[1], testOrgID, team1.ID, false, dashboardaccess.PERMISSION_ADMIN)
require.NoError(t, err)
err = teamSvc.UpdateTeamMember(context.Background(), &team.UpdateTeamMemberCommand{OrgID: testOrgID, TeamID: team1.ID, UserID: userIds[0], Permission: 0})
require.NoError(t, err)

View File

@ -4,7 +4,7 @@ import (
"context"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/setting"
)
@ -45,7 +45,7 @@ func (s *Service) GetTeamIDsByUser(ctx context.Context, query *team.GetTeamIDsBy
return s.store.GetIDsByUser(ctx, query)
}
func (s *Service) AddTeamMember(userID, orgID, teamID int64, isExternal bool, permission dashboards.PermissionType) error {
func (s *Service) AddTeamMember(userID, orgID, teamID int64, isExternal bool, permission dashboardaccess.PermissionType) error {
return s.store.AddMember(userID, orgID, teamID, isExternal, permission)
}

View File

@ -3,7 +3,7 @@ package teamtest
import (
"context"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/team"
)
@ -45,7 +45,7 @@ func (s *FakeService) GetTeamsByUser(ctx context.Context, query *team.GetTeamsBy
return s.ExpectedTeamsByUser, s.ExpectedError
}
func (s *FakeService) AddTeamMember(userID, orgID, teamID int64, isExternal bool, permission dashboards.PermissionType) error {
func (s *FakeService) AddTeamMember(userID, orgID, teamID int64, isExternal bool, permission dashboardaccess.PermissionType) error {
return s.ExpectedError
}