mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 01:27:32 +00:00
feat: bulk scim v2 endpoint (#9256)
# Which Problems Are Solved * Adds support for the bulk SCIM v2 endpoint # How the Problems Are Solved * Adds support for the bulk SCIM v2 endpoint under `POST /scim/v2/{orgID}/Bulk` # Additional Context Part of #8140 Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
This commit is contained in:
698
internal/api/scim/integration_test/bulk_test.go
Normal file
698
internal/api/scim/integration_test/bulk_test.go
Normal file
@@ -0,0 +1,698 @@
|
||||
//go:build integration
|
||||
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/scim/resources"
|
||||
"github.com/zitadel/zitadel/internal/api/scim/schemas"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/internal/integration/scim"
|
||||
"github.com/zitadel/zitadel/internal/test"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed testdata/bulk_test_full.json
|
||||
bulkFullJson []byte
|
||||
|
||||
//go:embed testdata/bulk_test_fail_on_errors.json
|
||||
bulkFailOnErrorsJson []byte
|
||||
|
||||
//go:embed testdata/bulk_test_errors.json
|
||||
bulkErrorsFullJson []byte
|
||||
|
||||
bulkTooManyOperationsJson []byte
|
||||
)
|
||||
|
||||
func init() {
|
||||
bulkFullJson = removeComments(bulkFullJson)
|
||||
bulkFailOnErrorsJson = removeComments(bulkFailOnErrorsJson)
|
||||
bulkErrorsFullJson = removeComments(bulkErrorsFullJson)
|
||||
bulkTooManyOperationsJson = test.Must(json.Marshal(buildTooManyOperationsRequest()))
|
||||
}
|
||||
|
||||
func TestBulk(t *testing.T) {
|
||||
iamOwnerCtx := Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
secondaryOrg := Instance.CreateOrganization(iamOwnerCtx, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
createdSecondaryOrgUser := createHumanUser(t, iamOwnerCtx, secondaryOrg.OrganizationId, 0)
|
||||
bulkMinimalUpdateSecondaryOrgJson := test.Must(json.Marshal(buildMinimalUpdateRequest(createdSecondaryOrgUser.UserId)))
|
||||
|
||||
membershipNotFoundErr := &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
Detail: "membership not found",
|
||||
Status: "404",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "AUTHZ-cdgFk",
|
||||
Message: "membership not found",
|
||||
},
|
||||
}
|
||||
|
||||
type wantErr struct {
|
||||
scimErrorType string
|
||||
status int
|
||||
zitadelErrID string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
body []byte
|
||||
ctx context.Context
|
||||
orgID string
|
||||
want *scim.BulkResponse
|
||||
wantErr *wantErr
|
||||
wantUsers map[string]*resources.ScimUser
|
||||
}{
|
||||
{
|
||||
name: "not authenticated",
|
||||
body: bulkFullJson,
|
||||
ctx: context.Background(),
|
||||
wantErr: &wantErr{
|
||||
status: http.StatusUnauthorized,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no permissions",
|
||||
body: bulkFullJson,
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||
want: &scim.BulkResponse{
|
||||
Schemas: []schemas.ScimSchemaType{schemas.IdBulkResponse},
|
||||
Operations: []*scim.BulkResponseOperation{
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Response: membershipNotFoundErr,
|
||||
Status: "404",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
BulkID: "1",
|
||||
Response: membershipNotFoundErr,
|
||||
Status: "404",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPatch,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
Detail: "Could not resolve bulkID 1 to created ID",
|
||||
Status: "400",
|
||||
ScimType: "invalidValue",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "SCIM-BLK4",
|
||||
Message: "Could not resolve bulkID 1 to created ID",
|
||||
},
|
||||
},
|
||||
Status: "400",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
BulkID: "2",
|
||||
Response: membershipNotFoundErr,
|
||||
Status: "404",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPut,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
Detail: "Could not resolve bulkID 2 to created ID",
|
||||
Status: "400",
|
||||
ScimType: "invalidValue",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "SCIM-BLK4",
|
||||
Message: "Could not resolve bulkID 2 to created ID",
|
||||
},
|
||||
},
|
||||
Status: "400",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
BulkID: "3",
|
||||
Response: membershipNotFoundErr,
|
||||
Status: "404",
|
||||
},
|
||||
{
|
||||
Method: http.MethodDelete,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
Detail: "Could not resolve bulkID 3 to created ID",
|
||||
Status: "400",
|
||||
ScimType: "invalidValue",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "SCIM-BLK4",
|
||||
Message: "Could not resolve bulkID 3 to created ID",
|
||||
},
|
||||
},
|
||||
Status: "400",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPatch,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
Detail: "User could not be found",
|
||||
Status: "404",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "COMMAND-ugjs0upun6",
|
||||
Message: "Errors.User.NotFound",
|
||||
},
|
||||
},
|
||||
Status: "404",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPatch,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
Detail: "Could not resolve bulkID 99 to created ID",
|
||||
Status: "400",
|
||||
ScimType: "invalidValue",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "SCIM-BLK4",
|
||||
Message: "Could not resolve bulkID 99 to created ID",
|
||||
},
|
||||
},
|
||||
Status: "400",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "full",
|
||||
body: bulkFullJson,
|
||||
want: &scim.BulkResponse{
|
||||
Schemas: []schemas.ScimSchemaType{schemas.IdBulkResponse},
|
||||
Operations: []*scim.BulkResponseOperation{
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Status: "201",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
BulkID: "1",
|
||||
Status: "201",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPatch,
|
||||
Status: "204",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
BulkID: "2",
|
||||
Status: "201",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPut,
|
||||
Status: "200",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
BulkID: "3",
|
||||
Status: "201",
|
||||
},
|
||||
{
|
||||
Method: http.MethodDelete,
|
||||
Status: "204",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPatch,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
Detail: "User could not be found",
|
||||
Status: "404",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "COMMAND-ugjs0upun6",
|
||||
Message: "Errors.User.NotFound",
|
||||
},
|
||||
},
|
||||
Status: "404",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPatch,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
Detail: "Could not resolve bulkID 99 to created ID",
|
||||
Status: "400",
|
||||
ScimType: "invalidValue",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "SCIM-BLK4",
|
||||
Message: "Could not resolve bulkID 99 to created ID",
|
||||
},
|
||||
},
|
||||
Status: "400",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantUsers: map[string]*resources.ScimUser{
|
||||
"scim-bulk-created-user-0": {
|
||||
ExternalID: "scim-bulk-created-user-0",
|
||||
UserName: "scim-bulk-created-user-0",
|
||||
Name: &resources.ScimUserName{
|
||||
Formatted: "scim-bulk-created-user-0-given-name scim-bulk-created-user-0-family-name",
|
||||
FamilyName: "scim-bulk-created-user-0-family-name",
|
||||
GivenName: "scim-bulk-created-user-0-given-name",
|
||||
},
|
||||
DisplayName: "scim-bulk-created-user-0-given-name scim-bulk-created-user-0-family-name",
|
||||
PreferredLanguage: test.Must(language.Parse("en")),
|
||||
Active: gu.Ptr(true),
|
||||
Emails: []*resources.ScimEmail{
|
||||
{
|
||||
Value: "scim-bulk-created-user-0@example.com",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"scim-bulk-created-user-1": {
|
||||
ExternalID: "scim-bulk-created-user-1",
|
||||
UserName: "scim-bulk-created-user-1",
|
||||
Name: &resources.ScimUserName{
|
||||
Formatted: "scim-bulk-created-user-1-given-name scim-bulk-created-user-1-family-name",
|
||||
FamilyName: "scim-bulk-created-user-1-family-name",
|
||||
GivenName: "scim-bulk-created-user-1-given-name",
|
||||
},
|
||||
DisplayName: "scim-bulk-created-user-1-given-name scim-bulk-created-user-1-family-name",
|
||||
NickName: "scim-bulk-created-user-1-nickname-patched",
|
||||
PreferredLanguage: test.Must(language.Parse("en")),
|
||||
Active: gu.Ptr(true),
|
||||
Emails: []*resources.ScimEmail{
|
||||
{
|
||||
Value: "scim-bulk-created-user-1@example.com",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
PhoneNumbers: []*resources.ScimPhoneNumber{
|
||||
{
|
||||
Value: "+41711231212",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"scim-bulk-created-user-2": {
|
||||
ExternalID: "scim-bulk-created-user-2",
|
||||
UserName: "scim-bulk-created-user-2",
|
||||
Name: &resources.ScimUserName{
|
||||
Formatted: "scim-bulk-created-user-2-given-name scim-bulk-created-user-2-family-name",
|
||||
FamilyName: "scim-bulk-created-user-2-family-name",
|
||||
GivenName: "scim-bulk-created-user-2-given-name",
|
||||
},
|
||||
DisplayName: "scim-bulk-created-user-2-given-name scim-bulk-created-user-2-family-name",
|
||||
NickName: "scim-bulk-created-user-2-nickname-patched",
|
||||
PreferredLanguage: test.Must(language.Parse("en")),
|
||||
Active: gu.Ptr(true),
|
||||
Emails: []*resources.ScimEmail{
|
||||
{
|
||||
Value: "scim-bulk-created-user-2@example.com",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
PhoneNumbers: []*resources.ScimPhoneNumber{
|
||||
{
|
||||
Value: "+41711231212",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "errors",
|
||||
body: bulkErrorsFullJson,
|
||||
want: &scim.BulkResponse{
|
||||
Schemas: []schemas.ScimSchemaType{schemas.IdBulkResponse},
|
||||
Operations: []*scim.BulkResponseOperation{
|
||||
{
|
||||
Method: http.MethodPatch,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
Detail: "User could not be found",
|
||||
Status: "404",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "COMMAND-ugjs0upun6",
|
||||
Message: "Errors.User.NotFound",
|
||||
},
|
||||
},
|
||||
Status: "404",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
ScimType: "invalidValue",
|
||||
Detail: "Email is empty",
|
||||
Status: "400",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "SCIM-EM19",
|
||||
Message: "Errors.User.Email.Empty",
|
||||
},
|
||||
},
|
||||
Status: "400",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
ScimType: "invalidValue",
|
||||
Detail: "Could not parse locale",
|
||||
Status: "400",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "SCIM-MD11",
|
||||
Message: "Could not parse locale",
|
||||
},
|
||||
},
|
||||
Status: "400",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
ScimType: "invalidValue",
|
||||
Detail: "Password is too short",
|
||||
Status: "400",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "COMMA-HuJf6",
|
||||
Message: "Errors.User.PasswordComplexityPolicy.MinLength",
|
||||
},
|
||||
},
|
||||
Status: "400",
|
||||
},
|
||||
{
|
||||
Method: "POST",
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
ScimType: "invalidValue",
|
||||
Detail: "Could not parse timezone",
|
||||
Status: "400",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "SCIM-MD12",
|
||||
Message: "Could not parse timezone",
|
||||
},
|
||||
},
|
||||
Status: "400",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
ScimType: "invalidValue",
|
||||
Detail: "Errors.Invalid.Argument",
|
||||
Status: "400",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "V2-zzad3",
|
||||
Message: "Errors.Invalid.Argument",
|
||||
},
|
||||
},
|
||||
Status: "400",
|
||||
},
|
||||
{
|
||||
Method: "POST",
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
ScimType: "invalidValue",
|
||||
Detail: "Given name in profile is empty",
|
||||
Status: "400",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "USER-UCej2",
|
||||
Message: "Errors.User.Profile.FirstNameEmpty",
|
||||
},
|
||||
},
|
||||
Status: "400",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fail on errors",
|
||||
body: bulkFailOnErrorsJson,
|
||||
want: &scim.BulkResponse{
|
||||
Schemas: []schemas.ScimSchemaType{schemas.IdBulkResponse},
|
||||
Operations: []*scim.BulkResponseOperation{
|
||||
{
|
||||
Method: http.MethodPatch,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
Detail: "User could not be found",
|
||||
Status: "404",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "COMMAND-ugjs0upun6",
|
||||
Message: "Errors.User.NotFound",
|
||||
},
|
||||
},
|
||||
Status: "404",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Status: "201",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
ScimType: "invalidValue",
|
||||
Detail: "Email is empty",
|
||||
Status: "400",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "SCIM-EM19",
|
||||
Message: "Errors.User.Email.Empty",
|
||||
},
|
||||
},
|
||||
Status: "400",
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Response: &scim.ScimError{
|
||||
Schemas: []string{
|
||||
"urn:ietf:params:scim:api:messages:2.0:Error",
|
||||
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail",
|
||||
},
|
||||
ScimType: "invalidValue",
|
||||
Detail: "Could not parse locale",
|
||||
Status: "400",
|
||||
ZitadelDetail: &scim.ZitadelErrorDetail{
|
||||
ID: "SCIM-MD11",
|
||||
Message: "Could not parse locale",
|
||||
},
|
||||
},
|
||||
Status: "400",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "too many operations",
|
||||
body: bulkTooManyOperationsJson,
|
||||
wantErr: &wantErr{
|
||||
status: http.StatusRequestEntityTooLarge,
|
||||
scimErrorType: "invalidValue",
|
||||
zitadelErrID: "SCIM-BLK19",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "another organization",
|
||||
body: bulkMinimalUpdateSecondaryOrgJson,
|
||||
orgID: secondaryOrg.OrganizationId,
|
||||
want: &scim.BulkResponse{
|
||||
Schemas: []schemas.ScimSchemaType{schemas.IdBulkResponse},
|
||||
Operations: []*scim.BulkResponseOperation{
|
||||
{
|
||||
Method: http.MethodPatch,
|
||||
Response: membershipNotFoundErr,
|
||||
Status: "404",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := tt.ctx
|
||||
if ctx == nil {
|
||||
ctx = CTX
|
||||
}
|
||||
|
||||
orgID := tt.orgID
|
||||
if orgID == "" {
|
||||
orgID = Instance.DefaultOrg.Id
|
||||
}
|
||||
|
||||
response, err := Instance.Client.SCIM.Bulk(ctx, orgID, tt.body)
|
||||
createdUserIDs := buildCreatedIDs(response)
|
||||
defer deleteUsers(t, createdUserIDs)
|
||||
|
||||
if tt.wantErr != nil {
|
||||
statusCode := tt.wantErr.status
|
||||
if statusCode == 0 {
|
||||
statusCode = http.StatusBadRequest
|
||||
}
|
||||
|
||||
scimErr := scim.RequireScimError(t, statusCode, err)
|
||||
assert.Equal(t, tt.wantErr.scimErrorType, scimErr.Error.ScimType)
|
||||
|
||||
if tt.wantErr.zitadelErrID != "" {
|
||||
assert.Equal(t, tt.wantErr.zitadelErrID, scimErr.Error.ZitadelDetail.ID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, []schemas.ScimSchemaType{schemas.IdBulkResponse}, response.Schemas)
|
||||
|
||||
locationPrefix := "http://" + Instance.Host() + path.Join(schemas.HandlerPrefix, orgID, "Users") + "/"
|
||||
for _, responseOperation := range response.Operations {
|
||||
// POST operations which result in an error don't expect a location
|
||||
if responseOperation.Method == http.MethodPost && responseOperation.Response != nil {
|
||||
require.Empty(t, responseOperation.Location)
|
||||
} else {
|
||||
require.True(t, strings.HasPrefix(responseOperation.Location, locationPrefix))
|
||||
}
|
||||
|
||||
// don't assert the location in the deep equal
|
||||
responseOperation.Location = ""
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.want, response) {
|
||||
x := test.Must(json.Marshal(tt.want))
|
||||
x2 := test.Must(json.Marshal(response))
|
||||
t.Errorf("want: %v, got: %v", x, x2)
|
||||
t.Errorf("want: %#v, got: %#v", tt.want, response)
|
||||
}
|
||||
|
||||
if tt.wantUsers != nil {
|
||||
for _, createdUserID := range createdUserIDs {
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
user, err := Instance.Client.SCIM.Users.Get(ctx, orgID, createdUserID)
|
||||
if err != nil {
|
||||
scim.RequireScimError(ttt, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
wantUser, ok := tt.wantUsers[user.UserName]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if !test.PartiallyDeepEqual(wantUser, user) {
|
||||
ttt.Errorf("want: %#v, got: %#v", wantUser, user)
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func buildCreatedIDs(response *scim.BulkResponse) []string {
|
||||
createdIds := make([]string, 0, len(response.Operations))
|
||||
for _, operation := range response.Operations {
|
||||
if operation.Method == http.MethodPost && operation.Status == "201" {
|
||||
parts := strings.Split(operation.Location, "/")
|
||||
createdIds = append(createdIds, parts[len(parts)-1])
|
||||
}
|
||||
}
|
||||
|
||||
return createdIds
|
||||
}
|
||||
|
||||
func deleteUsers(t require.TestingT, ids []string) {
|
||||
for _, id := range ids {
|
||||
err := Instance.Client.SCIM.Users.Delete(CTX, Instance.DefaultOrg.Id, id)
|
||||
|
||||
// only not found errors are ok (if the user is deleted in a later on bulk request)
|
||||
if err != nil {
|
||||
scim.RequireScimError(t, http.StatusNotFound, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildMinimalUpdateRequest(userID string) *scim.BulkRequest {
|
||||
return &scim.BulkRequest{
|
||||
Schemas: []schemas.ScimSchemaType{schemas.IdBulkRequest},
|
||||
Operations: []*scim.BulkRequestOperation{
|
||||
{
|
||||
Method: http.MethodPatch,
|
||||
Path: "/Users/" + userID,
|
||||
Data: simpleReplacePatchBody("nickname", `"foo-bar-nickname"`),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildTooManyOperationsRequest() *scim.BulkRequest {
|
||||
req := &scim.BulkRequest{
|
||||
Schemas: []schemas.ScimSchemaType{schemas.IdBulkRequest},
|
||||
Operations: make([]*scim.BulkRequestOperation, 101), // default config (100) + 1, see defaults.yaml
|
||||
}
|
||||
|
||||
for i := 0; i < len(req.Operations); i++ {
|
||||
req.Operations[i] = &scim.BulkRequestOperation{
|
||||
Method: http.MethodPost,
|
||||
Path: "/Users",
|
||||
Data: minimalUserJson,
|
||||
}
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
Reference in New Issue
Block a user