Add Capabilities api

pull/95508/head
Karl Persson 8 months ago
parent 5533b30135
commit 95379dcd23
  1. 16
      pkg/services/authz/zanzana/common/info.go
  2. 24
      pkg/services/authz/zanzana/common/relation.go
  3. 232
      pkg/services/authz/zanzana/proto/v1/extention.pb.go
  4. 16
      pkg/services/authz/zanzana/proto/v1/extention.proto
  5. 38
      pkg/services/authz/zanzana/proto/v1/extention_grpc.pb.go
  6. 69
      pkg/services/authz/zanzana/server/server_capabilities.go
  7. 68
      pkg/services/authz/zanzana/server/server_capabilities_test.go
  8. 12
      pkg/services/authz/zanzana/server/server_check.go
  9. 13
      pkg/services/authz/zanzana/server/server_list.go
  10. 22
      pkg/services/authz/zanzana/server/server_test.go
  11. 28
      pkg/services/authz/zanzana/zanzana.go

@ -23,12 +23,12 @@ func GetTypeInfo(group, resource string) (TypeInfo, bool) {
}
var VerbMapping = map[string]string{
utils.VerbGet: "read",
utils.VerbList: "read",
utils.VerbWatch: "read",
utils.VerbCreate: "create",
utils.VerbUpdate: "write",
utils.VerbPatch: "write",
utils.VerbDelete: "delete",
utils.VerbDeleteCollection: "delete",
utils.VerbGet: RelationRead,
utils.VerbList: RelationRead,
utils.VerbWatch: RelationRead,
utils.VerbCreate: RelationCreate,
utils.VerbUpdate: RelationWrite,
utils.VerbPatch: RelationWrite,
utils.VerbDelete: RelationDelete,
utils.VerbDeleteCollection: RelationDelete,
}

@ -0,0 +1,24 @@
package common
// Common relation for each resource
const (
RelationView string = "view"
RelationEdit string = "edit"
RelationAdmin string = "admin"
RelationRead string = "read"
RelationWrite string = "write"
RelationCreate string = "create"
RelationDelete string = "delete"
RelationPermissionsRead string = "permissions_read"
RelationPermissionsWrite string = "permissions_write"
)
var ResourceRelations = [...]string{
RelationRead,
RelationWrite,
RelationCreate,
RelationDelete,
RelationPermissionsRead,
RelationPermissionsWrite,
}

