zitadel/internal/api/authz/authorization_test.go
Tim Möhlmann a2f60f2e7a
perf(query): org permission function for resources (#9677)
# Which Problems Are Solved

Classic permission checks execute for every returned row on resource
based search APIs. Complete background and problem definition can be
found here: https://github.com/zitadel/zitadel/issues/9188

# How the Problems Are Solved

- PermissionClause function now support dynamic query building, so it
supports multiple cases.
- PermissionClause is applied to all list resources which support org
level permissions.
- Wrap permission logic into wrapper functions so we keep the business
logic clean.

# Additional Changes

- Handle org ID optimization in the query package, so it is reusable for
all resources, instead of extracting the filter in the API.
- Cleanup and test system user conversion in the authz package. (context
middleware)
- Fix: `core_integration_db_up` make recipe was missing the postgres
service.

# Additional Context

- Related to https://github.com/zitadel/zitadel/issues/9190
2025-04-15 18:38:25 +02:00

405 lines
8.2 KiB
Go

package authz
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/zerrors"
)
type TestRequest struct {
Test string
}
func Test_CheckUserPermissions(t *testing.T) {
type args struct {
req *TestRequest
perms []string
authOpt Option
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "no permissions",
args: args{
req: &TestRequest{},
perms: []string{},
},
wantErr: true,
},
{
name: "has permission and no context requested",
args: args{
req: &TestRequest{},
perms: []string{"project.read"},
authOpt: Option{CheckParam: ""},
},
wantErr: false,
},
{
name: "context requested and has global permission",
args: args{
req: &TestRequest{Test: "Test"},
perms: []string{"project.read", "project.read:1"},
authOpt: Option{CheckParam: "Test"},
},
wantErr: false,
},
{
name: "context requested and has specific permission",
args: args{
req: &TestRequest{Test: "Test"},
perms: []string{"project.read:Test"},
authOpt: Option{CheckParam: "Test"},
},
wantErr: false,
},
{
name: "context requested and has no permission",
args: args{
req: &TestRequest{Test: "Hodor"},
perms: []string{"project.read:Test"},
authOpt: Option{CheckParam: "Test"},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := checkUserPermissions(tt.args.req, tt.args.perms, tt.args.authOpt)
if tt.wantErr && err == nil {
t.Errorf("got wrong result, should get err: actual: %v ", err)
}
if !tt.wantErr && err != nil {
t.Errorf("shouldn't get err: %v ", err)
}
if tt.wantErr && !zerrors.IsPermissionDenied(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func Test_SplitPermission(t *testing.T) {
type args struct {
perm string
}
tests := []struct {
name string
args args
permName string
permCtxID string
}{
{
name: "permission with context id",
args: args{
perm: "project.read:ctxID",
},
permName: "project.read",
permCtxID: "ctxID",
},
{
name: "permission without context id",
args: args{
perm: "project.read",
},
permName: "project.read",
permCtxID: "",
},
{
name: "permission to many parts",
args: args{
perm: "project.read:1:0",
},
permName: "project.read",
permCtxID: "1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
name, id := SplitPermission(tt.args.perm)
if name != tt.permName {
t.Errorf("got wrong result on name, expecting: %v, actual: %v ", tt.permName, name)
}
if id != tt.permCtxID {
t.Errorf("got wrong result on id, expecting: %v, actual: %v ", tt.permCtxID, id)
}
})
}
}
func Test_HasContextPermission(t *testing.T) {
type args struct {
req *TestRequest
fieldname string
perms []string
}
tests := []struct {
name string
args args
result bool
}{
{
name: "existing context permission",
args: args{
req: &TestRequest{Test: "right"},
fieldname: "Test",
perms: []string{"test:wrong", "test:right"},
},
result: true,
},
{
name: "not existing context permission",
args: args{
req: &TestRequest{Test: "test"},
fieldname: "Test",
perms: []string{"test:wrong", "test:wrong2"},
},
result: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := hasContextPermission(tt.args.req, tt.args.fieldname, tt.args.perms)
if result != tt.result {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
}
})
}
}
func Test_GetFieldFromReq(t *testing.T) {
type args struct {
req *TestRequest
fieldname string
}
tests := []struct {
name string
args args
result string
}{
{
name: "existing field",
args: args{
req: &TestRequest{Test: "TestValue"},
fieldname: "Test",
},
result: "TestValue",
},
{
name: "not existing field",
args: args{
req: &TestRequest{Test: "TestValue"},
fieldname: "Test2",
},
result: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getFieldFromReq(tt.args.req, tt.args.fieldname)
if result != tt.result {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
}
})
}
}
func Test_HasGlobalPermission(t *testing.T) {
type args struct {
perms []string
}
tests := []struct {
name string
args args
result bool
}{
{
name: "global perm existing",
args: args{
perms: []string{"perm:1", "perm:2", "perm"},
},
result: true,
},
{
name: "global perm not existing",
args: args{
perms: []string{"perm:1", "perm:2", "perm:3"},
},
result: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := HasGlobalPermission(tt.args.perms)
if result != tt.result {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
}
})
}
}
func Test_GetPermissionCtxIDs(t *testing.T) {
type args struct {
perms []string
}
tests := []struct {
name string
args args
result []string
}{
{
name: "no specific permission",
args: args{
perms: []string{"perm"},
},
result: []string{},
},
{
name: "ctx id",
args: args{
perms: []string{"perm:1", "perm", "perm:3"},
},
result: []string{"1", "3"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := GetAllPermissionCtxIDs(tt.args.perms)
if !equalStringArray(result, tt.result) {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
}
})
}
}
func Test_systemMembershipsToUserPermissions(t *testing.T) {
roleMap := []RoleMapping{
{
Role: "FOO_BAR",
Permissions: []string{"foo.bar.read", "foo.bar.write"},
},
{
Role: "BAR_FOO",
Permissions: []string{"bar.foo.read", "bar.foo.write", "foo.bar.read"},
},
}
type args struct {
memberships Memberships
roleMap []RoleMapping
}
tests := []struct {
name string
args args
want []SystemUserPermissions
}{
{
name: "nil memberships",
args: args{
memberships: nil,
roleMap: roleMap,
},
want: nil,
},
{
name: "empty memberships",
args: args{
memberships: Memberships{},
roleMap: roleMap,
},
want: []SystemUserPermissions{},
},
{
name: "single membership",
args: args{
memberships: Memberships{
{
MemberType: MemberTypeSystem,
AggregateID: "1",
ObjectID: "2",
Roles: []string{"FOO_BAR"},
},
},
roleMap: roleMap,
},
want: []SystemUserPermissions{
{
MemberType: MemberTypeSystem,
AggregateID: "1",
ObjectID: "2",
Permissions: []string{"foo.bar.read", "foo.bar.write"},
},
},
},
{
name: "multiple memberships",
args: args{
memberships: Memberships{
{
MemberType: MemberTypeSystem,
AggregateID: "1",
ObjectID: "2",
Roles: []string{"FOO_BAR"},
},
{
MemberType: MemberTypeIAM,
AggregateID: "1",
ObjectID: "2",
Roles: []string{"BAR_FOO"},
},
},
roleMap: roleMap,
},
want: []SystemUserPermissions{
{
MemberType: MemberTypeSystem,
AggregateID: "1",
ObjectID: "2",
Permissions: []string{"foo.bar.read", "foo.bar.write"},
},
{
MemberType: MemberTypeIAM,
AggregateID: "1",
ObjectID: "2",
Permissions: []string{"bar.foo.read", "bar.foo.write", "foo.bar.read"},
},
},
},
{
name: "multiple roles",
args: args{
memberships: Memberships{
{
MemberType: MemberTypeSystem,
AggregateID: "1",
ObjectID: "2",
Roles: []string{"FOO_BAR", "BAR_FOO"},
},
},
roleMap: roleMap,
},
want: []SystemUserPermissions{
{
MemberType: MemberTypeSystem,
AggregateID: "1",
ObjectID: "2",
Permissions: []string{"bar.foo.read", "bar.foo.write", "foo.bar.read", "foo.bar.write"},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := systemMembershipsToUserPermissions(tt.args.memberships, tt.args.roleMap)
assert.Equal(t, tt.want, got)
})
}
}