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
This commit is contained in:
Tim Möhlmann
2025-04-15 19:38:25 +03:00
committed by GitHub
parent 3b8a2ab811
commit a2f60f2e7a
23 changed files with 741 additions and 172 deletions

View File

@@ -554,7 +554,7 @@ func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, w
if err != nil {
return nil, nil, nil, nil, err
}
users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{orgSearch}}, org, nil)
users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{orgSearch}}, nil)
if err != nil {
return nil, nil, nil, nil, err
}

View File

@@ -108,7 +108,7 @@ func (s *Server) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgDomain str
if err != nil {
return nil, err
}
users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{loginName}}, "", nil)
users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{loginName}}, nil)
if err != nil {
return nil, err
}

View File

@@ -329,7 +329,7 @@ func (s *Server) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgDomain, or
}
queries = append(queries, owner)
}
users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: queries}, orgID, nil)
users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: queries}, nil)
if err != nil {
return nil, err
}

View File

@@ -69,7 +69,7 @@ func (s *Server) ListUsers(ctx context.Context, req *mgmt_pb.ListUsersRequest) (
if err != nil {
return nil, err
}
res, err := s.query.SearchUsers(ctx, queries, orgID, nil)
res, err := s.query.SearchUsers(ctx, queries, nil)
if err != nil {
return nil, err
}

View File

@@ -30,11 +30,11 @@ func (s *Server) GetUserByID(ctx context.Context, req *user.GetUserByIDRequest)
}
func (s *Server) ListUsers(ctx context.Context, req *user.ListUsersRequest) (*user.ListUsersResponse, error) {
queries, filterOrgId, err := listUsersRequestToModel(req)
queries, err := listUsersRequestToModel(req)
if err != nil {
return nil, err
}
res, err := s.query.SearchUsers(ctx, queries, filterOrgId, s.checkPermission)
res, err := s.query.SearchUsers(ctx, queries, s.checkPermission)
if err != nil {
return nil, err
}
@@ -171,11 +171,11 @@ func accessTokenTypeToPb(accessTokenType domain.OIDCTokenType) user.AccessTokenT
}
}
func listUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueries, string, error) {
func listUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueries, error) {
offset, limit, asc := object.ListQueryToQuery(req.Query)
queries, filterOrgId, err := userQueriesToQuery(req.Queries, 0 /*start from level 0*/)
queries, err := userQueriesToQuery(req.Queries, 0 /*start from level 0*/)
if err != nil {
return nil, "", err
return nil, err
}
return &query.UserSearchQueries{
SearchRequest: query.SearchRequest{
@@ -185,7 +185,7 @@ func listUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueri
SortingColumn: userFieldNameToSortingColumn(req.SortingColumn),
},
Queries: queries,
}, filterOrgId, nil
}, nil
}
func userFieldNameToSortingColumn(field user.UserFieldName) query.Column {
@@ -215,18 +215,15 @@ func userFieldNameToSortingColumn(field user.UserFieldName) query.Column {
}
}
func userQueriesToQuery(queries []*user.SearchQuery, level uint8) (_ []query.SearchQuery, filterOrgId string, err error) {
func userQueriesToQuery(queries []*user.SearchQuery, level uint8) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
if orgFilter := query.GetOrganizationIdQuery(); orgFilter != nil {
filterOrgId = orgFilter.OrganizationId
}
q[i], err = userQueryToQuery(query, level)
if err != nil {
return nil, filterOrgId, err
return nil, err
}
}
return q, filterOrgId, nil
return q, nil
}
func userQueryToQuery(query *user.SearchQuery, level uint8) (query.SearchQuery, error) {
@@ -320,14 +317,14 @@ func inUserIdsQueryToQuery(q *user.InUserIDQuery) (query.SearchQuery, error) {
return query.NewUserInUserIdsSearchQuery(q.UserIds)
}
func orQueryToQuery(q *user.OrQuery, level uint8) (query.SearchQuery, error) {
mappedQueries, _, err := userQueriesToQuery(q.Queries, level+1)
mappedQueries, err := userQueriesToQuery(q.Queries, level+1)
if err != nil {
return nil, err
}
return query.NewUserOrSearchQuery(mappedQueries)
}
func andQueryToQuery(q *user.AndQuery, level uint8) (query.SearchQuery, error) {
mappedQueries, _, err := userQueriesToQuery(q.Queries, level+1)
mappedQueries, err := userQueriesToQuery(q.Queries, level+1)
if err != nil {
return nil, err
}

View File

@@ -29,11 +29,11 @@ func (s *Server) GetUserByID(ctx context.Context, req *user.GetUserByIDRequest)
}
func (s *Server) ListUsers(ctx context.Context, req *user.ListUsersRequest) (*user.ListUsersResponse, error) {
queries, filterOrgIds, err := listUsersRequestToModel(req)
queries, err := listUsersRequestToModel(req)
if err != nil {
return nil, err
}
res, err := s.query.SearchUsers(ctx, queries, filterOrgIds, s.checkPermission)
res, err := s.query.SearchUsers(ctx, queries, s.checkPermission)
if err != nil {
return nil, err
}
@@ -165,11 +165,11 @@ func accessTokenTypeToPb(accessTokenType domain.OIDCTokenType) user.AccessTokenT
}
}
func listUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueries, string, error) {
func listUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueries, error) {
offset, limit, asc := object.ListQueryToQuery(req.Query)
queries, filterOrgId, err := userQueriesToQuery(req.Queries, 0 /*start from level 0*/)
queries, err := userQueriesToQuery(req.Queries, 0 /*start from level 0*/)
if err != nil {
return nil, "", err
return nil, err
}
return &query.UserSearchQueries{
SearchRequest: query.SearchRequest{
@@ -179,7 +179,7 @@ func listUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueri
SortingColumn: userFieldNameToSortingColumn(req.SortingColumn),
},
Queries: queries,
}, filterOrgId, nil
}, nil
}
func userFieldNameToSortingColumn(field user.UserFieldName) query.Column {
@@ -209,18 +209,15 @@ func userFieldNameToSortingColumn(field user.UserFieldName) query.Column {
}
}
func userQueriesToQuery(queries []*user.SearchQuery, level uint8) (_ []query.SearchQuery, filterOrgId string, err error) {
func userQueriesToQuery(queries []*user.SearchQuery, level uint8) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
if orgFilter := query.GetOrganizationIdQuery(); orgFilter != nil {
filterOrgId = orgFilter.OrganizationId
}
q[i], err = userQueryToQuery(query, level)
if err != nil {
return nil, filterOrgId, err
return nil, err
}
}
return q, filterOrgId, nil
return q, nil
}
func userQueryToQuery(query *user.SearchQuery, level uint8) (query.SearchQuery, error) {
@@ -314,14 +311,14 @@ func inUserIdsQueryToQuery(q *user.InUserIDQuery) (query.SearchQuery, error) {
return query.NewUserInUserIdsSearchQuery(q.UserIds)
}
func orQueryToQuery(q *user.OrQuery, level uint8) (query.SearchQuery, error) {
mappedQueries, _, err := userQueriesToQuery(q.Queries, level+1)
mappedQueries, err := userQueriesToQuery(q.Queries, level+1)
if err != nil {
return nil, err
}
return query.NewUserOrSearchQuery(mappedQueries)
}
func andQueryToQuery(q *user.AndQuery, level uint8) (query.SearchQuery, error) {
mappedQueries, _, err := userQueriesToQuery(q.Queries, level+1)
mappedQueries, err := userQueriesToQuery(q.Queries, level+1)
if err != nil {
return nil, err
}