@ -162,6 +162,156 @@ func (x *ListResponse) GetItems() []string {
return nil
}
type CapabilitiesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"`
Group string `protobuf:"bytes,3,opt,name=group,proto3" json:"group,omitempty"`
Namespace string `protobuf:"bytes,5,opt,name=namespace,proto3" json:"namespace,omitempty"`
Resource string `protobuf:"bytes,4,opt,name=resource,proto3" json:"resource,omitempty"`
Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"`
Folder string `protobuf:"bytes,9,opt,name=folder,proto3" json:"folder,omitempty"`
Path string `protobuf:"bytes,8,opt,name=path,proto3" json:"path,omitempty"`
Subresource string `protobuf:"bytes,7,opt,name=subresource,proto3" json:"subresource,omitempty"`
}
func (x *CapabilitiesRequest) Reset() {
*x = CapabilitiesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_extention_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CapabilitiesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CapabilitiesRequest) ProtoMessage() {}
func (x *CapabilitiesRequest) ProtoReflect() protoreflect.Message {
mi := &file_extention_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CapabilitiesRequest.ProtoReflect.Descriptor instead.
func (*CapabilitiesRequest) Descriptor() ([]byte, []int) {
return file_extention_proto_rawDescGZIP(), []int{2}
}
func (x *CapabilitiesRequest) GetSubject() string {
if x != nil {
return x.Subject
}
return ""
}
func (x *CapabilitiesRequest) GetGroup() string {
if x != nil {
return x.Group
}
return ""
}
func (x *CapabilitiesRequest) GetNamespace() string {
if x != nil {
return x.Namespace
}
return ""
}
func (x *CapabilitiesRequest) GetResource() string {
if x != nil {
return x.Resource
}
return ""
}
func (x *CapabilitiesRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *CapabilitiesRequest) GetFolder() string {
if x != nil {
return x.Folder
}
return ""
}
func (x *CapabilitiesRequest) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
func (x *CapabilitiesRequest) GetSubresource() string {
if x != nil {
return x.Subresource
}
return ""
}
type CapabilitiesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Capabilities []string `protobuf:"bytes,1,rep,name=capabilities,proto3" json:"capabilities,omitempty"`
}
func (x *CapabilitiesResponse) Reset() {
*x = CapabilitiesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_extention_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CapabilitiesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CapabilitiesResponse) ProtoMessage() {}
func (x *CapabilitiesResponse) ProtoReflect() protoreflect.Message {
mi := &file_extention_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CapabilitiesResponse.ProtoReflect.Descriptor instead.
func (*CapabilitiesResponse) Descriptor() ([]byte, []int) {
return file_extention_proto_rawDescGZIP(), []int{3}
}
func (x *CapabilitiesResponse) GetCapabilities() []string {
if x != nil {
return x.Capabilities
}
return nil
}
var File_extention_proto protoreflect.FileDescriptor
var file_extention_proto_rawDesc = []byte{
@ -181,17 +331,41 @@ var file_extention_proto_rawDesc = []byte{
0x52, 0x03, 0x61, 0x6c, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x73,
0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x73, 0x12,
0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05,
0x69, 0x74, 0x65, 0x6d, 0x73, 0x32, 0x62, 0x0a, 0x15, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x45, 0x78,
0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49,
0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1f, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x2e, 0x65,
0x78, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x2e,
0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x13, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69,
0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a,
0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1c, 0x0a,
0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66,
0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c,
0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75,
0x62, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x3a, 0x0a, 0x14, 0x43, 0x61, 0x70,
0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65,
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c,
0x69, 0x74, 0x69, 0x65, 0x73, 0x32, 0xc5, 0x01, 0x0a, 0x15, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x45,
0x78, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
0x49, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1f, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x2e,
0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73,
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x38, 0x5a, 0x36, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f,
0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x7a,
0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x0c, 0x43, 0x61,
0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x27, 0x2e, 0x61, 0x75, 0x74,
0x68, 0x7a, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e,
0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x2e, 0x65, 0x78, 0x74, 0x65,
0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c,
0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x38, 0x5a,
0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66,
0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x2f, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -206,16 +380,20 @@ func file_extention_proto_rawDescGZIP() []byte {
return file_extention_proto_rawDescData
}
var file_extention_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_extention_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_extention_proto_goTypes = []any{
(*ListRequest)(nil), // 0: authz.extention.v1.ListRequest
(*ListResponse)(nil), // 1: authz.extention.v1.ListResponse
(*CapabilitiesRequest)(nil), // 2: authz.extention.v1.CapabilitiesRequest
(*CapabilitiesResponse)(nil), // 3: authz.extention.v1.CapabilitiesResponse
}
var file_extention_proto_depIdxs = []int32{
0, // 0: authz.extention.v1.AuthzExtentionService.List:input_type -> authz.extention.v1.ListRequest
1, // 1: authz.extention.v1.AuthzExtentionService.List:output_type -> authz.extention.v1.ListResponse
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
2, // 1: authz.extention.v1.AuthzExtentionService.Capabilities:input_type -> authz.extention.v1.CapabilitiesRequest
1, // 2: authz.extention.v1.AuthzExtentionService.List:output_type -> authz.extention.v1.ListResponse
3, // 3: authz.extention.v1.AuthzExtentionService.Capabilities:output_type -> authz.extention.v1.CapabilitiesResponse
2, // [2:4] is the sub-list for method output_type
0, // [0:2] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
@ -251,6 +429,30 @@ func file_extention_proto_init() {
return nil
}
}
file_extention_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*CapabilitiesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_extention_proto_msgTypes[3].Exporter = func(v any, i int) any {
switch v := v.(*CapabilitiesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
@ -258,7 +460,7 @@ func file_extention_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_extention_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
},

@ -6,6 +6,7 @@ package authz.extention.v1;
service AuthzExtentionService {
rpc List(ListRequest) returns (ListResponse);
rpc Capabilities(CapabilitiesRequest) returns (CapabilitiesResponse);
}
message ListRequest {
@ -21,3 +22,18 @@ message ListResponse {
repeated string folders = 2;
repeated string items = 3;
}
message CapabilitiesRequest {
string subject = 1;
string group = 3;
string namespace = 5;
string resource = 4;
string name = 6;
string folder = 9;
string path = 8;
string subresource = 7;
}
message CapabilitiesResponse {
repeated string capabilities = 1;
}

@ -20,6 +20,7 @@ const _ = grpc.SupportPackageIsVersion8
const (
AuthzExtentionService_List_FullMethodName = "/authz.extention.v1.AuthzExtentionService/List"
AuthzExtentionService_Capabilities_FullMethodName = "/authz.extention.v1.AuthzExtentionService/Capabilities"
)
// AuthzExtentionServiceClient is the client API for AuthzExtentionService service.
@ -27,6 +28,7 @@ const (
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type AuthzExtentionServiceClient interface {
List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error)
Capabilities(ctx context.Context, in *CapabilitiesRequest, opts ...grpc.CallOption) (*CapabilitiesResponse, error)
}
type authzExtentionServiceClient struct {
@ -47,11 +49,22 @@ func (c *authzExtentionServiceClient) List(ctx context.Context, in *ListRequest,
return out, nil
}
func (c *authzExtentionServiceClient) Capabilities(ctx context.Context, in *CapabilitiesRequest, opts ...grpc.CallOption) (*CapabilitiesResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CapabilitiesResponse)
err := c.cc.Invoke(ctx, AuthzExtentionService_Capabilities_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// AuthzExtentionServiceServer is the server API for AuthzExtentionService service.
// All implementations should embed UnimplementedAuthzExtentionServiceServer
// for forward compatibility
type AuthzExtentionServiceServer interface {
List(context.Context, *ListRequest) (*ListResponse, error)
Capabilities(context.Context, *CapabilitiesRequest) (*CapabilitiesResponse, error)
}
// UnimplementedAuthzExtentionServiceServer should be embedded to have forward compatible implementations.
@ -61,6 +74,9 @@ type UnimplementedAuthzExtentionServiceServer struct {
func (UnimplementedAuthzExtentionServiceServer) List(context.Context, *ListRequest) (*ListResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method List not implemented")
}
func (UnimplementedAuthzExtentionServiceServer) Capabilities(context.Context, *CapabilitiesRequest) (*CapabilitiesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Capabilities not implemented")
}
// UnsafeAuthzExtentionServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to AuthzExtentionServiceServer will
@ -91,6 +107,24 @@ func _AuthzExtentionService_List_Handler(srv interface{}, ctx context.Context, d
return interceptor(ctx, in, info, handler)
}
func _AuthzExtentionService_Capabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CapabilitiesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AuthzExtentionServiceServer).Capabilities(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AuthzExtentionService_Capabilities_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AuthzExtentionServiceServer).Capabilities(ctx, req.(*CapabilitiesRequest))
}
return interceptor(ctx, in, info, handler)
}
// AuthzExtentionService_ServiceDesc is the grpc.ServiceDesc for AuthzExtentionService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -102,6 +136,10 @@ var AuthzExtentionService_ServiceDesc = grpc.ServiceDesc{
MethodName: "List",
Handler: _AuthzExtentionService_List_Handler,
},
{
MethodName: "Capabilities",
Handler: _AuthzExtentionService_Capabilities_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "extention.proto",

@ -0,0 +1,69 @@
package server
import (
"context"
authzv1 "github.com/grafana/authlib/authz/proto/v1"
"github.com/grafana/grafana/pkg/services/authz/zanzana/common"
authzextv1 "github.com/grafana/grafana/pkg/services/authz/zanzana/proto/v1"
)
func (s *Server) Capabilities(ctx context.Context, r *authzextv1.CapabilitiesRequest) (*authzextv1.CapabilitiesResponse, error) {
if info, ok := common.GetTypeInfo(r.Group, r.Resource); ok {
return s.capabilitiesTyped(ctx, r, info)
}
return s.capabilitiesGeneric(ctx, r)
}
func (s *Server) capabilitiesTyped(ctx context.Context, r *authzextv1.CapabilitiesRequest, info common.TypeInfo) (*authzextv1.CapabilitiesResponse, error) {
out := make([]string, 0, len(common.ResourceRelations))
for _, relation := range common.ResourceRelations {
res, err := s.checkTyped(ctx, &authzv1.CheckRequest{
Subject: r.Subject,
Group: r.Group,
Resource: r.Resource,
Namespace: r.Namespace,
Name: r.Name,
Folder: r.Folder,
Subresource: r.Subresource,
Path: r.Path,
}, info, relation)
if err != nil {
return nil, err
}
if res.GetAllowed() {
out = append(out, relation)
}
}
return &authzextv1.CapabilitiesResponse{Capabilities: out}, nil
}
func (s *Server) capabilitiesGeneric(ctx context.Context, r *authzextv1.CapabilitiesRequest) (*authzextv1.CapabilitiesResponse, error) {
out := make([]string, 0, len(common.ResourceRelations))
for _, relation := range common.ResourceRelations {
res, err := s.checkGeneric(ctx, &authzv1.CheckRequest{
Subject: r.Subject,
Group: r.Group,
Resource: r.Resource,
Namespace: r.Namespace,
Name: r.Name,
Folder: r.Folder,
Subresource: r.Subresource,
Path: r.Path,
}, relation)
if err != nil {
return nil, err
}
if res.GetAllowed() {
out = append(out, relation)
}
}
return &authzextv1.CapabilitiesResponse{Capabilities: out}, nil
}

@ -0,0 +1,68 @@
package server
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/authz/zanzana/common"
authzextv1 "github.com/grafana/grafana/pkg/services/authz/zanzana/proto/v1"
)
func testCapabilities(t *testing.T, server *Server) {
newReq := func(subject, group, resource, folder, name string) *authzextv1.CapabilitiesRequest {
return &authzextv1.CapabilitiesRequest{
// FIXME: namespace should map to store
// Namespace: storeID,
Subject: subject,
Group: group,
Resource: resource,
Name: name,
Folder: folder,
}
}
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, res.GetCapabilities(), []string{common.RelationRead, common.RelationWrite})
})
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, res.GetCapabilities(), []string{common.RelationRead, common.RelationWrite})
})
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, res.GetCapabilities(), []string{common.RelationRead})
})
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, res.GetCapabilities(), []string{common.RelationRead})
})
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, res.GetCapabilities(), []string{common.RelationRead, common.RelationWrite, common.RelationCreate, common.RelationDelete})
})
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, res.GetCapabilities(), []string{common.RelationRead})
})
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, res.GetCapabilities(), []string{common.RelationRead})
})
}

