diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index f78b1fc5f24..bfc1470765d 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -838,14 +838,14 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr if dashboardService == nil { dashboardService, err = service.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, dashboardPermissions, - ac, folderSvc, fStore, nil, + ac, folderSvc, fStore, nil, zanzana.NewNoopClient(), ) require.NoError(t, err) } dashboardProvisioningService, err := service.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, dashboardPermissions, - ac, folderSvc, fStore, nil, + ac, folderSvc, fStore, nil, zanzana.NewNoopClient(), ) require.NoError(t, err) diff --git a/pkg/api/folder_bench_test.go b/pkg/api/folder_bench_test.go index 9581cb5795a..a2333c8dbdc 100644 --- a/pkg/api/folder_bench_test.go +++ b/pkg/api/folder_bench_test.go @@ -478,7 +478,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl( sc.cfg, dashStore, folderStore, features, folderPermissions, dashboardPermissions, ac, - folderServiceWithFlagOn, fStore, nil, + folderServiceWithFlagOn, fStore, nil, zanzana.NewNoopClient(), ) require.NoError(b, err) diff --git a/pkg/services/authz/proto/v1/extention.pb.go b/pkg/services/authz/proto/v1/extention.pb.go index 9e5fdc71d17..53b0a40b152 100644 --- a/pkg/services/authz/proto/v1/extention.pb.go +++ b/pkg/services/authz/proto/v1/extention.pb.go @@ -769,6 +769,242 @@ func (*WriteResponse) Descriptor() ([]byte, []int) { return file_extention_proto_rawDescGZIP(), []int{12} } +type BatchCheckRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` + Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + Items []*BatchCheckItem `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *BatchCheckRequest) Reset() { + *x = BatchCheckRequest{} + mi := &file_extention_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchCheckRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchCheckRequest) ProtoMessage() {} + +func (x *BatchCheckRequest) ProtoReflect() protoreflect.Message { + mi := &file_extention_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchCheckRequest.ProtoReflect.Descriptor instead. +func (*BatchCheckRequest) Descriptor() ([]byte, []int) { + return file_extention_proto_rawDescGZIP(), []int{13} +} + +func (x *BatchCheckRequest) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *BatchCheckRequest) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *BatchCheckRequest) GetItems() []*BatchCheckItem { + if x != nil { + return x.Items + } + return nil +} + +type BatchCheckItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Verb string `protobuf:"bytes,1,opt,name=verb,proto3" json:"verb,omitempty"` + Group string `protobuf:"bytes,2,opt,name=group,proto3" json:"group,omitempty"` + Resource string `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + Subresource string `protobuf:"bytes,5,opt,name=subresource,proto3" json:"subresource,omitempty"` + Folder string `protobuf:"bytes,6,opt,name=folder,proto3" json:"folder,omitempty"` +} + +func (x *BatchCheckItem) Reset() { + *x = BatchCheckItem{} + mi := &file_extention_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchCheckItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchCheckItem) ProtoMessage() {} + +func (x *BatchCheckItem) ProtoReflect() protoreflect.Message { + mi := &file_extention_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchCheckItem.ProtoReflect.Descriptor instead. +func (*BatchCheckItem) Descriptor() ([]byte, []int) { + return file_extention_proto_rawDescGZIP(), []int{14} +} + +func (x *BatchCheckItem) GetVerb() string { + if x != nil { + return x.Verb + } + return "" +} + +func (x *BatchCheckItem) GetGroup() string { + if x != nil { + return x.Group + } + return "" +} + +func (x *BatchCheckItem) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *BatchCheckItem) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *BatchCheckItem) GetSubresource() string { + if x != nil { + return x.Subresource + } + return "" +} + +func (x *BatchCheckItem) GetFolder() string { + if x != nil { + return x.Folder + } + return "" +} + +type BatchCheckResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Groups map[string]*BatchCheckGroupResource `protobuf:"bytes,1,rep,name=groups,proto3" json:"groups,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *BatchCheckResponse) Reset() { + *x = BatchCheckResponse{} + mi := &file_extention_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchCheckResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchCheckResponse) ProtoMessage() {} + +func (x *BatchCheckResponse) ProtoReflect() protoreflect.Message { + mi := &file_extention_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchCheckResponse.ProtoReflect.Descriptor instead. +func (*BatchCheckResponse) Descriptor() ([]byte, []int) { + return file_extention_proto_rawDescGZIP(), []int{15} +} + +func (x *BatchCheckResponse) GetGroups() map[string]*BatchCheckGroupResource { + if x != nil { + return x.Groups + } + return nil +} + +type BatchCheckGroupResource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items map[string]bool `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` +} + +func (x *BatchCheckGroupResource) Reset() { + *x = BatchCheckGroupResource{} + mi := &file_extention_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchCheckGroupResource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchCheckGroupResource) ProtoMessage() {} + +func (x *BatchCheckGroupResource) ProtoReflect() protoreflect.Message { + mi := &file_extention_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchCheckGroupResource.ProtoReflect.Descriptor instead. +func (*BatchCheckGroupResource) Descriptor() ([]byte, []int) { + return file_extention_proto_rawDescGZIP(), []int{16} +} + +func (x *BatchCheckGroupResource) GetItems() map[string]bool { + if x != nil { + return x.Items + } + return nil +} + var File_extention_proto protoreflect.FileDescriptor var file_extention_proto_rawDesc = []byte{ @@ -874,27 +1110,74 @@ var file_extention_proto_rawDesc = []byte{ 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x22, 0x0f, 0x0a, 0x0d, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x32, 0xfb, 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, 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, 0x49, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x1f, + 0x65, 0x22, 0x85, 0x01, 0x0a, 0x11, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, + 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, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, + 0x38, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x61, 0x64, 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, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4c, 0x0a, 0x05, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x61, 0x75, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x74, + 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xa4, 0x01, 0x0a, 0x0e, 0x42, 0x61, + 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, + 0x76, 0x65, 0x72, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x76, 0x65, 0x72, 0x62, + 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, + 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, + 0x22, 0xc8, 0x01, 0x0a, 0x12, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x2e, + 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x73, 0x1a, 0x66, 0x0a, 0x0b, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x41, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x2e, 0x65, 0x78, 0x74, 0x65, + 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xa1, 0x01, 0x0a, 0x17, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x2e, 0x65, + 0x78, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, + 0x69, 0x74, 0x65, 0x6d, 0x73, 0x1a, 0x38, 0x0a, 0x0a, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, + 0xd8, 0x02, 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, 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, 0x5b, 0x0a, 0x0a, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x12, 0x25, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x7a, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x49, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x1f, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x7a, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x61, 0x64, 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, - 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, - 0x75, 0x74, 0x68, 0x7a, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 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, + 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x05, + 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x2e, 0x65, 0x78, + 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x2e, + 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x72, 0x69, + 0x74, 0x65, 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 ( @@ -909,7 +1192,7 @@ func file_extention_proto_rawDescGZIP() []byte { return file_extention_proto_rawDescData } -var file_extention_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_extention_proto_msgTypes = make([]protoimpl.MessageInfo, 19) var file_extention_proto_goTypes = []any{ (*ListRequest)(nil), // 0: authz.extention.v1.ListRequest (*ListResponse)(nil), // 1: authz.extention.v1.ListResponse @@ -924,33 +1207,45 @@ var file_extention_proto_goTypes = []any{ (*WriteRequestDeletes)(nil), // 10: authz.extention.v1.WriteRequestDeletes (*WriteRequest)(nil), // 11: authz.extention.v1.WriteRequest (*WriteResponse)(nil), // 12: authz.extention.v1.WriteResponse - (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp - (*structpb.Struct)(nil), // 14: google.protobuf.Struct - (*wrapperspb.Int32Value)(nil), // 15: google.protobuf.Int32Value + (*BatchCheckRequest)(nil), // 13: authz.extention.v1.BatchCheckRequest + (*BatchCheckItem)(nil), // 14: authz.extention.v1.BatchCheckItem + (*BatchCheckResponse)(nil), // 15: authz.extention.v1.BatchCheckResponse + (*BatchCheckGroupResource)(nil), // 16: authz.extention.v1.BatchCheckGroupResource + nil, // 17: authz.extention.v1.BatchCheckResponse.GroupsEntry + nil, // 18: authz.extention.v1.BatchCheckGroupResource.ItemsEntry + (*timestamppb.Timestamp)(nil), // 19: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 20: google.protobuf.Struct + (*wrapperspb.Int32Value)(nil), // 21: google.protobuf.Int32Value } var file_extention_proto_depIdxs = []int32{ 5, // 0: authz.extention.v1.TupleKey.condition:type_name -> authz.extention.v1.RelationshipCondition 2, // 1: authz.extention.v1.Tuple.key:type_name -> authz.extention.v1.TupleKey - 13, // 2: authz.extention.v1.Tuple.timestamp:type_name -> google.protobuf.Timestamp - 14, // 3: authz.extention.v1.RelationshipCondition.context:type_name -> google.protobuf.Struct + 19, // 2: authz.extention.v1.Tuple.timestamp:type_name -> google.protobuf.Timestamp + 20, // 3: authz.extention.v1.RelationshipCondition.context:type_name -> google.protobuf.Struct 7, // 4: authz.extention.v1.ReadRequest.tuple_key:type_name -> authz.extention.v1.ReadRequestTupleKey - 15, // 5: authz.extention.v1.ReadRequest.page_size:type_name -> google.protobuf.Int32Value + 21, // 5: authz.extention.v1.ReadRequest.page_size:type_name -> google.protobuf.Int32Value 3, // 6: authz.extention.v1.ReadResponse.tuples:type_name -> authz.extention.v1.Tuple 2, // 7: authz.extention.v1.WriteRequestWrites.tuple_keys:type_name -> authz.extention.v1.TupleKey 4, // 8: authz.extention.v1.WriteRequestDeletes.tuple_keys:type_name -> authz.extention.v1.TupleKeyWithoutCondition 9, // 9: authz.extention.v1.WriteRequest.writes:type_name -> authz.extention.v1.WriteRequestWrites 10, // 10: authz.extention.v1.WriteRequest.deletes:type_name -> authz.extention.v1.WriteRequestDeletes - 0, // 11: authz.extention.v1.AuthzExtentionService.List:input_type -> authz.extention.v1.ListRequest - 6, // 12: authz.extention.v1.AuthzExtentionService.Read:input_type -> authz.extention.v1.ReadRequest - 11, // 13: authz.extention.v1.AuthzExtentionService.Write:input_type -> authz.extention.v1.WriteRequest - 1, // 14: authz.extention.v1.AuthzExtentionService.List:output_type -> authz.extention.v1.ListResponse - 8, // 15: authz.extention.v1.AuthzExtentionService.Read:output_type -> authz.extention.v1.ReadResponse - 12, // 16: authz.extention.v1.AuthzExtentionService.Write:output_type -> authz.extention.v1.WriteResponse - 14, // [14:17] is the sub-list for method output_type - 11, // [11:14] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 14, // 11: authz.extention.v1.BatchCheckRequest.items:type_name -> authz.extention.v1.BatchCheckItem + 17, // 12: authz.extention.v1.BatchCheckResponse.groups:type_name -> authz.extention.v1.BatchCheckResponse.GroupsEntry + 18, // 13: authz.extention.v1.BatchCheckGroupResource.items:type_name -> authz.extention.v1.BatchCheckGroupResource.ItemsEntry + 16, // 14: authz.extention.v1.BatchCheckResponse.GroupsEntry.value:type_name -> authz.extention.v1.BatchCheckGroupResource + 0, // 15: authz.extention.v1.AuthzExtentionService.List:input_type -> authz.extention.v1.ListRequest + 13, // 16: authz.extention.v1.AuthzExtentionService.BatchCheck:input_type -> authz.extention.v1.BatchCheckRequest + 6, // 17: authz.extention.v1.AuthzExtentionService.Read:input_type -> authz.extention.v1.ReadRequest + 11, // 18: authz.extention.v1.AuthzExtentionService.Write:input_type -> authz.extention.v1.WriteRequest + 1, // 19: authz.extention.v1.AuthzExtentionService.List:output_type -> authz.extention.v1.ListResponse + 15, // 20: authz.extention.v1.AuthzExtentionService.BatchCheck:output_type -> authz.extention.v1.BatchCheckResponse + 8, // 21: authz.extention.v1.AuthzExtentionService.Read:output_type -> authz.extention.v1.ReadResponse + 12, // 22: authz.extention.v1.AuthzExtentionService.Write:output_type -> authz.extention.v1.WriteResponse + 19, // [19:23] is the sub-list for method output_type + 15, // [15:19] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name } func init() { file_extention_proto_init() } @@ -964,7 +1259,7 @@ func file_extention_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_extention_proto_rawDesc, NumEnums: 0, - NumMessages: 13, + NumMessages: 19, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/services/authz/proto/v1/extention.proto b/pkg/services/authz/proto/v1/extention.proto index 0c4b67bfd23..45d1607b03c 100644 --- a/pkg/services/authz/proto/v1/extention.proto +++ b/pkg/services/authz/proto/v1/extention.proto @@ -10,6 +10,8 @@ import "google/protobuf/wrappers.proto"; service AuthzExtentionService { rpc List(ListRequest) returns (ListResponse); + rpc BatchCheck(BatchCheckRequest) returns (BatchCheckResponse); + rpc Read(ReadRequest) returns (ReadResponse); rpc Write(WriteRequest) returns (WriteResponse); } @@ -86,3 +88,26 @@ message WriteRequest { } message WriteResponse {} + +message BatchCheckRequest { + string subject = 1; + string namespace = 2; + repeated BatchCheckItem items = 3; +} + +message BatchCheckItem { + string verb = 1; + string group = 2; + string resource = 3; + string name = 4; + string subresource = 5; + string folder = 6; +} + +message BatchCheckResponse { + map groups = 1; +} + +message BatchCheckGroupResource { + map items = 1; +} diff --git a/pkg/services/authz/proto/v1/extention_grpc.pb.go b/pkg/services/authz/proto/v1/extention_grpc.pb.go index 7b73cf22306..ee814e97a0f 100644 --- a/pkg/services/authz/proto/v1/extention_grpc.pb.go +++ b/pkg/services/authz/proto/v1/extention_grpc.pb.go @@ -19,9 +19,10 @@ import ( const _ = grpc.SupportPackageIsVersion8 const ( - AuthzExtentionService_List_FullMethodName = "/authz.extention.v1.AuthzExtentionService/List" - AuthzExtentionService_Read_FullMethodName = "/authz.extention.v1.AuthzExtentionService/Read" - AuthzExtentionService_Write_FullMethodName = "/authz.extention.v1.AuthzExtentionService/Write" + AuthzExtentionService_List_FullMethodName = "/authz.extention.v1.AuthzExtentionService/List" + AuthzExtentionService_BatchCheck_FullMethodName = "/authz.extention.v1.AuthzExtentionService/BatchCheck" + AuthzExtentionService_Read_FullMethodName = "/authz.extention.v1.AuthzExtentionService/Read" + AuthzExtentionService_Write_FullMethodName = "/authz.extention.v1.AuthzExtentionService/Write" ) // AuthzExtentionServiceClient is the client API for AuthzExtentionService service. @@ -29,6 +30,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) + BatchCheck(ctx context.Context, in *BatchCheckRequest, opts ...grpc.CallOption) (*BatchCheckResponse, error) Read(ctx context.Context, in *ReadRequest, opts ...grpc.CallOption) (*ReadResponse, error) Write(ctx context.Context, in *WriteRequest, opts ...grpc.CallOption) (*WriteResponse, error) } @@ -51,6 +53,16 @@ func (c *authzExtentionServiceClient) List(ctx context.Context, in *ListRequest, return out, nil } +func (c *authzExtentionServiceClient) BatchCheck(ctx context.Context, in *BatchCheckRequest, opts ...grpc.CallOption) (*BatchCheckResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BatchCheckResponse) + err := c.cc.Invoke(ctx, AuthzExtentionService_BatchCheck_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *authzExtentionServiceClient) Read(ctx context.Context, in *ReadRequest, opts ...grpc.CallOption) (*ReadResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ReadResponse) @@ -76,6 +88,7 @@ func (c *authzExtentionServiceClient) Write(ctx context.Context, in *WriteReques // for forward compatibility type AuthzExtentionServiceServer interface { List(context.Context, *ListRequest) (*ListResponse, error) + BatchCheck(context.Context, *BatchCheckRequest) (*BatchCheckResponse, error) Read(context.Context, *ReadRequest) (*ReadResponse, error) Write(context.Context, *WriteRequest) (*WriteResponse, error) } @@ -87,6 +100,9 @@ type UnimplementedAuthzExtentionServiceServer struct { func (UnimplementedAuthzExtentionServiceServer) List(context.Context, *ListRequest) (*ListResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method List not implemented") } +func (UnimplementedAuthzExtentionServiceServer) BatchCheck(context.Context, *BatchCheckRequest) (*BatchCheckResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BatchCheck not implemented") +} func (UnimplementedAuthzExtentionServiceServer) Read(context.Context, *ReadRequest) (*ReadResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Read not implemented") } @@ -123,6 +139,24 @@ func _AuthzExtentionService_List_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _AuthzExtentionService_BatchCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BatchCheckRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthzExtentionServiceServer).BatchCheck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthzExtentionService_BatchCheck_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthzExtentionServiceServer).BatchCheck(ctx, req.(*BatchCheckRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _AuthzExtentionService_Read_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ReadRequest) if err := dec(in); err != nil { @@ -170,6 +204,10 @@ var AuthzExtentionService_ServiceDesc = grpc.ServiceDesc{ MethodName: "List", Handler: _AuthzExtentionService_List_Handler, }, + { + MethodName: "BatchCheck", + Handler: _AuthzExtentionService_BatchCheck_Handler, + }, { MethodName: "Read", Handler: _AuthzExtentionService_Read_Handler, diff --git a/pkg/services/authz/zanzana/client.go b/pkg/services/authz/zanzana/client.go index 9bb9fca456e..e7b6a7a2715 100644 --- a/pkg/services/authz/zanzana/client.go +++ b/pkg/services/authz/zanzana/client.go @@ -16,6 +16,7 @@ type Client interface { List(ctx context.Context, id claims.AuthInfo, req authz.ListRequest) (*authzextv1.ListResponse, error) Read(ctx context.Context, req *authzextv1.ReadRequest) (*authzextv1.ReadResponse, error) Write(ctx context.Context, req *authzextv1.WriteRequest) error + BatchCheck(ctx context.Context, req *authzextv1.BatchCheckRequest) (*authzextv1.BatchCheckResponse, error) } func NewNoopClient() *client.NoopClient { diff --git a/pkg/services/authz/zanzana/client/client.go b/pkg/services/authz/zanzana/client/client.go index 819cf2b91a5..784a95e5a77 100644 --- a/pkg/services/authz/zanzana/client/client.go +++ b/pkg/services/authz/zanzana/client/client.go @@ -169,3 +169,10 @@ func (c *Client) Write(ctx context.Context, req *authzextv1.WriteRequest) error _, err := c.authzext.Write(ctx, req) return err } + +func (c *Client) BatchCheck(ctx context.Context, req *authzextv1.BatchCheckRequest) (*authzextv1.BatchCheckResponse, error) { + ctx, span := tracer.Start(ctx, "authz.zanzana.client.Check") + defer span.End() + + return c.authzext.BatchCheck(ctx, req) +} diff --git a/pkg/services/authz/zanzana/client/noop.go b/pkg/services/authz/zanzana/client/noop.go index 40d12ac0496..3ea571c6bd2 100644 --- a/pkg/services/authz/zanzana/client/noop.go +++ b/pkg/services/authz/zanzana/client/noop.go @@ -36,3 +36,7 @@ func (nc NoopClient) Read(ctx context.Context, req *authzextv1.ReadRequest) (*au func (nc NoopClient) Write(ctx context.Context, req *authzextv1.WriteRequest) error { return nil } + +func (nc NoopClient) BatchCheck(ctx context.Context, req *authzextv1.BatchCheckRequest) (*authzextv1.BatchCheckResponse, error) { + return nil, nil +} diff --git a/pkg/services/authz/zanzana/server/server_batch_check.go b/pkg/services/authz/zanzana/server/server_batch_check.go new file mode 100644 index 00000000000..22b6b3ecec1 --- /dev/null +++ b/pkg/services/authz/zanzana/server/server_batch_check.go @@ -0,0 +1,64 @@ +package server + +import ( + "context" + + authzv1 "github.com/grafana/authlib/authz/proto/v1" + + authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" + "github.com/grafana/grafana/pkg/services/authz/zanzana/common" +) + +func (s *Server) BatchCheck(ctx context.Context, r *authzextv1.BatchCheckRequest) (*authzextv1.BatchCheckResponse, error) { + ctx, span := tracer.Start(ctx, "authzServer.BatchCheck") + defer span.End() + + batchRes := &authzextv1.BatchCheckResponse{ + Groups: make(map[string]*authzextv1.BatchCheckGroupResource), + } + + subject := r.GetSubject() + + for _, item := range r.Items { + groupPrefix := common.FormatGroupResource(item.GetGroup(), item.GetResource()) + allowed, err := s.batchCheckItem(ctx, subject, r.Namespace, item) + if err != nil { + return nil, err + } + + if _, ok := batchRes.Groups[groupPrefix]; !ok { + batchRes.Groups[groupPrefix] = &authzextv1.BatchCheckGroupResource{ + Items: make(map[string]bool), + } + } + batchRes.Groups[groupPrefix].Items[item.GetName()] = allowed + } + + return batchRes, nil +} + +func (s *Server) batchCheckItem(ctx context.Context, subject string, namespace string, item *authzextv1.BatchCheckItem) (bool, error) { + req := &authzv1.CheckRequest{ + Namespace: namespace, + Subject: subject, + Verb: item.GetVerb(), + Group: item.GetGroup(), + Resource: item.GetResource(), + Name: item.GetName(), + Folder: item.GetFolder(), + Subresource: item.GetSubresource(), + } + + var res *authzv1.CheckResponse + var err error + if info, ok := common.GetTypeInfo(item.GetGroup(), item.GetResource()); ok { + res, err = s.checkTyped(ctx, req, info) + } else { + res, err = s.checkGeneric(ctx, req) + } + if err != nil { + return false, err + } + + return res.Allowed, nil +} diff --git a/pkg/services/authz/zanzana/server/server_batch_check_test.go b/pkg/services/authz/zanzana/server/server_batch_check_test.go new file mode 100644 index 00000000000..9b64be2651f --- /dev/null +++ b/pkg/services/authz/zanzana/server/server_batch_check_test.go @@ -0,0 +1,120 @@ +package server + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "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" +) + +func newBatch(subject, group, resource string, items []*authzextv1.BatchCheckItem) *authzextv1.BatchCheckRequest { + for i, item := range items { + items[i] = &authzextv1.BatchCheckItem{ + Verb: utils.VerbGet, + Group: group, + Resource: resource, + Name: item.GetName(), + Folder: item.GetFolder(), + } + } + + return &authzextv1.BatchCheckRequest{ + Namespace: "default", + Subject: subject, + Items: items, + } +} + +func testBatchCheck(t *testing.T, server *Server) { + t.Run("user:1 should only be able to read resource:dashboards.grafana.app/dashboards/1", func(t *testing.T) { + groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) + res, err := server.BatchCheck(context.Background(), newBatch("user:1", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ + {Name: "1", Folder: "1"}, + {Name: "2", Folder: "2"}, + })) + require.NoError(t, err) + require.Len(t, res.Groups[groupPrefix].Items, 2) + + assert.True(t, res.Groups[groupPrefix].Items["1"]) + assert.False(t, res.Groups[groupPrefix].Items["2"]) + }) + + t.Run("user:2 should be able to read resource:dashboards.grafana.app/dashboards/{1,2} through namespace", func(t *testing.T) { + groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) + res, err := server.BatchCheck(context.Background(), newBatch("user:2", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ + {Name: "1", Folder: "1"}, + {Name: "2", Folder: "2"}, + })) + require.NoError(t, err) + assert.Len(t, res.Groups[groupPrefix].Items, 2) + }) + + t.Run("user:3 should be able to read resource:dashboards.grafana.app/dashboards/1 with set relation", func(t *testing.T) { + groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) + res, err := server.BatchCheck(context.Background(), newBatch("user:3", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ + {Name: "1", Folder: "1"}, + {Name: "2", Folder: "2"}, + })) + require.NoError(t, err) + require.Len(t, res.Groups[groupPrefix].Items, 2) + + assert.True(t, res.Groups[groupPrefix].Items["1"]) + assert.False(t, res.Groups[groupPrefix].Items["2"]) + }) + + t.Run("user:4 should be able to read all dashboards.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) { + groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) + res, err := server.BatchCheck(context.Background(), newBatch("user:4", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ + {Name: "1", Folder: "1"}, + {Name: "2", Folder: "3"}, + {Name: "3", Folder: "2"}, + })) + require.NoError(t, err) + require.Len(t, res.Groups[groupPrefix].Items, 3) + + assert.True(t, res.Groups[groupPrefix].Items["1"]) + assert.True(t, res.Groups[groupPrefix].Items["2"]) + assert.False(t, res.Groups[groupPrefix].Items["3"]) + }) + + t.Run("user:5 should be able to read resource:dashboards.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) { + groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) + res, err := server.BatchCheck(context.Background(), newBatch("user:5", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ + {Name: "1", Folder: "1"}, + {Name: "2", Folder: "2"}, + })) + require.NoError(t, err) + require.Len(t, res.Groups[groupPrefix].Items, 2) + + assert.True(t, res.Groups[groupPrefix].Items["1"]) + assert.False(t, res.Groups[groupPrefix].Items["2"]) + }) + + t.Run("user:6 should be able to read folder 1", func(t *testing.T) { + groupPrefix := zanzana.FormatGroupResource(folderGroup, folderResource) + res, err := server.BatchCheck(context.Background(), newBatch("user:6", folderGroup, folderResource, []*authzextv1.BatchCheckItem{ + {Name: "1"}, + {Name: "2"}, + })) + require.NoError(t, err) + require.Len(t, res.Groups[groupPrefix].Items, 2) + + assert.True(t, res.Groups[groupPrefix].Items["1"]) + assert.False(t, res.Groups[groupPrefix].Items["2"]) + }) + + t.Run("user:7 should be able to read folder {1,2} through namespace access", func(t *testing.T) { + groupPrefix := zanzana.FormatGroupResource(folderGroup, folderResource) + res, err := server.BatchCheck(context.Background(), newBatch("user:7", folderGroup, folderResource, []*authzextv1.BatchCheckItem{ + {Name: "1"}, + {Name: "2"}, + })) + require.NoError(t, err) + require.Len(t, res.Groups[groupPrefix].Items, 2) + }) +} diff --git a/pkg/services/authz/zanzana/server/server_check.go b/pkg/services/authz/zanzana/server/server_check.go index 3bb77aaf182..f0e78045292 100644 --- a/pkg/services/authz/zanzana/server/server_check.go +++ b/pkg/services/authz/zanzana/server/server_check.go @@ -20,6 +20,31 @@ func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.C return s.checkGeneric(ctx, r) } +// checkNamespace checks if subject has access through namespace +func (s *Server) checkNamespace(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.CheckResponse, error) { + storeInf, err := s.getStoreInfo(ctx, r.Namespace) + if err != nil { + return nil, err + } + + relation := common.VerbMapping[r.GetVerb()] + + res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{ + StoreId: storeInf.Id, + AuthorizationModelId: storeInf.AuthorizationModelId, + TupleKey: &openfgav1.CheckRequestTupleKey{ + User: r.GetSubject(), + Relation: relation, + Object: common.NewNamespaceResourceIdent(r.GetGroup(), r.GetResource()), + }, + }) + if err != nil { + return nil, err + } + + return &authzv1.CheckResponse{Allowed: res.GetAllowed()}, nil +} + func (s *Server) checkTyped(ctx context.Context, r *authzv1.CheckRequest, info common.TypeInfo) (*authzv1.CheckResponse, error) { storeInf, err := s.getStoreInfo(ctx, r.Namespace) if err != nil { @@ -47,20 +72,12 @@ func (s *Server) checkTyped(ctx context.Context, r *authzv1.CheckRequest, info c } // 2. check if subject has access through namespace - res, err = s.openfga.Check(ctx, &openfgav1.CheckRequest{ - StoreId: storeInf.Id, - AuthorizationModelId: storeInf.AuthorizationModelId, - TupleKey: &openfgav1.CheckRequestTupleKey{ - User: r.GetSubject(), - Relation: relation, - Object: common.NewNamespaceResourceIdent(r.GetGroup(), r.GetResource()), - }, - }) + nsRes, err := s.checkNamespace(ctx, r) if err != nil { return nil, err } - return &authzv1.CheckResponse{Allowed: res.GetAllowed()}, nil + return &authzv1.CheckResponse{Allowed: nsRes.GetAllowed()}, nil } func (s *Server) checkGeneric(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.CheckResponse, error) { @@ -96,21 +113,12 @@ func (s *Server) checkGeneric(ctx context.Context, r *authzv1.CheckRequest) (*au } // 2. check if subject has access through namespace - res, err = s.openfga.Check(ctx, &openfgav1.CheckRequest{ - StoreId: storeInf.Id, - AuthorizationModelId: storeInf.AuthorizationModelId, - TupleKey: &openfgav1.CheckRequestTupleKey{ - User: r.GetSubject(), - Relation: relation, - Object: common.NewNamespaceResourceIdent(r.GetGroup(), r.GetResource()), - }, - }) - + nsRes, err := s.checkNamespace(ctx, r) if err != nil { return nil, err } - if res.GetAllowed() { + if nsRes.GetAllowed() { return &authzv1.CheckResponse{Allowed: true}, nil } diff --git a/pkg/services/authz/zanzana/server/server_test.go b/pkg/services/authz/zanzana/server/server_test.go index 9e04ceb117b..afa49bb6757 100644 --- a/pkg/services/authz/zanzana/server/server_test.go +++ b/pkg/services/authz/zanzana/server/server_test.go @@ -49,6 +49,10 @@ func TestIntegrationServer(t *testing.T) { t.Run("test list", func(t *testing.T) { testList(t, srv) }) + + t.Run("test batch check", func(t *testing.T) { + testBatchCheck(t, srv) + }) } func setup(t *testing.T, testDB db.DB, cfg *setting.Cfg) *Server { diff --git a/pkg/services/authz/zanzana/zanzana.go b/pkg/services/authz/zanzana/zanzana.go index 2001d248780..52a63697cb4 100644 --- a/pkg/services/authz/zanzana/zanzana.go +++ b/pkg/services/authz/zanzana/zanzana.go @@ -83,6 +83,8 @@ var ( ToOpenFGATuples = common.ToOpenFGATuples ToOpenFGATupleKey = common.ToOpenFGATupleKey ToOpenFGATupleKeyWithoutCondition = common.ToOpenFGATupleKeyWithoutCondition + + FormatGroupResource = common.FormatGroupResource ) // NewTupleEntry constructs new openfga entry type:name[#relation]. @@ -166,6 +168,14 @@ func TranslateToCheckRequest(namespace, action, kind, folder, name string) (*aut return req, true } +func TranslateToGroupResource(kind string) string { + translation, ok := resourceTranslations[kind] + if !ok { + return "" + } + return common.FormatGroupResource(translation.group, translation.resource) +} + func TranslateBasicRole(name string) string { return basicRolesTranslations[name] } diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 168400e5b95..6f8def67f13 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -19,6 +19,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" "github.com/grafana/grafana/pkg/services/datasources" @@ -59,6 +60,7 @@ type DashboardServiceImpl struct { folderPermissions accesscontrol.FolderPermissionsService dashboardPermissions accesscontrol.DashboardPermissionsService ac accesscontrol.AccessControl + zclient zanzana.Client metrics *dashboardsMetrics } @@ -67,7 +69,7 @@ func ProvideDashboardServiceImpl( cfg *setting.Cfg, dashboardStore dashboards.Store, folderStore folder.FolderStore, features featuremgmt.FeatureToggles, folderPermissionsService accesscontrol.FolderPermissionsService, dashboardPermissionsService accesscontrol.DashboardPermissionsService, ac accesscontrol.AccessControl, - folderSvc folder.Service, fStore folder.Store, r prometheus.Registerer, + folderSvc folder.Service, fStore folder.Store, r prometheus.Registerer, zclient zanzana.Client, ) (*DashboardServiceImpl, error) { dashSvc := &DashboardServiceImpl{ cfg: cfg, @@ -77,6 +79,7 @@ func ProvideDashboardServiceImpl( folderPermissions: folderPermissionsService, dashboardPermissions: dashboardPermissionsService, ac: ac, + zclient: zclient, folderStore: folderStore, folderService: folderSvc, metrics: newDashboardsMetrics(r), diff --git a/pkg/services/dashboards/service/dashboard_service_integration_test.go b/pkg/services/dashboards/service/dashboard_service_integration_test.go index 8333cdf0d4d..1ab159bc0b5 100644 --- a/pkg/services/dashboards/service/dashboard_service_integration_test.go +++ b/pkg/services/dashboards/service/dashboard_service_integration_test.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" + "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -883,6 +884,7 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc foldertest.NewFakeService(), folder.NewFakeStore(), nil, + zanzana.NewNoopClient(), ) require.NoError(t, err) guardian.InitAccessControlGuardian(cfg, ac, dashboardService) @@ -949,6 +951,7 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt foldertest.NewFakeService(), folder.NewFakeStore(), nil, + zanzana.NewNoopClient(), ) require.NoError(t, err) res, err := service.SaveDashboard(context.Background(), &dto, false) @@ -974,6 +977,7 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto foldertest.NewFakeService(), folder.NewFakeStore(), nil, + zanzana.NewNoopClient(), ) require.NoError(t, err) _, err = service.SaveDashboard(context.Background(), &dto, false) @@ -1018,6 +1022,7 @@ func saveTestDashboard(t *testing.T, title string, orgID int64, folderUID string foldertest.NewFakeService(), folder.NewFakeStore(), nil, + zanzana.NewNoopClient(), ) require.NoError(t, err) res, err := service.SaveDashboard(context.Background(), &dto, false) @@ -1069,6 +1074,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da foldertest.NewFakeService(), folder.NewFakeStore(), nil, + zanzana.NewNoopClient(), ) require.NoError(t, err) res, err := service.SaveDashboard(context.Background(), &dto, false) diff --git a/pkg/services/dashboards/service/zanzana.go b/pkg/services/dashboards/service/zanzana.go index 5d72c776742..468032c6213 100644 --- a/pkg/services/dashboards/service/zanzana.go +++ b/pkg/services/dashboards/service/zanzana.go @@ -6,9 +6,15 @@ import ( "github.com/prometheus/client_golang/prometheus" + authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" + "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/dashboards" ) +const ( + defaultQueryLimit = 1000 +) + type searchResult struct { runner string result []dashboards.DashboardSearchProjection @@ -79,7 +85,113 @@ func (dr *DashboardServiceImpl) findDashboardsZanzanaCompare(ctx context.Context return first.result, first.err } -func (dr *DashboardServiceImpl) findDashboardsZanzana(_ context.Context, _ dashboards.FindPersistedDashboardsQuery) ([]dashboards.DashboardSearchProjection, error) { - // FIXME: Implement using the new schema - return []dashboards.DashboardSearchProjection{}, nil +func (dr *DashboardServiceImpl) findDashboardsZanzana(ctx context.Context, query dashboards.FindPersistedDashboardsQuery) ([]dashboards.DashboardSearchProjection, error) { + return dr.findDashboardsZanzanaCheck(ctx, query) +} + +// findDashboardsZanzanaCheck implements "Search, then check" strategy. It first performs search query, then filters out results +// by checking access to each item. +func (dr *DashboardServiceImpl) findDashboardsZanzanaCheck(ctx context.Context, query dashboards.FindPersistedDashboardsQuery) ([]dashboards.DashboardSearchProjection, error) { + ctx, span := tracer.Start(ctx, "dashboards.service.findDashboardsZanzanaCheck") + defer span.End() + + result := make([]dashboards.DashboardSearchProjection, 0, query.Limit) + + query.SkipAccessControlFilter = true + // Remember initial query limit + limit := query.Limit + // Set limit to default to prevent pagination issues + query.Limit = defaultQueryLimit + if query.Page == 0 { + query.Page = 1 + } + + for len(result) < int(limit) { + findRes, err := dr.dashboardStore.FindDashboards(ctx, &query) + if err != nil { + return nil, err + } + + remains := limit - int64(len(result)) + res, err := dr.checkDashboardsBatch(ctx, query, findRes, remains) + if err != nil { + return nil, err + } + + result = append(result, res...) + query.Page++ + + // Stop when last page reached + if len(findRes) < defaultQueryLimit { + break + } + } + + return result, nil +} + +func (dr *DashboardServiceImpl) checkDashboardsBatch(ctx context.Context, query dashboards.FindPersistedDashboardsQuery, searchRes []dashboards.DashboardSearchProjection, remains int64) ([]dashboards.DashboardSearchProjection, error) { + ctx, span := tracer.Start(ctx, "dashboards.service.checkDashboardsBatch") + defer span.End() + + if len(searchRes) == 0 { + return nil, nil + } + + batchReqItems := make([]*authzextv1.BatchCheckItem, 0, len(searchRes)) + + for _, d := range searchRes { + // FIXME: support different access levels + kind := zanzana.KindDashboards + action := dashboards.ActionDashboardsRead + if d.IsFolder { + kind = zanzana.KindFolders + action = dashboards.ActionFoldersRead + } + + checkReq, ok := zanzana.TranslateToCheckRequest("", action, kind, d.FolderUID, d.UID) + if !ok { + continue + } + + batchReqItems = append(batchReqItems, &authzextv1.BatchCheckItem{ + Verb: checkReq.Verb, + Group: checkReq.Group, + Resource: checkReq.Resource, + Name: checkReq.Name, + Folder: checkReq.Folder, + Subresource: checkReq.Subresource, + }) + } + + batchReq := authzextv1.BatchCheckRequest{ + Namespace: query.SignedInUser.GetNamespace(), + Subject: query.SignedInUser.GetUID(), + Items: batchReqItems, + } + + res, err := dr.zclient.BatchCheck(ctx, &batchReq) + if err != nil { + return nil, err + } + + result := make([]dashboards.DashboardSearchProjection, 0) + for _, d := range searchRes { + if len(result) >= int(remains) { + break + } + + kind := zanzana.KindDashboards + if d.IsFolder { + kind = zanzana.KindFolders + } + groupResource := zanzana.TranslateToGroupResource(kind) + if group, ok := res.Groups[groupResource]; ok { + if allowed := group.Items[d.UID]; allowed { + result = append(result, d) + } + } + } + + return result, nil } diff --git a/pkg/services/dashboardsnapshots/service/service_test.go b/pkg/services/dashboardsnapshots/service/service_test.go index c8fad1ad7d6..13ce9928ead 100644 --- a/pkg/services/dashboardsnapshots/service/service_test.go +++ b/pkg/services/dashboardsnapshots/service/service_test.go @@ -12,6 +12,7 @@ import ( dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1" "github.com/grafana/grafana/pkg/infra/db" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" + "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/dashboards" dashdb "github.com/grafana/grafana/pkg/services/dashboards/database" dashsvc "github.com/grafana/grafana/pkg/services/dashboards/service" @@ -99,7 +100,7 @@ func TestValidateDashboardExists(t *testing.T) { secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore)) dashboardStore, err := dashdb.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotatest.New(false, nil)) require.NoError(t, err) - dashSvc, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashboardStore, folderimpl.ProvideDashboardFolderStore(sqlStore), nil, nil, nil, acmock.New(), foldertest.NewFakeService(), folder.NewFakeStore(), nil) + dashSvc, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashboardStore, folderimpl.ProvideDashboardFolderStore(sqlStore), nil, nil, nil, acmock.New(), foldertest.NewFakeService(), folder.NewFakeStore(), nil, zanzana.NewNoopClient()) require.NoError(t, err) s := ProvideService(dsStore, secretsService, dashSvc) ctx := context.Background() diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index 017863a36c5..f75f426c3a3 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -490,7 +490,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { CanEditValue: true, }) - dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn, nestedFolderStore, nil) + dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn, nestedFolderStore, nil, zanzana.NewNoopClient()) require.NoError(t, err) alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, dashSrv, ac, b) @@ -573,7 +573,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { }) dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOff, - folderPermissions, dashboardPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil) + folderPermissions, dashboardPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil, zanzana.NewNoopClient()) require.NoError(t, err) alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, dashSrv, ac, b) @@ -719,7 +719,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { tc.service.dashboardStore = dashStore tc.service.store = nestedFolderStore - dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, dashboardPermissions, ac, tc.service, tc.service.store, nil) + dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, dashboardPermissions, ac, tc.service, tc.service.store, nil, zanzana.NewNoopClient()) require.NoError(t, err) alertStore, err := ngstore.ProvideDBStore(cfg, tc.featuresFlag, db, tc.service, dashSrv, ac, b) require.NoError(t, err) @@ -1504,6 +1504,7 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { serviceWithFlagOn, nestedFolderStore, nil, + zanzana.NewNoopClient(), ) require.NoError(t, err) diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 94e19f86380..4de7051404c 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -310,6 +310,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash foldertest.NewFakeService(), folder.NewFakeStore(), nil, + zanzana.NewNoopClient(), ) require.NoError(t, err) dashboard, err := service.SaveDashboard(context.Background(), dashItem, true) @@ -396,7 +397,7 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena cfg, dashboardStore, folderStore, features, folderPermissions, dashboardPermissions, ac, foldertest.NewFakeService(), folder.NewFakeStore(), - nil, + nil, zanzana.NewNoopClient(), ) require.NoError(t, svcErr) guardian.InitAccessControlGuardian(cfg, ac, dashboardService) @@ -458,7 +459,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo cfg, dashboardStore, folderStore, features, folderPermissions, dashboardPermissions, ac, foldertest.NewFakeService(), folder.NewFakeStore(), - nil, + nil, zanzana.NewNoopClient(), ) require.NoError(t, dashSvcErr) guardian.InitAccessControlGuardian(cfg, ac, dashService) diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index f61304f9837..99bd097c883 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -7,6 +7,9 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/bus" @@ -19,6 +22,7 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol/testutil" + "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/database" dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service" @@ -38,8 +42,6 @@ import ( "github.com/grafana/grafana/pkg/services/user/userimpl" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tests/testsuite" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" ) const userInDbName = "user_in_db" @@ -734,7 +736,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash cfg, dashboardStore, folderStore, featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), dashPermissionService, ac, foldertest.NewFakeService(), folder.NewFakeStore(), - nil, + nil, zanzana.NewNoopClient(), ) require.NoError(t, err) dashboard, err := service.SaveDashboard(context.Background(), dashItem, true) @@ -831,7 +833,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo cfg, dashStore, folderStore, features, acmock.NewMockedPermissionsService(), dashPermissionService, ac, foldertest.NewFakeService(), folder.NewFakeStore(), - nil, + nil, zanzana.NewNoopClient(), ) require.NoError(t, err) guardian.InitAccessControlGuardian(cfg, ac, dashService) diff --git a/pkg/services/ngalert/testutil/testutil.go b/pkg/services/ngalert/testutil/testutil.go index 417680c39c2..d74d6e5c2ef 100644 --- a/pkg/services/ngalert/testutil/testutil.go +++ b/pkg/services/ngalert/testutil/testutil.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" + "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/database" dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service" @@ -61,7 +62,7 @@ func SetupDashboardService(tb testing.TB, sqlStore db.DB, fs *folderimpl.Dashboa cfg, dashboardStore, fs, features, folderPermissions, dashboardPermissions, ac, foldertest.NewFakeService(), folder.NewFakeStore(), - nil, + nil, zanzana.NewNoopClient(), ) require.NoError(tb, err) diff --git a/pkg/services/publicdashboards/api/query_test.go b/pkg/services/publicdashboards/api/query_test.go index e12350ddba4..e94df756efa 100644 --- a/pkg/services/publicdashboards/api/query_test.go +++ b/pkg/services/publicdashboards/api/query_test.go @@ -10,12 +10,13 @@ import ( "testing" "github.com/aws/aws-sdk-go/aws" - "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/components/simplejson" @@ -24,6 +25,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/annotations/annotationstest" + "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/dashboards" dashboardStore "github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/dashboards/service" @@ -324,7 +326,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) dashService, err := service.ProvideDashboardServiceImpl( cfg, dashboardStoreService, folderStore, featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), dashPermissionService, ac, - foldertest.NewFakeService(), folder.NewFakeStore(), nil, + foldertest.NewFakeService(), folder.NewFakeStore(), nil, zanzana.NewNoopClient(), ) require.NoError(t, err)