From 23670e51080b1ffae80ec3618271052dbb1fcdf8 Mon Sep 17 00:00:00 2001 From: Jonas Hungershausen Date: Mon, 23 Sep 2024 12:31:58 +0200 Subject: [PATCH] chore: parse UUID, add tests and index --- identity/handler.go | 33 ++++++++++------ identity/handler_test.go | 38 +++++++++++++++++++ ...95000000001_organization_id_index.down.sql | 1 + ...3095000000001_organization_id_index.up.sql | 1 + 4 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 persistence/sql/migrations/sql/20240923095000000001_organization_id_index.down.sql create mode 100644 persistence/sql/migrations/sql/20240923095000000001_organization_id_index.up.sql diff --git a/identity/handler.go b/identity/handler.go index 2a089e9b578e..90755f2e8dad 100644 --- a/identity/handler.go +++ b/identity/handler.go @@ -11,6 +11,8 @@ import ( "strings" "time" + "github.com/gofrs/uuid" + "github.com/ory/x/crdbx" "github.com/ory/x/pagination/keysetpagination" @@ -175,6 +177,7 @@ type listIdentitiesParameters struct { // // If `ids` is set, this parameter is ignored. // required: false + // in: query OrganizationID string `json:"organization_id"` crdbx.ConsistencyRequestParameters @@ -199,6 +202,7 @@ type listIdentitiesParameters struct { // default: errorGeneric func (h *Handler) list(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { includeCredentials := r.URL.Query()["include_credential"] + var err error var declassify []CredentialsType for _, v := range includeCredentials { tc, ok := ParseCredentialsType(v) @@ -210,18 +214,25 @@ func (h *Handler) list(w http.ResponseWriter, r *http.Request, _ httprouter.Para } } - var ( - err error - params = ListIdentityParameters{ - Expand: ExpandDefault, - IdsFilter: r.URL.Query()["ids"], - CredentialsIdentifier: r.URL.Query().Get("credentials_identifier"), - CredentialsIdentifierSimilar: r.URL.Query().Get("preview_credentials_identifier_similar"), - OrganizationID: x.ParseUUID(r.URL.Query().Get("organization_id")), - ConsistencyLevel: crdbx.ConsistencyLevelFromRequest(r), - DeclassifyCredentials: declassify, + + var orgId uuid.UUID + if orgIdStr := r.URL.Query().Get("organization_id"); orgIdStr != "" { + orgId, err = uuid.FromString(r.URL.Query().Get("organization_id")) + if err != nil { + h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Invalid UUID value `%s` for parameter `organization_id`.", r.URL.Query().Get("organization_id")))) + return } - ) + } + + params := ListIdentityParameters{ + Expand: ExpandDefault, + IdsFilter: r.URL.Query()["ids"], + CredentialsIdentifier: r.URL.Query().Get("credentials_identifier"), + CredentialsIdentifierSimilar: r.URL.Query().Get("preview_credentials_identifier_similar"), + OrganizationID: orgId, + ConsistencyLevel: crdbx.ConsistencyLevelFromRequest(r), + DeclassifyCredentials: declassify, + } if params.CredentialsIdentifier != "" && params.CredentialsIdentifierSimilar != "" { h.r.Writer().WriteError(w, r, herodot.ErrBadRequest.WithReason("Cannot pass both credentials_identifier and preview_credentials_identifier_similar.")) return diff --git a/identity/handler_test.go b/identity/handler_test.go index d97c2f73fae4..bb9979a39095 100644 --- a/identity/handler_test.go +++ b/identity/handler_test.go @@ -1457,6 +1457,44 @@ func TestHandler(t *testing.T) { } }) + t.Run("organizations", func(t *testing.T) { + t.Run("case=should list organization identities", func(t *testing.T) { + for name, ts := range map[string]*httptest.Server{"admin": adminTS} { + t.Run("endpoint="+name, func(t *testing.T) { + orgID := uuid.Must(uuid.NewV4()) + email := x.NewUUID().String() + "@ory.sh" + reg.IdentityManager().Create(ctx, &identity.Identity{ + Traits: identity.Traits(`{"email":"` + email + `"}`), + OrganizationID: uuid.NullUUID{UUID: orgID, Valid: true}, + }) + + res := get(t, ts, "/identities?organization_id="+orgID.String(), http.StatusOK) + assert.Len(t, res.Array(), 1) + assert.EqualValues(t, email, res.Get(`0.traits.email`).String(), "%s", res.Raw) + }) + } + }) + + t.Run("case=malformed organization id should return an error", func(t *testing.T) { + for name, ts := range map[string]*httptest.Server{"admin": adminTS} { + t.Run("endpoint="+name, func(t *testing.T) { + res := get(t, ts, "/identities?organization_id=not-a-uuid", http.StatusBadRequest) + assert.Contains(t, res.Get("error.reason").String(), "Invalid UUID value `not-a-uuid` for parameter `organization_id`.", "%s", res.Raw) + }) + } + }) + + t.Run("case=unknown organization id should return an empty list", func(t *testing.T) { + for name, ts := range map[string]*httptest.Server{"admin": adminTS} { + t.Run("endpoint="+name, func(t *testing.T) { + id := x.NewUUID() + res := get(t, ts, "/identities?organization_id="+id.String(), http.StatusOK) + assert.Len(t, res.Array(), 0) + }) + } + }) + }) + t.Run("case=should list all identities with credentials", func(t *testing.T) { t.Run("include_credential=oidc should include OIDC credentials config", func(t *testing.T) { res := get(t, adminTS, "/identities?include_credential=oidc&credentials_identifier=bar:foo.oidc@bar.com", http.StatusOK) diff --git a/persistence/sql/migrations/sql/20240923095000000001_organization_id_index.down.sql b/persistence/sql/migrations/sql/20240923095000000001_organization_id_index.down.sql new file mode 100644 index 000000000000..a7fe812fe93a --- /dev/null +++ b/persistence/sql/migrations/sql/20240923095000000001_organization_id_index.down.sql @@ -0,0 +1 @@ +DROP INDEX identities_organization_id; \ No newline at end of file diff --git a/persistence/sql/migrations/sql/20240923095000000001_organization_id_index.up.sql b/persistence/sql/migrations/sql/20240923095000000001_organization_id_index.up.sql new file mode 100644 index 000000000000..2fc9301ddc82 --- /dev/null +++ b/persistence/sql/migrations/sql/20240923095000000001_organization_id_index.up.sql @@ -0,0 +1 @@ +CREATE INDEX identities_organization_id ON identities (organization_id ASC); \ No newline at end of file