Skip to content

Commit

Permalink
Merge b7382f3 into 5535fcb
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr committed May 24, 2023
2 parents 5535fcb + b7382f3 commit 331041b
Show file tree
Hide file tree
Showing 23 changed files with 414 additions and 53 deletions.
8 changes: 8 additions & 0 deletions .schema/openapi/patches/identity.yaml
Expand Up @@ -11,6 +11,14 @@
- oidc
- webauthn
- lookup_secret
- op: add
path: /paths/~1admin~1identities~1{id}/get/parameters/1/schema/items/enum
value:
- password
- totp
- oidc
- webauthn
- lookup_secret
- op: remove
path: /components/schemas/updateIdentityBody/properties/metadata_admin/type
- op: remove
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -48,7 +48,7 @@ docs/swagger:
npx @redocly/openapi-cli preview-docs spec/swagger.json

.bin/golangci-lint: Makefile
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -d -b .bin v1.50.1
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -d -b .bin v1.52.2

.bin/hydra: Makefile
bash <(curl https://raw.githubusercontent.com/ory/meta/master/install.sh) -d -b .bin hydra v2.0.2
Expand Down
2 changes: 1 addition & 1 deletion examples/go/identity/get/main.go
Expand Up @@ -19,7 +19,7 @@ func getIdentity() *ory.Identity {
ctx := context.Background()
created := pkg.CreateIdentity(client)

identity, res, err := client.IdentityApi.GetIdentity(ctx, created.Id).Execute()
identity, res, err := client.IdentityApi.GetIdentity(ctx, created.Id).IncludeCredential([]string{"password"}).Execute()
pkg.SDKExitOnError(err, res)

return identity
Expand Down
@@ -0,0 +1,40 @@
{
"credentials": {
"oidc": {
"type": "oidc",
"identifiers": [
"bar",
"baz"
],
"version": 0
},
"password": {
"type": "password",
"identifiers": [
"bar",
"zab"
],
"config": {
"some": "secret"
},
"version": 0
},
"webauthn": {
"type": "webauthn",
"identifiers": [
"bar",
"foo"
],
"config": {
"some": "secret",
"user_handle": "rVIFaWRcTTuQLkXFmQWpgA=="
},
"version": 1
}
},
"schema_id": "default",
"state": "active",
"traits": {},
"metadata_public": null,
"metadata_admin": null
}
@@ -0,0 +1,36 @@
{
"credentials": {
"oidc": {
"type": "oidc",
"identifiers": [
"bar",
"baz"
],
"version": 0
},
"password": {
"type": "password",
"identifiers": [
"bar",
"zab"
],
"config": {
"some": "secret"
},
"version": 0
},
"webauthn": {
"type": "webauthn",
"identifiers": [
"bar",
"foo"
],
"version": 1
}
},
"schema_id": "default",
"state": "active",
"traits": {},
"metadata_public": null,
"metadata_admin": null
}
@@ -0,0 +1,33 @@
{
"credentials": {
"oidc": {
"type": "oidc",
"identifiers": [
"bar",
"baz"
],
"version": 0
},
"password": {
"type": "password",
"identifiers": [
"bar",
"zab"
],
"version": 0
},
"webauthn": {
"type": "webauthn",
"identifiers": [
"bar",
"foo"
],
"version": 1
}
},
"schema_id": "default",
"state": "active",
"traits": {},
"metadata_public": null,
"metadata_admin": null
}
@@ -0,0 +1,10 @@
{
"type": "oidc",
"identifiers": [
"bar",
"baz"
],
"version": 0,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
}
@@ -0,0 +1,13 @@
{
"type": "password",
"identifiers": [
"zab",
"bar"
],
"config": {
"some": "secret"
},
"version": 0,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
}
@@ -0,0 +1,13 @@
{
"type": "webauthn",
"identifiers": [
"foo",
"bar"
],
"config": {
"some": "secret"
},
"version": 0,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
}
@@ -0,0 +1,10 @@
{
"type": "oidc",
"identifiers": [
"bar",
"baz"
],
"version": 0,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
}
@@ -0,0 +1,10 @@
{
"type": "password",
"identifiers": [
"zab",
"bar"
],
"version": 0,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
}
@@ -0,0 +1,13 @@
{
"type": "webauthn",
"identifiers": [
"foo",
"bar"
],
"config": {
"some": "secret"
},
"version": 0,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
}
@@ -0,0 +1,10 @@
{
"type": "oidc",
"identifiers": [
"bar",
"baz"
],
"version": 0,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
}
@@ -0,0 +1,10 @@
{
"type": "password",
"identifiers": [
"zab",
"bar"
],
"version": 0,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
}
@@ -0,0 +1,10 @@
{
"type": "webauthn",
"identifiers": [
"foo",
"bar"
],
"version": 0,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
}
18 changes: 18 additions & 0 deletions identity/credentials.go
Expand Up @@ -77,6 +77,24 @@ const (
CredentialsTypeRecoveryCode CredentialsType = "code_recovery"
)

// ParseCredentialsType parses a string into a CredentialsType or returns false as the second argument.
func ParseCredentialsType(in string) (CredentialsType, bool) {
for _, t := range []CredentialsType{
CredentialsTypePassword,
CredentialsTypeOIDC,
CredentialsTypeTOTP,
CredentialsTypeLookup,
CredentialsTypeWebAuthn,
CredentialsTypeRecoveryLink,
CredentialsTypeRecoveryCode,
} {
if t.String() == in {
return t, true
}
}
return "", false
}

// Credentials represents a specific credential type
//
// swagger:model identityCredentials
Expand Down
28 changes: 28 additions & 0 deletions identity/credentials_test.go
Expand Up @@ -6,6 +6,8 @@ package identity
import (
"testing"

"github.com/stretchr/testify/require"

"github.com/mohae/deepcopy"
"github.com/stretchr/testify/assert"

Expand All @@ -29,3 +31,29 @@ func TestAALOrder(t *testing.T) {
assert.True(t, AuthenticatorAssuranceLevel1 < AuthenticatorAssuranceLevel3)
assert.True(t, AuthenticatorAssuranceLevel2 < AuthenticatorAssuranceLevel3)
}

func TestParseCredentialsType(t *testing.T) {
for _, tc := range []struct {
input string
expected CredentialsType
}{
{"password", CredentialsTypePassword},
{"oidc", CredentialsTypeOIDC},
{"totp", CredentialsTypeTOTP},
{"webauthn", CredentialsTypeWebAuthn},
{"lookup_secret", CredentialsTypeLookup},
{"link_recovery", CredentialsTypeRecoveryLink},
{"code_recovery", CredentialsTypeRecoveryCode},
} {
t.Run("case="+tc.input, func(t *testing.T) {
actual, ok := ParseCredentialsType(tc.input)
require.True(t, ok)
assert.Equal(t, tc.expected, actual)
})
}

t.Run("case=unknown", func(t *testing.T) {
_, ok := ParseCredentialsType("unknown")
require.False(t, ok)
})
}
29 changes: 16 additions & 13 deletions identity/handler.go
Expand Up @@ -204,8 +204,8 @@ type getIdentity struct {

// Include Credentials in Response
//
// Currently, only `oidc` is supported. This will return the initial OAuth 2.0 Access,
// Refresh and (optionally) OpenID Connect ID Token.
// Include any credential, for example `password` or `oidc`, in the response. When set to `oidc`, This will return
// the initial OAuth 2.0 Access Token, OAuth 2.0 Refresh Token and the OpenID Connect ID Token if available.
//
// required: false
// in: query
Expand Down Expand Up @@ -241,21 +241,24 @@ func (h *Handler) get(w http.ResponseWriter, r *http.Request, ps httprouter.Para
return
}

if declassify := r.URL.Query().Get("include_credential"); declassify == "oidc" {
emit, err := i.WithDeclassifiedCredentialsOIDC(r.Context(), h.r)
if err != nil {
h.r.Writer().WriteError(w, r, err)
includeCredentials := r.URL.Query()["include_credential"]
var declassify []CredentialsType
for _, v := range includeCredentials {
tc, ok := ParseCredentialsType(v)
if ok {
declassify = append(declassify, tc)
} else {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Invalid value `%s` for parameter `include_credential`.", declassify)))

Check warning on line 251 in identity/handler.go

View check run for this annotation

Codecov / codecov/patch

identity/handler.go#L251

Added line #L251 was not covered by tests
return
}
h.r.Writer().Write(w, r, WithCredentialsAndAdminMetadataInJSON(*emit))
return
} else if len(declassify) > 0 {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Invalid value `%s` for parameter `include_credential`.", declassify)))
return

}

h.r.Writer().Write(w, r, WithCredentialsMetadataAndAdminMetadataInJSON(*i))
emit, err := i.WithDeclassifiedCredentials(r.Context(), h.r, declassify)
if err != nil {
h.r.Writer().WriteError(w, r, err)
return
}
h.r.Writer().Write(w, r, WithCredentialsAndAdminMetadataInJSON(*emit))
}

// Create Identity Parameters
Expand Down
27 changes: 27 additions & 0 deletions identity/handler_test.go
Expand Up @@ -436,6 +436,33 @@ func TestHandler(t *testing.T) {
}
})

t.Run("case=should get identity with credentials", func(t *testing.T) {
i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID)
credentials := map[identity.CredentialsType]identity.Credentials{
identity.CredentialsTypePassword: {Identifiers: []string{"zab", "bar"}, Type: identity.CredentialsTypePassword, Config: sqlxx.JSONRawMessage("{\"some\" : \"secret\"}")},
identity.CredentialsTypeOIDC: {Type: identity.CredentialsTypeOIDC, Identifiers: []string{"bar", "baz"}, Config: sqlxx.JSONRawMessage("{\"some\" : \"secret\"}")},
identity.CredentialsTypeWebAuthn: {Type: identity.CredentialsTypeWebAuthn, Identifiers: []string{"foo", "bar"}, Config: sqlxx.JSONRawMessage("{\"some\" : \"secret\", \"user_handle\": \"rVIFaWRcTTuQLkXFmQWpgA==\"}")},
}
i.Credentials = credentials
require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i))

excludeKeys := snapshotx.ExceptNestedKeys("id", "created_at", "updated_at", "schema_url", "state_changed_at")
t.Run("case=should get identity without credentials included", func(t *testing.T) {
res := get(t, adminTS, "/identities/"+i.ID.String(), http.StatusOK)
snapshotx.SnapshotT(t, json.RawMessage(res.Raw), excludeKeys)
})

t.Run("case=should get identity with password credentials included", func(t *testing.T) {
res := get(t, adminTS, "/identities/"+i.ID.String()+"?include_credential=password", http.StatusOK)
snapshotx.SnapshotT(t, json.RawMessage(res.Raw), excludeKeys)
})

t.Run("case=should get identity with password and webauthn credentials included", func(t *testing.T) {
res := get(t, adminTS, "/identities/"+i.ID.String()+"?include_credential=password&include_credential=webauthn", http.StatusOK)
snapshotx.SnapshotT(t, json.RawMessage(res.Raw), excludeKeys)
})
})

t.Run("case=should pass if no oidc credentials are set", func(t *testing.T) {
for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} {
t.Run("endpoint="+name, func(t *testing.T) {
Expand Down

0 comments on commit 331041b

Please sign in to comment.