mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 23:27:23 +00:00
fix: scim 2 filter: the username should be treated case-insensitive (#9257)
# Which Problems Are Solved - when listing users via scim v2.0 filters applied to the username are applied case-sensitive # How the Problems Are Solved - when a query filter is appleid on the username it is applied case-insensitive # Additional Context Part of https://github.com/zitadel/zitadel/issues/8140
This commit is contained in:
parent
b10428fb56
commit
accfb7525a
@ -33,6 +33,7 @@ type MappedQueryBuilderFunc func(ctx context.Context, compareValue *CompValue, o
|
||||
type QueryFieldInfo struct {
|
||||
Column query.Column
|
||||
FieldType FieldType
|
||||
CaseInsensitive bool
|
||||
BuildMappedQuery MappedQueryBuilderFunc
|
||||
}
|
||||
|
||||
@ -290,19 +291,36 @@ func (b *queryBuilder) buildTextQuery(field *QueryFieldInfo, right CompValue, op
|
||||
}
|
||||
|
||||
var comp query.TextComparison
|
||||
switch {
|
||||
case op.Equal:
|
||||
comp = query.TextEquals
|
||||
case op.NotEqual:
|
||||
comp = query.TextNotEquals
|
||||
case op.Contains:
|
||||
comp = query.TextContains
|
||||
case op.StartsWith:
|
||||
comp = query.TextStartsWith
|
||||
case op.EndsWith:
|
||||
comp = query.TextEndsWith
|
||||
default:
|
||||
return nil, serrors.ThrowInvalidFilter(zerrors.ThrowInvalidArgument(nil, "SCIM-FF425", "Invalid filter expression: unsupported comparison operator for text fields"))
|
||||
if field.CaseInsensitive {
|
||||
switch {
|
||||
case op.Equal:
|
||||
comp = query.TextEqualsIgnoreCase
|
||||
case op.NotEqual:
|
||||
comp = query.TextNotEqualsIgnoreCase
|
||||
case op.Contains:
|
||||
comp = query.TextContainsIgnoreCase
|
||||
case op.StartsWith:
|
||||
comp = query.TextStartsWithIgnoreCase
|
||||
case op.EndsWith:
|
||||
comp = query.TextEndsWithIgnoreCase
|
||||
default:
|
||||
return nil, serrors.ThrowInvalidFilter(zerrors.ThrowInvalidArgument(nil, "SCIM-FF529", "Invalid filter expression: unsupported comparison operator for text fields"))
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case op.Equal:
|
||||
comp = query.TextEquals
|
||||
case op.NotEqual:
|
||||
comp = query.TextNotEquals
|
||||
case op.Contains:
|
||||
comp = query.TextContains
|
||||
case op.StartsWith:
|
||||
comp = query.TextStartsWith
|
||||
case op.EndsWith:
|
||||
comp = query.TextEndsWith
|
||||
default:
|
||||
return nil, serrors.ThrowInvalidFilter(zerrors.ThrowInvalidArgument(nil, "SCIM-FF425", "Invalid filter expression: unsupported comparison operator for text fields"))
|
||||
}
|
||||
}
|
||||
|
||||
return query.NewTextQuery(field.Column, *right.StringValue, comp)
|
||||
|
@ -20,10 +20,11 @@ var fieldPathColumnMapping = FieldPathMapping{
|
||||
Column: query.UserChangeDateCol,
|
||||
FieldType: FieldTypeTimestamp,
|
||||
},
|
||||
// a string field
|
||||
// a case-insensitive string field
|
||||
"username": {
|
||||
Column: query.UserUsernameCol,
|
||||
FieldType: FieldTypeString,
|
||||
Column: query.UserUsernameCol,
|
||||
FieldType: FieldTypeString,
|
||||
CaseInsensitive: true,
|
||||
},
|
||||
// a nested string field
|
||||
"name.familyname": {
|
||||
@ -76,7 +77,7 @@ func TestFilter_BuildQuery(t *testing.T) {
|
||||
{
|
||||
name: "simple binary operator",
|
||||
filter: `userName eq "bjensen"`,
|
||||
want: test.Must(query.NewTextQuery(query.UserUsernameCol, "bjensen", query.TextEquals)),
|
||||
want: test.Must(query.NewTextQuery(query.UserUsernameCol, "bjensen", query.TextEqualsIgnoreCase)),
|
||||
},
|
||||
{
|
||||
name: "binary operator equals null",
|
||||
@ -176,7 +177,7 @@ func TestFilter_BuildQuery(t *testing.T) {
|
||||
{
|
||||
name: "urn prefixed binary operator",
|
||||
filter: `urn:ietf:params:scim:schemas:core:2.0:User:userName sw "J"`,
|
||||
want: test.Must(query.NewTextQuery(query.UserUsernameCol, "J", query.TextStartsWith)),
|
||||
want: test.Must(query.NewTextQuery(query.UserUsernameCol, "J", query.TextStartsWithIgnoreCase)),
|
||||
},
|
||||
{
|
||||
name: "urn prefixed nested binary operator",
|
||||
@ -196,7 +197,7 @@ func TestFilter_BuildQuery(t *testing.T) {
|
||||
{
|
||||
name: "and logical expression",
|
||||
filter: `name.familyName pr and userName eq "bjensen"`,
|
||||
want: test.Must(query.NewAndQuery(test.Must(query.NewNotNullQuery(query.HumanLastNameCol)), test.Must(query.NewTextQuery(query.UserUsernameCol, "bjensen", query.TextEquals)))),
|
||||
want: test.Must(query.NewAndQuery(test.Must(query.NewNotNullQuery(query.HumanLastNameCol)), test.Must(query.NewTextQuery(query.UserUsernameCol, "bjensen", query.TextEqualsIgnoreCase)))),
|
||||
},
|
||||
{
|
||||
name: "timestamp condition equal",
|
||||
@ -243,7 +244,7 @@ func TestFilter_BuildQuery(t *testing.T) {
|
||||
filter: `userName eq "rudolpho" and emails co "example.com" or emails.value co "example2.org"`,
|
||||
want: test.Must(query.NewOrQuery(
|
||||
test.Must(query.NewAndQuery(
|
||||
test.Must(query.NewTextQuery(query.UserUsernameCol, "rudolpho", query.TextEquals)),
|
||||
test.Must(query.NewTextQuery(query.UserUsernameCol, "rudolpho", query.TextEqualsIgnoreCase)),
|
||||
test.Must(query.NewTextQuery(query.HumanEmailCol, "example.com", query.TextContains))),
|
||||
),
|
||||
test.Must(query.NewTextQuery(query.HumanEmailCol, "example2.org", query.TextContains)))),
|
||||
@ -252,7 +253,7 @@ func TestFilter_BuildQuery(t *testing.T) {
|
||||
name: "nested and / or with grouping",
|
||||
filter: `userName ne "rudolpho" and (emails co "example.com" or emails.value co "example.org")`,
|
||||
want: test.Must(query.NewAndQuery(
|
||||
test.Must(query.NewTextQuery(query.UserUsernameCol, "rudolpho", query.TextNotEquals)),
|
||||
test.Must(query.NewTextQuery(query.UserUsernameCol, "rudolpho", query.TextNotEqualsIgnoreCase)),
|
||||
test.Must(query.NewOrQuery(
|
||||
test.Must(query.NewTextQuery(query.HumanEmailCol, "example.com", query.TextContains)),
|
||||
test.Must(query.NewTextQuery(query.HumanEmailCol, "example.org", query.TextContains)),
|
||||
@ -263,7 +264,7 @@ func TestFilter_BuildQuery(t *testing.T) {
|
||||
name: "nested value path path",
|
||||
filter: `userName eq "Hans" and emails[value ew "@example.org" or value ew "@example.com"]`,
|
||||
want: test.Must(query.NewAndQuery(
|
||||
test.Must(query.NewTextQuery(query.UserUsernameCol, "Hans", query.TextEquals)),
|
||||
test.Must(query.NewTextQuery(query.UserUsernameCol, "Hans", query.TextEqualsIgnoreCase)),
|
||||
test.Must(query.NewOrQuery(
|
||||
test.Must(query.NewTextQuery(query.HumanEmailCol, "@example.org", query.TextEndsWith)),
|
||||
test.Must(query.NewTextQuery(query.HumanEmailCol, "@example.com", query.TextEndsWith)),
|
||||
@ -288,13 +289,13 @@ func TestFilter_BuildQuery(t *testing.T) {
|
||||
want: test.Must(query.NewAndQuery(
|
||||
test.Must(query.NewTextQuery(query.HumanEmailCol, "@example.com", query.TextEndsWith)),
|
||||
test.Must(query.NewTextQuery(query.HumanLastNameCol, "hans", query.TextContains)),
|
||||
test.Must(query.NewTextQuery(query.UserUsernameCol, "peter", query.TextContains)),
|
||||
test.Must(query.NewTextQuery(query.UserUsernameCol, "peter", query.TextContainsIgnoreCase)),
|
||||
)),
|
||||
},
|
||||
{
|
||||
name: "negation",
|
||||
filter: `not(username eq "foo")`,
|
||||
want: test.Must(query.NewNotQuery(test.Must(query.NewTextQuery(query.UserUsernameCol, "foo", query.TextEquals)))),
|
||||
want: test.Must(query.NewNotQuery(test.Must(query.NewTextQuery(query.UserUsernameCol, "foo", query.TextEqualsIgnoreCase)))),
|
||||
},
|
||||
{
|
||||
name: "negation with complex filter",
|
||||
|
@ -29,8 +29,9 @@ var fieldPathColumnMapping = filter.FieldPathMapping{
|
||||
FieldType: filter.FieldTypeString,
|
||||
},
|
||||
"username": {
|
||||
Column: query.UserUsernameCol,
|
||||
FieldType: filter.FieldTypeString,
|
||||
Column: query.UserUsernameCol,
|
||||
FieldType: filter.FieldTypeString,
|
||||
CaseInsensitive: true,
|
||||
},
|
||||
"name.familyname": {
|
||||
Column: query.HumanLastNameCol,
|
||||
|
@ -288,6 +288,7 @@ func NewTextQuery(col Column, value string, compare TextComparison) (*textQuery,
|
||||
// handle the comparisons which use (i)like and therefore need to escape potential wildcards in the value
|
||||
switch compare {
|
||||
case TextEqualsIgnoreCase,
|
||||
TextNotEqualsIgnoreCase,
|
||||
TextStartsWith,
|
||||
TextStartsWithIgnoreCase,
|
||||
TextEndsWith,
|
||||
@ -334,6 +335,8 @@ func (q *textQuery) comp() sq.Sqlizer {
|
||||
return sq.NotEq{q.Column.identifier(): q.Text}
|
||||
case TextEqualsIgnoreCase:
|
||||
return sq.ILike{q.Column.identifier(): q.Text}
|
||||
case TextNotEqualsIgnoreCase:
|
||||
return sq.NotILike{q.Column.identifier(): q.Text}
|
||||
case TextStartsWith:
|
||||
return sq.Like{q.Column.identifier(): q.Text + "%"}
|
||||
case TextStartsWithIgnoreCase:
|
||||
@ -368,6 +371,7 @@ const (
|
||||
TextContainsIgnoreCase
|
||||
TextListContains
|
||||
TextNotEquals
|
||||
TextNotEqualsIgnoreCase
|
||||
|
||||
textCompareMax
|
||||
)
|
||||
|
@ -905,6 +905,19 @@ func TestNewTextQuery(t *testing.T) {
|
||||
Compare: TextNotEquals,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not equal ignore case",
|
||||
args: args{
|
||||
column: testCol,
|
||||
value: "h_urst%",
|
||||
compare: TextNotEqualsIgnoreCase,
|
||||
},
|
||||
want: &textQuery{
|
||||
Column: testCol,
|
||||
Text: "h\\_urst\\%",
|
||||
Compare: TextNotEqualsIgnoreCase,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "starts with",
|
||||
args: args{
|
||||
@ -1194,6 +1207,28 @@ func TestTextQuery_comp(t *testing.T) {
|
||||
query: sq.ILike{"test_table.test_col": "Hurst"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not equals",
|
||||
fields: fields{
|
||||
Column: testCol,
|
||||
Text: "Hurst",
|
||||
Compare: TextNotEquals,
|
||||
},
|
||||
want: want{
|
||||
query: sq.NotEq{"test_table.test_col": "Hurst"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not equals ignore case",
|
||||
fields: fields{
|
||||
Column: testCol,
|
||||
Text: "Hurst",
|
||||
Compare: TextNotEqualsIgnoreCase,
|
||||
},
|
||||
want: want{
|
||||
query: sq.NotILike{"test_table.test_col": "Hurst"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "equals ignore case wildcard",
|
||||
fields: fields{
|
||||
|
Loading…
x
Reference in New Issue
Block a user