mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Zanzana: Update relation names (#97638)
* Update relation names to match k8s verbs * Only check namespace if relation is valid * Only list for valid relations
This commit is contained in:
parent
4712883601
commit
718612aabf
@ -55,19 +55,19 @@ func NewZanzanaReconciler(cfg *setting.Cfg, client zanzana.Client, store db.DB,
|
||||
newResourceReconciler(
|
||||
"managed folder permissions",
|
||||
managedPermissionsCollector(store, zanzana.KindFolders),
|
||||
zanzanaCollector(zanzana.FolderRelations),
|
||||
zanzanaCollector(zanzana.RelationsFolder),
|
||||
client,
|
||||
),
|
||||
newResourceReconciler(
|
||||
"managed dashboard permissions",
|
||||
managedPermissionsCollector(store, zanzana.KindDashboards),
|
||||
zanzanaCollector(zanzana.ResourceRelations),
|
||||
zanzanaCollector(zanzana.RelationsResouce),
|
||||
client,
|
||||
),
|
||||
newResourceReconciler(
|
||||
"role permissions",
|
||||
rolePermissionsCollector(store),
|
||||
zanzanaCollector(zanzana.FolderRelations),
|
||||
zanzanaCollector(zanzana.RelationsFolder),
|
||||
client,
|
||||
),
|
||||
newResourceReconciler(
|
||||
|
@ -11,11 +11,15 @@ type TypeInfo struct {
|
||||
Relations []string
|
||||
}
|
||||
|
||||
func (t TypeInfo) IsValidRelation(relation string) bool {
|
||||
return isValidRelation(relation, t.Relations)
|
||||
}
|
||||
|
||||
var typedResources = map[string]TypeInfo{
|
||||
FormatGroupResource(
|
||||
folderalpha1.FolderResourceInfo.GroupResource().Group,
|
||||
folderalpha1.FolderResourceInfo.GroupResource().Resource,
|
||||
): {Type: "folder", Relations: append(ResourceRelations, RelationCreate)},
|
||||
): {Type: "folder", Relations: RelationsFolder},
|
||||
}
|
||||
|
||||
func GetTypeInfo(group, resource string) (TypeInfo, bool) {
|
||||
@ -24,19 +28,19 @@ func GetTypeInfo(group, resource string) (TypeInfo, bool) {
|
||||
}
|
||||
|
||||
var VerbMapping = map[string]string{
|
||||
utils.VerbGet: RelationRead,
|
||||
utils.VerbList: RelationRead,
|
||||
utils.VerbWatch: RelationRead,
|
||||
utils.VerbGet: RelationGet,
|
||||
utils.VerbList: RelationGet,
|
||||
utils.VerbWatch: RelationGet,
|
||||
utils.VerbCreate: RelationCreate,
|
||||
utils.VerbUpdate: RelationWrite,
|
||||
utils.VerbPatch: RelationWrite,
|
||||
utils.VerbUpdate: RelationUpdate,
|
||||
utils.VerbPatch: RelationUpdate,
|
||||
utils.VerbDelete: RelationDelete,
|
||||
utils.VerbDeleteCollection: RelationDelete,
|
||||
}
|
||||
|
||||
var RelationToVerbMapping = map[string]string{
|
||||
RelationRead: utils.VerbGet,
|
||||
RelationGet: utils.VerbGet,
|
||||
RelationCreate: utils.VerbCreate,
|
||||
RelationWrite: utils.VerbUpdate,
|
||||
RelationUpdate: utils.VerbUpdate,
|
||||
RelationDelete: utils.VerbDelete,
|
||||
}
|
||||
|
@ -31,44 +31,74 @@ const (
|
||||
RelationSetEdit string = "edit"
|
||||
RelationSetAdmin string = "admin"
|
||||
|
||||
RelationRead string = "read"
|
||||
RelationWrite string = "write"
|
||||
RelationCreate string = "create"
|
||||
RelationDelete string = "delete"
|
||||
RelationPermissionsRead string = "permissions_read"
|
||||
RelationPermissionsWrite string = "permissions_write"
|
||||
RelationGet string = "get"
|
||||
RelationUpdate string = "update"
|
||||
RelationCreate string = "create"
|
||||
RelationDelete string = "delete"
|
||||
|
||||
RelationFolderResourceSetView string = "resource_" + RelationSetView
|
||||
RelationFolderResourceSetEdit string = "resource_" + RelationSetEdit
|
||||
RelationFolderResourceSetAdmin string = "resource_" + RelationSetAdmin
|
||||
|
||||
RelationFolderResourceRead string = "resource_" + RelationRead
|
||||
RelationFolderResourceWrite string = "resource_" + RelationWrite
|
||||
RelationFolderResourceCreate string = "resource_" + RelationCreate
|
||||
RelationFolderResourceDelete string = "resource_" + RelationDelete
|
||||
RelationFolderResourcePermissionsRead string = "resource_" + RelationPermissionsRead
|
||||
RelationFolderResourcePermissionsWrite string = "resource_" + RelationPermissionsWrite
|
||||
RelationFolderResourceGet string = "resource_" + RelationGet
|
||||
RelationFolderResourceUpdate string = "resource_" + RelationUpdate
|
||||
RelationFolderResourceCreate string = "resource_" + RelationCreate
|
||||
RelationFolderResourceDelete string = "resource_" + RelationDelete
|
||||
)
|
||||
|
||||
var ResourceRelations = []string{
|
||||
RelationRead,
|
||||
RelationWrite,
|
||||
// RelationsNamespace are relations that can be added on type "namespace".
|
||||
var RelationsNamespace = []string{
|
||||
RelationGet,
|
||||
RelationUpdate,
|
||||
RelationCreate,
|
||||
RelationDelete,
|
||||
RelationPermissionsRead,
|
||||
RelationPermissionsWrite,
|
||||
}
|
||||
|
||||
var FolderRelations = append(
|
||||
ResourceRelations,
|
||||
RelationCreate,
|
||||
RelationFolderResourceRead,
|
||||
RelationFolderResourceWrite,
|
||||
// RelationsResource are relations that can be added on type "resource".
|
||||
var RelationsResource = []string{
|
||||
RelationGet,
|
||||
RelationUpdate,
|
||||
RelationDelete,
|
||||
}
|
||||
|
||||
// RelationsFolderResource are relations that can be added on type "folder" for child resources.
|
||||
var RelationsFolderResource = []string{
|
||||
RelationFolderResourceGet,
|
||||
RelationFolderResourceUpdate,
|
||||
RelationFolderResourceCreate,
|
||||
RelationFolderResourceDelete,
|
||||
RelationFolderResourcePermissionsRead,
|
||||
RelationFolderResourcePermissionsWrite,
|
||||
}
|
||||
|
||||
// RelationsFolder are relations that can be added on type "folder".
|
||||
var RelationsFolder = append(
|
||||
RelationsFolderResource,
|
||||
RelationGet,
|
||||
RelationUpdate,
|
||||
RelationCreate,
|
||||
RelationDelete,
|
||||
)
|
||||
|
||||
func IsNamespaceRelation(relation string) bool {
|
||||
return isValidRelation(relation, RelationsNamespace)
|
||||
}
|
||||
|
||||
func IsFolderResourceRelation(relation string) bool {
|
||||
return isValidRelation(relation, RelationsFolderResource)
|
||||
}
|
||||
|
||||
func IsResourceRelation(relation string) bool {
|
||||
return isValidRelation(relation, RelationsResource)
|
||||
}
|
||||
|
||||
func isValidRelation(relation string, valid []string) bool {
|
||||
for _, r := range valid {
|
||||
if r == relation {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func FolderResourceRelation(relation string) string {
|
||||
return fmt.Sprintf("%s_%s", TypeResource, relation)
|
||||
}
|
||||
@ -258,10 +288,18 @@ func AddRenderContext(req *openfgav1.CheckRequest) {
|
||||
|
||||
req.ContextualTuples.TupleKeys = append(req.ContextualTuples.TupleKeys, &openfgav1.TupleKey{
|
||||
User: req.TupleKey.User,
|
||||
Relation: "view",
|
||||
Relation: RelationSetView,
|
||||
Object: NewNamespaceResourceIdent(
|
||||
dashboardalpha1.DashboardResourceInfo.GroupResource().Group,
|
||||
dashboardalpha1.DashboardResourceInfo.GroupResource().Resource,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
func NewResourceContext(group, resource string) *structpb.Struct {
|
||||
return &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"requested_group": structpb.NewStringValue(FormatGroupResource(group, resource)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -12,12 +12,10 @@ type namespace
|
||||
define edit: [user, service-account, team#member, role#assignee] or admin
|
||||
define admin: [user, service-account, team#member, role#assignee]
|
||||
|
||||
define read: [user, service-account, render, team#member, role#assignee] or view
|
||||
define get: [user, service-account, render, team#member, role#assignee] or view
|
||||
define create: [user, service-account, team#member, role#assignee] or edit
|
||||
define write: [user, service-account, team#member, role#assignee] or edit
|
||||
define update: [user, service-account, team#member, role#assignee] or edit
|
||||
define delete: [user, service-account, team#member, role#assignee] or edit
|
||||
define permissions_read: [user, service-account, team#member, role#assignee] or admin
|
||||
define permissions_write: [user, service-account, team#member, role#assignee] or admin
|
||||
|
||||
type role
|
||||
relations
|
||||
@ -29,8 +27,6 @@ type team
|
||||
define admin: [user, service-account]
|
||||
define member: [user, service-account] or admin
|
||||
|
||||
define read: [role#assignee] or member
|
||||
define write: [role#assignee] or admin
|
||||
define get: [role#assignee] or member
|
||||
define update: [role#assignee] or admin
|
||||
define delete: [role#assignee] or admin
|
||||
define permissions_read: [role#assignee] or admin
|
||||
define permissions_write: [role#assignee] or admin
|
||||
|
@ -9,9 +9,7 @@ type folder
|
||||
define edit: [user, service-account, team#member, role#assignee] or admin or edit from parent
|
||||
define admin: [user, service-account, team#member, role#assignee] or admin from parent
|
||||
|
||||
define read: [user, service-account, team#member, role#assignee] or view or read from parent
|
||||
define get: [user, service-account, team#member, role#assignee] or view or get from parent
|
||||
define create: [user, service-account, team#member, role#assignee] or edit or create from parent
|
||||
define write: [user, service-account, team#member, role#assignee] or edit or write from parent
|
||||
define update: [user, service-account, team#member, role#assignee] or edit or update from parent
|
||||
define delete: [user, service-account, team#member, role#assignee] or edit or delete from parent
|
||||
define permissions_read: [user, service-account, team#member, role#assignee] or admin or permissions_read from parent
|
||||
define permissions_write: [user, service-account, team#member, role#assignee] or admin or permissions_write from parent
|
||||
|
@ -6,12 +6,10 @@ extend type folder
|
||||
define resource_edit: [user, service-account, team#member, role#assignee] or resource_admin or resource_edit from parent
|
||||
define resource_admin: [user, service-account, team#member, role#assignee] or resource_admin from parent
|
||||
|
||||
define resource_read: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_view or resource_read from parent
|
||||
define resource_get: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_view or resource_get from parent
|
||||
define resource_create: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_create from parent
|
||||
define resource_write: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_write from parent
|
||||
define resource_update: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_update from parent
|
||||
define resource_delete: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_delete from parent
|
||||
define resource_permissions_read: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_permissions_read from parent
|
||||
define resource_permissions_write: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_permissions_write from parent
|
||||
|
||||
type resource
|
||||
relations
|
||||
@ -19,11 +17,9 @@ type resource
|
||||
define edit: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin
|
||||
define admin: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter]
|
||||
|
||||
define read: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or view
|
||||
define write: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit
|
||||
define get: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or view
|
||||
define update: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit
|
||||
define delete: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit
|
||||
define permissions_read: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin
|
||||
define permissions_write: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin
|
||||
|
||||
condition group_filter(requested_group: string, group_resource: string) {
|
||||
requested_group == group_resource
|
||||
|
@ -20,7 +20,7 @@ func (s *Server) Capabilities(ctx context.Context, r *authzextv1.CapabilitiesReq
|
||||
}
|
||||
|
||||
func (s *Server) capabilitiesTyped(ctx context.Context, r *authzextv1.CapabilitiesRequest, info common.TypeInfo, store *storeInfo) (*authzextv1.CapabilitiesResponse, error) {
|
||||
out := make([]string, 0, len(common.ResourceRelations))
|
||||
out := make([]string, 0, len(common.RelationsResource))
|
||||
for _, relation := range info.Relations {
|
||||
res, err := s.checkNamespace(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
|
||||
if err != nil {
|
||||
@ -46,8 +46,8 @@ func (s *Server) capabilitiesTyped(ctx context.Context, r *authzextv1.Capabiliti
|
||||
}
|
||||
|
||||
func (s *Server) capabilitiesGeneric(ctx context.Context, r *authzextv1.CapabilitiesRequest, store *storeInfo) (*authzextv1.CapabilitiesResponse, error) {
|
||||
out := make([]string, 0, len(common.ResourceRelations))
|
||||
for _, relation := range common.ResourceRelations {
|
||||
out := make([]string, 0, len(common.RelationsResource))
|
||||
for _, relation := range common.RelationsResource {
|
||||
res, err := s.checkNamespace(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -26,42 +26,42 @@ func testCapabilities(t *testing.T, server *Server) {
|
||||
t.Run("user:1 should only be able to read and write resource:dashboards.grafana.app/dashboards/1", func(t *testing.T) {
|
||||
res, err := server.Capabilities(context.Background(), newReq("user:1", dashboardGroup, dashboardResource, "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{common.RelationRead, common.RelationWrite}, res.GetCapabilities())
|
||||
assert.Equal(t, []string{common.RelationGet, common.RelationUpdate}, res.GetCapabilities())
|
||||
})
|
||||
|
||||
t.Run("user:2 should be able to read and write resource:dashboards.grafana.app/dashboards/1 through namespace", func(t *testing.T) {
|
||||
res, err := server.Capabilities(context.Background(), newReq("user:2", dashboardGroup, dashboardResource, "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{common.RelationRead, common.RelationWrite}, res.GetCapabilities())
|
||||
assert.Equal(t, []string{common.RelationGet, common.RelationUpdate}, res.GetCapabilities())
|
||||
})
|
||||
|
||||
t.Run("user:3 should be able to read resource:dashboards.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
|
||||
res, err := server.Capabilities(context.Background(), newReq("user:3", dashboardGroup, dashboardResource, "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{common.RelationRead}, res.GetCapabilities())
|
||||
assert.Equal(t, []string{common.RelationGet}, res.GetCapabilities())
|
||||
})
|
||||
|
||||
t.Run("user:4 should be able to read dashboards.grafana.app/dashboards in folder 1", func(t *testing.T) {
|
||||
res, err := server.Capabilities(context.Background(), newReq("user:4", dashboardGroup, dashboardResource, "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{common.RelationRead}, res.GetCapabilities())
|
||||
assert.Equal(t, []string{common.RelationGet}, res.GetCapabilities())
|
||||
})
|
||||
|
||||
t.Run("user:5 should be able to read, write, create and delete resource:dashboards.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) {
|
||||
res, err := server.Capabilities(context.Background(), newReq("user:5", dashboardGroup, dashboardResource, "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{common.RelationRead, common.RelationWrite, common.RelationDelete}, res.GetCapabilities())
|
||||
assert.Equal(t, []string{common.RelationGet, common.RelationUpdate, common.RelationDelete}, res.GetCapabilities())
|
||||
})
|
||||
|
||||
t.Run("user:6 should be able to read folder 1 ", func(t *testing.T) {
|
||||
t.Run("user:6 should be able to read folder 1", func(t *testing.T) {
|
||||
res, err := server.Capabilities(context.Background(), newReq("user:6", folderGroup, folderResource, "", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{common.RelationRead}, res.GetCapabilities())
|
||||
assert.Equal(t, []string{common.RelationGet}, res.GetCapabilities())
|
||||
})
|
||||
|
||||
t.Run("user:7 should be able to read folder one through namespace access", func(t *testing.T) {
|
||||
res, err := server.Capabilities(context.Background(), newReq("user:7", folderGroup, folderResource, "", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{common.RelationRead}, res.GetCapabilities())
|
||||
assert.Equal(t, []string{common.RelationGet}, res.GetCapabilities())
|
||||
})
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
|
||||
authzv1 "github.com/grafana/authlib/authz/proto/v1"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/authz/zanzana/common"
|
||||
)
|
||||
@ -22,8 +21,6 @@ func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.C
|
||||
}
|
||||
|
||||
relation := common.VerbMapping[r.GetVerb()]
|
||||
|
||||
// Check if subject has access through namespace
|
||||
res, err := s.checkNamespace(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -39,9 +36,13 @@ func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.C
|
||||
return s.checkGeneric(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), r.GetName(), r.GetFolder(), store)
|
||||
}
|
||||
|
||||
// checkTyped performes check on the root "namespace". If subject has access through the namespace they have access to
|
||||
// checkTyped checks on the root "namespace". If subject has access through the namespace they have access to
|
||||
// every resource for that "GroupResource".
|
||||
func (s *Server) checkNamespace(ctx context.Context, subject, relation, group, resource string, store *storeInfo) (*authzv1.CheckResponse, error) {
|
||||
if !common.IsNamespaceRelation(relation) {
|
||||
return &authzv1.CheckResponse{Allowed: false}, nil
|
||||
}
|
||||
|
||||
req := &openfgav1.CheckRequest{
|
||||
StoreId: store.ID,
|
||||
AuthorizationModelId: store.ModelID,
|
||||
@ -51,6 +52,7 @@ func (s *Server) checkNamespace(ctx context.Context, subject, relation, group, r
|
||||
Object: common.NewNamespaceResourceIdent(group, resource),
|
||||
},
|
||||
}
|
||||
|
||||
if strings.HasPrefix(subject, fmt.Sprintf("%s:", common.TypeRenderService)) {
|
||||
common.AddRenderContext(req)
|
||||
}
|
||||
@ -63,8 +65,12 @@ func (s *Server) checkNamespace(ctx context.Context, subject, relation, group, r
|
||||
return &authzv1.CheckResponse{Allowed: res.GetAllowed()}, nil
|
||||
}
|
||||
|
||||
// checkTyped performes checks on our typed resources e.g. folder.
|
||||
// checkTyped checks on our typed resources e.g. folder.
|
||||
func (s *Server) checkTyped(ctx context.Context, subject, relation, name string, info common.TypeInfo, store *storeInfo) (*authzv1.CheckResponse, error) {
|
||||
if !info.IsValidRelation(relation) {
|
||||
return &authzv1.CheckResponse{Allowed: false}, nil
|
||||
}
|
||||
|
||||
// Check if subject has direct access to resource
|
||||
res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{
|
||||
StoreId: store.ID,
|
||||
@ -86,27 +92,26 @@ func (s *Server) checkTyped(ctx context.Context, subject, relation, name string,
|
||||
return &authzv1.CheckResponse{Allowed: false}, nil
|
||||
}
|
||||
|
||||
// checkGeneric check our generic "resource" type.
|
||||
// checkGeneric check our generic "resource" type. It checks:
|
||||
// 1. If subject has access as a sub resource for a folder.
|
||||
// 2. If subject has direct access to resource.
|
||||
func (s *Server) checkGeneric(ctx context.Context, subject, relation, group, resource, name, folder string, store *storeInfo) (*authzv1.CheckResponse, error) {
|
||||
groupResource := structpb.NewStringValue(common.FormatGroupResource(group, resource))
|
||||
var (
|
||||
resourceCtx = common.NewResourceContext(group, resource)
|
||||
folderRelation = common.FolderResourceRelation(relation)
|
||||
)
|
||||
|
||||
// Create relation can only exist on namespace or folder level.
|
||||
// So we skip direct resource access check.
|
||||
if relation != common.RelationCreate {
|
||||
// Check if subject has direct access to resource
|
||||
if folder != "" && common.IsFolderResourceRelation(folderRelation) {
|
||||
// Check if subject has access as a sub resource for the folder
|
||||
res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{
|
||||
StoreId: store.ID,
|
||||
AuthorizationModelId: store.ModelID,
|
||||
TupleKey: &openfgav1.CheckRequestTupleKey{
|
||||
User: subject,
|
||||
Relation: relation,
|
||||
Object: common.NewResourceIdent(group, resource, name),
|
||||
},
|
||||
Context: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"requested_group": groupResource,
|
||||
},
|
||||
Relation: common.FolderResourceRelation(relation),
|
||||
Object: common.NewFolderIdent(folder),
|
||||
},
|
||||
Context: resourceCtx,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@ -114,28 +119,24 @@ func (s *Server) checkGeneric(ctx context.Context, subject, relation, group, res
|
||||
}
|
||||
|
||||
if res.GetAllowed() {
|
||||
return &authzv1.CheckResponse{Allowed: true}, nil
|
||||
return &authzv1.CheckResponse{Allowed: res.GetAllowed()}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if folder == "" {
|
||||
if !common.IsResourceRelation(relation) {
|
||||
return &authzv1.CheckResponse{Allowed: false}, nil
|
||||
}
|
||||
|
||||
// Check if subject has access as a sub resource for the folder
|
||||
// Check if subject has direct access to resource
|
||||
res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{
|
||||
StoreId: store.ID,
|
||||
AuthorizationModelId: store.ModelID,
|
||||
TupleKey: &openfgav1.CheckRequestTupleKey{
|
||||
User: subject,
|
||||
Relation: common.FolderResourceRelation(relation),
|
||||
Object: common.NewFolderIdent(folder),
|
||||
},
|
||||
Context: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"requested_group": groupResource,
|
||||
},
|
||||
Relation: relation,
|
||||
Object: common.NewResourceIdent(group, resource, name),
|
||||
},
|
||||
Context: resourceCtx,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"strings"
|
||||
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
|
||||
"github.com/grafana/grafana/pkg/services/authz/zanzana/common"
|
||||
@ -23,15 +22,7 @@ func (s *Server) List(ctx context.Context, r *authzextv1.ListRequest) (*authzext
|
||||
|
||||
relation := common.VerbMapping[r.GetVerb()]
|
||||
|
||||
res, err := s.checkNamespace(
|
||||
ctx,
|
||||
r.GetSubject(),
|
||||
relation,
|
||||
r.GetGroup(),
|
||||
r.GetResource(),
|
||||
store,
|
||||
)
|
||||
|
||||
res, err := s.checkNamespace(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -55,8 +46,12 @@ func (s *Server) listObjects(ctx context.Context, req *openfgav1.ListObjectsRequ
|
||||
}
|
||||
|
||||
func (s *Server) listTyped(ctx context.Context, subject, relation string, info common.TypeInfo, store *storeInfo) (*authzextv1.ListResponse, error) {
|
||||
if !info.IsValidRelation(relation) {
|
||||
return &authzextv1.ListResponse{}, nil
|
||||
}
|
||||
|
||||
// List all resources user has access too
|
||||
listRes, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{
|
||||
res, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{
|
||||
StoreId: store.ID,
|
||||
AuthorizationModelId: store.ModelID,
|
||||
Type: info.Type,
|
||||
@ -68,50 +63,56 @@ func (s *Server) listTyped(ctx context.Context, subject, relation string, info c
|
||||
}
|
||||
|
||||
return &authzextv1.ListResponse{
|
||||
Items: typedObjects(info.Type, listRes.GetObjects()),
|
||||
Items: typedObjects(info.Type, res.GetObjects()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) listGeneric(ctx context.Context, subject, relation, group, resource string, store *storeInfo) (*authzextv1.ListResponse, error) {
|
||||
groupResource := structpb.NewStringValue(common.FormatGroupResource(group, resource))
|
||||
var (
|
||||
resourceCtx = common.NewResourceContext(group, resource)
|
||||
folderRelation = common.FolderResourceRelation(relation)
|
||||
)
|
||||
|
||||
// 1. List all folders subject has access to resource type in
|
||||
folders, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{
|
||||
StoreId: store.ID,
|
||||
AuthorizationModelId: store.ModelID,
|
||||
Type: common.TypeFolder,
|
||||
Relation: common.FolderResourceRelation(relation),
|
||||
User: subject,
|
||||
Context: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"requested_group": groupResource,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var folders []string
|
||||
if common.IsFolderResourceRelation(folderRelation) {
|
||||
res, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{
|
||||
StoreId: store.ID,
|
||||
AuthorizationModelId: store.ModelID,
|
||||
Type: common.TypeFolder,
|
||||
Relation: folderRelation,
|
||||
User: subject,
|
||||
Context: resourceCtx,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
folders = res.GetObjects()
|
||||
}
|
||||
|
||||
// 2. List all resource directly assigned to subject
|
||||
direct, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{
|
||||
StoreId: store.ID,
|
||||
AuthorizationModelId: store.ModelID,
|
||||
Type: common.TypeResource,
|
||||
Relation: relation,
|
||||
User: subject,
|
||||
Context: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"requested_group": groupResource,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var resources []string
|
||||
if common.IsResourceRelation(relation) {
|
||||
res, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{
|
||||
StoreId: store.ID,
|
||||
AuthorizationModelId: store.ModelID,
|
||||
Type: common.TypeResource,
|
||||
Relation: relation,
|
||||
User: subject,
|
||||
Context: resourceCtx,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources = res.GetObjects()
|
||||
}
|
||||
|
||||
return &authzextv1.ListResponse{
|
||||
Folders: folderObject(folders.GetObjects()),
|
||||
Items: directObjects(group, resource, direct.GetObjects()),
|
||||
Folders: folderObject(folders),
|
||||
Items: directObjects(group, resource, resources),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -80,19 +80,19 @@ func setup(t *testing.T, testDB db.DB, cfg *setting.Cfg) *Server {
|
||||
AuthorizationModelId: storeInf.ModelID,
|
||||
Writes: &openfgav1.WriteRequestWrites{
|
||||
TupleKeys: []*openfgav1.TupleKey{
|
||||
common.NewResourceTuple("user:1", "read", dashboardGroup, dashboardResource, "1"),
|
||||
common.NewResourceTuple("user:1", "write", dashboardGroup, dashboardResource, "1"),
|
||||
common.NewNamespaceResourceTuple("user:2", "read", dashboardGroup, dashboardResource),
|
||||
common.NewNamespaceResourceTuple("user:2", "write", dashboardGroup, dashboardResource),
|
||||
common.NewResourceTuple("user:3", "view", dashboardGroup, dashboardResource, "1"),
|
||||
common.NewFolderResourceTuple("user:4", "read", dashboardGroup, dashboardResource, "1"),
|
||||
common.NewFolderResourceTuple("user:4", "read", dashboardGroup, dashboardResource, "3"),
|
||||
common.NewFolderResourceTuple("user:5", "edit", dashboardGroup, dashboardResource, "1"),
|
||||
common.NewFolderTuple("user:6", "read", "1"),
|
||||
common.NewNamespaceResourceTuple("user:7", "read", folderGroup, folderResource),
|
||||
common.NewResourceTuple("user:1", common.RelationGet, dashboardGroup, dashboardResource, "1"),
|
||||
common.NewResourceTuple("user:1", common.RelationUpdate, dashboardGroup, dashboardResource, "1"),
|
||||
common.NewNamespaceResourceTuple("user:2", common.RelationGet, dashboardGroup, dashboardResource),
|
||||
common.NewNamespaceResourceTuple("user:2", common.RelationUpdate, dashboardGroup, dashboardResource),
|
||||
common.NewResourceTuple("user:3", common.RelationSetView, dashboardGroup, dashboardResource, "1"),
|
||||
common.NewFolderResourceTuple("user:4", common.RelationGet, dashboardGroup, dashboardResource, "1"),
|
||||
common.NewFolderResourceTuple("user:4", common.RelationGet, dashboardGroup, dashboardResource, "3"),
|
||||
common.NewFolderResourceTuple("user:5", common.RelationSetEdit, dashboardGroup, dashboardResource, "1"),
|
||||
common.NewFolderTuple("user:6", common.RelationGet, "1"),
|
||||
common.NewNamespaceResourceTuple("user:7", common.RelationGet, folderGroup, folderResource),
|
||||
common.NewFolderParentTuple("5", "4"),
|
||||
common.NewFolderParentTuple("6", "5"),
|
||||
common.NewFolderResourceTuple("user:8", "edit", dashboardGroup, dashboardResource, "5"),
|
||||
common.NewFolderResourceTuple("user:8", common.RelationSetEdit, dashboardGroup, dashboardResource, "5"),
|
||||
common.NewFolderResourceTuple("user:9", "create", dashboardGroup, dashboardResource, "5"),
|
||||
},
|
||||
},
|
||||
|
@ -56,18 +56,14 @@ var resourceTranslations = map[string]resourceTranslation{
|
||||
group: folderGroup,
|
||||
resource: folderResource,
|
||||
mapping: map[string]actionMappig{
|
||||
"folders:read": newMapping(RelationRead),
|
||||
"folders:write": newMapping(RelationWrite),
|
||||
"folders:create": newMapping(RelationCreate),
|
||||
"folders:delete": newMapping(RelationDelete),
|
||||
"folders.permissions:read": newMapping(RelationPermissionsRead),
|
||||
"folders.permissions:write": newMapping(RelationPermissionsWrite),
|
||||
"dashboards:read": newScopedMapping(RelationRead, dashboardGroup, dashboardResource),
|
||||
"dashboards:write": newScopedMapping(RelationWrite, dashboardGroup, dashboardResource),
|
||||
"dashboards:create": newScopedMapping(RelationCreate, dashboardGroup, dashboardResource),
|
||||
"dashboards:delete": newScopedMapping(RelationDelete, dashboardGroup, dashboardResource),
|
||||
"dashboards.permissions:read": newScopedMapping(RelationPermissionsRead, dashboardGroup, dashboardResource),
|
||||
"dashboards.permissions:write": newScopedMapping(RelationPermissionsWrite, dashboardGroup, dashboardResource),
|
||||
"folders:read": newMapping(RelationGet),
|
||||
"folders:write": newMapping(RelationUpdate),
|
||||
"folders:create": newMapping(RelationCreate),
|
||||
"folders:delete": newMapping(RelationDelete),
|
||||
"dashboards:read": newScopedMapping(RelationGet, dashboardGroup, dashboardResource),
|
||||
"dashboards:write": newScopedMapping(RelationUpdate, dashboardGroup, dashboardResource),
|
||||
"dashboards:create": newScopedMapping(RelationCreate, dashboardGroup, dashboardResource),
|
||||
"dashboards:delete": newScopedMapping(RelationDelete, dashboardGroup, dashboardResource),
|
||||
},
|
||||
},
|
||||
KindDashboards: {
|
||||
@ -75,12 +71,10 @@ var resourceTranslations = map[string]resourceTranslation{
|
||||
group: dashboardGroup,
|
||||
resource: dashboardResource,
|
||||
mapping: map[string]actionMappig{
|
||||
"dashboards:read": newMapping(RelationRead),
|
||||
"dashboards:write": newMapping(RelationWrite),
|
||||
"dashboards:create": newMapping(RelationCreate),
|
||||
"dashboards:delete": newMapping(RelationDelete),
|
||||
"dashboards.permissions:read": newMapping(RelationPermissionsRead),
|
||||
"dashboards.permissions:write": newMapping(RelationPermissionsWrite),
|
||||
"dashboards:read": newMapping(RelationGet),
|
||||
"dashboards:write": newMapping(RelationUpdate),
|
||||
"dashboards:create": newMapping(RelationCreate),
|
||||
"dashboards:delete": newMapping(RelationDelete),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -31,28 +31,25 @@ const (
|
||||
RelationSetEdit = common.RelationSetEdit
|
||||
RelationSetAdmin = common.RelationSetAdmin
|
||||
|
||||
RelationRead = common.RelationRead
|
||||
RelationWrite = common.RelationWrite
|
||||
RelationCreate = common.RelationCreate
|
||||
RelationDelete = common.RelationDelete
|
||||
RelationPermissionsRead = common.RelationPermissionsRead
|
||||
RelationPermissionsWrite = common.RelationPermissionsWrite
|
||||
RelationGet = common.RelationGet
|
||||
RelationUpdate = common.RelationUpdate
|
||||
RelationCreate = common.RelationCreate
|
||||
RelationDelete = common.RelationDelete
|
||||
|
||||
RelationFolderResourceSetView = common.RelationFolderResourceSetView
|
||||
RelationFolderResourceSetEdit = common.RelationFolderResourceSetEdit
|
||||
RelationFolderResourceSetAdmin = common.RelationFolderResourceSetAdmin
|
||||
|
||||
RelationFolderResourceRead = common.RelationFolderResourceRead
|
||||
RelationFolderResourceWrite = common.RelationFolderResourceWrite
|
||||
RelationFolderResourceCreate = common.RelationFolderResourceCreate
|
||||
RelationFolderResourceDelete = common.RelationFolderResourceDelete
|
||||
RelationFolderResourcePermissionsRead = common.RelationFolderResourcePermissionsRead
|
||||
RelationFolderResourcePermissionsWrite = common.RelationFolderResourcePermissionsWrite
|
||||
RelationFolderResourceRead = common.RelationFolderResourceGet
|
||||
RelationFolderResourceWrite = common.RelationFolderResourceUpdate
|
||||
RelationFolderResourceCreate = common.RelationFolderResourceCreate
|
||||
RelationFolderResourceDelete = common.RelationFolderResourceDelete
|
||||
)
|
||||
|
||||
var (
|
||||
FolderRelations = common.FolderRelations
|
||||
ResourceRelations = common.ResourceRelations
|
||||
RelationsFolder = common.RelationsFolder
|
||||
RelationsFolderResource = common.RelationsFolder
|
||||
RelationsResouce = common.RelationsResource
|
||||
)
|
||||
|
||||
const (
|
||||
|
Loading…
Reference in New Issue
Block a user