MG-370 - Add fine grained access control to alarms (#404)

* add access control to rules engine

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* add access control to reports

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* add access control to alarms

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove unused variables

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update authorization method

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* revert code

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove roles

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update alarm permissions

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update alarm permissions

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address comments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix tests

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* revert endpoint changes

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix make fetch

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* revert env variable

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove rule prefix

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove trailing line

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove unused constants

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* re consumer

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update listing

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix tests

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix rule roles interface

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* refactor listing commands

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fetch supermq

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address coments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update script

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address comments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fetch supermq

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix time layout

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix role name

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address comments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove white spaces

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update check usperadmin method

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update go mod file

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix tests

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* add missing env variable

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
This commit is contained in:
Steve Munene
2026-03-13 16:29:32 +03:00
committed by GitHub
parent 6dbcfcae58
commit 2ef8437d8b
43 changed files with 1071 additions and 292 deletions
+1
View File
@@ -6,4 +6,5 @@ package policies
const (
RulesType = "rules"
ReportsType = "reports"
AlarmsType = "alarms"
)
+204
View File
@@ -0,0 +1,204 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package consumer
import (
"encoding/json"
"time"
"github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/re"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/roles"
rconsumer "github.com/absmach/supermq/pkg/roles/rolemanager/events/consumer"
)
var (
errDecodeAddRuleEvent = errors.New("failed to decode rule add event")
errDecodeUpdateRuleEvent = errors.New("failed to decode rule update event")
errDecodeUpdateRuleTagsEvent = errors.New("failed to decode rule update tags event")
errDecodeUpdateRuleScheduleEvent = errors.New("failed to decode rule update schedule event")
errDecodeEnableRuleEvent = errors.New("failed to decode rule enable event")
errDecodeDisableRuleEvent = errors.New("failed to decode rule disable event")
errDecodeRemoveRuleEvent = errors.New("failed to decode rule remove event")
errID = errors.New("missing or invalid 'id'")
errName = errors.New("missing or invalid 'name'")
errTags = errors.New("invalid 'tags'")
errStatus = errors.New("missing or invalid 'status'")
errConvertStatus = errors.New("failed to convert status")
errCreatedBy = errors.New("missing or invalid 'created_by'")
errCreatedAt = errors.New("failed to parse 'created_at' time")
errUpdatedAt = errors.New("failed to parse 'updated_at' time")
errDecodeLogic = errors.New("failed to decode 'logic'")
errDecodeSchedule = errors.New("failed to decode 'schedule'")
)
// ToRule decodes a map[string]any event payload into a re.Rule.
func ToRule(data map[string]any) (re.Rule, error) {
var r re.Rule
id, ok := data["id"].(string)
if !ok {
return re.Rule{}, errID
}
r.ID = id
name, ok := data["name"].(string)
if !ok {
return re.Rule{}, errName
}
r.Name = name
stat, ok := data["status"].(string)
if !ok {
return re.Rule{}, errStatus
}
st, err := re.ToStatus(stat)
if err != nil {
return re.Rule{}, errors.Wrap(errConvertStatus, err)
}
r.Status = st
cby, ok := data["created_by"].(string)
if !ok {
return re.Rule{}, errCreatedBy
}
r.CreatedBy = cby
cat, ok := data["created_at"].(string)
if !ok {
return re.Rule{}, errCreatedAt
}
ct, err := time.Parse(re.TimeLayout, cat)
if err != nil {
return re.Rule{}, errors.Wrap(errCreatedAt, err)
}
r.CreatedAt = ct
if domain, ok := data["domain"].(string); ok {
r.DomainID = domain
}
if itags, ok := data["tags"].([]any); ok {
tags, err := rconsumer.ToStrings(itags)
if err != nil {
return re.Rule{}, errors.Wrap(errTags, err)
}
r.Tags = tags
}
if meta, ok := data["metadata"].(map[string]any); ok {
r.Metadata = meta
}
if uby, ok := data["updated_by"].(string); ok {
r.UpdatedBy = uby
}
if uat, ok := data["updated_at"].(string); ok {
ut, err := time.Parse(re.TimeLayout, uat)
if err != nil {
return re.Rule{}, errors.Wrap(errUpdatedAt, err)
}
r.UpdatedAt = ut
}
if ic, ok := data["input_channel"].(string); ok {
r.InputChannel = ic
}
if it, ok := data["input_topic"].(string); ok {
r.InputTopic = it
}
if rawLogic, ok := data["logic"].(map[string]any); ok {
b, err := json.Marshal(rawLogic)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeLogic, err)
}
if err := json.Unmarshal(b, &r.Logic); err != nil {
return re.Rule{}, errors.Wrap(errDecodeLogic, err)
}
}
if rawSched, ok := data["schedule"].(map[string]any); ok {
b, err := json.Marshal(rawSched)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeSchedule, err)
}
var sched schedule.Schedule
if err := json.Unmarshal(b, &sched); err != nil {
return re.Rule{}, errors.Wrap(errDecodeSchedule, err)
}
r.Schedule = sched
}
return r, nil
}
func decodeAddRuleEvent(data map[string]any) (re.Rule, []roles.RoleProvision, error) {
r, err := ToRule(data)
if err != nil {
return re.Rule{}, nil, errors.Wrap(errDecodeAddRuleEvent, err)
}
var rps []roles.RoleProvision
if irps, ok := data["roles_provisioned"].([]any); ok {
rps, err = rconsumer.ToRoleProvisions(irps)
if err != nil {
return re.Rule{}, nil, errors.Wrap(errDecodeAddRuleEvent, err)
}
}
return r, rps, nil
}
func decodeUpdateRuleEvent(data map[string]any) (re.Rule, error) {
r, err := ToRule(data)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeUpdateRuleEvent, err)
}
return r, nil
}
func decodeUpdateRuleTagsEvent(data map[string]any) (re.Rule, error) {
r, err := ToRule(data)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeUpdateRuleTagsEvent, err)
}
return r, nil
}
func decodeUpdateRuleScheduleEvent(data map[string]any) (re.Rule, error) {
r, err := ToRule(data)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeUpdateRuleScheduleEvent, err)
}
return r, nil
}
func decodeEnableRuleEvent(data map[string]any) (re.Rule, error) {
r, err := ToRule(data)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeEnableRuleEvent, err)
}
return r, nil
}
func decodeDisableRuleEvent(data map[string]any) (re.Rule, error) {
r, err := ToRule(data)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeDisableRuleEvent, err)
}
return r, nil
}
func decodeRemoveRuleEvent(data map[string]any) (string, error) {
id, ok := data["id"].(string)
if !ok {
return "", errors.Wrap(errDecodeRemoveRuleEvent, errID)
}
return id, nil
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package consumer contains events consumer for events
// published by the Rules Engine service.
package consumer
+193
View File
@@ -0,0 +1,193 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package consumer
import (
"context"
"log/slog"
"github.com/absmach/magistrala/re"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/events"
"github.com/absmach/supermq/pkg/events/store"
rconsumer "github.com/absmach/supermq/pkg/roles/rolemanager/events/consumer"
)
const (
stream = "events.supermq.rule.*"
create = "rule.create"
update = "rule.update"
updateTags = "rule.update_tags"
updateSchedule = "rule.update_schedule"
enable = "rule.enable"
disable = "rule.disable"
remove = "rule.remove"
)
var (
errNoOperationKey = errors.New("operation key is not found in event message")
errAddRuleEvent = errors.New("failed to consume rule create event")
errUpdateRuleEvent = errors.New("failed to consume rule update event")
errUpdateRuleTagsEvent = errors.New("failed to consume rule update tags event")
errUpdateRuleScheduleEvent = errors.New("failed to consume rule update schedule event")
errEnableRuleEvent = errors.New("failed to consume rule enable event")
errDisableRuleEvent = errors.New("failed to consume rule disable event")
errRemoveRuleEvent = errors.New("failed to consume rule remove event")
)
type eventHandler struct {
repo re.Repository
rolesEventHandler rconsumer.EventHandler
}
func RulesEventsSubscribe(ctx context.Context, repo re.Repository, esURL, esConsumerName string, logger *slog.Logger) error {
subscriber, err := store.NewSubscriber(ctx, esURL, logger)
if err != nil {
return err
}
subConfig := events.SubscriberConfig{
Stream: stream,
Consumer: esConsumerName,
Handler: NewEventHandler(repo),
Ordered: true,
}
return subscriber.Subscribe(ctx, subConfig)
}
// NewEventHandler returns new event store handler.
func NewEventHandler(repo re.Repository) events.EventHandler {
reh := rconsumer.NewEventHandler("rule", repo)
return &eventHandler{
repo: repo,
rolesEventHandler: reh,
}
}
func (es *eventHandler) Handle(ctx context.Context, event events.Event) error {
msg, err := event.Encode()
if err != nil {
return err
}
op, ok := msg["operation"]
if !ok {
return errNoOperationKey
}
switch op {
case create:
return es.addRuleHandler(ctx, msg)
case update:
return es.updateRuleHandler(ctx, msg)
case updateTags:
return es.updateRuleTagsHandler(ctx, msg)
case updateSchedule:
return es.updateRuleScheduleHandler(ctx, msg)
case enable:
return es.enableRuleHandler(ctx, msg)
case disable:
return es.disableRuleHandler(ctx, msg)
case remove:
return es.removeRuleHandler(ctx, msg)
}
return es.rolesEventHandler.Handle(ctx, op, msg)
}
func (es *eventHandler) addRuleHandler(ctx context.Context, data map[string]any) error {
r, rps, err := decodeAddRuleEvent(data)
if err != nil {
return errors.Wrap(errAddRuleEvent, err)
}
if _, err := es.repo.AddRule(ctx, r); err != nil {
return errors.Wrap(errAddRuleEvent, err)
}
if _, err := es.repo.AddRoles(ctx, rps); err != nil {
return errors.Wrap(errAddRuleEvent, err)
}
return nil
}
func (es *eventHandler) updateRuleHandler(ctx context.Context, data map[string]any) error {
r, err := decodeUpdateRuleEvent(data)
if err != nil {
return errors.Wrap(errUpdateRuleEvent, err)
}
if _, err := es.repo.UpdateRule(ctx, r); err != nil {
return errors.Wrap(errUpdateRuleEvent, err)
}
return nil
}
func (es *eventHandler) updateRuleTagsHandler(ctx context.Context, data map[string]any) error {
r, err := decodeUpdateRuleTagsEvent(data)
if err != nil {
return errors.Wrap(errUpdateRuleTagsEvent, err)
}
if _, err := es.repo.UpdateRuleTags(ctx, r); err != nil {
return errors.Wrap(errUpdateRuleTagsEvent, err)
}
return nil
}
func (es *eventHandler) updateRuleScheduleHandler(ctx context.Context, data map[string]any) error {
r, err := decodeUpdateRuleScheduleEvent(data)
if err != nil {
return errors.Wrap(errUpdateRuleScheduleEvent, err)
}
if _, err := es.repo.UpdateRuleSchedule(ctx, r); err != nil {
return errors.Wrap(errUpdateRuleScheduleEvent, err)
}
return nil
}
func (es *eventHandler) enableRuleHandler(ctx context.Context, data map[string]any) error {
r, err := decodeEnableRuleEvent(data)
if err != nil {
return errors.Wrap(errEnableRuleEvent, err)
}
if _, err := es.repo.UpdateRuleStatus(ctx, r); err != nil {
return errors.Wrap(errEnableRuleEvent, err)
}
return nil
}
func (es *eventHandler) disableRuleHandler(ctx context.Context, data map[string]any) error {
r, err := decodeDisableRuleEvent(data)
if err != nil {
return errors.Wrap(errDisableRuleEvent, err)
}
if _, err := es.repo.UpdateRuleStatus(ctx, r); err != nil {
return errors.Wrap(errDisableRuleEvent, err)
}
return nil
}
func (es *eventHandler) removeRuleHandler(ctx context.Context, data map[string]any) error {
id, err := decodeRemoveRuleEvent(data)
if err != nil {
return errors.Wrap(errRemoveRuleEvent, err)
}
if err := es.repo.RemoveRule(ctx, id); err != nil {
return errors.Wrap(errRemoveRuleEvent, err)
}
return nil
}