fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! feat(permissions): Addeding system user support for permission check v2

This commit is contained in:
Iraq Jaber
2025-03-13 12:05:14 +04:00
parent aedd767838
commit 47959c7d99
9 changed files with 180 additions and 42 deletions

View File

@@ -113,7 +113,8 @@ core_unit_test:
.PHONY: core_integration_db_up
core_integration_db_up:
docker compose -f internal/integration/config/docker-compose.yaml up --pull always --wait $${INTEGRATION_DB_FLAVOR} cache
# docker compose -f internal/integration/config/docker-compose.yaml up --pull always --wait $${INTEGRATION_DB_FLAVOR} cache
docker compose -f internal/integration/config/docker-compose.yaml up --wait $${INTEGRATION_DB_FLAVOR} cache
.PHONY: core_integration_db_down
core_integration_db_down:

View File

@@ -4,7 +4,11 @@ CREATE OR REPLACE FUNCTION eventstore.permitted_orgs(
instanceId TEXT
, userId TEXT
, perm TEXT
, system_user_perms TEXT[]
, system_user_memeber_type INTEGER[]
, system_user_instance_id TEXT[]
, system_user_aggregate_id TEXT[]
, system_user_permissions TEXT[][]
, system_user_permissions_length TEXT[][]
, filter_orgs TEXT
, org_ids OUT TEXT[]
@@ -16,12 +20,12 @@ DECLARE
matched_roles TEXT[]; -- roles containing permission
BEGIN
IF system_user_perms IS NOT NULL THEN
IF system_user_memeber_type IS NOT NULL THEN
DECLARE
system_user_permission_found bool;
BEGIN
SELECT result.perm_found INTO system_user_permission_found
FROM (SELECT COALESCE((SELECT array_position(system_user_perms, perm) > 0 ), false) AS perm_found) AS result;
FROM (SELECT eventstore.get_org_permission(perm, instanceId,filter_orgs, system_user_memeber_type, system_user_instance_id, system_user_aggregate_id, system_user_permissions, system_user_permissions_length) AS perm_found) AS result;
IF system_user_permission_found THEN
SELECT array_agg(o.org_id) INTO org_ids
@@ -75,3 +79,83 @@ BEGIN
RETURN;
END;
$$;
DROP FUNCTION IF EXISTS eventstore.get_org_permission;
CREATE OR REPLACE FUNCTION eventstore.get_org_permission(
perm TEXT
, istance_id TEXT
, org_id TEXT
, system_user_memeber_type INTEGER[]
, sustem_user_instance_id TEXT[]
, system_user_aggregate_id TEXT[]
, system_user_permissions TEXT[][]
, system_user_permissions_length TEXT[][]
-- , outt OUT TEXT[]
, outt OUT BOOL
)
LANGUAGE 'plpgsql'
AS $$
DECLARE
i INTEGER;
length INTEGER;
permission_length INTEGER;
BEGIN
outt := FALSE;
length := array_length(system_user_memeber_type, 1);
-- length := 3;
DROP TABLE IF EXISTS permissions;
CREATE TEMPORARY TABLE permissions (
member_type INTEGER,
instance_id TEXT,
aggregate_id TEXT,
permission TEXT
);
-- <<outer_loop>>
FOR i IN 1..length LOOP
-- only interested in organization level and higher
IF system_user_memeber_type[i] > 3 THEN
CONTINUE;
END IF;
permission_length := system_user_permissions_length[i];
FOR j IN 1..permission_length LOOP
IF system_user_permissions[i][j] != perm THEN
CONTINUE;
END IF;
INSERT INTO permissions (member_type, instance_id, aggregate_id, permission) VALUES
(system_user_memeber_type[i], sustem_user_instance_id[i], system_user_aggregate_id[i], system_user_permissions[i][j] );
END LOOP;
END LOOP;
outt := 4;
RETURN;
SELECT TRUE INTO outt
FROM (SELECT p.member_type FROM permissions p
WHERE
-- check instance id
CASE WHEN p.member_type = 1 OR p.member_type = 2 THEN -- System or IAM
p.aggregate_id = instance_id
OR p.instance_id IS NULL
ELSE
p.instance_id = instance_id
OR p.instance_id IS NULL
END
AND
-- check organization
CASE WHEN p.member_type = 3 THEN -- organization
p.aggregate_id = org_id
ELSE
TRUE
END
Limit 1
) AS result;
DROP TABLE permissions;
END;
$$;

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"reflect"
"slices"
"strings"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
@@ -129,46 +130,69 @@ func GetAllPermissionCtxIDs(perms []string) []string {
return ctxIDs
}
type SystemUserAuthParams struct {
MemberType []int32
InstanceID []string
AggregateID []string
Permissions [][]string
PermissionsLength []int32
}
func addGetSystemUserRolesFuncToCtx(ctx context.Context, systemUserRoleMap []RoleMapping, requestedPermissions []string, ctxData CtxData) context.Context {
if len(ctxData.SystemMemberships) == 0 {
return ctx
} else if ctxData.SystemMemberships[0].MemberType == MemberTypeSystem {
ctx = context.WithValue(ctx, systemUserRolesFuncKey, func() func(ctx context.Context) ([]string, error) {
var permissions []string
return func(ctx context.Context) ([]string, error) {
if permissions != nil {
return permissions, nil
// } else if ctxData.SystemMemberships[0].MemberType == MemberTypeSystem {
} else {
ctx = context.WithValue(ctx, systemUserRolesFuncKey, func() func(ctx context.Context) *SystemUserAuthParams {
var systemUserAuthParams *SystemUserAuthParams
chann := make(chan struct{}, 1)
return func(ctx context.Context) *SystemUserAuthParams {
chann <- struct{}{}
if systemUserAuthParams != nil {
return systemUserAuthParams
}
var err error
permissions, err = getSystemUserPermissions(ctx, systemUserRoleMap)
return permissions, err
defer func() {
<-chann
close(chann)
}()
systemUserAuthParams = &SystemUserAuthParams{
MemberType: make([]int32, len(ctxData.SystemMemberships)),
InstanceID: make([]string, len(ctxData.SystemMemberships)),
AggregateID: make([]string, len(ctxData.SystemMemberships)),
Permissions: make([][]string, len(ctxData.SystemMemberships)),
PermissionsLength: make([]int32, len(ctxData.SystemMemberships)),
}
for i, systemPerm := range ctxData.SystemMemberships {
permissions := []string{}
for _, role := range systemPerm.Roles {
permissions = append(permissions, getPermissionsFromRole(systemUserRoleMap, role)...)
}
slices.Sort(permissions)
permissions = slices.Compact(permissions)
systemUserAuthParams.MemberType[i] = MemberTypeServerToMemberTypeDBMap[systemPerm.MemberType]
systemUserAuthParams.InstanceID[i] = systemPerm.InstanceID
systemUserAuthParams.AggregateID[i] = systemPerm.AggregateID
systemUserAuthParams.Permissions[i] = permissions
systemUserAuthParams.PermissionsLength[i] = int32(len(permissions))
}
return systemUserAuthParams
}
}())
} else {
ctx = context.WithValue(ctx, systemUserRolesFuncKey, func(ctx context.Context) ([]string, error) {
return requestedPermissions, nil
})
}
return ctx
}
func GetSystemUserRoles(ctx context.Context) ([]string, error) {
func GetSystemUserAuthParams(ctx context.Context) (*SystemUserAuthParams, error) {
getSystemUserRolesFuncValue := ctx.Value(systemUserRolesFuncKey)
if getSystemUserRolesFuncValue == nil {
return nil, nil
return &SystemUserAuthParams{}, nil
}
getSystemUserRolesFunc, ok := getSystemUserRolesFuncValue.(func(context.Context) ([]string, error))
getSystemUserRolesFunc, ok := getSystemUserRolesFuncValue.(func(context.Context) *SystemUserAuthParams)
if !ok {
return nil, errors.New("unable to obtain systems role func")
}
return getSystemUserRolesFunc(ctx)
}
func getSystemUserPermissions(ctx context.Context, systemUserRoleMap []RoleMapping) ([]string, error) {
var permissions []string
for _, systemUserRoles := range systemUserRoleMap {
permissions = append(permissions, systemUserRoles.Permissions...)
}
return permissions, nil
return getSystemUserRolesFunc(ctx), nil
}

View File

@@ -51,6 +51,7 @@ type Memberships []*Membership
type Membership struct {
MemberType MemberType
AggregateID string
InstanceID string
// ObjectID differs from aggregate id if object is sub of an aggregate
ObjectID string
@@ -71,6 +72,12 @@ const (
MemberTypeSystem
)
var MemberTypeServerToMemberTypeDBMap map[MemberType]int32 = map[MemberType]int32{
MemberTypeSystem: 1,
MemberTypeIAM: 2,
MemberTypeOrganization: 3,
}
type TokenVerifier interface {
ExistsOrg(ctx context.Context, id, domain string) (string, error)
ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (projectID string, origins []string, err error)

View File

@@ -4,11 +4,13 @@ import (
"context"
"crypto/rsa"
"errors"
"fmt"
"os"
"sync"
"time"
"github.com/go-jose/go-jose/v4"
"github.com/zitadel/logging"
"github.com/zitadel/oidc/v3/pkg/op"
"github.com/zitadel/zitadel/internal/crypto"
@@ -31,7 +33,14 @@ func StartSystemTokenVerifierFromConfig(issuer string, keys map[string]*SystemAP
}
for _, membership := range key.Memberships {
switch membership.MemberType {
case MemberTypeSystem, MemberTypeIAM, MemberTypeOrganization:
case MemberTypeSystem, MemberTypeIAM:
systemUsers[userID] = key.Memberships
case MemberTypeOrganization:
if membership.AggregateID != "" && membership.InstanceID == "" {
errorMsg := fmt.Sprintf("system user %s must include InstancID if AggregateID is set for member type Organization", userID)
logging.Error(errorMsg)
return nil, errors.New(errorMsg)
}
systemUsers[userID] = key.Memberships
case MemberTypeUnspecified, MemberTypeProject, MemberTypeProjectGrant:
return nil, errors.New("for system users, only the membership types System, IAM and Organization are supported")
@@ -67,7 +76,7 @@ func (s *SystemTokenVerifierFromConfig) VerifySystemToken(ctx context.Context, t
for _, membership := range systemUserMemberships {
if membership.MemberType == MemberTypeSystem ||
membership.MemberType == MemberTypeIAM && GetInstance(ctx).InstanceID() == membership.AggregateID ||
membership.MemberType == MemberTypeOrganization && orgID == membership.AggregateID {
membership.MemberType == MemberTypeOrganization && orgID == membership.AggregateID && GetInstance(ctx).InstanceID() == membership.InstanceID {
matchingMemberships = append(matchingMemberships, membership)
}
}

View File

@@ -10,7 +10,6 @@ import (
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/drone/envsubst"
@@ -73,7 +72,7 @@ func privateIPv4() (net.IP, error) {
}
}
//change: use "POD_IP"
// change: use "POD_IP"
ip := net.ParseIP(os.Getenv("POD_IP"))
if ip == nil {
return nil, errors.New("no private ip address")
@@ -139,8 +138,8 @@ func machineID() (uint16, error) {
errors = append(errors, "No machine identification method enabled.")
}
logging.WithFields("errors", strings.Join(errors, ", ")).Panic("none of the enabled methods for identifying the machine succeeded")
//this return will never happen because of panic one line before
// logging.WithFields("errors", strings.Join(errors, ", ")).Panic("none of the enabled methods for identifying the machine succeeded")
// this return will never happen because of panic one line before
return 0, nil
}

View File

@@ -88,6 +88,13 @@ SystemAPIUsers:
- MemberType: IAM
Roles:
- "NO_ROLES"
- MemberType: Organization
Roles:
- "SYSTEM_OWNER"
- "IAM_OWNER"
- "ORG_OWNER"
AggregateID: "123456789012345678"
InstanceID: "123456789012345678"
KeyData: "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFqTVdFWDZtK0gzWndaV1ptTUhxbApHbVoxa0wvRVlWZzJCb24yQm5wOU5LTXdoVTlpK29CcUwrR0FzVVZYdnBkMmhVTy9ZK1VpVzlRdnJ4K3ZBeVpNCmdrNjRRNlFFNm5ZMWJncnV3aEJDUC85ZWlMMzVvOTRHelhiS2RDSEF5bFNBQmRHemZaTDN1YUgwVndvRk9neU0KZkJveTdGMHFLRXA0bVp5ZUhmMFo3ZXZacVVyRDVNcEZMTjBhUnRqVWpwOTFpd0tGU29kYXY1S25sYW4vSGtQaQpzN3NnLzBmVURRRDRzZ2ZvcndManJWYnI1aUtxSTBHQ3VhUEwzazRQOEdnY1haczVJcHUzb1BDZXdWUTBvd1hoCjJvRXVTdlNDYS8wTmxYanRLMlRqbmlYeTVSL2NaVXF3NzNOd0NFdjl4N1pLaU51dkpEWkw2UnM5Q0xJT3RhVkUKTFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="
InitProjections:

View File

@@ -12,7 +12,7 @@ import (
const (
// eventstore.permitted_orgs(instanceid text, userid text, perm text, system_user_perms text[], filter_orgs text)
wherePermittedOrgsClause = "%s = ANY(eventstore.permitted_orgs(?, ?, ?, ?, ?))"
wherePermittedOrgsClause = "%s = ANY(eventstore.permitted_orgs(?, ?, ?, ?, ?, ?, ?, ?, ?))"
wherePermittedOrgsOrCurrentUserClause = "(" + wherePermittedOrgsClause + " OR %s = ?" + ")"
)
@@ -24,7 +24,7 @@ const (
// and is typically the `resource_owner` column in ZITADEL.
// We use full identifiers in the query builder so this function should be
// called with something like `UserResourceOwnerCol.identifier()` for example.
func wherePermittedOrgs(ctx context.Context, query sq.SelectBuilder, systemUserPermissions []string, filterOrgIds, orgIDColumn, permission string) sq.SelectBuilder {
func wherePermittedOrgs(ctx context.Context, query sq.SelectBuilder, systemUserAuthParams *authz.SystemUserAuthParams, filterOrgIds, orgIDColumn, permission string) sq.SelectBuilder {
userID := authz.GetCtxData(ctx).UserID
logging.WithFields("permission_check_v2_flag", authz.GetFeatures(ctx).PermissionCheckV2, "org_id_column", orgIDColumn, "permission", permission, "user_id", userID).Debug("permitted orgs check used")
@@ -33,12 +33,12 @@ func wherePermittedOrgs(ctx context.Context, query sq.SelectBuilder, systemUserP
authz.GetInstance(ctx).InstanceID(),
userID,
permission,
systemUserPermissions,
systemUserAuthParams,
filterOrgIds,
)
}
func wherePermittedOrgsOrCurrentUser(ctx context.Context, query sq.SelectBuilder, systemUserPermissions []string, filterOrgIds, orgIDColumn, userIdColum, permission string) sq.SelectBuilder {
func wherePermittedOrgsOrCurrentUser(ctx context.Context, query sq.SelectBuilder, systemUserAuthParams *authz.SystemUserAuthParams, filterOrgIds, orgIDColumn, userIdColum, permission string) sq.SelectBuilder {
userID := authz.GetCtxData(ctx).UserID
logging.WithFields("permission_check_v2_flag", authz.GetFeatures(ctx).PermissionCheckV2, "org_id_column", orgIDColumn, "user_id_colum", userIdColum, "permission", permission, "user_id", userID).Debug("permitted orgs check used")
@@ -47,7 +47,11 @@ func wherePermittedOrgsOrCurrentUser(ctx context.Context, query sq.SelectBuilder
authz.GetInstance(ctx).InstanceID(),
userID,
permission,
systemUserPermissions,
systemUserAuthParams.MemberType,
systemUserAuthParams.InstanceID,
systemUserAuthParams.AggregateID,
systemUserAuthParams.Permissions,
systemUserAuthParams.PermissionsLength,
filterOrgIds,
userID,
)

View File

@@ -5,6 +5,7 @@ import (
"database/sql"
_ "embed"
"errors"
"fmt"
"slices"
"strings"
"time"
@@ -630,6 +631,7 @@ func (q *Queries) CountUsers(ctx context.Context, queries *UserSearchQueries) (c
return err
}, stmt, args...)
if err != nil {
fmt.Printf("@@ >>>>>>>>>>>>>>>>>>>>>>>>>>>> err = %+v\n", err)
return 0, zerrors.ThrowInternal(err, "QUERY-AG4gs", "Errors.Internal")
}
return count, err
@@ -656,7 +658,7 @@ func (q *Queries) searchUsers(ctx context.Context, queries *UserSearchQueries, f
})
if permissionCheckV2 {
// extract system user roles
systemUserPermissions, err := authz.GetSystemUserRoles(ctx)
systemUserPermissions, err := authz.GetSystemUserAuthParams(ctx)
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-GS9gs", "Errors.Internal")
}
@@ -673,6 +675,7 @@ func (q *Queries) searchUsers(ctx context.Context, queries *UserSearchQueries, f
return err
}, stmt, args...)
if err != nil {
fmt.Printf("@@ >>>>>>>>>>>>>>>>>>>>>>>>>>>> err = %+v\n", err)
return nil, zerrors.ThrowInternal(err, "QUERY-AG4gs", "Errors.Internal")
}
users.State, err = q.latestState(ctx, userTable)