Files
grafana/pkg/services/authz/zanzana/server/server_list.go
T
Alexander Zobnin 5a91ab46af Zanzana: Use StreamedListObjects to fetch full list of resources (#97025)
* Initial streamed version of list

* instantiate openfga client to use StreamedListObjects

* Add config option for using streamed version

* Use caching

* fix cache init

* Fix hashing

* refactor
2024-11-27 14:05:41 +02:00

140 lines
3.8 KiB
Go

package server
import (
"context"
"fmt"
"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"
)
func (s *Server) List(ctx context.Context, r *authzextv1.ListRequest) (*authzextv1.ListResponse, error) {
ctx, span := tracer.Start(ctx, "authzServer.List")
defer span.End()
store, err := s.getStoreInfo(ctx, r.Namespace)
if err != nil {
return nil, err
}
relation := common.VerbMapping[r.GetVerb()]
res, err := s.checkNamespace(
ctx,
r.GetSubject(),
relation,
r.GetGroup(),
r.GetResource(),
store,
)
if err != nil {
return nil, err
}
if res.GetAllowed() {
return &authzextv1.ListResponse{All: true}, nil
}
if info, ok := common.GetTypeInfo(r.GetGroup(), r.GetResource()); ok {
return s.listTyped(ctx, r.GetSubject(), relation, info, store)
}
return s.listGeneric(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
}
func (s *Server) listObjects(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) {
if s.cfg.UseStreamedListObjects {
return s.streamedListObjects(ctx, req)
}
return s.openfga.ListObjects(ctx, req)
}
func (s *Server) listTyped(ctx context.Context, subject, relation string, info common.TypeInfo, store *storeInfo) (*authzextv1.ListResponse, error) {
// List all resources user has access too
listRes, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{
StoreId: store.ID,
AuthorizationModelId: store.ModelID,
Type: info.Type,
Relation: relation,
User: subject,
})
if err != nil {
return nil, err
}
return &authzextv1.ListResponse{
Items: typedObjects(info.Type, listRes.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))
// 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
}
// 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
}
return &authzextv1.ListResponse{
Folders: folderObject(folders.GetObjects()),
Items: directObjects(group, resource, direct.GetObjects()),
}, nil
}
func typedObjects(typ string, objects []string) []string {
prefix := fmt.Sprintf("%s:", typ)
for i := range objects {
objects[i] = strings.TrimPrefix(objects[i], prefix)
}
return objects
}
func directObjects(group, resource string, objects []string) []string {
prefix := fmt.Sprintf("%s:%s/%s/", resourceType, group, resource)
for i := range objects {
objects[i] = strings.TrimPrefix(objects[i], prefix)
}
return objects
}
func folderObject(objects []string) []string {
for i := range objects {
objects[i] = strings.TrimPrefix(objects[i], folderTypePrefix)
}
return objects
}