mirror of
https://github.com/zitadel/zitadel.git
synced 2025-10-29 21:46:05 +00:00
fix(api): sorting on list users endpoints (#10750)
# Which Problems Are Solved
#10415 added the possibility to filter users based on metadata. To
prevent duplicate results an sql `DISTINCT` was added. This resulted in
issues if the list was sorted on string columns like `username` or
`displayname`, since they are sorted using `lower`. Using `DISTINCT`
requires the `order by` column to be part of the `SELECT` statement.
# How the Problems Are Solved
Added the order by column to the statement.
# Additional Changes
None
# Additional Context
- relates to #10415
- backport to v4.x
---------
Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
(cherry picked from commit 2c0ee0008f)
This commit is contained in:
@@ -634,7 +634,7 @@ func (q *Queries) searchUsers(ctx context.Context, queries *UserSearchQueries, p
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
query, scan := prepareUsersQuery()
|
||||
query, scan := prepareUsersQuery(queries.SortingColumn)
|
||||
query = userPermissionCheckV2(ctx, query, permissionCheckV2, queries.Queries)
|
||||
stmt, args, err := queries.toQuery(query).Where(sq.Eq{
|
||||
UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
@@ -1270,7 +1270,7 @@ func prepareUserUniqueQuery() (sq.SelectBuilder, func(*sql.Row) (bool, error)) {
|
||||
}
|
||||
}
|
||||
|
||||
func prepareUsersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
func prepareUsersQuery(orderBy Column) (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
return sq.Select(
|
||||
UserIDCol.identifier(),
|
||||
UserCreationDateCol.identifier(),
|
||||
@@ -1302,6 +1302,7 @@ func prepareUsersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
MachineDescriptionCol.identifier(),
|
||||
MachineSecretCol.identifier(),
|
||||
MachineAccessTokenTypeCol.identifier(),
|
||||
orderBy.orderBy(),
|
||||
countColumn.identifier()).
|
||||
Distinct().
|
||||
From(userTable.identifier()).
|
||||
@@ -1319,6 +1320,7 @@ func prepareUsersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
preferredLoginName := sql.NullString{}
|
||||
|
||||
human, machine := sqlHuman{}, sqlMachine{}
|
||||
var orderByValue any
|
||||
|
||||
err := rows.Scan(
|
||||
&u.ID,
|
||||
@@ -1354,6 +1356,7 @@ func prepareUsersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
&machine.encodedSecret,
|
||||
&machine.accessTokenType,
|
||||
|
||||
&orderByValue,
|
||||
&count,
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
@@ -396,6 +397,7 @@ var (
|
||||
` projections.users14_machines.description,` +
|
||||
` projections.users14_machines.secret,` +
|
||||
` projections.users14_machines.access_token_type,` +
|
||||
` projections.users14.id,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.users14` +
|
||||
` LEFT JOIN projections.users14_humans ON projections.users14.id = projections.users14_humans.user_id AND projections.users14.instance_id = projections.users14_humans.instance_id` +
|
||||
@@ -435,6 +437,7 @@ var (
|
||||
"description",
|
||||
"secret",
|
||||
"access_token_type",
|
||||
"id",
|
||||
"count",
|
||||
}
|
||||
countUsersQuery = "SELECT COUNT(*) OVER () FROM projections.users14"
|
||||
@@ -944,8 +947,10 @@ func Test_UserPrepares(t *testing.T) {
|
||||
object: (*NotifyUser)(nil),
|
||||
},
|
||||
{
|
||||
name: "prepareUsersQuery no result",
|
||||
prepare: prepareUsersQuery,
|
||||
name: "prepareUsersQuery no result",
|
||||
prepare: func() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
return prepareUsersQuery(UserIDCol)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQuery(
|
||||
regexp.QuoteMeta(usersQuery),
|
||||
@@ -962,8 +967,10 @@ func Test_UserPrepares(t *testing.T) {
|
||||
object: &Users{Users: []*User{}},
|
||||
},
|
||||
{
|
||||
name: "prepareUsersQuery one result",
|
||||
prepare: prepareUsersQuery,
|
||||
name: "prepareUsersQuery one result",
|
||||
prepare: func() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
return prepareUsersQuery(UserIDCol)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
regexp.QuoteMeta(usersQuery),
|
||||
@@ -1002,6 +1009,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
"id", // orderBy col
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -1043,8 +1051,10 @@ func Test_UserPrepares(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareUsersQuery multiple results",
|
||||
prepare: prepareUsersQuery,
|
||||
name: "prepareUsersQuery multiple results",
|
||||
prepare: func() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
return prepareUsersQuery(UserIDCol)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
regexp.QuoteMeta(usersQuery),
|
||||
@@ -1083,6 +1093,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
"id", // orderBy col
|
||||
},
|
||||
{
|
||||
"id",
|
||||
@@ -1117,6 +1128,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
"description",
|
||||
"secret",
|
||||
domain.OIDCTokenTypeBearer,
|
||||
"id", // orderBy col
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -1176,8 +1188,10 @@ func Test_UserPrepares(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareUsersQuery sql err",
|
||||
prepare: prepareUsersQuery,
|
||||
name: "prepareUsersQuery sql err",
|
||||
prepare: func() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
return prepareUsersQuery(UserIDCol)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
regexp.QuoteMeta(usersQuery),
|
||||
|
||||
Reference in New Issue
Block a user