From ee8e5fc1031b558d00f67bc14ef11a279e73ff36 Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Mon, 14 Oct 2024 11:15:04 +0200 Subject: [PATCH] test: add test case for android auth --- identity/credentials_webauthn.go | 7 ++- .../success/android/internal_context.json | 7 +++ .../success/android/response.json | 9 +++ .../success/{ => browser}/identity.json | 0 .../{ => browser}/internal_context.json | 0 .../success/{ => browser}/response.json | 0 .../passkey/passkey_registration_test.go | 58 +++++++++++++++++-- .../strategy/passkey/testfixture_test.go | 33 ++++++++--- 8 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 selfservice/strategy/passkey/fixtures/registration/success/android/internal_context.json create mode 100644 selfservice/strategy/passkey/fixtures/registration/success/android/response.json rename selfservice/strategy/passkey/fixtures/registration/success/{ => browser}/identity.json (100%) rename selfservice/strategy/passkey/fixtures/registration/success/{ => browser}/internal_context.json (100%) rename selfservice/strategy/passkey/fixtures/registration/success/{ => browser}/response.json (100%) diff --git a/identity/credentials_webauthn.go b/identity/credentials_webauthn.go index d8064471a631..ae6b2e34cb77 100644 --- a/identity/credentials_webauthn.go +++ b/identity/credentials_webauthn.go @@ -104,11 +104,14 @@ func (c *CredentialWebAuthn) ToWebAuthn() *webauthn.Credential { PublicKey: c.PublicKey, AttestationType: c.AttestationType, Transport: c.Transport, - Authenticator: webauthn.Authenticator{ + } + + if c.Authenticator != nil { + wc.Authenticator = webauthn.Authenticator{ AAGUID: c.Authenticator.AAGUID, SignCount: c.Authenticator.SignCount, CloneWarning: c.Authenticator.CloneWarning, - }, + } } if c.Flags != nil { diff --git a/selfservice/strategy/passkey/fixtures/registration/success/android/internal_context.json b/selfservice/strategy/passkey/fixtures/registration/success/android/internal_context.json new file mode 100644 index 000000000000..cd9949ce0093 --- /dev/null +++ b/selfservice/strategy/passkey/fixtures/registration/success/android/internal_context.json @@ -0,0 +1,7 @@ +{ + "passkey_session_data": { + "challenge": "mFtAwmtDDdwcO6200I2H6oWjzOiF21lZhQVlrC4tdaU", + "user_id": "d29OeDNJVjdYR2NRa09RVHhNVG1ZbHE1ejBDYzM1dGV3UWxFT25yaUJKcTUyb0VOR0pUMk5PeXExRXp3Z2M2dg", + "userVerification": "" + } +} diff --git a/selfservice/strategy/passkey/fixtures/registration/success/android/response.json b/selfservice/strategy/passkey/fixtures/registration/success/android/response.json new file mode 100644 index 000000000000..8b480b356790 --- /dev/null +++ b/selfservice/strategy/passkey/fixtures/registration/success/android/response.json @@ -0,0 +1,9 @@ +{ + "id": "mK2RV0b2NUGDsj8QqH0XtQ", + "rawId": "mK2RV0b2NUGDsj8QqH0XtQ", + "response": { + "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUJYVxRmHaAcJuz7n2X5FJILFPwxIhVpoURyBRglMxnFpdAAAAAOqbjWZNAR0hPOS2tIy1ddQAEJitkVdG9jVBg7I_EKh9F7WlAQIDJiABIVggjEkfDDjIm8yAYfth4u0EV7ApX4kclQONhpK5BLc7W6wiWCCHiHhRNqf8Qhc7bjoIFTqw4lafiC7yrXvojU_WMNcutA", + "clientDataJson": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibUZ0QXdtdEREZHdjTzYyMDBJMkg2b1dqek9pRjIxbFpoUVZsckM0dGRhVSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOlMyUmZOWWdKbVFpS2dkNi1zZGJqVzdwaGNMX09UUDR2R0U4TDUxUTJHQjAiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20udHJwLmFuZC5wZXJzb25hbC5xbCJ9" + }, + "type": "public-key" +} diff --git a/selfservice/strategy/passkey/fixtures/registration/success/identity.json b/selfservice/strategy/passkey/fixtures/registration/success/browser/identity.json similarity index 100% rename from selfservice/strategy/passkey/fixtures/registration/success/identity.json rename to selfservice/strategy/passkey/fixtures/registration/success/browser/identity.json diff --git a/selfservice/strategy/passkey/fixtures/registration/success/internal_context.json b/selfservice/strategy/passkey/fixtures/registration/success/browser/internal_context.json similarity index 100% rename from selfservice/strategy/passkey/fixtures/registration/success/internal_context.json rename to selfservice/strategy/passkey/fixtures/registration/success/browser/internal_context.json diff --git a/selfservice/strategy/passkey/fixtures/registration/success/response.json b/selfservice/strategy/passkey/fixtures/registration/success/browser/response.json similarity index 100% rename from selfservice/strategy/passkey/fixtures/registration/success/response.json rename to selfservice/strategy/passkey/fixtures/registration/success/browser/response.json diff --git a/selfservice/strategy/passkey/passkey_registration_test.go b/selfservice/strategy/passkey/passkey_registration_test.go index 86a7a7992e68..df70e3241aa9 100644 --- a/selfservice/strategy/passkey/passkey_registration_test.go +++ b/selfservice/strategy/passkey/passkey_registration_test.go @@ -8,6 +8,8 @@ import ( "net/url" "testing" + "github.com/ory/x/assertx" + "github.com/ory/kratos/selfservice/flow" "github.com/stretchr/testify/assert" @@ -28,12 +30,21 @@ import ( var ( flows = []string{"spa", "browser"} - //go:embed fixtures/registration/success/response.json + //go:embed fixtures/registration/success/browser/response.json registrationFixtureSuccessResponse []byte - //go:embed fixtures/registration/success/internal_context.json - registrationFixtureSuccessInternalContext []byte + + //go:embed fixtures/registration/success/browser/internal_context.json + registrationFixtureSuccessBrowserInternalContext []byte + + //go:embed fixtures/registration/success/android/response.json + registrationFixtureSuccessAndroidResponse []byte + + //go:embed fixtures/registration/success/android/internal_context.json + registrationFixtureSuccessAndroidInternalContext []byte + //go:embed fixtures/registration/failure/internal_context_missing_user_id.json registrationFixtureFailureInternalContextMissingUserID []byte + //go:embed fixtures/registration/failure/internal_context_wrong_user_id.json registrationFixtureFailureInternalContextWrongUserID []byte ) @@ -180,7 +191,7 @@ func TestRegistration(t *testing.T) { for _, f := range flows { t.Run("type="+f, func(t *testing.T) { - actual, _, _ := fix.submitPasskeyRegistration(t, f, testhelpers.NewClientWithCookies(t), values) + actual, _, _ := fix.submitPasskeyBrowserRegistration(t, f, testhelpers.NewClientWithCookies(t), values) assert.NotEmpty(t, gjson.Get(actual, "id").String(), "%s", actual) assert.Contains(t, gjson.Get(actual, "ui.action").String(), fix.publicTS.URL+registration.RouteSubmitFlow, "%s", actual) registrationhelpers.CheckFormContent(t, []byte(actual), node.PasskeyRegister, "csrf_token", "traits.username", "traits.foobar") @@ -220,7 +231,7 @@ func TestRegistration(t *testing.T) { for _, f := range flows { t.Run("type="+f, func(t *testing.T) { - actual, _, _ := fix.submitPasskeyRegistration(t, f, testhelpers.NewClientWithCookies(t), values, + actual, _, _ := fix.submitPasskeyBrowserRegistration(t, f, testhelpers.NewClientWithCookies(t), values, withInternalContext(sqlxx.JSONRawMessage(tc.internalContext))) if flowIsSPA(f) { assert.Equal(t, "Internal Server Error", gjson.Get(actual, "error.status").String(), "%s", actual) @@ -305,6 +316,43 @@ func TestRegistration(t *testing.T) { } }) + t.Run("case=should create the identity when using android", func(t *testing.T) { + fix.useRedirNoSessionTS() + t.Cleanup(fix.useRedirTS) + fix.disableSessionAfterRegistration() + + fix.conf.MustSet(fix.ctx, config.ViperKeyPasskeyRPID, "www.troweprice.com") + fix.conf.MustSet(fix.ctx, config.ViperKeyPasskeyRPOrigins, []string{"android:apk-key-hash:S2RfNYgJmQiKgd6-sdbjW7phcL_OTP4vGE8L51Q2GB0"}) + + for _, f := range flows { + t.Run("type="+f, func(t *testing.T) { + email := f + "-" + testhelpers.RandomEmail() + userID := f + "-user-" + randx.MustString(8, randx.AlphaNum) + + expectReturnTo := fix.redirNoSessionTS.URL + "/registration-return-ts" + actual, res, _ := fix.submitPasskeyAndroidRegistration(t, f, testhelpers.NewClientWithCookies(t), func(v url.Values) { + values(email)(v) + v.Set(node.PasskeyRegister, string(registrationFixtureSuccessAndroidResponse)) + }, withUserID(userID)) + + if f == "spa" { + expectReturnTo = fix.publicTS.URL + assert.Equal(t, email, gjson.Get(actual, "identity.traits.username").String(), "%s", actual) + assert.False(t, gjson.Get(actual, "session").Exists(), "because the registration yielded no session, the user is not expected to be signed in: %s", actual) + } else { + assert.Equal(t, "null\n", actual, "because the registration yielded no session, the user is not expected to be signed in: %s", actual) + } + + assert.Contains(t, res.Request.URL.String(), expectReturnTo, "%+v\n\t%s", res.Request, assertx.PrettifyJSONPayload(t, actual)) + + i, _, err := fix.reg.PrivilegedIdentityPool().FindByCredentialsIdentifier(fix.ctx, identity.CredentialsTypePasskey, userID) + require.NoError(t, err) + assert.Equal(t, "aal1", i.InternalAvailableAAL.String) + assert.Equal(t, email, gjson.GetBytes(i.Traits, "username").String(), "%s", actual) + }) + } + }) + t.Run("case=should accept valid transient payload", func(t *testing.T) { fix.useRedirNoSessionTS() t.Cleanup(fix.useRedirTS) diff --git a/selfservice/strategy/passkey/testfixture_test.go b/selfservice/strategy/passkey/testfixture_test.go index 1f3090177341..3f0fadfd2387 100644 --- a/selfservice/strategy/passkey/testfixture_test.go +++ b/selfservice/strategy/passkey/testfixture_test.go @@ -241,12 +241,6 @@ type submitPasskeyOpt struct { internalContext sqlxx.JSONRawMessage } -func newSubmitPasskeyOpt() *submitPasskeyOpt { - return &submitPasskeyOpt{ - internalContext: registrationFixtureSuccessInternalContext, - } -} - type submitPasskeyOption func(o *submitPasskeyOpt) func withUserID(id string) submitPasskeyOption { @@ -261,6 +255,29 @@ func withInternalContext(ic sqlxx.JSONRawMessage) submitPasskeyOption { } } +func (fix *fixture) submitPasskeyBrowserRegistration( + t *testing.T, + flowType string, + client *http.Client, + cb func(values url.Values), + opts ...submitPasskeyOption, +) (string, *http.Response, *kratos.RegistrationFlow) { + return fix.submitPasskeyRegistration(t, flowType, client, cb, append([]submitPasskeyOption{withInternalContext(registrationFixtureSuccessBrowserInternalContext)}, opts...)...) +} + +func (fix *fixture) submitPasskeyAndroidRegistration( + t *testing.T, + flowType string, + client *http.Client, + cb func(values url.Values), + opts ...submitPasskeyOption, +) (string, *http.Response, *kratos.RegistrationFlow) { + return fix.submitPasskeyRegistration(t, flowType, client, cb, + append([]submitPasskeyOption{withInternalContext( + registrationFixtureSuccessAndroidInternalContext, + )}, opts...)...) +} + func (fix *fixture) submitPasskeyRegistration( t *testing.T, flowType string, @@ -268,7 +285,7 @@ func (fix *fixture) submitPasskeyRegistration( cb func(values url.Values), opts ...submitPasskeyOption, ) (string, *http.Response, *kratos.RegistrationFlow) { - o := newSubmitPasskeyOpt() + o := &submitPasskeyOpt{} for _, fn := range opts { fn(o) } @@ -302,7 +319,7 @@ func (fix *fixture) submitPasskeyRegistration( } func (fix *fixture) makeRegistration(t *testing.T, flowType string, values func(v url.Values), opts ...submitPasskeyOption) (actual string, res *http.Response, fetchedFlow *registration.Flow) { - actual, res, actualFlow := fix.submitPasskeyRegistration(t, flowType, testhelpers.NewClientWithCookies(t), values, opts...) + actual, res, actualFlow := fix.submitPasskeyBrowserRegistration(t, flowType, testhelpers.NewClientWithCookies(t), values, opts...) fetchedFlow, err := fix.reg.RegistrationFlowPersister().GetRegistrationFlow(fix.ctx, uuid.FromStringOrNil(actualFlow.Id)) require.NoError(t, err)