diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index cb5f0b6efceb..abb897fe24c5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -152,6 +152,8 @@ jobs: name: Install Firefox - uses: browser-actions/setup-geckodriver@latest name: Install Geckodriver + with: + geckodriver-version: 0.32.0 - uses: ory/ci/checkout@master with: fetch-depth: 2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a34aa1c43ba..abb364c98f86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ **Table of Contents** -- [ (2022-12-19)](#2022-12-19) +- [ (2022-12-20)](#2022-12-20) - [Breaking Changes](#breaking-changes) - [Bug Fixes](#bug-fixes) - [Features](#features) @@ -285,7 +285,7 @@ -# [](https://github.com/ory/kratos/compare/v0.11.0...v) (2022-12-19) +# [](https://github.com/ory/kratos/compare/v0.11.0...v) (2022-12-20) ## Breaking Changes @@ -293,6 +293,8 @@ The `/admin/courier/messages` endpoint now uses `keysetpagination` instead. ### Bug Fixes +- Add missing indexes ([#2973](https://github.com/ory/kratos/issues/2973)) + ([bbb3995](https://github.com/ory/kratos/commit/bbb399572926bd433928b22764f7b3558bb0c21d)) - Add missing indexes for identity delete ([#2952](https://github.com/ory/kratos/issues/2952)) ([dc311f9](https://github.com/ory/kratos/commit/dc311f9a9dc0dbb26e2375b3cd4232a4e8cccb61)): @@ -307,9 +309,24 @@ The `/admin/courier/messages` endpoint now uses `keysetpagination` instead. ([ae8ad7b](https://github.com/ory/kratos/commit/ae8ad7be5b6f3dbb9142bee55448a71c7df44e52)) - Flaky test now stable ([4e5dcd0](https://github.com/ory/kratos/commit/4e5dcd0df6baffda8b15eda37fd7a247793f3297)) +- Listing sessions query ([#2958](https://github.com/ory/kratos/issues/2958)) + ([3e06c99](https://github.com/ory/kratos/commit/3e06c991ad557f4629ef7412c256ede2386a7bed)), + closes [#2930](https://github.com/ory/kratos/issues/2930) +- Pin geckodriver version to bypass GitHub API quota + ([#2972](https://github.com/ory/kratos/issues/2972)) + ([585cb9e](https://github.com/ory/kratos/commit/585cb9e79be5de8b3d684313edb72bb703ffaa78)) - Respect `return_to` URL parameter in registration flow when the user is already registered ([#2957](https://github.com/ory/kratos/issues/2957)) ([3462ce1](https://github.com/ory/kratos/commit/3462ce1512d03529b613421a69bcf4c1d5e98e08)) +- Spurious cancelation of async webhooks, better tracing + ([#2969](https://github.com/ory/kratos/issues/2969)) + ([72de640](https://github.com/ory/kratos/commit/72de640bad75da29424222bd613a21d10e1811ec)): + + Previously, async webhooks (response.ignore=true) would be canceled early once + the incoming Kratos request was served and it's associated context released. + We now dissociate the cancellation of async hooks from the normal request + processing flow. + - Update pquerna/otp to fix TOTP URL encoding ([#2951](https://github.com/ory/kratos/issues/2951)) ([7248636](https://github.com/ory/kratos/commit/72486368f5403c02772e4a99ed9edc34e84c217c)): @@ -319,8 +336,13 @@ The `/admin/courier/messages` endpoint now uses `keysetpagination` instead. apps, and would show up in the issuer name, e.g. "My+Issuer" instead of "My Issuer". +- Webhook tracing instrumentation+memory leak + ([f0044a3](https://github.com/ory/kratos/commit/f0044a365b39a5f940d6d268977744f8fcb2e49b)) + ### Features +- Add client IP to span events + ([7ce3a74](https://github.com/ory/kratos/commit/7ce3a7471243898e111ca3e2b5d1346131c55dae)) - Add NID to logs in courier ([#2956](https://github.com/ory/kratos/issues/2956)) ([b407aa9](https://github.com/ory/kratos/commit/b407aa9427382f38dd8a992a6998202a7b6ba83a)) diff --git a/driver/registry_default.go b/driver/registry_default.go index ce94e333047b..67d185224cbf 100644 --- a/driver/registry_default.go +++ b/driver/registry_default.go @@ -517,6 +517,7 @@ func (m *RegistryDefault) ContinuityCookieManager(ctx context.Context) sessions. func (m *RegistryDefault) Tracer(ctx context.Context) *otelx.Tracer { if m.trc == nil { + m.Logger().WithError(errors.WithStack(errors.New(""))).Warn("No tracer setup in RegistryDefault") return otelx.NewNoop(m.l, m.Config().Tracing(ctx)) // should never happen } return m.trc diff --git a/go.mod b/go.mod index 0a9e13c2296a..957e6ce45d8f 100644 --- a/go.mod +++ b/go.mod @@ -77,7 +77,7 @@ require ( github.com/ory/jsonschema/v3 v3.0.7 github.com/ory/mail/v3 v3.0.0 github.com/ory/nosurf v1.2.7 - github.com/ory/x v0.0.519 + github.com/ory/x v0.0.523 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 @@ -96,10 +96,10 @@ require ( go.opentelemetry.io/otel v1.11.1 go.opentelemetry.io/otel/trace v1.11.1 golang.org/x/crypto v0.1.0 - golang.org/x/net v0.1.0 + golang.org/x/net v0.3.0 golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 golang.org/x/sync v0.1.0 - golang.org/x/tools v0.2.0 + golang.org/x/tools v0.4.0 ) require ( @@ -123,7 +123,7 @@ require ( github.com/boombuler/barcode v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/cfssl v1.6.1 // indirect github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect @@ -151,7 +151,6 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fullstorydev/grpcurl v1.8.1 // indirect github.com/fxamacker/cbor/v2 v2.4.0 // indirect - github.com/go-bindata/go-bindata v3.1.2+incompatible // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.21.4 // indirect @@ -202,7 +201,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/serf v0.9.7 // indirect github.com/huandu/xstrings v1.3.2 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.13.0 // indirect github.com/jackc/pgio v1.0.0 // indirect @@ -225,7 +224,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.0 // indirect github.com/lib/pq v1.10.7 // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect @@ -269,7 +268,7 @@ require ( github.com/soheilhy/cmux v0.1.5 // indirect github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect - github.com/spf13/afero v1.9.2 // indirect + github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/viper v1.12.0 // indirect @@ -314,10 +313,10 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.7.0 // indirect go.uber.org/zap v1.17.0 // indirect - golang.org/x/mod v0.6.0 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/term v0.1.0 // indirect - golang.org/x/text v0.4.0 // indirect + golang.org/x/mod v0.7.0 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/term v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect golang.org/x/time v0.1.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 8e8bf9b4677e..6d2cbbf528ef 100644 --- a/go.sum +++ b/go.sum @@ -216,8 +216,9 @@ github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -390,8 +391,6 @@ github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49P github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE= -github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -801,8 +800,9 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= @@ -963,8 +963,8 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= @@ -1141,22 +1141,8 @@ github.com/ory/sessions v1.2.2-0.20220110165800-b09c17334dc2 h1:zm6sDvHy/U9XrGpi github.com/ory/sessions v1.2.2-0.20220110165800-b09c17334dc2/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE= github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= -github.com/ory/x v0.0.513 h1:45AruNHDwqhTvNtMnQy2/wYooMv+raVhuOP454mV/Os= -github.com/ory/x v0.0.513/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= -github.com/ory/x v0.0.517-0.20221125124208-eeecbb39902b h1:MkAOUX6klLRRtz6YIG694Q9rDADrW9LuPrVwvr5CWGE= -github.com/ory/x v0.0.517-0.20221125124208-eeecbb39902b/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= -github.com/ory/x v0.0.517-0.20221128091752-551ffb438eeb h1:a3U3DXqBbUr90ElIuzwRW/QI9akBZhgR49c39faItmA= -github.com/ory/x v0.0.517-0.20221128091752-551ffb438eeb/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= -github.com/ory/x v0.0.517-0.20221128100459-e2df1f5d26ba h1:z/ElG4rbwrxjkqaTsEDNEP3NyxrM5GW00WPuiVkgCb8= -github.com/ory/x v0.0.517-0.20221128100459-e2df1f5d26ba/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= -github.com/ory/x v0.0.518-0.20221130104542-b94c6f320cce h1:ZeWblm5vjgkq/R/xD+3ZU1L5UvNp93CrhZNyvlGgjZQ= -github.com/ory/x v0.0.518-0.20221130104542-b94c6f320cce/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= -github.com/ory/x v0.0.518-0.20221130125643-031a59af1925 h1:PvrVOQXY+VPASWyNPRqFPhUBUV8C68wmIUXZ7PEZRRA= -github.com/ory/x v0.0.518-0.20221130125643-031a59af1925/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= -github.com/ory/x v0.0.518 h1:vIo1zLI+HpqqjzuBVuowYWyDx1wmqPjFFvZBcXHmEdE= -github.com/ory/x v0.0.518/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= -github.com/ory/x v0.0.519 h1:T8/LbbQQqm+3P7bfI838T7eECv6+laXlvIyCp0QB+R8= -github.com/ory/x v0.0.519/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= +github.com/ory/x v0.0.523 h1:vn8e+8tV3RqD8RlvoE6lLPUnjpjua1ExJDMFy3Z5TAQ= +github.com/ory/x v0.0.523/go.mod h1:ayJio5x/fK4RwTgfgzs3JetOaaOSxso9hQjc3mFY8z0= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= @@ -1340,8 +1326,8 @@ github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY52 github.com/spf13/afero v1.3.4/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= +github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.2-0.20200723214538-8d17101741c8/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -1641,8 +1627,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1713,8 +1699,8 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1769,8 +1755,8 @@ golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1781,8 +1767,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1871,8 +1857,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/persistence/sql/migrations/sql/20221220124639000000_errors_index.down.sql b/persistence/sql/migrations/sql/20221220124639000000_errors_index.down.sql new file mode 100644 index 000000000000..706fa24ba79c --- /dev/null +++ b/persistence/sql/migrations/sql/20221220124639000000_errors_index.down.sql @@ -0,0 +1,3 @@ +CREATE INDEX selfservice_errors_nid_idx ON selfservice_errors (id, nid); + +DROP INDEX selfservice_errors_errors_nid_id_idx; diff --git a/persistence/sql/migrations/sql/20221220124639000000_errors_index.mysql.down.sql b/persistence/sql/migrations/sql/20221220124639000000_errors_index.mysql.down.sql new file mode 100644 index 000000000000..d07b35940e31 --- /dev/null +++ b/persistence/sql/migrations/sql/20221220124639000000_errors_index.mysql.down.sql @@ -0,0 +1,6 @@ +CREATE INDEX selfservice_errors_nid_idx ON selfservice_errors (id, nid); + +-- needed for foreign key constraint, was there before implicitly +CREATE INDEX selfservice_errors_nid_only_idx ON selfservice_errors (nid); + +DROP INDEX selfservice_errors_errors_nid_id_idx ON selfservice_errors; diff --git a/persistence/sql/migrations/sql/20221220124639000000_errors_index.mysql.up.sql b/persistence/sql/migrations/sql/20221220124639000000_errors_index.mysql.up.sql new file mode 100644 index 000000000000..30cbda798d8b --- /dev/null +++ b/persistence/sql/migrations/sql/20221220124639000000_errors_index.mysql.up.sql @@ -0,0 +1,4 @@ +CREATE INDEX selfservice_errors_errors_nid_id_idx ON selfservice_errors (nid, id); + +-- This index is not needed anymore, because the primary ID index together with the new index cover all queries. +DROP INDEX selfservice_errors_nid_idx ON selfservice_errors; diff --git a/persistence/sql/migrations/sql/20221220124639000000_errors_index.up.sql b/persistence/sql/migrations/sql/20221220124639000000_errors_index.up.sql new file mode 100644 index 000000000000..072022573e58 --- /dev/null +++ b/persistence/sql/migrations/sql/20221220124639000000_errors_index.up.sql @@ -0,0 +1,4 @@ +CREATE INDEX selfservice_errors_errors_nid_id_idx ON selfservice_errors (nid, id); + +-- This index is not needed anymore, because the primary ID index together with the new index cover all queries. +DROP INDEX selfservice_errors_nid_idx; diff --git a/persistence/sql/persister_courier.go b/persistence/sql/persister_courier.go index e4c08e87be3d..a51a46e2da36 100644 --- a/persistence/sql/persister_courier.go +++ b/persistence/sql/persister_courier.go @@ -7,7 +7,6 @@ import ( "context" "database/sql" "encoding/json" - "fmt" "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" @@ -137,11 +136,7 @@ func (p *Persister) SetMessageStatus(ctx context.Context, id uuid.UUID, ms couri defer span.End() count, err := p.GetConnection(ctx).RawQuery( - // #nosec G201 - fmt.Sprintf( - "UPDATE %s SET status = ? WHERE id = ? AND nid = ?", - "courier_messages", - ), + "UPDATE courier_messages SET status = ? WHERE id = ? AND nid = ?", ms, id, p.NetworkID(ctx), @@ -162,11 +157,7 @@ func (p *Persister) IncrementMessageSendCount(ctx context.Context, id uuid.UUID) defer span.End() count, err := p.GetConnection(ctx).RawQuery( - // #nosec G201 - fmt.Sprintf( - "UPDATE %s SET send_count = send_count + 1 WHERE id = ? AND nid = ?", - "courier_messages", - ), + "UPDATE courier_messages SET send_count = send_count + 1 WHERE id = ? AND nid = ?", id, p.NetworkID(ctx), ).ExecWithCount() diff --git a/persistence/sql/persister_session.go b/persistence/sql/persister_session.go index 259b7fcdfb4b..e02a02e148db 100644 --- a/persistence/sql/persister_session.go +++ b/persistence/sql/persister_session.go @@ -58,6 +58,7 @@ func (p *Persister) GetSession(ctx context.Context, sid uuid.UUID, expandables s s.Identity = i } + s.Active = s.IsActive() return &s, nil } @@ -77,7 +78,11 @@ func (p *Persister) ListSessions(ctx context.Context, active *bool, paginatorOpt if err := p.Transaction(ctx, func(ctx context.Context, c *pop.Connection) error { q := c.Where("nid = ?", nid) if active != nil { - q = q.Where("active = ?", *active) + if *active { + q.Where("active = ? AND expires_at >= ?", *active, time.Now().UTC()) + } else { + q.Where("(active = ? OR expires_at < ?)", *active, time.Now().UTC()) + } } // if len(expandables) > 0 { @@ -106,6 +111,7 @@ func (p *Persister) ListSessions(ctx context.Context, active *bool, paginatorOpt return err } + sess.Active = sess.IsActive() sess.Identity = i } } @@ -114,7 +120,7 @@ func (p *Persister) ListSessions(ctx context.Context, active *bool, paginatorOpt return nil, 0, nil, err } - s, nextPage := keysetpagination.Result[session.Session](s, paginator) + s, nextPage := keysetpagination.Result(s, paginator) return s, t, nextPage, nil } @@ -133,7 +139,11 @@ func (p *Persister) ListSessionsByIdentity(ctx context.Context, iID uuid.UUID, a q = q.Where("id != ?", except) } if active != nil { - q = q.Where("active = ?", *active) + if *active { + q.Where("active = ? AND expires_at >= ?", *active, time.Now().UTC()) + } else { + q.Where("(active = ? OR expires_at < ?)", *active, time.Now().UTC()) + } } if len(expandables) > 0 { q = q.Eager(expandables.ToEager()...) @@ -152,12 +162,13 @@ func (p *Persister) ListSessionsByIdentity(ctx context.Context, iID uuid.UUID, a } if expandables.Has(session.ExpandSessionIdentity) { - for _, s := range s { - i, err := p.GetIdentity(ctx, s.IdentityID) - if err != nil { - return err - } + i, err := p.GetIdentity(ctx, iID) + if err != nil { + return sqlcon.HandleError(err) + } + for _, s := range s { + s.Active = s.IsActive() s.Identity = i } } diff --git a/selfservice/flow/login/hook.go b/selfservice/flow/login/hook.go index 9e98cb922f81..dea6d3587204 100644 --- a/selfservice/flow/login/hook.go +++ b/selfservice/flow/login/hook.go @@ -21,6 +21,7 @@ import ( "github.com/ory/kratos/ui/container" "github.com/ory/kratos/ui/node" "github.com/ory/kratos/x" + "github.com/ory/x/httpx" "github.com/ory/x/otelx/semconv" ) @@ -174,6 +175,7 @@ func (e *HookExecutor) PostLoginHook(w http.ResponseWriter, r *http.Request, g n trace.WithAttributes( attribute.String(semconv.AttrIdentityID, i.ID.String()), attribute.String(semconv.AttrNID, i.NID.String()), + attribute.String(semconv.AttrClientIP, httpx.ClientIP(r)), attribute.String("flow", string(flow.TypeAPI)), ), ) @@ -202,6 +204,7 @@ func (e *HookExecutor) PostLoginHook(w http.ResponseWriter, r *http.Request, g n trace.WithAttributes( attribute.String(semconv.AttrIdentityID, i.ID.String()), attribute.String(semconv.AttrNID, i.NID.String()), + attribute.String(semconv.AttrClientIP, httpx.ClientIP(r)), attribute.String("flow", string(flow.TypeBrowser)), ), ) diff --git a/selfservice/flow/registration/hook.go b/selfservice/flow/registration/hook.go index 5ac8a82a2d01..17926a761a03 100644 --- a/selfservice/flow/registration/hook.go +++ b/selfservice/flow/registration/hook.go @@ -13,6 +13,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" + "github.com/ory/x/httpx" "github.com/ory/x/otelx/semconv" "github.com/ory/x/sqlcon" @@ -164,6 +165,7 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque trace.WithAttributes( attribute.String(semconv.AttrIdentityID, i.ID.String()), attribute.String(semconv.AttrNID, i.NID.String()), + attribute.String(semconv.AttrClientIP, httpx.ClientIP(r)), attribute.String("flow", string(a.Type)), ), ) diff --git a/selfservice/hook/web_hook.go b/selfservice/hook/web_hook.go index 98bc24fc27c3..f23701abbdf0 100644 --- a/selfservice/hook/web_hook.go +++ b/selfservice/hook/web_hook.go @@ -8,14 +8,18 @@ import ( "encoding/json" "fmt" "net/http" + "time" "github.com/pkg/errors" "github.com/tidwall/gjson" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" + semconv "go.opentelemetry.io/otel/semconv/v1.11.0" "go.opentelemetry.io/otel/trace" "github.com/ory/kratos/ui/node" "github.com/ory/x/jsonnetsecure" + "github.com/ory/x/otelx" "github.com/ory/kratos/identity" "github.com/ory/kratos/request" @@ -29,7 +33,6 @@ import ( "github.com/ory/kratos/session" "github.com/ory/kratos/text" "github.com/ory/kratos/x" - "github.com/ory/x/otelx" ) var ( @@ -98,177 +101,169 @@ func NewWebHook(r webHookDependencies, c json.RawMessage) *WebHook { } func (e *WebHook) ExecuteLoginPreHook(_ http.ResponseWriter, req *http.Request, flow *login.Flow) error { - ctx, _ := e.deps.Tracer(req.Context()).Tracer().Start(req.Context(), "selfservice.hook.ExecutePreLoginHook") - return e.execute(ctx, &templateContext{ - Flow: flow, - RequestHeaders: req.Header, - RequestMethod: req.Method, - RequestURL: x.RequestURL(req).String(), - RequestCookies: cookies(req), + return otelx.WithSpan(req.Context(), "selfservice.hook.ExecuteLoginPreHook", func(ctx context.Context) error { + return e.execute(ctx, &templateContext{ + Flow: flow, + RequestHeaders: req.Header, + RequestMethod: req.Method, + RequestURL: x.RequestURL(req).String(), + RequestCookies: cookies(req), + }) }) } func (e *WebHook) ExecuteLoginPostHook(_ http.ResponseWriter, req *http.Request, _ node.UiNodeGroup, flow *login.Flow, session *session.Session) error { - ctx, _ := e.deps.Tracer(req.Context()).Tracer().Start(req.Context(), "selfservice.hook.ExecutePostLoginHook") - return e.execute(ctx, &templateContext{ - Flow: flow, - RequestHeaders: req.Header, - RequestMethod: req.Method, - RequestURL: x.RequestURL(req).String(), - RequestCookies: cookies(req), - Identity: session.Identity, + return otelx.WithSpan(req.Context(), "selfservice.hook.ExecuteLoginPostHook", func(ctx context.Context) error { + return e.execute(ctx, &templateContext{ + Flow: flow, + RequestHeaders: req.Header, + RequestMethod: req.Method, + RequestURL: x.RequestURL(req).String(), + RequestCookies: cookies(req), + Identity: session.Identity, + }) }) } func (e *WebHook) ExecuteVerificationPreHook(_ http.ResponseWriter, req *http.Request, flow *verification.Flow) error { - ctx, _ := e.deps.Tracer(req.Context()).Tracer().Start(req.Context(), "selfservice.hook.ExecutePreVerificationHook") - return e.execute(ctx, &templateContext{ - Flow: flow, - RequestHeaders: req.Header, - RequestMethod: req.Method, - RequestURL: x.RequestURL(req).String(), - RequestCookies: cookies(req), + return otelx.WithSpan(req.Context(), "selfservice.hook.ExecuteVerificationPreHook", func(ctx context.Context) error { + return e.execute(ctx, &templateContext{ + Flow: flow, + RequestHeaders: req.Header, + RequestMethod: req.Method, + RequestURL: x.RequestURL(req).String(), + RequestCookies: cookies(req), + }) }) } func (e *WebHook) ExecutePostVerificationHook(_ http.ResponseWriter, req *http.Request, flow *verification.Flow, id *identity.Identity) error { - ctx, _ := e.deps.Tracer(req.Context()).Tracer().Start(req.Context(), "selfservice.hook.ExecutePostVerificationHook") - return e.execute(ctx, &templateContext{ - Flow: flow, - RequestHeaders: req.Header, - RequestMethod: req.Method, - RequestURL: x.RequestURL(req).String(), - RequestCookies: cookies(req), - Identity: id, + return otelx.WithSpan(req.Context(), "selfservice.hook.ExecutePostVerificationHook", func(ctx context.Context) error { + return e.execute(ctx, &templateContext{ + Flow: flow, + RequestHeaders: req.Header, + RequestMethod: req.Method, + RequestURL: x.RequestURL(req).String(), + RequestCookies: cookies(req), + Identity: id, + }) }) } func (e *WebHook) ExecuteRecoveryPreHook(_ http.ResponseWriter, req *http.Request, flow *recovery.Flow) error { - ctx, _ := e.deps.Tracer(req.Context()).Tracer().Start(req.Context(), "selfservice.hook.ExecutePreRecoveryHook") - return e.execute(ctx, &templateContext{ - Flow: flow, - RequestHeaders: req.Header, - RequestMethod: req.Method, - RequestCookies: cookies(req), - RequestURL: x.RequestURL(req).String(), + return otelx.WithSpan(req.Context(), "selfservice.hook.ExecuteRecoveryPreHook", func(ctx context.Context) error { + return e.execute(ctx, &templateContext{ + Flow: flow, + RequestHeaders: req.Header, + RequestMethod: req.Method, + RequestCookies: cookies(req), + RequestURL: x.RequestURL(req).String(), + }) }) } func (e *WebHook) ExecutePostRecoveryHook(_ http.ResponseWriter, req *http.Request, flow *recovery.Flow, session *session.Session) error { - ctx, _ := e.deps.Tracer(req.Context()).Tracer().Start(req.Context(), "selfservice.hook.ExecutePostRecoveryHook") - return e.execute(ctx, &templateContext{ - Flow: flow, - RequestHeaders: req.Header, - RequestMethod: req.Method, - RequestURL: x.RequestURL(req).String(), - RequestCookies: cookies(req), - Identity: session.Identity, + return otelx.WithSpan(req.Context(), "selfservice.hook.ExecutePostRecoveryHook", func(ctx context.Context) error { + return e.execute(ctx, &templateContext{ + Flow: flow, + RequestHeaders: req.Header, + RequestMethod: req.Method, + RequestURL: x.RequestURL(req).String(), + RequestCookies: cookies(req), + Identity: session.Identity, + }) }) } func (e *WebHook) ExecuteRegistrationPreHook(_ http.ResponseWriter, req *http.Request, flow *registration.Flow) error { - ctx, _ := e.deps.Tracer(req.Context()).Tracer().Start(req.Context(), "selfservice.hook.ExecuteRegistrationPreHook") - return e.execute(ctx, &templateContext{ - Flow: flow, - RequestHeaders: req.Header, - RequestMethod: req.Method, - RequestURL: x.RequestURL(req).String(), - RequestCookies: cookies(req), + return otelx.WithSpan(req.Context(), "selfservice.hook.ExecuteRegistrationPreHook", func(ctx context.Context) error { + return e.execute(ctx, &templateContext{ + Flow: flow, + RequestHeaders: req.Header, + RequestMethod: req.Method, + RequestURL: x.RequestURL(req).String(), + RequestCookies: cookies(req), + }) }) } func (e *WebHook) ExecutePostRegistrationPrePersistHook(_ http.ResponseWriter, req *http.Request, flow *registration.Flow, id *identity.Identity) error { - ctx, _ := e.deps.Tracer(req.Context()).Tracer().Start(req.Context(), "selfservice.hook.ExecutePostRegistrationPrePersistHook") if !gjson.GetBytes(e.conf, "can_interrupt").Bool() { return nil } - - return e.execute(ctx, &templateContext{ - Flow: flow, - RequestHeaders: req.Header, - RequestMethod: req.Method, - RequestURL: x.RequestURL(req).String(), - RequestCookies: cookies(req), - Identity: id, + return otelx.WithSpan(req.Context(), "selfservice.hook.ExecutePostRegistrationPrePersistHook", func(ctx context.Context) error { + return e.execute(ctx, &templateContext{ + Flow: flow, + RequestHeaders: req.Header, + RequestMethod: req.Method, + RequestURL: x.RequestURL(req).String(), + RequestCookies: cookies(req), + Identity: id, + }) }) } func (e *WebHook) ExecutePostRegistrationPostPersistHook(_ http.ResponseWriter, req *http.Request, flow *registration.Flow, session *session.Session) error { - ctx, _ := e.deps.Tracer(req.Context()).Tracer().Start(req.Context(), "selfservice.hook.ExecutePostRegistrationPostPersistHook") if gjson.GetBytes(e.conf, "can_interrupt").Bool() { return nil } - - return e.execute(ctx, &templateContext{ - Flow: flow, - RequestHeaders: req.Header, - RequestMethod: req.Method, - RequestURL: x.RequestURL(req).String(), - RequestCookies: cookies(req), - Identity: session.Identity, + return otelx.WithSpan(req.Context(), "selfservice.hook.ExecutePostRegistrationPostPersistHook", func(ctx context.Context) error { + return e.execute(ctx, &templateContext{ + Flow: flow, + RequestHeaders: req.Header, + RequestMethod: req.Method, + RequestURL: x.RequestURL(req).String(), + RequestCookies: cookies(req), + Identity: session.Identity, + }) }) } func (e *WebHook) ExecuteSettingsPreHook(_ http.ResponseWriter, req *http.Request, flow *settings.Flow) error { - ctx, _ := e.deps.Tracer(req.Context()).Tracer().Start(req.Context(), "selfservice.hook.ExecutePreSettingsHook") - return e.execute(ctx, &templateContext{ - Flow: flow, - RequestHeaders: req.Header, - RequestMethod: req.Method, - RequestURL: x.RequestURL(req).String(), - RequestCookies: cookies(req), + return otelx.WithSpan(req.Context(), "selfservice.hook.ExecuteSettingsPreHook", func(ctx context.Context) error { + return e.execute(ctx, &templateContext{ + Flow: flow, + RequestHeaders: req.Header, + RequestMethod: req.Method, + RequestURL: x.RequestURL(req).String(), + RequestCookies: cookies(req), + }) }) } func (e *WebHook) ExecuteSettingsPostPersistHook(_ http.ResponseWriter, req *http.Request, flow *settings.Flow, id *identity.Identity) error { - ctx, _ := e.deps.Tracer(req.Context()).Tracer().Start(req.Context(), "selfservice.hook.ExecuteSettingsPostPersistHook") if gjson.GetBytes(e.conf, "can_interrupt").Bool() { return nil } - - return e.execute(ctx, &templateContext{ - Flow: flow, - RequestHeaders: req.Header, - RequestMethod: req.Method, - RequestURL: x.RequestURL(req).String(), - RequestCookies: cookies(req), - Identity: id, + return otelx.WithSpan(req.Context(), "selfservice.hook.ExecuteSettingsPostPersistHook", func(ctx context.Context) error { + return e.execute(ctx, &templateContext{ + Flow: flow, + RequestHeaders: req.Header, + RequestMethod: req.Method, + RequestURL: x.RequestURL(req).String(), + RequestCookies: cookies(req), + Identity: id, + }) }) } func (e *WebHook) ExecuteSettingsPrePersistHook(_ http.ResponseWriter, req *http.Request, flow *settings.Flow, id *identity.Identity) error { - ctx, _ := e.deps.Tracer(req.Context()).Tracer().Start(req.Context(), "selfservice.hook.ExecuteSettingsPrePersistHook") if !gjson.GetBytes(e.conf, "can_interrupt").Bool() { return nil } - - return e.execute(ctx, &templateContext{ - Flow: flow, - RequestHeaders: req.Header, - RequestMethod: req.Method, - RequestURL: x.RequestURL(req).String(), - RequestCookies: cookies(req), - Identity: id, + return otelx.WithSpan(req.Context(), "selfservice.hook.ExecuteSettingsPrePersistHook", func(ctx context.Context) error { + return e.execute(ctx, &templateContext{ + Flow: flow, + RequestHeaders: req.Header, + RequestMethod: req.Method, + RequestURL: x.RequestURL(req).String(), + RequestCookies: cookies(req), + Identity: id, + }) }) } func (e *WebHook) execute(ctx context.Context, data *templateContext) error { - span := trace.SpanFromContext(ctx) - attrs := map[string]string{ - "webhook.http.method": data.RequestMethod, - "webhook.http.url": data.RequestURL, - "webhook.http.headers": fmt.Sprintf("%#v", data.RequestHeaders), - } - - if data.Identity != nil { - attrs["webhook.identity.id"] = data.Identity.ID.String() - } else { - attrs["webhook.identity.id"] = "" - } - - span.SetAttributes(otelx.StringAttrs(attrs)...) - defer span.End() - builder, err := request.NewBuilder(e.conf, e.deps) if err != nil { return err @@ -281,35 +276,78 @@ func (e *WebHook) execute(ctx context.Context, data *templateContext) error { return err } - errChan := make(chan error, 1) + attrs := semconv.HTTPClientAttributesFromHTTPRequest(req.Request) + if data.Identity != nil { + attrs = append(attrs, + attribute.String("webhook.identity.id", data.Identity.ID.String()), + attribute.String("webhook.identity.nid", data.Identity.NID.String()), + ) + } + + var ( + httpClient = e.deps.HTTPClient(ctx) + ignoreResponse = gjson.GetBytes(e.conf, "response.ignore").Bool() + canInterrupt = gjson.GetBytes(e.conf, "can_interrupt").Bool() + tracer = trace.SpanFromContext(ctx).TracerProvider().Tracer("kratos-webhooks") + spanOpts = []trace.SpanStartOption{trace.WithAttributes(attrs...)} + errChan = make(chan error, 1) + ) + + ctx, span := tracer.Start(ctx, "selfservice.webhook", spanOpts...) + e.deps.Logger().WithRequest(req.Request).Info("Dispatching webhook") + + req = req.WithContext(ctx) + if ignoreResponse { + // This is one of the few places where spawning a context.Background() is ok. We need to do this + // because the function runs asynchronously and we don't want to cancel the request if the + // incoming request context is cancelled. + // + // The webhook will still cancel after 30 seconds as that is the configured timeout for the HTTP client. + req = req.WithContext(context.Background()) + // spanOpts = append(spanOpts, trace.WithNewRoot()) + } + + startTime := time.Now() go func() { defer close(errChan) + defer span.End() - resp, err := e.deps.HTTPClient(ctx).Do(req.WithContext(ctx)) + resp, err := httpClient.Do(req) if err != nil { + span.SetStatus(codes.Error, err.Error()) errChan <- errors.WithStack(err) return } defer resp.Body.Close() + span.SetAttributes(semconv.HTTPAttributesFromHTTPStatusCode(resp.StatusCode)...) if resp.StatusCode >= http.StatusBadRequest { - if gjson.GetBytes(e.conf, "can_interrupt").Bool() { + span.SetStatus(codes.Error, "HTTP status code >= 400") + if canInterrupt { if err := parseWebhookResponse(resp); err != nil { + span.SetStatus(codes.Error, err.Error()) errChan <- err } } - errChan <- fmt.Errorf("web hook failed with status code %v", resp.StatusCode) - span.SetStatus(codes.Error, fmt.Sprintf("web hook failed with status code %v", resp.StatusCode)) + errChan <- fmt.Errorf("webhook failed with status code %v", resp.StatusCode) return } errChan <- nil }() - if gjson.GetBytes(e.conf, "response.ignore").Bool() { + if ignoreResponse { + traceID, spanID := span.SpanContext().TraceID(), span.SpanContext().SpanID() + logger := e.deps.Logger().WithField("otel", map[string]string{ + "trace_id": traceID.String(), + "span_id": spanID.String(), + }) go func() { - err := <-errChan - e.deps.Logger().WithError(err).Warning("A web hook request failed but the error was ignored because the configuration indicated that the upstream response should be ignored.") + if err := <-errChan; err != nil { + logger.WithField("duration", time.Since(startTime)).WithError(err).Warning("Webhook request failed but the error was ignored because the configuration indicated that the upstream response should be ignored.") + } else { + logger.WithField("duration", time.Since(startTime)).Info("Webhook request succeeded") + } }() return nil } @@ -323,7 +361,7 @@ func parseWebhookResponse(resp *http.Response) (err error) { } var hookResponse rawHookResponse if err := json.NewDecoder(resp.Body).Decode(&hookResponse); err != nil { - return errors.Wrap(err, "hook response could not be unmarshalled properly from JSON") + return errors.Wrap(err, "webhook response could not be unmarshalled properly from JSON") } var validationErrs []*schema.ValidationError @@ -343,11 +381,11 @@ func parseWebhookResponse(resp *http.Response) (err error) { Context: detail.Context, }) } - validationErrs = append(validationErrs, schema.NewHookValidationError(msg.InstancePtr, "a web-hook target returned an error", messages)) + validationErrs = append(validationErrs, schema.NewHookValidationError(msg.InstancePtr, "a webhook target returned an error", messages)) } if len(validationErrs) == 0 { - return errors.New("error while parsing hook response: got no validation errors") + return errors.New("error while parsing webhook response: got no validation errors") } return schema.NewValidationListError(validationErrs) diff --git a/selfservice/hook/web_hook_integration_test.go b/selfservice/hook/web_hook_integration_test.go index 1c7ddb21df00..87bec5b24003 100644 --- a/selfservice/hook/web_hook_integration_test.go +++ b/selfservice/hook/web_hook_integration_test.go @@ -19,6 +19,7 @@ import ( "testing" "time" + "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/require" "github.com/ory/kratos/schema" @@ -365,7 +366,7 @@ func TestWebHooks(t *testing.T) { }`, ) - webhookError := schema.NewValidationListError([]*schema.ValidationError{schema.NewHookValidationError("#/traits/username", "a web-hook target returned an error", text.Messages{{ID: 1234, Type: "info", Text: "error message"}})}) + webhookError := schema.NewValidationListError([]*schema.ValidationError{schema.NewHookValidationError("#/traits/username", "a webhook target returned an error", text.Messages{{ID: 1234, Type: "info", Text: "error message"}})}) for _, tc := range []struct { uc string callWebHook func(wh *hook.WebHook, req *http.Request, f flow.Flow, s *session.Session) error @@ -839,3 +840,84 @@ func TestDisallowPrivateIPRanges(t *testing.T) { require.Contains(t, err.Error(), "192.168.178.0 is not a public IP address") }) } + +func TestAsyncWebhook(t *testing.T) { + _, reg := internal.NewFastRegistryWithMocks(t) + logger := logrusx.New("kratos", "test") + logHook := new(test.Hook) + logger.Logger.Hooks.Add(logHook) + whDeps := struct { + x.SimpleLoggerWithClient + *jsonnetsecure.TestProvider + }{ + x.SimpleLoggerWithClient{L: logger, C: reg.HTTPClient(context.Background()), T: otelx.NewNoop(logger, &otelx.Config{ServiceName: "kratos"})}, + jsonnetsecure.NewTestProvider(t), + } + + req := &http.Request{ + Header: map[string][]string{"Some-Header": {"Some-Value"}}, + Host: "www.ory.sh", + TLS: new(tls.ConnectionState), + URL: &url.URL{Path: "/some_end_point"}, + Method: http.MethodPost, + } + + incomingCtx, incomingCancel := context.WithCancel(context.Background()) + if deadline, ok := t.Deadline(); ok { + // cancel this context one second before test timeout for clean shutdown + var cleanup context.CancelFunc + incomingCtx, cleanup = context.WithDeadline(incomingCtx, deadline.Add(-time.Second)) + defer cleanup() + } + + req = req.WithContext(incomingCtx) + s := &session.Session{ID: x.NewUUID(), Identity: &identity.Identity{ID: x.NewUUID()}} + f := &login.Flow{ID: x.NewUUID()} + + handlerEntered, blockHandlerOnExit := make(chan struct{}), make(chan struct{}) + webhookReceiver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + close(handlerEntered) + <-blockHandlerOnExit + w.Write([]byte("ok")) + })) + t.Cleanup(webhookReceiver.Close) + + wh := hook.NewWebHook(&whDeps, json.RawMessage(fmt.Sprintf(` + { + "url": %q, + "method": "GET", + "body": "file://stub/test_body.jsonnet", + "response": { + "ignore": true + } + }`, webhookReceiver.URL))) + err := wh.ExecuteLoginPostHook(nil, req, node.DefaultGroup, f, s) + require.NoError(t, err) // execution returns immediately for async webhook + select { + case <-time.After(200 * time.Millisecond): + t.Fatal("timed out waiting for webhook request to reach test handler") + case <-handlerEntered: + // ok + } + // at this point, a goroutine is in the middle of the call to our test handler and waiting for a response + incomingCancel() // simulate the incoming Kratos request having finished + close(blockHandlerOnExit) + timeout := time.After(200 * time.Millisecond) + var found bool + for !found { + for _, entry := range logHook.AllEntries() { + if entry.Message == "Webhook request succeeded" { + found = true + break + } + } + + select { + case <-timeout: + t.Fatal("timed out waiting for successful webhook completion") + case <-time.After(50 * time.Millisecond): + // continue loop + } + } + require.True(t, found) +} diff --git a/session/handler_test.go b/session/handler_test.go index 9aed7a72887a..4e1704adcc34 100644 --- a/session/handler_test.go +++ b/session/handler_test.go @@ -632,6 +632,48 @@ func TestHandlerAdminSessionManagement(t *testing.T) { assert.False(t, session.Active) }) + t.Run("case=session status should be false when session expiry is past", func(t *testing.T) { + client := testhelpers.NewClientWithCookies(t) + + s.ExpiresAt = time.Now().Add(-time.Hour * 1) + require.NoError(t, reg.SessionPersister().UpsertSession(ctx, s)) + + assert.NotEqual(t, uuid.Nil, s.ID) + assert.NotEqual(t, uuid.Nil, s.Identity.ID) + + req, _ := http.NewRequest("GET", ts.URL+"/admin/sessions/"+s.ID.String(), nil) + res, err := client.Do(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, res.StatusCode) + + body, err := io.ReadAll(res.Body) + require.NoError(t, err) + assert.Equal(t, "false", gjson.GetBytes(body, "active").String(), "%s", body) + }) + + t.Run("case=session status should be false for inactive identity", func(t *testing.T) { + client := testhelpers.NewClientWithCookies(t) + var s1 *Session + require.NoError(t, faker.FakeData(&s1)) + s1.Active = true + s1.Identity.State = identity.StateInactive + require.NoError(t, reg.Persister().CreateIdentity(ctx, s1.Identity)) + + assert.Equal(t, uuid.Nil, s1.ID) + require.NoError(t, reg.SessionPersister().UpsertSession(ctx, s1)) + assert.NotEqual(t, uuid.Nil, s1.ID) + assert.NotEqual(t, uuid.Nil, s1.Identity.ID) + + req, _ := http.NewRequest("GET", ts.URL+"/admin/sessions/"+s1.ID.String()+"?expand=Identity", nil) + res, err := client.Do(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, res.StatusCode) + + body, err := io.ReadAll(res.Body) + require.NoError(t, err) + assert.Equal(t, "false", gjson.GetBytes(body, "active").String(), "%s", body) + }) + req, _ := http.NewRequest("DELETE", ts.URL+"/admin/identities/"+s.Identity.ID.String()+"/sessions", nil) res, err := client.Do(req) require.NoError(t, err) @@ -649,52 +691,6 @@ func TestHandlerAdminSessionManagement(t *testing.T) { }) }) - t.Run("case=session status should be false for inactive identity", func(t *testing.T) { - client := testhelpers.NewClientWithCookies(t) - var s *Session - require.NoError(t, faker.FakeData(&s)) - s.Active = true - s.Identity.State = identity.StateInactive - require.NoError(t, reg.Persister().CreateIdentity(ctx, s.Identity)) - - assert.Equal(t, uuid.Nil, s.ID) - require.NoError(t, reg.SessionPersister().UpsertSession(ctx, s)) - assert.NotEqual(t, uuid.Nil, s.ID) - assert.NotEqual(t, uuid.Nil, s.Identity.ID) - - req, _ := http.NewRequest("GET", ts.URL+"/admin/sessions/"+s.ID.String()+"?expand=Identity", nil) - res, err := client.Do(req) - require.NoError(t, err) - assert.Equal(t, http.StatusOK, res.StatusCode) - - body, err := io.ReadAll(res.Body) - require.NoError(t, err) - assert.Equal(t, "false", gjson.GetBytes(body, "active").String(), "%s", body) - }) - - t.Run("case=session status should be false when session expiry is past", func(t *testing.T) { - client := testhelpers.NewClientWithCookies(t) - var s *Session - require.NoError(t, faker.FakeData(&s)) - s.Active = true - s.ExpiresAt = time.Now().Add(-time.Hour * 1) - require.NoError(t, reg.Persister().CreateIdentity(ctx, s.Identity)) - - assert.Equal(t, uuid.Nil, s.ID) - require.NoError(t, reg.SessionPersister().UpsertSession(ctx, s)) - assert.NotEqual(t, uuid.Nil, s.ID) - assert.NotEqual(t, uuid.Nil, s.Identity.ID) - - req, _ := http.NewRequest("GET", ts.URL+"/admin/sessions/"+s.ID.String(), nil) - res, err := client.Do(req) - require.NoError(t, err) - assert.Equal(t, http.StatusOK, res.StatusCode) - - body, err := io.ReadAll(res.Body) - require.NoError(t, err) - assert.Equal(t, "false", gjson.GetBytes(body, "active").String(), "%s", body) - }) - t.Run("case=should return 400 when bad UUID is sent", func(t *testing.T) { client := testhelpers.NewClientWithCookies(t) @@ -719,8 +715,9 @@ func TestHandlerAdminSessionManagement(t *testing.T) { t.Run("case=should return pagination headers on list response", func(t *testing.T) { client := testhelpers.NewClientWithCookies(t) - i := identity.NewIdentity("") - require.NoError(t, reg.IdentityManager().Create(ctx, i)) + var i *identity.Identity + require.NoError(t, faker.FakeData(&i)) + require.NoError(t, reg.Persister().CreateIdentity(ctx, i)) numSessions := 5 numSessionsActive := 2 @@ -731,78 +728,35 @@ func TestHandlerAdminSessionManagement(t *testing.T) { sess[j].Identity = i if j < numSessionsActive { sess[j].Active = true + sess[j].ExpiresAt = time.Now().Add(time.Hour) } else { sess[j].Active = false + sess[j].ExpiresAt = time.Now().Add(-time.Hour) } require.NoError(t, reg.SessionPersister().UpsertSession(ctx, &sess[j])) } for _, tc := range []struct { activeOnly string - expectedTotalCount int + expectedSessionIds []uuid.UUID }{ { activeOnly: "true", - expectedTotalCount: numSessionsActive, + expectedSessionIds: []uuid.UUID{sess[0].ID, sess[1].ID}, }, { activeOnly: "false", - expectedTotalCount: numSessions - numSessionsActive, + expectedSessionIds: []uuid.UUID{sess[2].ID, sess[3].ID, sess[4].ID}, }, { activeOnly: "", - expectedTotalCount: numSessions, + expectedSessionIds: []uuid.UUID{sess[0].ID, sess[1].ID, sess[2].ID, sess[3].ID, sess[4].ID}, }, } { t.Run(fmt.Sprintf("active=%#v", tc.activeOnly), func(t *testing.T) { - reqURL := ts.URL + "/admin/identities/" + i.ID.String() + "/sessions" - if tc.activeOnly != "" { - reqURL += "?active=" + tc.activeOnly - } - req, _ := http.NewRequest("GET", reqURL, nil) - res, err := client.Do(req) - require.NoError(t, err) - require.Equal(t, http.StatusOK, res.StatusCode) - - totalCount, err := strconv.Atoi(res.Header.Get("X-Total-Count")) - require.NoError(t, err) - require.Equal(t, tc.expectedTotalCount, totalCount) - require.NotEqual(t, "", res.Header.Get("Link")) - }) - } - }) + sessions, _, _ := reg.SessionPersister().ListSessionsByIdentity(ctx, i.ID, nil, 1, 10, uuid.Nil, ExpandEverything) + require.Equal(t, 5, len(sessions)) - t.Run("case=should respect active on list", func(t *testing.T) { - client := testhelpers.NewClientWithCookies(t) - i := identity.NewIdentity("") - require.NoError(t, reg.IdentityManager().Create(ctx, i)) - - sess := make([]Session, 2) - for j := range sess { - require.NoError(t, faker.FakeData(&sess[j])) - sess[j].Identity = i - sess[j].Active = j%2 == 0 - require.NoError(t, reg.SessionPersister().UpsertSession(ctx, &sess[j])) - } - - for _, tc := range []struct { - activeOnly string - expectedIDs []uuid.UUID - }{ - { - activeOnly: "true", - expectedIDs: []uuid.UUID{sess[0].ID}, - }, - { - activeOnly: "false", - expectedIDs: []uuid.UUID{sess[1].ID}, - }, - { - activeOnly: "", - expectedIDs: []uuid.UUID{sess[0].ID, sess[1].ID}, - }, - } { - t.Run(fmt.Sprintf("active=%#v", tc.activeOnly), func(t *testing.T) { reqURL := ts.URL + "/admin/identities/" + i.ID.String() + "/sessions" if tc.activeOnly != "" { reqURL += "?active=" + tc.activeOnly @@ -812,17 +766,18 @@ func TestHandlerAdminSessionManagement(t *testing.T) { require.NoError(t, err) require.Equal(t, http.StatusOK, res.StatusCode) - var sessions []Session - require.NoError(t, json.NewDecoder(res.Body).Decode(&sessions)) - require.Equal(t, len(sessions), len(tc.expectedIDs)) - - for _, id := range tc.expectedIDs { - found := false - for _, s := range sessions { - found = found || s.ID == id - } - assert.True(t, found) + var actualSessions []Session + require.NoError(t, json.NewDecoder(res.Body).Decode(&actualSessions)) + actualSessionIds := make([]uuid.UUID, 0) + for _, s := range actualSessions { + actualSessionIds = append(actualSessionIds, s.ID) } + + totalCount, err := strconv.Atoi(res.Header.Get("X-Total-Count")) + require.NoError(t, err) + assert.Equal(t, len(tc.expectedSessionIds), totalCount) + assert.NotEqual(t, "", res.Header.Get("Link")) + assert.ElementsMatch(t, tc.expectedSessionIds, actualSessionIds) }) } }) diff --git a/session/session.go b/session/session.go index 74edf61a0462..8d45aebc1c6b 100644 --- a/session/session.go +++ b/session/session.go @@ -153,17 +153,6 @@ func (s Session) TableName(ctx context.Context) string { return "sessions" } -func (s Session) MarshalJSON() ([]byte, error) { - type sl Session - s.Active = s.IsActive() - - result, err := json.Marshal(sl(s)) - if err != nil { - return nil, err - } - return result, nil -} - func (s *Session) CompletedLoginFor(method identity.CredentialsType, aal identity.AuthenticatorAssuranceLevel) { s.AMR = append(s.AMR, AuthenticationMethod{Method: method, AAL: aal, CompletedAt: time.Now().UTC()}) } @@ -261,19 +250,9 @@ func (s *Session) SaveSessionDeviceInformation(r *http.Request) { device.UserAgent = stringsx.GetPointer(strings.Join(agent, " ")) } - if trueClientIP := r.Header.Get("True-Client-IP"); trueClientIP != "" { - device.IPAddress = &trueClientIP - } else if realClientIP := r.Header.Get("X-Real-IP"); realClientIP != "" { - device.IPAddress = &realClientIP - } else if forwardedIP := r.Header.Get("X-Forwarded-For"); forwardedIP != "" { - ip, _ := httpx.GetClientIPAddressesWithoutInternalIPs(strings.Split(forwardedIP, ",")) - device.IPAddress = &ip - } else { - device.IPAddress = &r.RemoteAddr - } + device.IPAddress = stringsx.GetPointer(httpx.ClientIP(r)) var clientGeoLocation []string - if r.Header.Get("Cf-Ipcity") != "" { clientGeoLocation = append(clientGeoLocation, r.Header.Get("Cf-Ipcity")) } diff --git a/session/test/persistence.go b/session/test/persistence.go index 22a4e2b9bde7..da972b80f75c 100644 --- a/session/test/persistence.go +++ b/session/test/persistence.go @@ -113,237 +113,235 @@ func TestPersister(ctx context.Context, conf *config.Config, p interface { }) }) - t.Run("method=listing", func(t *testing.T) { - i := identity.NewIdentity("") - require.NoError(t, p.CreateIdentity(ctx, i)) - sess := make([]session.Session, 4) - for j := range sess { - require.NoError(t, faker.FakeData(&sess[j])) - sess[j].Identity = i - sess[j].Active = j%2 == 0 - - var device session.Device - require.NoError(t, faker.FakeData(&device)) - sess[j].Devices = []session.Device{ - device, - } - require.NoError(t, p.UpsertSession(ctx, &sess[j])) + t.Run("case=update session", func(t *testing.T) { + expected.AuthenticatorAssuranceLevel = identity.AuthenticatorAssuranceLevel3 + require.NoError(t, p.UpsertSession(ctx, &expected)) + + actual, err := p.GetSessionByToken(ctx, expected.Token, session.ExpandDefault) + check(actual, err) + assert.Equal(t, identity.AuthenticatorAssuranceLevel3, actual.AuthenticatorAssuranceLevel) + }) + + t.Run("case=remove amr and update", func(t *testing.T) { + expected.AMR = nil + require.NoError(t, p.UpsertSession(ctx, &expected)) + + actual, err := p.GetSessionByToken(ctx, expected.Token, session.ExpandDefault) + check(actual, err) + assert.Empty(t, actual.AMR) + }) + }) + + t.Run("case=list sessions", func(t *testing.T) { + var identity1 identity.Identity + require.NoError(t, faker.FakeData(&identity1)) + + // Second identity to test listing by identity isolation + var identity2 identity.Identity + var identity2Session session.Session + require.NoError(t, faker.FakeData(&identity2)) + require.NoError(t, faker.FakeData(&identity2Session)) + + // Create seed identities + _, l := testhelpers.NewNetwork(t, ctx, p) + require.NoError(t, l.CreateIdentity(ctx, &identity1)) + require.NoError(t, l.CreateIdentity(ctx, &identity2)) + + seedSessionIDs := make([]uuid.UUID, 5) + seedSessionsList := make([]session.Session, 5) + for j := range seedSessionsList { + require.NoError(t, faker.FakeData(&seedSessionsList[j])) + seedSessionsList[j].Identity = &identity1 + seedSessionsList[j].Active = j%2 == 0 + + if seedSessionsList[j].Active { + seedSessionsList[j].ExpiresAt = time.Now().UTC().Add(time.Hour) + } else { + seedSessionsList[j].ExpiresAt = time.Now().UTC().Add(-time.Hour) } - for _, tc := range []struct { - desc string - except uuid.UUID - expected []session.Session - active *bool - }{ - { - desc: "all", - expected: sess, - }, - { - desc: "except one", - except: sess[0].ID, - expected: []session.Session{ - sess[1], - sess[2], - sess[3], - }, + var device session.Device + require.NoError(t, faker.FakeData(&device)) + seedSessionsList[j].Devices = []session.Device{ + device, + } + require.NoError(t, l.UpsertSession(ctx, &seedSessionsList[j])) + seedSessionIDs[j] = seedSessionsList[j].ID + } + + identity2Session.Identity = &identity2 + identity2Session.Active = true + identity2Session.ExpiresAt = time.Now().UTC().Add(time.Hour) + require.NoError(t, l.UpsertSession(ctx, &identity2Session)) + + for _, tc := range []struct { + desc string + except uuid.UUID + expectedSessionIds []uuid.UUID + active *bool + }{ + { + desc: "all", + expectedSessionIds: seedSessionIDs, + }, + { + desc: "except one", + except: seedSessionsList[0].ID, + expectedSessionIds: []uuid.UUID{ + seedSessionIDs[1], + seedSessionIDs[2], + seedSessionIDs[3], + seedSessionIDs[4], }, - { - desc: "active only", - active: pointerx.Bool(true), - expected: []session.Session{ - sess[0], - sess[2], - }, + }, + { + desc: "active only", + active: pointerx.Bool(true), + expectedSessionIds: []uuid.UUID{ + seedSessionIDs[0], + seedSessionIDs[2], + seedSessionIDs[4], }, - { - desc: "active only and except", - active: pointerx.Bool(true), - except: sess[0].ID, - expected: []session.Session{ - sess[2], - }, + }, + { + desc: "active only and except", + active: pointerx.Bool(true), + except: seedSessionsList[0].ID, + expectedSessionIds: []uuid.UUID{ + seedSessionIDs[2], + seedSessionIDs[4], }, - { - desc: "inactive only", - active: pointerx.Bool(false), - expected: []session.Session{ - sess[1], - sess[3], - }, + }, + { + desc: "inactive only", + active: pointerx.Bool(false), + expectedSessionIds: []uuid.UUID{ + seedSessionIDs[1], + seedSessionIDs[3], }, - { - desc: "inactive only and except", - active: pointerx.Bool(false), - except: sess[3].ID, - expected: []session.Session{ - sess[1], - }, + }, + { + desc: "inactive only and except", + active: pointerx.Bool(false), + except: seedSessionsList[3].ID, + expectedSessionIds: []uuid.UUID{ + seedSessionIDs[1], }, - } { - t.Run("case=ListSessionsByIdentity "+tc.desc, func(t *testing.T) { - actual, total, err := p.ListSessionsByIdentity(ctx, i.ID, tc.active, 1, 10, tc.except, session.ExpandEverything) - require.NoError(t, err) - - require.Equal(t, len(tc.expected), len(actual)) - require.Equal(t, int64(len(tc.expected)), total) - for _, es := range tc.expected { - found := false - for _, as := range actual { - if as.ID == es.ID { - found = true - assert.Equal(t, len(es.Devices), len(as.Devices)) - } - } - assert.True(t, found) - } - }) - } - - t.Run("case=ListSessionsByIdentity - other network", func(t *testing.T) { - _, other := testhelpers.NewNetwork(t, ctx, p) - actual, total, err := other.ListSessionsByIdentity(ctx, i.ID, nil, 1, 10, uuid.Nil, session.ExpandNothing) + }, + } { + t.Run("case=by Identity "+tc.desc, func(t *testing.T) { + actual, total, err := l.ListSessionsByIdentity(ctx, identity1.ID, tc.active, 1, 10, tc.except, session.ExpandEverything) require.NoError(t, err) - require.Equal(t, int64(0), total) - assert.Len(t, actual, 0) + + actualSessionIds := make([]uuid.UUID, 0) + for _, s := range actual { + actualSessionIds = append(actualSessionIds, s.ID) + } + + assert.Equal(t, int64(len(tc.expectedSessionIds)), total) + assert.ElementsMatch(t, tc.expectedSessionIds, actualSessionIds) }) + } - for _, tc := range []struct { - desc string - except uuid.UUID - expected []session.Session - active *bool - }{ - { - desc: "all", - expected: append(sess, expected), - }, - { - desc: "active only", - active: pointerx.Bool(true), - expected: []session.Session{ - expected, - sess[0], - sess[2], - }, + t.Run("case=by Identity on other network", func(t *testing.T) { + _, other := testhelpers.NewNetwork(t, ctx, p) + actual, total, err := other.ListSessionsByIdentity(ctx, identity1.ID, nil, 1, 10, uuid.Nil, session.ExpandNothing) + require.NoError(t, err) + require.Equal(t, int64(0), total) + assert.Len(t, actual, 0) + }) + + for _, tc := range []struct { + desc string + except uuid.UUID + expected []session.Session + active *bool + }{ + { + desc: "all", + expected: append(seedSessionsList, identity2Session), + }, + { + desc: "active only", + active: pointerx.Bool(true), + expected: []session.Session{ + seedSessionsList[0], + seedSessionsList[2], + seedSessionsList[4], + identity2Session, }, - { - desc: "inactive only", - active: pointerx.Bool(false), - expected: []session.Session{ - sess[1], - sess[3], - }, + }, + { + desc: "inactive only", + active: pointerx.Bool(false), + expected: []session.Session{ + seedSessionsList[1], + seedSessionsList[3], }, - } { - t.Run("case=ListSessions "+tc.desc, func(t *testing.T) { - paginatorOpts := make([]keysetpagination.Option, 0) - actual, total, nextPage, err := p.ListSessions(ctx, tc.active, paginatorOpts, session.ExpandEverything) - require.NoError(t, err) - - require.Equal(t, len(tc.expected), len(actual)) - require.Equal(t, int64(len(tc.expected)), total) - assert.Equal(t, true, nextPage.IsLast()) - assert.Equal(t, uuid.Nil.String(), nextPage.Token().Encode()) - assert.Equal(t, 250, nextPage.Size()) - for _, es := range tc.expected { - found := false - for _, as := range actual { - if as.ID == es.ID { - found = true - assert.Equal(t, len(es.Devices), len(as.Devices)) - assert.Equal(t, es.Identity.ID.String(), as.Identity.ID.String()) - } - } - assert.True(t, found) - } - }) - } - - t.Run("case=ListSessions last page", func(t *testing.T) { + }, + } { + t.Run("case=all "+tc.desc, func(t *testing.T) { paginatorOpts := make([]keysetpagination.Option, 0) - actual, total, page, err := p.ListSessions(ctx, nil, paginatorOpts, session.ExpandEverything) + actual, total, nextPage, err := l.ListSessions(ctx, tc.active, paginatorOpts, session.ExpandEverything) require.NoError(t, err) - require.Equal(t, 5, len(actual)) - require.Equal(t, int64(5), total) - assert.Equal(t, true, page.IsLast()) - assert.Equal(t, uuid.Nil.String(), page.Token().Encode()) - assert.Equal(t, 250, page.Size()) - }) - - t.Run("case=ListSessions page iteration", func(t *testing.T) { - - }) - - t.Run("case=ListSessions - other network", func(t *testing.T) { - var identity1 identity.Identity - require.NoError(t, faker.FakeData(&identity1)) - - _, other := testhelpers.NewNetwork(t, ctx, p) - require.NoError(t, other.CreateIdentity(ctx, &identity1)) - - expectedIDs := make([]uuid.UUID, 5) - seedSessionsList := make([]session.Session, 5) - for j := range seedSessionsList { - require.NoError(t, faker.FakeData(&seedSessionsList[j])) - seedSessionsList[j].Identity = &identity1 - seedSessionsList[j].Active = j%2 == 0 - - var device session.Device - require.NoError(t, faker.FakeData(&device)) - seedSessionsList[j].Devices = []session.Device{ - device, + require.Equal(t, len(tc.expected), len(actual)) + require.Equal(t, int64(len(tc.expected)), total) + assert.Equal(t, true, nextPage.IsLast()) + assert.Equal(t, uuid.Nil.String(), nextPage.Token().Encode()) + assert.Equal(t, 250, nextPage.Size()) + for _, es := range tc.expected { + found := false + for _, as := range actual { + if as.ID == es.ID { + found = true + assert.Equal(t, len(es.Devices), len(as.Devices)) + assert.Equal(t, es.Identity.ID.String(), as.Identity.ID.String()) + } } - require.NoError(t, other.UpsertSession(ctx, &seedSessionsList[j])) - expectedIDs[j] = seedSessionsList[j].ID + assert.True(t, found) } + }) + } - paginatorOpts := make([]keysetpagination.Option, 0) - paginatorOpts = append(paginatorOpts, keysetpagination.WithSize(3)) - firstPageItems, total, page1, err := other.ListSessions(ctx, nil, paginatorOpts, session.ExpandEverything) - require.NoError(t, err) - require.Equal(t, int64(5), total) - assert.Len(t, firstPageItems, 3) - - assert.Equal(t, false, page1.IsLast()) - assert.Equal(t, firstPageItems[len(firstPageItems)-1].ID.String(), page1.Token().Encode()) - assert.Equal(t, 3, page1.Size()) - - // Validate secondPageItems page - secondPageItems, total, page2, err := other.ListSessions(ctx, nil, page1.ToOptions(), session.ExpandEverything) - require.NoError(t, err) - - acutalIDs := make([]uuid.UUID, 0) - for _, s := range append(firstPageItems, secondPageItems...) { - acutalIDs = append(acutalIDs, s.ID) - } - assert.ElementsMatch(t, expectedIDs, acutalIDs) + t.Run("case=all sessions pagination only one page", func(t *testing.T) { + paginatorOpts := make([]keysetpagination.Option, 0) + actual, total, page, err := l.ListSessions(ctx, nil, paginatorOpts, session.ExpandEverything) + require.NoError(t, err) - require.Equal(t, int64(5), total) - assert.Len(t, secondPageItems, 2) - assert.True(t, page2.IsLast()) - assert.Equal(t, 3, page2.Size()) - }) + require.Equal(t, 6, len(actual)) + require.Equal(t, int64(6), total) + assert.Equal(t, true, page.IsLast()) + assert.Equal(t, uuid.Nil.String(), page.Token().Encode()) + assert.Equal(t, 250, page.Size()) }) - t.Run("case=update session", func(t *testing.T) { - expected.AuthenticatorAssuranceLevel = identity.AuthenticatorAssuranceLevel3 - require.NoError(t, p.UpsertSession(ctx, &expected)) + t.Run("case=all sessions pagination multiple pages", func(t *testing.T) { + paginatorOpts := make([]keysetpagination.Option, 0) + paginatorOpts = append(paginatorOpts, keysetpagination.WithSize(3)) + firstPageItems, total, page1, err := l.ListSessions(ctx, nil, paginatorOpts, session.ExpandEverything) + require.NoError(t, err) + require.Equal(t, int64(6), total) + assert.Len(t, firstPageItems, 3) - actual, err := p.GetSessionByToken(ctx, expected.Token, session.ExpandDefault) - check(actual, err) - assert.Equal(t, identity.AuthenticatorAssuranceLevel3, actual.AuthenticatorAssuranceLevel) - }) + assert.Equal(t, false, page1.IsLast()) + assert.Equal(t, firstPageItems[len(firstPageItems)-1].ID.String(), page1.Token().Encode()) + assert.Equal(t, 3, page1.Size()) - t.Run("case=remove amr and update", func(t *testing.T) { - expected.AMR = nil - require.NoError(t, p.UpsertSession(ctx, &expected)) + // Validate secondPageItems page + secondPageItems, total, page2, err := l.ListSessions(ctx, nil, page1.ToOptions(), session.ExpandEverything) + require.NoError(t, err) - actual, err := p.GetSessionByToken(ctx, expected.Token, session.ExpandDefault) - check(actual, err) - assert.Empty(t, actual.AMR) + acutalIDs := make([]uuid.UUID, 0) + for _, s := range append(firstPageItems, secondPageItems...) { + acutalIDs = append(acutalIDs, s.ID) + } + assert.ElementsMatch(t, append(seedSessionIDs, identity2Session.ID), acutalIDs) + + require.Equal(t, int64(6), total) + assert.Len(t, secondPageItems, 3) + assert.True(t, page2.IsLast()) + assert.Equal(t, 3, page2.Size()) }) })