From 5c4321d8d605c5c09537e345f56b447ac5856f95 Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Thu, 25 Aug 2022 11:49:54 +0200 Subject: [PATCH] fix: regression in session store --- contrib/quickstart/5-min/hydra.yml | 65 +++++++++++++++- go.mod | 1 + oauth2/.snapshots/TestUnmarshalSession.json | 49 ++++++++++++ oauth2/fixtures/v1.11.8-session.json | 47 +++++++++++ oauth2/fixtures/v1.11.9-session.json | 47 +++++++++++ oauth2/session.go | 70 +++++++++++++++++ oauth2/session_test.go | 86 +++++++++++++++++++++ 7 files changed, 362 insertions(+), 3 deletions(-) create mode 100644 oauth2/.snapshots/TestUnmarshalSession.json create mode 100644 oauth2/fixtures/v1.11.8-session.json create mode 100644 oauth2/fixtures/v1.11.9-session.json create mode 100644 oauth2/session_test.go diff --git a/contrib/quickstart/5-min/hydra.yml b/contrib/quickstart/5-min/hydra.yml index 8d69cc1d243..95472d3f95d 100644 --- a/contrib/quickstart/5-min/hydra.yml +++ b/contrib/quickstart/5-min/hydra.yml @@ -1,6 +1,28 @@ +version: v1.10.7 serve: cookies: same_site_mode: Lax + public: + cors: + enabled: false # CORS is managed by CloudFront + +webfinger: + oidc_discovery: + supported_claims: + - sub + - persona_id + - persona_krn + - grantType + - market + - zone + supported_scope: + - offline + - offline_access + - openid + - default + jwks: + broadcast_keys: + - hydra.openid.id-token urls: self: @@ -16,7 +38,44 @@ secrets: oidc: subject_identifiers: supported_types: - - pairwise - public - pairwise: - salt: youReallyNeedToChangeThis + +strategies: + access_token: jwt + scope: wildcard + +oauth2: + session: + encrypt_at_rest: false + exclude_not_before_claim: false + allowed_top_level_claims: + - persona_id + - persona_krn + - grantType + - market + - zone + - login_session_id + hashers: + bcrypt: + cost: 10 + pkce: + enforced_for_public_clients: true + enforced: false + client_credentials: + default_grant_allowed_scope: false + expose_internal_errors: false + +ttl: + auth_code: 2m + access_token: 5m + id_token: 1h + refresh_token: 24h + login_consent_request: 30m + +log: + leak_sensitive_values: true + format: json + level: trace + +sqa: + opt_out: true diff --git a/go.mod b/go.mod index 86302bc483f..11791a5e293 100644 --- a/go.mod +++ b/go.mod @@ -63,6 +63,7 @@ require ( github.com/spf13/cobra v1.4.0 github.com/stretchr/testify v1.7.0 github.com/tidwall/gjson v1.14.1 + github.com/tidwall/sjson v1.2.4 github.com/toqueteos/webbrowser v1.2.0 github.com/urfave/negroni v1.0.0 go.uber.org/automaxprocs v1.3.0 diff --git a/oauth2/.snapshots/TestUnmarshalSession.json b/oauth2/.snapshots/TestUnmarshalSession.json new file mode 100644 index 00000000000..723df624f4a --- /dev/null +++ b/oauth2/.snapshots/TestUnmarshalSession.json @@ -0,0 +1,49 @@ +{ + "id_token": { + "id_token_claims": { + "jti": "", + "iss": "http://127.0.0.1:4444/", + "sub": "foo@bar.com", + "aud": [ + "auth-code-client" + ], + "nonce": "mbxojlzlkefzmlecvrzfkmpm", + "exp": "0001-01-01T00:00:00Z", + "iat": "2022-08-25T09:21:04Z", + "rat": "2022-08-25T09:20:54Z", + "auth_time": "2022-08-25T09:21:01Z", + "at_hash": "", + "acr": "0", + "amr": [], + "c_hash": "", + "ext": { + "sid": "177e1f44-a1e9-415c-bfa3-8b62280b182d" + } + }, + "headers": { + "extra": { + "kid": "public:hydra.openid.id-token" + } + }, + "expires_at": { + "access_token": "2022-08-25T09:26:05Z", + "authorize_code": "2022-08-25T09:23:04.432089764Z", + "refresh_token": "2022-08-26T09:21:05Z" + }, + "username": "", + "subject": "foo@bar.com" + }, + "extra": {}, + "kid": "public:hydra.jwt.access-token", + "client_id": "auth-code-client", + "consent_challenge": "2261efbd447044a1b2f76b05c6aca164", + "exclude_not_before_claim": false, + "allowed_top_level_claims": [ + "persona_id", + "persona_krn", + "grantType", + "market", + "zone", + "login_session_id" + ] +} diff --git a/oauth2/fixtures/v1.11.8-session.json b/oauth2/fixtures/v1.11.8-session.json new file mode 100644 index 00000000000..a7070d03c32 --- /dev/null +++ b/oauth2/fixtures/v1.11.8-session.json @@ -0,0 +1,47 @@ +{ + "idToken": { + "Claims": { + "JTI": "", + "Issuer": "http://127.0.0.1:4444/", + "Subject": "foo@bar.com", + "Audience": ["auth-code-client"], + "Nonce": "mbxojlzlkefzmlecvrzfkmpm", + "ExpiresAt": "0001-01-01T00:00:00Z", + "IssuedAt": "2022-08-25T09:21:04Z", + "RequestedAt": "2022-08-25T09:20:54Z", + "AuthTime": "2022-08-25T09:21:01Z", + "AccessTokenHash": "", + "AuthenticationContextClassReference": "0", + "AuthenticationMethodsReferences": [], + "CodeHash": "", + "Extra": { + "sid": "177e1f44-a1e9-415c-bfa3-8b62280b182d" + } + }, + "Headers": { + "Extra": { + "kid": "public:hydra.openid.id-token" + } + }, + "ExpiresAt": { + "access_token": "2022-08-25T09:26:05Z", + "authorize_code": "2022-08-25T09:23:04.432089764Z", + "refresh_token": "2022-08-26T09:21:05Z" + }, + "Username": "", + "Subject": "foo@bar.com" + }, + "extra": {}, + "KID": "public:hydra.jwt.access-token", + "ClientID": "auth-code-client", + "ConsentChallenge": "2261efbd447044a1b2f76b05c6aca164", + "ExcludeNotBeforeClaim": false, + "AllowedTopLevelClaims": [ + "persona_id", + "persona_krn", + "grantType", + "market", + "zone", + "login_session_id" + ] +} diff --git a/oauth2/fixtures/v1.11.9-session.json b/oauth2/fixtures/v1.11.9-session.json new file mode 100644 index 00000000000..2ded034a556 --- /dev/null +++ b/oauth2/fixtures/v1.11.9-session.json @@ -0,0 +1,47 @@ +{ + "id_token": { + "id_token_claims": { + "jti": "", + "iss": "http://127.0.0.1:4444/", + "sub": "foo@bar.com", + "aud": ["auth-code-client"], + "nonce": "mbxojlzlkefzmlecvrzfkmpm", + "exp": "0001-01-01T00:00:00Z", + "iat": "2022-08-25T09:21:04Z", + "rat": "2022-08-25T09:20:54Z", + "auth_time": "2022-08-25T09:21:01Z", + "at_hash": "", + "acr": "0", + "amr": [], + "c_hash": "", + "ext": { + "sid": "177e1f44-a1e9-415c-bfa3-8b62280b182d" + } + }, + "headers": { + "extra": { + "kid": "public:hydra.openid.id-token" + } + }, + "expires_at": { + "access_token": "2022-08-25T09:26:05Z", + "authorize_code": "2022-08-25T09:23:04.432089764Z", + "refresh_token": "2022-08-26T09:21:05Z" + }, + "username": "", + "subject": "foo@bar.com" + }, + "extra": {}, + "kid": "public:hydra.jwt.access-token", + "client_id": "auth-code-client", + "consent_challenge": "2261efbd447044a1b2f76b05c6aca164", + "exclude_not_before_claim": false, + "allowed_top_level_claims": [ + "persona_id", + "persona_krn", + "grantType", + "market", + "zone", + "login_session_id" + ] +} diff --git a/oauth2/session.go b/oauth2/session.go index 979fe0a4f8f..9647a4af60e 100644 --- a/oauth2/session.go +++ b/oauth2/session.go @@ -21,8 +21,13 @@ package oauth2 import ( + "encoding/json" "time" + "github.com/pkg/errors" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" + "github.com/mohae/deepcopy" "github.com/ory/fosite" @@ -124,3 +129,68 @@ func (s *Session) Clone() fosite.Session { return deepcopy.Copy(s).(fosite.Session) } + +var keyRewrites = map[string]string{ + "Extra": "extra", + "KID": "kid", + "ClientID": "client_id", + "ConsentChallenge": "consent_challenge", + "ExcludeNotBeforeClaim": "exclude_not_before_claim", + "AllowedTopLevelClaims": "allowed_top_level_claims", + "idToken.Headers.Extra": "id_token.headers.extra", + "idToken.ExpiresAt": "id_token.expires_at", + "idToken.Username": "id_token.username", + "idToken.Subject": "id_token.subject", + "idToken.Claims.JTI": "id_token.id_token_claims.jti", + "idToken.Claims.Issuer": "id_token.id_token_claims.iss", + "idToken.Claims.Subject": "id_token.id_token_claims.sub", + "idToken.Claims.Audience": "id_token.id_token_claims.aud", + "idToken.Claims.Nonce": "id_token.id_token_claims.nonce", + "idToken.Claims.ExpiresAt": "id_token.id_token_claims.exp", + "idToken.Claims.IssuedAt": "id_token.id_token_claims.iat", + "idToken.Claims.RequestedAt": "id_token.id_token_claims.rat", + "idToken.Claims.AuthTime": "id_token.id_token_claims.auth_time", + "idToken.Claims.AccessTokenHash": "id_token.id_token_claims.at_hash", + "idToken.Claims.AuthenticationContextClassReference": "id_token.id_token_claims.acr", + "idToken.Claims.AuthenticationMethodsReferences": "id_token.id_token_claims.amr", + "idToken.Claims.CodeHash": "id_token.id_token_claims.c_hash", + "idToken.Claims.Extra": "id_token.id_token_claims.ext", +} + +func (s *Session) UnmarshalJSON(in []byte) (err error) { + type t Session + interpret := in + parsed := gjson.ParseBytes(in) + + for orig, update := range keyRewrites { + if !parsed.Get(orig).Exists() { + continue + } + interpret, err = sjson.SetRawBytes(interpret, update, []byte(parsed.Get(orig).Raw)) + if err != nil { + return errors.WithStack(err) + } + } + + for orig := range keyRewrites { + interpret, err = sjson.DeleteBytes(interpret, orig) + if err != nil { + return errors.WithStack(err) + } + } + + if parsed.Get("idToken").Exists() { + interpret, err = sjson.DeleteBytes(interpret, "idToken") + if err != nil { + return errors.WithStack(err) + } + } + + var tt t + if err := json.Unmarshal(interpret, &tt); err != nil { + return errors.WithStack(err) + } + + *s = Session(tt) + return nil +} diff --git a/oauth2/session_test.go b/oauth2/session_test.go new file mode 100644 index 00000000000..3422645700c --- /dev/null +++ b/oauth2/session_test.go @@ -0,0 +1,86 @@ +package oauth2 + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ory/fosite" + "github.com/ory/fosite/handler/openid" + "github.com/ory/fosite/token/jwt" + "github.com/ory/x/assertx" + "github.com/ory/x/snapshotx" + + _ "embed" +) + +//go:embed fixtures/v1.11.8-session.json +var v1118Session []byte + +//go:embed fixtures/v1.11.9-session.json +var v1119Session []byte + +func parseTime(t *testing.T, ts string) time.Time { + out, err := time.Parse(time.RFC3339Nano, ts) + require.NoError(t, err) + return out +} + +func TestUnmarshalSession(t *testing.T) { + expect := &Session{ + DefaultSession: &openid.DefaultSession{ + Claims: &jwt.IDTokenClaims{ + JTI: "", + Issuer: "http://127.0.0.1:4444/", + Subject: "foo@bar.com", + Audience: []string{"auth-code-client"}, + Nonce: "mbxojlzlkefzmlecvrzfkmpm", + ExpiresAt: parseTime(t, "0001-01-01T00:00:00Z"), + IssuedAt: parseTime(t, "2022-08-25T09:21:04Z"), + RequestedAt: parseTime(t, "2022-08-25T09:20:54Z"), + AuthTime: parseTime(t, "2022-08-25T09:21:01Z"), + AccessTokenHash: "", + AuthenticationContextClassReference: "0", + AuthenticationMethodsReferences: []string{}, + CodeHash: "", + Extra: map[string]interface{}{ + "sid": "177e1f44-a1e9-415c-bfa3-8b62280b182d", + }, + }, + Headers: &jwt.Headers{Extra: map[string]interface{}{ + "kid": "public:hydra.openid.id-token", + }}, + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.AccessToken: parseTime(t, "2022-08-25T09:26:05Z"), + fosite.AuthorizeCode: parseTime(t, "2022-08-25T09:23:04.432089764Z"), + fosite.RefreshToken: parseTime(t, "2022-08-26T09:21:05Z"), + }, + Username: "", + Subject: "foo@bar.com", + }, + Extra: map[string]interface{}{}, + KID: "public:hydra.jwt.access-token", + ClientID: "auth-code-client", + ConsentChallenge: "2261efbd447044a1b2f76b05c6aca164", + ExcludeNotBeforeClaim: false, + AllowedTopLevelClaims: []string{ + "persona_id", + "persona_krn", + "grantType", + "market", + "zone", + "login_session_id", + }, + } + + var actual Session + require.NoError(t, json.Unmarshal(v1118Session, &actual)) + assertx.EqualAsJSON(t, expect, &actual) + snapshotx.SnapshotTExcept(t, &actual, nil) + + require.NoError(t, json.Unmarshal(v1119Session, &actual)) + assertx.EqualAsJSON(t, expect, &actual) + snapshotx.SnapshotTExcept(t, &actual, nil) +}