Files
Dušan Borovčanin 61d0427898 NOISSUE - Rename to Magistrala (#3427)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-04-06 15:23:42 +02:00

377 lines
8.9 KiB
Go

// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package auth_test
import (
"testing"
"time"
apiutil "github.com/absmach/magistrala/api/http/util"
"github.com/absmach/magistrala/auth"
channelsOps "github.com/absmach/magistrala/channels/operations"
clientsOps "github.com/absmach/magistrala/clients/operations"
groupsOps "github.com/absmach/magistrala/groups/operations"
"github.com/stretchr/testify/assert"
)
func TestScopeAuthorized(t *testing.T) {
cases := []struct {
desc string
scope *auth.Scope
entityType auth.EntityType
domainID string
operation string
entityID string
expected bool
}{
{
desc: "Authorized with matching entity type, domain, operation and entity ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
DomainID: "domain1",
Operation: "view",
EntityID: "entity1",
},
entityType: auth.GroupsType,
domainID: "domain1",
operation: "view",
entityID: "entity1",
expected: true,
},
{
desc: "Authorized with wildcard entity ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
DomainID: "domain1",
Operation: "view",
EntityID: "*",
},
entityType: auth.GroupsType,
domainID: "domain1",
operation: "view",
entityID: "any-entity",
expected: true,
},
{
desc: "Authorized without domain ID",
scope: &auth.Scope{
EntityType: auth.ClientsType,
DomainID: "",
Operation: "view",
EntityID: "client1",
},
entityType: auth.ClientsType,
domainID: "domain1",
operation: "view",
entityID: "client1",
expected: true,
},
{
desc: "Not authorized with different entity type",
scope: &auth.Scope{
EntityType: auth.GroupsType,
DomainID: "domain1",
Operation: "view",
EntityID: "entity1",
},
entityType: auth.ChannelsType,
domainID: "domain1",
operation: "view",
entityID: "entity1",
expected: false,
},
{
desc: "Not authorized with different domain ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
DomainID: "domain1",
Operation: "view",
EntityID: "entity1",
},
entityType: auth.GroupsType,
domainID: "domain2",
operation: "view",
entityID: "entity1",
expected: false,
},
{
desc: "Not authorized with different operation",
scope: &auth.Scope{
EntityType: auth.GroupsType,
DomainID: "domain1",
Operation: "view",
EntityID: "entity1",
},
entityType: auth.GroupsType,
domainID: "domain1",
operation: "delete",
entityID: "entity1",
expected: false,
},
{
desc: "Not authorized with different entity ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
DomainID: "domain1",
Operation: "view",
EntityID: "entity1",
},
entityType: auth.GroupsType,
domainID: "domain1",
operation: "view",
entityID: "entity2",
expected: false,
},
{
desc: "Not authorized with nil scope",
scope: nil,
entityType: auth.GroupsType,
domainID: "domain1",
operation: "view",
entityID: "entity1",
expected: false,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
result := tc.scope.Authorized(tc.entityType, tc.domainID, tc.operation, tc.entityID)
assert.Equal(t, tc.expected, result, "Authorized() = %v, expected %v", result, tc.expected)
})
}
}
func TestScopeValidate(t *testing.T) {
cases := []struct {
desc string
scope *auth.Scope
err error
}{
{
desc: "Valid scope for groups with domain ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
DomainID: "domain1",
Operation: "view",
EntityID: "entity1",
},
err: nil,
},
{
desc: "Valid scope for channels with domain ID",
scope: &auth.Scope{
EntityType: auth.ChannelsType,
DomainID: "domain1",
Operation: "view",
EntityID: "channel1",
},
err: nil,
},
{
desc: "Valid scope for clients with domain ID",
scope: &auth.Scope{
EntityType: auth.ClientsType,
DomainID: "domain1",
Operation: "update",
EntityID: "client1",
},
err: nil,
},
{
desc: "Valid scope for messages with domain ID",
scope: &auth.Scope{
EntityType: auth.MessagesType,
DomainID: "domain1",
Operation: "message_publish",
EntityID: "message1",
},
err: nil,
},
{
desc: "Valid scope for dashboard with domain ID",
scope: &auth.Scope{
EntityType: auth.DashboardType,
DomainID: "domain1",
Operation: "dashboard_share",
EntityID: "dashboard1",
},
err: nil,
},
{
desc: "Valid scope with wildcard entity ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
DomainID: "domain1",
Operation: "view",
EntityID: "*",
},
err: nil,
},
{
desc: "Invalid nil scope",
scope: nil,
err: assert.AnError, // Will be checked with Contains
},
{
desc: "Invalid scope without entity ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
DomainID: "domain1",
Operation: groupsOps.OperationDetails()[groupsOps.OpViewGroup].Name,
EntityID: "",
},
err: apiutil.ErrMissingEntityID,
},
{
desc: "Invalid scope for groups without domain ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
DomainID: "",
Operation: groupsOps.OperationDetails()[groupsOps.OpViewGroup].Name,
EntityID: "entity1",
},
err: apiutil.ErrMissingDomainID,
},
{
desc: "Invalid scope for channels without domain ID",
scope: &auth.Scope{
EntityType: auth.ChannelsType,
DomainID: "",
Operation: channelsOps.OperationDetails()[channelsOps.OpViewChannel].Name,
EntityID: "channel1",
},
err: apiutil.ErrMissingDomainID,
},
{
desc: "Invalid scope for clients without domain ID",
scope: &auth.Scope{
EntityType: auth.ClientsType,
DomainID: "",
Operation: clientsOps.OperationDetails()[clientsOps.OpViewClient].Name,
EntityID: "client1",
},
err: apiutil.ErrMissingDomainID,
},
{
desc: "Invalid scope for dashboard without domain ID",
scope: &auth.Scope{
EntityType: auth.DashboardType,
DomainID: "",
Operation: auth.OpShare,
EntityID: "dashboard1",
},
err: apiutil.ErrMissingDomainID,
},
{
desc: "Invalid scope for messages without domain ID",
scope: &auth.Scope{
EntityType: auth.MessagesType,
DomainID: "",
Operation: auth.OpPublish,
EntityID: "message1",
},
err: apiutil.ErrMissingDomainID,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.scope.Validate()
if tc.err != nil {
assert.Error(t, err, "Validate() should return error")
if tc.err != assert.AnError {
assert.Equal(t, tc.err, err, "Validate() error = %v, expected %v", err, tc.err)
}
} else {
assert.NoError(t, err, "Validate() should not return error")
}
})
}
}
func TestPATValidate(t *testing.T) {
cases := []struct {
desc string
pat *auth.PAT
err bool
}{
{
desc: "Valid PAT",
pat: &auth.PAT{
ID: "pat-id",
User: "user-id",
Name: "test-pat",
Description: "test description",
},
err: false,
},
{
desc: "Invalid nil PAT",
pat: nil,
err: true,
},
{
desc: "Invalid PAT without name",
pat: &auth.PAT{
ID: "pat-id",
User: "user-id",
Name: "",
Description: "test description",
},
err: true,
},
{
desc: "Invalid PAT without user",
pat: &auth.PAT{
ID: "pat-id",
User: "",
Name: "test-pat",
Description: "test description",
},
err: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.pat.Validate()
if tc.err {
assert.Error(t, err, "Validate() should return error")
} else {
assert.NoError(t, err, "Validate() should not return error")
}
})
}
}
func TestPATMarshalUnmarshalBinary(t *testing.T) {
pat := auth.PAT{
ID: "pat-id",
User: "user-id",
Name: "test-pat",
Description: "test description",
Secret: "secret",
IssuedAt: time.Now().UTC().Round(time.Second),
ExpiresAt: time.Now().UTC().Add(24 * time.Hour).Round(time.Second),
Status: auth.ActiveStatus,
}
// Marshal
data, err := pat.MarshalBinary()
assert.NoError(t, err, "MarshalBinary() should not return error")
assert.NotNil(t, data, "MarshalBinary() should return data")
// Unmarshal
var newPAT auth.PAT
err = newPAT.UnmarshalBinary(data)
assert.NoError(t, err, "UnmarshalBinary() should not return error")
assert.Equal(t, pat.ID, newPAT.ID, "ID mismatch")
assert.Equal(t, pat.User, newPAT.User, "User mismatch")
assert.Equal(t, pat.Name, newPAT.Name, "Name mismatch")
assert.Equal(t, pat.Description, newPAT.Description, "Description mismatch")
assert.Equal(t, pat.Secret, newPAT.Secret, "Secret mismatch")
assert.Equal(t, pat.Status, newPAT.Status, "Status mismatch")
}