Skip to content

Commit

Permalink
Merge branch 'master' into jonas-jonas/fix/patchMetadata
Browse files Browse the repository at this point in the history
  • Loading branch information
jonas-jonas authored Feb 15, 2023
2 parents 2fb6565 + 8c6e3a1 commit 6372639
Show file tree
Hide file tree
Showing 14 changed files with 303 additions and 34 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ flows.
([39fa31f](https://github.com/ory/kratos/commit/39fa31f85deb3f015aa0f1b30b4a17e4b51d461b))
- Identity.CopyWithoutCredentials
([989c99d](https://github.com/ory/kratos/commit/989c99d6a32e02759a8a7a07606a90832afec460))
- Implement offline scope in the way google expects
([#3088](https://github.com/ory/kratos/issues/3088))
([39043d4](https://github.com/ory/kratos/commit/39043d451e154af44123ba031381f0e3c10fbb00))
- Issuer missing from netid claims
([#3080](https://github.com/ory/kratos/issues/3080))
([dec7cbc](https://github.com/ory/kratos/commit/dec7cbc4286cbbe2d787b1f8998ee57054d7c95b)):
Expand Down Expand Up @@ -378,6 +381,8 @@ flows.
closes [#2422](https://github.com/ory/kratos/issues/2422)
- Don't pre-generate UUIDs for transient objects
([e17f307](https://github.com/ory/kratos/commit/e17f307732f8ced34727d5f3a70929866a0595e0))
- Identity by identifier ([#3077](https://github.com/ory/kratos/issues/3077))
([c288d4d](https://github.com/ory/kratos/commit/c288d4d136bca1a9ed3931b4827967eb44e80ede))
- Improve tracing span naming in hooks
([bf828d3](https://github.com/ory/kratos/commit/bf828d3f5d56a963529e98958f4039f0dc569979))
- Let DB generate ID for session devices
Expand Down
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{"[email protected]"},
Config: sqlxx.JSONRawMessage(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), // foobar
}},
State: identity.StateActive,
Traits: identity.Traits(`{"username":"[email protected]"}`),
}

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

res := get(t, adminTS, "/[email protected]", http.StatusOK)
assert.EqualValues(t, i1.ID.String(), res.Get("0.id").String(), "%s", res.Raw)
assert.EqualValues(t, "[email protected]", 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, "[email protected]", res.Get("0.credentials.password.identifiers.0").String(), res.Raw)
})

t.Run("case=should get oidc credential", func(t *testing.T) {
id := createOidcIdentity(t, "[email protected]", "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-%[email protected]", 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: "[email protected]",
})
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: "[email protected]",
})
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("", "[email protected]")
expected.Traits = identity.Traits(`{}`)

Expand Down
16 changes: 12 additions & 4 deletions internal/client-go/api_identity.go

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

16 changes: 12 additions & 4 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
Loading

0 comments on commit 6372639

Please sign in to comment.