SMQ-2568 - Check Domain enabled / disabled status during Authn or Authz (#2586)

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
Signed-off-by: Arvindh <arvindh91@gmail.com>
Co-authored-by: Arvindh <arvindh91@gmail.com>
This commit is contained in:
Felix Gateru
2024-12-24 11:57:32 +03:00
committed by GitHub
parent 4f73a52192
commit 8e552d0a96
50 changed files with 1348 additions and 309 deletions
+32 -20
View File
@@ -10,6 +10,7 @@
package v1
import (
v1 "github.com/absmach/supermq/api/grpc/common/v1"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
@@ -116,21 +117,28 @@ var File_domains_v1_domains_proto protoreflect.FileDescriptor
var file_domains_v1_domains_proto_rawDesc = []byte{
0x0a, 0x18, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x64, 0x6f, 0x6d, 0x61,
0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x22, 0x29, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74,
0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x64, 0x22, 0x1f, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52,
0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
0x69, 0x64, 0x32, 0x61, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73,
0x65, 0x72, 0x46, 0x72, 0x6f, 0x6d, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x19, 0x2e,
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74,
0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69,
0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72,
0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x62, 0x73, 0x6d, 0x61, 0x63, 0x68, 0x2f, 0x73, 0x75, 0x70, 0x65,
0x72, 0x6d, 0x71, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x16, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76,
0x31, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x29,
0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x12,
0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x1f, 0x0a, 0x0d, 0x44, 0x65, 0x6c,
0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x32, 0xb1, 0x01, 0x0a, 0x0e, 0x44,
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a,
0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x46, 0x72, 0x6f, 0x6d, 0x44,
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x19, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73,
0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65,
0x71, 0x1a, 0x19, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4e,
0x0a, 0x0e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79,
0x12, 0x1c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74,
0x72, 0x69, 0x65, 0x76, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1c,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69,
0x65, 0x76, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x35,
0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x62, 0x73,
0x6d, 0x61, 0x63, 0x68, 0x2f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x6d, 0x71, 0x2f, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69,
0x6e, 0x73, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -147,14 +155,18 @@ func file_domains_v1_domains_proto_rawDescGZIP() []byte {
var file_domains_v1_domains_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_domains_v1_domains_proto_goTypes = []any{
(*DeleteUserRes)(nil), // 0: domains.v1.DeleteUserRes
(*DeleteUserReq)(nil), // 1: domains.v1.DeleteUserReq
(*DeleteUserRes)(nil), // 0: domains.v1.DeleteUserRes
(*DeleteUserReq)(nil), // 1: domains.v1.DeleteUserReq
(*v1.RetrieveEntityReq)(nil), // 2: common.v1.RetrieveEntityReq
(*v1.RetrieveEntityRes)(nil), // 3: common.v1.RetrieveEntityRes
}
var file_domains_v1_domains_proto_depIdxs = []int32{
1, // 0: domains.v1.DomainsService.DeleteUserFromDomains:input_type -> domains.v1.DeleteUserReq
0, // 1: domains.v1.DomainsService.DeleteUserFromDomains:output_type -> domains.v1.DeleteUserRes
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
2, // 1: domains.v1.DomainsService.RetrieveEntity:input_type -> common.v1.RetrieveEntityReq
0, // 2: domains.v1.DomainsService.DeleteUserFromDomains:output_type -> domains.v1.DeleteUserRes
3, // 3: domains.v1.DomainsService.RetrieveEntity:output_type -> common.v1.RetrieveEntityRes
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
+39
View File
@@ -11,6 +11,7 @@ package v1
import (
context "context"
v1 "github.com/absmach/supermq/api/grpc/common/v1"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
@@ -23,6 +24,7 @@ const _ = grpc.SupportPackageIsVersion9
const (
DomainsService_DeleteUserFromDomains_FullMethodName = "/domains.v1.DomainsService/DeleteUserFromDomains"
DomainsService_RetrieveEntity_FullMethodName = "/domains.v1.DomainsService/RetrieveEntity"
)
// DomainsServiceClient is the client API for DomainsService service.
@@ -33,6 +35,7 @@ const (
// domains functionalities for SuperMQ services.
type DomainsServiceClient interface {
DeleteUserFromDomains(ctx context.Context, in *DeleteUserReq, opts ...grpc.CallOption) (*DeleteUserRes, error)
RetrieveEntity(ctx context.Context, in *v1.RetrieveEntityReq, opts ...grpc.CallOption) (*v1.RetrieveEntityRes, error)
}
type domainsServiceClient struct {
@@ -53,6 +56,16 @@ func (c *domainsServiceClient) DeleteUserFromDomains(ctx context.Context, in *De
return out, nil
}
func (c *domainsServiceClient) RetrieveEntity(ctx context.Context, in *v1.RetrieveEntityReq, opts ...grpc.CallOption) (*v1.RetrieveEntityRes, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(v1.RetrieveEntityRes)
err := c.cc.Invoke(ctx, DomainsService_RetrieveEntity_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// DomainsServiceServer is the server API for DomainsService service.
// All implementations must embed UnimplementedDomainsServiceServer
// for forward compatibility.
@@ -61,6 +74,7 @@ func (c *domainsServiceClient) DeleteUserFromDomains(ctx context.Context, in *De
// domains functionalities for SuperMQ services.
type DomainsServiceServer interface {
DeleteUserFromDomains(context.Context, *DeleteUserReq) (*DeleteUserRes, error)
RetrieveEntity(context.Context, *v1.RetrieveEntityReq) (*v1.RetrieveEntityRes, error)
mustEmbedUnimplementedDomainsServiceServer()
}
@@ -74,6 +88,9 @@ type UnimplementedDomainsServiceServer struct{}
func (UnimplementedDomainsServiceServer) DeleteUserFromDomains(context.Context, *DeleteUserReq) (*DeleteUserRes, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteUserFromDomains not implemented")
}
func (UnimplementedDomainsServiceServer) RetrieveEntity(context.Context, *v1.RetrieveEntityReq) (*v1.RetrieveEntityRes, error) {
return nil, status.Errorf(codes.Unimplemented, "method RetrieveEntity not implemented")
}
func (UnimplementedDomainsServiceServer) mustEmbedUnimplementedDomainsServiceServer() {}
func (UnimplementedDomainsServiceServer) testEmbeddedByValue() {}
@@ -113,6 +130,24 @@ func _DomainsService_DeleteUserFromDomains_Handler(srv interface{}, ctx context.
return interceptor(ctx, in, info, handler)
}
func _DomainsService_RetrieveEntity_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(v1.RetrieveEntityReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DomainsServiceServer).RetrieveEntity(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DomainsService_RetrieveEntity_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DomainsServiceServer).RetrieveEntity(ctx, req.(*v1.RetrieveEntityReq))
}
return interceptor(ctx, in, info, handler)
}
// DomainsService_ServiceDesc is the grpc.ServiceDesc for DomainsService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -124,6 +159,10 @@ var DomainsService_ServiceDesc = grpc.ServiceDesc{
MethodName: "DeleteUserFromDomains",
Handler: _DomainsService_DeleteUserFromDomains_Handler,
},
{
MethodName: "RetrieveEntity",
Handler: _DomainsService_RetrieveEntity_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "domains/v1/domains.proto",
-1
View File
@@ -81,7 +81,6 @@ func AuthorizationMiddleware(svc channels.Service, repo channels.Repository, aut
}
func (am *authorizationMiddleware) CreateChannels(ctx context.Context, session authn.Session, chs ...channels.Channel) ([]channels.Channel, error) {
// If domain is disabled , then this authorization will fail for all non-admin domain users
if err := am.extAuthorize(ctx, channels.DomainOpCreateChannel, authz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
+23 -7
View File
@@ -25,6 +25,7 @@ import (
authsvcAuthn "github.com/absmach/supermq/pkg/authn/authsvc"
smqauthz "github.com/absmach/supermq/pkg/authz"
authsvcAuthz "github.com/absmach/supermq/pkg/authz/authsvc"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/events"
"github.com/absmach/supermq/pkg/events/store"
"github.com/absmach/supermq/pkg/grpcclient"
@@ -48,12 +49,13 @@ import (
)
const (
svcName = "bootstrap"
envPrefixDB = "SMQ_BOOTSTRAP_DB_"
envPrefixHTTP = "SMQ_BOOTSTRAP_HTTP_"
envPrefixAuth = "SMQ_AUTH_GRPC_"
defDB = "bootstrap"
defSvcHTTPPort = "9013"
svcName = "bootstrap"
envPrefixDB = "SMQ_BOOTSTRAP_DB_"
envPrefixHTTP = "SMQ_BOOTSTRAP_HTTP_"
envPrefixAuth = "SMQ_AUTH_GRPC_"
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
defDB = "bootstrap"
defSvcHTTPPort = "9013"
stream = "events.supermq.clients"
streamID = "supermq.bootstrap"
@@ -148,7 +150,21 @@ func main() {
logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure())
defer authnClient.Close()
authz, authzClient, err := authsvcAuthz.NewAuthorization(ctx, grpcCfg)
domsGrpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&domsGrpcCfg, env.Options{Prefix: envPrefixDomains}); err != nil {
logger.Error(fmt.Sprintf("failed to load domains gRPC client configuration : %s", err))
exitCode = 1
return
}
domainsAuthz, _, domainsHandler, err := domainsAuthz.NewAuthorization(ctx, domsGrpcCfg)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer domainsHandler.Close()
authz, authzClient, err := authsvcAuthz.NewAuthorization(ctx, grpcCfg, domainsAuthz)
if err != nil {
logger.Error(err.Error())
exitCode = 1
+17 -1
View File
@@ -29,6 +29,7 @@ import (
authsvcAuthn "github.com/absmach/supermq/pkg/authn/authsvc"
smqauthz "github.com/absmach/supermq/pkg/authz"
authsvcAuthz "github.com/absmach/supermq/pkg/authz/authsvc"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/grpcclient"
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
"github.com/absmach/supermq/pkg/policies"
@@ -61,6 +62,7 @@ const (
envPrefixAuth = "SMQ_AUTH_GRPC_"
envPrefixClients = "SMQ_CLIENTS_AUTH_GRPC_"
envPrefixGroups = "SMQ_GROUPS_GRPC_"
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
defDB = "channels"
defSvcHTTPPort = "9005"
defSvcGRPCPort = "7005"
@@ -162,7 +164,21 @@ func main() {
defer authnClient.Close()
logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure())
authz, authzClient, err := authsvcAuthz.NewAuthorization(ctx, grpcCfg)
domsGrpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&domsGrpcCfg, env.Options{Prefix: envPrefixDomains}); err != nil {
logger.Error(fmt.Sprintf("failed to load domains gRPC client configuration : %s", err))
exitCode = 1
return
}
domAuthz, _, domainsHandler, err := domainsAuthz.NewAuthorization(ctx, domsGrpcCfg)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer domainsHandler.Close()
authz, authzClient, err := authsvcAuthz.NewAuthorization(ctx, grpcCfg, domAuthz)
if err != nil {
logger.Error(err.Error())
exitCode = 1
+17 -1
View File
@@ -32,6 +32,7 @@ import (
authsvcAuthn "github.com/absmach/supermq/pkg/authn/authsvc"
smqauthz "github.com/absmach/supermq/pkg/authz"
authsvcAuthz "github.com/absmach/supermq/pkg/authz/authsvc"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/grpcclient"
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
"github.com/absmach/supermq/pkg/policies"
@@ -65,6 +66,7 @@ const (
envPrefixAuth = "SMQ_AUTH_GRPC_"
envPrefixChannels = "SMQ_CHANNELS_GRPC_"
envPrefixGroups = "SMQ_GROUPS_GRPC_"
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
defDB = "clients"
defSvcHTTPPort = "9000"
defSvcAuthGRPCPort = "7000"
@@ -179,7 +181,21 @@ func main() {
defer authnClient.Close()
logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure())
authz, authzClient, err := authsvcAuthz.NewAuthorization(ctx, grpcCfg)
domsGrpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&domsGrpcCfg, env.Options{Prefix: envPrefixDomains}); err != nil {
logger.Error(fmt.Sprintf("failed to load domains gRPC client configuration : %s", err))
exitCode = 1
return
}
domAuthz, _, domainsHandler, err := domainsAuthz.NewAuthorization(ctx, domsGrpcCfg)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer domainsHandler.Close()
authz, authzClient, err := authsvcAuthz.NewAuthorization(ctx, grpcCfg, domAuthz)
if err != nil {
logger.Error(err.Error())
exitCode = 1
+37 -19
View File
@@ -19,14 +19,18 @@ import (
domainsSvc "github.com/absmach/supermq/domains"
domainsgrpcapi "github.com/absmach/supermq/domains/api/grpc"
httpapi "github.com/absmach/supermq/domains/api/http"
cache "github.com/absmach/supermq/domains/cache"
"github.com/absmach/supermq/domains/events"
dmw "github.com/absmach/supermq/domains/middleware"
dpostgres "github.com/absmach/supermq/domains/postgres"
"github.com/absmach/supermq/domains/private"
dtracing "github.com/absmach/supermq/domains/tracing"
redisclient "github.com/absmach/supermq/internal/clients/redis"
smqlog "github.com/absmach/supermq/logger"
authsvcAuthn "github.com/absmach/supermq/pkg/authn/authsvc"
"github.com/absmach/supermq/pkg/authz"
authsvcAuthz "github.com/absmach/supermq/pkg/authz/authsvc"
domainsAuthz "github.com/absmach/supermq/pkg/domains/psvc"
"github.com/absmach/supermq/pkg/grpcclient"
"github.com/absmach/supermq/pkg/jaeger"
"github.com/absmach/supermq/pkg/policies"
@@ -45,7 +49,6 @@ import (
"github.com/authzed/grpcutil"
"github.com/caarlos0/env/v11"
"github.com/go-chi/chi/v5"
"github.com/jmoiron/sqlx"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
@@ -65,16 +68,18 @@ const (
)
type config struct {
LogLevel string `env:"SMQ_DOMAINS_LOG_LEVEL" envDefault:"info"`
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"SMQ_DOMAINS_INSTANCE_ID" envDefault:""`
SpicedbHost string `env:"SMQ_SPICEDB_HOST" envDefault:"localhost"`
SpicedbPort string `env:"SMQ_SPICEDB_PORT" envDefault:"50051"`
SpicedbSchemaFile string `env:"SMQ_SPICEDB_SCHEMA_FILE" envDefault:"schema.zed"`
SpicedbPreSharedKey string `env:"SMQ_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"`
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
LogLevel string `env:"SMQ_DOMAINS_LOG_LEVEL" envDefault:"info"`
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"`
CacheURL string `env:"SMQ_DOMAINS_CACHE_URL" envDefault:"redis://localhost:6379/0"`
CacheKeyDuration time.Duration `env:"SMQ_DOMAINS_CACHE_KEY_DURATION" envDefault:"10m"`
InstanceID string `env:"SMQ_DOMAINS_INSTANCE_ID" envDefault:""`
SpicedbHost string `env:"SMQ_SPICEDB_HOST" envDefault:"localhost"`
SpicedbPort string `env:"SMQ_SPICEDB_PORT" envDefault:"50051"`
SpicedbSchemaFile string `env:"SMQ_SPICEDB_SCHEMA_FILE" envDefault:"schema.zed"`
SpicedbPreSharedKey string `env:"SMQ_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"`
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
}
func main() {
@@ -153,7 +158,23 @@ func main() {
defer authnHandler.Close()
logger.Info("Authn successfully connected to auth gRPC server " + authnHandler.Secure())
authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, clientConfig)
database := postgres.NewDatabase(db, dbConfig, tracer)
domainsRepo := dpostgres.New(database)
cacheclient, err := redisclient.Connect(cfg.CacheURL)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer cacheclient.Close()
cache := cache.NewDomainsCache(cacheclient, cfg.CacheKeyDuration)
psvc := private.New(domainsRepo, cache)
domAuthz := domainsAuthz.NewAuthorization(psvc)
authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, clientConfig, domAuthz)
if err != nil {
logger.Error(fmt.Sprintf("authz failed to connect to auth gRPC server : %s", err.Error()))
exitCode = 1
@@ -170,7 +191,7 @@ func main() {
}
logger.Info("Policy client successfully connected to spicedb gRPC server")
svc, err := newDomainService(ctx, db, tracer, cfg, dbConfig, authz, policyService, logger)
svc, err := newDomainService(ctx, domainsRepo, cache, tracer, cfg, authz, policyService, logger)
if err != nil {
logger.Error(fmt.Sprintf("failed to create %s service: %s", svcName, err.Error()))
exitCode = 1
@@ -185,7 +206,7 @@ func main() {
}
registerDomainsServiceServer := func(srv *grpc.Server) {
reflection.Register(srv)
grpcDomainsV1.RegisterDomainsServiceServer(srv, domainsgrpcapi.NewDomainsServer(svc))
grpcDomainsV1.RegisterDomainsServiceServer(srv, domainsgrpcapi.NewDomainsServer(psvc))
}
gs := grpcserver.NewServer(ctx, cancel, svcName, grpcServerConfig, registerDomainsServiceServer, logger)
@@ -221,10 +242,7 @@ func main() {
}
}
func newDomainService(ctx context.Context, db *sqlx.DB, tracer trace.Tracer, cfg config, dbConfig pgclient.Config, authz authz.Authorization, policiessvc policies.Service, logger *slog.Logger) (domains.Service, error) {
database := postgres.NewDatabase(db, dbConfig, tracer)
domainsRepo := dpostgres.New(database)
func newDomainService(ctx context.Context, domainsRepo domainsSvc.Repository, cache domainsSvc.Cache, tracer trace.Tracer, cfg config, authz authz.Authorization, policiessvc policies.Service, logger *slog.Logger) (domains.Service, error) {
idProvider := uuid.New()
sidProvider, err := sid.New()
if err != nil {
@@ -236,7 +254,7 @@ func newDomainService(ctx context.Context, db *sqlx.DB, tracer trace.Tracer, cfg
return nil, err
}
svc, err := domainsSvc.New(domainsRepo, policiessvc, idProvider, sidProvider, availableActions, builtInRoles)
svc, err := domainsSvc.New(domainsRepo, cache, policiessvc, idProvider, sidProvider, availableActions, builtInRoles)
if err != nil {
return nil, fmt.Errorf("failed to init domain service: %w", err)
}
+16 -1
View File
@@ -30,6 +30,7 @@ import (
authsvcAuthn "github.com/absmach/supermq/pkg/authn/authsvc"
smqauthz "github.com/absmach/supermq/pkg/authz"
authsvcAuthz "github.com/absmach/supermq/pkg/authz/authsvc"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/grpcclient"
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
"github.com/absmach/supermq/pkg/policies"
@@ -157,7 +158,21 @@ func main() {
defer authnHandler.Close()
logger.Info("Authn successfully connected to auth gRPC server " + authnHandler.Secure())
authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, authClientConfig)
domsGrpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&domsGrpcCfg, env.Options{Prefix: envPrefixDomains}); err != nil {
logger.Error(fmt.Sprintf("failed to load domains gRPC client configuration : %s", err))
exitCode = 1
return
}
domAuthz, _, domainsHandler, err := domainsAuthz.NewAuthorization(ctx, domsGrpcCfg)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer domainsHandler.Close()
authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, authClientConfig, domAuthz)
if err != nil {
logger.Error("failed to create authz " + err.Error())
exitCode = 1
+23 -7
View File
@@ -23,6 +23,7 @@ import (
authsvcAuthn "github.com/absmach/supermq/pkg/authn/authsvc"
smqauthz "github.com/absmach/supermq/pkg/authz"
authsvcAuthz "github.com/absmach/supermq/pkg/authz/authsvc"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/grpcclient"
"github.com/absmach/supermq/pkg/jaeger"
"github.com/absmach/supermq/pkg/postgres"
@@ -39,12 +40,13 @@ import (
)
const (
svcName = "invitations"
envPrefixDB = "SMQ_INVITATIONS_DB_"
envPrefixHTTP = "SMQ_INVITATIONS_HTTP_"
envPrefixAuth = "SMQ_AUTH_GRPC_"
defDB = "invitations"
defSvcHTTPPort = "9020"
svcName = "invitations"
envPrefixDB = "SMQ_INVITATIONS_DB_"
envPrefixHTTP = "SMQ_INVITATIONS_HTTP_"
envPrefixAuth = "SMQ_AUTH_GRPC_"
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
defDB = "invitations"
defSvcHTTPPort = "9020"
)
type config struct {
@@ -120,7 +122,21 @@ func main() {
defer authnHandler.Close()
logger.Info("AuthN successfully connected to auth gRPC server " + authnHandler.Secure())
authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, authClientCfg)
domsGrpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&domsGrpcCfg, env.Options{Prefix: envPrefixDomains}); err != nil {
logger.Error(fmt.Sprintf("failed to load domains gRPC client configuration : %s", err))
exitCode = 1
return
}
domAuthz, _, domainsHandler, err := domainsAuthz.NewAuthorization(ctx, domsGrpcCfg)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer domainsHandler.Close()
authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, authClientCfg, domAuthz)
if err != nil {
logger.Error(err.Error())
exitCode = 1
+23 -7
View File
@@ -23,6 +23,7 @@ import (
authsvcAuthn "github.com/absmach/supermq/pkg/authn/authsvc"
smqauthz "github.com/absmach/supermq/pkg/authz"
authsvcAuthz "github.com/absmach/supermq/pkg/authz/authsvc"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/events/store"
"github.com/absmach/supermq/pkg/grpcclient"
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
@@ -39,12 +40,13 @@ import (
)
const (
svcName = "journal"
envPrefixDB = "SMQ_JOURNAL_DB_"
envPrefixHTTP = "SMQ_JOURNAL_HTTP_"
envPrefixAuth = "SMQ_AUTH_GRPC_"
defDB = "journal"
defSvcHTTPPort = "9021"
svcName = "journal"
envPrefixDB = "SMQ_JOURNAL_DB_"
envPrefixHTTP = "SMQ_JOURNAL_HTTP_"
envPrefixAuth = "SMQ_AUTH_GRPC_"
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
defDB = "journal"
defSvcHTTPPort = "9021"
)
type config struct {
@@ -111,7 +113,21 @@ func main() {
defer authnHandler.Close()
logger.Info("AuthN successfully connected to auth gRPC server " + authnHandler.Secure())
authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, authClientCfg)
domsGrpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&domsGrpcCfg, env.Options{Prefix: envPrefixDomains}); err != nil {
logger.Error(fmt.Sprintf("failed to load domains gRPC client configuration : %s", err))
exitCode = 1
return
}
domAuthz, _, domainsHandler, err := domainsAuthz.NewAuthorization(ctx, domsGrpcCfg)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer domainsHandler.Close()
authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, authClientCfg, domAuthz)
if err != nil {
logger.Error(err.Error())
exitCode = 1
+16 -17
View File
@@ -23,6 +23,7 @@ import (
authsvcAuthn "github.com/absmach/supermq/pkg/authn/authsvc"
smqauthz "github.com/absmach/supermq/pkg/authz"
authsvcAuthz "github.com/absmach/supermq/pkg/authz/authsvc"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/grpcclient"
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
"github.com/absmach/supermq/pkg/oauth2"
@@ -181,7 +182,21 @@ func main() {
defer authnHandler.Close()
logger.Info("AuthN successfully connected to auth gRPC server " + authnHandler.Secure())
authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, authClientConfig)
domsGrpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&domsGrpcCfg, env.Options{Prefix: envPrefixDomains}); err != nil {
logger.Error(fmt.Sprintf("failed to load domains gRPC client configuration : %s", err))
exitCode = 1
return
}
domAuthz, domainsClient, domainsHandler, err := domainsAuthz.NewAuthorization(ctx, domsGrpcCfg)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer domainsHandler.Close()
authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, authClientConfig, domAuthz)
if err != nil {
logger.Error("failed to create authz " + err.Error())
exitCode = 1
@@ -190,22 +205,6 @@ func main() {
defer authzHandler.Close()
logger.Info("AuthZ successfully connected to auth gRPC server " + authzHandler.Secure())
domainsClientConfig := grpcclient.Config{}
if err := env.ParseWithOptions(&domainsClientConfig, env.Options{Prefix: envPrefixDomains}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err))
exitCode = 1
return
}
domainsClient, domainsHandler, err := grpcclient.SetupDomainsClient(ctx, domainsClientConfig)
if err != nil {
logger.Error("failed to setup domain gRPC clients " + err.Error())
exitCode = 1
return
}
defer domainsHandler.Close()
logger.Info("DomainsService gRPC client successfully connected to domains gRPC server " + domainsHandler.Secure())
policyService, err := newPolicyService(cfg, logger)
if err != nil {
logger.Error("failed to create new policies service " + err.Error())
+2
View File
@@ -125,6 +125,8 @@ SMQ_DOMAINS_DB_SSL_KEY=
SMQ_DOMAINS_DB_SSL_CERT=
SMQ_DOMAINS_DB_SSL_ROOT_CERT=
SMQ_DOMAINS_INSTANCE_ID=
SMQ_DOMAINS_CACHE_URL=redis://domains-redis:${SMQ_REDIS_TCP_PORT}/0
SMQ_DOMAINS_CACHE_KEY_DURATION=10m
#### Domains Client Config
SMQ_DOMAINS_URL=http://domains:9003
@@ -65,6 +65,11 @@ services:
SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY}
SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST}
SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT}
SMQ_DOMAINS_GRPC_URL: ${SMQ_DOMAINS_GRPC_URL}
SMQ_DOMAINS_GRPC_TIMEOUT: ${SMQ_DOMAINS_GRPC_TIMEOUT}
SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
networks:
- supermq-base-net
volumes:
+5
View File
@@ -61,6 +61,11 @@ services:
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
SMQ_JOURNAL_INSTANCE_ID: ${SMQ_JOURNAL_INSTANCE_ID}
SMQ_DOMAINS_GRPC_URL: ${SMQ_DOMAINS_GRPC_URL}
SMQ_DOMAINS_GRPC_TIMEOUT: ${SMQ_DOMAINS_GRPC_TIMEOUT}
SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
ports:
- ${SMQ_JOURNAL_HTTP_PORT}:${SMQ_JOURNAL_HTTP_PORT}
networks:
+32
View File
@@ -19,6 +19,7 @@ volumes:
supermq-auth-db-volume:
supermq-pat-db-volume:
supermq-domains-db-volume:
supermq-domains-redis-volume:
supermq-invitations-db-volume:
supermq-ui-db-volume:
@@ -174,6 +175,15 @@ services:
- supermq-base-net
volumes:
- supermq-domains-db-volume:/var/lib/postgresql/data
domains-redis:
image: redis:7.2.4-alpine
container_name: supermq-domains-redis
restart: on-failure
networks:
- supermq-base-net
volumes:
- supermq-domains-redis-volume:/data
domains:
image: supermq/domains:${SMQ_RELEASE_TAG}
@@ -214,6 +224,8 @@ services:
SMQ_DOMAINS_DB_SSL_ROOT_CERT: ${SMQ_DOMAINS_DB_SSL_ROOT_CERT}
SMQ_DOMAINS_INSTANCE_ID: ${SMQ_DOMAINS_INSTANCE_ID}
SMQ_ES_URL: ${SMQ_ES_URL}
SMQ_DOMAINS_CACHE_URL: ${SMQ_DOMAINS_CACHE_URL}
SMQ_DOMAINS_CACHE_KEY_DURATION: ${SMQ_DOMAINS_CACHE_KEY_DURATION}
SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL}
SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT}
SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
@@ -313,6 +325,11 @@ services:
SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
SMQ_DOMAINS_GRPC_URL: ${SMQ_DOMAINS_GRPC_URL}
SMQ_DOMAINS_GRPC_TIMEOUT: ${SMQ_DOMAINS_GRPC_TIMEOUT}
SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
@@ -457,6 +474,11 @@ services:
SMQ_GROUPS_GRPC_CLIENT_CERT: ${SMQ_GROUPS_GRPC_CLIENT_CERT:+/groups-grpc-client.crt}
SMQ_GROUPS_GRPC_CLIENT_KEY: ${SMQ_GROUPS_GRPC_CLIENT_KEY:+/groups-grpc-client.key}
SMQ_GROUPS_GRPC_SERVER_CA_CERTS: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+/groups-grpc-server-ca.crt}
SMQ_DOMAINS_GRPC_URL: ${SMQ_DOMAINS_GRPC_URL}
SMQ_DOMAINS_GRPC_TIMEOUT: ${SMQ_DOMAINS_GRPC_TIMEOUT}
SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
@@ -587,6 +609,11 @@ services:
SMQ_GROUPS_GRPC_CLIENT_CERT: ${SMQ_GROUPS_GRPC_CLIENT_CERT:+/groups-grpc-client.crt}
SMQ_GROUPS_GRPC_CLIENT_KEY: ${SMQ_GROUPS_GRPC_CLIENT_KEY:+/groups-grpc-client.key}
SMQ_GROUPS_GRPC_SERVER_CA_CERTS: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+/groups-grpc-server-ca.crt}
SMQ_DOMAINS_GRPC_URL: ${SMQ_DOMAINS_GRPC_URL}
SMQ_DOMAINS_GRPC_TIMEOUT: ${SMQ_DOMAINS_GRPC_TIMEOUT}
SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
SMQ_ES_URL: ${SMQ_ES_URL}
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
@@ -784,6 +811,11 @@ services:
SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt}
SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key}
SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt}
SMQ_DOMAINS_GRPC_URL: ${SMQ_DOMAINS_GRPC_URL}
SMQ_DOMAINS_GRPC_TIMEOUT: ${SMQ_DOMAINS_GRPC_TIMEOUT}
SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
SMQ_ES_URL: ${SMQ_ES_URL}
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
+3 -2
View File
@@ -251,7 +251,6 @@ definition domain {
relation update: role#member | team#member
relation enable: role#member | team#member
relation disable: role#member | team#member
relation membership: role#member | team#member
relation read: role#member | team#member
relation delete: role#member | team#member
@@ -304,7 +303,6 @@ definition domain {
permission read_permission = read + team->domain_read + organization->admin
permission enable_permission = enable + team->domain_update + organization->admin
permission disable_permission = disable + team->domain_update + organization->admin
permission membership_permission = membership + team->domain_membership + organization->admin
permission delete_permission = delete + team->domain_delete + organization->admin
permission manage_role_permission = manage_role + team->domain_manage_role + organization->admin
@@ -312,6 +310,9 @@ definition domain {
permission remove_role_users_permission = remove_role_users + team->domain_remove_role_users + organization->admin
permission view_role_users_permission = view_role_users + team->domain_view_role_users + organization->admin
permission membership = read + update + enable + disable + delete + manage_role + add_role_users + remove_role_users + view_role_users
permission admin = read & update & enable & disable & delete & manage_role & add_role_users & remove_role_users & view_role_users
permission client_create_permission = client_create + team->client_create + organization->admin
permission channel_create_permission = channel_create + team->channel_create + organization->admin
permission group_create_permission = group_create + team->group_create + organization->admin
+42 -1
View File
@@ -7,6 +7,7 @@ import (
"context"
"time"
grpcCommonV1 "github.com/absmach/supermq/api/grpc/common/v1"
grpcDomainsV1 "github.com/absmach/supermq/api/grpc/domains/v1"
grpcapi "github.com/absmach/supermq/auth/api/grpc"
"github.com/go-kit/kit/endpoint"
@@ -20,6 +21,7 @@ var _ grpcDomainsV1.DomainsServiceClient = (*domainsGrpcClient)(nil)
type domainsGrpcClient struct {
deleteUserFromDomains endpoint.Endpoint
retrieveEntity endpoint.Endpoint
timeout time.Duration
}
@@ -34,7 +36,14 @@ func NewDomainsClient(conn *grpc.ClientConn, timeout time.Duration) grpcDomainsV
decodeDeleteUserResponse,
grpcDomainsV1.DeleteUserRes{},
).Endpoint(),
retrieveEntity: kitgrpc.NewClient(
conn,
domainsSvcName,
"RetrieveEntity",
encodeRetrieveEntityRequest,
decodeRetrieveEntityResponse,
grpcCommonV1.RetrieveEntityRes{},
).Endpoint(),
timeout: timeout,
}
}
@@ -65,3 +74,35 @@ func encodeDeleteUserRequest(_ context.Context, grpcReq interface{}) (interface{
Id: req.ID,
}, nil
}
func (client domainsGrpcClient) RetrieveEntity(ctx context.Context, in *grpcCommonV1.RetrieveEntityReq, opts ...grpc.CallOption) (*grpcCommonV1.RetrieveEntityRes, error) {
ctx, cancel := context.WithTimeout(ctx, client.timeout)
defer cancel()
res, err := client.retrieveEntity(ctx, retrieveEntityReq{
ID: in.GetId(),
})
if err != nil {
return &grpcCommonV1.RetrieveEntityRes{}, grpcapi.DecodeError(err)
}
rdsr := res.(retrieveEntityRes)
return &grpcCommonV1.RetrieveEntityRes{
Entity: &grpcCommonV1.EntityBasic{
Id: rdsr.id,
Status: uint32(rdsr.status),
},
}, nil
}
func decodeRetrieveEntityResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(*grpcCommonV1.RetrieveEntityRes)
return retrieveEntityRes{id: res.Entity.GetId(), status: uint8(res.Entity.GetStatus())}, nil
}
func encodeRetrieveEntityRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(retrieveEntityReq)
return &grpcCommonV1.RetrieveEntityReq{
Id: req.ID,
}, nil
}
+20 -1
View File
@@ -6,7 +6,7 @@ package grpc
import (
"context"
"github.com/absmach/supermq/domains"
domains "github.com/absmach/supermq/domains/private"
"github.com/go-kit/kit/endpoint"
)
@@ -24,3 +24,22 @@ func deleteUserFromDomainsEndpoint(svc domains.Service) endpoint.Endpoint {
return deleteUserRes{deleted: true}, nil
}
}
func retrieveEntityEndpoint(svc domains.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(retrieveEntityReq)
if err := req.validate(); err != nil {
return retrieveEntityRes{}, err
}
dom, err := svc.RetrieveEntity(ctx, req.ID)
if err != nil {
return retrieveEntityRes{}, err
}
return retrieveEntityRes{
id: dom.ID,
status: uint8(dom.Status),
}, nil
}
}
+1 -2
View File
@@ -12,8 +12,8 @@ import (
grpcDomainsV1 "github.com/absmach/supermq/api/grpc/domains/v1"
apiutil "github.com/absmach/supermq/api/http/util"
"github.com/absmach/supermq/domains"
grpcapi "github.com/absmach/supermq/domains/api/grpc"
domains "github.com/absmach/supermq/domains/private"
"github.com/absmach/supermq/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -31,7 +31,6 @@ const (
description = "Description"
groupName = "smqx"
adminpermission = "admin"
authoritiesObj = "authorities"
memberRelation = "member"
loginDuration = 30 * time.Minute
+12
View File
@@ -18,3 +18,15 @@ func (req deleteUserPoliciesReq) validate() error {
return nil
}
type retrieveEntityReq struct {
ID string
}
func (req retrieveEntityReq) validate() error {
if req.ID == "" {
return apiutil.ErrMissingID
}
return nil
}
+5
View File
@@ -6,3 +6,8 @@ package grpc
type deleteUserRes struct {
deleted bool
}
type retrieveEntityRes struct {
id string
status uint8
}
+36 -1
View File
@@ -6,9 +6,10 @@ package grpc
import (
"context"
grpcCommonV1 "github.com/absmach/supermq/api/grpc/common/v1"
grpcDomainsV1 "github.com/absmach/supermq/api/grpc/domains/v1"
grpcapi "github.com/absmach/supermq/auth/api/grpc"
"github.com/absmach/supermq/domains"
domains "github.com/absmach/supermq/domains/private"
kitgrpc "github.com/go-kit/kit/transport/grpc"
)
@@ -17,6 +18,7 @@ var _ grpcDomainsV1.DomainsServiceServer = (*domainsGrpcServer)(nil)
type domainsGrpcServer struct {
grpcDomainsV1.UnimplementedDomainsServiceServer
deleteUserFromDomains kitgrpc.Handler
retrieveEntity kitgrpc.Handler
}
func NewDomainsServer(svc domains.Service) grpcDomainsV1.DomainsServiceServer {
@@ -26,6 +28,11 @@ func NewDomainsServer(svc domains.Service) grpcDomainsV1.DomainsServiceServer {
decodeDeleteUserRequest,
encodeDeleteUserResponse,
),
retrieveEntity: kitgrpc.NewServer(
retrieveEntityEndpoint(svc),
decodeRetrieveEntityRequest,
encodeRetrieveEntityResponse,
),
}
}
@@ -48,3 +55,31 @@ func (s *domainsGrpcServer) DeleteUserFromDomains(ctx context.Context, req *grpc
}
return res.(*grpcDomainsV1.DeleteUserRes), nil
}
func decodeRetrieveEntityRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*grpcCommonV1.RetrieveEntityReq)
return retrieveEntityReq{
ID: req.GetId(),
}, nil
}
func encodeRetrieveEntityResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(retrieveEntityRes)
return &grpcCommonV1.RetrieveEntityRes{
Entity: &grpcCommonV1.EntityBasic{
Id: res.id,
Status: uint32(res.status),
},
}, nil
}
func (s *domainsGrpcServer) RetrieveEntity(ctx context.Context, req *grpcCommonV1.RetrieveEntityReq) (*grpcCommonV1.RetrieveEntityRes, error) {
_, res, err := s.retrieveEntity.ServeGRPC(ctx, req)
if err != nil {
return nil, grpcapi.EncodeError(err)
}
return res.(*grpcCommonV1.RetrieveEntityRes), nil
}
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"os"
"testing"
"github.com/absmach/supermq/domains/mocks"
"github.com/absmach/supermq/domains/private/mocks"
)
var svc *mocks.Service
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package cache contains the domain concept definitions needed to
// support Magistrala Domains cache service functionality.
package cache
+66
View File
@@ -0,0 +1,66 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package cache
import (
"context"
"time"
"github.com/absmach/supermq/domains"
"github.com/absmach/supermq/pkg/errors"
repoerr "github.com/absmach/supermq/pkg/errors/repository"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/redis/go-redis/v9"
)
type domainsCache struct {
client *redis.Client
duration time.Duration
}
func NewDomainsCache(client *redis.Client, duration time.Duration) domains.Cache {
return &domainsCache{
client: client,
duration: duration,
}
}
func (dc *domainsCache) Save(ctx context.Context, domainID string, status domains.Status) error {
if domainID == "" {
return errors.Wrap(repoerr.ErrCreateEntity, errors.New("domain ID is empty"))
}
statusString := status.String()
if statusString == domains.Unknown {
return errors.Wrap(repoerr.ErrCreateEntity, svcerr.ErrInvalidStatus)
}
if err := dc.client.Set(ctx, domainID, status.String(), dc.duration).Err(); err != nil {
return errors.Wrap(repoerr.ErrCreateEntity, err)
}
return nil
}
func (dc *domainsCache) Status(ctx context.Context, domainID string) (domains.Status, error) {
st, err := dc.client.Get(ctx, domainID).Result()
if err != nil {
return domains.AllStatus, errors.Wrap(repoerr.ErrNotFound, err)
}
status, err := domains.ToStatus(st)
if err != nil {
return domains.AllStatus, errors.Wrap(repoerr.ErrNotFound, err)
}
return status, nil
}
func (dc *domainsCache) Remove(ctx context.Context, domainID string) error {
if domainID == "" {
return errors.Wrap(repoerr.ErrRemoveEntity, errors.New("domain ID is empty"))
}
if err := dc.client.Del(ctx, domainID).Err(); err != nil {
return errors.Wrap(repoerr.ErrRemoveEntity, err)
}
return nil
}
+209
View File
@@ -0,0 +1,209 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package cache_test
import (
"context"
"fmt"
"strings"
"testing"
"time"
"github.com/absmach/supermq/domains"
"github.com/absmach/supermq/domains/cache"
"github.com/absmach/supermq/internal/testsutil"
"github.com/absmach/supermq/pkg/errors"
repoerr "github.com/absmach/supermq/pkg/errors/repository"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/assert"
)
func setupDomainsClient(t *testing.T) domains.Cache {
opts, err := redis.ParseURL(redisURL)
assert.Nil(t, err, fmt.Sprintf("got unexpected error on parsing redis URL: %s", err))
redisClient := redis.NewClient(opts)
return cache.NewDomainsCache(redisClient, 10*time.Minute)
}
func TestSave(t *testing.T) {
dc := setupDomainsClient(t)
domainID := testsutil.GenerateUUID(t)
cases := []struct {
desc string
domainID string
status domains.Status
err error
}{
{
desc: "Save with enabled status",
domainID: domainID,
status: domains.EnabledStatus,
err: nil,
},
{
desc: "Save with disabled status",
domainID: testsutil.GenerateUUID(t),
status: domains.DisabledStatus,
err: nil,
},
{
desc: "Save with frozen status",
domainID: testsutil.GenerateUUID(t),
status: domains.FreezeStatus,
err: nil,
},
{
desc: "Save with empty domain ID",
domainID: "",
status: domains.EnabledStatus,
err: repoerr.ErrCreateEntity,
},
{
desc: "Save with all status",
domainID: testsutil.GenerateUUID(t),
status: domains.AllStatus,
err: nil,
},
{
desc: "Save with invalid status",
domainID: testsutil.GenerateUUID(t),
status: domains.Status(6),
err: repoerr.ErrCreateEntity,
},
{
desc: "Save the same record",
domainID: domainID,
status: domains.EnabledStatus,
err: nil,
},
{
desc: "Save client with long id ",
domainID: strings.Repeat("a", 513*1024*1024),
status: domains.EnabledStatus,
err: repoerr.ErrCreateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := dc.Save(context.Background(), tc.domainID, tc.status)
assert.True(t, errors.Contains(err, tc.err))
})
}
}
func TestStatus(t *testing.T) {
dc := setupDomainsClient(t)
enabledDomainID := testsutil.GenerateUUID(t)
err := dc.Save(context.Background(), enabledDomainID, domains.EnabledStatus)
assert.Nil(t, err, fmt.Sprintf("Unexpected error while trying to save: %s", err))
disabledDomainID := testsutil.GenerateUUID(t)
err = dc.Save(context.Background(), disabledDomainID, domains.DisabledStatus)
assert.Nil(t, err, fmt.Sprintf("Unexpected error while trying to save: %s", err))
frozenDomainID := testsutil.GenerateUUID(t)
err = dc.Save(context.Background(), frozenDomainID, domains.FreezeStatus)
assert.Nil(t, err, fmt.Sprintf("Unexpected error while trying to save: %s", err))
allDomainID := testsutil.GenerateUUID(t)
err = dc.Save(context.Background(), allDomainID, domains.AllStatus)
assert.Nil(t, err, fmt.Sprintf("Unexpected error while trying to save: %s", err))
cases := []struct {
desc string
domainID string
status domains.Status
err error
}{
{
desc: "Get domain status from cache for enabled domain",
domainID: enabledDomainID,
status: domains.EnabledStatus,
err: nil,
},
{
desc: "Get domain status from cache for disabled domain",
domainID: disabledDomainID,
status: domains.DisabledStatus,
err: nil,
},
{
desc: "Get domain status from cache for frozen domain",
domainID: frozenDomainID,
status: domains.FreezeStatus,
err: nil,
},
{
desc: "Get domain status from cache for all domain",
domainID: allDomainID,
status: domains.AllStatus,
err: nil,
},
{
desc: "Get domain status from cache for non existing domain",
domainID: testsutil.GenerateUUID(t),
status: domains.AllStatus,
err: repoerr.ErrNotFound,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
status, err := dc.Status(context.Background(), tc.domainID)
assert.True(t, errors.Contains(err, tc.err))
assert.Equal(t, tc.status, status)
})
}
}
func TestRemove(t *testing.T) {
dc := setupDomainsClient(t)
domainID := testsutil.GenerateUUID(t)
err := dc.Save(context.Background(), domainID, domains.EnabledStatus)
assert.Nil(t, err, fmt.Sprintf("Unexpected error while trying to save: %s", err))
cases := []struct {
desc string
domainID string
err error
}{
{
desc: "Remove domain from cache",
domainID: domainID,
err: nil,
},
{
desc: "Remove domain from cache with empty domain ID",
domainID: "",
err: repoerr.ErrRemoveEntity,
},
{
desc: "Remove non existing domain from cache",
domainID: testsutil.GenerateUUID(t),
err: nil,
},
{
desc: "Remove domain from cache with long id",
domainID: strings.Repeat("a", 513*1024*1024),
err: repoerr.ErrRemoveEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := dc.Remove(context.Background(), tc.domainID)
assert.True(t, errors.Contains(err, tc.err))
if err == nil {
_, err = dc.Status(context.Background(), tc.domainID)
assert.True(t, errors.Contains(err, repoerr.ErrNotFound))
}
})
}
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package cache_test
import (
"context"
"fmt"
"log"
"os"
"testing"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
"github.com/redis/go-redis/v9"
)
var (
redisClient *redis.Client
redisURL string
)
func TestMain(m *testing.M) {
pool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
container, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: "redis",
Tag: "7.2.4-alpine",
}, func(config *docker.HostConfig) {
config.AutoRemove = true
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
})
if err != nil {
log.Fatalf("Could not start container: %s", err)
}
redisURL = fmt.Sprintf("redis://localhost:%s/0", container.GetPort("6379/tcp"))
opts, err := redis.ParseURL(redisURL)
if err != nil {
log.Fatalf("Could not parse redis URL: %s", err)
}
if err := pool.Retry(func() error {
redisClient = redis.NewClient(opts)
return redisClient.Ping(context.Background()).Err()
}); err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
code := m.Run()
if err := pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
}
os.Exit(code)
}
+14 -1
View File
@@ -169,7 +169,6 @@ type Service interface {
DisableDomain(ctx context.Context, sesssion authn.Session, id string) (Domain, error)
FreezeDomain(ctx context.Context, sesssion authn.Session, id string) (Domain, error)
ListDomains(ctx context.Context, sesssion authn.Session, page Page) (DomainsPage, error)
DeleteUserFromDomains(ctx context.Context, id string) error
roles.RoleManager
}
@@ -199,3 +198,17 @@ type Repository interface {
roles.Repository
}
// Cache contains domains caching interface.
//
//go:generate mockery --name Cache --output=./mocks --filename cache.go --quiet --note "Copyright (c) Abstract Machines"
type Cache interface {
// Save stores pair domain status and domain id.
Save(ctx context.Context, domainID string, status Status) error
// Status returns domain status for given domain ID.
Status(ctx context.Context, domainID string) (Status, error)
// Removes domain from cache.
Remove(ctx context.Context, domainID string) error
}
+26 -21
View File
@@ -11,20 +11,22 @@ import (
)
const (
domainPrefix = "domain."
domainCreate = domainPrefix + "create"
domainRetrieve = domainPrefix + "retrieve"
domainUpdate = domainPrefix + "update"
domainEnable = domainPrefix + "enable"
domainDisable = domainPrefix + "disable"
domainFreeze = domainPrefix + "freeze"
domainList = domainPrefix + "list"
domainUserDelete = domainPrefix + "user_delete"
domainPrefix = "domain."
domainCreate = domainPrefix + "create"
domainRetrieve = domainPrefix + "retrieve"
domainRetrieveStatus = domainPrefix + "retrieve_status"
domainUpdate = domainPrefix + "update"
domainEnable = domainPrefix + "enable"
domainDisable = domainPrefix + "disable"
domainFreeze = domainPrefix + "freeze"
domainList = domainPrefix + "list"
domainUserDelete = domainPrefix + "user_delete"
)
var (
_ events.Event = (*createDomainEvent)(nil)
_ events.Event = (*retrieveDomainEvent)(nil)
_ events.Event = (*retrieveDomainStatusEvent)(nil)
_ events.Event = (*updateDomainEvent)(nil)
_ events.Event = (*enableDomainEvent)(nil)
_ events.Event = (*disableDomainEvent)(nil)
@@ -91,6 +93,21 @@ func (rde retrieveDomainEvent) Encode() (map[string]interface{}, error) {
return val, nil
}
type retrieveDomainStatusEvent struct {
id string
status domains.Status
}
func (rdse retrieveDomainStatusEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
"operation": domainRetrieve,
"id": rdse.id,
"status": rdse.status.String(),
}
return val, nil
}
type updateDomainEvent struct {
domains.Domain
}
@@ -220,15 +237,3 @@ func (lde listDomainsEvent) Encode() (map[string]interface{}, error) {
return val, nil
}
type deleteUserFromDomainsEvent struct {
userID string
}
func (dude deleteUserFromDomainsEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
"operation": domainUserDelete,
"user_id": dude.userID,
}
return val, nil
}
-14
View File
@@ -164,17 +164,3 @@ func (es *eventStore) ListDomains(ctx context.Context, session authn.Session, p
return dp, nil
}
func (es *eventStore) DeleteUserFromDomains(ctx context.Context, userID string) error {
if err := es.svc.DeleteUserFromDomains(ctx, userID); err != nil {
return err
}
event := deleteUserFromDomainsEvent{userID}
if err := es.Publish(ctx, event); err != nil {
return err
}
return nil
}
+3 -7
View File
@@ -107,12 +107,12 @@ func (am *authorizationMiddleware) DisableDomain(ctx context.Context, session au
func (am *authorizationMiddleware) FreezeDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) {
// Only SuperAdmin can freeze the domain
if err := am.authz.Authorize(ctx, authz.PolicyReq{
Subject: session.DomainUserID,
Subject: session.UserID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Permission: policies.AdminPermission,
Object: id,
ObjectType: policies.DomainType,
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
}); err != nil {
return domains.Domain{}, err
}
@@ -133,10 +133,6 @@ func (am *authorizationMiddleware) ListDomains(ctx context.Context, session auth
return am.svc.ListDomains(ctx, session, page)
}
func (am *authorizationMiddleware) DeleteUserFromDomains(ctx context.Context, id string) (err error) {
return am.svc.DeleteUserFromDomains(ctx, id)
}
func (am *authorizationMiddleware) authorize(ctx context.Context, op svcutil.Operation, authReq authz.PolicyReq) error {
perm, err := am.opp.GetPermission(op)
if err != nil {
-16
View File
@@ -163,19 +163,3 @@ func (lm *loggingMiddleware) ListDomains(ctx context.Context, session authn.Sess
}(time.Now())
return lm.svc.ListDomains(ctx, session, page)
}
func (lm *loggingMiddleware) DeleteUserFromDomains(ctx context.Context, id string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("id", id),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Delete entity policies failed to complete successfully", args...)
return
}
lm.logger.Info("Delete entity policies completed successfully", args...)
}(time.Now())
return lm.svc.DeleteUserFromDomains(ctx, id)
}
-8
View File
@@ -91,11 +91,3 @@ func (ms *metricsMiddleware) ListDomains(ctx context.Context, session authn.Sess
}(time.Now())
return ms.svc.ListDomains(ctx, session, page)
}
func (ms *metricsMiddleware) DeleteUserFromDomains(ctx context.Context, id string) error {
defer func(begin time.Time) {
ms.counter.With("method", "delete_user_from_domains").Add(1)
ms.latency.With("method", "delete_user_from_domains").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.DeleteUserFromDomains(ctx, id)
}
+95
View File
@@ -0,0 +1,95 @@
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Copyright (c) Abstract Machines
package mocks
import (
context "context"
domains "github.com/absmach/supermq/domains"
mock "github.com/stretchr/testify/mock"
)
// Cache is an autogenerated mock type for the Cache type
type Cache struct {
mock.Mock
}
// Remove provides a mock function with given fields: ctx, domainID
func (_m *Cache) Remove(ctx context.Context, domainID string) error {
ret := _m.Called(ctx, domainID)
if len(ret) == 0 {
panic("no return value specified for Remove")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, domainID)
} else {
r0 = ret.Error(0)
}
return r0
}
// Save provides a mock function with given fields: ctx, domainID, status
func (_m *Cache) Save(ctx context.Context, domainID string, status domains.Status) error {
ret := _m.Called(ctx, domainID, status)
if len(ret) == 0 {
panic("no return value specified for Save")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, domains.Status) error); ok {
r0 = rf(ctx, domainID, status)
} else {
r0 = ret.Error(0)
}
return r0
}
// Status provides a mock function with given fields: ctx, domainID
func (_m *Cache) Status(ctx context.Context, domainID string) (domains.Status, error) {
ret := _m.Called(ctx, domainID)
if len(ret) == 0 {
panic("no return value specified for Status")
}
var r0 domains.Status
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (domains.Status, error)); ok {
return rf(ctx, domainID)
}
if rf, ok := ret.Get(0).(func(context.Context, string) domains.Status); ok {
r0 = rf(ctx, domainID)
} else {
r0 = ret.Get(0).(domains.Status)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, domainID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewCache creates a new instance of Cache. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewCache(t interface {
mock.TestingT
Cleanup(func())
}) *Cache {
mock := &Cache{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+76
View File
@@ -9,6 +9,8 @@ package mocks
import (
context "context"
commonv1 "github.com/absmach/supermq/api/grpc/common/v1"
grpc "google.golang.org/grpc"
mock "github.com/stretchr/testify/mock"
@@ -103,6 +105,80 @@ func (_c *DomainsServiceClient_DeleteUserFromDomains_Call) RunAndReturn(run func
return _c
}
// RetrieveEntity provides a mock function with given fields: ctx, in, opts
func (_m *DomainsServiceClient) RetrieveEntity(ctx context.Context, in *commonv1.RetrieveEntityReq, opts ...grpc.CallOption) (*commonv1.RetrieveEntityRes, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for RetrieveEntity")
}
var r0 *commonv1.RetrieveEntityRes
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *commonv1.RetrieveEntityReq, ...grpc.CallOption) (*commonv1.RetrieveEntityRes, error)); ok {
return rf(ctx, in, opts...)
}
if rf, ok := ret.Get(0).(func(context.Context, *commonv1.RetrieveEntityReq, ...grpc.CallOption) *commonv1.RetrieveEntityRes); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*commonv1.RetrieveEntityRes)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *commonv1.RetrieveEntityReq, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DomainsServiceClient_RetrieveEntity_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RetrieveEntity'
type DomainsServiceClient_RetrieveEntity_Call struct {
*mock.Call
}
// RetrieveEntity is a helper method to define mock.On call
// - ctx context.Context
// - in *commonv1.RetrieveEntityReq
// - opts ...grpc.CallOption
func (_e *DomainsServiceClient_Expecter) RetrieveEntity(ctx interface{}, in interface{}, opts ...interface{}) *DomainsServiceClient_RetrieveEntity_Call {
return &DomainsServiceClient_RetrieveEntity_Call{Call: _e.mock.On("RetrieveEntity",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *DomainsServiceClient_RetrieveEntity_Call) Run(run func(ctx context.Context, in *commonv1.RetrieveEntityReq, opts ...grpc.CallOption)) *DomainsServiceClient_RetrieveEntity_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*commonv1.RetrieveEntityReq), variadicArgs...)
})
return _c
}
func (_c *DomainsServiceClient_RetrieveEntity_Call) Return(_a0 *commonv1.RetrieveEntityRes, _a1 error) *DomainsServiceClient_RetrieveEntity_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *DomainsServiceClient_RetrieveEntity_Call) RunAndReturn(run func(context.Context, *commonv1.RetrieveEntityReq, ...grpc.CallOption) (*commonv1.RetrieveEntityRes, error)) *DomainsServiceClient_RetrieveEntity_Call {
_c.Call.Return(run)
return _c
}
// NewDomainsServiceClient creates a new instance of DomainsServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewDomainsServiceClient(t interface {
-18
View File
@@ -77,24 +77,6 @@ func (_m *Service) CreateDomain(ctx context.Context, sesssion authn.Session, d d
return r0, r1
}
// DeleteUserFromDomains provides a mock function with given fields: ctx, id
func (_m *Service) DeleteUserFromDomains(ctx context.Context, id string) error {
ret := _m.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for DeleteUserFromDomains")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// DisableDomain provides a mock function with given fields: ctx, sesssion, id
func (_m *Service) DisableDomain(ctx context.Context, sesssion authn.Session, id string) (domains.Domain, error) {
ret := _m.Called(ctx, sesssion, id)
+77
View File
@@ -0,0 +1,77 @@
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Copyright (c) Abstract Machines
package mocks
import (
context "context"
domains "github.com/absmach/supermq/domains"
mock "github.com/stretchr/testify/mock"
)
// Service is an autogenerated mock type for the Service type
type Service struct {
mock.Mock
}
// DeleteUserFromDomains provides a mock function with given fields: ctx, id
func (_m *Service) DeleteUserFromDomains(ctx context.Context, id string) error {
ret := _m.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for DeleteUserFromDomains")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// RetrieveEntity provides a mock function with given fields: ctx, id
func (_m *Service) RetrieveEntity(ctx context.Context, id string) (domains.Domain, error) {
ret := _m.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for RetrieveEntity")
}
var r0 domains.Domain
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (domains.Domain, error)); ok {
return rf(ctx, id)
}
if rf, ok := ret.Get(0).(func(context.Context, string) domains.Domain); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Get(0).(domains.Domain)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewService(t interface {
mock.TestingT
Cleanup(func())
}) *Service {
mock := &Service{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package private
import (
"context"
"github.com/absmach/supermq/domains"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
)
const defLimit = 100
//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines"
type Service interface {
RetrieveEntity(ctx context.Context, id string) (domains.Domain, error)
DeleteUserFromDomains(ctx context.Context, id string) error
}
var _ Service = (*service)(nil)
func New(repo domains.Repository, cache domains.Cache) Service {
return service{
repo: repo,
cache: cache,
}
}
type service struct {
repo domains.Repository
cache domains.Cache
}
func (svc service) RetrieveEntity(ctx context.Context, id string) (domains.Domain, error) {
status, err := svc.cache.Status(ctx, id)
if err == nil {
return domains.Domain{ID: id, Status: status}, nil
}
dom, err := svc.repo.RetrieveByID(ctx, id)
if err != nil {
return domains.Domain{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
status = dom.Status
if err := svc.cache.Save(ctx, id, status); err != nil {
return domains.Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return domains.Domain{ID: dom.ID, Status: dom.Status}, nil
}
func (svc service) DeleteUserFromDomains(ctx context.Context, id string) (err error) {
domainsPage, err := svc.repo.ListDomains(ctx, domains.Page{UserID: id, Limit: defLimit})
if err != nil {
return err
}
if domainsPage.Total > defLimit {
for i := defLimit; i < int(domainsPage.Total); i += defLimit {
page := domains.Page{UserID: id, Offset: uint64(i), Limit: defLimit}
dp, err := svc.repo.ListDomains(ctx, page)
if err != nil {
return err
}
domainsPage.Domains = append(domainsPage.Domains, dp.Domains...)
}
}
return nil
}
-1
View File
@@ -40,7 +40,6 @@ const (
enablePermission = "enable_permission"
disablePermission = "disable_permission"
readPermission = "read_permission"
membershipPermission = "membership_permission"
deletePermission = "delete_permission"
manageRolePermission = "manage_role_permission"
addRoleUsersPermission = "add_role_users_permission"
+15 -44
View File
@@ -15,8 +15,6 @@ import (
"github.com/absmach/supermq/pkg/roles"
)
const defLimit = 100
var (
errCreateDomainPolicy = errors.New("failed to create domain policy")
errRollbackRepo = errors.New("failed to rollback repo")
@@ -24,6 +22,7 @@ var (
type service struct {
repo Repository
cache Cache
policy policies.Service
idProvider supermq.IDProvider
roles.ProvisionManageService
@@ -31,7 +30,7 @@ type service struct {
var _ Service = (*service)(nil)
func New(repo Repository, policy policies.Service, idProvider supermq.IDProvider, sidProvider supermq.IDProvider, availableActions []roles.Action, builtInRoles map[roles.BuiltInRoleName][]roles.Action) (Service, error) {
func New(repo Repository, cache Cache, policy policies.Service, idProvider supermq.IDProvider, sidProvider supermq.IDProvider, availableActions []roles.Action, builtInRoles map[roles.BuiltInRoleName][]roles.Action) (Service, error) {
rpms, err := roles.NewProvisionManageService(policies.DomainType, repo, policy, sidProvider, availableActions, builtInRoles)
if err != nil {
return nil, err
@@ -39,6 +38,7 @@ func New(repo Repository, policy policies.Service, idProvider supermq.IDProvider
return &service{
repo: repo,
cache: cache,
policy: policy,
idProvider: idProvider,
ProvisionManageService: rpms,
@@ -123,6 +123,10 @@ func (svc service) EnableDomain(ctx context.Context, session authn.Session, id s
if err != nil {
return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
if err := svc.cache.Remove(ctx, id); err != nil {
return dom, errors.Wrap(svcerr.ErrRemoveEntity, err)
}
return dom, nil
}
@@ -132,6 +136,10 @@ func (svc service) DisableDomain(ctx context.Context, session authn.Session, id
if err != nil {
return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
if err := svc.cache.Remove(ctx, id); err != nil {
return dom, errors.Wrap(svcerr.ErrRemoveEntity, err)
}
return dom, nil
}
@@ -142,6 +150,10 @@ func (svc service) FreezeDomain(ctx context.Context, session authn.Session, id s
if err != nil {
return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
if err := svc.cache.Remove(ctx, id); err != nil {
return dom, errors.Wrap(svcerr.ErrRemoveEntity, err)
}
return dom, nil
}
@@ -157,44 +169,3 @@ func (svc service) ListDomains(ctx context.Context, session authn.Session, p Pag
}
return dp, nil
}
func (svc service) DeleteUserFromDomains(ctx context.Context, id string) (err error) {
domainsPage, err := svc.repo.ListDomains(ctx, Page{UserID: id, Limit: defLimit})
if err != nil {
return err
}
if domainsPage.Total > defLimit {
for i := defLimit; i < int(domainsPage.Total); i += defLimit {
page := Page{UserID: id, Offset: uint64(i), Limit: defLimit}
dp, err := svc.repo.ListDomains(ctx, page)
if err != nil {
return err
}
domainsPage.Domains = append(domainsPage.Domains, dp.Domains...)
}
}
// if err := svc.RemoveMembersFromAllRoles(ctx, authn.Session{}, []string{id}); err != nil {
// return err
// }
////////////ToDo//////////////
// Remove user from all roles in all domains
//////////////////////////
// for _, domain := range domainsPage.Domains {
// req := policies.Policy{
// Subject: policies.EncodeDomainUserID(domain.ID, id),
// SubjectType: policies.UserType,
// }
// if err := svc.policies.DeletePolicyFilter(ctx, req); err != nil {
// return err
// }
// }
// if err := svc.repo.DeleteUserPolicies(ctx, id); err != nil {
// return err
// }
return nil
}
+52 -75
View File
@@ -59,11 +59,13 @@ var (
var (
drepo *mocks.Repository
dcache *mocks.Cache
policy *policiesMocks.Service
)
func newService() domains.Service {
drepo = new(mocks.Repository)
dcache = new(mocks.Cache)
idProvider := uuid.NewMock()
sidProvider := sid.NewMock()
policy = new(policiesMocks.Service)
@@ -71,7 +73,7 @@ func newService() domains.Service {
builtInRoles := map[roles.BuiltInRoleName][]roles.Action{
groups.BuiltInRoleAdmin: availableActions,
}
ds, _ := domains.New(drepo, policy, idProvider, sidProvider, availableActions, builtInRoles)
ds, _ := domains.New(drepo, dcache, policy, idProvider, sidProvider, availableActions, builtInRoles)
return ds
}
@@ -312,6 +314,8 @@ func TestEnableDomain(t *testing.T) {
domainID string
enableRes domains.Domain
enableErr error
cacheErr error
resp domains.Domain
err error
}{
{
@@ -319,6 +323,7 @@ func TestEnableDomain(t *testing.T) {
session: validSession,
domainID: domain.ID,
enableRes: enabledDomain,
resp: enabledDomain,
err: nil,
},
{
@@ -326,6 +331,7 @@ func TestEnableDomain(t *testing.T) {
session: validSession,
domainID: "",
enableErr: repoerr.ErrNotFound,
resp: domains.Domain{},
err: svcerr.ErrUpdateEntity,
},
{
@@ -333,17 +339,29 @@ func TestEnableDomain(t *testing.T) {
session: validSession,
domainID: domain.ID,
enableErr: errors.ErrMalformedEntity,
resp: domains.Domain{},
err: svcerr.ErrUpdateEntity,
},
{
desc: "enable domain with failed to remove cache",
session: validSession,
domainID: domain.ID,
enableRes: enabledDomain,
cacheErr: errors.ErrMalformedEntity,
resp: enabledDomain,
err: svcerr.ErrRemoveEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := drepo.On("Update", context.Background(), tc.domainID, tc.session.UserID, domains.DomainReq{Status: &status}).Return(tc.enableRes, tc.enableErr)
cacheCall := dcache.On("Remove", context.Background(), tc.domainID).Return(tc.cacheErr)
domain, err := svc.EnableDomain(context.Background(), tc.session, tc.domainID)
assert.True(t, errors.Contains(err, tc.err))
assert.Equal(t, tc.enableRes, domain)
assert.Equal(t, tc.resp, domain)
repoCall.Unset()
cacheCall.Unset()
})
}
}
@@ -361,6 +379,8 @@ func TestDisableDomain(t *testing.T) {
domainID string
disableRes domains.Domain
disableErr error
cacheErr error
resp domains.Domain
err error
}{
{
@@ -368,6 +388,7 @@ func TestDisableDomain(t *testing.T) {
session: validSession,
domainID: domain.ID,
disableRes: disabledDomain,
resp: disabledDomain,
err: nil,
},
{
@@ -375,6 +396,7 @@ func TestDisableDomain(t *testing.T) {
session: validSession,
domainID: "",
disableErr: repoerr.ErrNotFound,
resp: domains.Domain{},
err: svcerr.ErrUpdateEntity,
},
{
@@ -382,17 +404,29 @@ func TestDisableDomain(t *testing.T) {
session: validSession,
domainID: domain.ID,
disableErr: errors.ErrMalformedEntity,
resp: domains.Domain{},
err: svcerr.ErrUpdateEntity,
},
{
desc: "disable domain with failed to remove cache",
session: validSession,
domainID: domain.ID,
disableRes: disabledDomain,
cacheErr: errors.ErrMalformedEntity,
resp: disabledDomain,
err: svcerr.ErrRemoveEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := drepo.On("Update", context.Background(), tc.domainID, tc.session.UserID, domains.DomainReq{Status: &status}).Return(tc.disableRes, tc.disableErr)
cacheCall := dcache.On("Remove", context.Background(), tc.domainID).Return(tc.cacheErr)
domain, err := svc.DisableDomain(context.Background(), tc.session, tc.domainID)
assert.True(t, errors.Contains(err, tc.err))
assert.Equal(t, tc.disableRes, domain)
repoCall.Unset()
cacheCall.Unset()
})
}
}
@@ -410,6 +444,8 @@ func TestFreezeDomain(t *testing.T) {
domainID string
freezeRes domains.Domain
freezeErr error
cacheErr error
resp domains.Domain
err error
}{
{
@@ -417,6 +453,7 @@ func TestFreezeDomain(t *testing.T) {
session: validSession,
domainID: domain.ID,
freezeRes: freezeDomain,
resp: freezeDomain,
err: nil,
},
{
@@ -424,6 +461,7 @@ func TestFreezeDomain(t *testing.T) {
session: validSession,
domainID: "",
freezeErr: repoerr.ErrNotFound,
resp: domains.Domain{},
err: svcerr.ErrUpdateEntity,
},
{
@@ -431,17 +469,29 @@ func TestFreezeDomain(t *testing.T) {
session: validSession,
domainID: domain.ID,
freezeErr: errors.ErrMalformedEntity,
resp: domains.Domain{},
err: svcerr.ErrUpdateEntity,
},
{
desc: "freeze domain with failed to remove cache",
session: validSession,
domainID: domain.ID,
freezeRes: freezeDomain,
cacheErr: errors.ErrMalformedEntity,
resp: freezeDomain,
err: svcerr.ErrRemoveEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := drepo.On("Update", context.Background(), tc.domainID, tc.session.UserID, domains.DomainReq{Status: &status}).Return(tc.freezeRes, tc.freezeErr)
cacheCall := dcache.On("Remove", context.Background(), tc.domainID).Return(tc.cacheErr)
domain, err := svc.FreezeDomain(context.Background(), tc.session, tc.domainID)
assert.True(t, errors.Contains(err, tc.err))
assert.Equal(t, tc.freezeRes, domain)
repoCall.Unset()
cacheCall.Unset()
})
}
}
@@ -518,76 +568,3 @@ func TestListDomains(t *testing.T) {
})
}
}
func TestDeleteUserFromDomains(t *testing.T) {
svc := newService()
cases := []struct {
desc string
userID string
listUserDomainsRes domains.DomainsPage
listUserDomainsRes1 domains.DomainsPage
listUserDomainsErr error
listUserDomainsErr1 error
err error
}{
{
desc: "delete user from domains successfully",
userID: id,
listUserDomainsRes: domains.DomainsPage{
Domains: []domains.Domain{domain},
Offset: 0,
Limit: 10,
Total: 1,
},
err: nil,
},
{
desc: "delete user from domains with repository error on list domains",
userID: id,
listUserDomainsErr: svcerr.ErrViewEntity,
err: svcerr.ErrViewEntity,
},
{
desc: "delete user from domains with domains greater than default limit",
userID: id,
listUserDomainsRes: domains.DomainsPage{
Domains: []domains.Domain{domain},
Offset: 0,
Limit: 100,
Total: 101,
},
listUserDomainsRes1: domains.DomainsPage{
Domains: []domains.Domain{domain},
Offset: 100,
Limit: 100,
Total: 101,
},
err: nil,
},
{
desc: "delete user from domains with domains greater than default limit with error",
userID: id,
listUserDomainsRes: domains.DomainsPage{
Domains: []domains.Domain{domain},
Offset: 0,
Limit: 100,
Total: 101,
},
listUserDomainsRes1: domains.DomainsPage{},
listUserDomainsErr1: svcerr.ErrViewEntity,
err: svcerr.ErrViewEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := drepo.On("ListDomains", context.Background(), domains.Page{UserID: tc.userID, Limit: 100}).Return(tc.listUserDomainsRes, tc.listUserDomainsErr)
repoCall1 := drepo.On("ListDomains", context.Background(), domains.Page{UserID: tc.userID, Offset: 100, Limit: 100}).Return(tc.listUserDomainsRes1, tc.listUserDomainsErr1)
err := svc.DeleteUserFromDomains(context.Background(), tc.userID)
assert.True(t, errors.Contains(err, tc.err))
repoCall.Unset()
repoCall1.Unset()
})
}
}
-6
View File
@@ -79,9 +79,3 @@ func (tm *tracingMiddleware) ListDomains(ctx context.Context, session authn.Sess
defer span.End()
return tm.svc.ListDomains(ctx, session, p)
}
func (tm *tracingMiddleware) DeleteUserFromDomains(ctx context.Context, id string) error {
ctx, span := tm.tracer.Start(ctx, "delete_user_from_domains")
defer span.End()
return tm.svc.DeleteUserFromDomains(ctx, id)
}
+1 -1
View File
@@ -45,7 +45,6 @@ type authorizationMiddleware struct {
authz smqauthz.Authorization
opp svcutil.OperationPerm
extOpp svcutil.ExternalOperationPerm
rmMW.RoleManagerAuthorizationMiddleware
}
@@ -371,6 +370,7 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, op svcutil.Ope
return err
}
pr.Permission = perm.String()
if err := am.authz.Authorize(ctx, pr); err != nil {
return err
}
+3 -3
View File
@@ -144,9 +144,9 @@ func NewRolesOperationPermissionMap() map[svcutil.Operation]svcutil.Permission {
const (
// External Permissions for the domain.
domainCreateGroupPermission = "channel_create_permission"
domainListGroupPermission = "membership_permission"
userListGroupsPermission = "membership_permission"
domainCreateGroupPermission = "group_create_permission"
domainListGroupPermission = "membership"
userListGroupsPermission = "membership"
clientListGroupPermission = "read_permission"
chanelListGroupPermission = "read_permission"
)
+8 -2
View File
@@ -4,13 +4,19 @@
syntax = "proto3";
package domains.v1;
option go_package = "github.com/absmach/supermq/api/grpc/domains/v1";
import "common/v1/common.proto";
option go_package = "github.com/absmach/supermq/internal/grpc/domains/v1";
// DomainsService is a service that provides access to
// domains functionalities for SuperMQ services.
service DomainsService {
rpc DeleteUserFromDomains(DeleteUserReq) returns (DeleteUserRes) {}
rpc DeleteUserFromDomains(DeleteUserReq)
returns (DeleteUserRes) {}
rpc RetrieveEntity(common.v1.RetrieveEntityReq)
returns (common.v1.RetrieveEntityRes) {}
}
message DeleteUserRes {
+65 -2
View File
@@ -8,19 +8,24 @@ import (
grpcAuthV1 "github.com/absmach/supermq/api/grpc/auth/v1"
"github.com/absmach/supermq/auth/api/grpc/auth"
"github.com/absmach/supermq/domains"
"github.com/absmach/supermq/pkg/authz"
pkgDomians "github.com/absmach/supermq/pkg/domains"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/absmach/supermq/pkg/grpcclient"
"github.com/absmach/supermq/pkg/policies"
grpchealth "google.golang.org/grpc/health/grpc_health_v1"
)
type authorization struct {
authSvcClient grpcAuthV1.AuthServiceClient
domains pkgDomians.Authorization
}
var _ authz.Authorization = (*authorization)(nil)
func NewAuthorization(ctx context.Context, cfg grpcclient.Config) (authz.Authorization, grpcclient.Handler, error) {
func NewAuthorization(ctx context.Context, cfg grpcclient.Config, domainsAuthz pkgDomians.Authorization) (authz.Authorization, grpcclient.Handler, error) {
client, err := grpcclient.NewHandler(cfg)
if err != nil {
return nil, nil, err
@@ -34,10 +39,26 @@ func NewAuthorization(ctx context.Context, cfg grpcclient.Config) (authz.Authori
return nil, nil, grpcclient.ErrSvcNotServing
}
authSvcClient := auth.NewAuthClient(client.Connection(), cfg.Timeout)
return authorization{authSvcClient}, client, nil
return authorization{
authSvcClient: authSvcClient,
domains: domainsAuthz,
}, client, nil
}
func (a authorization) Authorize(ctx context.Context, pr authz.PolicyReq) error {
if pr.SubjectType == policies.UserType && (pr.ObjectType == policies.GroupType || pr.ObjectType == policies.ClientType || pr.ObjectType == policies.DomainType) {
domainID := pr.Domain
if domainID == "" {
if pr.ObjectType != policies.DomainType {
return svcerr.ErrDomainAuthorization
}
domainID = pr.Object
}
if err := a.checkDomain(ctx, pr.SubjectType, pr.Subject, domainID); err != nil {
return errors.Wrap(svcerr.ErrDomainAuthorization, err)
}
}
req := grpcAuthV1.AuthZReq{
Domain: pr.Domain,
SubjectType: pr.SubjectType,
@@ -58,3 +79,45 @@ func (a authorization) Authorize(ctx context.Context, pr authz.PolicyReq) error
}
return nil
}
func (a authorization) checkDomain(ctx context.Context, subjectType, subject, domainID string) error {
dom, err := a.domains.RetrieveEntity(ctx, domainID)
if err != nil {
return errors.Wrap(svcerr.ErrViewEntity, err)
}
switch dom.Status {
case domains.FreezeStatus:
_, err := a.authSvcClient.Authorize(ctx, &grpcAuthV1.AuthZReq{
Subject: subject,
SubjectType: subjectType,
Permission: policies.AdminPermission,
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
})
return err
case domains.DisabledStatus:
_, err := a.authSvcClient.Authorize(ctx, &grpcAuthV1.AuthZReq{
Subject: subject,
SubjectType: subjectType,
Permission: policies.AdminPermission,
Object: domainID,
ObjectType: policies.DomainType,
})
return err
case domains.EnabledStatus:
_, err := a.authSvcClient.Authorize(ctx, &grpcAuthV1.AuthZReq{
Subject: subject,
SubjectType: subjectType,
Permission: policies.MembershipPermission,
Object: domainID,
ObjectType: policies.DomainType,
})
return err
default:
return svcerr.ErrInvalidStatus
}
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package domains
import (
"context"
"github.com/absmach/supermq/domains"
)
type Authorization interface {
RetrieveEntity(ctx context.Context, id string) (domains.Domain, error)
}
+44
View File
@@ -0,0 +1,44 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package grpcclient
import (
"context"
grpcCommonV1 "github.com/absmach/supermq/api/grpc/common/v1"
grpcDomainsV1 "github.com/absmach/supermq/api/grpc/domains/v1"
"github.com/absmach/supermq/domains"
pkgDomains "github.com/absmach/supermq/pkg/domains"
"github.com/absmach/supermq/pkg/grpcclient"
)
type authorization struct {
domainsSvcClient grpcDomainsV1.DomainsServiceClient
}
var _ pkgDomains.Authorization = (*authorization)(nil)
func NewAuthorization(ctx context.Context, cfg grpcclient.Config) (pkgDomains.Authorization, grpcDomainsV1.DomainsServiceClient, grpcclient.Handler, error) {
domainsClient, domainsHandler, err := grpcclient.SetupDomainsClient(ctx, cfg)
if err != nil {
return nil, nil, nil, err
}
return authorization{domainsSvcClient: domainsClient}, domainsClient, domainsHandler, nil
}
func (a authorization) RetrieveEntity(ctx context.Context, id string) (domains.Domain, error) {
req := grpcCommonV1.RetrieveEntityReq{
Id: id,
}
res, err := a.domainsSvcClient.RetrieveEntity(ctx, &req)
if err != nil {
return domains.Domain{}, err
}
return domains.Domain{
ID: res.Entity.GetId(),
Status: domains.Status(res.Entity.GetStatus()),
}, nil
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package domainscache
import (
"context"
"github.com/absmach/supermq/domains"
"github.com/absmach/supermq/domains/private"
pkgDomains "github.com/absmach/supermq/pkg/domains"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
)
type authorization struct {
psvc private.Service
}
var _ pkgDomains.Authorization = (*authorization)(nil)
func NewAuthorization(psvc private.Service) pkgDomains.Authorization {
return authorization{
psvc: psvc,
}
}
func (a authorization) RetrieveEntity(ctx context.Context, id string) (domains.Domain, error) {
dom, err := a.psvc.RetrieveEntity(ctx, id)
if err != nil {
return domains.Domain{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
return dom, nil
}
+1 -1
View File
@@ -17,7 +17,7 @@ import (
clientsgrpcapi "github.com/absmach/supermq/clients/api/grpc"
climocks "github.com/absmach/supermq/clients/private/mocks"
domainsgrpcapi "github.com/absmach/supermq/domains/api/grpc"
domainsMocks "github.com/absmach/supermq/domains/mocks"
domainsMocks "github.com/absmach/supermq/domains/private/mocks"
smqlog "github.com/absmach/supermq/logger"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/grpcclient"