NOISSUE - Seperate PAT from policy (#3330)
Continuous Delivery / lint-and-build (push) Has been cancelled
Continuous Delivery / Build and Push Docker Images (push) Has been cancelled
Deploy GitHub Pages / swagger-ui (push) Has been cancelled
CI Pipeline / Check Certs (push) Has been cancelled
CI Pipeline / Lint Proto (push) Has been cancelled
CI Pipeline / lint-and-build (push) Has been cancelled
CI Pipeline / Detect Changes (push) Has been cancelled
CI Pipeline / Upload Coverage (push) Has been cancelled
CI Pipeline / Test ${{ matrix.module }} (push) Has been cancelled
Property Based Tests / api-test (push) Has been cancelled

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
Signed-off-by: Arvindh <arvindh91@gmail.com>
Co-authored-by: Arvindh <arvindh91@gmail.com>
This commit is contained in:
Steve Munene
2026-02-26 18:35:47 +03:00
committed by GitHub
parent 175f0e08ab
commit 15a6c026e9
27 changed files with 826 additions and 431 deletions
+145 -37
View File
@@ -147,11 +147,6 @@ type PolicyReq struct {
Permission string `protobuf:"bytes,7,opt,name=permission,proto3" json:"permission,omitempty"`
Object string `protobuf:"bytes,8,opt,name=object,proto3" json:"object,omitempty"`
ObjectType string `protobuf:"bytes,9,opt,name=object_type,json=objectType,proto3" json:"object_type,omitempty"`
PatId string `protobuf:"bytes,10,opt,name=pat_id,json=patId,proto3" json:"pat_id,omitempty"`
Operation string `protobuf:"bytes,11,opt,name=operation,proto3" json:"operation,omitempty"`
UserId string `protobuf:"bytes,12,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
EntityId string `protobuf:"bytes,13,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"`
EntityType string `protobuf:"bytes,14,opt,name=entity_type,json=entityType,proto3" json:"entity_type,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -249,41 +244,142 @@ func (x *PolicyReq) GetObjectType() string {
return ""
}
func (x *PolicyReq) GetPatId() string {
type PATReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
PatId string `protobuf:"bytes,1,opt,name=pat_id,json=patId,proto3" json:"pat_id,omitempty"`
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
Operation string `protobuf:"bytes,3,opt,name=operation,proto3" json:"operation,omitempty"`
UserId string `protobuf:"bytes,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
EntityId string `protobuf:"bytes,5,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"`
EntityType string `protobuf:"bytes,6,opt,name=entity_type,json=entityType,proto3" json:"entity_type,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PATReq) Reset() {
*x = PATReq{}
mi := &file_auth_v1_auth_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PATReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PATReq) ProtoMessage() {}
func (x *PATReq) ProtoReflect() protoreflect.Message {
mi := &file_auth_v1_auth_proto_msgTypes[3]
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 PATReq.ProtoReflect.Descriptor instead.
func (*PATReq) Descriptor() ([]byte, []int) {
return file_auth_v1_auth_proto_rawDescGZIP(), []int{3}
}
func (x *PATReq) GetPatId() string {
if x != nil {
return x.PatId
}
return ""
}
func (x *PolicyReq) GetOperation() string {
func (x *PATReq) GetDomain() string {
if x != nil {
return x.Domain
}
return ""
}
func (x *PATReq) GetOperation() string {
if x != nil {
return x.Operation
}
return ""
}
func (x *PolicyReq) GetUserId() string {
func (x *PATReq) GetUserId() string {
if x != nil {
return x.UserId
}
return ""
}
func (x *PolicyReq) GetEntityId() string {
func (x *PATReq) GetEntityId() string {
if x != nil {
return x.EntityId
}
return ""
}
func (x *PolicyReq) GetEntityType() string {
func (x *PATReq) GetEntityType() string {
if x != nil {
return x.EntityType
}
return ""
}
type AuthZReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
PolicyReq *PolicyReq `protobuf:"bytes,1,opt,name=policy_req,json=policyReq,proto3" json:"policy_req,omitempty"`
PatReq *PATReq `protobuf:"bytes,2,opt,name=pat_req,json=patReq,proto3,oneof" json:"pat_req,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AuthZReq) Reset() {
*x = AuthZReq{}
mi := &file_auth_v1_auth_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AuthZReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AuthZReq) ProtoMessage() {}
func (x *AuthZReq) ProtoReflect() protoreflect.Message {
mi := &file_auth_v1_auth_proto_msgTypes[4]
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 AuthZReq.ProtoReflect.Descriptor instead.
func (*AuthZReq) Descriptor() ([]byte, []int) {
return file_auth_v1_auth_proto_rawDescGZIP(), []int{4}
}
func (x *AuthZReq) GetPolicyReq() *PolicyReq {
if x != nil {
return x.PolicyReq
}
return nil
}
func (x *AuthZReq) GetPatReq() *PATReq {
if x != nil {
return x.PatReq
}
return nil
}
type AuthZRes struct {
state protoimpl.MessageState `protogen:"open.v1"`
Authorized bool `protobuf:"varint,1,opt,name=authorized,proto3" json:"authorized,omitempty"`
@@ -294,7 +390,7 @@ type AuthZRes struct {
func (x *AuthZRes) Reset() {
*x = AuthZRes{}
mi := &file_auth_v1_auth_proto_msgTypes[3]
mi := &file_auth_v1_auth_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -306,7 +402,7 @@ func (x *AuthZRes) String() string {
func (*AuthZRes) ProtoMessage() {}
func (x *AuthZRes) ProtoReflect() protoreflect.Message {
mi := &file_auth_v1_auth_proto_msgTypes[3]
mi := &file_auth_v1_auth_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -319,7 +415,7 @@ func (x *AuthZRes) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthZRes.ProtoReflect.Descriptor instead.
func (*AuthZRes) Descriptor() ([]byte, []int) {
return file_auth_v1_auth_proto_rawDescGZIP(), []int{3}
return file_auth_v1_auth_proto_rawDescGZIP(), []int{5}
}
func (x *AuthZRes) GetAuthorized() bool {
@@ -347,7 +443,7 @@ const file_auth_v1_auth_proto_rawDesc = "" +
"\x02id\x18\x01 \x01(\tR\x02id\x12\x17\n" +
"\auser_id\x18\x02 \x01(\tR\x06userId\x12\x1b\n" +
"\tuser_role\x18\x03 \x01(\rR\buserRole\x12\x1a\n" +
"\bverified\x18\x04 \x01(\bR\bverified\"\xaf\x03\n" +
"\bverified\x18\x04 \x01(\bR\bverified\"\xa3\x02\n" +
"\tPolicyReq\x12\x16\n" +
"\x06domain\x18\x01 \x01(\tR\x06domain\x12!\n" +
"\fsubject_type\x18\x02 \x01(\tR\vsubjectType\x12!\n" +
@@ -360,21 +456,28 @@ const file_auth_v1_auth_proto_rawDesc = "" +
"permission\x12\x16\n" +
"\x06object\x18\b \x01(\tR\x06object\x12\x1f\n" +
"\vobject_type\x18\t \x01(\tR\n" +
"objectType\x12\x15\n" +
"\x06pat_id\x18\n" +
" \x01(\tR\x05patId\x12\x1c\n" +
"\toperation\x18\v \x01(\tR\toperation\x12\x17\n" +
"\auser_id\x18\f \x01(\tR\x06userId\x12\x1b\n" +
"\tentity_id\x18\r \x01(\tR\bentityId\x12\x1f\n" +
"\ventity_type\x18\x0e \x01(\tR\n" +
"entityType\":\n" +
"objectType\"\xac\x01\n" +
"\x06PATReq\x12\x15\n" +
"\x06pat_id\x18\x01 \x01(\tR\x05patId\x12\x16\n" +
"\x06domain\x18\x02 \x01(\tR\x06domain\x12\x1c\n" +
"\toperation\x18\x03 \x01(\tR\toperation\x12\x17\n" +
"\auser_id\x18\x04 \x01(\tR\x06userId\x12\x1b\n" +
"\tentity_id\x18\x05 \x01(\tR\bentityId\x12\x1f\n" +
"\ventity_type\x18\x06 \x01(\tR\n" +
"entityType\"x\n" +
"\bAuthZReq\x121\n" +
"\n" +
"policy_req\x18\x01 \x01(\v2\x12.auth.v1.PolicyReqR\tpolicyReq\x12-\n" +
"\apat_req\x18\x02 \x01(\v2\x0f.auth.v1.PATReqH\x00R\x06patReq\x88\x01\x01B\n" +
"\n" +
"\b_pat_req\":\n" +
"\bAuthZRes\x12\x1e\n" +
"\n" +
"authorized\x18\x01 \x01(\bR\n" +
"authorized\x12\x0e\n" +
"\x02id\x18\x02 \x01(\tR\x02id2{\n" +
"\vAuthService\x124\n" +
"\tAuthorize\x12\x12.auth.v1.PolicyReq\x1a\x11.auth.v1.AuthZRes\"\x00\x126\n" +
"\x02id\x18\x02 \x01(\tR\x02id2z\n" +
"\vAuthService\x123\n" +
"\tAuthorize\x12\x11.auth.v1.AuthZReq\x1a\x11.auth.v1.AuthZRes\"\x00\x126\n" +
"\fAuthenticate\x12\x11.auth.v1.AuthNReq\x1a\x11.auth.v1.AuthNRes\"\x00B-Z+github.com/absmach/supermq/api/grpc/auth/v1b\x06proto3"
var (
@@ -389,23 +492,27 @@ func file_auth_v1_auth_proto_rawDescGZIP() []byte {
return file_auth_v1_auth_proto_rawDescData
}
var file_auth_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_auth_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_auth_v1_auth_proto_goTypes = []any{
(*AuthNReq)(nil), // 0: auth.v1.AuthNReq
(*AuthNRes)(nil), // 1: auth.v1.AuthNRes
(*PolicyReq)(nil), // 2: auth.v1.PolicyReq
(*AuthZRes)(nil), // 3: auth.v1.AuthZRes
(*PATReq)(nil), // 3: auth.v1.PATReq
(*AuthZReq)(nil), // 4: auth.v1.AuthZReq
(*AuthZRes)(nil), // 5: auth.v1.AuthZRes
}
var file_auth_v1_auth_proto_depIdxs = []int32{
2, // 0: auth.v1.AuthService.Authorize:input_type -> auth.v1.PolicyReq
0, // 1: auth.v1.AuthService.Authenticate:input_type -> auth.v1.AuthNReq
3, // 2: auth.v1.AuthService.Authorize:output_type -> auth.v1.AuthZRes
1, // 3: auth.v1.AuthService.Authenticate:output_type -> auth.v1.AuthNRes
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
2, // 0: auth.v1.AuthZReq.policy_req:type_name -> auth.v1.PolicyReq
3, // 1: auth.v1.AuthZReq.pat_req:type_name -> auth.v1.PATReq
4, // 2: auth.v1.AuthService.Authorize:input_type -> auth.v1.AuthZReq
0, // 3: auth.v1.AuthService.Authenticate:input_type -> auth.v1.AuthNReq
5, // 4: auth.v1.AuthService.Authorize:output_type -> auth.v1.AuthZRes
1, // 5: auth.v1.AuthService.Authenticate:output_type -> auth.v1.AuthNRes
4, // [4:6] is the sub-list for method output_type
2, // [2:4] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_auth_v1_auth_proto_init() }
@@ -413,13 +520,14 @@ func file_auth_v1_auth_proto_init() {
if File_auth_v1_auth_proto != nil {
return
}
file_auth_v1_auth_proto_msgTypes[4].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_auth_v1_auth_proto_rawDesc), len(file_auth_v1_auth_proto_rawDesc)),
NumEnums: 0,
NumMessages: 4,
NumMessages: 6,
NumExtensions: 0,
NumServices: 1,
},
+6 -6
View File
@@ -33,7 +33,7 @@ const (
// AuthService is a service that provides authentication
// and authorization functionalities for SuperMQ services.
type AuthServiceClient interface {
Authorize(ctx context.Context, in *PolicyReq, opts ...grpc.CallOption) (*AuthZRes, error)
Authorize(ctx context.Context, in *AuthZReq, opts ...grpc.CallOption) (*AuthZRes, error)
Authenticate(ctx context.Context, in *AuthNReq, opts ...grpc.CallOption) (*AuthNRes, error)
}
@@ -45,7 +45,7 @@ func NewAuthServiceClient(cc grpc.ClientConnInterface) AuthServiceClient {
return &authServiceClient{cc}
}
func (c *authServiceClient) Authorize(ctx context.Context, in *PolicyReq, opts ...grpc.CallOption) (*AuthZRes, error) {
func (c *authServiceClient) Authorize(ctx context.Context, in *AuthZReq, opts ...grpc.CallOption) (*AuthZRes, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AuthZRes)
err := c.cc.Invoke(ctx, AuthService_Authorize_FullMethodName, in, out, cOpts...)
@@ -72,7 +72,7 @@ func (c *authServiceClient) Authenticate(ctx context.Context, in *AuthNReq, opts
// AuthService is a service that provides authentication
// and authorization functionalities for SuperMQ services.
type AuthServiceServer interface {
Authorize(context.Context, *PolicyReq) (*AuthZRes, error)
Authorize(context.Context, *AuthZReq) (*AuthZRes, error)
Authenticate(context.Context, *AuthNReq) (*AuthNRes, error)
mustEmbedUnimplementedAuthServiceServer()
}
@@ -84,7 +84,7 @@ type AuthServiceServer interface {
// pointer dereference when methods are called.
type UnimplementedAuthServiceServer struct{}
func (UnimplementedAuthServiceServer) Authorize(context.Context, *PolicyReq) (*AuthZRes, error) {
func (UnimplementedAuthServiceServer) Authorize(context.Context, *AuthZReq) (*AuthZRes, error) {
return nil, status.Errorf(codes.Unimplemented, "method Authorize not implemented")
}
func (UnimplementedAuthServiceServer) Authenticate(context.Context, *AuthNReq) (*AuthNRes, error) {
@@ -112,7 +112,7 @@ func RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) {
}
func _AuthService_Authorize_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PolicyReq)
in := new(AuthZReq)
if err := dec(in); err != nil {
return nil, err
}
@@ -124,7 +124,7 @@ func _AuthService_Authorize_Handler(srv interface{}, ctx context.Context, dec fu
FullMethod: AuthService_Authorize_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AuthServiceServer).Authorize(ctx, req.(*PolicyReq))
return srv.(AuthServiceServer).Authorize(ctx, req.(*AuthZReq))
}
return interceptor(ctx, in, info, handler)
}
+48 -30
View File
@@ -70,27 +70,35 @@ func decodeIdentifyResponse(_ context.Context, grpcRes any) (any, error) {
return authenticateRes{id: res.GetId(), userID: res.GetUserId(), userRole: auth.Role(res.UserRole), verified: res.GetVerified()}, nil
}
func (client authGrpcClient) Authorize(ctx context.Context, req *grpcAuthV1.PolicyReq, _ ...grpc.CallOption) (r *grpcAuthV1.AuthZRes, err error) {
func (client authGrpcClient) Authorize(ctx context.Context, req *grpcAuthV1.AuthZReq, _ ...grpc.CallOption) (r *grpcAuthV1.AuthZRes, err error) {
ctx, cancel := context.WithTimeout(ctx, client.timeout)
defer cancel()
var authReqData authReq
if req != nil {
authReqData = authReq{
Domain: req.GetDomain(),
SubjectType: req.GetSubjectType(),
Subject: req.GetSubject(),
SubjectKind: req.GetSubjectKind(),
Relation: req.GetRelation(),
Permission: req.GetPermission(),
ObjectType: req.GetObjectType(),
Object: req.GetObject(),
UserID: req.GetUserId(),
PatID: req.GetPatId(),
EntityType: req.GetEntityType(),
Operation: req.GetOperation(),
EntityID: req.GetEntityId(),
policyReq := req.GetPolicyReq()
patReq := req.GetPatReq()
if policyReq != nil {
authReqData = authReq{
Domain: policyReq.GetDomain(),
SubjectType: policyReq.GetSubjectType(),
Subject: policyReq.GetSubject(),
SubjectKind: policyReq.GetSubjectKind(),
Relation: policyReq.GetRelation(),
Permission: policyReq.GetPermission(),
ObjectType: policyReq.GetObjectType(),
Object: policyReq.GetObject(),
}
}
if patReq != nil {
authReqData.UserID = patReq.GetUserId()
authReqData.PatID = patReq.GetPatId()
authReqData.EntityType = patReq.GetEntityType()
authReqData.Operation = patReq.GetOperation()
authReqData.EntityID = patReq.GetEntityId()
}
}
@@ -111,19 +119,29 @@ func decodeAuthorizeResponse(_ context.Context, grpcRes any) (any, error) {
func encodeAuthorizeRequest(_ context.Context, grpcReq any) (any, error) {
req := grpcReq.(authReq)
return &grpcAuthV1.PolicyReq{
Domain: req.Domain,
SubjectType: req.SubjectType,
Subject: req.Subject,
SubjectKind: req.SubjectKind,
Relation: req.Relation,
Permission: req.Permission,
ObjectType: req.ObjectType,
Object: req.Object,
UserId: req.UserID,
PatId: req.PatID,
EntityType: req.EntityType,
Operation: req.Operation,
EntityId: req.EntityID,
}, nil
authZReq := &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Domain: req.Domain,
SubjectType: req.SubjectType,
Subject: req.Subject,
SubjectKind: req.SubjectKind,
Relation: req.Relation,
Permission: req.Permission,
ObjectType: req.ObjectType,
Object: req.Object,
},
}
if req.PatID != "" {
authZReq.PatReq = &grpcAuthV1.PATReq{
PatId: req.PatID,
Domain: req.Domain,
Operation: req.Operation,
UserId: req.UserID,
EntityId: req.EntityID,
EntityType: req.EntityType,
}
}
return authZReq, nil
}
+17 -6
View File
@@ -35,6 +35,22 @@ func authorizeEndpoint(svc auth.Service) endpoint.Endpoint {
return authorizeRes{}, err
}
var pat *auth.PATAuthz
if req.PatID != "" {
entityType, err := auth.ParseEntityType(req.EntityType)
if err != nil {
return authorizeRes{authorized: false}, err
}
pat = &auth.PATAuthz{
PatID: req.PatID,
UserID: req.UserID,
EntityType: entityType,
EntityID: req.EntityID,
Operation: req.Operation,
Domain: req.Domain,
}
}
err := svc.Authorize(ctx, policies.Policy{
Domain: req.Domain,
SubjectType: req.SubjectType,
@@ -44,12 +60,7 @@ func authorizeEndpoint(svc auth.Service) endpoint.Endpoint {
Permission: req.Permission,
ObjectType: req.ObjectType,
Object: req.Object,
PatID: req.PatID,
Operation: req.Operation,
UserID: req.UserID,
EntityType: req.EntityType,
EntityID: req.EntityID,
})
}, pat)
if err != nil {
return authorizeRes{authorized: false}, err
}
+135 -101
View File
@@ -129,20 +129,22 @@ func TestAuthorize(t *testing.T) {
cases := []struct {
desc string
token string
authRequest *grpcAuthV1.PolicyReq
authRequest *grpcAuthV1.AuthZReq
authResponse *grpcAuthV1.AuthZRes
err error
}{
{
desc: "authorize user with authorized token",
token: validToken,
authRequest: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: usersType,
Object: authoritiesObj,
ObjectType: usersType,
Relation: memberRelation,
Permission: adminPermission,
authRequest: &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: usersType,
Object: authoritiesObj,
ObjectType: usersType,
Relation: memberRelation,
Permission: adminPermission,
},
},
authResponse: &grpcAuthV1.AuthZRes{Authorized: true},
err: nil,
@@ -150,13 +152,15 @@ func TestAuthorize(t *testing.T) {
{
desc: "authorize user with unauthorized token",
token: inValidToken,
authRequest: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: usersType,
Object: authoritiesObj,
ObjectType: usersType,
Relation: memberRelation,
Permission: adminPermission,
authRequest: &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: usersType,
Object: authoritiesObj,
ObjectType: usersType,
Relation: memberRelation,
Permission: adminPermission,
},
},
authResponse: &grpcAuthV1.AuthZRes{Authorized: false},
err: svcerr.ErrAuthorization,
@@ -164,13 +168,15 @@ func TestAuthorize(t *testing.T) {
{
desc: "authorize user with empty subject",
token: validToken,
authRequest: &grpcAuthV1.PolicyReq{
Subject: "",
SubjectType: usersType,
Object: authoritiesObj,
ObjectType: usersType,
Relation: memberRelation,
Permission: adminPermission,
authRequest: &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: "",
SubjectType: usersType,
Object: authoritiesObj,
ObjectType: usersType,
Relation: memberRelation,
Permission: adminPermission,
},
},
authResponse: &grpcAuthV1.AuthZRes{Authorized: false},
err: apiutil.ErrMissingPolicySub,
@@ -178,13 +184,15 @@ func TestAuthorize(t *testing.T) {
{
desc: "authorize user with empty subject type",
token: validToken,
authRequest: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: "",
Object: authoritiesObj,
ObjectType: usersType,
Relation: memberRelation,
Permission: adminPermission,
authRequest: &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: "",
Object: authoritiesObj,
ObjectType: usersType,
Relation: memberRelation,
Permission: adminPermission,
},
},
authResponse: &grpcAuthV1.AuthZRes{Authorized: false},
err: apiutil.ErrMissingPolicySub,
@@ -192,13 +200,15 @@ func TestAuthorize(t *testing.T) {
{
desc: "authorize user with empty object",
token: validToken,
authRequest: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: usersType,
Object: "",
ObjectType: usersType,
Relation: memberRelation,
Permission: adminPermission,
authRequest: &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: usersType,
Object: "",
ObjectType: usersType,
Relation: memberRelation,
Permission: adminPermission,
},
},
authResponse: &grpcAuthV1.AuthZRes{Authorized: false},
err: apiutil.ErrMissingPolicyObj,
@@ -206,13 +216,15 @@ func TestAuthorize(t *testing.T) {
{
desc: "authorize user with empty object type",
token: validToken,
authRequest: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: usersType,
Object: authoritiesObj,
ObjectType: "",
Relation: memberRelation,
Permission: adminPermission,
authRequest: &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: usersType,
Object: authoritiesObj,
ObjectType: "",
Relation: memberRelation,
Permission: adminPermission,
},
},
authResponse: &grpcAuthV1.AuthZRes{Authorized: false},
err: apiutil.ErrMissingPolicyObj,
@@ -220,13 +232,15 @@ func TestAuthorize(t *testing.T) {
{
desc: "authorize user with empty permission",
token: validToken,
authRequest: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: usersType,
Object: authoritiesObj,
ObjectType: usersType,
Relation: memberRelation,
Permission: "",
authRequest: &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: usersType,
Object: authoritiesObj,
ObjectType: usersType,
Relation: memberRelation,
Permission: "",
},
},
authResponse: &grpcAuthV1.AuthZRes{Authorized: false},
err: apiutil.ErrMalformedPolicyPer,
@@ -234,19 +248,24 @@ func TestAuthorize(t *testing.T) {
{
desc: "authorize user with valid PAT token",
token: validPATToken,
authRequest: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Permission: policies.ViewPermission,
PatId: id,
ObjectType: policies.ClientType,
Domain: domainID,
Operation: "view",
Object: clientID,
UserId: id,
EntityId: clientID,
EntityType: auth.ClientsScopeStr,
authRequest: &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Permission: policies.ViewPermission,
ObjectType: policies.ClientType,
Domain: domainID,
Object: clientID,
},
PatReq: &grpcAuthV1.PATReq{
PatId: id,
Domain: domainID,
Operation: "view",
UserId: id,
EntityId: clientID,
EntityType: auth.ClientsScopeStr,
},
},
authResponse: &grpcAuthV1.AuthZRes{Authorized: true},
err: nil,
@@ -254,19 +273,24 @@ func TestAuthorize(t *testing.T) {
{
desc: "authorize user with unauthorized PAT token",
token: inValidToken,
authRequest: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Permission: policies.ViewPermission,
PatId: id,
ObjectType: policies.ClientType,
Domain: domainID,
Operation: "view",
Object: clientID,
UserId: id,
EntityId: clientID,
EntityType: auth.ClientsScopeStr,
authRequest: &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Permission: policies.ViewPermission,
ObjectType: policies.ClientType,
Domain: domainID,
Object: clientID,
},
PatReq: &grpcAuthV1.PATReq{
PatId: id,
Domain: domainID,
Operation: "view",
UserId: id,
EntityId: clientID,
EntityType: auth.ClientsScopeStr,
},
},
authResponse: &grpcAuthV1.AuthZRes{Authorized: false},
err: svcerr.ErrAuthorization,
@@ -274,18 +298,23 @@ func TestAuthorize(t *testing.T) {
{
desc: "authorize PAT with missing user id",
token: validPATToken,
authRequest: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Permission: policies.ViewPermission,
PatId: id,
ObjectType: policies.ClientType,
Domain: domainID,
Operation: "view",
Object: clientID,
EntityId: clientID,
EntityType: auth.ClientsScopeStr,
authRequest: &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Permission: policies.ViewPermission,
ObjectType: policies.ClientType,
Domain: domainID,
Object: clientID,
},
PatReq: &grpcAuthV1.PATReq{
PatId: id,
Domain: domainID,
Operation: "view",
EntityId: clientID,
EntityType: auth.ClientsScopeStr,
},
},
authResponse: &grpcAuthV1.AuthZRes{Authorized: false},
err: apiutil.ErrMissingUserID,
@@ -293,18 +322,23 @@ func TestAuthorize(t *testing.T) {
{
desc: "authorize PAT with missing entity id",
token: validPATToken,
authRequest: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Permission: policies.ViewPermission,
PatId: id,
ObjectType: policies.ClientType,
Domain: domainID,
Operation: "view",
Object: clientID,
UserId: id,
EntityType: auth.ClientsScopeStr,
authRequest: &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: id,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Permission: policies.ViewPermission,
ObjectType: policies.ClientType,
Domain: domainID,
Object: clientID,
},
PatReq: &grpcAuthV1.PATReq{
PatId: id,
Domain: domainID,
Operation: "view",
UserId: id,
EntityType: auth.ClientsScopeStr,
},
},
authResponse: &grpcAuthV1.AuthZRes{Authorized: false},
err: apiutil.ErrMissingID,
@@ -312,7 +346,7 @@ func TestAuthorize(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
svcCall := svc.On("Authorize", mock.Anything, mock.Anything).Return(tc.err)
svcCall := svc.On("Authorize", mock.Anything, mock.Anything, mock.Anything).Return(tc.err)
ar, err := grpcClient.Authorize(context.Background(), tc.authRequest)
if ar != nil {
assert.Equal(t, tc.authResponse, ar, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.authResponse, ar))
+29 -17
View File
@@ -45,7 +45,7 @@ func (s *authGrpcServer) Authenticate(ctx context.Context, req *grpcAuthV1.AuthN
return res.(*grpcAuthV1.AuthNRes), nil
}
func (s *authGrpcServer) Authorize(ctx context.Context, req *grpcAuthV1.PolicyReq) (*grpcAuthV1.AuthZRes, error) {
func (s *authGrpcServer) Authorize(ctx context.Context, req *grpcAuthV1.AuthZReq) (*grpcAuthV1.AuthZRes, error) {
_, res, err := s.authorize.ServeGRPC(ctx, req)
if err != nil {
return nil, grpcapi.EncodeError(err)
@@ -64,26 +64,38 @@ func encodeAuthenticateResponse(_ context.Context, grpcRes any) (any, error) {
}
func decodeAuthorizeRequest(_ context.Context, grpcReq any) (any, error) {
req := grpcReq.(*grpcAuthV1.PolicyReq)
req := grpcReq.(*grpcAuthV1.AuthZReq)
if req == nil {
return authReq{}, nil
}
return authReq{
Domain: req.GetDomain(),
SubjectType: req.GetSubjectType(),
SubjectKind: req.GetSubjectKind(),
Subject: req.GetSubject(),
Relation: req.GetRelation(),
Permission: req.GetPermission(),
ObjectType: req.GetObjectType(),
Object: req.GetObject(),
UserID: req.GetUserId(),
PatID: req.GetPatId(),
EntityType: req.GetEntityType(),
Operation: req.GetOperation(),
EntityID: req.GetEntityId(),
}, nil
policyReq := req.GetPolicyReq()
patReq := req.GetPatReq()
if policyReq == nil {
return authReq{}, nil
}
authRequest := authReq{
Domain: policyReq.GetDomain(),
SubjectType: policyReq.GetSubjectType(),
SubjectKind: policyReq.GetSubjectKind(),
Subject: policyReq.GetSubject(),
Relation: policyReq.GetRelation(),
Permission: policyReq.GetPermission(),
ObjectType: policyReq.GetObjectType(),
Object: policyReq.GetObject(),
}
if patReq != nil {
authRequest.UserID = patReq.GetUserId()
authRequest.PatID = patReq.GetPatId()
authRequest.EntityType = patReq.GetEntityType()
authRequest.Operation = patReq.GetOperation()
authRequest.EntityID = patReq.GetEntityId()
}
return authRequest, nil
}
func encodeAuthorizeResponse(_ context.Context, grpcRes any) (any, error) {
+14 -2
View File
@@ -110,7 +110,7 @@ func (lm *loggingMiddleware) RetrieveJWKS() (jwks []auth.PublicKeyInfo) {
return lm.svc.RetrieveJWKS()
}
func (lm *loggingMiddleware) Authorize(ctx context.Context, pr policies.Policy) (err error) {
func (lm *loggingMiddleware) Authorize(ctx context.Context, pr policies.Policy, patAuthz *auth.PATAuthz) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
@@ -125,6 +125,18 @@ func (lm *loggingMiddleware) Authorize(ctx context.Context, pr policies.Policy)
),
slog.String("permission", pr.Permission),
}
if patAuthz != nil {
args = append(args,
slog.Group("pat",
slog.String("pat_id", patAuthz.PatID),
slog.String("user_id", patAuthz.UserID),
slog.String("entity_type", patAuthz.EntityType.String()),
slog.String("entity_id", patAuthz.EntityID),
slog.String("operation", patAuthz.Operation),
slog.String("domain", patAuthz.Domain),
),
)
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Authorize failed", args...)
@@ -132,7 +144,7 @@ func (lm *loggingMiddleware) Authorize(ctx context.Context, pr policies.Policy)
}
lm.logger.Info("Authorize completed successfully", args...)
}(time.Now())
return lm.svc.Authorize(ctx, pr)
return lm.svc.Authorize(ctx, pr, patAuthz)
}
func (lm *loggingMiddleware) CreatePAT(ctx context.Context, token, name, description string, duration time.Duration) (pa auth.PAT, err error) {
+2 -2
View File
@@ -75,12 +75,12 @@ func (ms *metricsMiddleware) RetrieveJWKS() []auth.PublicKeyInfo {
return ms.svc.RetrieveJWKS()
}
func (ms *metricsMiddleware) Authorize(ctx context.Context, pr policies.Policy) error {
func (ms *metricsMiddleware) Authorize(ctx context.Context, pr policies.Policy, patAuthz *auth.PATAuthz) error {
defer func(begin time.Time) {
ms.counter.With("method", "authorize").Add(1)
ms.latency.With("method", "authorize").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Authorize(ctx, pr)
return ms.svc.Authorize(ctx, pr, patAuthz)
}
func (ms *metricsMiddleware) CreatePAT(ctx context.Context, token, name, description string, duration time.Duration) (auth.PAT, error) {
+17 -4
View File
@@ -65,8 +65,8 @@ func (tm *tracingMiddleware) RetrieveJWKS() []auth.PublicKeyInfo {
return tm.svc.RetrieveJWKS()
}
func (tm *tracingMiddleware) Authorize(ctx context.Context, pr policies.Policy) error {
ctx, span := tm.tracer.Start(ctx, "authorize", trace.WithAttributes(
func (tm *tracingMiddleware) Authorize(ctx context.Context, pr policies.Policy, patAuthz *auth.PATAuthz) error {
attributes := []attribute.KeyValue{
attribute.String("subject", pr.Subject),
attribute.String("subject_type", pr.SubjectType),
attribute.String("subject_relation", pr.SubjectRelation),
@@ -74,10 +74,23 @@ func (tm *tracingMiddleware) Authorize(ctx context.Context, pr policies.Policy)
attribute.String("object_type", pr.ObjectType),
attribute.String("relation", pr.Relation),
attribute.String("permission", pr.Permission),
))
}
if patAuthz != nil {
attributes = append(attributes,
attribute.String("pat_id", patAuthz.PatID),
attribute.String("pat_user_id", patAuthz.UserID),
attribute.String("pat_entity_type", patAuthz.EntityType.String()),
attribute.String("pat_entity_id", patAuthz.EntityID),
attribute.String("pat_operation", patAuthz.Operation),
attribute.String("pat_domain", patAuthz.Domain),
)
}
ctx, span := tm.tracer.Start(ctx, "authorize", trace.WithAttributes(attributes...))
defer span.End()
return tm.svc.Authorize(ctx, pr)
return tm.svc.Authorize(ctx, pr, patAuthz)
}
func (tm *tracingMiddleware) CreatePAT(ctx context.Context, token, name, description string, duration time.Duration) (auth.PAT, error) {
+15 -8
View File
@@ -11,6 +11,7 @@ package mocks
import (
"context"
"github.com/absmach/supermq/auth"
"github.com/absmach/supermq/pkg/policies"
mock "github.com/stretchr/testify/mock"
)
@@ -43,16 +44,16 @@ func (_m *Authz) EXPECT() *Authz_Expecter {
}
// Authorize provides a mock function for the type Authz
func (_mock *Authz) Authorize(ctx context.Context, pr policies.Policy) error {
ret := _mock.Called(ctx, pr)
func (_mock *Authz) Authorize(ctx context.Context, pr policies.Policy, patAuthz *auth.PATAuthz) error {
ret := _mock.Called(ctx, pr, patAuthz)
if len(ret) == 0 {
panic("no return value specified for Authorize")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(context.Context, policies.Policy) error); ok {
r0 = returnFunc(ctx, pr)
if returnFunc, ok := ret.Get(0).(func(context.Context, policies.Policy, *auth.PATAuthz) error); ok {
r0 = returnFunc(ctx, pr, patAuthz)
} else {
r0 = ret.Error(0)
}
@@ -67,11 +68,12 @@ type Authz_Authorize_Call struct {
// Authorize is a helper method to define mock.On call
// - ctx context.Context
// - pr policies.Policy
func (_e *Authz_Expecter) Authorize(ctx interface{}, pr interface{}) *Authz_Authorize_Call {
return &Authz_Authorize_Call{Call: _e.mock.On("Authorize", ctx, pr)}
// - patAuthz *auth.PATAuthz
func (_e *Authz_Expecter) Authorize(ctx interface{}, pr interface{}, patAuthz interface{}) *Authz_Authorize_Call {
return &Authz_Authorize_Call{Call: _e.mock.On("Authorize", ctx, pr, patAuthz)}
}
func (_c *Authz_Authorize_Call) Run(run func(ctx context.Context, pr policies.Policy)) *Authz_Authorize_Call {
func (_c *Authz_Authorize_Call) Run(run func(ctx context.Context, pr policies.Policy, patAuthz *auth.PATAuthz)) *Authz_Authorize_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
@@ -81,9 +83,14 @@ func (_c *Authz_Authorize_Call) Run(run func(ctx context.Context, pr policies.Po
if args[1] != nil {
arg1 = args[1].(policies.Policy)
}
var arg2 *auth.PATAuthz
if args[2] != nil {
arg2 = args[2].(*auth.PATAuthz)
}
run(
arg0,
arg1,
arg2,
)
})
return _c
@@ -94,7 +101,7 @@ func (_c *Authz_Authorize_Call) Return(err error) *Authz_Authorize_Call {
return _c
}
func (_c *Authz_Authorize_Call) RunAndReturn(run func(ctx context.Context, pr policies.Policy) error) *Authz_Authorize_Call {
func (_c *Authz_Authorize_Call) RunAndReturn(run func(ctx context.Context, pr policies.Policy, patAuthz *auth.PATAuthz) error) *Authz_Authorize_Call {
_c.Call.Return(run)
return _c
}
+14 -8
View File
@@ -114,16 +114,16 @@ func (_c *Service_AddScope_Call) RunAndReturn(run func(ctx context.Context, toke
}
// Authorize provides a mock function for the type Service
func (_mock *Service) Authorize(ctx context.Context, pr policies.Policy) error {
ret := _mock.Called(ctx, pr)
func (_mock *Service) Authorize(ctx context.Context, pr policies.Policy, patAuthz *auth.PATAuthz) error {
ret := _mock.Called(ctx, pr, patAuthz)
if len(ret) == 0 {
panic("no return value specified for Authorize")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(context.Context, policies.Policy) error); ok {
r0 = returnFunc(ctx, pr)
if returnFunc, ok := ret.Get(0).(func(context.Context, policies.Policy, *auth.PATAuthz) error); ok {
r0 = returnFunc(ctx, pr, patAuthz)
} else {
r0 = ret.Error(0)
}
@@ -138,11 +138,12 @@ type Service_Authorize_Call struct {
// Authorize is a helper method to define mock.On call
// - ctx context.Context
// - pr policies.Policy
func (_e *Service_Expecter) Authorize(ctx interface{}, pr interface{}) *Service_Authorize_Call {
return &Service_Authorize_Call{Call: _e.mock.On("Authorize", ctx, pr)}
// - patAuthz *auth.PATAuthz
func (_e *Service_Expecter) Authorize(ctx interface{}, pr interface{}, patAuthz interface{}) *Service_Authorize_Call {
return &Service_Authorize_Call{Call: _e.mock.On("Authorize", ctx, pr, patAuthz)}
}
func (_c *Service_Authorize_Call) Run(run func(ctx context.Context, pr policies.Policy)) *Service_Authorize_Call {
func (_c *Service_Authorize_Call) Run(run func(ctx context.Context, pr policies.Policy, patAuthz *auth.PATAuthz)) *Service_Authorize_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
@@ -152,9 +153,14 @@ func (_c *Service_Authorize_Call) Run(run func(ctx context.Context, pr policies.
if args[1] != nil {
arg1 = args[1].(policies.Policy)
}
var arg2 *auth.PATAuthz
if args[2] != nil {
arg2 = args[2].(*auth.PATAuthz)
}
run(
arg0,
arg1,
arg2,
)
})
return _c
@@ -165,7 +171,7 @@ func (_c *Service_Authorize_Call) Return(err error) *Service_Authorize_Call {
return _c
}
func (_c *Service_Authorize_Call) RunAndReturn(run func(ctx context.Context, pr policies.Policy) error) *Service_Authorize_Call {
func (_c *Service_Authorize_Call) RunAndReturn(run func(ctx context.Context, pr policies.Policy, patAuthz *auth.PATAuthz) error) *Service_Authorize_Call {
_c.Call.Return(run)
return _c
}
+16
View File
@@ -69,6 +69,7 @@ const (
DashboardType
MessagesType
DomainsType
UsersType
)
const (
@@ -78,6 +79,7 @@ const (
DashboardsStr = "dashboards"
MessagesStr = "messages"
DomainsStr = "domains"
UsersStr = "users"
)
func (et EntityType) String() string {
@@ -94,6 +96,8 @@ func (et EntityType) String() string {
return MessagesStr
case DomainsType:
return DomainsStr
case UsersType:
return UsersStr
default:
return fmt.Sprintf("unknown domain entity type %d", et)
}
@@ -113,6 +117,8 @@ func ParseEntityType(et string) (EntityType, error) {
return MessagesType, nil
case DomainsStr:
return DomainsType, nil
case UsersStr:
return UsersType, nil
default:
return 0, fmt.Errorf("unknown domain entity type %s", et)
}
@@ -280,6 +286,16 @@ func (s *Scope) Validate() error {
return nil
}
// PATAuthz represents the PAT authorization request fields.
type PATAuthz struct {
PatID string
UserID string
EntityType EntityType
EntityID string
Operation string
Domain string
}
// PAT represents Personal Access Token.
type PAT struct {
ID string `json:"id,omitempty"`
+7 -11
View File
@@ -56,7 +56,7 @@ type Authz interface {
// `object`. Authorize returns a non-nil error if the subject has
// no relation on the object (which simply means the operation is
// denied).
Authorize(ctx context.Context, pr policies.Policy) error
Authorize(ctx context.Context, pr policies.Policy, patAuthz *PATAuthz) error
}
// Authn specifies an API that must be fulfilled by the domain service
@@ -205,13 +205,9 @@ func (svc service) RetrieveJWKS() []PublicKeyInfo {
return keys
}
func (svc service) Authorize(ctx context.Context, pr policies.Policy) error {
if pr.PatID != "" {
entityType, err := ParseEntityType(pr.EntityType)
if err != nil {
return err
}
if err := svc.AuthorizePAT(ctx, pr.UserID, pr.PatID, entityType, pr.Domain, pr.Operation, pr.EntityID); err != nil {
func (svc service) Authorize(ctx context.Context, pr policies.Policy, patAuthz *PATAuthz) error {
if patAuthz != nil {
if err := svc.AuthorizePAT(ctx, patAuthz.UserID, patAuthz.PatID, patAuthz.EntityType, patAuthz.Domain, patAuthz.Operation, patAuthz.EntityID); err != nil {
return err
}
}
@@ -336,7 +332,7 @@ func (svc service) checkUserRole(ctx context.Context, key Key) (err error) {
Permission: policies.AdminPermission,
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
}); err != nil {
}, nil); err != nil {
return errRoleAuth
}
return nil
@@ -347,7 +343,7 @@ func (svc service) checkUserRole(ctx context.Context, key Key) (err error) {
Permission: policies.MembershipPermission,
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
}); err != nil {
}, nil); err != nil {
return errRoleAuth
}
return nil
@@ -364,7 +360,7 @@ func (svc service) getUserRole(ctx context.Context, userID string) (role Role) {
Permission: policies.AdminPermission,
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
}); err == nil {
}, nil); err == nil {
rl = AdminRole
}
+105 -72
View File
@@ -648,19 +648,20 @@ func TestAuthorize(t *testing.T) {
cases := []struct {
desc string
policyReq policies.Policy
patAuthz *auth.PATAuthz
checkDomainPolicyReq policies.Policy
checkPolicyReq policies.Policy
patEntityType auth.EntityType
patScopeErr error
expectPATCheck bool
expectCheckPolicy bool
patErr error
parseReq string
parseRes auth.Key
parseErr error
callBackErr error
checkPolicyErr error
checkDomainPolicyErr error
authorizePATErr error
err error
}{
{
desc: "authorize a user key successfully",
policyReq: policies.Policy{
@@ -677,41 +678,51 @@ func TestAuthorize(t *testing.T) {
ObjectType: policies.PlatformType,
Permission: policies.AdminPermission,
},
expectCheckPolicy: true,
err: nil,
checkDomainPolicyReq: policies.Policy{
Subject: userID,
SubjectType: policies.UserType,
Object: validID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
},
err: nil,
},
{
desc: "authorize with PAT scope successfully",
desc: "authorize with PAT successfully",
policyReq: policies.Policy{
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
Permission: policies.AdminPermission,
PatID: validID,
UserID: userID,
EntityType: auth.ChannelsScopeStr,
Subject: userID,
Object: validID,
ObjectType: policies.ClientType,
Permission: policies.ViewPermission,
Domain: domainID,
Operation: auth.OpListChannels,
EntityID: auth.AnyIDs,
},
patAuthz: &auth.PATAuthz{
PatID: validID,
UserID: userID,
EntityType: auth.ClientsType,
EntityID: validID,
Operation: "read",
Domain: domainID,
},
checkDomainPolicyReq: policies.Policy{
Subject: userID,
SubjectType: policies.UserType,
Object: domainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
},
checkPolicyReq: policies.Policy{
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
Permission: policies.AdminPermission,
PatID: validID,
UserID: userID,
EntityType: auth.ChannelsScopeStr,
Subject: userID,
Object: validID,
ObjectType: policies.ClientType,
Permission: policies.ViewPermission,
Domain: domainID,
Operation: auth.OpListChannels,
EntityID: auth.AnyIDs,
},
patEntityType: auth.ChannelsType,
expectPATCheck: true,
expectCheckPolicy: true,
err: nil,
err: nil,
},
{
desc: "authorize with PAT scope check failure",
@@ -721,12 +732,14 @@ func TestAuthorize(t *testing.T) {
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
Permission: policies.AdminPermission,
PatID: validID,
UserID: userID,
EntityType: auth.ChannelsScopeStr,
Domain: domainID,
Operation: auth.OpListChannels,
EntityID: auth.AnyIDs,
},
patAuthz: &auth.PATAuthz{
PatID: validID,
UserID: userID,
EntityType: auth.ChannelsType,
Domain: domainID,
Operation: auth.OpListChannels,
EntityID: auth.AnyIDs,
},
checkPolicyReq: policies.Policy{
SubjectType: policies.UserType,
@@ -734,17 +747,9 @@ func TestAuthorize(t *testing.T) {
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
Permission: policies.AdminPermission,
PatID: validID,
UserID: userID,
EntityType: auth.ChannelsScopeStr,
Domain: domainID,
Operation: auth.OpListChannels,
EntityID: auth.AnyIDs,
},
patEntityType: auth.ChannelsType,
patScopeErr: repoerr.ErrNotFound,
expectPATCheck: true,
err: svcerr.ErrAuthorization,
patErr: svcerr.ErrAuthorization,
err: svcerr.ErrAuthorization,
},
{
desc: "authorize with invalid PAT entity type",
@@ -754,12 +759,14 @@ func TestAuthorize(t *testing.T) {
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
Permission: policies.AdminPermission,
PatID: validID,
UserID: userID,
EntityType: "invalid",
Domain: domainID,
Operation: auth.OpListChannels,
EntityID: auth.AnyIDs,
},
patAuthz: &auth.PATAuthz{
PatID: validID,
UserID: userID,
EntityType: auth.EntityType(100),
Domain: domainID,
Operation: auth.OpListChannels,
EntityID: auth.AnyIDs,
},
checkPolicyReq: policies.Policy{
SubjectType: policies.UserType,
@@ -767,32 +774,55 @@ func TestAuthorize(t *testing.T) {
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
Permission: policies.AdminPermission,
PatID: validID,
UserID: userID,
EntityType: "invalid",
Domain: domainID,
Operation: auth.OpListChannels,
EntityID: auth.AnyIDs,
},
err: errors.New("unknown domain entity type invalid"),
patErr: errors.New("unknown domain entity type invalid"),
err: errors.New("unknown domain entity type invalid"),
},
{
desc: "authorize invalid platform object",
desc: "authorize with PAT but PAT authorization fails",
policyReq: policies.Policy{
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Object: "invalid",
ObjectType: policies.PlatformType,
Permission: policies.AdminPermission,
Subject: userID,
Object: validID,
ObjectType: policies.ClientType,
Permission: policies.ViewPermission,
},
checkPolicyReq: policies.Policy{
patAuthz: &auth.PATAuthz{
PatID: validID,
UserID: userID,
EntityType: auth.ClientsType,
EntityID: validID,
Operation: "read",
Domain: domainID,
},
checkPolicyReq: policies.Policy{},
authorizePATErr: svcerr.ErrAuthorization,
err: svcerr.ErrAuthorization,
},
{
desc: "authorize with PAT - PAT authorization fails but policy check not reached",
policyReq: policies.Policy{
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Object: "invalid",
ObjectType: policies.PlatformType,
Permission: policies.AdminPermission,
Subject: userID,
Object: validID,
ObjectType: policies.ClientType,
Permission: policies.ViewPermission,
},
err: svcerr.ErrMalformedEntity,
patAuthz: &auth.PATAuthz{
PatID: validID,
UserID: userID,
EntityType: auth.ClientsType,
EntityID: validID,
Operation: "write",
Domain: domainID,
},
checkPolicyReq: policies.Policy{},
authorizePATErr: svcerr.ErrAuthorization,
err: svcerr.ErrAuthorization,
},
{
desc: "authorize with policy check error",
@@ -810,23 +840,26 @@ func TestAuthorize(t *testing.T) {
ObjectType: policies.PlatformType,
Permission: policies.AdminPermission,
},
checkPolicyErr: repoerr.ErrNotFound,
expectCheckPolicy: true,
err: svcerr.ErrAuthorization,
checkPolicyErr: repoerr.ErrNotFound,
err: svcerr.ErrAuthorization,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
var policyCall *mock.Call
if tc.expectCheckPolicy {
if tc.checkPolicyReq != (policies.Policy{}) {
policyCall = pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPolicyReq).Return(tc.checkPolicyErr)
}
var patCall *mock.Call
if tc.expectPATCheck {
patCall = patsrepo.On("CheckScope", mock.Anything, tc.policyReq.UserID, tc.policyReq.PatID, tc.patEntityType, tc.policyReq.Domain, tc.policyReq.Operation, tc.policyReq.EntityID).Return(tc.patScopeErr)
if tc.patAuthz != nil {
patErr := tc.patErr
if patErr == nil {
patErr = tc.authorizePATErr
}
patCall = patsrepo.On("CheckScope", mock.Anything, tc.patAuthz.UserID, tc.patAuthz.PatID, tc.patAuthz.EntityType, tc.patAuthz.Domain, tc.patAuthz.Operation, tc.patAuthz.EntityID).Return(patErr)
}
repoCall := krepo.On("Remove", mock.Anything, mock.Anything, mock.Anything).Return(nil)
err := svc.Authorize(context.Background(), tc.policyReq)
err := svc.Authorize(context.Background(), tc.policyReq, tc.patAuthz)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
if policyCall != nil {
policyCall.Unset()
+18 -9
View File
@@ -326,9 +326,8 @@ func (am *authorizationMiddleware) RemoveParentGroup(ctx context.Context, sessio
}
func (am *authorizationMiddleware) authorize(ctx context.Context, session authn.Session, entityType string, op permissions.Operation, req smqauthz.PolicyReq) error {
req.UserID = session.UserID
req.PatID = session.PatID
req.Domain = session.DomainID
perm, err := am.entitiesOps.GetPermission(entityType, op)
if err != nil {
return err
@@ -336,14 +335,24 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, session authn.
req.Permission = perm.String()
req.EntityID = req.Object
req.EntityType = auth.ChannelsType.String()
req.Operation = am.entitiesOps.OperationName(entityType, op)
if op == operations.OpListUserChannels || op == dOperations.OpCreateDomainChannels || op == dOperations.OpListDomainChannels {
req.EntityID = auth.AnyIDs
var pat *smqauthz.PATReq
if session.PatID != "" {
entityID := req.Object
opName := am.entitiesOps.OperationName(entityType, op)
if op == operations.OpListUserChannels || op == dOperations.OpCreateDomainChannels || op == dOperations.OpListDomainChannels {
entityID = auth.AnyIDs
}
pat = &smqauthz.PATReq{
UserID: session.UserID,
PatID: session.PatID,
EntityID: entityID,
EntityType: auth.ChannelsType.String(),
Operation: opName,
Domain: session.DomainID,
}
}
if err := am.authz.Authorize(ctx, req); err != nil {
if err := am.authz.Authorize(ctx, req, pat); err != nil {
return err
}
@@ -360,7 +369,7 @@ func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session
Permission: policies.AdminPermission,
ObjectType: policies.PlatformType,
Object: policies.SuperMQObject,
}); err != nil {
}, nil); err != nil {
return err
}
return nil
+17 -9
View File
@@ -257,8 +257,6 @@ func (am *authorizationMiddleware) RemoveParentGroup(ctx context.Context, sessio
}
func (am *authorizationMiddleware) authorize(ctx context.Context, session authn.Session, entityType string, op permissions.Operation, req smqauthz.PolicyReq) error {
req.UserID = session.UserID
req.PatID = session.PatID
req.Domain = session.DomainID
perm, err := am.entitiesOps.GetPermission(entityType, op)
@@ -268,14 +266,24 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, session authn.
req.Permission = perm.String()
req.EntityID = req.Object
req.EntityType = auth.ClientsType.String()
req.Operation = am.entitiesOps.OperationName(entityType, op)
if op == operations.OpListUserClients || op == dOperations.OpCreateDomainClients || op == dOperations.OpListDomainClients {
req.EntityID = auth.AnyIDs
var pat *smqauthz.PATReq
if session.PatID != "" {
entityID := req.Object
opName := am.entitiesOps.OperationName(entityType, op)
if op == operations.OpListUserClients || op == dOperations.OpCreateDomainClients || op == dOperations.OpListDomainClients {
entityID = auth.AnyIDs
}
pat = &smqauthz.PATReq{
UserID: session.UserID,
PatID: session.PatID,
EntityID: entityID,
EntityType: auth.ClientsType.String(),
Operation: opName,
Domain: session.DomainID,
}
}
if err := am.authz.Authorize(ctx, req); err != nil {
if err := am.authz.Authorize(ctx, req, pat); err != nil {
return err
}
@@ -292,7 +300,7 @@ func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session
Permission: policies.AdminPermission,
ObjectType: policies.PlatformType,
Object: policies.SuperMQObject,
}); err != nil {
}, nil); err != nil {
return err
}
return nil
+1 -1
View File
@@ -412,7 +412,7 @@ func createAdminPolicy(ctx context.Context, userID string, authz smqauthz.Author
Permission: policies.AdministratorRelation,
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
}); err != nil {
}, nil); err != nil {
err := policyService.AddPolicy(ctx, policies.Policy{
SubjectType: policies.UserType,
Subject: userID,
+62 -11
View File
@@ -6,6 +6,7 @@ package middleware
import (
"context"
"github.com/absmach/supermq/auth"
"github.com/absmach/supermq/domains"
"github.com/absmach/supermq/domains/operations"
"github.com/absmach/supermq/pkg/authn"
@@ -61,7 +62,7 @@ func (am *authorizationMiddleware) RetrieveDomain(ctx context.Context, session a
return am.svc.RetrieveDomain(ctx, session, id, withRoles)
}
if err := am.authorize(ctx, policies.DomainType, operations.OpRetrieveDomain, authz.PolicyReq{
if err := am.authorize(ctx, session, policies.DomainType, operations.OpRetrieveDomain, authz.PolicyReq{
Subject: session.DomainUserID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
@@ -75,7 +76,7 @@ func (am *authorizationMiddleware) RetrieveDomain(ctx context.Context, session a
}
func (am *authorizationMiddleware) UpdateDomain(ctx context.Context, session authn.Session, id string, d domains.DomainReq) (domains.Domain, error) {
if err := am.authorize(ctx, policies.DomainType, operations.OpUpdateDomain, authz.PolicyReq{
if err := am.authorize(ctx, session, policies.DomainType, operations.OpUpdateDomain, authz.PolicyReq{
Subject: session.DomainUserID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
@@ -89,7 +90,7 @@ func (am *authorizationMiddleware) UpdateDomain(ctx context.Context, session aut
}
func (am *authorizationMiddleware) EnableDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) {
if err := am.authorize(ctx, policies.DomainType, operations.OpEnableDomain, authz.PolicyReq{
if err := am.authorize(ctx, session, policies.DomainType, operations.OpEnableDomain, authz.PolicyReq{
Subject: session.DomainUserID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
@@ -103,7 +104,7 @@ func (am *authorizationMiddleware) EnableDomain(ctx context.Context, session aut
}
func (am *authorizationMiddleware) DisableDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) {
if err := am.authorize(ctx, policies.DomainType, operations.OpDisableDomain, authz.PolicyReq{
if err := am.authorize(ctx, session, policies.DomainType, operations.OpDisableDomain, authz.PolicyReq{
Subject: session.DomainUserID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
@@ -125,7 +126,7 @@ func (am *authorizationMiddleware) FreezeDomain(ctx context.Context, session aut
Permission: policies.AdminPermission,
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
}); err != nil {
}, nil); err != nil {
return domains.Domain{}, err
}
@@ -141,7 +142,7 @@ func (am *authorizationMiddleware) ListDomains(ctx context.Context, session auth
}
func (am *authorizationMiddleware) SendInvitation(ctx context.Context, session authn.Session, invitation domains.Invitation) (domains.Invitation, error) {
if err := am.authorize(ctx, policies.DomainType, operations.OpSendDomainInvitation, authz.PolicyReq{
if err := am.authorize(ctx, session, policies.DomainType, operations.OpSendDomainInvitation, authz.PolicyReq{
Subject: session.DomainUserID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
@@ -151,6 +152,10 @@ func (am *authorizationMiddleware) SendInvitation(ctx context.Context, session a
return domains.Invitation{}, err
}
if err := am.checkAdmin(ctx, session); err != nil {
return domains.Invitation{}, err
}
return am.svc.SendInvitation(ctx, session, invitation)
}
@@ -159,7 +164,7 @@ func (am *authorizationMiddleware) ListInvitations(ctx context.Context, session
}
func (am *authorizationMiddleware) ListDomainInvitations(ctx context.Context, session authn.Session, page domains.InvitationPageMeta) (invs domains.InvitationPage, err error) {
if err := am.authorize(ctx, policies.DomainType, operations.OpListDomainInvitations, authz.PolicyReq{
if err := am.authorize(ctx, session, policies.DomainType, operations.OpListDomainInvitations, authz.PolicyReq{
Subject: session.DomainUserID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
@@ -181,7 +186,7 @@ func (am *authorizationMiddleware) RejectInvitation(ctx context.Context, session
}
func (am *authorizationMiddleware) DeleteInvitation(ctx context.Context, session authn.Session, inviteeUserID, domainID string) (err error) {
if err := am.authorize(ctx, policies.DomainType, operations.OpDeleteDomainInvitation, authz.PolicyReq{
if err := am.authorize(ctx, session, policies.DomainType, operations.OpDeleteDomainInvitation, authz.PolicyReq{
Subject: session.DomainUserID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
@@ -194,7 +199,9 @@ func (am *authorizationMiddleware) DeleteInvitation(ctx context.Context, session
return am.svc.DeleteInvitation(ctx, session, inviteeUserID, domainID)
}
func (am *authorizationMiddleware) authorize(ctx context.Context, entityType string, op permissions.Operation, authReq authz.PolicyReq) error {
func (am *authorizationMiddleware) authorize(ctx context.Context, session authn.Session, entityType string, op permissions.Operation, authReq authz.PolicyReq) error {
authReq.Domain = session.DomainID
perm, err := am.entitiesOps.GetPermission(entityType, op)
if err != nil {
return err
@@ -202,13 +209,57 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, entityType str
authReq.Permission = perm.String()
if err := am.authz.Authorize(ctx, authReq); err != nil {
var pat *smqauthz.PATReq
if session.PatID != "" {
entityID := authReq.Object
opName := am.entitiesOps.OperationName(entityType, op)
pat = &smqauthz.PATReq{
UserID: session.UserID,
PatID: session.PatID,
EntityID: entityID,
EntityType: auth.DomainsType.String(),
Operation: opName,
Domain: session.DomainID,
}
}
if err := am.authz.Authorize(ctx, authReq, pat); err != nil {
return err
}
return nil
}
// checkAdmin checks if the given user is a domain or platform administrator.
func (am *authorizationMiddleware) checkAdmin(ctx context.Context, session authn.Session) error {
req := smqauthz.PolicyReq{
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Permission: policies.AdminPermission,
ObjectType: policies.DomainType,
Object: session.DomainID,
}
if err := am.authz.Authorize(ctx, req, nil); err == nil {
return nil
}
req = smqauthz.PolicyReq{
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.UserID,
Permission: policies.AdminPermission,
ObjectType: policies.PlatformType,
Object: policies.SuperMQObject,
}
if err := am.authz.Authorize(ctx, req, nil); err == nil {
return nil
}
return svcerr.ErrAuthorization
}
func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session authn.Session) error {
if session.Role != authn.AdminRole {
return svcerr.ErrSuperAdminAction
@@ -219,7 +270,7 @@ func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session
Permission: policies.AdminPermission,
ObjectType: policies.PlatformType,
Object: policies.SuperMQObject,
}); err != nil {
}, nil); err != nil {
return err
}
return nil
+17 -9
View File
@@ -376,15 +376,13 @@ func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session
Permission: policies.AdminPermission,
ObjectType: policies.PlatformType,
Object: policies.SuperMQObject,
}); err != nil {
}, nil); err != nil {
return err
}
return nil
}
func (am *authorizationMiddleware) authorize(ctx context.Context, session authn.Session, entityType string, op permissions.Operation, pr smqauthz.PolicyReq) error {
pr.UserID = session.UserID
pr.PatID = session.PatID
pr.Domain = session.DomainID
perm, err := am.entitiesOps.GetPermission(entityType, op)
@@ -393,14 +391,24 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, session authn.
}
pr.Permission = perm.String()
pr.EntityID = pr.Object
pr.EntityType = auth.GroupsType.String()
pr.Operation = am.entitiesOps.OperationName(entityType, op)
if op == dOperations.OpListDomainGroups || op == dOperations.OpCreateDomainGroups {
pr.EntityID = auth.AnyIDs
var pat *smqauthz.PATReq
if session.PatID != "" {
entityID := pr.Object
opName := am.entitiesOps.OperationName(entityType, op)
if op == dOperations.OpListDomainGroups || op == dOperations.OpCreateDomainGroups {
entityID = auth.AnyIDs
}
pat = &smqauthz.PATReq{
UserID: session.UserID,
PatID: session.PatID,
EntityID: entityID,
EntityType: auth.GroupsType.String(),
Operation: opName,
Domain: session.DomainID,
}
}
if err := am.authz.Authorize(ctx, pr); err != nil {
if err := am.authz.Authorize(ctx, pr, pat); err != nil {
return err
}
return nil
+15 -6
View File
@@ -9,7 +9,7 @@ option go_package = "github.com/absmach/supermq/api/grpc/auth/v1";
// AuthService is a service that provides authentication
// and authorization functionalities for SuperMQ services.
service AuthService {
rpc Authorize(PolicyReq) returns (AuthZRes) {}
rpc Authorize(AuthZReq) returns (AuthZRes) {}
rpc Authenticate(AuthNReq) returns (AuthNRes) {}
}
@@ -35,11 +35,20 @@ message PolicyReq {
string permission = 7;
string object = 8;
string object_type = 9;
string pat_id = 10;
string operation = 11;
string user_id = 12;
string entity_id = 13;
string entity_type = 14;
}
message PATReq {
string pat_id = 1;
string domain = 2;
string operation = 3;
string user_id = 4;
string entity_id = 5;
string entity_type = 6;
}
message AuthZReq {
PolicyReq policy_req = 1;
optional PATReq pat_req = 2;
}
message AuthZRes {
+2 -2
View File
@@ -58,7 +58,7 @@ func (am *authorizationMiddleware) RetrieveAll(ctx context.Context, session smqa
ObjectType: objectType,
Object: object,
}
if err := am.authz.Authorize(ctx, req); err != nil {
if err := am.authz.Authorize(ctx, req, nil); err != nil {
return journal.JournalsPage{}, err
}
@@ -76,7 +76,7 @@ func (am *authorizationMiddleware) RetrieveClientTelemetry(ctx context.Context,
Object: clientID,
}
if err := am.authz.Authorize(ctx, req); err != nil {
if err := am.authz.Authorize(ctx, req, nil); err != nil {
return journal.ClientTelemetry{}, err
}
+48 -34
View File
@@ -46,7 +46,7 @@ func NewAuthorization(ctx context.Context, cfg grpcclient.Config, domainsAuthz p
}, client, nil
}
func (a authorization) Authorize(ctx context.Context, pr authz.PolicyReq) error {
func (a authorization) Authorize(ctx context.Context, pr authz.PolicyReq, pat *authz.PATReq) error {
if pr.SubjectType == policies.UserType && (pr.ObjectType == policies.GroupType || pr.ObjectType == policies.ClientType || pr.ObjectType == policies.DomainType) {
domainID := pr.Domain
if domainID == "" {
@@ -60,21 +60,29 @@ func (a authorization) Authorize(ctx context.Context, pr authz.PolicyReq) error
}
}
req := grpcAuthV1.PolicyReq{
Domain: pr.Domain,
SubjectType: pr.SubjectType,
SubjectKind: pr.SubjectKind,
SubjectRelation: pr.SubjectRelation,
Subject: pr.Subject,
Relation: pr.Relation,
Permission: pr.Permission,
Object: pr.Object,
ObjectType: pr.ObjectType,
PatId: pr.PatID,
Operation: pr.Operation,
UserId: pr.UserID,
EntityId: pr.EntityID,
EntityType: pr.EntityType,
req := grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Domain: pr.Domain,
SubjectType: pr.SubjectType,
SubjectKind: pr.SubjectKind,
SubjectRelation: pr.SubjectRelation,
Subject: pr.Subject,
Relation: pr.Relation,
Permission: pr.Permission,
Object: pr.Object,
ObjectType: pr.ObjectType,
},
}
if pat != nil {
req.PatReq = &grpcAuthV1.PATReq{
PatId: pat.PatID,
Domain: pat.Domain,
Operation: pat.Operation,
UserId: pat.UserID,
EntityId: pat.EntityID,
EntityType: pat.EntityType,
}
}
res, err := a.authSvcClient.Authorize(ctx, &req)
@@ -95,32 +103,38 @@ func (a authorization) checkDomain(ctx context.Context, subjectType, subject, do
switch status {
case domains.FreezeStatus:
_, err := a.authSvcClient.Authorize(ctx, &grpcAuthV1.PolicyReq{
Subject: subject,
SubjectType: subjectType,
Permission: policies.AdminPermission,
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
_, err := a.authSvcClient.Authorize(ctx, &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: subject,
SubjectType: subjectType,
Permission: policies.AdminPermission,
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
},
})
return err
case domains.DisabledStatus:
_, err := a.authSvcClient.Authorize(ctx, &grpcAuthV1.PolicyReq{
Subject: subject,
SubjectType: subjectType,
Permission: policies.AdminPermission,
Object: domainID,
ObjectType: policies.DomainType,
_, err := a.authSvcClient.Authorize(ctx, &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: subject,
SubjectType: subjectType,
Permission: policies.AdminPermission,
Object: domainID,
ObjectType: policies.DomainType,
},
})
return err
case domains.EnabledStatus:
_, err := a.authSvcClient.Authorize(ctx, &grpcAuthV1.PolicyReq{
Subject: subject,
SubjectType: subjectType,
Permission: policies.MembershipPermission,
Object: domainID,
ObjectType: policies.DomainType,
_, err := a.authSvcClient.Authorize(ctx, &grpcAuthV1.AuthZReq{
PolicyReq: &grpcAuthV1.PolicyReq{
Subject: subject,
SubjectType: subjectType,
Permission: policies.MembershipPermission,
Object: domainID,
ObjectType: policies.DomainType,
},
})
return err
+21 -8
View File
@@ -42,17 +42,30 @@ type PolicyReq struct {
// Permission contains the permission. Supported permissions are admin, delete, edit, share, view,
// membership, create, admin_only, edit_only, view_only, membership_only, ext_admin, ext_edit, ext_view.
Permission string `json:"permission,omitempty"`
}
// PAT authorization fields
UserID string `json:"user_id,omitempty"`
PatID string `json:"pat_id,omitempty"`
EntityType string `json:"entity_type,omitempty"`
DomainID string `json:"domain_id,omitempty"`
Operation string `json:"operation,omitempty"`
EntityID string `json:"entity_id,omitempty"`
// PATReq represents a Personal Access Token authorization request.
type PATReq struct {
// PatID contains the personal access token ID.
PatID string `json:"pat_id"`
// Domain contains the domain ID.
Domain string `json:"domain"`
// Operation contains the operation type for PAT authorization.
Operation string `json:"operation"`
// UserID contains the user ID for PAT authorization.
UserID string `json:"user_id"`
// EntityID contains the entity ID for PAT authorization.
EntityID string `json:"entity_id"`
// EntityType contains the entity type for PAT authorization.
EntityType string `json:"entity_type"`
}
// Authz is supermq authorization library.
type Authorization interface {
Authorize(ctx context.Context, pr PolicyReq) error
Authorize(ctx context.Context, pr PolicyReq, pat *PATReq) error
}
+14 -8
View File
@@ -43,16 +43,16 @@ func (_m *Authorization) EXPECT() *Authorization_Expecter {
}
// Authorize provides a mock function for the type Authorization
func (_mock *Authorization) Authorize(ctx context.Context, pr authz.PolicyReq) error {
ret := _mock.Called(ctx, pr)
func (_mock *Authorization) Authorize(ctx context.Context, pr authz.PolicyReq, pat *authz.PATReq) error {
ret := _mock.Called(ctx, pr, pat)
if len(ret) == 0 {
panic("no return value specified for Authorize")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authz.PolicyReq) error); ok {
r0 = returnFunc(ctx, pr)
if returnFunc, ok := ret.Get(0).(func(context.Context, authz.PolicyReq, *authz.PATReq) error); ok {
r0 = returnFunc(ctx, pr, pat)
} else {
r0 = ret.Error(0)
}
@@ -67,11 +67,12 @@ type Authorization_Authorize_Call struct {
// Authorize is a helper method to define mock.On call
// - ctx context.Context
// - pr authz.PolicyReq
func (_e *Authorization_Expecter) Authorize(ctx interface{}, pr interface{}) *Authorization_Authorize_Call {
return &Authorization_Authorize_Call{Call: _e.mock.On("Authorize", ctx, pr)}
// - pat *authz.PATReq
func (_e *Authorization_Expecter) Authorize(ctx interface{}, pr interface{}, pat interface{}) *Authorization_Authorize_Call {
return &Authorization_Authorize_Call{Call: _e.mock.On("Authorize", ctx, pr, pat)}
}
func (_c *Authorization_Authorize_Call) Run(run func(ctx context.Context, pr authz.PolicyReq)) *Authorization_Authorize_Call {
func (_c *Authorization_Authorize_Call) Run(run func(ctx context.Context, pr authz.PolicyReq, pat *authz.PATReq)) *Authorization_Authorize_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
@@ -81,9 +82,14 @@ func (_c *Authorization_Authorize_Call) Run(run func(ctx context.Context, pr aut
if args[1] != nil {
arg1 = args[1].(authz.PolicyReq)
}
var arg2 *authz.PATReq
if args[2] != nil {
arg2 = args[2].(*authz.PATReq)
}
run(
arg0,
arg1,
arg2,
)
})
return _c
@@ -94,7 +100,7 @@ func (_c *Authorization_Authorize_Call) Return(err error) *Authorization_Authori
return _c
}
func (_c *Authorization_Authorize_Call) RunAndReturn(run func(ctx context.Context, pr authz.PolicyReq) error) *Authorization_Authorize_Call {
func (_c *Authorization_Authorize_Call) RunAndReturn(run func(ctx context.Context, pr authz.PolicyReq, pat *authz.PATReq) error) *Authorization_Authorize_Call {
_c.Call.Return(run)
return _c
}
-13
View File
@@ -49,19 +49,6 @@ type Policy struct {
// Permission contains the permission. Supported permissions are admin, delete, edit, share, view,
// membership, create, admin_only, edit_only, view_only, membership_only, ext_admin, ext_edit, ext_view.
Permission string `json:"permission,omitempty"`
// PAT authorization fields
// PatID contains the personal access token ID.
PatID string `json:"pat_id,omitempty"`
// Operation contains the operation type for PAT authorization.
Operation string `json:"operation,omitempty"`
// UserID contains the user ID for PAT authorization.
UserID string `json:"user_id,omitempty"`
// EntityType contains the entity type for PAT authorization.
EntityType string `json:"entity_type,omitempty"`
// EntityID contains the entity ID for PAT authorization.
EntityID string `json:"entity_id,omitempty"`
}
func (pr Policy) String() string {
@@ -5,6 +5,7 @@ package middleware
import (
"context"
"fmt"
"github.com/absmach/supermq/auth"
"github.com/absmach/supermq/pkg/authn"
@@ -293,8 +294,6 @@ func (ram RoleManagerAuthorizationMiddleware) RoleRemoveMembers(ctx context.Cont
}
func (ram RoleManagerAuthorizationMiddleware) authorize(ctx context.Context, session authn.Session, op permissions.RoleOperation, pr smqauthz.PolicyReq) error {
pr.UserID = session.UserID
pr.PatID = session.PatID
pr.Domain = session.DomainID
perm, err := ram.ops.GetPermission(op)
@@ -304,19 +303,31 @@ func (ram RoleManagerAuthorizationMiddleware) authorize(ctx context.Context, ses
pr.Permission = perm.String()
pr.EntityID = pr.Object
opName := ram.ops.OperationName(op)
switch pr.ObjectType {
case policies.GroupType:
pr.EntityType = auth.GroupsType.String()
case policies.ClientType:
pr.EntityType = auth.ClientsType.String()
case policies.ChannelType:
pr.EntityType = auth.ChannelsType.String()
var pat *smqauthz.PATReq
if session.PatID != "" {
opName := ram.ops.OperationName(op)
var patEntityType string
switch pr.ObjectType {
case policies.GroupType:
patEntityType = auth.GroupsType.String()
case policies.ClientType:
patEntityType = auth.ClientsType.String()
case policies.ChannelType:
patEntityType = auth.ChannelsType.String()
default:
return errors.Wrap(errors.ErrAuthorization, fmt.Errorf("unsupported entity type for PAT: %s", pr.ObjectType))
}
pat = &smqauthz.PATReq{
UserID: session.UserID,
PatID: session.PatID,
EntityID: pr.Object,
EntityType: patEntityType,
Operation: auth.RoleOperationPrefix + opName,
Domain: session.DomainID,
}
}
pr.Operation = auth.RoleOperationPrefix + opName
if err := ram.authz.Authorize(ctx, pr); err != nil {
if err := ram.authz.Authorize(ctx, pr, pat); err != nil {
return errors.Wrap(errors.ErrAuthorization, err)
}
@@ -338,7 +349,7 @@ func (ram RoleManagerAuthorizationMiddleware) validateMembers(ctx context.Contex
SubjectKind: policies.UsersKind,
Object: policies.SuperMQObject,
ObjectType: policies.PlatformType,
}); err != nil {
}, nil); err != nil {
return errors.Wrap(errors.ErrMissingMember, err)
}
}
@@ -353,7 +364,7 @@ func (ram RoleManagerAuthorizationMiddleware) validateMembers(ctx context.Contex
SubjectKind: policies.UsersKind,
Object: session.DomainID,
ObjectType: policies.DomainType,
}); err != nil {
}, nil); err != nil {
return errors.Wrap(errors.ErrMissingDomainMember, err)
}
}
+15 -2
View File
@@ -7,6 +7,7 @@ import (
"context"
grpcTokenV1 "github.com/absmach/supermq/api/grpc/token/v1"
"github.com/absmach/supermq/auth"
"github.com/absmach/supermq/pkg/authn"
smqauthz "github.com/absmach/supermq/pkg/authz"
svcerr "github.com/absmach/supermq/pkg/errors/service"
@@ -190,7 +191,7 @@ func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session
Permission: policies.AdminPermission,
ObjectType: policies.PlatformType,
Object: policies.SuperMQObject,
}); err != nil {
}, nil); err != nil {
return err
}
return nil
@@ -207,7 +208,19 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, session authn.
Object: obj,
}
if err := am.authz.Authorize(ctx, req); err != nil {
var pat *smqauthz.PATReq
if session.PatID != "" {
pat = &smqauthz.PATReq{
UserID: session.UserID,
PatID: session.PatID,
EntityID: subj,
EntityType: auth.UsersType.String(),
Operation: perm,
Domain: domain,
}
}
if err := am.authz.Authorize(ctx, req, pat); err != nil {
return err
}
return nil