mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:57:31 +00:00
feat: list users scim v2 endpoint (#9187)
# Which Problems Are Solved - Adds support for the list users SCIM v2 endpoint # How the Problems Are Solved - Adds support for the list users SCIM v2 endpoints under `GET /scim/v2/{orgID}/Users` and `POST /scim/v2/{orgID}/Users/.search` # Additional Changes - adds a new function `SearchUserMetadataForUsers` to the query layer to query a metadata keyset for given user ids - adds a new function `NewUserMetadataExistsQuery` to the query layer to query a given metadata key value pair exists - adds a new function `CountUsers` to the query layer to count users without reading any rows - handle `ErrorAlreadyExists` as scim errors `uniqueness` - adds `NumberLessOrEqual` and `NumberGreaterOrEqual` query comparison methods - adds `BytesQuery` with `BytesEquals` and `BytesNotEquals` query comparison methods # Additional Context Part of #8140 Supported fields for scim filters: * `meta.created` * `meta.lastModified` * `id` * `username` * `name.familyName` * `name.givenName` * `emails` and `emails.value` * `active` only eq and ne * `externalId` only eq and ne
This commit is contained in:
@@ -24,6 +24,7 @@ type UserMetadataList struct {
|
||||
|
||||
type UserMetadata struct {
|
||||
CreationDate time.Time `json:"creation_date,omitempty"`
|
||||
UserID string `json:"-"`
|
||||
ChangeDate time.Time `json:"change_date,omitempty"`
|
||||
ResourceOwner string `json:"resource_owner,omitempty"`
|
||||
Sequence uint64 `json:"sequence,omitempty"`
|
||||
@@ -107,6 +108,38 @@ func (q *Queries) GetUserMetadataByKey(ctx context.Context, shouldTriggerBulk bo
|
||||
return metadata, err
|
||||
}
|
||||
|
||||
func (q *Queries) SearchUserMetadataForUsers(ctx context.Context, shouldTriggerBulk bool, userIDs []string, queries *UserMetadataSearchQueries) (metadata *UserMetadataList, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if shouldTriggerBulk {
|
||||
_, traceSpan := tracing.NewNamedSpan(ctx, "TriggerUserMetadataProjection")
|
||||
ctx, err = projection.UserMetadataProjection.Trigger(ctx, handler.WithAwaitRunning())
|
||||
logging.OnError(err).Debug("trigger failed")
|
||||
traceSpan.EndWithError(err)
|
||||
}
|
||||
|
||||
query, scan := prepareUserMetadataListQuery(ctx, q.client)
|
||||
eq := sq.Eq{
|
||||
UserMetadataUserIDCol.identifier(): userIDs,
|
||||
UserMetadataInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}
|
||||
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Egbgd", "Errors.Query.SQLStatment")
|
||||
}
|
||||
|
||||
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
|
||||
metadata, err = scan(rows)
|
||||
return err
|
||||
}, stmt, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata.State, err = q.latestState(ctx, userMetadataTable)
|
||||
return metadata, err
|
||||
}
|
||||
|
||||
func (q *Queries) SearchUserMetadata(ctx context.Context, shouldTriggerBulk bool, userID string, queries *UserMetadataSearchQueries, withOwnerRemoved bool) (metadata *UserMetadataList, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@@ -164,6 +197,44 @@ func NewUserMetadataKeySearchQuery(value string, comparison TextComparison) (Sea
|
||||
return NewTextQuery(UserMetadataKeyCol, value, comparison)
|
||||
}
|
||||
|
||||
func NewUserMetadataExistsQuery(key string, value []byte, keyComparison TextComparison, valueComparison BytesComparison) (SearchQuery, error) {
|
||||
// linking queries for the subselect
|
||||
instanceQuery, err := NewColumnComparisonQuery(UserMetadataInstanceIDCol, UserInstanceIDCol, ColumnEquals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userIDQuery, err := NewColumnComparisonQuery(UserMetadataUserIDCol, UserIDCol, ColumnEquals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// text query to select data from the linked sub select
|
||||
metadataKeyQuery, err := NewTextQuery(UserMetadataKeyCol, key, keyComparison)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// text query to select data from the linked sub select
|
||||
metadataValueQuery, err := NewBytesQuery(UserMetadataValueCol, value, valueComparison)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// full definition of the sub select
|
||||
subSelect, err := NewSubSelect(UserMetadataUserIDCol, []SearchQuery{instanceQuery, userIDQuery, metadataKeyQuery, metadataValueQuery})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// "WHERE * IN (*)" query with subquery as list-data provider
|
||||
return NewListQuery(
|
||||
UserIDCol,
|
||||
subSelect,
|
||||
ListIn,
|
||||
)
|
||||
}
|
||||
|
||||
func prepareUserMetadataQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*UserMetadata, error)) {
|
||||
return sq.Select(
|
||||
UserMetadataCreationDateCol.identifier(),
|
||||
@@ -200,6 +271,7 @@ func prepareUserMetadataListQuery(ctx context.Context, db prepareDatabase) (sq.S
|
||||
return sq.Select(
|
||||
UserMetadataCreationDateCol.identifier(),
|
||||
UserMetadataChangeDateCol.identifier(),
|
||||
UserMetadataUserIDCol.identifier(),
|
||||
UserMetadataResourceOwnerCol.identifier(),
|
||||
UserMetadataSequenceCol.identifier(),
|
||||
UserMetadataKeyCol.identifier(),
|
||||
@@ -215,6 +287,7 @@ func prepareUserMetadataListQuery(ctx context.Context, db prepareDatabase) (sq.S
|
||||
err := rows.Scan(
|
||||
&m.CreationDate,
|
||||
&m.ChangeDate,
|
||||
&m.UserID,
|
||||
&m.ResourceOwner,
|
||||
&m.Sequence,
|
||||
&m.Key,
|
||||
|
Reference in New Issue
Block a user