From f3a7230cc0c7f409ac50e6c6c366b543a0912a80 Mon Sep 17 00:00:00 2001 From: Steve Munene Date: Thu, 16 Apr 2026 11:01:03 +0300 Subject: [PATCH] NOISSUE - Add PAT support for rules and reports (#3466) Signed-off-by: nyagamunene --- alarms/middleware/authorization.go | 4 +-- alarms/operations/operations.go | 6 ++-- auth/pat.go | 32 ++++++++++++++++++-- auth/pat_test.go | 46 +++++++++++++++++++++++++++++ docker/permission.yaml | 10 +++---- re/middleware/authorization.go | 9 ++++-- re/operations/operations.go | 2 +- reports/middleware/authorization.go | 9 ++++-- reports/operations/operations.go | 2 +- 9 files changed, 102 insertions(+), 18 deletions(-) diff --git a/alarms/middleware/authorization.go b/alarms/middleware/authorization.go index e0abcd7f2..cb725660e 100644 --- a/alarms/middleware/authorization.go +++ b/alarms/middleware/authorization.go @@ -141,8 +141,8 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions pat = &smqauthz.PATReq{ UserID: session.UserID, PatID: session.PatID, - EntityID: session.DomainID, - EntityType: operations.EntityType, + EntityID: auth.AnyIDs, + EntityType: auth.RulesType.String(), Operation: opName, Domain: session.DomainID, } diff --git a/alarms/operations/operations.go b/alarms/operations/operations.go index 90c85cd3e..13c0a09ce 100644 --- a/alarms/operations/operations.go +++ b/alarms/operations/operations.go @@ -33,15 +33,15 @@ func OperationDetails() map[permissions.Operation]permissions.OperationDetails { PermissionRequired: true, }, OpAssignAlarm: { - Name: "assign", + Name: "alarm_assign", PermissionRequired: true, }, OpAcknowledgeAlarm: { - Name: "acknowledge", + Name: "alarm_acknowledge", PermissionRequired: true, }, OpResolveAlarm: { - Name: "resolve", + Name: "alarm_resolve", PermissionRequired: true, }, OpUpdateAlarm: { diff --git a/auth/pat.go b/auth/pat.go index 290706905..05b6e3134 100644 --- a/auth/pat.go +++ b/auth/pat.go @@ -44,7 +44,17 @@ const ( OpMessageSubscribe = "message_subscribe" ) -var errInvalidEntityOp = errors.NewRequestError("operation not valid for entity type") +var ( + errInvalidEntityOp = errors.NewRequestError("operation not valid for entity type") + errAlarmOpRequiresWildcardEntityID = errors.NewRequestError("alarm operations on rules entity type require wildcard entity ID") +) + +// alarmOnlyOperations are RulesType operations authorized at the domain level; only wildcard entity ID is valid. +var alarmOnlyOperations = map[string]struct{}{ + "alarm_assign": {}, + "alarm_acknowledge": {}, + "alarm_resolve": {}, +} type Operation = permissions.Operation @@ -70,6 +80,8 @@ const ( MessagesType DomainsType UsersType + RulesType + ReportsType ) const ( @@ -80,6 +92,8 @@ const ( MessagesStr = "messages" DomainsStr = "domains" UsersStr = "users" + RulesScopeStr = "rules" + ReportsScopeStr = "reports" ) func (et EntityType) String() string { @@ -98,6 +112,10 @@ func (et EntityType) String() string { return DomainsStr case UsersType: return UsersStr + case RulesType: + return RulesScopeStr + case ReportsType: + return ReportsScopeStr default: return fmt.Sprintf("unknown domain entity type %d", et) } @@ -119,6 +137,10 @@ func ParseEntityType(et string) (EntityType, error) { return DomainsType, nil case UsersStr: return UsersType, nil + case RulesScopeStr: + return RulesType, nil + case ReportsScopeStr: + return ReportsType, nil default: return 0, fmt.Errorf("unknown domain entity type %s", et) } @@ -147,7 +169,7 @@ func (et *EntityType) UnmarshalText(data []byte) (err error) { func IsValidOperationForEntity(entityType EntityType, operation string) bool { switch entityType { - case ClientsType, ChannelsType, GroupsType, DomainsType: + case ClientsType, ChannelsType, GroupsType, DomainsType, RulesType, ReportsType: return true case DashboardType: return operation == OpDashboardShare || operation == OpDashboardUnshare @@ -283,6 +305,12 @@ func (s *Scope) Validate() error { return errors.Wrap(apiutil.ErrInvalidQueryParams, errInvalidEntityOp) } + if s.EntityType == RulesType { + if _, ok := alarmOnlyOperations[s.Operation]; ok && s.EntityID != AnyIDs { + return errors.Wrap(apiutil.ErrInvalidQueryParams, errAlarmOpRequiresWildcardEntityID) + } + } + return nil } diff --git a/auth/pat_test.go b/auth/pat_test.go index f18e9a227..3bbaa3ae4 100644 --- a/auth/pat_test.go +++ b/auth/pat_test.go @@ -41,6 +41,16 @@ func TestEntityTypeString(t *testing.T) { et: auth.MessagesType, expected: "messages", }, + { + desc: "Rules entity type", + et: auth.RulesType, + expected: "rules", + }, + { + desc: "Reports entity type", + et: auth.ReportsType, + expected: "reports", + }, { desc: "Unknown entity type", et: auth.EntityType(100), @@ -87,6 +97,18 @@ func TestParseEntityType(t *testing.T) { expected: auth.DashboardType, err: false, }, + { + desc: "Parse rules", + et: "rules", + expected: auth.RulesType, + err: false, + }, + { + desc: "Parse reports", + et: "reports", + expected: auth.ReportsType, + err: false, + }, { desc: "Parse unknown entity type", et: "unknown", @@ -133,6 +155,18 @@ func TestEntityTypeMarshalJSON(t *testing.T) { expected: []byte(`"clients"`), err: nil, }, + { + desc: "Marshal rules", + et: auth.RulesType, + expected: []byte(`"rules"`), + err: nil, + }, + { + desc: "Marshal reports", + et: auth.ReportsType, + expected: []byte(`"reports"`), + err: nil, + }, } for _, tc := range cases { @@ -163,6 +197,18 @@ func TestEntityTypeUnmarshalJSON(t *testing.T) { expected: auth.ChannelsType, err: false, }, + { + desc: "Unmarshal rules", + data: []byte(`"rules"`), + expected: auth.RulesType, + err: false, + }, + { + desc: "Unmarshal reports", + data: []byte(`"reports"`), + expected: auth.ReportsType, + err: false, + }, { desc: "Unmarshal unknown", data: []byte(`"unknown"`), diff --git a/docker/permission.yaml b/docker/permission.yaml index 32268c5db..58d7a4375 100644 --- a/docker/permission.yaml +++ b/docker/permission.yaml @@ -137,13 +137,13 @@ alarm: - view: alarm_read_permission - update: alarm_update_permission - delete: alarm_delete_permission - - assign: alarm_assign_permission - - acknowledge: alarm_acknowledge_permission - - resolve: alarm_resolve_permission + - alarm_assign: alarm_assign_permission + - alarm_acknowledge: alarm_acknowledge_permission + - alarm_resolve: alarm_resolve_permission rule: operations: - - add: rule_create_permission + - create: rule_create_permission - list: rule_read_permission - view: read_permission - update: update_permission @@ -174,7 +174,7 @@ rule: report: operations: - - add: report_create_permission + - create: report_create_permission - list: report_read_permission - generate: report_read_permission - view: read_permission diff --git a/re/middleware/authorization.go b/re/middleware/authorization.go index 9638c80c2..d02d38e00 100644 --- a/re/middleware/authorization.go +++ b/re/middleware/authorization.go @@ -6,6 +6,7 @@ package middleware import ( "context" + "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/pkg/authn" smqauthz "github.com/absmach/magistrala/pkg/authz" "github.com/absmach/magistrala/pkg/errors" @@ -156,12 +157,16 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions var pat *smqauthz.PATReq if session.PatID != "" { + entityID := obj + if objType == policies.DomainType { + entityID = auth.AnyIDs + } opName := am.entitiesOps.OperationName(operations.EntityType, op) pat = &smqauthz.PATReq{ UserID: session.UserID, PatID: session.PatID, - EntityID: session.DomainID, - EntityType: operations.EntityType, + EntityID: entityID, + EntityType: auth.RulesType.String(), Operation: opName, Domain: session.DomainID, } diff --git a/re/operations/operations.go b/re/operations/operations.go index 522cf1eea..b41bd5c9e 100644 --- a/re/operations/operations.go +++ b/re/operations/operations.go @@ -23,7 +23,7 @@ const ( func OperationDetails() map[permissions.Operation]permissions.OperationDetails { return map[permissions.Operation]permissions.OperationDetails{ OpAddRule: { - Name: "add", + Name: "create", PermissionRequired: true, }, OpViewRule: { diff --git a/reports/middleware/authorization.go b/reports/middleware/authorization.go index da417e4e4..409b05187 100644 --- a/reports/middleware/authorization.go +++ b/reports/middleware/authorization.go @@ -6,6 +6,7 @@ package middleware import ( "context" + "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/pkg/authn" smqauthz "github.com/absmach/magistrala/pkg/authz" "github.com/absmach/magistrala/pkg/errors" @@ -175,12 +176,16 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions var pat *smqauthz.PATReq if session.PatID != "" { + entityID := obj + if objType == policies.DomainType { + entityID = auth.AnyIDs + } opName := am.entitiesOps.OperationName(operations.EntityType, op) pat = &smqauthz.PATReq{ UserID: session.UserID, PatID: session.PatID, - EntityID: session.DomainID, - EntityType: operations.EntityType, + EntityID: entityID, + EntityType: auth.ReportsType.String(), Operation: opName, Domain: session.DomainID, } diff --git a/reports/operations/operations.go b/reports/operations/operations.go index 24c723016..92f85b47c 100644 --- a/reports/operations/operations.go +++ b/reports/operations/operations.go @@ -26,7 +26,7 @@ const ( func OperationDetails() map[permissions.Operation]permissions.OperationDetails { return map[permissions.Operation]permissions.OperationDetails{ OpAddReportConfig: { - Name: "add", + Name: "create", PermissionRequired: true, }, OpViewReportConfig: {