Files
magistrala/alarms/middleware/authorization_test.go
T
Arvindh 513a84361b
Property Based Tests / api-test (push) Has been cancelled
Continuous Delivery / lint-and-build (push) Has been cancelled
Deploy GitHub Pages / swagger-ui (push) Has been cancelled
CI Pipeline / Lint Proto (push) Has been cancelled
CI Pipeline / Detect Changes (push) Has been cancelled
Continuous Delivery / Build and Push Docker Images (push) Has been cancelled
CI Pipeline / lint-and-build (push) Has been cancelled
CI Pipeline / Test ${{ matrix.module }} (push) Has been cancelled
CI Pipeline / Upload Coverage (push) Has been cancelled
NOISSUE - Remove unwanted alarms operations (#3548)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2026-07-08 12:27:47 +02:00

225 lines
7.7 KiB
Go

// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package middleware
import (
"context"
"testing"
"github.com/absmach/magistrala/alarms"
"github.com/absmach/magistrala/alarms/mocks"
"github.com/absmach/magistrala/alarms/operations"
"github.com/absmach/magistrala/internal/atom"
"github.com/absmach/magistrala/pkg/authn"
"github.com/absmach/magistrala/pkg/permissions"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
type recordingAtomAuthorizer struct {
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 (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
expectedPM.DomainID = "domain-1"
session := authn.Session{UserID: "user-1", DomainID: "domain-1", DomainUserID: "domain-1_user-1"}
authz := &recordingAtomAuthorizer{allowed: true}
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()
page, err := wrapped.ListAlarms(context.Background(), session, pm)
require.NoError(t, err)
assert.Equal(t, uint64(10), page.Limit)
require.Len(t, authz.reqs, 1)
assert.Equal(t, atom.AuthzRequest{
SubjectID: "user-1",
Action: "alarm_read",
ResourceID: "",
ObjectKind: "tenant",
ObjectID: "domain-1",
Context: map[string]any{
"domain_id": "domain-1",
"legacy_object_type": "domain",
},
}, authz.reqs[0])
}
func TestListAlarmsFiltersToReadableRulesWhenTenantAlarmReadDenied(t *testing.T) {
svc := mocks.NewService(t)
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)
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)
require.NoError(t, err)
require.Len(t, authz.reqs, 1)
assert.Equal(t, "alarm_read", authz.reqs[0].Action)
assert.Equal(t, "tenant", authz.reqs[0].ObjectKind)
require.Len(t, authz.queries, 1)
assert.Equal(t, atom.AuthorizedObjectIDsQuery{
SubjectID: "user-1",
Action: "alarm_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 == "alarm_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, "alarm_read", authz.reqs[1].Action)
assert.Equal(t, "resource", authz.reqs[1].ObjectKind)
assert.Equal(t, "rules", authz.reqs[1].Context["legacy_object_type"])
}
func TestListAlarmsSuperAdminSkipsListAuthorization(t *testing.T) {
svc := mocks.NewService(t)
pm := alarms.PageMetadata{Limit: 10}
expectedPM := pm
expectedPM.DomainID = "domain-1"
session := authn.Session{UserID: "admin-1", DomainID: "domain-1", Role: authn.SuperAdminRole}
authz := &recordingAtomAuthorizer{allowed: true}
wrapped, err := NewAtomAuthorizationMiddleware(svc, authz, testEntitiesOps(t))
require.NoError(t, err)
svc.On("ListAlarms", mock.Anything, mock.MatchedBy(func(s authn.Session) bool {
return s.SuperAdmin
}), expectedPM).Return(alarms.AlarmsPage{Limit: 10}, nil).Once()
_, err = wrapped.ListAlarms(context.Background(), session, pm)
require.NoError(t, err)
require.Len(t, authz.reqs, 1)
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 op, detail := range details {
if detail.PermissionRequired {
perms[detail.Name] = testPermission(op, detail.Name)
}
}
entitiesOps, err := permissions.NewEntitiesOperations(
permissions.EntitiesPermission{operations.EntityType: perms},
permissions.EntitiesOperationDetails[permissions.Operation]{operations.EntityType: details},
)
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)
}
}