From a2177c36b65a159478e7598344a12bdfe0683671 Mon Sep 17 00:00:00 2001 From: Jonas Hungershausen Date: Tue, 14 Feb 2023 12:52:51 +0100 Subject: [PATCH 1/2] fix: correctly apply patches to identity metadata --- identity/handler.go | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/identity/handler.go b/identity/handler.go index 3b7acf6e6f90..741474ccaa67 100644 --- a/identity/handler.go +++ b/identity/handler.go @@ -664,37 +664,53 @@ func (h *Handler) patch(w http.ResponseWriter, r *http.Request, ps httprouter.Pa credentials := identity.Credentials oldState := identity.State - if err := jsonx.ApplyJSONPatch(requestBody, identity, "/id", "/stateChangedAt", "/credentials"); err != nil { - h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("%s", err).WithWrap(err))) + patchedIdentity := WithAdminMetadataInJSON(*identity) + + if err := jsonx.ApplyJSONPatch(requestBody, &patchedIdentity, "/id", "/stateChangedAt", "/credentials"); err != nil { + h.r.Writer().WriteError(w, r, errors.WithStack( + herodot. + ErrBadRequest. + WithReasonf("An error occured when applying the JSON patch"). + WithErrorf("%v", err). + WithWrap(err), + )) return } // See https://github.com/ory/cloud/issues/148 // The apply patch operation overrides the credentials with an empty map. - identity.Credentials = credentials + patchedIdentity.Credentials = credentials - if oldState != identity.State { + if oldState != patchedIdentity.State { // Check if the changed state was actually valid - if err := identity.State.IsValid(); err != nil { - h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("%s", err).WithWrap(err))) + if err := patchedIdentity.State.IsValid(); err != nil { + h.r.Writer().WriteError(w, r, errors.WithStack( + herodot. + ErrBadRequest. + WithReasonf("The supplied state ('%s') was not valid. Valid states are ('%s', '%s').", string(patchedIdentity.State), StateActive, StateInactive). + WithErrorf("%v", err). + WithWrap(err), + )) return } // If the state changed, we need to update the timestamp of it stateChangedAt := sqlxx.NullTime(time.Now()) - identity.StateChangedAt = &stateChangedAt + patchedIdentity.StateChangedAt = &stateChangedAt } + updatedIdenty := Identity(patchedIdentity) + if err := h.r.IdentityManager().Update( r.Context(), - identity, + &updatedIdenty, ManagerAllowWriteProtectedTraits, ); err != nil { h.r.Writer().WriteError(w, r, err) return } - h.r.Writer().Write(w, r, WithCredentialsMetadataAndAdminMetadataInJSON(*identity)) + h.r.Writer().Write(w, r, WithCredentialsMetadataAndAdminMetadataInJSON(updatedIdenty)) } func deletCredentialWebAuthFromIdentity(identity *Identity) (*Identity, error) { From 2fb656550eb75916a8d4fc6010002b50e7b92af6 Mon Sep 17 00:00:00 2001 From: Jonas Hungershausen Date: Tue, 14 Feb 2023 16:55:10 +0100 Subject: [PATCH 2/2] chore: add tests --- identity/handler_test.go | 344 ++++++++++++++++++++++----------------- 1 file changed, 193 insertions(+), 151 deletions(-) diff --git a/identity/handler_test.go b/identity/handler_test.go index 5713c133379b..8b47082d6774 100644 --- a/identity/handler_test.go +++ b/identity/handler_test.go @@ -524,191 +524,233 @@ func TestHandler(t *testing.T) { } }) - t.Run("case=PATCH update of state should update state changed at timestamp", func(t *testing.T) { - uuid := x.NewUUID().String() - i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, uuid))} - require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) - + t.Run("case=should delete a user and no longer be able to retrieve it", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { - patch := []patch{ - {"op": "replace", "path": "/state", "value": identity.StateInactive}, - } - - res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) - assert.EqualValues(t, uuid, res.Get("traits.subject").String(), "%s", res.Raw) - assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) - assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) - assert.EqualValues(t, identity.StateInactive, res.Get("state").String(), "%s", res.Raw) - assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) - - res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) - assert.EqualValues(t, i.ID.String(), res.Get("id").String(), "%s", res.Raw) - assert.EqualValues(t, uuid, res.Get("traits.subject").String(), "%s", res.Raw) - assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) - assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) - assert.EqualValues(t, identity.StateInactive, res.Get("state").String(), "%s", res.Raw) - assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) + res := send(t, ts, "POST", "/identities", http.StatusCreated, json.RawMessage(`{"traits": {"bar":"baz"}}`)) + remove(t, ts, "/identities/"+res.Get("id").String(), http.StatusNoContent) + _ = get(t, ts, "/identities/"+res.Get("id").String(), http.StatusNotFound) }) } }) + }) - t.Run("case=PATCH update should not persist if schema id is invalid", func(t *testing.T) { - uuid := x.NewUUID().String() - i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, uuid))} - require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) + t.Run("case=PATCH update of state should update state changed at timestamp", func(t *testing.T) { + uuid := x.NewUUID().String() + i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, uuid))} + require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) - for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { - t.Run("endpoint="+name, func(t *testing.T) { - patch := []patch{ - {"op": "replace", "path": "/schema_id", "value": "invalid-id"}, - } + for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { + t.Run("endpoint="+name, func(t *testing.T) { + patch := []patch{ + {"op": "replace", "path": "/state", "value": identity.StateInactive}, + } - res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusBadRequest, &patch) - assert.Contains(t, res.Get("error.reason").String(), "invalid-id", "%s", res.Raw) + res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) + assert.EqualValues(t, uuid, res.Get("traits.subject").String(), "%s", res.Raw) + assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) + assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) + assert.EqualValues(t, identity.StateInactive, res.Get("state").String(), "%s", res.Raw) + assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) - res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) - // Assert that the schema ID is unchanged - assert.EqualValues(t, i.SchemaID, res.Get("schema_id").String(), "%s", res.Raw) - assert.EqualValues(t, uuid, res.Get("traits.subject").String(), "%s", res.Raw) - assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) - assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) + res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) + assert.EqualValues(t, i.ID.String(), res.Get("id").String(), "%s", res.Raw) + assert.EqualValues(t, uuid, res.Get("traits.subject").String(), "%s", res.Raw) + assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) + assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) + assert.EqualValues(t, identity.StateInactive, res.Get("state").String(), "%s", res.Raw) + assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) + }) + } + }) - }) - } - }) + t.Run("case=PATCH update should not persist if schema id is invalid", func(t *testing.T) { + uuid := x.NewUUID().String() + i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, uuid))} + require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) - t.Run("case=PATCH update should not persist if invalid state is supplied", func(t *testing.T) { - uuid := x.NewUUID().String() - i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, uuid))} - require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) + for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { + t.Run("endpoint="+name, func(t *testing.T) { + patch := []patch{ + {"op": "replace", "path": "/schema_id", "value": "invalid-id"}, + } - for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { - t.Run("endpoint="+name, func(t *testing.T) { - patch := []patch{ - {"op": "replace", "path": "/state", "value": "invalid-value"}, - } + res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusBadRequest, &patch) + assert.Contains(t, res.Get("error.reason").String(), "invalid-id", "%s", res.Raw) - res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusBadRequest, &patch) - assert.EqualValues(t, "identity state is not valid", res.Get("error.reason").String(), "%s", res.Raw) + res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) + // Assert that the schema ID is unchanged + assert.EqualValues(t, i.SchemaID, res.Get("schema_id").String(), "%s", res.Raw) + assert.EqualValues(t, uuid, res.Get("traits.subject").String(), "%s", res.Raw) + assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) + assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) - res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) - // Assert that the schema ID is unchanged - assert.EqualValues(t, i.SchemaID, res.Get("schema_id").String(), "%s", res.Raw) - assert.EqualValues(t, uuid, res.Get("traits.subject").String(), "%s", res.Raw) - assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) - assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) - assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) - }) - } - }) + }) + } + }) - t.Run("case=PATCH update should update nested fields", func(t *testing.T) { - uuid := x.NewUUID().String() - i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, uuid))} - require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) + t.Run("case=PATCH update should not persist if invalid state is supplied", func(t *testing.T) { + uuid := x.NewUUID().String() + i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, uuid))} + require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) - for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { - t.Run("endpoint="+name, func(t *testing.T) { - patch := []patch{ - {"op": "replace", "path": "/traits/subject", "value": "patched-subject"}, - } + for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { + t.Run("endpoint="+name, func(t *testing.T) { + patch := []patch{ + {"op": "replace", "path": "/state", "value": "invalid-value"}, + } - res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) - assert.EqualValues(t, "patched-subject", res.Get("traits.subject").String(), "%s", res.Raw) + res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusBadRequest, &patch) + assert.EqualValues(t, "The supplied state ('invalid-value') was not valid. Valid states are ('active', 'inactive').", res.Get("error.reason").String(), "%s", res.Raw) - res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) - // Assert that the schema ID is unchanged - assert.EqualValues(t, i.SchemaID, res.Get("schema_id").String(), "%s", res.Raw) - assert.EqualValues(t, "patched-subject", res.Get("traits.subject").String(), "%s", res.Raw) - }) - } - }) + res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) + // Assert that the schema ID is unchanged + assert.EqualValues(t, i.SchemaID, res.Get("schema_id").String(), "%s", res.Raw) + assert.EqualValues(t, uuid, res.Get("traits.subject").String(), "%s", res.Raw) + assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) + assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) + assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) + }) + } + }) - t.Run("case=PATCH should fail if no JSON payload is sent", func(t *testing.T) { - for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { - t.Run("endpoint="+name, func(t *testing.T) { - res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusBadRequest, nil) - assert.Contains(t, res.Get("error.reason").String(), `unexpected end of JSON input`, res.Raw) - }) - } - }) + t.Run("case=PATCH update should update nested fields", func(t *testing.T) { + uuid := x.NewUUID().String() + i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, uuid))} + require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) - t.Run("case=PATCH should fail if credentials are updated", func(t *testing.T) { + for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { + t.Run("endpoint="+name, func(t *testing.T) { + patch := []patch{ + {"op": "replace", "path": "/traits/subject", "value": "patched-subject"}, + } + + res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) + assert.EqualValues(t, "patched-subject", res.Get("traits.subject").String(), "%s", res.Raw) + + res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) + // Assert that the schema ID is unchanged + assert.EqualValues(t, i.SchemaID, res.Get("schema_id").String(), "%s", res.Raw) + assert.EqualValues(t, "patched-subject", res.Get("traits.subject").String(), "%s", res.Raw) + }) + } + }) + + t.Run("case=PATCH should fail if no JSON payload is sent", func(t *testing.T) { + uuid := x.NewUUID().String() + i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, uuid))} + require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) + for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { + t.Run("endpoint="+name, func(t *testing.T) { + res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusBadRequest, nil) + assert.Contains(t, res.Get("error.message").String(), `unexpected end of JSON input`, res.Raw) + }) + } + }) + + t.Run("case=PATCH should fail if credentials are updated", func(t *testing.T) { + uuid := x.NewUUID().String() + i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, uuid))} + require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) + + for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { + + t.Run("endpoint="+name, func(t *testing.T) { + patch := []patch{ + {"op": "replace", "path": "/credentials", "value": "patched-credentials"}, + } + + res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusBadRequest, &patch) + + assert.EqualValues(t, "patch includes denied path: /credentials", res.Get("error.message").String(), "%s", res.Raw) + }) + } + }) + + t.Run("case=PATCH should not invalidate credentials ory/cloud#148", func(t *testing.T) { + // see https://github.com/ory/cloud/issues/148 + + createCredentials := func(t *testing.T) (*identity.Identity, string, string) { + t.Helper() uuid := x.NewUUID().String() - i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, uuid))} + email := uuid + "@ory.sh" + password := "ljanf123akf" + p, err := reg.Hasher(ctx).Generate(context.Background(), []byte(password)) + require.NoError(t, err) + i := &identity.Identity{Traits: identity.Traits(`{"email":"` + email + `"}`)} + i.SetCredentials(identity.CredentialsTypePassword, identity.Credentials{ + Type: identity.CredentialsTypePassword, + Identifiers: []string{email}, + Config: sqlxx.JSONRawMessage(`{"hashed_password":"` + string(p) + `"}`), + }) require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) + return i, email, password + } - for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { + for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { + t.Run("endpoint="+name, func(t *testing.T) { + i, email, password := createCredentials(t) + values := func(v url.Values) { + v.Set("identifier", email) + v.Set("password", password) + } - t.Run("endpoint="+name, func(t *testing.T) { - patch := []patch{ - {"op": "replace", "path": "/credentials", "value": "patched-credentials"}, - } + // verify login works initially + loginResponse := testhelpers.SubmitLoginForm(t, true, ts.Client(), ts, values, false, true, 200, "") + require.NotEmpty(t, gjson.Get(loginResponse, "session_token").String(), "expected to find a session token, found none") - res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusBadRequest, &patch) + patch := []patch{ + {"op": "replace", "path": "/metadata_public", "value": map[string]string{"role": "user"}}, + } - assert.EqualValues(t, "patch includes denied path: /credentials", res.Get("error.reason").String(), "%s", res.Raw) - }) - } - }) + res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) + assert.EqualValues(t, "user", res.Get("metadata_public.role").String(), "%s", res.Raw) + assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) - t.Run("case=PATCH should not invalidate credentials ory/cloud#148", func(t *testing.T) { - // see https://github.com/ory/cloud/issues/148 + loginResponse = testhelpers.SubmitLoginForm(t, true, ts.Client(), ts, values, false, true, 200, "") + msgs := gjson.Get(loginResponse, "ui.messages") + require.Empty(t, msgs.Array(), "expected to find no messages: %s", msgs.String()) + }) + } + }) - createCredentials := func(t *testing.T) (*identity.Identity, string, string) { - t.Helper() - uuid := x.NewUUID().String() - email := uuid + "@ory.sh" - password := "ljanf123akf" - p, err := reg.Hasher(ctx).Generate(context.Background(), []byte(password)) - require.NoError(t, err) - i := &identity.Identity{Traits: identity.Traits(`{"email":"` + email + `"}`)} - i.SetCredentials(identity.CredentialsTypePassword, identity.Credentials{ - Type: identity.CredentialsTypePassword, - Identifiers: []string{email}, - Config: sqlxx.JSONRawMessage(`{"hashed_password":"` + string(p) + `"}`), - }) - require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) - return i, email, password - } + t.Run("case=PATCH should update metadata_admin correctly", func(t *testing.T) { + uuid := x.NewUUID().String() + i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, uuid))} + require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) - for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { - t.Run("endpoint="+name, func(t *testing.T) { - i, email, password := createCredentials(t) - values := func(v url.Values) { - v.Set("identifier", email) - v.Set("password", password) - } + for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { + t.Run("endpoint="+name, func(t *testing.T) { + patch := []patch{ + {"op": "add", "path": "/metadata_admin", "value": "metadata admin"}, + } - // verify login works initially - loginResponse := testhelpers.SubmitLoginForm(t, true, ts.Client(), ts, values, false, true, 200, "") - require.NotEmpty(t, gjson.Get(loginResponse, "session_token").String(), "expected to find a session token, found none") + res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) - patch := []patch{ - {"op": "replace", "path": "/metadata_public", "value": map[string]string{"role": "user"}}, - } + assert.True(t, res.Get("metadata_admin").Exists(), "%s", res.Raw) + assert.EqualValues(t, "metadata admin", res.Get("metadata_admin").String(), "%s", res.Raw) + }) + } + }) - res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) - assert.EqualValues(t, "user", res.Get("metadata_public.role").String(), "%s", res.Raw) - assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) + t.Run("case=PATCH should update nested metadata_admin fields correctly", func(t *testing.T) { + uuid := x.NewUUID().String() + i := &identity.Identity{MetadataAdmin: sqlxx.NullJSONRawMessage(fmt.Sprintf(`{"id": "%s", "allowed": true}`, uuid))} + require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) - loginResponse = testhelpers.SubmitLoginForm(t, true, ts.Client(), ts, values, false, true, 200, "") - msgs := gjson.Get(loginResponse, "ui.messages") - require.Empty(t, msgs.Array(), "expected to find no messages: %s", msgs.String()) - }) - } - }) + for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { + t.Run("endpoint="+name, func(t *testing.T) { + patch := []patch{ + {"op": "replace", "path": "/metadata_admin/allowed", "value": "false"}, + } - t.Run("case=should delete a user and no longer be able to retrieve it", func(t *testing.T) { - for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { - t.Run("endpoint="+name, func(t *testing.T) { - res := send(t, ts, "POST", "/identities", http.StatusCreated, json.RawMessage(`{"traits": {"bar":"baz"}}`)) - remove(t, ts, "/identities/"+res.Get("id").String(), http.StatusNoContent) - _ = get(t, ts, "/identities/"+res.Get("id").String(), http.StatusNotFound) - }) - } - }) + res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) + + assert.True(t, res.Get("metadata_admin.allowed").Exists(), "%s", res.Raw) + assert.EqualValues(t, false, res.Get("metadata_admin.allowed").Bool(), "%s", res.Raw) + assert.EqualValues(t, uuid, res.Get("metadata_admin.id").String(), "%s", res.Raw) + }) + } }) t.Run("case=should return entity with credentials metadata", func(t *testing.T) {