mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
a727372573
commit
40a9f7162a
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
3
pkg/services/authz/rbac/store/folder_query.sql
Normal file
3
pkg/services/authz/rbac/store/folder_query.sql
Normal file
@ -0,0 +1,3 @@
|
||||
SELECT uid, parent_uid
|
||||
FROM {{ .Ident .FolderTable }} as u
|
||||
WHERE u.org_id = {{ .Arg .Query.OrgID }}
|
@ -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
|
||||
}
|
||||
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
3
pkg/services/authz/rbac/store/testdata/mysql--folder_query-folder_query.sql
vendored
Executable file
3
pkg/services/authz/rbac/store/testdata/mysql--folder_query-folder_query.sql
vendored
Executable file
@ -0,0 +1,3 @@
|
||||
SELECT uid, parent_uid
|
||||
FROM `grafana`.`folder` as u
|
||||
WHERE u.org_id = 1
|
3
pkg/services/authz/rbac/store/testdata/postgres--folder_query-folder_query.sql
vendored
Executable file
3
pkg/services/authz/rbac/store/testdata/postgres--folder_query-folder_query.sql
vendored
Executable file
@ -0,0 +1,3 @@
|
||||
SELECT uid, parent_uid
|
||||
FROM "grafana"."folder" as u
|
||||
WHERE u.org_id = 1
|
3
pkg/services/authz/rbac/store/testdata/sqlite--folder_query-folder_query.sql
vendored
Executable file
3
pkg/services/authz/rbac/store/testdata/sqlite--folder_query-folder_query.sql
vendored
Executable file
@ -0,0 +1,3 @@
|
||||
SELECT uid, parent_uid
|
||||
FROM "grafana"."folder" as u
|
||||
WHERE u.org_id = 1
|
Loading…
Reference in New Issue
Block a user