mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-26 01:02:16 +00:00
fix(api): implement missing filters on ListAdministrators endpoint (#11319)
# Which Problems Are Solved A customer reported that multiple filters on the ListAdministrators endpoint were returning a `List.Query.Invalid` error. They were missing the implementation. # How the Problems Are Solved Implement `and`, `or`, `not` and `role` filters. # Additional Changes None # Additional Context - reported by customer - requires backport to v4.x
This commit is contained in:
@@ -584,6 +584,117 @@ func TestServer_ListAdministrators(t *testing.T) {
|
||||
Administrators: []*internal_permission.Administrator{projectGrantAdministrator1, projectGrantAdministrator2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list, role filter",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
req: &internal_permission.ListAdministratorsRequest{
|
||||
Filters: []*internal_permission.AdministratorSearchFilter{
|
||||
{
|
||||
Filter: &internal_permission.AdministratorSearchFilter_Role{
|
||||
Role: &internal_permission.RoleFilter{
|
||||
RoleKey: "PROJECT_OWNER",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Pagination: &filter.PaginationRequest{Asc: true, Limit: 2},
|
||||
},
|
||||
},
|
||||
want: &internal_permission.ListAdministratorsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 4, // 3 project admins with PROJECT_OWNER role and the project creator
|
||||
AppliedLimit: 2,
|
||||
},
|
||||
Administrators: []*internal_permission.Administrator{projectAdministrator1, projectAdministrator2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list, and with not filter",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
req: &internal_permission.ListAdministratorsRequest{
|
||||
Filters: []*internal_permission.AdministratorSearchFilter{
|
||||
{
|
||||
Filter: &internal_permission.AdministratorSearchFilter_And{
|
||||
And: &internal_permission.AndFilter{
|
||||
Queries: []*internal_permission.AdministratorSearchFilter{
|
||||
{
|
||||
Filter: &internal_permission.AdministratorSearchFilter_Role{
|
||||
Role: &internal_permission.RoleFilter{
|
||||
RoleKey: "PROJECT_OWNER",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Filter: &internal_permission.AdministratorSearchFilter_Not{
|
||||
Not: &internal_permission.NotFilter{
|
||||
Query: &internal_permission.AdministratorSearchFilter{
|
||||
Filter: &internal_permission.AdministratorSearchFilter_InUserIdsFilter{
|
||||
InUserIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{projectAdministrator1.GetUser().GetId(), projectAdministrator2.GetUser().GetId()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Pagination: &filter.PaginationRequest{Asc: true, Limit: 1},
|
||||
},
|
||||
},
|
||||
want: &internal_permission.ListAdministratorsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 2, // 1 project admin and the project creator
|
||||
AppliedLimit: 1,
|
||||
},
|
||||
Administrators: []*internal_permission.Administrator{projectAdministrator3},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list, or filter",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
req: &internal_permission.ListAdministratorsRequest{
|
||||
Filters: []*internal_permission.AdministratorSearchFilter{
|
||||
{
|
||||
Filter: &internal_permission.AdministratorSearchFilter_Or{
|
||||
Or: &internal_permission.OrFilter{
|
||||
Queries: []*internal_permission.AdministratorSearchFilter{
|
||||
{
|
||||
Filter: &internal_permission.AdministratorSearchFilter_InUserIdsFilter{
|
||||
InUserIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{projectGrantAdministrator1.GetUser().GetId()},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Filter: &internal_permission.AdministratorSearchFilter_Role{
|
||||
Role: &internal_permission.RoleFilter{
|
||||
RoleKey: "PROJECT_OWNER",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Pagination: &filter.PaginationRequest{Asc: true, Limit: 2},
|
||||
},
|
||||
},
|
||||
want: &internal_permission.ListAdministratorsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 5, // 3 project grant admins, the project creator and the project grant admin
|
||||
AppliedLimit: 2,
|
||||
},
|
||||
Administrators: []*internal_permission.Administrator{projectAdministrator1, projectAdministrator2},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -766,7 +877,6 @@ func TestServer_ListAdministrators_PermissionV2(t *testing.T) {
|
||||
instancePermissionV2.CreateProjectGrantMembership(t, iamOwnerCtx, projectResp.GetId(), grantedOrganizationResp.GetOrganizationId(), userProjectGrantResp.GetUserId())
|
||||
patProjectGrantResp := instancePermissionV2.CreatePersonalAccessToken(iamOwnerCtx, userProjectGrantResp.GetUserId())
|
||||
projectGrantOwnerCtx := integration.WithAuthorizationToken(CTX, patProjectGrantResp.Token)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
dep func(*internal_permission.ListAdministratorsRequest, *internal_permission.ListAdministratorsResponse)
|
||||
|
||||
@@ -33,7 +33,7 @@ func (s *Server) listAdministratorsRequestToModel(req *internal_permission.ListA
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := administratorSearchFiltersToQuery(req.Filters)
|
||||
queries, err := administratorSearchFiltersToQuery(req.Filters, 0) // start at level 0
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,10 +63,10 @@ func administratorFieldNameToSortingColumn(field internal_permission.Administrat
|
||||
}
|
||||
}
|
||||
|
||||
func administratorSearchFiltersToQuery(queries []*internal_permission.AdministratorSearchFilter) (_ []query.SearchQuery, err error) {
|
||||
func administratorSearchFiltersToQuery(queries []*internal_permission.AdministratorSearchFilter, level uint8) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries))
|
||||
for i, qry := range queries {
|
||||
q[i], err = administratorFilterToModel(qry)
|
||||
q[i], err = administratorFilterToQuery(qry, level)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -74,7 +74,11 @@ func administratorSearchFiltersToQuery(queries []*internal_permission.Administra
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func administratorFilterToModel(filter *internal_permission.AdministratorSearchFilter) (query.SearchQuery, error) {
|
||||
func administratorFilterToQuery(filter *internal_permission.AdministratorSearchFilter, level uint8) (query.SearchQuery, error) {
|
||||
if level > 20 {
|
||||
// can't go deeper than 20 levels of nesting.
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PERM-zsQ97", "Errors.Query.TooManyNestingLevels")
|
||||
}
|
||||
switch q := filter.Filter.(type) {
|
||||
case *internal_permission.AdministratorSearchFilter_InUserIdsFilter:
|
||||
return inUserIDsFilterToQuery(q.InUserIdsFilter)
|
||||
@@ -90,8 +94,16 @@ func administratorFilterToModel(filter *internal_permission.AdministratorSearchF
|
||||
return userDisplayNameFilterToQuery(q.UserDisplayName)
|
||||
case *internal_permission.AdministratorSearchFilter_Resource:
|
||||
return resourceFilterToQuery(q.Resource)
|
||||
case *internal_permission.AdministratorSearchFilter_Role:
|
||||
return roleFilterToQuery(q.Role)
|
||||
case *internal_permission.AdministratorSearchFilter_And:
|
||||
return andQueryToQuery(q.And, level)
|
||||
case *internal_permission.AdministratorSearchFilter_Or:
|
||||
return orQueryToQuery(q.Or, level)
|
||||
case *internal_permission.AdministratorSearchFilter_Not:
|
||||
return notQueryToQuery(q.Not, level)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "ORG-vR9nC", "List.Query.Invalid")
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PERM-vR9nC", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +163,32 @@ func resourceFilterToQuery(q *internal_permission.ResourceFilter) (query.SearchQ
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func roleFilterToQuery(q *internal_permission.RoleFilter) (query.SearchQuery, error) {
|
||||
return query.NewMembershipRoleQuery(q.GetRoleKey())
|
||||
}
|
||||
|
||||
func orQueryToQuery(q *internal_permission.OrFilter, level uint8) (query.SearchQuery, error) {
|
||||
mappedQueries, err := administratorSearchFiltersToQuery(q.Queries, level+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return query.NewOrQuery(mappedQueries...)
|
||||
}
|
||||
func andQueryToQuery(q *internal_permission.AndFilter, level uint8) (query.SearchQuery, error) {
|
||||
mappedQueries, err := administratorSearchFiltersToQuery(q.Queries, level+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return query.NewAndQuery(mappedQueries...)
|
||||
}
|
||||
func notQueryToQuery(q *internal_permission.NotFilter, level uint8) (query.SearchQuery, error) {
|
||||
mappedQuery, err := administratorFilterToQuery(q.Query, level+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return query.NewNotQuery(mappedQuery)
|
||||
}
|
||||
|
||||
func administratorsToPb(administrators []*query.Administrator) []*internal_permission.Administrator {
|
||||
a := make([]*internal_permission.Administrator, len(administrators))
|
||||
for i, admin := range administrators {
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package internal_permission
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/internal_permission/v2"
|
||||
)
|
||||
|
||||
func Test_administratorFilterToQuery(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
type args struct {
|
||||
filter *internal_permission.AdministratorSearchFilter
|
||||
level uint8
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want query.SearchQuery
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "max nested queries",
|
||||
args: args{
|
||||
filter: &internal_permission.AdministratorSearchFilter{
|
||||
Filter: &internal_permission.AdministratorSearchFilter_And{
|
||||
And: &internal_permission.AndFilter{
|
||||
Queries: []*internal_permission.AdministratorSearchFilter{
|
||||
{
|
||||
Filter: &internal_permission.AdministratorSearchFilter_CreationDate{
|
||||
CreationDate: &filter.TimestampFilter{
|
||||
Timestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
level: 19,
|
||||
},
|
||||
want: func() query.SearchQuery {
|
||||
dateQuery, _ := query.NewMembershipCreationDateQuery(now, query.TimestampEquals)
|
||||
q, _ := query.NewAndQuery(dateQuery)
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "too many nested queries",
|
||||
args: args{
|
||||
filter: &internal_permission.AdministratorSearchFilter{
|
||||
Filter: &internal_permission.AdministratorSearchFilter_And{
|
||||
And: &internal_permission.AndFilter{
|
||||
Queries: []*internal_permission.AdministratorSearchFilter{
|
||||
{
|
||||
Filter: &internal_permission.AdministratorSearchFilter_And{
|
||||
And: &internal_permission.AndFilter{
|
||||
Queries: []*internal_permission.AdministratorSearchFilter{
|
||||
{
|
||||
Filter: &internal_permission.AdministratorSearchFilter_CreationDate{
|
||||
CreationDate: &filter.TimestampFilter{
|
||||
Timestamp: timestamppb.New(time.Now().UTC()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
level: 19,
|
||||
},
|
||||
wantErr: zerrors.ThrowInvalidArgument(nil, "PERM-zsQ97", "Errors.Query.TooManyNestingLevels"),
|
||||
},
|
||||
{
|
||||
name: "invalid filter",
|
||||
args: args{
|
||||
filter: &internal_permission.AdministratorSearchFilter{
|
||||
Filter: nil,
|
||||
},
|
||||
level: 0,
|
||||
},
|
||||
wantErr: zerrors.ThrowInvalidArgument(nil, "PERM-vR9nC", "List.Query.Invalid"),
|
||||
},
|
||||
// rest is tested in integration tests
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := administratorFilterToQuery(tt.args.filter, tt.args.level)
|
||||
assert.Equal(t, tt.want, got)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,10 @@ func NewMembershipIsIAMQuery() (SearchQuery, error) {
|
||||
return NewNotNullQuery(InstanceMemberIAMID)
|
||||
}
|
||||
|
||||
func NewMembershipRoleQuery(role string) (SearchQuery, error) {
|
||||
return NewTextQuery(membershipRoles, role, TextListContains)
|
||||
}
|
||||
|
||||
func (q *MembershipSearchQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||
query = q.SearchRequest.toQuery(query)
|
||||
for _, q := range q.Queries {
|
||||
|
||||
Reference in New Issue
Block a user