mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Zanzana: Support sub resources (#98201)
* Create and use common ResourceInfo struct * Add support for formatting group resource with subresource * Add initial support for handling subresource * Add test for checking subresource for generic resource * Bump authlib
This commit is contained in:
parent
9e3094d68e
commit
9ed4bf3cd2
2
go.mod
2
go.mod
@ -74,7 +74,7 @@ require (
|
||||
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
|
||||
github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/alerting v0.0.0-20241211182001-0f317eb6b2f7 // @grafana/alerting-backend
|
||||
github.com/grafana/authlib v0.0.0-20241220142117-e573433309e8 // @grafana/identity-access-team
|
||||
github.com/grafana/authlib v0.0.0-20250107102310-3edeb9fc9d5f // @grafana/identity-access-team
|
||||
github.com/grafana/authlib/claims v0.0.0-20241202085737-df90af04f335 // @grafana/identity-access-team
|
||||
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d // @grafana/dataviz-squad
|
||||
github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code
|
||||
|
4
go.sum
4
go.sum
@ -2278,8 +2278,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grafana/alerting v0.0.0-20241211182001-0f317eb6b2f7 h1:VGLUQ2mwzlF1NGwTxpSfv1RnuOsDlNh/NT5KRvhZ0sQ=
|
||||
github.com/grafana/alerting v0.0.0-20241211182001-0f317eb6b2f7/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU=
|
||||
github.com/grafana/authlib v0.0.0-20241220142117-e573433309e8 h1:ctiV5lFI0zdccm4uRLSHIpdUukLvDNc9KGtYKal2eXE=
|
||||
github.com/grafana/authlib v0.0.0-20241220142117-e573433309e8/go.mod h1:x7df73G3xuSD35Xv9cjaMLyPJCgM9Z/Wj5ISouoAfiI=
|
||||
github.com/grafana/authlib v0.0.0-20250107102310-3edeb9fc9d5f h1:BcgUTu26JtOudfpQ8LoLpZNV2CdMEyhLUZweCUgETZw=
|
||||
github.com/grafana/authlib v0.0.0-20250107102310-3edeb9fc9d5f/go.mod h1:x7df73G3xuSD35Xv9cjaMLyPJCgM9Z/Wj5ISouoAfiI=
|
||||
github.com/grafana/authlib/claims v0.0.0-20241202085737-df90af04f335 h1:3DHH81RJCi8Bcgn2MdBh7vgWUshmAFjZzBCVuxiQ0uk=
|
||||
github.com/grafana/authlib/claims v0.0.0-20241202085737-df90af04f335/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
|
||||
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk=
|
||||
|
@ -656,6 +656,7 @@ github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
github.com/grafana/authlib v0.0.0-20241220142117-e573433309e8/go.mod h1:x7df73G3xuSD35Xv9cjaMLyPJCgM9Z/Wj5ISouoAfiI=
|
||||
github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2 h1:qhugDMdQ4Vp68H0tp/0iN17DM2ehRo1rLEdOFe/gB8I=
|
||||
github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2/go.mod h1:w/aiO1POVIeXUQyl0VQSZjl5OAGDTL5aX+4v0RA1tcw=
|
||||
github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak=
|
||||
|
@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/apimachinery
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/grafana/authlib v0.0.0-20241220142117-e573433309e8 // @grafana/identity-access-team
|
||||
github.com/grafana/authlib v0.0.0-20250107102310-3edeb9fc9d5f // @grafana/identity-access-team
|
||||
github.com/grafana/authlib/claims v0.0.0-20241202085737-df90af04f335 // @grafana/identity-access-team
|
||||
github.com/stretchr/testify v1.10.0
|
||||
k8s.io/apimachinery v0.32.0
|
||||
|
@ -28,8 +28,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/grafana/authlib v0.0.0-20241220142117-e573433309e8 h1:ctiV5lFI0zdccm4uRLSHIpdUukLvDNc9KGtYKal2eXE=
|
||||
github.com/grafana/authlib v0.0.0-20241220142117-e573433309e8/go.mod h1:x7df73G3xuSD35Xv9cjaMLyPJCgM9Z/Wj5ISouoAfiI=
|
||||
github.com/grafana/authlib v0.0.0-20250107102310-3edeb9fc9d5f h1:BcgUTu26JtOudfpQ8LoLpZNV2CdMEyhLUZweCUgETZw=
|
||||
github.com/grafana/authlib v0.0.0-20250107102310-3edeb9fc9d5f/go.mod h1:x7df73G3xuSD35Xv9cjaMLyPJCgM9Z/Wj5ISouoAfiI=
|
||||
github.com/grafana/authlib/claims v0.0.0-20241202085737-df90af04f335 h1:3DHH81RJCi8Bcgn2MdBh7vgWUshmAFjZzBCVuxiQ0uk=
|
||||
github.com/grafana/authlib/claims v0.0.0-20241202085737-df90af04f335/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
|
@ -394,6 +394,19 @@ func rolePermissionsCollector(store db.DB) legacyTupleCollector {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -1,46 +1,122 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
authzv1 "github.com/grafana/authlib/authz/proto/v1"
|
||||
folderalpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
||||
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
|
||||
)
|
||||
|
||||
type TypeInfo struct {
|
||||
type typeInfo struct {
|
||||
Type string
|
||||
Relations []string
|
||||
}
|
||||
|
||||
func (t TypeInfo) IsValidRelation(relation string) bool {
|
||||
return isValidRelation(relation, t.Relations)
|
||||
}
|
||||
|
||||
var typedResources = map[string]TypeInfo{
|
||||
var typedResources = map[string]typeInfo{
|
||||
FormatGroupResource(
|
||||
folderalpha1.FolderResourceInfo.GroupResource().Group,
|
||||
folderalpha1.FolderResourceInfo.GroupResource().Resource,
|
||||
"",
|
||||
): {Type: "folder", Relations: RelationsFolder},
|
||||
}
|
||||
|
||||
func GetTypeInfo(group, resource string) (TypeInfo, bool) {
|
||||
info, ok := typedResources[FormatGroupResource(group, resource)]
|
||||
func getTypeInfo(group, resource string) (typeInfo, bool) {
|
||||
info, ok := typedResources[FormatGroupResource(group, resource, "")]
|
||||
return info, ok
|
||||
}
|
||||
|
||||
var VerbMapping = map[string]string{
|
||||
utils.VerbGet: RelationGet,
|
||||
utils.VerbList: RelationGet,
|
||||
utils.VerbWatch: RelationGet,
|
||||
utils.VerbCreate: RelationCreate,
|
||||
utils.VerbUpdate: RelationUpdate,
|
||||
utils.VerbPatch: RelationUpdate,
|
||||
utils.VerbDelete: RelationDelete,
|
||||
utils.VerbDeleteCollection: RelationDelete,
|
||||
func NewResourceInfoFromCheck(r *authzv1.CheckRequest) ResourceInfo {
|
||||
if info, ok := getTypeInfo(r.GetGroup(), r.GetResource()); ok {
|
||||
return newResource(info.Type, r.GetGroup(), r.GetResource(), r.GetName(), r.GetFolder(), r.GetSubresource(), info.Relations)
|
||||
}
|
||||
return newResource(TypeResource, r.GetGroup(), r.GetResource(), r.GetName(), r.GetFolder(), r.GetSubresource(), RelationsResource)
|
||||
}
|
||||
|
||||
var RelationToVerbMapping = map[string]string{
|
||||
RelationGet: utils.VerbGet,
|
||||
RelationCreate: utils.VerbCreate,
|
||||
RelationUpdate: utils.VerbUpdate,
|
||||
RelationDelete: utils.VerbDelete,
|
||||
func NewResourceInfoFromBatchItem(i *authzextv1.BatchCheckItem) ResourceInfo {
|
||||
if info, ok := getTypeInfo(i.GetGroup(), i.GetResource()); ok {
|
||||
return newResource(info.Type, i.GetGroup(), i.GetResource(), i.GetName(), i.GetFolder(), i.GetSubresource(), info.Relations)
|
||||
}
|
||||
return newResource(TypeResource, i.GetGroup(), i.GetResource(), i.GetName(), i.GetFolder(), i.GetSubresource(), RelationsResource)
|
||||
}
|
||||
|
||||
func NewResourceInfoFromList(r *authzv1.ListRequest) ResourceInfo {
|
||||
if info, ok := getTypeInfo(r.GetGroup(), r.GetResource()); ok {
|
||||
return newResource(info.Type, r.GetGroup(), r.GetResource(), "", "", r.GetSubresource(), info.Relations)
|
||||
}
|
||||
return newResource(TypeResource, r.GetGroup(), r.GetResource(), "", "", r.GetSubresource(), RelationsResource)
|
||||
}
|
||||
|
||||
func newResource(typ string, group, resource, name, folder, subresource string, relations []string) ResourceInfo {
|
||||
return ResourceInfo{
|
||||
typ: typ,
|
||||
group: group,
|
||||
resource: resource,
|
||||
name: name,
|
||||
folder: folder,
|
||||
subresource: subresource,
|
||||
relations: relations,
|
||||
}
|
||||
}
|
||||
|
||||
type ResourceInfo struct {
|
||||
typ string
|
||||
group string
|
||||
resource string
|
||||
name string
|
||||
folder string
|
||||
subresource string
|
||||
relations []string
|
||||
}
|
||||
|
||||
func (r ResourceInfo) GroupResource() string {
|
||||
return FormatGroupResource(r.group, r.resource, r.subresource)
|
||||
}
|
||||
|
||||
func (r ResourceInfo) GroupResourceIdent() string {
|
||||
return NewGroupResourceIdent(r.group, r.resource, r.subresource)
|
||||
}
|
||||
|
||||
func (r ResourceInfo) ResourceIdent() string {
|
||||
if r.name == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if r.IsGeneric() {
|
||||
return NewResourceIdent(r.group, r.resource, r.subresource, r.name)
|
||||
}
|
||||
|
||||
return NewTypedIdent(r.typ, r.name)
|
||||
}
|
||||
|
||||
func (r ResourceInfo) FolderIdent() string {
|
||||
if r.folder == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return NewFolderIdent(r.folder)
|
||||
}
|
||||
|
||||
func (r ResourceInfo) IsGeneric() bool {
|
||||
return r.typ == TypeResource
|
||||
}
|
||||
|
||||
func (r ResourceInfo) Type() string {
|
||||
return r.typ
|
||||
}
|
||||
|
||||
func (r ResourceInfo) Context() *structpb.Struct {
|
||||
if !r.IsGeneric() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"requested_group": structpb.NewStringValue(r.GroupResource()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r ResourceInfo) IsValidRelation(relation string) bool {
|
||||
return isValidRelation(relation, r.relations)
|
||||
}
|
||||
|
@ -6,10 +6,13 @@ import (
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
dashboardalpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
|
||||
)
|
||||
|
||||
const ClusterNamespace = "cluster"
|
||||
|
||||
const (
|
||||
TypeUser string = "user"
|
||||
TypeServiceAccount string = "service-account"
|
||||
@ -82,7 +85,25 @@ var RelationsFolder = append(
|
||||
RelationDelete,
|
||||
)
|
||||
|
||||
const ClusterNamespace = "cluster"
|
||||
// VerbMapping is mapping a k8s verb to a zanzana relation.
|
||||
var VerbMapping = map[string]string{
|
||||
utils.VerbGet: RelationGet,
|
||||
utils.VerbList: RelationGet,
|
||||
utils.VerbWatch: RelationGet,
|
||||
utils.VerbCreate: RelationCreate,
|
||||
utils.VerbUpdate: RelationUpdate,
|
||||
utils.VerbPatch: RelationUpdate,
|
||||
utils.VerbDelete: RelationDelete,
|
||||
utils.VerbDeleteCollection: RelationDelete,
|
||||
}
|
||||
|
||||
// RelationToVerbMapping is mapping a zanzana relation to k8s verb.
|
||||
var RelationToVerbMapping = map[string]string{
|
||||
RelationGet: utils.VerbGet,
|
||||
RelationCreate: utils.VerbCreate,
|
||||
RelationUpdate: utils.VerbUpdate,
|
||||
RelationDelete: utils.VerbDelete,
|
||||
}
|
||||
|
||||
func IsGroupResourceRelation(relation string) bool {
|
||||
return isValidRelation(relation, RelationsGroupResource)
|
||||
@ -92,10 +113,6 @@ 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 {
|
||||
@ -113,32 +130,36 @@ func NewTypedIdent(typ string, name string) string {
|
||||
return fmt.Sprintf("%s:%s", typ, name)
|
||||
}
|
||||
|
||||
func NewResourceIdent(group, resource, name string) string {
|
||||
return fmt.Sprintf("%s:%s/%s", TypeResource, FormatGroupResource(group, resource), name)
|
||||
func NewResourceIdent(group, resource, subresource, name string) string {
|
||||
return fmt.Sprintf("%s:%s/%s", TypeResource, FormatGroupResource(group, resource, subresource), name)
|
||||
}
|
||||
|
||||
func NewFolderIdent(name string) string {
|
||||
return fmt.Sprintf("%s:%s", TypeFolder, name)
|
||||
}
|
||||
|
||||
func NewGroupResourceIdent(group, resource string) string {
|
||||
return fmt.Sprintf("%s:%s", TypeGroupResouce, FormatGroupResource(group, resource))
|
||||
func NewGroupResourceIdent(group, resource, subresource string) string {
|
||||
return fmt.Sprintf("%s:%s", TypeGroupResouce, FormatGroupResource(group, resource, subresource))
|
||||
}
|
||||
|
||||
func FormatGroupResource(group, resource string) string {
|
||||
func FormatGroupResource(group, resource, subresource string) string {
|
||||
if subresource != "" {
|
||||
return fmt.Sprintf("%s/%s/%s", group, resource, subresource)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", group, resource)
|
||||
}
|
||||
|
||||
func NewResourceTuple(subject, relation, group, resource, name string) *openfgav1.TupleKey {
|
||||
func NewResourceTuple(subject, relation, group, resource, subresource, name string) *openfgav1.TupleKey {
|
||||
return &openfgav1.TupleKey{
|
||||
User: subject,
|
||||
Relation: relation,
|
||||
Object: NewResourceIdent(group, resource, name),
|
||||
Object: NewResourceIdent(group, resource, subresource, name),
|
||||
Condition: &openfgav1.RelationshipCondition{
|
||||
Name: "group_filter",
|
||||
Context: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"group_resource": structpb.NewStringValue(FormatGroupResource(group, resource)),
|
||||
"group_resource": structpb.NewStringValue(FormatGroupResource(group, resource, subresource)),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -151,7 +172,7 @@ func isFolderResourceRelationSet(relation string) bool {
|
||||
relation == RelationFolderResourceSetAdmin
|
||||
}
|
||||
|
||||
func NewFolderResourceTuple(subject, relation, group, resource, folder string) *openfgav1.TupleKey {
|
||||
func NewFolderResourceTuple(subject, relation, group, resource, subresource, folder string) *openfgav1.TupleKey {
|
||||
relation = FolderResourceRelation(relation)
|
||||
var condition *openfgav1.RelationshipCondition
|
||||
if !isFolderResourceRelationSet(relation) {
|
||||
@ -160,7 +181,7 @@ func NewFolderResourceTuple(subject, relation, group, resource, folder string) *
|
||||
Context: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"group_resources": structpb.NewListValue(&structpb.ListValue{
|
||||
Values: []*structpb.Value{structpb.NewStringValue(FormatGroupResource(group, resource))},
|
||||
Values: []*structpb.Value{structpb.NewStringValue(FormatGroupResource(group, resource, subresource))},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@ -175,18 +196,18 @@ func NewFolderResourceTuple(subject, relation, group, resource, folder string) *
|
||||
}
|
||||
}
|
||||
|
||||
func NewGroupResourceTuple(subject, relation, group, resource string) *openfgav1.TupleKey {
|
||||
func NewGroupResourceTuple(subject, relation, group, resource, subresource string) *openfgav1.TupleKey {
|
||||
return &openfgav1.TupleKey{
|
||||
User: subject,
|
||||
Relation: relation,
|
||||
Object: NewGroupResourceIdent(group, resource),
|
||||
Object: NewGroupResourceIdent(group, resource, subresource),
|
||||
}
|
||||
}
|
||||
|
||||
func NewFolderParentTuple(folder, parent string) *openfgav1.TupleKey {
|
||||
return &openfgav1.TupleKey{
|
||||
Object: NewFolderIdent(folder),
|
||||
Relation: "parent",
|
||||
Relation: RelationParent,
|
||||
User: NewFolderIdent(parent),
|
||||
}
|
||||
}
|
||||
@ -306,14 +327,7 @@ func AddRenderContext(req *openfgav1.CheckRequest) {
|
||||
Object: NewGroupResourceIdent(
|
||||
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)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ func (s *Server) BatchCheck(ctx context.Context, r *authzextv1.BatchCheckRequest
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groupResource := common.FormatGroupResource(item.GetGroup(), item.GetResource())
|
||||
groupResource := common.FormatGroupResource(item.GetGroup(), item.GetResource(), item.GetSubresource())
|
||||
if _, ok := batchRes.Groups[groupResource]; !ok {
|
||||
batchRes.Groups[groupResource] = &authzextv1.BatchCheckGroupResource{
|
||||
Items: make(map[string]bool),
|
||||
@ -51,12 +51,13 @@ func (s *Server) batchCheckItem(
|
||||
) (*authzv1.CheckResponse, error) {
|
||||
var (
|
||||
relation = common.VerbMapping[item.GetVerb()]
|
||||
groupResource = common.FormatGroupResource(item.GetGroup(), item.GetResource())
|
||||
resource = common.NewResourceInfoFromBatchItem(item)
|
||||
groupResource = resource.GroupResource()
|
||||
)
|
||||
|
||||
allowed, ok := groupResourceAccess[groupResource]
|
||||
if !ok {
|
||||
res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, item.GetGroup(), item.GetResource(), store)
|
||||
res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, resource, store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -69,8 +70,9 @@ func (s *Server) batchCheckItem(
|
||||
return &authzv1.CheckResponse{Allowed: true}, nil
|
||||
}
|
||||
|
||||
if info, ok := common.GetTypeInfo(item.GetGroup(), item.GetResource()); ok {
|
||||
return s.checkTyped(ctx, r.GetSubject(), relation, item.GetName(), info, store)
|
||||
if resource.IsGeneric() {
|
||||
return s.checkGeneric(ctx, r.GetSubject(), relation, resource, store)
|
||||
}
|
||||
return s.checkGeneric(ctx, r.GetSubject(), relation, item.GetGroup(), item.GetResource(), item.GetName(), item.GetFolder(), store)
|
||||
|
||||
return s.checkTyped(ctx, r.GetSubject(), relation, resource, store)
|
||||
}
|
||||
|
@ -9,18 +9,19 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
|
||||
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
||||
"github.com/grafana/grafana/pkg/services/authz/zanzana/common"
|
||||
)
|
||||
|
||||
func testBatchCheck(t *testing.T, server *Server) {
|
||||
newReq := func(subject, verb, group, resource string, items []*authzextv1.BatchCheckItem) *authzextv1.BatchCheckRequest {
|
||||
newReq := func(subject, verb, group, resource, subresource string, items []*authzextv1.BatchCheckItem) *authzextv1.BatchCheckRequest {
|
||||
for i, item := range items {
|
||||
items[i] = &authzextv1.BatchCheckItem{
|
||||
Verb: verb,
|
||||
Group: group,
|
||||
Resource: resource,
|
||||
Name: item.GetName(),
|
||||
Folder: item.GetFolder(),
|
||||
Verb: verb,
|
||||
Group: group,
|
||||
Resource: resource,
|
||||
Subresource: subresource,
|
||||
Name: item.GetName(),
|
||||
Folder: item.GetFolder(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,8 +33,8 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
}
|
||||
|
||||
t.Run("user:1 should only be able to read resource:dashboard.grafana.app/dashboards/1", func(t *testing.T) {
|
||||
groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1", Folder: "1"},
|
||||
{Name: "2", Folder: "2"},
|
||||
}))
|
||||
@ -45,8 +46,8 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/{1,2} through group_resource", func(t *testing.T) {
|
||||
groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1", Folder: "1"},
|
||||
{Name: "2", Folder: "2"},
|
||||
}))
|
||||
@ -55,8 +56,8 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:3 should be able to read resource:dashboard.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
|
||||
groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1", Folder: "1"},
|
||||
{Name: "2", Folder: "2"},
|
||||
}))
|
||||
@ -68,8 +69,8 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:4 should be able to read all dashboard.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) {
|
||||
groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1", Folder: "1"},
|
||||
{Name: "2", Folder: "3"},
|
||||
{Name: "3", Folder: "2"},
|
||||
@ -83,8 +84,8 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:5 should be able to read resource:dashboard.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) {
|
||||
groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:5", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:5", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1", Folder: "1"},
|
||||
{Name: "2", Folder: "2"},
|
||||
}))
|
||||
@ -96,8 +97,8 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:6 should be able to read folder 1", func(t *testing.T) {
|
||||
groupResource := zanzana.FormatGroupResource(folderGroup, folderResource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:6", utils.VerbGet, folderGroup, folderResource, []*authzextv1.BatchCheckItem{
|
||||
groupResource := common.FormatGroupResource(folderGroup, folderResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:6", utils.VerbGet, folderGroup, folderResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1"},
|
||||
{Name: "2"},
|
||||
}))
|
||||
@ -109,8 +110,8 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:7 should be able to read folder {1,2} through group_resource access", func(t *testing.T) {
|
||||
groupResource := zanzana.FormatGroupResource(folderGroup, folderResource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, []*authzextv1.BatchCheckItem{
|
||||
groupResource := common.FormatGroupResource(folderGroup, folderResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1"},
|
||||
{Name: "2"},
|
||||
}))
|
||||
@ -121,8 +122,8 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:8 should be able to read all resoruce:dashboard.grafana.app/dashboards in folder 6 through folder 5", func(t *testing.T) {
|
||||
groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "10", Folder: "6"},
|
||||
{Name: "20", Folder: "6"},
|
||||
}))
|
||||
@ -133,8 +134,8 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:9 should be able to create dashboards in folder 6 through folder 5", func(t *testing.T) {
|
||||
groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:9", utils.VerbCreate, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:9", utils.VerbCreate, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "10", Folder: "6"},
|
||||
{Name: "20", Folder: "6"},
|
||||
}))
|
||||
@ -144,4 +145,50 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
require.True(t, res.Groups[groupResource].Items["10"])
|
||||
require.True(t, res.Groups[groupResource].Items["20"])
|
||||
})
|
||||
|
||||
t.Run("user:10 should be able to get dashboard status for 10 and 11", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, statusSubresource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:10", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, []*authzextv1.BatchCheckItem{
|
||||
{Name: "10", Folder: "6"},
|
||||
{Name: "11", Folder: "6"},
|
||||
{Name: "12", Folder: "6"},
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
t.Log(res.Groups)
|
||||
require.Len(t, res.Groups[groupResource].Items, 3)
|
||||
require.True(t, res.Groups[groupResource].Items["10"])
|
||||
require.True(t, res.Groups[groupResource].Items["11"])
|
||||
require.False(t, res.Groups[groupResource].Items["12"])
|
||||
})
|
||||
|
||||
t.Run("user:11 should be able to get dashboard status for 10, 11 and 12 through group_resource", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, statusSubresource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:11", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, []*authzextv1.BatchCheckItem{
|
||||
{Name: "10", Folder: "6"},
|
||||
{Name: "11", Folder: "6"},
|
||||
{Name: "12", Folder: "6"},
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
t.Log(res.Groups)
|
||||
require.Len(t, res.Groups[groupResource].Items, 3)
|
||||
require.True(t, res.Groups[groupResource].Items["10"])
|
||||
require.True(t, res.Groups[groupResource].Items["11"])
|
||||
require.True(t, res.Groups[groupResource].Items["12"])
|
||||
})
|
||||
|
||||
t.Run("user:12 should be able to get dashboard status in folder 5 and 6", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, statusSubresource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, []*authzextv1.BatchCheckItem{
|
||||
{Name: "10", Folder: "5"},
|
||||
{Name: "11", Folder: "6"},
|
||||
{Name: "12", Folder: "6"},
|
||||
{Name: "13", Folder: "1"},
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res.Groups[groupResource].Items, 4)
|
||||
require.True(t, res.Groups[groupResource].Items["10"])
|
||||
require.True(t, res.Groups[groupResource].Items["11"])
|
||||
require.True(t, res.Groups[groupResource].Items["12"])
|
||||
require.False(t, res.Groups[groupResource].Items["13"])
|
||||
})
|
||||
}
|
||||
|
@ -21,7 +21,9 @@ func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.C
|
||||
}
|
||||
|
||||
relation := common.VerbMapping[r.GetVerb()]
|
||||
res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
|
||||
|
||||
resource := common.NewResourceInfoFromCheck(r)
|
||||
res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, resource, store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -30,15 +32,16 @@ func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.C
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if info, ok := common.GetTypeInfo(r.GetGroup(), r.GetResource()); ok {
|
||||
return s.checkTyped(ctx, r.GetSubject(), relation, r.GetName(), info, store)
|
||||
if resource.IsGeneric() {
|
||||
return s.checkGeneric(ctx, r.GetSubject(), relation, resource, store)
|
||||
}
|
||||
return s.checkGeneric(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), r.GetName(), r.GetFolder(), store)
|
||||
|
||||
return s.checkTyped(ctx, r.GetSubject(), relation, resource, store)
|
||||
}
|
||||
|
||||
// checkGroupResource check if subject has access to the full "GroupResource", if they do they can access every object
|
||||
// within it.
|
||||
func (s *Server) checkGroupResource(ctx context.Context, subject, relation, group, resource string, store *storeInfo) (*authzv1.CheckResponse, error) {
|
||||
func (s *Server) checkGroupResource(ctx context.Context, subject, relation string, resource common.ResourceInfo, store *storeInfo) (*authzv1.CheckResponse, error) {
|
||||
if !common.IsGroupResourceRelation(relation) {
|
||||
return &authzv1.CheckResponse{Allowed: false}, nil
|
||||
}
|
||||
@ -49,7 +52,7 @@ func (s *Server) checkGroupResource(ctx context.Context, subject, relation, grou
|
||||
TupleKey: &openfgav1.CheckRequestTupleKey{
|
||||
User: subject,
|
||||
Relation: relation,
|
||||
Object: common.NewGroupResourceIdent(group, resource),
|
||||
Object: resource.GroupResourceIdent(),
|
||||
},
|
||||
}
|
||||
|
||||
@ -66,8 +69,8 @@ func (s *Server) checkGroupResource(ctx context.Context, subject, relation, grou
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func (s *Server) checkTyped(ctx context.Context, subject, relation string, resource common.ResourceInfo, store *storeInfo) (*authzv1.CheckResponse, error) {
|
||||
if !resource.IsValidRelation(relation) {
|
||||
return &authzv1.CheckResponse{Allowed: false}, nil
|
||||
}
|
||||
|
||||
@ -78,7 +81,7 @@ func (s *Server) checkTyped(ctx context.Context, subject, relation, name string,
|
||||
TupleKey: &openfgav1.CheckRequestTupleKey{
|
||||
User: subject,
|
||||
Relation: relation,
|
||||
Object: common.NewTypedIdent(info.Type, name),
|
||||
Object: resource.ResourceIdent(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@ -95,21 +98,22 @@ func (s *Server) checkTyped(ctx context.Context, subject, relation, name string,
|
||||
// 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) {
|
||||
func (s *Server) checkGeneric(ctx context.Context, subject, relation string, resource common.ResourceInfo, store *storeInfo) (*authzv1.CheckResponse, error) {
|
||||
var (
|
||||
resourceCtx = common.NewResourceContext(group, resource)
|
||||
folderIdent = resource.FolderIdent()
|
||||
resourceCtx = resource.Context()
|
||||
folderRelation = common.FolderResourceRelation(relation)
|
||||
)
|
||||
|
||||
if folder != "" && common.IsFolderResourceRelation(folderRelation) {
|
||||
if folderIdent != "" && common.IsFolderResourceRelation(folderRelation) {
|
||||
// Check if subject has access as a sub resource for the folder
|
||||
res, err := s.check(ctx, &openfgav1.CheckRequest{
|
||||
StoreId: store.ID,
|
||||
AuthorizationModelId: store.ModelID,
|
||||
TupleKey: &openfgav1.CheckRequestTupleKey{
|
||||
User: subject,
|
||||
Relation: common.FolderResourceRelation(relation),
|
||||
Object: common.NewFolderIdent(folder),
|
||||
Relation: folderRelation,
|
||||
Object: folderIdent,
|
||||
},
|
||||
Context: resourceCtx,
|
||||
})
|
||||
@ -123,7 +127,8 @@ func (s *Server) checkGeneric(ctx context.Context, subject, relation, group, res
|
||||
}
|
||||
}
|
||||
|
||||
if !common.IsResourceRelation(relation) {
|
||||
resourceIdent := resource.ResourceIdent()
|
||||
if !resource.IsValidRelation(relation) || resourceIdent == "" {
|
||||
return &authzv1.CheckResponse{Allowed: false}, nil
|
||||
}
|
||||
|
||||
@ -134,7 +139,7 @@ func (s *Server) checkGeneric(ctx context.Context, subject, relation, group, res
|
||||
TupleKey: &openfgav1.CheckRequestTupleKey{
|
||||
User: subject,
|
||||
Relation: relation,
|
||||
Object: common.NewResourceIdent(group, resource, name),
|
||||
Object: resourceIdent,
|
||||
},
|
||||
Context: resourceCtx,
|
||||
})
|
||||
|
@ -12,104 +12,145 @@ import (
|
||||
)
|
||||
|
||||
func testCheck(t *testing.T, server *Server) {
|
||||
newReq := func(subject, verb, group, resource, folder, name string) *authzv1.CheckRequest {
|
||||
newReq := func(subject, verb, group, resource, subresource, folder, name string) *authzv1.CheckRequest {
|
||||
return &authzv1.CheckRequest{
|
||||
Namespace: namespace,
|
||||
Subject: subject,
|
||||
Verb: verb,
|
||||
Group: group,
|
||||
Resource: resource,
|
||||
Name: name,
|
||||
Folder: folder,
|
||||
Namespace: namespace,
|
||||
Subject: subject,
|
||||
Verb: verb,
|
||||
Group: group,
|
||||
Resource: resource,
|
||||
Subresource: subresource,
|
||||
Name: name,
|
||||
Folder: folder,
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("user:1 should only be able to read resource:dashboard.grafana.app/dashboards/1", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "1", "1"))
|
||||
res, err := server.Check(context.Background(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
// sanity check
|
||||
res, err = server.Check(context.Background(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "1", "2"))
|
||||
res, err = server.Check(context.Background(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "2"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
|
||||
// sanity check no access to subresource
|
||||
res, err = server.Check(context.Background(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/1 through group_resource", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, "1", "1"))
|
||||
res, err := server.Check(context.Background(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:3 should be able to read resource:dashboard.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "1", "1"))
|
||||
res, err := server.Check(context.Background(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
// sanity check
|
||||
res, err = server.Check(context.Background(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "1", "2"))
|
||||
res, err = server.Check(context.Background(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "2"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:4 should be able to read all dashboard.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "1", "1"))
|
||||
res, err := server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "3", "2"))
|
||||
res, err = server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "3", "2"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
// sanity check
|
||||
res, err = server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "1", "2"))
|
||||
res, err = server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "2"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "2", "2"))
|
||||
res, err = server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "2", "2"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:5 should be able to read resource:dashboard.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:5", utils.VerbGet, dashboardGroup, dashboardResource, "1", "1"))
|
||||
res, err := server.Check(context.Background(), newReq("user:5", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:6 should be able to read folder 1 ", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:6", utils.VerbGet, folderGroup, folderResource, "", "1"))
|
||||
res, err := server.Check(context.Background(), newReq("user:6", utils.VerbGet, folderGroup, folderResource, "", "", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:7 should be able to read folder one through group_resource access", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "1"))
|
||||
res, err := server.Check(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "10"))
|
||||
res, err = server.Check(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "", "10"))
|
||||
require.NoError(t, err)
|
||||
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(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "6", "10"))
|
||||
res, err := server.Check(context.Background(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "", "6", "10"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "5", "11"))
|
||||
res, err = server.Check(context.Background(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "", "5", "11"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:8", utils.VerbGet, folderGroup, folderResource, "4", "12"))
|
||||
res, err = server.Check(context.Background(), newReq("user:8", utils.VerbGet, folderGroup, folderResource, "", "4", "12"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:9 should be able to create dashboards in folder 5", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:9", utils.VerbCreate, dashboardGroup, dashboardResource, "5", ""))
|
||||
res, err := server.Check(context.Background(), newReq("user:9", utils.VerbCreate, dashboardGroup, dashboardResource, "", "5", ""))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:10 should be able to read dashboard status for dashboard 10", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:10", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "", "10"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:10", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:11 should be able to read dashboard status for dashboard 10 through group_resource", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:11", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "", "10"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:12 should be able to read dashboard status for all dashboards in folder 5", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "5", "10"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "5", "11"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
// inherited from folder 5
|
||||
res, err = server.Check(context.Background(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "6", "12"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "1", "13"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
})
|
||||
}
|
||||
|
@ -21,8 +21,9 @@ func (s *Server) List(ctx context.Context, r *authzv1.ListRequest) (*authzv1.Lis
|
||||
}
|
||||
|
||||
relation := common.VerbMapping[r.GetVerb()]
|
||||
resource := common.NewResourceInfoFromList(r)
|
||||
|
||||
res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
|
||||
res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, resource, store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -31,11 +32,11 @@ func (s *Server) List(ctx context.Context, r *authzv1.ListRequest) (*authzv1.Lis
|
||||
return &authzv1.ListResponse{All: true}, nil
|
||||
}
|
||||
|
||||
if info, ok := common.GetTypeInfo(r.GetGroup(), r.GetResource()); ok {
|
||||
return s.listTyped(ctx, r.GetSubject(), relation, info, store)
|
||||
if resource.IsGeneric() {
|
||||
return s.listGeneric(ctx, r.GetSubject(), relation, resource, store)
|
||||
}
|
||||
|
||||
return s.listGeneric(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
|
||||
return s.listTyped(ctx, r.GetSubject(), relation, resource, store)
|
||||
}
|
||||
|
||||
func (s *Server) listObjects(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) {
|
||||
@ -50,8 +51,8 @@ func (s *Server) listObjects(ctx context.Context, req *openfgav1.ListObjectsRequ
|
||||
return s.openfga.ListObjects(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) listTyped(ctx context.Context, subject, relation string, info common.TypeInfo, store *storeInfo) (*authzv1.ListResponse, error) {
|
||||
if !info.IsValidRelation(relation) {
|
||||
func (s *Server) listTyped(ctx context.Context, subject, relation string, resource common.ResourceInfo, store *storeInfo) (*authzv1.ListResponse, error) {
|
||||
if !resource.IsValidRelation(relation) {
|
||||
return &authzv1.ListResponse{}, nil
|
||||
}
|
||||
|
||||
@ -59,7 +60,7 @@ func (s *Server) listTyped(ctx context.Context, subject, relation string, info c
|
||||
res, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{
|
||||
StoreId: store.ID,
|
||||
AuthorizationModelId: store.ModelID,
|
||||
Type: info.Type,
|
||||
Type: resource.Type(),
|
||||
Relation: relation,
|
||||
User: subject,
|
||||
})
|
||||
@ -68,14 +69,14 @@ func (s *Server) listTyped(ctx context.Context, subject, relation string, info c
|
||||
}
|
||||
|
||||
return &authzv1.ListResponse{
|
||||
Items: typedObjects(info.Type, res.GetObjects()),
|
||||
Items: typedObjects(resource.Type(), res.GetObjects()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) listGeneric(ctx context.Context, subject, relation, group, resource string, store *storeInfo) (*authzv1.ListResponse, error) {
|
||||
func (s *Server) listGeneric(ctx context.Context, subject, relation string, resource common.ResourceInfo, store *storeInfo) (*authzv1.ListResponse, error) {
|
||||
var (
|
||||
resourceCtx = common.NewResourceContext(group, resource)
|
||||
folderRelation = common.FolderResourceRelation(relation)
|
||||
resourceCtx = resource.Context()
|
||||
)
|
||||
|
||||
// 1. List all folders subject has access to resource type in
|
||||
@ -98,8 +99,8 @@ func (s *Server) listGeneric(ctx context.Context, subject, relation, group, reso
|
||||
}
|
||||
|
||||
// 2. List all resource directly assigned to subject
|
||||
var resources []string
|
||||
if common.IsResourceRelation(relation) {
|
||||
var objects []string
|
||||
if resource.IsValidRelation(relation) {
|
||||
res, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{
|
||||
StoreId: store.ID,
|
||||
AuthorizationModelId: store.ModelID,
|
||||
@ -112,12 +113,12 @@ func (s *Server) listGeneric(ctx context.Context, subject, relation, group, reso
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources = res.GetObjects()
|
||||
objects = res.GetObjects()
|
||||
}
|
||||
|
||||
return &authzv1.ListResponse{
|
||||
Folders: folderObject(folders),
|
||||
Items: directObjects(group, resource, resources),
|
||||
Items: directObjects(resource.GroupResource(), objects),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -129,8 +130,8 @@ func typedObjects(typ string, objects []string) []string {
|
||||
return objects
|
||||
}
|
||||
|
||||
func directObjects(group, resource string, objects []string) []string {
|
||||
prefix := fmt.Sprintf("%s:%s/%s/", resourceType, group, resource)
|
||||
func directObjects(gr string, objects []string) []string {
|
||||
prefix := fmt.Sprintf("%s:%s/", resourceType, gr)
|
||||
for i := range objects {
|
||||
objects[i] = strings.TrimPrefix(objects[i], prefix)
|
||||
}
|
||||
|
@ -12,18 +12,19 @@ import (
|
||||
)
|
||||
|
||||
func testList(t *testing.T, server *Server) {
|
||||
newList := func(subject, group, resource string) *authzv1.ListRequest {
|
||||
newList := func(subject, group, resource, subresource string) *authzv1.ListRequest {
|
||||
return &authzv1.ListRequest{
|
||||
Namespace: namespace,
|
||||
Verb: utils.VerbList,
|
||||
Subject: subject,
|
||||
Group: group,
|
||||
Resource: resource,
|
||||
Namespace: namespace,
|
||||
Verb: utils.VerbList,
|
||||
Subject: subject,
|
||||
Group: group,
|
||||
Resource: resource,
|
||||
Subresource: subresource,
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("user:1 should list resource:dashboard.grafana.app/dashboards/1", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:1", dashboardGroup, dashboardResource))
|
||||
res, err := server.List(context.Background(), newList("user:1", dashboardGroup, dashboardResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 1)
|
||||
assert.Len(t, res.GetFolders(), 0)
|
||||
@ -31,7 +32,7 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:2 should be able to list all through group", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:2", dashboardGroup, dashboardResource))
|
||||
res, err := server.List(context.Background(), newList("user:2", dashboardGroup, dashboardResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAll())
|
||||
assert.Len(t, res.GetItems(), 0)
|
||||
@ -39,7 +40,7 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:3 should be able to list resource:dashboard.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:3", dashboardGroup, dashboardResource))
|
||||
res, err := server.List(context.Background(), newList("user:3", dashboardGroup, dashboardResource, ""))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, res.GetItems(), 1)
|
||||
@ -48,24 +49,17 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:4 should be able to list all dashboard.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:4", dashboardGroup, dashboardResource))
|
||||
res, err := server.List(context.Background(), newList("user:4", dashboardGroup, dashboardResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 0)
|
||||
assert.Len(t, res.GetFolders(), 2)
|
||||
|
||||
first := res.GetFolders()[0]
|
||||
second := res.GetFolders()[1]
|
||||
|
||||
if first == "3" {
|
||||
first, second = second, first
|
||||
}
|
||||
|
||||
assert.Equal(t, first, "1")
|
||||
assert.Equal(t, second, "3")
|
||||
assert.Contains(t, res.GetFolders(), "1")
|
||||
assert.Contains(t, res.GetFolders(), "3")
|
||||
})
|
||||
|
||||
t.Run("user:5 should be get list all dashboard.grafana.app/dashboards in folder 1 with set relation", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:5", dashboardGroup, dashboardResource))
|
||||
t.Run("user:5 should be list all dashboard.grafana.app/dashboards in folder 1 with set relation", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:5", dashboardGroup, dashboardResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 0)
|
||||
assert.Len(t, res.GetFolders(), 1)
|
||||
@ -73,7 +67,7 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:6 should be able to list folder 1", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:6", folderGroup, folderResource))
|
||||
res, err := server.List(context.Background(), newList("user:6", folderGroup, folderResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 1)
|
||||
assert.Len(t, res.GetFolders(), 0)
|
||||
@ -81,10 +75,47 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:7 should be able to list all folders", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:7", folderGroup, folderResource))
|
||||
res, err := server.List(context.Background(), newList("user:7", folderGroup, folderResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 0)
|
||||
assert.Len(t, res.GetFolders(), 0)
|
||||
assert.True(t, res.GetAll())
|
||||
})
|
||||
|
||||
t.Run("user:8 should be able to list resoruce:dashboard.grafana.app/dashboard in folder 6 and folder 5", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:8", dashboardGroup, dashboardResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetFolders(), 2)
|
||||
|
||||
assert.Contains(t, res.GetFolders(), "5")
|
||||
assert.Contains(t, res.GetFolders(), "6")
|
||||
})
|
||||
|
||||
t.Run("user:10 should be able to get resoruce:dashboard.grafana.app/dashboard/status for 10 and 11", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:10", dashboardGroup, dashboardResource, statusSubresource))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetFolders(), 0)
|
||||
assert.Len(t, res.GetItems(), 2)
|
||||
|
||||
assert.Contains(t, res.GetItems(), "10")
|
||||
assert.Contains(t, res.GetItems(), "11")
|
||||
})
|
||||
|
||||
t.Run("user:11 should be able to list all resoruce:dashboard.grafana.app/dashboard/status ", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:11", dashboardGroup, dashboardResource, statusSubresource))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 0)
|
||||
assert.Len(t, res.GetFolders(), 0)
|
||||
assert.True(t, res.GetAll())
|
||||
})
|
||||
|
||||
t.Run("user:12 should be able to list all resoruce:dashboard.grafana.app/dashboard/status in folder 5 and 6", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:12", dashboardGroup, dashboardResource, statusSubresource))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 0)
|
||||
assert.Len(t, res.GetFolders(), 2)
|
||||
|
||||
assert.Contains(t, res.GetFolders(), "5")
|
||||
assert.Contains(t, res.GetFolders(), "6")
|
||||
})
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ const (
|
||||
|
||||
folderGroup = "folder.grafana.app"
|
||||
folderResource = "folders"
|
||||
|
||||
statusSubresource = "status"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@ -76,20 +78,24 @@ 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", common.RelationGet, dashboardGroup, dashboardResource, "1"),
|
||||
common.NewResourceTuple("user:1", common.RelationUpdate, dashboardGroup, dashboardResource, "1"),
|
||||
common.NewGroupResourceTuple("user:2", common.RelationGet, dashboardGroup, dashboardResource),
|
||||
common.NewGroupResourceTuple("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.NewResourceTuple("user:1", common.RelationGet, dashboardGroup, dashboardResource, "", "1"),
|
||||
common.NewResourceTuple("user:1", common.RelationUpdate, dashboardGroup, dashboardResource, "", "1"),
|
||||
common.NewGroupResourceTuple("user:2", common.RelationGet, dashboardGroup, dashboardResource, ""),
|
||||
common.NewGroupResourceTuple("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.NewGroupResourceTuple("user:7", common.RelationGet, folderGroup, folderResource),
|
||||
common.NewGroupResourceTuple("user:7", common.RelationGet, folderGroup, folderResource, ""),
|
||||
common.NewFolderParentTuple("5", "4"),
|
||||
common.NewFolderParentTuple("6", "5"),
|
||||
common.NewFolderResourceTuple("user:8", common.RelationSetEdit, dashboardGroup, dashboardResource, "5"),
|
||||
common.NewFolderResourceTuple("user:9", "create", dashboardGroup, dashboardResource, "5"),
|
||||
common.NewFolderResourceTuple("user:8", common.RelationSetEdit, dashboardGroup, dashboardResource, "", "5"),
|
||||
common.NewFolderResourceTuple("user:9", common.RelationCreate, dashboardGroup, dashboardResource, "", "5"),
|
||||
common.NewResourceTuple("user:10", common.RelationGet, dashboardGroup, dashboardResource, statusSubresource, "10"),
|
||||
common.NewResourceTuple("user:10", common.RelationGet, dashboardGroup, dashboardResource, statusSubresource, "11"),
|
||||
common.NewGroupResourceTuple("user:11", common.RelationGet, dashboardGroup, dashboardResource, statusSubresource),
|
||||
common.NewFolderResourceTuple("user:12", common.RelationGet, dashboardGroup, dashboardResource, statusSubresource, "5"),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@ -29,17 +29,18 @@ type resourceTranslation struct {
|
||||
}
|
||||
|
||||
type actionMappig struct {
|
||||
relation string
|
||||
group string
|
||||
resource string
|
||||
relation string
|
||||
group string
|
||||
resource string
|
||||
subresource string
|
||||
}
|
||||
|
||||
func newMapping(relation string) actionMappig {
|
||||
return newScopedMapping(relation, "", "")
|
||||
func newMapping(relation, subresource string) actionMappig {
|
||||
return newScopedMapping(relation, "", "", subresource)
|
||||
}
|
||||
|
||||
func newScopedMapping(relation, group, resource string) actionMappig {
|
||||
return actionMappig{relation, group, resource}
|
||||
func newScopedMapping(relation, group, resource, subresource string) actionMappig {
|
||||
return actionMappig{relation, group, resource, subresource}
|
||||
}
|
||||
|
||||
var (
|
||||
@ -56,14 +57,14 @@ var resourceTranslations = map[string]resourceTranslation{
|
||||
group: folderGroup,
|
||||
resource: folderResource,
|
||||
mapping: map[string]actionMappig{
|
||||
"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),
|
||||
"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: {
|
||||
@ -71,10 +72,10 @@ var resourceTranslations = map[string]resourceTranslation{
|
||||
group: dashboardGroup,
|
||||
resource: dashboardResource,
|
||||
mapping: map[string]actionMappig{
|
||||
"dashboards:read": newMapping(RelationGet),
|
||||
"dashboards:write": newMapping(RelationUpdate),
|
||||
"dashboards:create": newMapping(RelationCreate),
|
||||
"dashboards:delete": newMapping(RelationDelete),
|
||||
"dashboards:read": newMapping(RelationGet, ""),
|
||||
"dashboards:write": newMapping(RelationUpdate, ""),
|
||||
"dashboards:create": newMapping(RelationCreate, ""),
|
||||
"dashboards:delete": newMapping(RelationDelete, ""),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -70,8 +70,6 @@ var (
|
||||
ToOpenFGATuples = common.ToOpenFGATuples
|
||||
ToOpenFGATupleKey = common.ToOpenFGATupleKey
|
||||
ToOpenFGATupleKeyWithoutCondition = common.ToOpenFGATupleKeyWithoutCondition
|
||||
|
||||
FormatGroupResource = common.FormatGroupResource
|
||||
)
|
||||
|
||||
// NewTupleEntry constructs new openfga entry type:name[#relation].
|
||||
@ -98,16 +96,16 @@ func TranslateToResourceTuple(subject string, action, kind, name string) (*openf
|
||||
}
|
||||
|
||||
if name == "*" {
|
||||
return common.NewGroupResourceTuple(subject, m.relation, translation.group, translation.resource), true
|
||||
return common.NewGroupResourceTuple(subject, m.relation, translation.group, translation.resource, m.subresource), true
|
||||
}
|
||||
|
||||
if translation.typ == TypeResource {
|
||||
return common.NewResourceTuple(subject, m.relation, translation.group, translation.resource, name), true
|
||||
return common.NewResourceTuple(subject, m.relation, translation.group, translation.resource, m.subresource, name), true
|
||||
}
|
||||
|
||||
if translation.typ == TypeFolder {
|
||||
if m.group != "" && m.resource != "" {
|
||||
return common.NewFolderResourceTuple(subject, m.relation, m.group, m.resource, name), true
|
||||
return common.NewFolderResourceTuple(subject, m.relation, m.group, m.resource, m.subresource, name), true
|
||||
}
|
||||
|
||||
return common.NewFolderTuple(subject, m.relation, name), true
|
||||
@ -177,7 +175,7 @@ func TranslateToGroupResource(kind string) string {
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return common.FormatGroupResource(translation.group, translation.resource)
|
||||
return common.FormatGroupResource(translation.group, translation.resource, "")
|
||||
}
|
||||
|
||||
func TranslateBasicRole(name string) string {
|
||||
|
Loading…
Reference in New Issue
Block a user