mirror of
https://github.com/absmach/magistrala.git
synced 2026-08-07 07:14:46 +00:00
NOISSUE - Fix Alarms roles (#3544)
Signed-off-by: Arvindh <arvindh91@gmail.com>
This commit is contained in:
@@ -57,6 +57,7 @@ type PageMetadata struct {
|
||||
Limit uint64 `json:"limit" db:"limit"`
|
||||
DomainID string `json:"domain_id" db:"domain_id"`
|
||||
RuleID string `json:"rule_id" db:"rule_id"`
|
||||
RuleIDs []string `json:"rule_ids" db:"rule_ids"`
|
||||
ChannelID string `json:"channel_id" db:"channel_id"`
|
||||
ClientID string `json:"client_id" db:"client_id"`
|
||||
Subtopic string `json:"subtopic" db:"subtopic"`
|
||||
|
||||
@@ -33,6 +33,16 @@ type authorizationMiddleware struct {
|
||||
|
||||
var _ alarms.Service = (*authorizationMiddleware)(nil)
|
||||
|
||||
const (
|
||||
atomObjectKindResource = "resource"
|
||||
atomObjectTypeResourceRule = "resource:" + atom.KindRule
|
||||
atomAuthorizedRulePageLimit = 100
|
||||
)
|
||||
|
||||
type atomAuthorizedObjectLister interface {
|
||||
AuthorizedObjectIDs(ctx context.Context, q atom.AuthorizedObjectIDsQuery) (atom.AuthorizedObjectIDs, error)
|
||||
}
|
||||
|
||||
func NewAuthorizationMiddleware(svc alarms.Service, authz smqauthz.Authorization, entitiesOps permissions.EntitiesOperations[permissions.Operation]) (alarms.Service, error) {
|
||||
if err := entitiesOps.Validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -62,14 +72,19 @@ func (am *authorizationMiddleware) CreateAlarm(ctx context.Context, alarm alarms
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) UpdateAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
current, err := am.svc.ViewAlarm(ctx, session, alarm.ID)
|
||||
if err != nil {
|
||||
return alarms.Alarm{}, err
|
||||
}
|
||||
|
||||
if len(alarm.Metadata) > 0 {
|
||||
if err := am.authorize(ctx, operations.OpUpdateAlarm, session, policies.DomainType, session.DomainID); err != nil {
|
||||
if err := am.authorizeAlarmOrRule(ctx, operations.OpUpdateAlarm, session, current); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
|
||||
}
|
||||
}
|
||||
|
||||
if alarm.AssigneeID != "" {
|
||||
if err := am.authorize(ctx, operations.OpAssignAlarm, session, policies.DomainType, session.DomainID); err != nil {
|
||||
if err := am.authorizeAlarmOrRule(ctx, operations.OpAssignAlarm, session, current); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
|
||||
}
|
||||
if am.atomAuthz == nil {
|
||||
@@ -89,13 +104,13 @@ func (am *authorizationMiddleware) UpdateAlarm(ctx context.Context, session auth
|
||||
}
|
||||
|
||||
if alarm.AcknowledgedBy != "" {
|
||||
if err := am.authorize(ctx, operations.OpAcknowledgeAlarm, session, policies.DomainType, session.DomainID); err != nil {
|
||||
if err := am.authorizeAlarmOrRule(ctx, operations.OpAcknowledgeAlarm, session, current); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
|
||||
}
|
||||
}
|
||||
|
||||
if alarm.ResolvedBy != "" {
|
||||
if err := am.authorize(ctx, operations.OpResolveAlarm, session, policies.DomainType, session.DomainID); err != nil {
|
||||
if err := am.authorizeAlarmOrRule(ctx, operations.OpResolveAlarm, session, current); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
|
||||
}
|
||||
}
|
||||
@@ -104,7 +119,11 @@ func (am *authorizationMiddleware) UpdateAlarm(ctx context.Context, session auth
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) DeleteAlarm(ctx context.Context, session authn.Session, id string) error {
|
||||
if err := am.authorize(ctx, operations.OpDeleteAlarm, session, policies.DomainType, session.DomainID); err != nil {
|
||||
alarm, err := am.svc.ViewAlarm(ctx, session, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := am.authorizeAlarmOrRule(ctx, operations.OpDeleteAlarm, session, alarm); err != nil {
|
||||
return errors.Wrap(errDomainDeleteAlarms, err)
|
||||
}
|
||||
|
||||
@@ -120,8 +139,25 @@ func (am *authorizationMiddleware) ListAlarms(ctx context.Context, session authn
|
||||
case err == nil:
|
||||
session.SuperAdmin = true
|
||||
case errors.Contains(err, svcerr.ErrSuperAdminAction):
|
||||
if err := am.authorize(ctx, operations.OpListAlarms, session, policies.DomainType, session.DomainID); err != nil {
|
||||
return alarms.AlarmsPage{}, errors.Wrap(errDomainViewAlarms, err)
|
||||
if err := am.authorizeTenantAlarm(ctx, operations.OpViewAlarm, session); err != nil {
|
||||
if pm.RuleID != "" {
|
||||
if ruleErr := am.authorizeRuleRead(ctx, session, pm.RuleID); ruleErr != nil {
|
||||
return alarms.AlarmsPage{}, errors.Wrap(errDomainViewAlarms, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
ruleIDs, ruleErr := am.authorizedReadableRuleIDs(ctx, session)
|
||||
if ruleErr != nil {
|
||||
return alarms.AlarmsPage{}, errors.Wrap(errDomainViewAlarms, err)
|
||||
}
|
||||
if len(ruleIDs) == 0 {
|
||||
return alarms.AlarmsPage{
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
Alarms: []alarms.Alarm{},
|
||||
}, nil
|
||||
}
|
||||
pm.RuleIDs = ruleIDs
|
||||
}
|
||||
default:
|
||||
return alarms.AlarmsPage{}, err
|
||||
@@ -131,20 +167,101 @@ func (am *authorizationMiddleware) ListAlarms(ctx context.Context, session authn
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) ViewAlarm(ctx context.Context, session authn.Session, id string) (alarms.Alarm, error) {
|
||||
if err := am.authorize(ctx, operations.OpViewAlarm, session, policies.DomainType, session.DomainID); err != nil {
|
||||
alarm, err := am.svc.ViewAlarm(ctx, session, id)
|
||||
if err != nil {
|
||||
return alarms.Alarm{}, err
|
||||
}
|
||||
if err := am.authorizeViewAlarm(ctx, session, alarm); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainViewAlarms, err)
|
||||
}
|
||||
|
||||
return am.svc.ViewAlarm(ctx, session, id)
|
||||
return alarm, nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session, objType, obj string) error {
|
||||
func (am *authorizationMiddleware) authorizeAlarmOrRule(ctx context.Context, op permissions.Operation, session authn.Session, alarm alarms.Alarm) error {
|
||||
tenantErr := am.authorizeTenantAlarm(ctx, op, session)
|
||||
if tenantErr == nil {
|
||||
return nil
|
||||
}
|
||||
if alarm.RuleID == "" {
|
||||
return tenantErr
|
||||
}
|
||||
if err := am.authorize(ctx, op, session, policies.RulesType, alarm.RuleID, atom.KindRule); err != nil {
|
||||
return tenantErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorizeViewAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) error {
|
||||
tenantErr := am.authorizeTenantAlarm(ctx, operations.OpViewAlarm, session)
|
||||
if tenantErr == nil {
|
||||
return nil
|
||||
}
|
||||
if alarm.RuleID == "" {
|
||||
return tenantErr
|
||||
}
|
||||
if err := am.authorizeRuleRead(ctx, session, alarm.RuleID); err != nil {
|
||||
return tenantErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorizeTenantAlarm(ctx context.Context, op permissions.Operation, session authn.Session) error {
|
||||
return am.authorize(ctx, op, session, policies.DomainType, session.DomainID, atom.KindAlarm)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorizeRuleRead(ctx context.Context, session authn.Session, ruleID string) error {
|
||||
if am.atomAuthz != nil {
|
||||
return atom.Authorize(ctx, am.atomAuthz, session, policies.ViewPermission, policies.RulesType, ruleID, atom.KindRule)
|
||||
}
|
||||
pr := smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: ruleID,
|
||||
ObjectType: policies.RulesType,
|
||||
Permission: policies.ViewPermission,
|
||||
}
|
||||
return am.authz.Authorize(ctx, pr, nil)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorizedReadableRuleIDs(ctx context.Context, session authn.Session) ([]string, error) {
|
||||
lister, ok := am.atomAuthz.(atomAuthorizedObjectLister)
|
||||
if !ok {
|
||||
return nil, errors.ErrAuthorization
|
||||
}
|
||||
|
||||
var ids []string
|
||||
for offset := uint64(0); ; offset += atomAuthorizedRulePageLimit {
|
||||
page, err := lister.AuthorizedObjectIDs(ctx, atom.AuthorizedObjectIDsQuery{
|
||||
SubjectID: atom.SubjectID(session),
|
||||
Action: atom.CapabilityName(policies.ViewPermission),
|
||||
ObjectKind: atomObjectKindResource,
|
||||
ObjectType: atomObjectTypeResourceRule,
|
||||
TenantID: session.DomainID,
|
||||
Limit: atomAuthorizedRulePageLimit,
|
||||
Offset: offset,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ids = append(ids, page.IDs...)
|
||||
if uint64(len(page.IDs)) < atomAuthorizedRulePageLimit || offset+uint64(len(page.IDs)) >= page.Total {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session, objType, obj, resourceKind string) error {
|
||||
perm, err := am.entitiesOps.GetPermission(operations.EntityType, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if am.atomAuthz != nil {
|
||||
return atom.Authorize(ctx, am.atomAuthz, session, perm.String(), objType, obj, atom.KindAlarm)
|
||||
return atom.Authorize(ctx, am.atomAuthz, session, perm.String(), objType, obj, resourceKind)
|
||||
}
|
||||
|
||||
pr := smqauthz.PolicyReq{
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/absmach/magistrala/alarms/operations"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
pkgerrors "github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/magistrala/pkg/permissions"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -20,16 +19,27 @@ import (
|
||||
)
|
||||
|
||||
type recordingAtomAuthorizer struct {
|
||||
allowed bool
|
||||
reqs []atom.AuthzRequest
|
||||
allowed bool
|
||||
allow func(atom.AuthzRequest) bool
|
||||
authorized atom.AuthorizedObjectIDs
|
||||
reqs []atom.AuthzRequest
|
||||
queries []atom.AuthorizedObjectIDsQuery
|
||||
}
|
||||
|
||||
func (a *recordingAtomAuthorizer) CheckAuthz(_ context.Context, req atom.AuthzRequest) (atom.AuthzResponse, error) {
|
||||
a.reqs = append(a.reqs, req)
|
||||
if a.allow != nil {
|
||||
return atom.AuthzResponse{Allowed: a.allow(req)}, nil
|
||||
}
|
||||
return atom.AuthzResponse{Allowed: a.allowed}, nil
|
||||
}
|
||||
|
||||
func TestListAlarmsAuthorizesRegularUser(t *testing.T) {
|
||||
func (a *recordingAtomAuthorizer) AuthorizedObjectIDs(_ context.Context, q atom.AuthorizedObjectIDsQuery) (atom.AuthorizedObjectIDs, error) {
|
||||
a.queries = append(a.queries, q)
|
||||
return a.authorized, nil
|
||||
}
|
||||
|
||||
func TestListAlarmsAuthorizesTenantAlarmReader(t *testing.T) {
|
||||
svc := mocks.NewService(t)
|
||||
pm := alarms.PageMetadata{Limit: 10}
|
||||
expectedPM := pm
|
||||
@@ -47,7 +57,7 @@ func TestListAlarmsAuthorizesRegularUser(t *testing.T) {
|
||||
require.Len(t, authz.reqs, 1)
|
||||
assert.Equal(t, atom.AuthzRequest{
|
||||
SubjectID: "user-1",
|
||||
Action: "list",
|
||||
Action: "alarm_read",
|
||||
ResourceID: "",
|
||||
ObjectKind: "tenant",
|
||||
ObjectID: "domain-1",
|
||||
@@ -58,16 +68,56 @@ func TestListAlarmsAuthorizesRegularUser(t *testing.T) {
|
||||
}, authz.reqs[0])
|
||||
}
|
||||
|
||||
func TestListAlarmsDeniedRegularUserDoesNotDelegate(t *testing.T) {
|
||||
func TestListAlarmsFiltersToReadableRulesWhenTenantAlarmReadDenied(t *testing.T) {
|
||||
svc := mocks.NewService(t)
|
||||
authz := &recordingAtomAuthorizer{allowed: false}
|
||||
pm := alarms.PageMetadata{Limit: 10}
|
||||
expectedPM := pm
|
||||
expectedPM.DomainID = "domain-1"
|
||||
expectedPM.RuleIDs = []string{"rule-1", "rule-2"}
|
||||
authz := &recordingAtomAuthorizer{
|
||||
allowed: false,
|
||||
authorized: atom.AuthorizedObjectIDs{IDs: []string{"rule-1", "rule-2"}, Total: 2},
|
||||
}
|
||||
wrapped, err := NewAtomAuthorizationMiddleware(svc, authz, testEntitiesOps(t))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = wrapped.ListAlarms(context.Background(), authn.Session{UserID: "user-1", DomainID: "domain-1"}, alarms.PageMetadata{})
|
||||
svc.On("ListAlarms", mock.Anything, authn.Session{UserID: "user-1", DomainID: "domain-1"}, expectedPM).Return(alarms.AlarmsPage{Limit: 10}, nil).Once()
|
||||
_, err = wrapped.ListAlarms(context.Background(), authn.Session{UserID: "user-1", DomainID: "domain-1"}, pm)
|
||||
|
||||
assert.True(t, pkgerrors.Contains(err, pkgerrors.ErrAuthorization))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, authz.reqs, 1)
|
||||
require.Len(t, authz.queries, 1)
|
||||
assert.Equal(t, atom.AuthorizedObjectIDsQuery{
|
||||
SubjectID: "user-1",
|
||||
Action: "read",
|
||||
ObjectKind: "resource",
|
||||
ObjectType: "resource:rule",
|
||||
TenantID: "domain-1",
|
||||
Limit: 100,
|
||||
}, authz.queries[0])
|
||||
}
|
||||
|
||||
func TestListAlarmsWithRuleFilterAuthorizesRuleRead(t *testing.T) {
|
||||
svc := mocks.NewService(t)
|
||||
pm := alarms.PageMetadata{Limit: 10, RuleID: "rule-1"}
|
||||
expectedPM := pm
|
||||
expectedPM.DomainID = "domain-1"
|
||||
session := authn.Session{UserID: "user-1", DomainID: "domain-1"}
|
||||
authz := &recordingAtomAuthorizer{
|
||||
allow: func(req atom.AuthzRequest) bool {
|
||||
return req.Action == "read" && req.ObjectKind == "resource" && req.ObjectID == "rule-1"
|
||||
},
|
||||
}
|
||||
wrapped, err := NewAtomAuthorizationMiddleware(svc, authz, testEntitiesOps(t))
|
||||
require.NoError(t, err)
|
||||
|
||||
svc.On("ListAlarms", mock.Anything, session, expectedPM).Return(alarms.AlarmsPage{Limit: 10}, nil).Once()
|
||||
_, err = wrapped.ListAlarms(context.Background(), session, pm)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, authz.reqs, 2)
|
||||
assert.Equal(t, "alarm_read", authz.reqs[0].Action)
|
||||
assert.Equal(t, "read", authz.reqs[1].Action)
|
||||
}
|
||||
|
||||
func TestListAlarmsSuperAdminSkipsListAuthorization(t *testing.T) {
|
||||
@@ -90,13 +140,56 @@ func TestListAlarmsSuperAdminSkipsListAuthorization(t *testing.T) {
|
||||
assert.Equal(t, "manage", authz.reqs[0].Action)
|
||||
}
|
||||
|
||||
func TestAcknowledgeAlarmAuthorizesRuleAlarmActionWhenTenantDenied(t *testing.T) {
|
||||
svc := mocks.NewService(t)
|
||||
session := authn.Session{UserID: "user-1", DomainID: "domain-1"}
|
||||
current := alarms.Alarm{ID: "alarm-1", RuleID: "rule-1", DomainID: "domain-1"}
|
||||
update := alarms.Alarm{ID: "alarm-1", AcknowledgedBy: "user-1"}
|
||||
authz := &recordingAtomAuthorizer{
|
||||
allow: func(req atom.AuthzRequest) bool {
|
||||
return req.Action == "alarm_acknowledge" && req.ObjectKind == "resource" && req.ObjectID == "rule-1"
|
||||
},
|
||||
}
|
||||
wrapped, err := NewAtomAuthorizationMiddleware(svc, authz, testEntitiesOps(t))
|
||||
require.NoError(t, err)
|
||||
|
||||
svc.On("ViewAlarm", mock.Anything, session, "alarm-1").Return(current, nil).Once()
|
||||
svc.On("UpdateAlarm", mock.Anything, session, update).Return(update, nil).Once()
|
||||
_, err = wrapped.UpdateAlarm(context.Background(), session, update)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, authz.reqs, 2)
|
||||
assert.Equal(t, atom.AuthzRequest{
|
||||
SubjectID: "user-1",
|
||||
Action: "alarm_acknowledge",
|
||||
ResourceID: "",
|
||||
ObjectKind: "tenant",
|
||||
ObjectID: "domain-1",
|
||||
Context: map[string]any{
|
||||
"domain_id": "domain-1",
|
||||
"legacy_object_type": "domain",
|
||||
},
|
||||
}, authz.reqs[0])
|
||||
assert.Equal(t, atom.AuthzRequest{
|
||||
SubjectID: "user-1",
|
||||
Action: "alarm_acknowledge",
|
||||
ResourceID: "rule-1",
|
||||
ObjectKind: "resource",
|
||||
ObjectID: "rule-1",
|
||||
Context: map[string]any{
|
||||
"domain_id": "domain-1",
|
||||
"legacy_object_type": "rules",
|
||||
},
|
||||
}, authz.reqs[1])
|
||||
}
|
||||
|
||||
func testEntitiesOps(t *testing.T) permissions.EntitiesOperations[permissions.Operation] {
|
||||
t.Helper()
|
||||
details := operations.OperationDetails()
|
||||
perms := make(map[string]permissions.Permission, len(details))
|
||||
for _, detail := range details {
|
||||
for op, detail := range details {
|
||||
if detail.PermissionRequired {
|
||||
perms[detail.Name] = permissions.Permission(detail.Name)
|
||||
perms[detail.Name] = testPermission(op, detail.Name)
|
||||
}
|
||||
}
|
||||
entitiesOps, err := permissions.NewEntitiesOperations(
|
||||
@@ -106,3 +199,22 @@ func testEntitiesOps(t *testing.T) permissions.EntitiesOperations[permissions.Op
|
||||
require.NoError(t, err)
|
||||
return entitiesOps
|
||||
}
|
||||
|
||||
func testPermission(op permissions.Operation, fallback string) permissions.Permission {
|
||||
switch op {
|
||||
case operations.OpViewAlarm, operations.OpListAlarms:
|
||||
return "alarm_read_permission"
|
||||
case operations.OpUpdateAlarm:
|
||||
return "alarm_update_permission"
|
||||
case operations.OpDeleteAlarm:
|
||||
return "alarm_delete_permission"
|
||||
case operations.OpAssignAlarm:
|
||||
return "alarm_assign_permission"
|
||||
case operations.OpAcknowledgeAlarm:
|
||||
return "alarm_acknowledge_permission"
|
||||
case operations.OpResolveAlarm:
|
||||
return "alarm_resolve_permission"
|
||||
default:
|
||||
return permissions.Permission(fallback)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,6 +464,9 @@ func pageQueryConditions(pm alarms.PageMetadata) []string {
|
||||
if pm.RuleID != "" {
|
||||
query = append(query, "alarms.rule_id = :rule_id")
|
||||
}
|
||||
if len(pm.RuleIDs) > 0 {
|
||||
query = append(query, "alarms.rule_id = ANY(:rule_ids)")
|
||||
}
|
||||
if pm.ChannelID != "" {
|
||||
query = append(query, "alarms.channel_id = :channel_id")
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ const (
|
||||
hookAuthOnPublish = "auth_on_publish"
|
||||
hookAuthOnSubscribe = "auth_on_subscribe"
|
||||
hookAuthOnUnsubscribe = "auth_on_unsubscribe"
|
||||
|
||||
hookProtocolAMQP091 = "amqp091"
|
||||
)
|
||||
|
||||
type hookRequest struct {
|
||||
@@ -83,6 +85,9 @@ func handleHook(ctx context.Context, parser messaging.TopicParser, req hookReque
|
||||
}
|
||||
|
||||
func resolveHookTopic(ctx context.Context, parser messaging.TopicParser, req hookRequest) (string, error) {
|
||||
if isInternalMessageConsumer(req) {
|
||||
return "", nil
|
||||
}
|
||||
if !isMessageTopic(req.Topic) {
|
||||
return "", nil
|
||||
}
|
||||
@@ -117,3 +122,15 @@ func isMessageTopic(topic string) bool {
|
||||
topic = strings.TrimPrefix(topic, "/")
|
||||
return strings.HasPrefix(topic, string(messaging.MsgTopicPrefix)+"/")
|
||||
}
|
||||
|
||||
func isInternalMessageConsumer(req hookRequest) bool {
|
||||
hook := strings.ToLower(strings.TrimSpace(req.Hook))
|
||||
if hook != hookAuthOnSubscribe && hook != hookAuthOnUnsubscribe {
|
||||
return false
|
||||
}
|
||||
if strings.ToLower(strings.TrimSpace(req.Protocol)) != hookProtocolAMQP091 {
|
||||
return false
|
||||
}
|
||||
topic := strings.TrimPrefix(strings.TrimSpace(req.Topic), "/")
|
||||
return topic == string(messaging.MsgTopicPrefix)+"/#"
|
||||
}
|
||||
|
||||
@@ -97,6 +97,27 @@ func TestHooksHandlerReturnsOKForNonMGTopic(t *testing.T) {
|
||||
require.Empty(t, res.Topic)
|
||||
}
|
||||
|
||||
func TestHooksHandlerAllowsInternalAMQPMessageConsumer(t *testing.T) {
|
||||
parser := &fakeHookParser{err: errors.New("should not parse internal consumer")}
|
||||
req := httptest.NewRequest("POST", "/hooks", strings.NewReader(`{
|
||||
"hook":"auth_on_subscribe",
|
||||
"protocol":"amqp091",
|
||||
"topic":"m/#"
|
||||
}`))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
MakeHooksHandler(parser).ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, 200, w.Code)
|
||||
require.False(t, parser.publishCalled)
|
||||
require.False(t, parser.subscribeCalled)
|
||||
|
||||
var res hookResponse
|
||||
require.NoError(t, json.NewDecoder(w.Body).Decode(&res))
|
||||
require.Equal(t, hookResultOK, res.Result)
|
||||
require.Empty(t, res.Topic)
|
||||
}
|
||||
|
||||
func TestHooksHandlerDeniesUnresolvedMGTopic(t *testing.T) {
|
||||
parser := &fakeHookParser{err: errors.New("failed to resolve channel route")}
|
||||
req := httptest.NewRequest("POST", "/hooks", strings.NewReader(`{"hook":"auth_on_publish","topic":"m/d1/c/ch1/messages"}`))
|
||||
|
||||
@@ -58,6 +58,18 @@ func CapabilityName(action string) string {
|
||||
normalized == "admin_permission",
|
||||
strings.Contains(normalized, "manage_role"):
|
||||
return atomActionManage
|
||||
case strings.Contains(normalized, atomActionAlarmRead):
|
||||
return atomActionAlarmRead
|
||||
case strings.Contains(normalized, atomActionAlarmUpdate):
|
||||
return atomActionAlarmUpdate
|
||||
case strings.Contains(normalized, atomActionAlarmDelete):
|
||||
return atomActionAlarmDelete
|
||||
case strings.Contains(normalized, atomActionAlarmAssign):
|
||||
return atomActionAlarmAssign
|
||||
case strings.Contains(normalized, atomActionAlarmAcknowledge):
|
||||
return atomActionAlarmAcknowledge
|
||||
case strings.Contains(normalized, atomActionAlarmResolve):
|
||||
return atomActionAlarmResolve
|
||||
case normalized == policies.ViewPermission,
|
||||
normalized == atomActionRead,
|
||||
strings.Contains(normalized, "read"),
|
||||
|
||||
@@ -17,10 +17,23 @@ var magistralaActionDescriptions = map[string]string{
|
||||
atomActionSubscribe: "Subscribe to channel messages",
|
||||
atomActionExecute: "Execute a command or action",
|
||||
atomActionList: "List objects",
|
||||
|
||||
atomActionAlarmRead: "Read alarms in a tenant or rule scope",
|
||||
atomActionAlarmUpdate: "Update alarms in a tenant or rule scope",
|
||||
atomActionAlarmDelete: "Delete alarms in a tenant or rule scope",
|
||||
atomActionAlarmAssign: "Assign alarms in a tenant or rule scope",
|
||||
atomActionAlarmAcknowledge: "Acknowledge alarms in a tenant or rule scope",
|
||||
atomActionAlarmResolve: "Resolve alarms in a tenant or rule scope",
|
||||
}
|
||||
|
||||
var magistralaActionApplicability = []CapabilityApplicabilitySpec{
|
||||
{ActionName: atomActionWrite, ObjectKind: atomObjectKindTenant},
|
||||
{ActionName: atomActionAlarmRead, ObjectKind: atomObjectKindTenant},
|
||||
{ActionName: atomActionAlarmUpdate, ObjectKind: atomObjectKindTenant},
|
||||
{ActionName: atomActionAlarmDelete, ObjectKind: atomObjectKindTenant},
|
||||
{ActionName: atomActionAlarmAssign, ObjectKind: atomObjectKindTenant},
|
||||
{ActionName: atomActionAlarmAcknowledge, ObjectKind: atomObjectKindTenant},
|
||||
{ActionName: atomActionAlarmResolve, ObjectKind: atomObjectKindTenant},
|
||||
|
||||
{ActionName: atomActionRead, ObjectKind: atomObjectKindGroup},
|
||||
{ActionName: atomActionWrite, ObjectKind: atomObjectKindGroup},
|
||||
@@ -41,6 +54,11 @@ var magistralaActionApplicability = []CapabilityApplicabilitySpec{
|
||||
{ActionName: atomActionManage, ObjectKind: atomObjectKindResource, ObjectType: atomObjectTypeResourceRule},
|
||||
{ActionName: atomActionExecute, ObjectKind: atomObjectKindResource, ObjectType: atomObjectTypeResourceRule},
|
||||
{ActionName: atomActionList, ObjectKind: atomObjectKindResource, ObjectType: atomObjectTypeResourceRule},
|
||||
{ActionName: atomActionAlarmUpdate, ObjectKind: atomObjectKindResource, ObjectType: atomObjectTypeResourceRule},
|
||||
{ActionName: atomActionAlarmDelete, ObjectKind: atomObjectKindResource, ObjectType: atomObjectTypeResourceRule},
|
||||
{ActionName: atomActionAlarmAssign, ObjectKind: atomObjectKindResource, ObjectType: atomObjectTypeResourceRule},
|
||||
{ActionName: atomActionAlarmAcknowledge, ObjectKind: atomObjectKindResource, ObjectType: atomObjectTypeResourceRule},
|
||||
{ActionName: atomActionAlarmResolve, ObjectKind: atomObjectKindResource, ObjectType: atomObjectTypeResourceRule},
|
||||
|
||||
{ActionName: atomActionRead, ObjectKind: atomObjectKindResource, ObjectType: atomObjectTypeResourceReport},
|
||||
{ActionName: atomActionWrite, ObjectKind: atomObjectKindResource, ObjectType: atomObjectTypeResourceReport},
|
||||
|
||||
@@ -103,7 +103,7 @@ func TestBootstrapMagistralaActionsCreatesMissingActionsAndApplicability(t *test
|
||||
t.Fatalf("bootstrap failed: %v", err)
|
||||
}
|
||||
|
||||
for _, name := range []string{atomActionRead, atomActionWrite, atomActionDelete, atomActionManage, atomActionPublish, atomActionSubscribe, atomActionExecute, atomActionList} {
|
||||
for _, name := range []string{atomActionRead, atomActionWrite, atomActionDelete, atomActionManage, atomActionPublish, atomActionSubscribe, atomActionExecute, atomActionList, atomActionAlarmRead, atomActionAlarmUpdate, atomActionAlarmDelete, atomActionAlarmAssign, atomActionAlarmAcknowledge, atomActionAlarmResolve} {
|
||||
if _, ok := actions[name]; !ok {
|
||||
t.Fatalf("action %q was not ensured", name)
|
||||
}
|
||||
@@ -112,6 +112,12 @@ func TestBootstrapMagistralaActionsCreatesMissingActionsAndApplicability(t *test
|
||||
t.Fatalf("unexpected applicability count: got %d want %d", len(applicability), len(magistralaActionApplicability))
|
||||
}
|
||||
assertApplicability(t, applicability, "write-id", atomObjectKindTenant, "")
|
||||
assertApplicability(t, applicability, "alarm_read-id", atomObjectKindTenant, "")
|
||||
assertApplicability(t, applicability, "alarm_update-id", atomObjectKindTenant, "")
|
||||
assertApplicability(t, applicability, "alarm_delete-id", atomObjectKindTenant, "")
|
||||
assertApplicability(t, applicability, "alarm_assign-id", atomObjectKindTenant, "")
|
||||
assertApplicability(t, applicability, "alarm_acknowledge-id", atomObjectKindTenant, "")
|
||||
assertApplicability(t, applicability, "alarm_resolve-id", atomObjectKindTenant, "")
|
||||
assertApplicability(t, applicability, "read-id", atomObjectKindGroup, "")
|
||||
assertApplicability(t, applicability, "write-id", atomObjectKindGroup, "")
|
||||
assertApplicability(t, applicability, "delete-id", atomObjectKindGroup, "")
|
||||
@@ -120,6 +126,11 @@ func TestBootstrapMagistralaActionsCreatesMissingActionsAndApplicability(t *test
|
||||
assertApplicability(t, applicability, "publish-id", atomObjectKindResource, "resource:channel")
|
||||
assertApplicability(t, applicability, "execute-id", atomObjectKindResource, "resource:rule")
|
||||
assertApplicability(t, applicability, "list-id", atomObjectKindResource, "resource:rule")
|
||||
assertApplicability(t, applicability, "alarm_update-id", atomObjectKindResource, "resource:rule")
|
||||
assertApplicability(t, applicability, "alarm_delete-id", atomObjectKindResource, "resource:rule")
|
||||
assertApplicability(t, applicability, "alarm_assign-id", atomObjectKindResource, "resource:rule")
|
||||
assertApplicability(t, applicability, "alarm_acknowledge-id", atomObjectKindResource, "resource:rule")
|
||||
assertApplicability(t, applicability, "alarm_resolve-id", atomObjectKindResource, "resource:rule")
|
||||
assertApplicability(t, applicability, "execute-id", atomObjectKindResource, "resource:report")
|
||||
assertApplicability(t, applicability, "list-id", atomObjectKindResource, "resource:report")
|
||||
assertApplicability(t, applicability, "manage-id", atomObjectKindResource, "resource:alarm")
|
||||
|
||||
@@ -12,6 +12,13 @@ const (
|
||||
atomActionSubscribe = "subscribe"
|
||||
atomActionExecute = "execute"
|
||||
atomActionList = "list"
|
||||
|
||||
atomActionAlarmRead = "alarm_read"
|
||||
atomActionAlarmUpdate = "alarm_update"
|
||||
atomActionAlarmDelete = "alarm_delete"
|
||||
atomActionAlarmAssign = "alarm_assign"
|
||||
atomActionAlarmAcknowledge = "alarm_acknowledge"
|
||||
atomActionAlarmResolve = "alarm_resolve"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Reference in New Issue
Block a user