feat(permissions): project member permission filter (#9757)

# Which Problems Are Solved

Add the possibility to filter project resources based on project member
roles.

# How the Problems Are Solved

Extend and refactor existing Pl/PgSQL functions to implement the
following:

- Solve O(n) complexity in returned resources IDs by returning a boolean
filter for instance level permissions.
- Individually permitted orgs are returned only if there was no instance
permission
- Individually permitted projects are returned only if there was no
instance permission
- Because of the multiple filter terms, use `INNER JOIN`s instead of
`WHERE` clauses.

# Additional Changes

- system permission function no longer query the organization view and
therefore can be `immutable`, giving big performance benefits for
frequently reused system users. (like our hosted login in Zitadel cloud)
- The permitted org and project functions are now defined as `stable`
because the don't modify on-disk data. This might give a small
performance gain
- The Pl/PgSQL functions are now tested using Go unit tests.

# Additional Context

- Depends on https://github.com/zitadel/zitadel/pull/9677
- Part of https://github.com/zitadel/zitadel/issues/9188
- Closes https://github.com/zitadel/zitadel/issues/9190
This commit is contained in:
Tim Möhlmann
2025-04-22 11:42:59 +03:00
committed by GitHub
parent 618143931b
commit 658ca3606b
18 changed files with 1403 additions and 225 deletions

View File

@@ -1,4 +1,4 @@
//go:generate enumer -type MemberType -trimprefix MemberType -json
//go:generate enumer -type MemberType -trimprefix MemberType -json -sql
package authz

View File

@@ -1,8 +1,9 @@
// Code generated by "enumer -type MemberType -trimprefix MemberType -json"; DO NOT EDIT.
// Code generated by "enumer -type MemberType -trimprefix MemberType -json -sql"; DO NOT EDIT.
package authz
import (
"database/sql/driver"
"encoding/json"
"fmt"
"strings"
@@ -110,3 +111,33 @@ func (i *MemberType) UnmarshalJSON(data []byte) error {
*i, err = MemberTypeString(s)
return err
}
func (i MemberType) Value() (driver.Value, error) {
return i.String(), nil
}
func (i *MemberType) Scan(value interface{}) error {
if value == nil {
return nil
}
var str string
switch v := value.(type) {
case []byte:
str = string(v)
case string:
str = v
case fmt.Stringer:
str = v.String()
default:
return fmt.Errorf("invalid value of MemberType: %[1]T(%[1]v)", value)
}
val, err := MemberTypeString(str)
if err != nil {
return err
}
*i = val
return nil
}