Skip to content

Commit

Permalink
Merge f5e8fb3 into fd37383
Browse files Browse the repository at this point in the history
  • Loading branch information
kelkarajay authored Feb 14, 2023
2 parents fd37383 + f5e8fb3 commit 0a78dae
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 26 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ GO_DEPENDENCIES = github.com/ory/go-acc \

define make-go-dependency
# go install is responsible for not re-building when the code hasn't changed
.bin/$(notdir $1): go.mod go.sum Makefile
.bin/$(notdir $1): go.mod go.sum
GOBIN=$(PWD)/.bin/ go install $1
endef
$(foreach dep, $(GO_DEPENDENCIES), $(eval $(call make-go-dependency, $(dep))))
Expand Down Expand Up @@ -54,7 +54,7 @@ docs/swagger:

.bin/ory: Makefile
curl https://raw.githubusercontent.com/ory/meta/master/install.sh | bash -s -- -b .bin ory v0.2.2
touch .bin/ory
touch -a -m .bin/ory

.PHONY: lint
lint: .bin/golangci-lint
Expand Down
29 changes: 22 additions & 7 deletions identity/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ type listIdentitiesResponse struct {
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
type listIdentitiesParameters struct {
migrationpagination.RequestParameters

// CredentialsIdentifier is the identifier (username, email) of the credentials to look up.
//
// required: false
// in: query
CredentialsIdentifier string `json:"credentials_identifier"`
}

// swagger:route GET /admin/identities identity listIdentities
Expand All @@ -147,22 +153,31 @@ type listIdentitiesParameters struct {
// default: errorGeneric
func (h *Handler) list(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
page, itemsPerPage := x.ParsePagination(r)
is, err := h.r.IdentityPool().ListIdentities(r.Context(), ExpandDefault, page, itemsPerPage)
if err != nil {
h.r.Writer().WriteError(w, r, err)
return

params := ListIdentityParameters{Expand: ExpandDefault, Page: page, PerPage: itemsPerPage, CredentialsIdentifier: r.URL.Query().Get("credentials_identifier")}
if params.CredentialsIdentifier != "" {
params.Expand = ExpandEverything
}

total, err := h.r.IdentityPool().CountIdentities(r.Context())
is, err := h.r.IdentityPool().ListIdentities(r.Context(), params)
if err != nil {
h.r.Writer().WriteError(w, r, err)
return
}

total := int64(len(is))
if params.CredentialsIdentifier == "" {
total, err = h.r.IdentityPool().CountIdentities(r.Context())
if err != nil {
h.r.Writer().WriteError(w, r, err)
return
}
}

// Identities using the marshaler for including metadata_admin
isam := make([]WithAdminMetadataInJSON, len(is))
isam := make([]WithCredentialsMetadataAndAdminMetadataInJSON, len(is))
for i, identity := range is {
isam[i] = WithAdminMetadataInJSON(identity)
isam[i] = WithCredentialsMetadataAndAdminMetadataInJSON(identity)
}

migrationpagination.PaginationHeader(w, urlx.AppendPaths(h.r.Config().SelfAdminURL(r.Context()), RouteCollection), total, page, itemsPerPage)
Expand Down
30 changes: 30 additions & 0 deletions identity/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,36 @@ func TestHandler(t *testing.T) {
}
})

t.Run("case=should return an empty array on a failed lookup with identifier", func(t *testing.T) {
res := get(t, adminTS, "/identities?credentials_identifier=find.by.non.existing.identifier@bar.com", http.StatusOK)
assert.EqualValues(t, int64(0), res.Get("#").Int(), "%s", res.Raw)
})

t.Run("case=should be able to lookup the identity using identifier", func(t *testing.T) {
i1 := &identity.Identity{
Credentials: map[identity.CredentialsType]identity.Credentials{
identity.CredentialsTypePassword: {
Type: identity.CredentialsTypePassword,
Identifiers: []string{"find.by.identifier@bar.com"},
Config: sqlxx.JSONRawMessage(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), // foobar
}},
State: identity.StateActive,
Traits: identity.Traits(`{"username":"find.by.identifier@bar.com"}`),
}

require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i1))

res := get(t, adminTS, "/identities?credentials_identifier=find.by.identifier@bar.com", http.StatusOK)
assert.EqualValues(t, i1.ID.String(), res.Get("0.id").String(), "%s", res.Raw)
assert.EqualValues(t, "find.by.identifier@bar.com", res.Get("0.traits.username").String(), "%s", res.Raw)
assert.EqualValues(t, defaultSchemaExternalURL, res.Get("0.schema_url").String(), "%s", res.Raw)
assert.EqualValues(t, config.DefaultIdentityTraitsSchemaID, res.Get("0.schema_id").String(), "%s", res.Raw)
assert.EqualValues(t, identity.StateActive, res.Get("0.state").String(), "%s", res.Raw)
assert.EqualValues(t, "password", res.Get("0.credentials.password.type").String(), res.Raw)
assert.EqualValues(t, "1", res.Get("0.credentials.password.identifiers.#").String(), res.Raw)
assert.EqualValues(t, "find.by.identifier@bar.com", res.Get("0.credentials.password.identifiers.0").String(), res.Raw)
})

