mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Zanzana: fix generic schema (#95648)
* Change schema so that resource checks on a folder walks the tree
This commit is contained in:
parent
4e1f0dadbd
commit
dfa8f786d2
@ -110,7 +110,7 @@ func folderTreeCollector2(store db.DB) legacyTupleCollector {
|
|||||||
// managedPermissionsCollector collects managed permissions into provided tuple map.
|
// managedPermissionsCollector collects managed permissions into provided tuple map.
|
||||||
// It will only store actions that are supported by our schema. Managed permissions can
|
// It will only store actions that are supported by our schema. Managed permissions can
|
||||||
// be directly mapped to user/team/role without having to write an intermediate role.
|
// be directly mapped to user/team/role without having to write an intermediate role.
|
||||||
func managedPermissionsCollector2(store db.DB) legacyTupleCollector {
|
func managedPermissionsCollector2(store db.DB, kind string) legacyTupleCollector {
|
||||||
return func(ctx context.Context) (map[string]map[string]*openfgav1.TupleKey, error) {
|
return func(ctx context.Context) (map[string]map[string]*openfgav1.TupleKey, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT u.uid as user_uid, t.uid as team_uid, p.action, p.kind, p.identifier, r.org_id
|
SELECT u.uid as user_uid, t.uid as team_uid, p.action, p.kind, p.identifier, r.org_id
|
||||||
@ -122,6 +122,7 @@ func managedPermissionsCollector2(store db.DB) legacyTupleCollector {
|
|||||||
LEFT JOIN team t ON tr.team_id = t.id
|
LEFT JOIN team t ON tr.team_id = t.id
|
||||||
LEFT JOIN builtin_role br ON r.id = br.role_id
|
LEFT JOIN builtin_role br ON r.id = br.role_id
|
||||||
WHERE r.name LIKE 'managed:%'
|
WHERE r.name LIKE 'managed:%'
|
||||||
|
AND p.kind = ?
|
||||||
`
|
`
|
||||||
type Permission struct {
|
type Permission struct {
|
||||||
RoleName string `xorm:"role_name"`
|
RoleName string `xorm:"role_name"`
|
||||||
@ -135,7 +136,7 @@ func managedPermissionsCollector2(store db.DB) legacyTupleCollector {
|
|||||||
|
|
||||||
var permissions []Permission
|
var permissions []Permission
|
||||||
err := store.WithDbSession(ctx, func(sess *db.Session) error {
|
err := store.WithDbSession(ctx, func(sess *db.Session) error {
|
||||||
return sess.SQL(query).Find(&permissions)
|
return sess.SQL(query, kind).Find(&permissions)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -164,6 +165,19 @@ func managedPermissionsCollector2(store db.DB) legacyTupleCollector {
|
|||||||
tuples[tuple.Object] = make(map[string]*openfgav1.TupleKey)
|
tuples[tuple.Object] = make(map[string]*openfgav1.TupleKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For resource actions on folders we need to merge the tuples into one with combined
|
||||||
|
// group_resources.
|
||||||
|
if zanzana.IsFolderResourceTuple(tuple) {
|
||||||
|
key := tupleStringWithoutCondition(tuple)
|
||||||
|
if t, ok := tuples[tuple.Object][key]; ok {
|
||||||
|
zanzana.MergeFolderResourceTuples(t, tuple)
|
||||||
|
} else {
|
||||||
|
tuples[tuple.Object][key] = tuple
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
tuples[tuple.Object][tuple.String()] = tuple
|
tuples[tuple.Object][tuple.String()] = tuple
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,6 +185,14 @@ func managedPermissionsCollector2(store db.DB) legacyTupleCollector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tupleStringWithoutCondition(tuple *openfgav1.TupleKey) string {
|
||||||
|
c := tuple.Condition
|
||||||
|
tuple.Condition = nil
|
||||||
|
s := tuple.String()
|
||||||
|
tuple.Condition = c
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func zanzanaCollector(client zanzana.Client, relations []string) zanzanaTupleCollector {
|
func zanzanaCollector(client zanzana.Client, relations []string) zanzanaTupleCollector {
|
||||||
return func(ctx context.Context, client zanzana.Client, object string) (map[string]*openfgav1.TupleKey, error) {
|
return func(ctx context.Context, client zanzana.Client, object string) (map[string]*openfgav1.TupleKey, error) {
|
||||||
// list will use continuation token to collect all tuples for object and relation
|
// list will use continuation token to collect all tuples for object and relation
|
||||||
@ -213,9 +235,13 @@ func zanzanaCollector(client zanzana.Client, relations []string) zanzanaTupleCol
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, t := range tuples {
|
for _, t := range tuples {
|
||||||
|
if zanzana.IsFolderResourceTuple(t.Key) {
|
||||||
|
out[tupleStringWithoutCondition(t.Key)] = t.Key
|
||||||
|
} else {
|
||||||
out[t.Key.String()] = t.Key
|
out[t.Key.String()] = t.Key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
@ -72,8 +72,14 @@ func NewZanzanaReconciler(client zanzana.Client, store db.DB, lock *serverlock.S
|
|||||||
client,
|
client,
|
||||||
),
|
),
|
||||||
newResourceReconciler(
|
newResourceReconciler(
|
||||||
"managed permissison",
|
"managed folder permissions",
|
||||||
managedPermissionsCollector2(store),
|
managedPermissionsCollector2(store, zanzana.KindFolders),
|
||||||
|
zanzanaCollector(client, zanzana.Folder2Relations),
|
||||||
|
client,
|
||||||
|
),
|
||||||
|
newResourceReconciler(
|
||||||
|
"managed dashboard permissions",
|
||||||
|
managedPermissionsCollector2(store, zanzana.KindDashboards),
|
||||||
zanzanaCollector(client, zanzana.ResourceRelations),
|
zanzanaCollector(client, zanzana.ResourceRelations),
|
||||||
client,
|
client,
|
||||||
),
|
),
|
||||||
|
@ -4,8 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
|
||||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
||||||
)
|
)
|
||||||
|
|
||||||
// legacyTupleCollector collects tuples groupd by object and tupleKey
|
// legacyTupleCollector collects tuples groupd by object and tupleKey
|
||||||
@ -47,13 +48,25 @@ func (r resourceReconciler) reconcile(ctx context.Context) error {
|
|||||||
|
|
||||||
// 3. Check if tuples from grafana db exists in zanzana and if not add them to writes
|
// 3. Check if tuples from grafana db exists in zanzana and if not add them to writes
|
||||||
for key, t := range tuples {
|
for key, t := range tuples {
|
||||||
_, ok := zanzanaTuples[key]
|
stored, ok := zanzanaTuples[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
writes = append(writes, t)
|
writes = append(writes, t)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. For folder resource tuples we also need to compare the stored group_resources
|
||||||
|
if zanzana.IsFolderResourceTuple(t) && t.String() != stored.String() {
|
||||||
|
deletes = append(deletes, &openfgav1.TupleKeyWithoutCondition{
|
||||||
|
User: t.User,
|
||||||
|
Relation: t.Relation,
|
||||||
|
Object: t.Object,
|
||||||
|
})
|
||||||
|
|
||||||
|
writes = append(writes, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Check if tuple from zanzana don't exists in grafana db, if not add them to deletes.
|
// 5. Check if tuple from zanzana don't exists in grafana db, if not add them to deletes.
|
||||||
for key, tuple := range zanzanaTuples {
|
for key, tuple := range zanzanaTuples {
|
||||||
_, ok := tuples[key]
|
_, ok := tuples[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -70,19 +83,6 @@ func (r resourceReconciler) reconcile(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: batch them together
|
|
||||||
if len(writes) > 0 {
|
|
||||||
err := batch(writes, 100, func(items []*openfgav1.TupleKey) error {
|
|
||||||
return r.client.Write(ctx, &openfgav1.WriteRequest{
|
|
||||||
Writes: &openfgav1.WriteRequestWrites{TupleKeys: items},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(deletes) > 0 {
|
if len(deletes) > 0 {
|
||||||
err := batch(deletes, 100, func(items []*openfgav1.TupleKeyWithoutCondition) error {
|
err := batch(deletes, 100, func(items []*openfgav1.TupleKeyWithoutCondition) error {
|
||||||
return r.client.Write(ctx, &openfgav1.WriteRequest{
|
return r.client.Write(ctx, &openfgav1.WriteRequest{
|
||||||
@ -95,5 +95,17 @@ func (r resourceReconciler) reconcile(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(writes) > 0 {
|
||||||
|
err := batch(writes, 100, func(items []*openfgav1.TupleKey) error {
|
||||||
|
return r.client.Write(ctx, &openfgav1.WriteRequest{
|
||||||
|
Writes: &openfgav1.WriteRequestWrites{TupleKeys: items},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,14 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
resourceType = "resource"
|
resourceType = "resource"
|
||||||
|
resourceTypeFolder = "folder2"
|
||||||
namespaceType = "namespace"
|
namespaceType = "namespace"
|
||||||
folderResourceType = "folder_resource"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func FolderResourceRelation(relation string) string {
|
||||||
|
return fmt.Sprintf("%s_%s", resourceType, relation)
|
||||||
|
}
|
||||||
|
|
||||||
func NewTypedIdent(typ string, name string) string {
|
func NewTypedIdent(typ string, name string) string {
|
||||||
return fmt.Sprintf("%s:%s", typ, name)
|
return fmt.Sprintf("%s:%s", typ, name)
|
||||||
}
|
}
|
||||||
@ -21,8 +25,8 @@ func NewResourceIdent(group, resource, name string) string {
|
|||||||
return fmt.Sprintf("%s:%s/%s", resourceType, FormatGroupResource(group, resource), name)
|
return fmt.Sprintf("%s:%s/%s", resourceType, FormatGroupResource(group, resource), name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFolderResourceIdent(group, resource, folder string) string {
|
func NewFolderIdent(name string) string {
|
||||||
return fmt.Sprintf("%s:%s/%s", folderResourceType, FormatGroupResource(group, resource), folder)
|
return fmt.Sprintf("folder2:%s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNamespaceResourceIdent(group, resource string) string {
|
func NewNamespaceResourceIdent(group, resource string) string {
|
||||||
@ -52,13 +56,15 @@ func NewResourceTuple(subject, relation, group, resource, name string) *openfgav
|
|||||||
func NewFolderResourceTuple(subject, relation, group, resource, folder string) *openfgav1.TupleKey {
|
func NewFolderResourceTuple(subject, relation, group, resource, folder string) *openfgav1.TupleKey {
|
||||||
return &openfgav1.TupleKey{
|
return &openfgav1.TupleKey{
|
||||||
User: subject,
|
User: subject,
|
||||||
Relation: relation,
|
Relation: FolderResourceRelation(relation),
|
||||||
Object: NewFolderResourceIdent(group, resource, folder),
|
Object: NewFolderIdent(folder),
|
||||||
Condition: &openfgav1.RelationshipCondition{
|
Condition: &openfgav1.RelationshipCondition{
|
||||||
Name: "group_filter",
|
Name: "folder_group_filter",
|
||||||
Context: &structpb.Struct{
|
Context: &structpb.Struct{
|
||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
"resource_group": structpb.NewStringValue(FormatGroupResource(group, resource)),
|
"group_resources": structpb.NewListValue(&structpb.ListValue{
|
||||||
|
Values: []*structpb.Value{structpb.NewStringValue(FormatGroupResource(group, resource))},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -73,6 +79,14 @@ func NewNamespaceResourceTuple(subject, relation, group, resource string) *openf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewFolderParentTuple(folder, parent string) *openfgav1.TupleKey {
|
||||||
|
return &openfgav1.TupleKey{
|
||||||
|
Object: NewFolderIdent(folder),
|
||||||
|
Relation: "parent",
|
||||||
|
User: NewFolderIdent(parent),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewFolderTuple(subject, relation, name string) *openfgav1.TupleKey {
|
func NewFolderTuple(subject, relation, name string) *openfgav1.TupleKey {
|
||||||
return NewTypedTuple("folder2", subject, relation, name)
|
return NewTypedTuple("folder2", subject, relation, name)
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,9 @@ type folder2
|
|||||||
define parent: [folder2]
|
define parent: [folder2]
|
||||||
|
|
||||||
# Action sets
|
# Action sets
|
||||||
define view: [user, team#member, role#assignee] or edit
|
define view: [user, team#member, role#assignee] or edit or view from parent
|
||||||
define edit: [user, team#member, role#assignee] or admin
|
define edit: [user, team#member, role#assignee] or admin or edit from parent
|
||||||
define admin: [user, team#member, role#assignee]
|
define admin: [user, team#member, role#assignee] or admin from parent
|
||||||
|
|
||||||
define read: [user, team#member, role#assignee] or view or read from parent
|
define read: [user, team#member, role#assignee] or view or read from parent
|
||||||
define create: [user, team#member, role#assignee] or edit or create from parent
|
define create: [user, team#member, role#assignee] or edit or create from parent
|
||||||
@ -29,18 +29,16 @@ type folder2
|
|||||||
define permissions_read: [user, team#member, role#assignee] or admin or permissions_read from parent
|
define permissions_read: [user, team#member, role#assignee] or admin or permissions_read from parent
|
||||||
define permissions_write: [user, team#member, role#assignee] or admin or permissions_write from parent
|
define permissions_write: [user, team#member, role#assignee] or admin or permissions_write from parent
|
||||||
|
|
||||||
type folder_resource
|
define resource_view: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_view from parent
|
||||||
relations
|
define resource_edit: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_edit from parent
|
||||||
define view: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or edit
|
define resource_admin: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin from parent
|
||||||
define edit: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin
|
|
||||||
define admin: [user with group_filter, team#member with group_filter, role#assignee with group_filter]
|
|
||||||
|
|
||||||
define read: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or view
|
define resource_read: [user 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 create: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or edit
|
define resource_create: [user 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 write: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or edit
|
define resource_write: [user 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 delete: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or edit
|
define resource_delete: [user 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 permissions_read: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin
|
define resource_permissions_read: [user 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 permissions_write: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin
|
define resource_permissions_write: [user 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
|
type resource
|
||||||
relations
|
relations
|
||||||
@ -55,7 +53,12 @@ type resource
|
|||||||
define permissions_read: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin
|
define permissions_read: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin
|
||||||
define permissions_write: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin
|
define permissions_write: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin
|
||||||
|
|
||||||
|
|
||||||
condition group_filter(requested_group: string, resource_group: string) {
|
condition group_filter(requested_group: string, resource_group: string) {
|
||||||
resource_group == requested_group
|
resource_group == requested_group
|
||||||
}
|
}
|
||||||
|
|
||||||
|
condition folder_group_filter(requested_group: string, group_resources: list<string>) {
|
||||||
|
requested_group in group_resources
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
resourceType = "resource"
|
resourceType = "resource"
|
||||||
namespaceType = "namespace"
|
namespaceType = "namespace"
|
||||||
folderResourceType = "folder_resource"
|
folderTypePrefix = "folder2:"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ authzv1.AuthzServiceServer = (*Server)(nil)
|
var _ authzv1.AuthzServiceServer = (*Server)(nil)
|
||||||
|
@ -113,8 +113,8 @@ func (s *Server) checkGeneric(ctx context.Context, r *authzv1.CheckRequest) (*au
|
|||||||
AuthorizationModelId: s.modelID,
|
AuthorizationModelId: s.modelID,
|
||||||
TupleKey: &openfgav1.CheckRequestTupleKey{
|
TupleKey: &openfgav1.CheckRequestTupleKey{
|
||||||
User: r.GetSubject(),
|
User: r.GetSubject(),
|
||||||
Relation: relation,
|
Relation: common.FolderResourceRelation(relation),
|
||||||
Object: common.NewFolderResourceIdent(r.GetGroup(), r.GetResource(), r.GetFolder()),
|
Object: common.NewFolderIdent(r.GetFolder()),
|
||||||
},
|
},
|
||||||
Context: &structpb.Struct{
|
Context: &structpb.Struct{
|
||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
|
@ -93,4 +93,18 @@ func testCheck(t *testing.T, server *Server) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, res.GetAllowed())
|
assert.True(t, res.GetAllowed())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("user:8 should be able to read all resoruce:dashboard.grafana.app/dashboar in folder 6 through folder 5", func(t *testing.T) {
|
||||||
|
res, err := server.Check(context.Background(), newRead("user:8", dashboardGroup, dashboardResource, "6", "10"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, res.GetAllowed())
|
||||||
|
|
||||||
|
res, err = server.Check(context.Background(), newRead("user:8", dashboardGroup, dashboardResource, "5", "11"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, res.GetAllowed())
|
||||||
|
|
||||||
|
res, err = server.Check(context.Background(), newRead("user:8", folderGroup, folderResource, "4", "12"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, res.GetAllowed())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -85,8 +85,8 @@ func (s *Server) listGeneric(ctx context.Context, r *authzextv1.ListRequest) (*a
|
|||||||
folders, err := s.openfga.ListObjects(ctx, &openfgav1.ListObjectsRequest{
|
folders, err := s.openfga.ListObjects(ctx, &openfgav1.ListObjectsRequest{
|
||||||
StoreId: s.storeID,
|
StoreId: s.storeID,
|
||||||
AuthorizationModelId: s.modelID,
|
AuthorizationModelId: s.modelID,
|
||||||
Type: "folder_resource",
|
Type: "folder2",
|
||||||
Relation: relation,
|
Relation: common.FolderResourceRelation(relation),
|
||||||
User: r.GetSubject(),
|
User: r.GetSubject(),
|
||||||
Context: &structpb.Struct{
|
Context: &structpb.Struct{
|
||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
@ -138,9 +138,8 @@ func directObjects(group, resource string, objects []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func folderObject(group, resource string, objects []string) []string {
|
func folderObject(group, resource string, objects []string) []string {
|
||||||
prefix := fmt.Sprintf("%s:%s/%s/", folderResourceType, group, resource)
|
|
||||||
for i := range objects {
|
for i := range objects {
|
||||||
objects[i] = strings.TrimPrefix(objects[i], prefix)
|
objects[i] = strings.TrimPrefix(objects[i], folderTypePrefix)
|
||||||
}
|
}
|
||||||
return objects
|
return objects
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,9 @@ func setup(t *testing.T, testDB db.DB, cfg *setting.Cfg) *Server {
|
|||||||
common.NewFolderResourceTuple("user:5", "view", dashboardGroup, dashboardResource, "1"),
|
common.NewFolderResourceTuple("user:5", "view", dashboardGroup, dashboardResource, "1"),
|
||||||
common.NewFolderTuple("user:6", "read", "1"),
|
common.NewFolderTuple("user:6", "read", "1"),
|
||||||
common.NewNamespaceResourceTuple("user:7", "read", folderGroup, folderResource),
|
common.NewNamespaceResourceTuple("user:7", "read", folderGroup, folderResource),
|
||||||
|
common.NewFolderParentTuple("5", "4"),
|
||||||
|
common.NewFolderParentTuple("6", "5"),
|
||||||
|
common.NewFolderResourceTuple("user:8", "read", dashboardGroup, dashboardResource, "5"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -35,9 +35,18 @@ const (
|
|||||||
RelationDelete string = "delete"
|
RelationDelete string = "delete"
|
||||||
RelationPermissionsRead string = "permissions_read"
|
RelationPermissionsRead string = "permissions_read"
|
||||||
RelationPermissionsWrite string = "permissions_write"
|
RelationPermissionsWrite string = "permissions_write"
|
||||||
|
|
||||||
|
FolderResourceRelationAdmin string = "resource_admin"
|
||||||
|
FolderResourceRelationRead string = "resource_read"
|
||||||
|
FolderResourceRelationWrite string = "resource_write"
|
||||||
|
FolderResourceRelationCreate string = "resource_create"
|
||||||
|
FolderResourceRelationDelete string = "resource_delete"
|
||||||
|
FolderResourceRelationPermissionsRead string = "resource_permissions_read"
|
||||||
|
FolderResourceRelationPermissionsWrite string = "resource_permissions_write"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ResourceRelations = []string{RelationRead, RelationWrite, RelationCreate, RelationDelete, RelationPermissionsRead, RelationPermissionsWrite}
|
var ResourceRelations = []string{RelationRead, RelationWrite, RelationCreate, RelationDelete, RelationPermissionsRead, RelationPermissionsWrite}
|
||||||
|
var Folder2Relations = append(ResourceRelations, FolderResourceRelationRead, FolderResourceRelationWrite, FolderResourceRelationCreate, FolderResourceRelationDelete, FolderResourceRelationPermissionsRead, FolderResourceRelationPermissionsWrite)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
KindOrg string = "org"
|
KindOrg string = "org"
|
||||||
@ -130,6 +139,16 @@ func TranslateToResourceTuple(subject string, action, kind, name string) (*openf
|
|||||||
return common.NewTypedTuple(translation.typ, subject, m.relation, name), true
|
return common.NewTypedTuple(translation.typ, subject, m.relation, name), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsFolderResourceTuple(t *openfgav1.TupleKey) bool {
|
||||||
|
return strings.HasPrefix(t.Object, TypeFolder2) && strings.HasPrefix(t.Relation, "resource_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeFolderResourceTuples(a, b *openfgav1.TupleKey) {
|
||||||
|
va := a.Condition.Context.Fields["group_resources"]
|
||||||
|
vb := b.Condition.Context.Fields["group_resources"]
|
||||||
|
va.GetListValue().Values = append(va.GetListValue().Values, vb.GetListValue().Values...)
|
||||||
|
}
|
||||||
|
|
||||||
func TranslateToOrgTuple(user string, action string, orgID int64) (*openfgav1.TupleKey, bool) {
|
func TranslateToOrgTuple(user string, action string, orgID int64) (*openfgav1.TupleKey, bool) {
|
||||||
typeTranslation, ok := actionKindTranslations[KindOrg]
|
typeTranslation, ok := actionKindTranslations[KindOrg]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
Loading…
Reference in New Issue
Block a user