diff --git a/pkg/services/accesscontrol/dualwrite/reconciler.go b/pkg/services/accesscontrol/dualwrite/reconciler.go index 2be38d90aac..930327340fb 100644 --- a/pkg/services/accesscontrol/dualwrite/reconciler.go +++ b/pkg/services/accesscontrol/dualwrite/reconciler.go @@ -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( diff --git a/pkg/services/authz/zanzana/common/info.go b/pkg/services/authz/zanzana/common/info.go index 0517a0be84a..5ec718d9933 100644 --- a/pkg/services/authz/zanzana/common/info.go +++ b/pkg/services/authz/zanzana/common/info.go @@ -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, } diff --git a/pkg/services/authz/zanzana/common/tuple.go b/pkg/services/authz/zanzana/common/tuple.go index 294427631a8..bf7d7a1fe33 100644 --- a/pkg/services/authz/zanzana/common/tuple.go +++ b/pkg/services/authz/zanzana/common/tuple.go @@ -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)), + }, + } +} diff --git a/pkg/services/authz/zanzana/schema/schema_core.fga b/pkg/services/authz/zanzana/schema/schema_core.fga index 86a3a212361..d0a2ff71cc9 100644 --- a/pkg/services/authz/zanzana/schema/schema_core.fga +++ b/pkg/services/authz/zanzana/schema/schema_core.fga @@ -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 diff --git a/pkg/services/authz/zanzana/schema/schema_folder.fga b/pkg/services/authz/zanzana/schema/schema_folder.fga index 117bff01199..d3cbb26bb64 100644 --- a/pkg/services/authz/zanzana/schema/schema_folder.fga +++ b/pkg/services/authz/zanzana/schema/schema_folder.fga @@ -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 diff --git a/pkg/services/authz/zanzana/schema/schema_resource.fga b/pkg/services/authz/zanzana/schema/schema_resource.fga index 9e90bf0472b..9ef4e3ca79e 100644 --- a/pkg/services/authz/zanzana/schema/schema_resource.fga +++ b/pkg/services/authz/zanzana/schema/schema_resource.fga @@ -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 diff --git a/pkg/services/authz/zanzana/server/server_capabilities.go b/pkg/services/authz/zanzana/server/server_capabilities.go index 4d63841137d..3d3974cd9a8 100644 --- a/pkg/services/authz/zanzana/server/server_capabilities.go +++ b/pkg/services/authz/zanzana/server/server_capabilities.go @@ -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 diff --git a/pkg/services/authz/zanzana/server/server_capabilities_test.go b/pkg/services/authz/zanzana/server/server_capabilities_test.go index c1d43d8a61a..051ff2c6ed1 100644 --- a/pkg/services/authz/zanzana/server/server_capabilities_test.go +++ b/pkg/services/authz/zanzana/server/server_capabilities_test.go @@ -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()) }) } diff --git a/pkg/services/authz/zanzana/server/server_check.go b/pkg/services/authz/zanzana/server/server_check.go index 792a9561760..c97e7e808bc 100644 --- a/pkg/services/authz/zanzana/server/server_check.go +++ b/pkg/services/authz/zanzana/server/server_check.go @@ -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 { diff --git a/pkg/services/authz/zanzana/server/server_list.go b/pkg/services/authz/zanzana/server/server_list.go index 513ebb2490c..c1ee3aaa0bc 100644 --- a/pkg/services/authz/zanzana/server/server_list.go +++ b/pkg/services/authz/zanzana/server/server_list.go @@ -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 } diff --git a/pkg/services/authz/zanzana/server/server_test.go b/pkg/services/authz/zanzana/server/server_test.go index 595da83ad0a..4327c55d602 100644 --- a/pkg/services/authz/zanzana/server/server_test.go +++ b/pkg/services/authz/zanzana/server/server_test.go @@ -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"), }, }, diff --git a/pkg/services/authz/zanzana/translations.go b/pkg/services/authz/zanzana/translations.go index 8432614c7db..79e103cb5ee 100644 --- a/pkg/services/authz/zanzana/translations.go +++ b/pkg/services/authz/zanzana/translations.go @@ -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), }, }, } diff --git a/pkg/services/authz/zanzana/zanzana.go b/pkg/services/authz/zanzana/zanzana.go index 512f1e6a07f..bb81103b86a 100644 --- a/pkg/services/authz/zanzana/zanzana.go +++ b/pkg/services/authz/zanzana/zanzana.go @@ -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 (