mirror of
https://github.com/absmach/magistrala.git
synced 2026-06-22 20:00:22 +00:00
61d0427898
Signed-off-by: dusan <borovcanindusan1@gmail.com>
377 lines
8.9 KiB
Go
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")
|
|
}
|