mirror of
https://github.com/absmach/magistrala.git
synced 2026-08-07 07:14:46 +00:00
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
Signed-off-by: Arvindh <arvindh91@gmail.com>
225 lines
7.7 KiB
Go
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)
|
|
}
|
|
}
|