@ -13,15 +13,14 @@ func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.C
ctx, span := tracer.Start(ctx, "authzServer.Check")
defer span.End()
relation := common.VerbMapping[r.GetVerb()]
if info, ok := common.GetTypeInfo(r.GetGroup(), r.GetResource()); ok {
return s.checkTyped(ctx, r, info)
return s.checkTyped(ctx, r, info, relation)
}
return s.checkGeneric(ctx, r)
return s.checkGeneric(ctx, r, relation)
}
func (s *Server) checkTyped(ctx context.Context, r *authzv1.CheckRequest, info common.TypeInfo) (*authzv1.CheckResponse, error) {
relation := common.VerbMapping[r.GetVerb()]
func (s *Server) checkTyped(ctx context.Context, r *authzv1.CheckRequest, info common.TypeInfo, relation string) (*authzv1.CheckResponse, error) {
// 1. check if subject has direct access to resource
res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{
StoreId: s.storeID,
@ -57,8 +56,7 @@ func (s *Server) checkTyped(ctx context.Context, r *authzv1.CheckRequest, info c
return &authzv1.CheckResponse{Allowed: res.GetAllowed()}, nil
}
func (s *Server) checkGeneric(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.CheckResponse, error) {
relation := common.VerbMapping[r.GetVerb()]
func (s *Server) checkGeneric(ctx context.Context, r *authzv1.CheckRequest, relation string) (*authzv1.CheckResponse, error) {
// 1. check if subject has direct access to resource
res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{
StoreId: s.storeID,

@ -16,15 +16,14 @@ func (s *Server) List(ctx context.Context, r *authzextv1.ListRequest) (*authzext
ctx, span := tracer.Start(ctx, "authzServer.List")
defer span.End()
relation := common.VerbMapping[r.GetVerb()]
if info, ok := common.GetTypeInfo(r.GetGroup(), r.GetResource()); ok {
return s.listTyped(ctx, r, info)
return s.listTyped(ctx, r, info, relation)
}
return s.listGeneric(ctx, r)
return s.listGeneric(ctx, r, relation)
}
func (s *Server) listTyped(ctx context.Context, r *authzextv1.ListRequest, info common.TypeInfo) (*authzextv1.ListResponse, error) {
relation := common.VerbMapping[r.GetVerb()]
func (s *Server) listTyped(ctx context.Context, r *authzextv1.ListRequest, info common.TypeInfo, relation string) (*authzextv1.ListResponse, error) {
// 1. check if subject has access through namespace because then they can read all of them
res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{
StoreId: s.storeID,
@ -60,9 +59,7 @@ func (s *Server) listTyped(ctx context.Context, r *authzextv1.ListRequest, info
}, nil
}
func (s *Server) listGeneric(ctx context.Context, r *authzextv1.ListRequest) (*authzextv1.ListResponse, error) {
relation := common.VerbMapping[r.GetVerb()]
func (s *Server) listGeneric(ctx context.Context, r *authzextv1.ListRequest, relation string) (*authzextv1.ListResponse, error) {
// 1. check if subject has access through namespace because then they can read all of them
res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{
StoreId: s.storeID,

@ -49,6 +49,10 @@ func TestIntegrationServer(t *testing.T) {
t.Run("test list", func(t *testing.T) {
testList(t, srv)
})
t.Run("test capabilities", func(t *testing.T) {
testCapabilities(t, srv)
})
}
func setup(t *testing.T, testDB db.DB, cfg *setting.Cfg) *Server {
@ -67,14 +71,16 @@ func setup(t *testing.T, testDB db.DB, cfg *setting.Cfg) *Server {
AuthorizationModelId: srv.modelID,
Writes: &openfgav1.WriteRequestWrites{
TupleKeys: []*openfgav1.TupleKey{
common.NewResourceTuple("user:1", "read", dashboardGroup, dashboardResource, "1"),
common.NewNamespaceResourceTuple("user:2", "read", 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", "view", dashboardGroup, dashboardResource, "1"),
common.NewFolderTuple("user:6", "read", "1"),
common.NewNamespaceResourceTuple("user:7", "read", folderGroup, folderResource),
common.NewResourceTuple("user:1", common.RelationRead, dashboardGroup, dashboardResource, "1"),
common.NewResourceTuple("user:1", common.RelationWrite, dashboardGroup, dashboardResource, "1"),
common.NewNamespaceResourceTuple("user:2", common.RelationRead, dashboardGroup, dashboardResource),
common.NewNamespaceResourceTuple("user:2", common.RelationWrite, dashboardGroup, dashboardResource),
common.NewResourceTuple("user:3", common.RelationView, dashboardGroup, dashboardResource, "1"),
common.NewFolderResourceTuple("user:4", common.RelationRead, dashboardGroup, dashboardResource, "1"),
common.NewFolderResourceTuple("user:4", common.RelationRead, dashboardGroup, dashboardResource, "3"),
common.NewFolderResourceTuple("user:5", common.RelationEdit, dashboardGroup, dashboardResource, "1"),
common.NewFolderTuple("user:6", common.RelationRead, "1"),
common.NewNamespaceResourceTuple("user:7", common.RelationRead, folderGroup, folderResource),
},
},
})

@ -21,23 +21,33 @@ const (
)
const (
// Team member relations
RelationTeamMember string = "member"
RelationTeamAdmin string = "admin"
// Folder tree relations
RelationParent string = "parent"
// Role assignment relation
RelationAssignee string = "assignee"
RelationOrg string = "org"
// FIXME action sets
RelationAdmin string = "admin"
RelationRead string = "read"
RelationWrite string = "write"
RelationCreate string = "create"
RelationDelete string = "delete"
RelationPermissionsRead string = "permissions_read"
RelationPermissionsWrite string = "permissions_write"
// sets for resources
RelationView string = common.RelationView
RelationEdit string = common.RelationEdit
RelationAdmin string = common.RelationAdmin
// common relations for resources
RelationRead string = common.RelationRead
RelationWrite string = common.RelationWrite
RelationCreate string = common.RelationCreate
RelationDelete string = common.RelationDelete
RelationPermissionsRead string = common.RelationPermissionsRead
RelationPermissionsWrite string = common.RelationPermissionsWrite
)
var ResourceRelations = []string{RelationRead, RelationWrite, RelationCreate, RelationDelete, RelationPermissionsRead, RelationPermissionsWrite}
var ResourceRelations = common.ResourceRelations
const (
KindOrg string = "org"

Loading…
Cancel
Save