t.Run("case=should get oidc credential", func(t *testing.T) {
id := createOidcIdentity(t, "foo.oidc@bar.com", "access_token", "refresh_token", "id_token", true)
for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} {
Expand Down
9 changes: 8 additions & 1 deletion identity/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@ import (
)

type (
ListIdentityParameters struct {
Expand Expandables
CredentialsIdentifier string
Page int
PerPage int
}

Pool interface {
// ListIdentities lists all identities in the store given the page and itemsPerPage.
ListIdentities(ctx context.Context, expandables sqlxx.Expandables, page, itemsPerPage int) ([]Identity, error)
ListIdentities(ctx context.Context, params ListIdentityParameters) ([]Identity, error)

// CountIdentities counts the number of identities in the store.
CountIdentities(ctx context.Context) (int64, error)
Expand Down
74 changes: 71 additions & 3 deletions identity/test/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func TestPool(ctx context.Context, conf *config.Config, p interface {
})

t.Run("list", func(t *testing.T) {
actual, err := p.ListIdentities(ctx, expand, 0, 10)
actual, err := p.ListIdentities(ctx, identity.ListIdentityParameters{Expand: expand, Page: 0, PerPage: 10})
require.NoError(t, err)
require.Len(t, actual, 1)
assertion(t, &actual[0])
Expand Down Expand Up @@ -569,7 +569,7 @@ func TestPool(ctx context.Context, conf *config.Config, p interface {
})

t.Run("case=list", func(t *testing.T) {
is, err := p.ListIdentities(ctx, identity.ExpandDefault, 0, 25)
is, err := p.ListIdentities(ctx, identity.ListIdentityParameters{Expand: identity.ExpandDefault, Page: 0, PerPage: 25})
require.NoError(t, err)
assert.Len(t, is, len(createdIDs))
for _, id := range createdIDs {
Expand All @@ -587,13 +587,81 @@ func TestPool(ctx context.Context, conf *config.Config, p interface {

t.Run("no results on other network", func(t *testing.T) {
_, p := testhelpers.NewNetwork(t, ctx, p)
is, err := p.ListIdentities(ctx, identity.ExpandDefault, 0, 25)
is, err := p.ListIdentities(ctx, identity.ListIdentityParameters{Expand: identity.ExpandDefault, Page: 0, PerPage: 25})
require.NoError(t, err)
assert.Len(t, is, 0)
})
})

t.Run("case=find identity by its credentials identifier", func(t *testing.T) {
var expectedIdentifiers []string
var expectedIdentities []*identity.Identity

for _, c := range []identity.CredentialsType{
identity.CredentialsTypePassword,
identity.CredentialsTypeWebAuthn,
identity.CredentialsTypeOIDC,
} {
identityIdentifier := fmt.Sprintf("find-identity-by-identifier-%s@ory.sh", c)
expected := identity.NewIdentity("")
expected.SetCredentials(c, identity.Credentials{Type: c, Identifiers: []string{identityIdentifier}, Config: sqlxx.JSONRawMessage(`{}`)})

require.NoError(t, p.CreateIdentity(ctx, expected))
createdIDs = append(createdIDs, expected.ID)
expectedIdentifiers = append(expectedIdentifiers, identityIdentifier)
expectedIdentities = append(expectedIdentities, expected)
}

actual, err := p.ListIdentities(ctx, identity.ListIdentityParameters{
Expand: identity.ExpandEverything,
})
require.NoError(t, err)
require.True(t, len(actual) > 0)

for c, ct := range []identity.CredentialsType{
identity.CredentialsTypePassword,
identity.CredentialsTypeWebAuthn,
} {
t.Run(ct.String(), func(t *testing.T) {
actual, err := p.ListIdentities(ctx, identity.ListIdentityParameters{
// Match is normalized
CredentialsIdentifier: expectedIdentifiers[c],
})
require.NoError(t, err)

expected := expectedIdentities[c]
require.Len(t, actual, 1)
assertx.EqualAsJSONExcept(t, expected, actual[0], []string{"credentials.config", "created_at", "updated_at", "state_changed_at"})
})
}

t.Run("only webauthn and password", func(t *testing.T) {
actual, err := p.ListIdentities(ctx, identity.ListIdentityParameters{
CredentialsIdentifier: "find-identity-by-identifier-oidc@ory.sh",
})
require.NoError(t, err)
assert.Len(t, actual, 0)
})

t.Run("non existing identifier", func(t *testing.T) {
actual, err := p.ListIdentities(ctx, identity.ListIdentityParameters{
CredentialsIdentifier: "find-identity-by-identifier-non-existing@ory.sh",
})
require.NoError(t, err)
assert.Len(t, actual, 0)
})

t.Run("not if on another network", func(t *testing.T) {
_, on := testhelpers.NewNetwork(t, ctx, p)
actual, err := on.ListIdentities(ctx, identity.ListIdentityParameters{
CredentialsIdentifier: expectedIdentifiers[0],
})
require.NoError(t, err)
assert.Len(t, actual, 0)
})
})

t.Run("case=find identity by its credentials type and identifier", func(t *testing.T) {
expected := passwordIdentity("", "find-credentials-identifier@ory.sh")
expected.Traits = identity.Traits(`{}`)

Expand Down
8 changes: 8 additions & 0 deletions internal/client-go/api_identity.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions internal/httpclient/api_identity.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions persistence/sql/migratest/migration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func TestMigrations(t *testing.T) {
defer wg.Done()
t.Parallel()

ids, err := d.PrivilegedIdentityPool().ListIdentities(context.Background(), identity.ExpandEverything, 0, 1000)
ids, err := d.PrivilegedIdentityPool().ListIdentities(context.Background(), identity.ListIdentityParameters{Expand: identity.ExpandEverything, Page: 0, PerPage: 1000})
require.NoError(t, err)
require.NotEmpty(t, ids)

Expand Down Expand Up @@ -185,7 +185,7 @@ func TestMigrations(t *testing.T) {
defer wg.Done()
t.Parallel()

ids, err := d.PrivilegedIdentityPool().ListIdentities(context.Background(), identity.ExpandNothing, 0, 1000)
ids, err := d.PrivilegedIdentityPool().ListIdentities(context.Background(), identity.ListIdentityParameters{Expand: identity.ExpandNothing, Page: 0, PerPage: 1000})
require.NoError(t, err)
require.NotEmpty(t, ids)

Expand Down
34 changes: 23 additions & 11 deletions persistence/sql/persister_identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,26 +377,38 @@ func (p *Persister) HydrateIdentityAssociations(ctx context.Context, i *identity
return p.injectTraitsSchemaURL(ctx, i)
}

func (p *Persister) ListIdentities(ctx context.Context, expand identity.Expandables, page, perPage int) (res []identity.Identity, err error) {
func (p *Persister) ListIdentities(ctx context.Context, params identity.ListIdentityParameters) (res []identity.Identity, err error) {
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListIdentities")
defer otelx.End(span, &err)
span.SetAttributes(
attribute.Int("page", page),
attribute.Int("per_page", perPage),
attribute.StringSlice("expand", expand.ToEager()),
attribute.Int("page", params.Page),
attribute.Int("per_page", params.PerPage),
attribute.StringSlice("expand", params.Expand.ToEager()),
attribute.Bool("use:credential_identifier_filter", params.CredentialsIdentifier != ""),
attribute.String("network.id", p.NetworkID(ctx).String()),
)

is := make([]identity.Identity, 0)

con := p.GetConnection(ctx)
query := con.
Where("nid = ?", p.NetworkID(ctx)).
Paginate(page, perPage).
Order("id DESC")

if len(expand) > 0 {
query = query.EagerPreload(expand.ToEager()...)
nid := p.NetworkID(ctx)
query := con.Where("identities.nid = ?", nid).Paginate(params.Page, params.PerPage).
Order("identities.id DESC")

if len(params.Expand) > 0 {
query = query.EagerPreload(params.Expand.ToEager()...)
}

if match := params.CredentialsIdentifier; len(match) > 0 {
// When filtering by credentials identifier, we most likely are looking for a username or email. It is therefore
// important to normalize the identifier before querying the database.
match = p.normalizeIdentifier(identity.CredentialsTypePassword, match)
query = query.
InnerJoin("identity_credentials ic", "ic.identity_id = identities.id").
InnerJoin("identity_credential_types ict", "ict.id = ic.identity_credential_type_id").
InnerJoin("identity_credential_identifiers ici", "ici.identity_credential_id = ic.id").
Where("(ic.nid = ? AND ici.nid = ? AND ici.identifier = ?)", nid, nid, match).
Where("ict.name IN (?)", identity.CredentialsTypeWebAuthn, identity.CredentialsTypePassword)
}

/* #nosec G201 TableName is static */
Expand Down
8 changes: 8 additions & 0 deletions spec/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -3237,6 +3237,14 @@
"minimum": 1,
"type": "integer"
}
},
{
"description": "Identifier\n\nThis query parameter can be used to lookup an identity using its identifier.\nFor example - an email address",
"in": "query",
"name": "identifier",
"schema": {
"type": "string"
}
}
],
"responses": {
Expand Down
6 changes: 6 additions & 0 deletions spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@
"description": "Pagination Page\n\nThis value is currently an integer, but it is not sequential. The value is not the page number, but a\nreference. The next page can be any number and some numbers might return an empty list.\n\nFor example, page 2 might not follow after page 1. And even if page 3 and 5 exist, but page 4 might not exist.",
"name": "page",
"in": "query"
},
{
"type": "string",
"description": "Identifier\n\nThis query parameter can be used to lookup an identity using its identifier.\nFor example - an email address",
"name": "identifier",
"in": "query"
}
],
"responses": {
Expand Down

0 comments on commit 0a78dae

Please sign in to comment.