AuthZ service: Build folder tree and check inherited permissions (#98074)

* build folder tree and check inherited permissions

* don't fetch dashboards

* remove unused queries
This commit is contained in:
Ieva 2024-12-18 14:19:16 +00:00 committed by GitHub
parent a727372573
commit 40a9f7162a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 193 additions and 15 deletions

View File

@ -3,11 +3,18 @@ package rbac
import "github.com/grafana/authlib/claims"
type CheckRequest struct {
Namespace claims.NamespaceInfo
UserUID string
Action string
Group string
Resource string
Verb string
Name string
Namespace claims.NamespaceInfo
UserUID string
Action string
Group string
Resource string
Verb string
Name string
ParentFolder string
}
type FolderNode struct {
uid string
parentUID *string
childrenUIDs []string
}

View File

@ -21,6 +21,7 @@ import (
"github.com/grafana/grafana/pkg/services/authz/mappers"
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
"github.com/grafana/grafana/pkg/services/authz/rbac/store"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/storage/legacysql"
)
@ -135,13 +136,14 @@ func (s *Service) validateRequest(ctx context.Context, req *authzv1.CheckRequest
}
checkReq := &CheckRequest{
Namespace: ns,
UserUID: userUID,
Action: action,
Group: req.GetGroup(),
Resource: req.GetResource(),
Verb: req.GetVerb(),
Name: req.GetName(),
Namespace: ns,
UserUID: userUID,
Action: action,
Group: req.GetGroup(),
Resource: req.GetResource(),
Verb: req.GetVerb(),
Name: req.GetName(),
ParentFolder: req.GetFolder(),
}
return checkReq, nil
}
@ -279,7 +281,11 @@ func (s *Service) checkPermission(ctx context.Context, scopeMap map[string]bool,
ctxLogger.Error("could not get attribute for resource", "resource", req.Resource)
return false, fmt.Errorf("could not get attribute for resource")
}
return scopeMap[scope], nil
if scopeMap[scope] {
return true, nil
}
return s.checkInheritedPermissions(ctx, scopeMap, req)
}
func getScopeMap(permissions []accesscontrol.Permission) map[string]bool {
@ -293,3 +299,69 @@ func getScopeMap(permissions []accesscontrol.Permission) map[string]bool {
}
return permMap
}
func (s *Service) checkInheritedPermissions(ctx context.Context, scopeMap map[string]bool, req *CheckRequest) (bool, error) {
if req.ParentFolder == "" {
return false, nil
}
ctxLogger := s.logger.FromContext(ctx)
folderMap, err := s.buildFolderTree(ctx, req.Namespace)
if err != nil {
ctxLogger.Error("could not build folder and dashboard tree", "error", err)
return false, err
}
currentUID := req.ParentFolder
for {
if node, has := folderMap[currentUID]; has {
scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(node.uid)
if scopeMap[scope] {
return true, nil
}
if node.parentUID == nil {
break
}
currentUID = *node.parentUID
} else {
break
}
}
return false, nil
}
func (s *Service) buildFolderTree(ctx context.Context, ns claims.NamespaceInfo) (map[string]FolderNode, error) {
folders, err := s.store.GetFolders(ctx, ns)
if err != nil {
return nil, fmt.Errorf("could not get folders: %w", err)
}
folderMap := make(map[string]FolderNode, len(folders))
for _, folder := range folders {
if node, has := folderMap[folder.UID]; !has {
folderMap[folder.UID] = FolderNode{
uid: folder.UID,
parentUID: folder.ParentUID,
}
} else {
node.parentUID = folder.ParentUID
folderMap[folder.UID] = node
}
// Register that the parent has this child node
if folder.ParentUID == nil {
continue
}
if parent, has := folderMap[*folder.ParentUID]; has {
parent.childrenUIDs = append(parent.childrenUIDs, folder.UID)
folderMap[*folder.ParentUID] = parent
} else {
folderMap[*folder.ParentUID] = FolderNode{
uid: *folder.ParentUID,
childrenUIDs: []string{folder.UID},
}
}
}
return folderMap, nil
}

View File

@ -0,0 +1,3 @@
SELECT uid, parent_uid
FROM {{ .Ident .FolderTable }} as u
WHERE u.org_id = {{ .Arg .Query.OrgID }}

View File

@ -28,3 +28,21 @@ type UserIdentifierQuery struct {
UserID int64
UserUID string
}
type FolderQuery struct {
OrgID int64
}
type DashboardQuery struct {
OrgID int64
}
type Folder struct {
UID string
ParentUID *string
}
type Dashboard struct {
UID string
ParentUID *string
}

View File

@ -17,6 +17,7 @@ var (
sqlUserPerms = mustTemplate("permission_query.sql")
sqlQueryBasicRoles = mustTemplate("basic_role_query.sql")
sqlUserIdentifiers = mustTemplate("user_identifier_query.sql")
sqlFolders = mustTemplate("folder_query.sql")
)
func mustTemplate(filename string) *template.Template {
@ -90,3 +91,22 @@ func newGetPermissions(sql *legacysql.LegacyDatabaseHelper, q *PermissionsQuery)
BuiltinRoleTable: sql.Table("builtin_role"),
}
}
type getFoldersQuery struct {
sqltemplate.SQLTemplate
Query *FolderQuery
FolderTable string
}
func (r getFoldersQuery) Validate() error {
return nil
}
func newGetFolders(sql *legacysql.LegacyDatabaseHelper, q *FolderQuery) getFoldersQuery {
return getFoldersQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
Query: q,
FolderTable: sql.Table("folder"),
}
}

View File

@ -35,6 +35,12 @@ func TestIdentityQueries(t *testing.T) {
return &v
}
getFolders := func(q *FolderQuery) sqltemplate.SQLTemplate {
v := newGetFolders(nodb, q)
v.SQLTemplate = mocks.NewTestingSQLTemplate()
return &v
}
mocks.CheckQuerySnapshots(t, mocks.TemplateTestSetup{
RootDir: "testdata",
Templates: map[*template.Template][]mocks.TemplateTestCase{
@ -92,6 +98,14 @@ func TestIdentityQueries(t *testing.T) {
}),
},
},
sqlFolders: {
{
Name: "folder_query",
Data: getFolders(&FolderQuery{
OrgID: 1,
}),
},
},
},
})
}

View File

@ -130,3 +130,38 @@ func (s *StoreImpl) GetBasicRoles(ctx context.Context, ns claims.NamespaceInfo,
return &role, nil
}
func (s *Store) GetFolders(ctx context.Context, ns claims.NamespaceInfo) ([]Folder, error) {
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
query := FolderQuery{OrgID: ns.OrgID}
req := newGetFolders(sql, &query)
q, err := sqltemplate.Execute(sqlFolders, req)
if err != nil {
return nil, err
}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
defer func() {
if rows != nil {
_ = rows.Close()
}
}()
if err != nil {
return nil, err
}
var folders []Folder
for rows.Next() {
var folder Folder
if err := rows.Scan(&folder.UID, &folder.ParentUID); err != nil {
return nil, err
}
folders = append(folders, folder)
}
return folders, nil
}

View File

@ -0,0 +1,3 @@
SELECT uid, parent_uid
FROM `grafana`.`folder` as u
WHERE u.org_id = 1

View File

@ -0,0 +1,3 @@
SELECT uid, parent_uid
FROM "grafana"."folder" as u
WHERE u.org_id = 1

View File

@ -0,0 +1,3 @@
SELECT uid, parent_uid
FROM "grafana"."folder" as u
WHERE u.org_id = 1