From 4137808eef7871a7376e7d3564e5f9f59a993acc Mon Sep 17 00:00:00 2001 From: Nikos Date: Mon, 5 Feb 2024 16:07:56 +0200 Subject: [PATCH 01/17] fix: Use Requester param in WriteAccessError The WriteAccessError is used to construct error responses as described in Section 5.2 of [RFC6749]. It is not limited to access token responses. Perhaps we should rename the function to Rfc6749TokenError. --- access_error.go | 5 +++-- oauth2.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/access_error.go b/access_error.go index b28283e0e..8e4a00182 100644 --- a/access_error.go +++ b/access_error.go @@ -10,11 +10,12 @@ import ( "net/http" ) -func (f *Fosite) WriteAccessError(ctx context.Context, rw http.ResponseWriter, req AccessRequester, err error) { +// Convert an error to an http response as per RFC6749 +func (f *Fosite) WriteAccessError(ctx context.Context, rw http.ResponseWriter, req Requester, err error) { f.writeJsonError(ctx, rw, req, err) } -func (f *Fosite) writeJsonError(ctx context.Context, rw http.ResponseWriter, requester AccessRequester, err error) { +func (f *Fosite) writeJsonError(ctx context.Context, rw http.ResponseWriter, requester Requester, err error) { rw.Header().Set("Content-Type", "application/json;charset=UTF-8") rw.Header().Set("Cache-Control", "no-store") rw.Header().Set("Pragma", "no-cache") diff --git a/oauth2.go b/oauth2.go index c25abf65a..a52eb1f6f 100644 --- a/oauth2.go +++ b/oauth2.go @@ -120,7 +120,7 @@ type OAuth2Provider interface { // // The following specs must be considered in any implementation of this method: // * https://tools.ietf.org/html/rfc6749#section-5.2 (everything) - WriteAccessError(ctx context.Context, rw http.ResponseWriter, requester AccessRequester, err error) + WriteAccessError(ctx context.Context, rw http.ResponseWriter, requester Requester, err error) // WriteAccessResponse writes the access response. // From 7ef1fb05bab49a8fe6e80f15b92127c49bd2c5df Mon Sep 17 00:00:00 2001 From: Nikos Date: Tue, 6 Feb 2024 10:53:18 +0200 Subject: [PATCH 02/17] fix: generalize validateAuthorizeAudience method --- audience_strategy.go | 6 +++--- authorize_request_handler.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/audience_strategy.go b/audience_strategy.go index d0e7e41da..5e25d8873 100644 --- a/audience_strategy.go +++ b/audience_strategy.go @@ -92,10 +92,10 @@ func GetAudiences(form url.Values) []string { } } -func (f *Fosite) validateAuthorizeAudience(ctx context.Context, r *http.Request, request *AuthorizeRequest) error { - audience := GetAudiences(request.Form) +func (f *Fosite) validateAudience(ctx context.Context, r *http.Request, request Requester) error { + audience := GetAudiences(request.GetRequestForm()) - if err := f.Config.GetAudienceStrategy(ctx)(request.Client.GetAudience(), audience); err != nil { + if err := f.Config.GetAudienceStrategy(ctx)(request.GetClient().GetAudience(), audience); err != nil { return err } diff --git a/authorize_request_handler.go b/authorize_request_handler.go index 486481cd4..f8df2bf4a 100644 --- a/authorize_request_handler.go +++ b/authorize_request_handler.go @@ -371,7 +371,7 @@ func (f *Fosite) newAuthorizeRequest(ctx context.Context, r *http.Request, isPAR return request, err } - if err := f.validateAuthorizeAudience(ctx, r, request); err != nil { + if err := f.validateAudience(ctx, r, request); err != nil { return request, err } From 4474d73309197a202b8a92894e049bd290e4b7bc Mon Sep 17 00:00:00 2001 From: Nikos Date: Tue, 6 Feb 2024 14:45:11 +0200 Subject: [PATCH 03/17] feat: add device flow base logic --- device_request.go | 15 +++ device_request_handler.go | 87 ++++++++++++++ device_request_handler_test.go | 214 +++++++++++++++++++++++++++++++++ device_request_test.go | 18 +++ device_response.go | 97 +++++++++++++++ device_response_test.go | 26 ++++ device_response_writer.go | 21 ++++ device_write.go | 37 ++++++ device_write_test.go | 55 +++++++++ 9 files changed, 570 insertions(+) create mode 100644 device_request.go create mode 100644 device_request_handler.go create mode 100644 device_request_handler_test.go create mode 100644 device_request_test.go create mode 100644 device_response.go create mode 100644 device_response_test.go create mode 100644 device_response_writer.go create mode 100644 device_write.go create mode 100644 device_write_test.go diff --git a/device_request.go b/device_request.go new file mode 100644 index 000000000..3c2df0636 --- /dev/null +++ b/device_request.go @@ -0,0 +1,15 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package fosite + +// DeviceRequest is an implementation of DeviceRequester +type DeviceRequest struct { + Request +} + +func NewDeviceRequest() *DeviceRequest { + return &DeviceRequest{ + Request: *NewRequest(), + } +} diff --git a/device_request_handler.go b/device_request_handler.go new file mode 100644 index 000000000..20d3b1e26 --- /dev/null +++ b/device_request_handler.go @@ -0,0 +1,87 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright © 2015-2021 Aeneas Rekkas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Aeneas Rekkas + * @copyright 2015-2021 Aeneas Rekkas + * @license Apache-2.0 + * + */ + +package fosite + +import ( + "context" + "net/http" + "strings" + + "github.com/ory/fosite/i18n" + "github.com/ory/x/errorsx" + "github.com/ory/x/otelx" + "go.opentelemetry.io/otel/trace" +) + +func (f *Fosite) NewDeviceRequest(ctx context.Context, r *http.Request) (_ DeviceRequester, err error) { + ctx, span := trace.SpanFromContext(ctx).TracerProvider().Tracer("github.com/ory/fosite").Start(ctx, "Fosite.NewAccessRequest") + defer otelx.End(span, &err) + + request := NewDeviceRequest() + request.Lang = i18n.GetLangFromRequest(f.Config.GetMessageCatalog(ctx), r) + + if r.Method != "POST" { + return request, errorsx.WithStack(ErrInvalidRequest.WithHintf("HTTP method is '%s', expected 'POST'.", r.Method)) + } else if err := r.ParseForm(); err != nil { + return nil, errorsx.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithWrap(err).WithDebug(err.Error())) + } else if len(r.PostForm) == 0 { + return request, errorsx.WithStack(ErrInvalidRequest.WithHint("The POST body can not be empty.")) + } + request.Form = r.PostForm + + client, clientErr := f.AuthenticateClient(ctx, r, r.PostForm) + if clientErr != nil { + return request, clientErr + } + if client.GetID() != request.Form.Get("client_id") { + return request, errorsx.WithStack(ErrInvalidRequest.WithHint("Provided client_id mismatch.")) + } + request.Client = client + + if !client.GetGrantTypes().Has(string(GrantTypeDeviceCode)) { + return nil, errorsx.WithStack(ErrInvalidGrant.WithHint("The requested OAuth 2.0 Client does not have the 'urn:ietf:params:oauth:grant-type:device_code' grant.")) + } + + if err := f.validateDeviceScope(ctx, r, request); err != nil { + return nil, err + } + + if err := f.validateAudience(ctx, r, request); err != nil { + return request, err + } + + return request, nil +} + +func (f *Fosite) validateDeviceScope(ctx context.Context, r *http.Request, request *DeviceRequest) error { + scope := RemoveEmpty(strings.Split(request.Form.Get("scope"), " ")) + for _, permission := range scope { + if !f.Config.GetScopeStrategy(ctx)(request.Client.GetScopes(), permission) { + return errorsx.WithStack(ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", permission)) + } + } + request.SetRequestedScopes(scope) + return nil +} diff --git a/device_request_handler_test.go b/device_request_handler_test.go new file mode 100644 index 000000000..d6dceeffa --- /dev/null +++ b/device_request_handler_test.go @@ -0,0 +1,214 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package fosite_test + +import ( + "context" + "fmt" + "net/http" + "net/url" + "testing" + + "github.com/golang/mock/gomock" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + . "github.com/ory/fosite" + "github.com/ory/fosite/internal" +) + +func TestNewDeviceRequestWithPublicClient(t *testing.T) { + ctrl := gomock.NewController(t) + store := internal.NewMockStorage(ctrl) + client := &DefaultClient{ID: "client_id"} + defer ctrl.Finish() + config := &Config{ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy} + fosite := &Fosite{Store: store, Config: config} + for k, c := range []struct { + header http.Header + form url.Values + method string + expectedError error + mock func() + expect DeviceRequester + }{ + /* invalid Method */ + { + expectedError: ErrInvalidRequest, + method: "GET", + mock: func() {}, + }, + /* empty request */ + { + expectedError: ErrInvalidRequest, + method: "POST", + mock: func() {}, + }, + /* invalid client */ + { + form: url.Values{ + "client_id": {"client_id"}, + "scope": {"foo bar"}, + }, + expectedError: ErrInvalidClient, + method: "POST", + mock: func() { + store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(nil, errors.New("")) + }, + }, + /* fails because scope not allowed */ + { + form: url.Values{ + "client_id": {"client_id"}, + "scope": {"17 42 foo"}, + }, + method: "POST", + mock: func() { + store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) + client.Public = true + client.Scopes = []string{"17", "42"} + client.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} + }, + expectedError: ErrInvalidScope, + }, + /* fails because scope not allowed */ + { + form: url.Values{ + "client_id": {"client_id"}, + "scope": {"17 42"}, + "audience": {"aud"}, + }, + method: "POST", + mock: func() { + store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) + client.Public = true + client.Scopes = []string{"17", "42"} + client.Audience = []string{"aud2"} + client.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} + }, + expectedError: ErrInvalidRequest, + }, + /* should fail because doesn't have proper grant */ + { + form: url.Values{ + "client_id": {"client_id"}, + "scope": {"17 42"}, + }, + method: "POST", + mock: func() { + store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) + client.Public = true + client.Scopes = []string{"17", "42"} + client.GrantTypes = []string{"authorization_code"} + }, + expectedError: ErrInvalidGrant, + }, + /* success case */ + { + form: url.Values{ + "client_id": {"client_id"}, + "scope": {"17 42"}, + }, + method: "POST", + mock: func() { + store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) + client.Public = true + client.Scopes = []string{"17", "42"} + client.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} + }, + }, + } { + t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { + c.mock() + r := &http.Request{ + Header: c.header, + PostForm: c.form, + Form: c.form, + Method: c.method, + } + + ar, err := fosite.NewDeviceRequest(context.Background(), r) + if c.expectedError != nil { + assert.EqualError(t, err, c.expectedError.Error()) + } else { + require.NoError(t, err) + assert.NotNil(t, ar.GetRequestedAt()) + } + }) + } +} + +func TestNewDeviceRequestWithClientAuthn(t *testing.T) { + ctrl := gomock.NewController(t) + store := internal.NewMockStorage(ctrl) + hasher := internal.NewMockHasher(ctrl) + client := &DefaultClient{ID: "client_id"} + defer ctrl.Finish() + config := &Config{ClientSecretsHasher: hasher, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy} + fosite := &Fosite{Store: store, Config: config} + for k, c := range []struct { + header http.Header + form url.Values + method string + expectedError error + mock func() + expect DeviceRequester + }{ + // No client authn provided + { + form: url.Values{ + "client_id": {"client_id"}, + "scope": {"foo bar"}, + }, + expectedError: ErrInvalidClient, + method: "POST", + mock: func() { + store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) + client.Public = false + client.Secret = []byte("client_secret") + client.Scopes = []string{"foo", "bar"} + client.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} + hasher.EXPECT().Compare(gomock.Any(), gomock.Any(), gomock.Any()).Return(errors.New("")) + }, + }, + // success + { + form: url.Values{ + "client_id": {"client_id"}, + "scope": {"foo bar"}, + }, + header: http.Header{ + "Authorization": {basicAuth("client_id", "client_secret")}, + }, + method: "POST", + mock: func() { + store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) + client.Public = false + client.Secret = []byte("client_secret") + client.Scopes = []string{"foo", "bar"} + client.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} + hasher.EXPECT().Compare(gomock.Any(), gomock.Eq([]byte("client_secret")), gomock.Eq([]byte("client_secret"))).Return(nil) + }, + }, + } { + t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { + c.mock() + r := &http.Request{ + Header: c.header, + PostForm: c.form, + Form: c.form, + Method: c.method, + } + + req, err := fosite.NewDeviceRequest(context.Background(), r) + if c.expectedError != nil { + assert.EqualError(t, err, c.expectedError.Error()) + } else { + require.NoError(t, err) + assert.NotNil(t, req.GetRequestedAt()) + } + }) + } +} diff --git a/device_request_test.go b/device_request_test.go new file mode 100644 index 000000000..571e83ba8 --- /dev/null +++ b/device_request_test.go @@ -0,0 +1,18 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package fosite + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDeviceRequest(t *testing.T) { + r := NewDeviceRequest() + r.Client = &DefaultClient{} + r.SetRequestedScopes([]string{"17", "42"}) + assert.True(t, r.GetRequestedScopes().Has("17", "42")) + assert.Equal(t, r.Client, r.GetClient()) +} diff --git a/device_response.go b/device_response.go new file mode 100644 index 000000000..b9b9d655e --- /dev/null +++ b/device_response.go @@ -0,0 +1,97 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package fosite + +import ( + "encoding/json" + "io" + "net/http" +) + +type deviceResponse struct { + Header http.Header + DeviceCode string `json:"device_code"` + UserCode string `json:"user_code"` + VerificationURI string `json:"verification_uri"` + VerificationURIComplete string `json:"verification_uri_complete,omitempty"` + ExpiresIn int64 `json:"expires_in"` + Interval int `json:"interval,omitempty"` +} + +type DeviceResponse struct { + deviceResponse +} + +func NewDeviceResponse() *DeviceResponse { + return &DeviceResponse{} +} + +func (d *DeviceResponse) GetDeviceCode() string { + return d.deviceResponse.DeviceCode +} + +// SetDeviceCode returns the response's user code +func (d *DeviceResponse) SetDeviceCode(code string) { + d.deviceResponse.DeviceCode = code +} + +func (d *DeviceResponse) GetUserCode() string { + return d.deviceResponse.UserCode +} + +func (d *DeviceResponse) SetUserCode(code string) { + d.deviceResponse.UserCode = code +} + +// GetVerificationURI returns the response's verification uri +func (d *DeviceResponse) GetVerificationURI() string { + return d.deviceResponse.VerificationURI +} + +func (d *DeviceResponse) SetVerificationURI(uri string) { + d.deviceResponse.VerificationURI = uri +} + +// GetVerificationURIComplete returns the response's complete verification uri if set +func (d *DeviceResponse) GetVerificationURIComplete() string { + return d.deviceResponse.VerificationURIComplete +} + +func (d *DeviceResponse) SetVerificationURIComplete(uri string) { + d.deviceResponse.VerificationURIComplete = uri +} + +// GetExpiresIn returns the response's device code and user code lifetime in seconds if set +func (d *DeviceResponse) GetExpiresIn() int64 { + return d.deviceResponse.ExpiresIn +} + +func (d *DeviceResponse) SetExpiresIn(seconds int64) { + d.deviceResponse.ExpiresIn = seconds +} + +// GetInterval returns the response's polling interval if set +func (d *DeviceResponse) GetInterval() int { + return d.deviceResponse.Interval +} + +func (d *DeviceResponse) SetInterval(seconds int) { + d.deviceResponse.Interval = seconds +} + +func (a *DeviceResponse) GetHeader() http.Header { + return a.deviceResponse.Header +} + +func (a *DeviceResponse) AddHeader(key, value string) { + a.deviceResponse.Header.Add(key, value) +} + +func (d *DeviceResponse) FromJson(r io.Reader) error { + return json.NewDecoder(r).Decode(&d.deviceResponse) +} + +func (d *DeviceResponse) ToJson(rw io.Writer) error { + return json.NewEncoder(rw).Encode(&d.deviceResponse) +} diff --git a/device_response_test.go b/device_response_test.go new file mode 100644 index 000000000..366899a1b --- /dev/null +++ b/device_response_test.go @@ -0,0 +1,26 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package fosite + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDeviceResponse(t *testing.T) { + r := NewDeviceResponse() + r.SetDeviceCode("device_code") + r.SetUserCode("user_code") + r.SetExpiresIn(5) + r.SetVerificationURI("https://www.example.com") + r.SetVerificationURIComplete("https://www.example.com?code=user_code") + r.SetInterval(5) + assert.Equal(t, "device_code", r.GetDeviceCode()) + assert.Equal(t, "user_code", r.GetUserCode()) + assert.Equal(t, int64(5), r.GetExpiresIn()) + assert.Equal(t, "https://www.example.com", r.GetVerificationURI()) + assert.Equal(t, "https://www.example.com?code=user_code", r.GetVerificationURIComplete()) + assert.Equal(t, 5, r.GetInterval()) +} diff --git a/device_response_writer.go b/device_response_writer.go new file mode 100644 index 000000000..82c5d7c93 --- /dev/null +++ b/device_response_writer.go @@ -0,0 +1,21 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package fosite + +import ( + "context" +) + +func (f *Fosite) NewDeviceResponse(ctx context.Context, r DeviceRequester, session Session) (DeviceResponder, error) { + var resp = &DeviceResponse{} + + r.SetSession(session) + for _, h := range f.Config.GetDeviceEndpointHandlers(ctx) { + if err := h.HandleDeviceEndpointRequest(ctx, r, resp); err != nil { + return nil, err + } + } + + return resp, nil +} diff --git a/device_write.go b/device_write.go new file mode 100644 index 000000000..0e8fa77b5 --- /dev/null +++ b/device_write.go @@ -0,0 +1,37 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package fosite + +import ( + "context" + "net/http" +) + +// TODO: Do documentation + +func (f *Fosite) WriteDeviceResponse(ctx context.Context, rw http.ResponseWriter, requester DeviceRequester, responder DeviceResponder) { + // Set custom headers, e.g. "X-MySuperCoolCustomHeader" or "X-DONT-CACHE-ME"... + wh := rw.Header() + rh := responder.GetHeader() + for k := range rh { + wh.Set(k, rh.Get(k)) + } + + rw.Header().Set("Content-Type", "application/json;charset=UTF-8") + rw.Header().Set("Cache-Control", "no-store") + rw.Header().Set("Pragma", "no-cache") + + deviceResponse := &DeviceResponse{ + deviceResponse{ + DeviceCode: responder.GetDeviceCode(), + UserCode: responder.GetUserCode(), + VerificationURI: responder.GetVerificationURI(), + VerificationURIComplete: responder.GetVerificationURIComplete(), + ExpiresIn: responder.GetExpiresIn(), + Interval: responder.GetInterval(), + }, + } + + _ = deviceResponse.ToJson(rw) +} diff --git a/device_write_test.go b/device_write_test.go new file mode 100644 index 000000000..0ed418cb7 --- /dev/null +++ b/device_write_test.go @@ -0,0 +1,55 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package fosite_test + +import ( + "context" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + . "github.com/ory/fosite" +) + +func TestWriteDeviceUserResponse(t *testing.T) { + oauth2 := &Fosite{Config: &Config{ + DeviceAndUserCodeLifespan: time.Minute, + DeviceAuthTokenPollingInterval: time.Minute, + DeviceVerificationURL: "http://ory.sh", + }} + + rw := httptest.NewRecorder() + ar := &Request{} + resp := &DeviceResponse{} + resp.SetUserCode("AAAA") + resp.SetDeviceCode("BBBB") + resp.SetInterval(int( + oauth2.Config.GetDeviceAuthTokenPollingInterval(context.TODO()).Round(time.Second).Seconds(), + )) + resp.SetExpiresIn(int64( + time.Now().Round(time.Second).Add(oauth2.Config.GetDeviceAndUserCodeLifespan(context.TODO())).Second(), + )) + resp.SetVerificationURI(oauth2.Config.GetDeviceVerificationURL(context.TODO())) + resp.SetVerificationURIComplete( + oauth2.Config.GetDeviceVerificationURL(context.TODO()) + "?user_code=" + resp.GetUserCode(), + ) + + oauth2.WriteDeviceResponse(context.Background(), rw, ar, resp) + + assert.Equal(t, 200, rw.Code) + + wroteDeviceResponse := DeviceResponse{} + err := wroteDeviceResponse.FromJson(rw.Body) + require.NoError(t, err) + + assert.Equal(t, resp.GetUserCode(), wroteDeviceResponse.UserCode) + assert.Equal(t, resp.GetDeviceCode(), wroteDeviceResponse.DeviceCode) + assert.Equal(t, resp.GetVerificationURI(), wroteDeviceResponse.VerificationURI) + assert.Equal(t, resp.GetVerificationURIComplete(), wroteDeviceResponse.VerificationURIComplete) + assert.Equal(t, resp.GetInterval(), wroteDeviceResponse.Interval) + assert.Equal(t, resp.GetExpiresIn(), wroteDeviceResponse.ExpiresIn) +} From ffd36618782228995c7c57d181f750d88742cb18 Mon Sep 17 00:00:00 2001 From: Nikos Date: Tue, 6 Feb 2024 14:46:01 +0200 Subject: [PATCH 04/17] fix: add handler for device authorization req --- handler/rfc8628/auth_handler.go | 55 ++++++++++ handler/rfc8628/auth_handler_test.go | 79 ++++++++++++++ handler/rfc8628/storage.go | 52 +++++++++ handler/rfc8628/strategy.go | 32 ++++++ handler/rfc8628/strategy_hmacsha.go | 84 +++++++++++++++ handler/rfc8628/strategy_hmacsha_test.go | 131 +++++++++++++++++++++++ 6 files changed, 433 insertions(+) create mode 100644 handler/rfc8628/auth_handler.go create mode 100644 handler/rfc8628/auth_handler_test.go create mode 100644 handler/rfc8628/storage.go create mode 100644 handler/rfc8628/strategy.go create mode 100644 handler/rfc8628/strategy_hmacsha.go create mode 100644 handler/rfc8628/strategy_hmacsha_test.go diff --git a/handler/rfc8628/auth_handler.go b/handler/rfc8628/auth_handler.go new file mode 100644 index 000000000..9072cbe46 --- /dev/null +++ b/handler/rfc8628/auth_handler.go @@ -0,0 +1,55 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package rfc8628 + +import ( + "context" + "time" + + "github.com/ory/fosite" + "github.com/ory/x/errorsx" +) + +type DeviceAuthHandler struct { + Storage RFC8628CoreStorage + Strategy RFC8628CodeStrategy + Config interface { + fosite.DeviceProvider + fosite.DeviceAndUserCodeLifespanProvider + } +} + +// DeviceAuthorizationHandler is a response handler for the Device Authorisation Grant as +// defined in https://tools.ietf.org/html/rfc8628#section-3.1 +func (d *DeviceAuthHandler) HandleDeviceEndpointRequest(ctx context.Context, dar fosite.DeviceRequester, resp fosite.DeviceResponder) error { + deviceCode, deviceCodeSignature, err := d.Strategy.GenerateDeviceCode(ctx) + if err != nil { + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + + userCode, userCodeSignature, err := d.Strategy.GenerateUserCode(ctx) + if err != nil { + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + + // Store the User Code session (this has no real data other that the user and device code), can be converted into a 'full' session after user auth + dar.GetSession().SetExpiresAt(fosite.AuthorizeCode, time.Now().UTC().Add(d.Config.GetDeviceAndUserCodeLifespan(ctx))) + if err := d.Storage.CreateDeviceCodeSession(ctx, deviceCodeSignature, dar.Sanitize(nil)); err != nil { + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + + dar.GetSession().SetExpiresAt(fosite.UserCode, time.Now().UTC().Add(d.Config.GetDeviceAndUserCodeLifespan(ctx)).Round(time.Second)) + if err := d.Storage.CreateUserCodeSession(ctx, userCodeSignature, dar.Sanitize(nil)); err != nil { + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + + // Populate the response fields + resp.SetDeviceCode(deviceCode) + resp.SetUserCode(userCode) + resp.SetVerificationURI(d.Config.GetDeviceVerificationURL(ctx)) + resp.SetVerificationURIComplete(d.Config.GetDeviceVerificationURL(ctx) + "?user_code=" + userCode) + resp.SetExpiresIn(int64(time.Until(dar.GetSession().GetExpiresAt(fosite.UserCode)).Seconds())) + resp.SetInterval(int(d.Config.GetDeviceAuthTokenPollingInterval(ctx).Seconds())) + return nil +} diff --git a/handler/rfc8628/auth_handler_test.go b/handler/rfc8628/auth_handler_test.go new file mode 100644 index 000000000..c29112bf6 --- /dev/null +++ b/handler/rfc8628/auth_handler_test.go @@ -0,0 +1,79 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package rfc8628_test + +import ( + "context" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/ory/fosite" + . "github.com/ory/fosite/handler/rfc8628" + "github.com/ory/fosite/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_HandleDeviceEndpointRequest(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + store := storage.NewMemoryStore() + handler := DeviceAuthHandler{ + Storage: store, + Strategy: &hmacshaStrategy, + Config: &fosite.Config{ + DeviceAndUserCodeLifespan: time.Minute * 10, + DeviceAuthTokenPollingInterval: time.Second * 5, + DeviceVerificationURL: "www.test.com", + AccessTokenLifespan: time.Hour, + RefreshTokenLifespan: time.Hour, + ScopeStrategy: fosite.HierarchicScopeStrategy, + AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, + RefreshTokenScopes: []string{"offline"}, + }, + } + + for _, c := range []struct { + handler DeviceAuthHandler + req *fosite.DeviceRequest + description string + expectErr error + expect func(t *testing.T, req *fosite.DeviceRequest, resp *fosite.DeviceResponse) + }{ + { + handler: handler, + req: &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ + Audience: []string{"https://www.ory.sh/api"}, + }, + Session: &fosite.DefaultSession{}, + }, + }, + expect: func(t *testing.T, req *fosite.DeviceRequest, resp *fosite.DeviceResponse) { + assert.NotEmpty(t, resp.GetDeviceCode()) + assert.NotEmpty(t, resp.GetUserCode()) + assert.Equal(t, len(resp.GetUserCode()), 8) + assert.Contains(t, resp.GetDeviceCode(), "ory_dc_") + assert.Contains(t, resp.GetDeviceCode(), ".") + assert.Equal(t, resp.GetVerificationURI(), "www.test.com") + }, + }, + } { + t.Run("case="+c.description, func(t *testing.T) { + resp := fosite.NewDeviceResponse() + err := c.handler.HandleDeviceEndpointRequest(context.Background(), c.req, resp) + if c.expectErr != nil { + require.EqualError(t, err, c.expectErr.Error()) + } else { + require.NoError(t, err) + } + + if c.expect != nil { + c.expect(t, c.req, resp) + } + }) + } +} diff --git a/handler/rfc8628/storage.go b/handler/rfc8628/storage.go new file mode 100644 index 000000000..f356c6aaa --- /dev/null +++ b/handler/rfc8628/storage.go @@ -0,0 +1,52 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package rfc8628 + +import ( + "context" + + "github.com/ory/fosite" + "github.com/ory/fosite/handler/oauth2" +) + +type RFC8628CoreStorage interface { + DeviceCodeStorage + UserCodeStorage + oauth2.AccessTokenStorage + oauth2.RefreshTokenStorage +} + +type DeviceCodeStorage interface { + // CreateDeviceCodeSession stores the device request for a given device code. + CreateDeviceCodeSession(ctx context.Context, signature string, request fosite.Requester) (err error) + + // GetDeviceCodeSession hydrates the session based on the given device code and returns the device request. + // If the device code has been invalidated with `InvalidateDeviceCodeSession`, this + // method should return the ErrInvalidatedDeviceCode error. + // + // Make sure to also return the fosite.Requester value when returning the fosite.ErrInvalidatedDeviceCode error! + GetDeviceCodeSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) + + // InvalidateDeviceCodeSession is called when an device code is being used. The state of the user + // code should be set to invalid and consecutive requests to GetDeviceCodeSession should return the + // ErrInvalidatedDeviceCode error. + InvalidateDeviceCodeSession(ctx context.Context, signature string) (err error) +} + +type UserCodeStorage interface { + // CreateUserCodeSession stores the device request for a given user code. + CreateUserCodeSession(ctx context.Context, signature string, request fosite.Requester) (err error) + + // GetUserCodeSession hydrates the session based on the given user code and returns the device request. + // If the user code has been invalidated with `InvalidateUserCodeSession`, this + // method should return the ErrInvalidatedUserCode error. + // + // Make sure to also return the fosite.Requester value when returning the fosite.ErrInvalidatedUserCode error! + GetUserCodeSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) + + // InvalidateUserCodeSession is called when an user code is being used. The state of the user + // code should be set to invalid and consecutive requests to GetUserCodeSession should return the + // ErrInvalidatedUserCode error. + InvalidateUserCodeSession(ctx context.Context, signature string) (err error) +} diff --git a/handler/rfc8628/strategy.go b/handler/rfc8628/strategy.go new file mode 100644 index 000000000..3b0df7a71 --- /dev/null +++ b/handler/rfc8628/strategy.go @@ -0,0 +1,32 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package rfc8628 + +import ( + "context" + + "github.com/ory/fosite" +) + +type RFC8628CodeStrategy interface { + DeviceRateLimitStrategy + DeviceCodeStrategy + UserCodeStrategy +} + +type DeviceRateLimitStrategy interface { + ShouldRateLimit(ctx context.Context, code string) bool +} + +type DeviceCodeStrategy interface { + DeviceCodeSignature(ctx context.Context, code string) (signature string, err error) + GenerateDeviceCode(ctx context.Context) (code string, signature string, err error) + ValidateDeviceCode(ctx context.Context, r fosite.Requester, code string) (err error) +} + +type UserCodeStrategy interface { + UserCodeSignature(ctx context.Context, code string) (signature string, err error) + GenerateUserCode(ctx context.Context) (code string, signature string, err error) + ValidateUserCode(ctx context.Context, r fosite.Requester, code string) (err error) +} diff --git a/handler/rfc8628/strategy_hmacsha.go b/handler/rfc8628/strategy_hmacsha.go new file mode 100644 index 000000000..a43fb82d5 --- /dev/null +++ b/handler/rfc8628/strategy_hmacsha.go @@ -0,0 +1,84 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package rfc8628 + +import ( + "context" + + "github.com/ory/x/randx" + "github.com/patrickmn/go-cache" + "golang.org/x/time/rate" + + "github.com/ory/fosite" + enigma "github.com/ory/fosite/token/hmac" +) + +type DefaultDeviceStrategy struct { + Enigma *enigma.HMACStrategy + RateLimiterCache *cache.Cache + Config interface { + fosite.DeviceProvider + fosite.DeviceAndUserCodeLifespanProvider + } +} + +var _ RFC8628CodeStrategy = (*DefaultDeviceStrategy)(nil) + +func (h *DefaultDeviceStrategy) GenerateUserCode(ctx context.Context) (token string, signature string, err error) { + seq, err := randx.RuneSequence(8, []rune(randx.AlphaUpper)) + if err != nil { + return "", "", err + } + userCode := string(seq) + signUserCode, signErr := h.UserCodeSignature(ctx, userCode) + if signErr != nil { + return "", "", err + } + return userCode, signUserCode, nil +} + +func (h *DefaultDeviceStrategy) UserCodeSignature(ctx context.Context, token string) (signature string, err error) { + return h.Enigma.Signature(token), nil +} + +func (h *DefaultDeviceStrategy) ValidateUserCode(ctx context.Context, r fosite.Requester, code string) (err error) { + // TODO + return nil +} + +func (h *DefaultDeviceStrategy) GenerateDeviceCode(ctx context.Context) (token string, signature string, err error) { + token, sig, err := h.Enigma.Generate(ctx) + if err != nil { + return "", "", err + } + + return "ory_dc_" + token, sig, nil +} + +func (h *DefaultDeviceStrategy) DeviceCodeSignature(ctx context.Context, token string) (signature string, err error) { + return h.Enigma.Signature(token), nil +} + +func (h *DefaultDeviceStrategy) ValidateDeviceCode(ctx context.Context, r fosite.Requester, code string) (err error) { + // TODO + return nil +} + +func (t *DefaultDeviceStrategy) ShouldRateLimit(context context.Context, code string) bool { + key := code + "_limiter" + + if x, found := t.RateLimiterCache.Get(key); found { + return !x.(*rate.Limiter).Allow() + } + + rateLimiter := rate.NewLimiter( + rate.Every( + t.Config.GetDeviceAuthTokenPollingInterval(context), + ), + 1, + ) + + t.RateLimiterCache.Set(key, rateLimiter, cache.DefaultExpiration) + return false +} diff --git a/handler/rfc8628/strategy_hmacsha_test.go b/handler/rfc8628/strategy_hmacsha_test.go new file mode 100644 index 000000000..894fc2ae3 --- /dev/null +++ b/handler/rfc8628/strategy_hmacsha_test.go @@ -0,0 +1,131 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package rfc8628_test + +import ( + "context" + "fmt" + "regexp" + "strings" + "testing" + "time" + + "github.com/patrickmn/go-cache" + "github.com/stretchr/testify/assert" + + "github.com/ory/fosite" + . "github.com/ory/fosite/handler/rfc8628" + "github.com/ory/fosite/token/hmac" +) + +var hmacshaStrategy = DefaultDeviceStrategy{ + Enigma: &hmac.HMACStrategy{Config: &fosite.Config{GlobalSecret: []byte("foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar")}}, + RateLimiterCache: cache.New(24*time.Minute, 2*24*time.Minute), + Config: &fosite.Config{ + AccessTokenLifespan: time.Minute * 24, + AuthorizeCodeLifespan: time.Minute * 24, + DeviceAndUserCodeLifespan: time.Minute * 24, + DeviceAuthTokenPollingInterval: 400 * time.Millisecond, + }, +} + +var hmacValidCase = fosite.Request{ + Client: &fosite.DefaultClient{ + Secret: []byte("foobarfoobarfoobarfoobar"), + }, + Session: &fosite.DefaultSession{ + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.UserCode: time.Now().UTC().Add(time.Hour), + fosite.DeviceCode: time.Now().UTC().Add(time.Hour), + }, + }, +} + +func TestHMACUserCode(t *testing.T) { + for k, c := range []struct { + r fosite.Request + pass bool + }{ + { + r: hmacValidCase, + pass: true, + }, + // TODO: add tests for expired codes + } { + t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { + userCode, signature, err := hmacshaStrategy.GenerateUserCode(context.TODO()) + assert.NoError(t, err) + regex := regexp.MustCompile("[ABCDEFGHIJKLMNOPQRSTUVWXYZ]{8}") + assert.Equal(t, len(regex.FindString(userCode)), len(userCode)) + + err = hmacshaStrategy.ValidateUserCode(context.TODO(), &c.r, userCode) + if c.pass { + assert.NoError(t, err) + validate, _ := hmacshaStrategy.Enigma.GenerateHMACForString(context.TODO(), userCode) + assert.Equal(t, signature, validate) + testSign, err := hmacshaStrategy.UserCodeSignature(context.TODO(), userCode) + assert.NoError(t, err) + assert.Equal(t, testSign, signature) + } else { + assert.Error(t, err) + } + }) + } +} + +func TestHMACDeviceCode(t *testing.T) { + for k, c := range []struct { + r fosite.Request + pass bool + }{ + { + r: hmacValidCase, + pass: true, + }, + // TODO: add tests for expired codes + } { + t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { + token, signature, err := hmacshaStrategy.GenerateDeviceCode(context.TODO()) + assert.NoError(t, err) + assert.Equal(t, strings.Split(token, ".")[1], signature) + assert.Contains(t, token, "ory_dc_") + + for k, token := range []string{ + token, + strings.TrimPrefix(token, "ory_dc_"), + } { + t.Run(fmt.Sprintf("prefix=%v", k == 0), func(t *testing.T) { + err = hmacshaStrategy.ValidateDeviceCode(context.TODO(), &c.r, token) + if c.pass { + assert.NoError(t, err) + validate := hmacshaStrategy.Enigma.Signature(token) + assert.Equal(t, signature, validate) + testSign, err := hmacshaStrategy.DeviceCodeSignature(context.TODO(), token) + assert.NoError(t, err) + assert.Equal(t, testSign, signature) + } else { + assert.Error(t, err) + } + }) + } + }) + } +} + +func TestRateLimit(t *testing.T) { + t.Run("ratelimit no-wait", func(t *testing.T) { + hmacshaStrategy.RateLimiterCache.Flush() + assert.False(t, hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA")) + assert.False(t, hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA")) + assert.True(t, hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA")) + }) + + t.Run("ratelimit wait", func(t *testing.T) { + hmacshaStrategy.RateLimiterCache.Flush() + assert.False(t, hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA")) + assert.False(t, hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA")) + time.Sleep(500 * time.Millisecond) + assert.False(t, hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA")) + }) +} From f04f7ec1bde80f220da8e3df233af49cda47f98e Mon Sep 17 00:00:00 2001 From: Nikos Date: Tue, 6 Feb 2024 14:46:34 +0200 Subject: [PATCH 05/17] fix: add device flow handlers to compose --- compose/compose.go | 6 +++ compose/compose_rfc8628.go | 19 +++++++++ compose/compose_strategy.go | 15 +++++++ config.go | 15 +++++++ config_default.go | 34 ++++++++++++++++ fosite.go | 17 ++++++++ go.mod | 6 ++- go.sum | 25 ++++++++++++ handler.go | 10 +++++ handler/rfc8628/strategy_hmacsha.go | 2 +- oauth2.go | 63 ++++++++++++++++++++++++++++- token/hmac/hmacsha.go | 20 +++++++++ token/hmac/hmacsha_test.go | 30 ++++++++++++++ 13 files changed, 259 insertions(+), 3 deletions(-) create mode 100644 compose/compose_rfc8628.go diff --git a/compose/compose.go b/compose/compose.go index 4ded32e5f..3d80856f9 100644 --- a/compose/compose.go +++ b/compose/compose.go @@ -53,6 +53,9 @@ func Compose(config *fosite.Config, storage interface{}, strategy interface{}, f if ph, ok := res.(fosite.PushedAuthorizeEndpointHandler); ok { config.PushedAuthorizeEndpointHandlers.Append(ph) } + if dh, ok := res.(fosite.DeviceEndpointHandler); ok { + config.DeviceEndpointHandlers.Append(dh) + } } return f @@ -68,6 +71,7 @@ func ComposeAllEnabled(config *fosite.Config, storage interface{}, key interface storage, &CommonStrategy{ CoreStrategy: NewOAuth2HMACStrategy(config), + RFC8628CodeStrategy: NewDeviceStrategy(config), OpenIDConnectTokenStrategy: NewOpenIDConnectStrategy(keyGetter, config), Signer: &jwt.DefaultSigner{GetPrivateKey: keyGetter}, }, @@ -86,6 +90,8 @@ func ComposeAllEnabled(config *fosite.Config, storage interface{}, key interface OAuth2TokenIntrospectionFactory, OAuth2TokenRevocationFactory, + RFC8628DeviceFactory, + OAuth2PKCEFactory, PushedAuthorizeHandlerFactory, ) diff --git a/compose/compose_rfc8628.go b/compose/compose_rfc8628.go new file mode 100644 index 000000000..fb377606b --- /dev/null +++ b/compose/compose_rfc8628.go @@ -0,0 +1,19 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package compose + +import ( + "github.com/ory/fosite" + "github.com/ory/fosite/handler/rfc8628" +) + +// RFC8628DeviceFactory creates an OAuth2 device code grant ("Device Authorization Grant") handler and registers +// an user code, device code, access token and a refresh token validator. +func RFC8628DeviceFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} { + return &rfc8628.DeviceAuthHandler{ + Strategy: strategy.(rfc8628.RFC8628CodeStrategy), + Storage: storage.(rfc8628.RFC8628CoreStorage), + Config: config, + } +} diff --git a/compose/compose_strategy.go b/compose/compose_strategy.go index cddcea5ef..748d8eab4 100644 --- a/compose/compose_strategy.go +++ b/compose/compose_strategy.go @@ -9,12 +9,15 @@ import ( "github.com/ory/fosite" "github.com/ory/fosite/handler/oauth2" "github.com/ory/fosite/handler/openid" + "github.com/ory/fosite/handler/rfc8628" "github.com/ory/fosite/token/hmac" "github.com/ory/fosite/token/jwt" + "github.com/patrickmn/go-cache" ) type CommonStrategy struct { oauth2.CoreStrategy + rfc8628.RFC8628CodeStrategy openid.OpenIDConnectTokenStrategy jwt.Signer } @@ -27,6 +30,7 @@ type HMACSHAStrategyConfigurator interface { fosite.GlobalSecretProvider fosite.RotatedGlobalSecretsProvider fosite.HMACHashingProvider + fosite.DeviceAndUserCodeLifespanProvider } func NewOAuth2HMACStrategy(config HMACSHAStrategyConfigurator) *oauth2.HMACSHAStrategy { @@ -50,3 +54,14 @@ func NewOpenIDConnectStrategy(keyGetter func(context.Context) (interface{}, erro Config: config, } } + +func NewDeviceStrategy(config fosite.Configurator) *rfc8628.DefaultDeviceStrategy { + return &rfc8628.DefaultDeviceStrategy{ + Enigma: &hmac.HMACStrategy{Config: config}, + RateLimiterCache: cache.New( + config.GetDeviceAndUserCodeLifespan(context.TODO()), + config.GetDeviceAndUserCodeLifespan(context.TODO())*2, + ), + Config: config, + } +} diff --git a/config.go b/config.go index 165e7b4b0..d95fa88d9 100644 --- a/config.go +++ b/config.go @@ -46,6 +46,10 @@ type IDTokenLifespanProvider interface { GetIDTokenLifespan(ctx context.Context) time.Duration } +type DeviceAndUserCodeLifespanProvider interface { + GetDeviceAndUserCodeLifespan(ctx context.Context) time.Duration +} + // ScopeStrategyProvider returns the provider for configuring the scope strategy. type ScopeStrategyProvider interface { // GetScopeStrategy returns the scope strategy. @@ -76,6 +80,11 @@ type DisableRefreshTokenValidationProvider interface { GetDisableRefreshTokenValidation(ctx context.Context) bool } +type DeviceProvider interface { + GetDeviceVerificationURL(ctx context.Context) string + GetDeviceAuthTokenPollingInterval(ctx context.Context) time.Duration +} + // BCryptCostProvider returns the provider for configuring the BCrypt hash cost. type BCryptCostProvider interface { // GetBCryptCost returns the BCrypt hash cost. @@ -306,3 +315,9 @@ type PushedAuthorizeRequestConfigProvider interface { // must contain the PAR request_uri. EnforcePushedAuthorize(ctx context.Context) bool } + +// DeviceEndpointHandlersProvider returns the provider for setting up the Device handlers. +type DeviceEndpointHandlersProvider interface { + // GetDeviceEndpointHandlers returns the handlers. + GetDeviceEndpointHandlers(ctx context.Context) DeviceEndpointHandlers +} diff --git a/config_default.go b/config_default.go index a2ae5ccec..cc6acece3 100644 --- a/config_default.go +++ b/config_default.go @@ -84,6 +84,15 @@ type Config struct { // IDTokenIssuer sets the default issuer of the ID Token. IDTokenIssuer string + // Sets how long a device user/device code pair is valid for + DeviceAndUserCodeLifespan time.Duration + + // DeviceAuthTokenPollingInterval sets the interval that clients should check for device code grants + DeviceAuthTokenPollingInterval time.Duration + + // DeviceVerificationURL is the URL of the device verification endpoint, this is is included with the device code request responses + DeviceVerificationURL string + // HashCost sets the cost of the password hashing cost. Defaults to 12. HashCost int @@ -213,6 +222,9 @@ type Config struct { // PushedAuthorizeContextLifespan is the lifespan of the PAR context PushedAuthorizeContextLifespan time.Duration + // DeviceEndpointHandlers is a list of handlers that are called before the device endpoint is served. + DeviceEndpointHandlers DeviceEndpointHandlers + // IsPushedAuthorizeEnforced enforces pushed authorization request for /authorize IsPushedAuthorizeEnforced bool } @@ -245,6 +257,10 @@ func (c *Config) GetTokenIntrospectionHandlers(ctx context.Context) TokenIntrosp return c.TokenIntrospectionHandlers } +func (c *Config) GetDeviceEndpointHandlers(ctx context.Context) DeviceEndpointHandlers { + return c.DeviceEndpointHandlers +} + func (c *Config) GetRevocationHandlers(ctx context.Context) RevocationHandlers { return c.RevocationHandlers } @@ -396,6 +412,13 @@ func (c *Config) GetRefreshTokenLifespan(_ context.Context) time.Duration { return c.RefreshTokenLifespan } +func (c *Config) GetDeviceAndUserCodeLifespan(_ context.Context) time.Duration { + if c.DeviceAndUserCodeLifespan == 0 { + return time.Minute * 10 + } + return c.DeviceAndUserCodeLifespan +} + // GetBCryptCost returns the bcrypt cost factor. Defaults to 12. func (c *Config) GetBCryptCost(_ context.Context) int { if c.HashCost == 0 { @@ -499,3 +522,14 @@ func (c *Config) GetPushedAuthorizeContextLifespan(ctx context.Context) time.Dur func (c *Config) EnforcePushedAuthorize(ctx context.Context) bool { return c.IsPushedAuthorizeEnforced } + +func (c *Config) GetDeviceVerificationURL(ctx context.Context) string { + return c.DeviceVerificationURL +} + +func (c *Config) GetDeviceAuthTokenPollingInterval(ctx context.Context) time.Duration { + if c.DeviceAuthTokenPollingInterval == 0 { + return time.Second * 5 + } + return c.DeviceAuthTokenPollingInterval +} diff --git a/fosite.go b/fosite.go index e84c964e2..c8fc05b58 100644 --- a/fosite.go +++ b/fosite.go @@ -82,6 +82,20 @@ func (a *PushedAuthorizeEndpointHandlers) Append(h PushedAuthorizeEndpointHandle *a = append(*a, h) } +// DeviceEndpointHandlers is a list of DeviceEndpointHandler +type DeviceEndpointHandlers []DeviceEndpointHandler + +// Append adds an DeviceEndpointHandlers to this list. Ignores duplicates based on reflect.TypeOf. +func (a *DeviceEndpointHandlers) Append(h DeviceEndpointHandler) { + for _, this := range *a { + if reflect.TypeOf(this) == reflect.TypeOf(h) { + return + } + } + + *a = append(*a, h) +} + var _ OAuth2Provider = (*Fosite)(nil) type Configurator interface { @@ -108,6 +122,7 @@ type Configurator interface { RefreshTokenLifespanProvider VerifiableCredentialsNonceLifespanProvider AuthorizeCodeLifespanProvider + DeviceAndUserCodeLifespanProvider TokenEntropyProvider RotatedGlobalSecretsProvider GlobalSecretProvider @@ -132,6 +147,8 @@ type Configurator interface { TokenIntrospectionHandlersProvider RevocationHandlersProvider UseLegacyErrorFormatProvider + DeviceEndpointHandlersProvider + DeviceProvider } func NewOAuth2Provider(s Storage, c Configurator) *Fosite { diff --git a/go.mod b/go.mod index ad75e99a3..052a60881 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/ory/go-convenience v0.1.0 github.com/ory/x v0.0.575 github.com/parnurzeal/gorequest v0.2.15 + github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.4 github.com/tidwall/gjson v1.14.3 @@ -33,6 +34,7 @@ require ( golang.org/x/net v0.13.0 golang.org/x/oauth2 v0.10.0 golang.org/x/text v0.11.0 + golang.org/x/time v0.1.0 ) require ( @@ -100,4 +102,6 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -go 1.20 +go 1.21 + +toolchain go1.21.4 diff --git a/go.sum b/go.sum index c23397f58..b268930d6 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,7 @@ github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -176,6 +177,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -209,6 +211,7 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -259,6 +262,7 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0f github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jandelgado/gcov2lcov v1.0.5 h1:rkBt40h0CVK4oCb8Dps950gvfd1rYvQ8+cWa346lVU0= +github.com/jandelgado/gcov2lcov v1.0.5/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -269,18 +273,24 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/parsers/json v0.1.0 h1:dzSZl5pf5bBcW0Acnu20Djleto19T0CfHcvZ14NJ6fU= +github.com/knadh/koanf/parsers/json v0.1.0/go.mod h1:ll2/MlXcZ2BfXD6YJcjVFzhG9P0TdJ207aIBKQhV2hY= github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME= +github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c= github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g= +github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -293,25 +303,30 @@ github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcncea github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/goveralls v0.0.12 h1:PEEeF0k1SsTjOBQ8FOmrOAoCu4ytuMaWCnWe94zxbCg= github.com/mattn/goveralls v0.0.12/go.mod h1:44ImGEUfmqH8bBtaMrYKsM65LXfNLWmwaxFGjZwgMSQ= github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b h1:Pip12xNtMvEFUBF4f8/b5yRXj94LLrNdLWELfOr2KcY= github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/nyaruka/phonenumbers v1.1.1 h1:fyoZmpLN2VCmAnc51XcrNOUVP2wT1ZzQl348ggIaXII= +github.com/nyaruka/phonenumbers v1.1.1/go.mod h1:cGaEsOrLjIL0iKGqJR5Rfywy86dSkbApEpXuM9KySNA= github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60= github.com/openzipkin/zipkin-go v0.4.1 h1:kNd/ST2yLLWhaWrkgchya40TJabe8Hioj9udfPcEO5A= @@ -321,11 +336,15 @@ github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe/go.mod h1:z4n3u6as84L github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTsTS8= github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs= github.com/ory/herodot v0.10.3-0.20230626083119-d7e5192f0d88 h1:J0CIFKdpUeqKbVMw7pQ1qLtUnflRM1JWAcOEq7Hp4yg= +github.com/ory/herodot v0.10.3-0.20230626083119-d7e5192f0d88/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc= github.com/ory/jsonschema/v3 v3.0.7 h1:GQ9qfZDiJqs4l2d3p56dozCChvejQFZyLKGHYzDzOSo= +github.com/ory/jsonschema/v3 v3.0.7/go.mod h1:g8c8YOtN4TrR2wYeMdT02GDmzJDI0fEW2nI26BECafY= github.com/ory/x v0.0.575 h1:LvOeR+YlJ6/JtvIJvSwMoDBY/i3GACUe7HpWXHGNUTA= github.com/ory/x v0.0.575/go.mod h1:aeJFTlvDLGYSABzPS3z5SeLcYC52Ek7uGZiuYGcTMSU= github.com/parnurzeal/gorequest v0.2.15 h1:oPjDCsF5IkD4gUk6vIgsxYNaSgvAnIh1EJeROn3HdJU= github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -399,7 +418,9 @@ github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhso github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -576,6 +597,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -652,6 +674,8 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 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= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -824,6 +848,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= diff --git a/handler.go b/handler.go index 2b9d0b412..d95b0f367 100644 --- a/handler.go +++ b/handler.go @@ -66,3 +66,13 @@ type PushedAuthorizeEndpointHandler interface { // the pushed authorize request, he must return nil and NOT modify session nor responder neither requester. HandlePushedAuthorizeEndpointRequest(ctx context.Context, requester AuthorizeRequester, responder PushedAuthorizeResponder) error } + +type DeviceEndpointHandler interface { + // HandleDeviceEndpointRequest handles a device authorize endpoint request. To extend the handler's capabilities, the http request + // is passed along, if further information retrieval is required. If the handler feels that he is not responsible for + // the device authorize request, he must return nil and NOT modify session nor responder neither requester. + // + // The following spec is a good example of what HandleDeviceUserRequest should do. + // * https://tools.ietf.org/html/rfc8628#section-3.2 + HandleDeviceEndpointRequest(ctx context.Context, requester DeviceRequester, responder DeviceResponder) error +} diff --git a/handler/rfc8628/strategy_hmacsha.go b/handler/rfc8628/strategy_hmacsha.go index a43fb82d5..3e600a164 100644 --- a/handler/rfc8628/strategy_hmacsha.go +++ b/handler/rfc8628/strategy_hmacsha.go @@ -39,7 +39,7 @@ func (h *DefaultDeviceStrategy) GenerateUserCode(ctx context.Context) (token str } func (h *DefaultDeviceStrategy) UserCodeSignature(ctx context.Context, token string) (signature string, err error) { - return h.Enigma.Signature(token), nil + return h.Enigma.GenerateHMACForString(ctx, token) } func (h *DefaultDeviceStrategy) ValidateUserCode(ctx context.Context, r fosite.Requester, code string) (err error) { diff --git a/oauth2.go b/oauth2.go index a52eb1f6f..b1d737441 100644 --- a/oauth2.go +++ b/oauth2.go @@ -23,6 +23,8 @@ const ( RefreshToken TokenType = "refresh_token" AuthorizeCode TokenType = "authorize_code" IDToken TokenType = "id_token" + UserCode TokenType = "user_code" + DeviceCode TokenType = "device_code" // PushedAuthorizeRequestContext represents the PAR context object PushedAuthorizeRequestContext TokenType = "par_context" @@ -31,7 +33,8 @@ const ( GrantTypeAuthorizationCode GrantType = "authorization_code" GrantTypePassword GrantType = "password" GrantTypeClientCredentials GrantType = "client_credentials" - GrantTypeJWTBearer GrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer" //nolint:gosec // this is not a hardcoded credential + GrantTypeJWTBearer GrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer" //nolint:gosec // this is not a hardcoded credential + GrantTypeDeviceCode GrantType = "urn:ietf:params:oauth:grant-type:device_code" //nolint:gosec // this is not a hardcoded credential BearerAccessToken string = "bearer" ) @@ -128,6 +131,34 @@ type OAuth2Provider interface { // https://tools.ietf.org/html/rfc6749#section-5.1 WriteAccessResponse(ctx context.Context, rw http.ResponseWriter, requester AccessRequester, responder AccessResponder) + // NewDeviceRequest validate the OAuth 2.0 Device Authorization Flow Request + // + // The following specs must be considered in any implementation of this method: + // * https://www.rfc-editor.org/rfc/rfc8628#section-3.1 (everything MUST be implemented) + // Parameters sent without a value MUST be treated as if they were + // omitted from the request. The authorization server MUST ignore + // unrecognized request parameters. Request and response parameters + // MUST NOT be included more than once. + NewDeviceRequest(ctx context.Context, req *http.Request) (DeviceRequester, error) + + // NewDeviceResponse persists the DeviceCodeSession and UserCodeSession in the store + // + // The following specs must be considered in any implementation of this method: + // * https://www.rfc-editor.org/rfc/rfc8628#section-3.2 (everything MUST be implemented) + // In response, the authorization server generates a unique device + // verification code and an end-user code that are valid for a limited + // time + NewDeviceResponse(ctx context.Context, requester DeviceRequester, session Session) (DeviceResponder, error) + + // WriteDeviceResponse return to the user both codes and + // some configuration informations in a JSON formated manner + // + // The following specs must be considered in any implementation of this method: + // * https://www.rfc-editor.org/rfc/rfc8628#section-3.2 (everything MUST be implemented) + // Response is a HTTP response body using the + // "application/json" format [RFC8259] with a 200 (OK) status code. + WriteDeviceResponse(ctx context.Context, rw http.ResponseWriter, requester DeviceRequester, responder DeviceResponder) + // NewRevocationRequest handles incoming token revocation requests and validates various parameters. // // The following specs must be considered in any implementation of this method: @@ -253,6 +284,11 @@ type AccessRequester interface { Requester } +// DeviceRequester is an device endpoint's request context. +type DeviceRequester interface { + Requester +} + // AuthorizeRequester is an authorize endpoint's request context. type AuthorizeRequester interface { // GetResponseTypes returns the requested response types @@ -365,3 +401,28 @@ type G11NContext interface { // GetLang returns the current language in the context GetLang() language.Tag } + +type DeviceResponder interface { + GetDeviceCode() string + SetDeviceCode(code string) + + GetUserCode() string + SetUserCode(code string) + + GetVerificationURI() string + SetVerificationURI(uri string) + + GetVerificationURIComplete() string + SetVerificationURIComplete(uri string) + + GetExpiresIn() int64 + SetExpiresIn(seconds int64) + + GetInterval() int + SetInterval(seconds int) + + // GetHeader returns the response's header + GetHeader() (header http.Header) + // AddHeader adds an header key value pair to the response + AddHeader(key, value string) +} diff --git a/token/hmac/hmacsha.go b/token/hmac/hmacsha.go index 84db7c529..86875f6b1 100644 --- a/token/hmac/hmacsha.go +++ b/token/hmac/hmacsha.go @@ -170,6 +170,26 @@ func (c *HMACStrategy) Signature(token string) string { return split[1] } +func (c *HMACStrategy) GenerateHMACForString(ctx context.Context, text string) (string, error) { + var signingKey [32]byte + + secrets, err := c.Config.GetGlobalSecret(ctx) + if err != nil { + return "", err + } + + if len(secrets) < minimumSecretLength { + return "", errors.Errorf("secret for signing HMAC-SHA512/256 is expected to be 32 byte long, got %d byte", len(secrets)) + } + copy(signingKey[:], secrets) + + bytes := []byte(text) + hashBytes := c.generateHMAC(ctx, bytes, &signingKey) + + b64 := base64.RawURLEncoding.EncodeToString(hashBytes) + return b64, nil +} + func (c *HMACStrategy) generateHMAC(ctx context.Context, data []byte, key *[32]byte) []byte { hasher := c.Config.GetHMACHasher(ctx) if hasher == nil { diff --git a/token/hmac/hmacsha_test.go b/token/hmac/hmacsha_test.go index 0df788302..fb3e60945 100644 --- a/token/hmac/hmacsha_test.go +++ b/token/hmac/hmacsha_test.go @@ -133,3 +133,33 @@ func TestCustomHMAC(t *testing.T) { require.NoError(t, sha512.Validate(context.Background(), token512)) require.EqualError(t, def.Validate(context.Background(), token512), fosite.ErrTokenSignatureMismatch.Error()) } + +func TestGenerateFromString(t *testing.T) { + cg := HMACStrategy{Config: &fosite.Config{ + GlobalSecret: []byte("1234567890123456789012345678901234567890")}, + } + for _, c := range []struct { + text string + hash string + }{ + { + text: "", + hash: "-n7EqD-bXkY3yYMH-ctEAGV8XLkU7Y6Bo6pbyT1agGA", + }, + { + text: " ", + hash: "zXJvonHTNSOOGj_QKl4RpIX_zXgD2YfXUfwuDKaTTIg", + }, + { + text: "Test", + hash: "TMeEaHS-cDC2nijiesCNtsOyBqHHtzWqAcWvceQT50g", + }, + { + text: "AnotherTest1234", + hash: "zHYDOZGjzhVjx5r8RlBhpnJemX5JxEEBUjVT01n3IFM", + }, + } { + hash, _ := cg.GenerateHMACForString(context.Background(), c.text) + assert.Equal(t, c.hash, hash) + } +} From eb86df8efe672c8974db8b2a3db539f0e54f5143 Mon Sep 17 00:00:00 2001 From: Nikos Date: Tue, 6 Feb 2024 16:28:49 +0200 Subject: [PATCH 06/17] fix: update memory storage --- storage/memory.go | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/storage/memory.go b/storage/memory.go index 3c6a5ad45..b4f88aa8a 100644 --- a/storage/memory.go +++ b/storage/memory.go @@ -41,12 +41,16 @@ type MemoryStore struct { IDSessions map[string]fosite.Requester AccessTokens map[string]fosite.Requester RefreshTokens map[string]StoreRefreshToken + DeviceCodes map[string]fosite.Requester + UserCodes map[string]fosite.Requester PKCES map[string]fosite.Requester Users map[string]MemoryUserRelation BlacklistedJTIs map[string]time.Time // In-memory request ID to token signatures AccessTokenRequestIDs map[string]string RefreshTokenRequestIDs map[string]string + DeviceCodesRequestIDs map[string]string + UserCodesRequestIDs map[string]string // Public keys to check signature in auth grant jwt assertion. IssuerPublicKeys map[string]IssuerPublicKeys PARSessions map[string]fosite.AuthorizeRequester @@ -56,11 +60,15 @@ type MemoryStore struct { idSessionsMutex sync.RWMutex accessTokensMutex sync.RWMutex refreshTokensMutex sync.RWMutex + userCodesMutex sync.RWMutex + deviceCodesMutex sync.RWMutex pkcesMutex sync.RWMutex usersMutex sync.RWMutex blacklistedJTIsMutex sync.RWMutex accessTokenRequestIDsMutex sync.RWMutex refreshTokenRequestIDsMutex sync.RWMutex + userCodesRequestIDsMutex sync.RWMutex + deviceCodesRequestIDsMutex sync.RWMutex issuerPublicKeysMutex sync.RWMutex parSessionsMutex sync.RWMutex } @@ -72,10 +80,14 @@ func NewMemoryStore() *MemoryStore { IDSessions: make(map[string]fosite.Requester), AccessTokens: make(map[string]fosite.Requester), RefreshTokens: make(map[string]StoreRefreshToken), + DeviceCodes: make(map[string]fosite.Requester), + UserCodes: make(map[string]fosite.Requester), PKCES: make(map[string]fosite.Requester), Users: make(map[string]MemoryUserRelation), AccessTokenRequestIDs: make(map[string]string), RefreshTokenRequestIDs: make(map[string]string), + DeviceCodesRequestIDs: make(map[string]string), + UserCodesRequestIDs: make(map[string]string), BlacklistedJTIs: make(map[string]time.Time), IssuerPublicKeys: make(map[string]IssuerPublicKeys), PARSessions: make(map[string]fosite.AuthorizeRequester), @@ -139,8 +151,12 @@ func NewExampleStore() *MemoryStore { AccessTokens: map[string]fosite.Requester{}, RefreshTokens: map[string]StoreRefreshToken{}, PKCES: map[string]fosite.Requester{}, + DeviceCodes: make(map[string]fosite.Requester), + UserCodes: make(map[string]fosite.Requester), AccessTokenRequestIDs: map[string]string{}, RefreshTokenRequestIDs: map[string]string{}, + DeviceCodesRequestIDs: make(map[string]string), + UserCodesRequestIDs: make(map[string]string), IssuerPublicKeys: map[string]IssuerPublicKeys{}, PARSessions: map[string]fosite.AuthorizeRequester{}, } @@ -497,3 +513,83 @@ func (s *MemoryStore) DeletePARSession(ctx context.Context, requestURI string) ( delete(s.PARSessions, requestURI) return nil } + +func (s *MemoryStore) CreateDeviceCodeSession(_ context.Context, signature string, req fosite.Requester) error { + // We first lock accessTokenRequestIDsMutex and then accessTokensMutex because this is the same order + // locking happens in RevokeAccessToken and using the same order prevents deadlocks. + s.deviceCodesRequestIDsMutex.Lock() + defer s.deviceCodesRequestIDsMutex.Unlock() + s.deviceCodesMutex.Lock() + defer s.deviceCodesMutex.Unlock() + + s.DeviceCodes[signature] = req + s.DeviceCodesRequestIDs[req.GetID()] = signature + return nil +} + +func (s *MemoryStore) UpdateDeviceCodeSession(_ context.Context, signature string, req fosite.Requester) error { + s.deviceCodesRequestIDsMutex.Lock() + defer s.deviceCodesRequestIDsMutex.Unlock() + s.deviceCodesMutex.Lock() + defer s.deviceCodesMutex.Unlock() + + // Only update if exist + if _, exists := s.DeviceCodes[signature]; exists { + s.DeviceCodes[signature] = req + s.DeviceCodesRequestIDs[req.GetID()] = signature + } + return nil +} + +func (s *MemoryStore) GetDeviceCodeSession(_ context.Context, signature string, _ fosite.Session) (fosite.Requester, error) { + s.deviceCodesMutex.RLock() + defer s.deviceCodesMutex.RUnlock() + + rel, ok := s.DeviceCodes[signature] + if !ok { + return nil, fosite.ErrNotFound + } + return rel, nil +} + +func (s *MemoryStore) InvalidateDeviceCodeSession(_ context.Context, code string) error { + s.deviceCodesRequestIDsMutex.Lock() + defer s.deviceCodesRequestIDsMutex.Unlock() + s.deviceCodesMutex.Lock() + defer s.deviceCodesMutex.Unlock() + + delete(s.DeviceCodes, code) + return nil +} + +func (s *MemoryStore) CreateUserCodeSession(_ context.Context, signature string, req fosite.Requester) error { + s.userCodesRequestIDsMutex.Lock() + defer s.userCodesRequestIDsMutex.Unlock() + s.userCodesMutex.Lock() + defer s.userCodesMutex.Unlock() + + s.UserCodes[signature] = req + s.UserCodesRequestIDs[req.GetID()] = signature + return nil +} + +func (s *MemoryStore) GetUserCodeSession(_ context.Context, signature string, _ fosite.Session) (fosite.Requester, error) { + s.userCodesMutex.RLock() + defer s.userCodesMutex.RUnlock() + + rel, ok := s.UserCodes[signature] + if !ok { + return nil, fosite.ErrNotFound + } + return rel, nil +} + +func (s *MemoryStore) InvalidateUserCodeSession(_ context.Context, code string) error { + s.userCodesRequestIDsMutex.Lock() + defer s.userCodesRequestIDsMutex.Unlock() + s.userCodesMutex.Lock() + defer s.userCodesMutex.Unlock() + + delete(s.UserCodes, code) + return nil +} From f2c68de52fff37d9870ab94d53efdb9e993332a6 Mon Sep 17 00:00:00 2001 From: Nikos Date: Wed, 7 Feb 2024 17:42:54 +0200 Subject: [PATCH 07/17] chore: update integration tests --- go.mod | 69 ++++---- go.sum | 154 +++++++++--------- .../authorize_device_grant_request_test.go | 141 ++++++++++++++++ integration/helper_endpoints_test.go | 24 +++ integration/helper_setup_test.go | 9 +- 5 files changed, 285 insertions(+), 112 deletions(-) create mode 100644 integration/authorize_device_grant_request_test.go diff --git a/go.mod b/go.mod index 052a60881..ff3a6610c 100644 --- a/go.mod +++ b/go.mod @@ -11,9 +11,9 @@ require ( github.com/cristalhq/jwt/v4 v4.0.2 github.com/dgraph-io/ristretto v0.1.1 github.com/ecordell/optgen v0.0.9 - github.com/go-jose/go-jose/v3 v3.0.0 + github.com/go-jose/go-jose/v3 v3.0.1 github.com/golang/mock v1.6.0 - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.3.1 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/hashicorp/go-retryablehttp v0.7.4 @@ -23,18 +23,18 @@ require ( github.com/oleiade/reflections v1.0.1 github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe github.com/ory/go-convenience v0.1.0 - github.com/ory/x v0.0.575 + github.com/ory/x v0.0.613 github.com/parnurzeal/gorequest v0.2.15 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.4 github.com/tidwall/gjson v1.14.3 - go.opentelemetry.io/otel/trace v1.16.0 - golang.org/x/crypto v0.11.0 - golang.org/x/net v0.13.0 - golang.org/x/oauth2 v0.10.0 - golang.org/x/text v0.11.0 - golang.org/x/time v0.1.0 + go.opentelemetry.io/otel/trace v1.21.0 + golang.org/x/crypto v0.18.0 + golang.org/x/net v0.20.0 + golang.org/x/oauth2 v0.15.0 + golang.org/x/text v0.14.0 + golang.org/x/time v0.4.0 ) require ( @@ -46,21 +46,21 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gobuffalo/pop/v6 v6.0.8 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/glog v1.1.1 // indirect + github.com/golang/glog v1.1.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b // indirect - github.com/openzipkin/zipkin-go v0.4.1 // indirect + github.com/openzipkin/zipkin-go v0.4.2 // indirect github.com/pelletier/go-toml/v2 v2.0.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761 // indirect @@ -75,28 +75,27 @@ require ( github.com/subosito/gotenv v1.4.2 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.42.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.17.0 // indirect - go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect - go.opentelemetry.io/contrib/samplers/jaegerremote v0.11.0 // indirect - go.opentelemetry.io/otel v1.16.0 // indirect - go.opentelemetry.io/otel/exporters/jaeger v1.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 // indirect - go.opentelemetry.io/otel/exporters/zipkin v1.16.0 // indirect - go.opentelemetry.io/otel/metric v1.16.0 // indirect - go.opentelemetry.io/otel/sdk v1.16.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.21.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.21.1 // indirect + go.opentelemetry.io/contrib/samplers/jaegerremote v0.15.1 // indirect + go.opentelemetry.io/otel v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/zipkin v1.21.0 // indirect + go.opentelemetry.io/otel/metric v1.21.0 // indirect + go.opentelemetry.io/otel/sdk v1.21.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/tools v0.11.1 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf // indirect - google.golang.org/grpc v1.57.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/tools v0.15.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index b268930d6..13b97e2f1 100644 --- a/go.sum +++ b/go.sum @@ -92,8 +92,8 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -101,13 +101,13 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= -github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= +github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -133,8 +133,8 @@ github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.1 h1:jxpi2eWoU84wbX9iIEyAeeoac3FLuifZpY9tcNUD9kw= -github.com/golang/glog v1.1.1/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -162,6 +162,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -176,8 +177,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -193,8 +194,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= @@ -205,8 +206,8 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 h1:dygLcbEBA+t/P7ck6a8AkXv6juQ4cK0RHBoh32jxhHM= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2/go.mod h1:Ap9RLCIJVtgQg1/BBgVEfypOAySvvlcpcVQkSzJCH4Y= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= @@ -329,18 +330,18 @@ github.com/nyaruka/phonenumbers v1.1.1 h1:fyoZmpLN2VCmAnc51XcrNOUVP2wT1ZzQl348gg github.com/nyaruka/phonenumbers v1.1.1/go.mod h1:cGaEsOrLjIL0iKGqJR5Rfywy86dSkbApEpXuM9KySNA= github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60= -github.com/openzipkin/zipkin-go v0.4.1 h1:kNd/ST2yLLWhaWrkgchya40TJabe8Hioj9udfPcEO5A= -github.com/openzipkin/zipkin-go v0.4.1/go.mod h1:qY0VqDSN1pOBN94dBc6w2GJlWLiovAyg7Qt6/I9HecM= +github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA= +github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY= github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe h1:rvu4obdvqR0fkSIJ8IfgzKOWwZ5kOT2UNfLq81Qk7rc= github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe/go.mod h1:z4n3u6as84LbV4YmgjHhnwtccQqzf4cZlSk9f1FhygI= github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTsTS8= github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs= -github.com/ory/herodot v0.10.3-0.20230626083119-d7e5192f0d88 h1:J0CIFKdpUeqKbVMw7pQ1qLtUnflRM1JWAcOEq7Hp4yg= -github.com/ory/herodot v0.10.3-0.20230626083119-d7e5192f0d88/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc= +github.com/ory/herodot v0.9.13 h1:cN/Z4eOkErl/9W7hDIDLb79IO/bfsH+8yscBjRpB4IU= +github.com/ory/herodot v0.9.13/go.mod h1:IWDs9kSvFQqw/cQ8zi5ksyYvITiUU4dI7glUrhZcJYo= github.com/ory/jsonschema/v3 v3.0.7 h1:GQ9qfZDiJqs4l2d3p56dozCChvejQFZyLKGHYzDzOSo= github.com/ory/jsonschema/v3 v3.0.7/go.mod h1:g8c8YOtN4TrR2wYeMdT02GDmzJDI0fEW2nI26BECafY= -github.com/ory/x v0.0.575 h1:LvOeR+YlJ6/JtvIJvSwMoDBY/i3GACUe7HpWXHGNUTA= -github.com/ory/x v0.0.575/go.mod h1:aeJFTlvDLGYSABzPS3z5SeLcYC52Ek7uGZiuYGcTMSU= +github.com/ory/x v0.0.613 h1:MHT0scH7hcrOkc3aH7qqYLzXVJkjhB0szWTwpD2lh8Q= +github.com/ory/x v0.0.613/go.mod h1:uH065puz8neija0neqwIN3PmXXfDsB9VbZTZ20Znoos= github.com/parnurzeal/gorequest v0.2.15 h1:oPjDCsF5IkD4gUk6vIgsxYNaSgvAnIh1EJeROn3HdJU= github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -357,8 +358,9 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= @@ -434,34 +436,32 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.42.0 h1:0vzgiFDsCh/jxRCR1xcRrtMoeCu2itXz/PsXst5P8rI= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.42.0/go.mod h1:y0vOY2OKFMOTvwxKfurStPayUUKGHlNeVqNneHmFXr0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 h1:pginetY7+onl4qN1vl0xW/V/v6OBZ0vVdH+esuJgvmM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0/go.mod h1:XiYsayHc36K3EByOO6nbAXnAWbrUxdjUROCEeeROOH8= -go.opentelemetry.io/contrib/propagators/b3 v1.17.0 h1:ImOVvHnku8jijXqkwCSyYKRDt2YrnGXD4BbhcpfbfJo= -go.opentelemetry.io/contrib/propagators/b3 v1.17.0/go.mod h1:IkfUfMpKWmynvvE0264trz0sf32NRTZL4nuAN9AbWRc= -go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 h1:Zbpbmwav32Ea5jSotpmkWEl3a6Xvd4tw/3xxGO1i05Y= -go.opentelemetry.io/contrib/propagators/jaeger v1.17.0/go.mod h1:tcTUAlmO8nuInPDSBVfG+CP6Mzjy5+gNV4mPxMbL0IA= -go.opentelemetry.io/contrib/samplers/jaegerremote v0.11.0 h1:P3JkQvs0s4Ww3hPb+jWFW9N6A0ioew7WwGTyqwgeofs= -go.opentelemetry.io/contrib/samplers/jaegerremote v0.11.0/go.mod h1:U+s0mJMfMC2gicc4WEgZ50JSR+5DhOIjcvFOCVAe8/U= -go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= -go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= -go.opentelemetry.io/otel/exporters/jaeger v1.16.0 h1:YhxxmXZ011C0aDZKoNw+juVWAmEfv/0W2XBOv9aHTaA= -go.opentelemetry.io/otel/exporters/jaeger v1.16.0/go.mod h1:grYbBo/5afWlPpdPZYhyn78Bk04hnvxn2+hvxQhKIQM= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 h1:cbsD4cUcviQGXdw8+bo5x2wazq10SKz8hEbtCRPcU78= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0/go.mod h1:JgXSGah17croqhJfhByOLVY719k1emAXC8MVhCIJlRs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 h1:iqjq9LAB8aK++sKVcELezzn655JnBNdsDhghU4G/So8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0/go.mod h1:hGXzO5bhhSHZnKvrDaXB82Y9DRFour0Nz/KrBh7reWw= -go.opentelemetry.io/otel/exporters/zipkin v1.16.0 h1:WdMSH6vIJ+myJfr/HB/pjsYoJWQP0Wz/iJ1haNO5hX4= -go.opentelemetry.io/otel/exporters/zipkin v1.16.0/go.mod h1:QjDOKdylighHJBc7pf4Vo6fdhtiEJEqww/3Df8TOWjo= -go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= -go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= -go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= -go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= -go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= -go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 h1:gbhw/u49SS3gkPWiYweQNJGm/uJN5GkI/FrosxSHT7A= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1/go.mod h1:GnOaBaFQ2we3b9AGWJpsBa7v1S5RlQzlC3O7dRMxZhM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/contrib/propagators/b3 v1.21.0 h1:uGdgDPNzwQWRwCXJgw/7h29JaRqcq9B87Iv4hJDKAZw= +go.opentelemetry.io/contrib/propagators/b3 v1.21.0/go.mod h1:D9GQXvVGT2pzyTfp1QBOnD1rzKEWzKjjwu5q2mslCUI= +go.opentelemetry.io/contrib/propagators/jaeger v1.21.1 h1:f4beMGDKiVzg9IcX7/VuWVy+oGdjx3dNJ72YehmtY5k= +go.opentelemetry.io/contrib/propagators/jaeger v1.21.1/go.mod h1:U9jhkEl8d1LL+QXY7q3kneJWJugiN3kZJV2OWz3hkBY= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.15.1 h1:Qb+5A+JbIjXwO7l4HkRUhgIn4Bzz0GNS2q+qdmSx+0c= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.15.1/go.mod h1:G4vNCm7fRk0kjZ6pGNLo5SpLxAUvOfSrcaegnT8TPck= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= +go.opentelemetry.io/otel/exporters/zipkin v1.21.0 h1:D+Gv6lSfrFBWmQYyxKjDd0Zuld9SRXpIrEsKZvE4DO4= +go.opentelemetry.io/otel/exporters/zipkin v1.21.0/go.mod h1:83oMKR6DzmHisFOW3I+yIMGZUTjxiWaiBI8M8+TU5zE= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -489,8 +489,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -528,8 +528,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -569,8 +569,8 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -580,8 +580,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -596,8 +596,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -651,8 +651,8 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -667,15 +667,16 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY= +golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -735,8 +736,8 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= -golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc= -golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= 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= @@ -768,8 +769,9 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -806,12 +808,12 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf h1:v5Cf4E9+6tawYrs/grq1q1hFpGtzlGFzgWHqwt6NFiU= -google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= -google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf h1:xkVZ5FdZJF4U82Q/JS+DcZA83s/GRVL+QrFMlexk9Yo= -google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf h1:guOdSPaeFgN+jEJwTo1dQ71hdBm+yKSCCKuTRkJzcVo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -828,8 +830,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/integration/authorize_device_grant_request_test.go b/integration/authorize_device_grant_request_test.go new file mode 100644 index 000000000..2f548f147 --- /dev/null +++ b/integration/authorize_device_grant_request_test.go @@ -0,0 +1,141 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package integration_test + +import ( + "context" + "fmt" + "net/url" + "testing" + + "github.com/ory/fosite" + "github.com/ory/fosite/compose" + "github.com/ory/fosite/handler/oauth2" + "github.com/ory/fosite/handler/openid" + "github.com/ory/fosite/internal/gen" + "github.com/ory/fosite/token/jwt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + goauth "golang.org/x/oauth2" +) + +func TestDeviceFlow(t *testing.T) { + for _, strategy := range []oauth2.AccessTokenStrategy{ + hmacStrategy, + } { + runDeviceFlowTest(t, strategy) + } +} + +func runDeviceFlowTest(t *testing.T, strategy interface{}) { + session := &defaultSession{ + DefaultSession: &openid.DefaultSession{ + Claims: &jwt.IDTokenClaims{ + Subject: "peter", + }, + Headers: &jwt.Headers{}, + Subject: "peter", + Username: "peteru", + }, + } + fc := new(fosite.Config) + fc.DeviceVerificationURL = "https://example.com/" + fc.RefreshTokenLifespan = -1 + fc.GlobalSecret = []byte("some-secret-thats-random-some-secret-thats-random-") + f := compose.ComposeAllEnabled(fc, fositeStore, gen.MustRSAKey()) + ts := mockServer(t, f, session) + defer ts.Close() + + oauthClient := &goauth.Config{ + ClientID: "device-client", + ClientSecret: "foobar", + Endpoint: goauth.Endpoint{ + TokenURL: ts.URL + tokenRelativePath, + DeviceAuthURL: ts.URL + deviceAuthRelativePath, + }, + } + for k, c := range []struct { + description string + setup func() + err bool + client fosite.Client + check func(t *testing.T, token *goauth.DeviceAuthResponse, err error) + params url.Values + }{ + { + description: "should fail with invalid_grant", + setup: func() { + fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{"authorization_code"} + }, + err: true, + check: func(t *testing.T, token *goauth.DeviceAuthResponse, err error) { + assert.ErrorContains(t, err, "invalid_grant") + }, + }, + { + description: "should fail with invalid_scope", + setup: func() { + oauthClient.Scopes = []string{"openid"} + fositeStore.Clients["device-client"].(*fosite.DefaultClient).Scopes = []string{"profile"} + }, + err: true, + check: func(t *testing.T, token *goauth.DeviceAuthResponse, err error) { + assert.ErrorContains(t, err, "invalid_scope") + }, + }, + { + description: "should fail with invalid_client", + setup: func() { + oauthClient.ClientID = "123" + }, + err: true, + check: func(t *testing.T, token *goauth.DeviceAuthResponse, err error) { + assert.ErrorContains(t, err, "invalid_client") + }, + }, + { + description: "should pass", + setup: func() {}, + err: false, + }, + } { + t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { + // Restore client + fositeStore.Clients["device-client"] = &fosite.DefaultClient{ + ID: "device-client", + Secret: []byte(`$2a$10$IxMdI6d.LIRZPpSfEwNoeu4rY3FhDREsxFJXikcgdRRAStxUlsuEO`), // = "foobar" + GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}, + Scopes: []string{"fosite", "offline", "openid"}, + Audience: []string{tokenURL}, + Public: true, + } + oauthClient = &goauth.Config{ + ClientID: "device-client", + ClientSecret: "foobar", + Endpoint: goauth.Endpoint{ + TokenURL: ts.URL + tokenRelativePath, + DeviceAuthURL: ts.URL + deviceAuthRelativePath, + }, + } + + c.setup() + + resp, err := oauthClient.DeviceAuth(context.Background()) + require.Equal(t, c.err, err != nil, "(%d) %s\n%s\n%s", k, c.description, c.err, err) + if !c.err { + assert.NotEmpty(t, resp.DeviceCode) + assert.NotEmpty(t, resp.UserCode) + assert.NotEmpty(t, resp.Interval) + assert.NotEmpty(t, resp.VerificationURI) + assert.NotEmpty(t, resp.VerificationURIComplete) + } + + if c.check != nil { + c.check(t, resp, err) + } + + t.Logf("Passed test case %d", k) + }) + } +} diff --git a/integration/helper_endpoints_test.go b/integration/helper_endpoints_test.go index f1c27ec99..91756533c 100644 --- a/integration/helper_endpoints_test.go +++ b/integration/helper_endpoints_test.go @@ -175,3 +175,27 @@ func pushedAuthorizeRequestHandler(t *testing.T, oauth2 fosite.OAuth2Provider, s oauth2.WritePushedAuthorizeResponse(ctx, rw, ar, response) } } + +func deviceAuthorizationEndpointHandler(t *testing.T, oauth2 fosite.OAuth2Provider, session fosite.Session) func(rw http.ResponseWriter, req *http.Request) { + return func(rw http.ResponseWriter, req *http.Request) { + ctx := fosite.NewContext() + + r, err := oauth2.NewDeviceRequest(ctx, req) + if err != nil { + t.Logf("Device auth request failed because: %+v", err) + t.Logf("Request: %+v", r) + oauth2.WriteAccessError(ctx, rw, r, err) + return + } + + response, err := oauth2.NewDeviceResponse(ctx, r, session) + if err != nil { + t.Logf("Device auth response failed because: %+v", err) + t.Logf("Request: %+v", r) + oauth2.WriteAccessError(ctx, rw, r, err) + return + } + + oauth2.WriteDeviceResponse(ctx, rw, r, response) + } +} diff --git a/integration/helper_setup_test.go b/integration/helper_setup_test.go index 493424037..c9c809202 100644 --- a/integration/helper_setup_test.go +++ b/integration/helper_setup_test.go @@ -41,6 +41,8 @@ const ( tokenURL = "https://www.ory.sh/api" tokenRelativePath = "/token" + + deviceAuthRelativePath = "/device/auth" ) var ( @@ -55,7 +57,7 @@ var fositeStore = &storage.MemoryStore{ Secret: []byte(`$2a$10$IxMdI6d.LIRZPpSfEwNoeu4rY3FhDREsxFJXikcgdRRAStxUlsuEO`), // = "foobar" RedirectURIs: []string{"http://localhost:3846/callback"}, ResponseTypes: []string{"id_token", "code", "token", "token code", "id_token code", "token id_token", "token code id_token"}, - GrantTypes: []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"}, + GrantTypes: []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials", "urn:ietf:params:oauth:grant-type:device_code"}, Scopes: []string{"fosite", "offline", "openid"}, Audience: []string{tokenURL}, }, @@ -113,6 +115,10 @@ var fositeStore = &storage.MemoryStore{ AccessTokenRequestIDs: map[string]string{}, RefreshTokenRequestIDs: map[string]string{}, PARSessions: map[string]fosite.AuthorizeRequester{}, + DeviceCodes: map[string]fosite.Requester{}, + UserCodes: map[string]fosite.Requester{}, + DeviceCodesRequestIDs: map[string]string{}, + UserCodesRequestIDs: map[string]string{}, } type defaultSession struct { @@ -204,6 +210,7 @@ func mockServer(t *testing.T, f fosite.OAuth2Provider, session fosite.Session) * router.HandleFunc("/introspect", tokenIntrospectionHandler(t, f, session)) router.HandleFunc("/revoke", tokenRevocationHandler(t, f, session)) router.HandleFunc("/par", pushedAuthorizeRequestHandler(t, f, session)) + router.HandleFunc(deviceAuthRelativePath, deviceAuthorizationEndpointHandler(t, f, session)) ts := httptest.NewServer(router) return ts From e4e1fe84a7a671712f3afbc57b2fc0509f53daa3 Mon Sep 17 00:00:00 2001 From: Nikos Date: Fri, 9 Feb 2024 14:31:09 +0200 Subject: [PATCH 08/17] fix: review comments --- compose/compose_rfc8628.go | 4 +- compose/compose_strategy.go | 1 + config.go | 2 + config_default.go | 15 +- device_request.go | 3 +- device_request_handler.go | 35 +--- device_request_handler_test.go | 157 +++++++++--------- device_request_test.go | 2 +- device_response.go | 59 ++++--- device_response_test.go | 2 +- device_response_writer.go | 3 +- device_write.go | 25 +-- device_write_test.go | 2 +- handler.go | 1 + handler/rfc8628/auth_handler.go | 10 +- handler/rfc8628/auth_handler_test.go | 62 +++---- handler/rfc8628/storage.go | 5 +- handler/rfc8628/strategy.go | 6 +- handler/rfc8628/strategy_hmacsha.go | 22 ++- handler/rfc8628/strategy_hmacsha_test.go | 2 +- .../authorize_device_grant_request_test.go | 4 +- oauth2.go | 13 ++ storage/memory.go | 7 + token/hmac/hmacsha.go | 1 + 24 files changed, 230 insertions(+), 213 deletions(-) diff --git a/compose/compose_rfc8628.go b/compose/compose_rfc8628.go index fb377606b..5217aeb7f 100644 --- a/compose/compose_rfc8628.go +++ b/compose/compose_rfc8628.go @@ -1,6 +1,8 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 +// Package compose provides various objects which can be used to +// instantiate OAuth2Providers with different functionality. package compose import ( diff --git a/compose/compose_strategy.go b/compose/compose_strategy.go index 748d8eab4..1367485a6 100644 --- a/compose/compose_strategy.go +++ b/compose/compose_strategy.go @@ -55,6 +55,7 @@ func NewOpenIDConnectStrategy(keyGetter func(context.Context) (interface{}, erro } } +// Create a new device strategy func NewDeviceStrategy(config fosite.Configurator) *rfc8628.DefaultDeviceStrategy { return &rfc8628.DefaultDeviceStrategy{ Enigma: &hmac.HMACStrategy{Config: config}, diff --git a/config.go b/config.go index d95fa88d9..86802f0e5 100644 --- a/config.go +++ b/config.go @@ -46,6 +46,7 @@ type IDTokenLifespanProvider interface { GetIDTokenLifespan(ctx context.Context) time.Duration } +// DeviceAndUserCodeLifespanProvider returns the provider for configuring the device and user code lifespan type DeviceAndUserCodeLifespanProvider interface { GetDeviceAndUserCodeLifespan(ctx context.Context) time.Duration } @@ -80,6 +81,7 @@ type DisableRefreshTokenValidationProvider interface { GetDisableRefreshTokenValidation(ctx context.Context) bool } +// DeviceProvider returns the provider for configuring the device flow type DeviceProvider interface { GetDeviceVerificationURL(ctx context.Context) string GetDeviceAuthTokenPollingInterval(ctx context.Context) time.Duration diff --git a/config_default.go b/config_default.go index cc6acece3..4d654dbc8 100644 --- a/config_default.go +++ b/config_default.go @@ -18,8 +18,10 @@ import ( ) const ( - defaultPARPrefix = "urn:ietf:params:oauth:request_uri:" - defaultPARContextLifetime = 5 * time.Minute + defaultPARPrefix = "urn:ietf:params:oauth:request_uri:" + defaultPARContextLifetime = 5 * time.Minute + defaultDeviceAndUserCodeLifespan = 10 * time.Minute + defaultAuthTokenPollingInterval = 5 * time.Second ) var ( @@ -257,6 +259,7 @@ func (c *Config) GetTokenIntrospectionHandlers(ctx context.Context) TokenIntrosp return c.TokenIntrospectionHandlers } +// GetDeviceEndpointHandlers return the Device Endpoint Handlers func (c *Config) GetDeviceEndpointHandlers(ctx context.Context) DeviceEndpointHandlers { return c.DeviceEndpointHandlers } @@ -412,9 +415,11 @@ func (c *Config) GetRefreshTokenLifespan(_ context.Context) time.Duration { return c.RefreshTokenLifespan } +// GetDeviceAndUserCodeLifespan returns how long the device and user codes should be valid. +// Defaults to 10 minutes func (c *Config) GetDeviceAndUserCodeLifespan(_ context.Context) time.Duration { if c.DeviceAndUserCodeLifespan == 0 { - return time.Minute * 10 + return defaultDeviceAndUserCodeLifespan } return c.DeviceAndUserCodeLifespan } @@ -523,13 +528,15 @@ func (c *Config) EnforcePushedAuthorize(ctx context.Context) bool { return c.IsPushedAuthorizeEnforced } +// GetDeviceVerificationURL returns the device verification URL func (c *Config) GetDeviceVerificationURL(ctx context.Context) string { return c.DeviceVerificationURL } +// GetDeviceAuthTokenPollingInterval returns configured device token endpoint polling interval func (c *Config) GetDeviceAuthTokenPollingInterval(ctx context.Context) time.Duration { if c.DeviceAuthTokenPollingInterval == 0 { - return time.Second * 5 + return defaultAuthTokenPollingInterval } return c.DeviceAuthTokenPollingInterval } diff --git a/device_request.go b/device_request.go index 3c2df0636..0b243b015 100644 --- a/device_request.go +++ b/device_request.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite @@ -8,6 +8,7 @@ type DeviceRequest struct { Request } +// NewDeviceRequest returns a new device request func NewDeviceRequest() *DeviceRequest { return &DeviceRequest{ Request: *NewRequest(), diff --git a/device_request_handler.go b/device_request_handler.go index 20d3b1e26..30d548257 100644 --- a/device_request_handler.go +++ b/device_request_handler.go @@ -1,27 +1,6 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 -/* - * Copyright © 2015-2021 Aeneas Rekkas - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @author Aeneas Rekkas - * @copyright 2015-2021 Aeneas Rekkas - * @license Apache-2.0 - * - */ - package fosite import ( @@ -29,12 +8,14 @@ import ( "net/http" "strings" - "github.com/ory/fosite/i18n" "github.com/ory/x/errorsx" "github.com/ory/x/otelx" "go.opentelemetry.io/otel/trace" + + "github.com/ory/fosite/i18n" ) +// NewDeviceRequest parses an http Request returns a Device request func (f *Fosite) NewDeviceRequest(ctx context.Context, r *http.Request) (_ DeviceRequester, err error) { ctx, span := trace.SpanFromContext(ctx).TracerProvider().Tracer("github.com/ory/fosite").Start(ctx, "Fosite.NewAccessRequest") defer otelx.End(span, &err) @@ -42,11 +23,13 @@ func (f *Fosite) NewDeviceRequest(ctx context.Context, r *http.Request) (_ Devic request := NewDeviceRequest() request.Lang = i18n.GetLangFromRequest(f.Config.GetMessageCatalog(ctx), r) - if r.Method != "POST" { + if r.Method != http.MethodPost { return request, errorsx.WithStack(ErrInvalidRequest.WithHintf("HTTP method is '%s', expected 'POST'.", r.Method)) - } else if err := r.ParseForm(); err != nil { + } + if err := r.ParseForm(); err != nil { return nil, errorsx.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithWrap(err).WithDebug(err.Error())) - } else if len(r.PostForm) == 0 { + } + if len(r.PostForm) == 0 { return request, errorsx.WithStack(ErrInvalidRequest.WithHint("The POST body can not be empty.")) } request.Form = r.PostForm diff --git a/device_request_handler_test.go b/device_request_handler_test.go index d6dceeffa..0b5b38e6d 100644 --- a/device_request_handler_test.go +++ b/device_request_handler_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test @@ -33,94 +33,87 @@ func TestNewDeviceRequestWithPublicClient(t *testing.T) { expectedError error mock func() expect DeviceRequester - }{ - /* invalid Method */ - { - expectedError: ErrInvalidRequest, - method: "GET", - mock: func() {}, + description string + }{{ + description: "invalid method", + expectedError: ErrInvalidRequest, + method: "GET", + mock: func() {}, + }, { + description: "empty request", + expectedError: ErrInvalidRequest, + method: "POST", + mock: func() {}, + }, { + description: "invalid client", + form: url.Values{ + "client_id": {"client_id"}, + "scope": {"foo bar"}, }, - /* empty request */ - { - expectedError: ErrInvalidRequest, - method: "POST", - mock: func() {}, + expectedError: ErrInvalidClient, + method: "POST", + mock: func() { + store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(nil, errors.New("")) }, - /* invalid client */ - { - form: url.Values{ - "client_id": {"client_id"}, - "scope": {"foo bar"}, - }, - expectedError: ErrInvalidClient, - method: "POST", - mock: func() { - store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(nil, errors.New("")) - }, + }, { + description: "fails because scope not allowed", + form: url.Values{ + "client_id": {"client_id"}, + "scope": {"17 42 foo"}, }, - /* fails because scope not allowed */ - { - form: url.Values{ - "client_id": {"client_id"}, - "scope": {"17 42 foo"}, - }, - method: "POST", - mock: func() { - store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) - client.Public = true - client.Scopes = []string{"17", "42"} - client.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} - }, - expectedError: ErrInvalidScope, + method: "POST", + mock: func() { + store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) + client.Public = true + client.Scopes = []string{"17", "42"} + client.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} }, - /* fails because scope not allowed */ - { - form: url.Values{ - "client_id": {"client_id"}, - "scope": {"17 42"}, - "audience": {"aud"}, - }, - method: "POST", - mock: func() { - store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) - client.Public = true - client.Scopes = []string{"17", "42"} - client.Audience = []string{"aud2"} - client.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} - }, - expectedError: ErrInvalidRequest, + expectedError: ErrInvalidScope, + }, { + description: "fails because audience not allowed", + form: url.Values{ + "client_id": {"client_id"}, + "scope": {"17 42"}, + "audience": {"aud"}, }, - /* should fail because doesn't have proper grant */ - { - form: url.Values{ - "client_id": {"client_id"}, - "scope": {"17 42"}, - }, - method: "POST", - mock: func() { - store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) - client.Public = true - client.Scopes = []string{"17", "42"} - client.GrantTypes = []string{"authorization_code"} - }, - expectedError: ErrInvalidGrant, + method: "POST", + mock: func() { + store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) + client.Public = true + client.Scopes = []string{"17", "42"} + client.Audience = []string{"aud2"} + client.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} }, - /* success case */ - { - form: url.Values{ - "client_id": {"client_id"}, - "scope": {"17 42"}, - }, - method: "POST", - mock: func() { - store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) - client.Public = true - client.Scopes = []string{"17", "42"} - client.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} - }, + expectedError: ErrInvalidRequest, + }, { + description: "fails because it doesn't have the proper grant", + form: url.Values{ + "client_id": {"client_id"}, + "scope": {"17 42"}, }, - } { - t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { + method: "POST", + mock: func() { + store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) + client.Public = true + client.Scopes = []string{"17", "42"} + client.GrantTypes = []string{"authorization_code"} + }, + expectedError: ErrInvalidGrant, + }, { + description: "success", + form: url.Values{ + "client_id": {"client_id"}, + "scope": {"17 42"}, + }, + method: "POST", + mock: func() { + store.EXPECT().GetClient(gomock.Any(), gomock.Eq("client_id")).Return(client, nil) + client.Public = true + client.Scopes = []string{"17", "42"} + client.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} + }, + }} { + t.Run(fmt.Sprintf("case=%d description=%s", k, c.description), func(t *testing.T) { c.mock() r := &http.Request{ Header: c.header, diff --git a/device_request_test.go b/device_request_test.go index 571e83ba8..7e67c0529 100644 --- a/device_request_test.go +++ b/device_request_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/device_response.go b/device_response.go index b9b9d655e..bad3e2064 100644 --- a/device_response.go +++ b/device_response.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite @@ -9,7 +9,8 @@ import ( "net/http" ) -type deviceResponse struct { +// DeviceResponse represents the device authorization response +type DeviceResponse struct { Header http.Header DeviceCode string `json:"device_code"` UserCode string `json:"user_code"` @@ -19,79 +20,87 @@ type deviceResponse struct { Interval int `json:"interval,omitempty"` } -type DeviceResponse struct { - deviceResponse -} - +// NewDeviceResponse returns a new DeviceResponse func NewDeviceResponse() *DeviceResponse { return &DeviceResponse{} } +// GetDeviceCode returns the response's device_code func (d *DeviceResponse) GetDeviceCode() string { - return d.deviceResponse.DeviceCode + return d.DeviceCode } -// SetDeviceCode returns the response's user code +// SetDeviceCode sets the response's device_code func (d *DeviceResponse) SetDeviceCode(code string) { - d.deviceResponse.DeviceCode = code + d.DeviceCode = code } +// GetUserCode returns the response's user_code func (d *DeviceResponse) GetUserCode() string { - return d.deviceResponse.UserCode + return d.UserCode } +// SetUserCode sets the response's user_code func (d *DeviceResponse) SetUserCode(code string) { - d.deviceResponse.UserCode = code + d.UserCode = code } // GetVerificationURI returns the response's verification uri func (d *DeviceResponse) GetVerificationURI() string { - return d.deviceResponse.VerificationURI + return d.VerificationURI } +// SetVerificationURI sets the response's verification uri func (d *DeviceResponse) SetVerificationURI(uri string) { - d.deviceResponse.VerificationURI = uri + d.VerificationURI = uri } // GetVerificationURIComplete returns the response's complete verification uri if set func (d *DeviceResponse) GetVerificationURIComplete() string { - return d.deviceResponse.VerificationURIComplete + return d.VerificationURIComplete } +// SetVerificationURIComplete sets the response's complete verification uri func (d *DeviceResponse) SetVerificationURIComplete(uri string) { - d.deviceResponse.VerificationURIComplete = uri + d.VerificationURIComplete = uri } // GetExpiresIn returns the response's device code and user code lifetime in seconds if set func (d *DeviceResponse) GetExpiresIn() int64 { - return d.deviceResponse.ExpiresIn + return d.ExpiresIn } +// SetExpiresIn sets the response's device code and user code lifetime in seconds func (d *DeviceResponse) SetExpiresIn(seconds int64) { - d.deviceResponse.ExpiresIn = seconds + d.ExpiresIn = seconds } // GetInterval returns the response's polling interval if set func (d *DeviceResponse) GetInterval() int { - return d.deviceResponse.Interval + return d.Interval } +// SetInterval sets the response's polling interval func (d *DeviceResponse) SetInterval(seconds int) { - d.deviceResponse.Interval = seconds + d.Interval = seconds } -func (a *DeviceResponse) GetHeader() http.Header { - return a.deviceResponse.Header +// GetHeader returns the response's headers +func (d *DeviceResponse) GetHeader() http.Header { + return d.Header } -func (a *DeviceResponse) AddHeader(key, value string) { - a.deviceResponse.Header.Add(key, value) +// AddHeader adds a header to the response +func (d *DeviceResponse) AddHeader(key, value string) { + d.Header.Add(key, value) } +// FromJson populates a response's fields from a json func (d *DeviceResponse) FromJson(r io.Reader) error { - return json.NewDecoder(r).Decode(&d.deviceResponse) + return json.NewDecoder(r).Decode(&d) } +// ToJson writes a response as a json func (d *DeviceResponse) ToJson(rw io.Writer) error { - return json.NewEncoder(rw).Encode(&d.deviceResponse) + return json.NewEncoder(rw).Encode(&d) } diff --git a/device_response_test.go b/device_response_test.go index 366899a1b..a4e95e168 100644 --- a/device_response_test.go +++ b/device_response_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/device_response_writer.go b/device_response_writer.go index 82c5d7c93..2cc17d096 100644 --- a/device_response_writer.go +++ b/device_response_writer.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite @@ -7,6 +7,7 @@ import ( "context" ) +// NewDeviceResponse returns a new DeviceResponder func (f *Fosite) NewDeviceResponse(ctx context.Context, r DeviceRequester, session Session) (DeviceResponder, error) { var resp = &DeviceResponse{} diff --git a/device_write.go b/device_write.go index 0e8fa77b5..9240140f9 100644 --- a/device_write.go +++ b/device_write.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite @@ -8,8 +8,7 @@ import ( "net/http" ) -// TODO: Do documentation - +// WriteDeviceResponse writes the device response func (f *Fosite) WriteDeviceResponse(ctx context.Context, rw http.ResponseWriter, requester DeviceRequester, responder DeviceResponder) { // Set custom headers, e.g. "X-MySuperCoolCustomHeader" or "X-DONT-CACHE-ME"... wh := rw.Header() @@ -23,15 +22,17 @@ func (f *Fosite) WriteDeviceResponse(ctx context.Context, rw http.ResponseWriter rw.Header().Set("Pragma", "no-cache") deviceResponse := &DeviceResponse{ - deviceResponse{ - DeviceCode: responder.GetDeviceCode(), - UserCode: responder.GetUserCode(), - VerificationURI: responder.GetVerificationURI(), - VerificationURIComplete: responder.GetVerificationURIComplete(), - ExpiresIn: responder.GetExpiresIn(), - Interval: responder.GetInterval(), - }, + DeviceCode: responder.GetDeviceCode(), + UserCode: responder.GetUserCode(), + VerificationURI: responder.GetVerificationURI(), + VerificationURIComplete: responder.GetVerificationURIComplete(), + ExpiresIn: responder.GetExpiresIn(), + Interval: responder.GetInterval(), } - _ = deviceResponse.ToJson(rw) + err := deviceResponse.ToJson(rw) + if err != nil { + http.Error(rw, ErrServerError.WithWrap(err).WithDebug(err.Error()).Error(), http.StatusInternalServerError) + return + } } diff --git a/device_write_test.go b/device_write_test.go index 0ed418cb7..7ec9b5d43 100644 --- a/device_write_test.go +++ b/device_write_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/handler.go b/handler.go index d95b0f367..6f11626f0 100644 --- a/handler.go +++ b/handler.go @@ -67,6 +67,7 @@ type PushedAuthorizeEndpointHandler interface { HandlePushedAuthorizeEndpointRequest(ctx context.Context, requester AuthorizeRequester, responder PushedAuthorizeResponder) error } +// DeviceEndpointHandler is the interface that handles https://tools.ietf.org/html/rfc8628 type DeviceEndpointHandler interface { // HandleDeviceEndpointRequest handles a device authorize endpoint request. To extend the handler's capabilities, the http request // is passed along, if further information retrieval is required. If the handler feels that he is not responsible for diff --git a/handler/rfc8628/auth_handler.go b/handler/rfc8628/auth_handler.go index 9072cbe46..6d97da3aa 100644 --- a/handler/rfc8628/auth_handler.go +++ b/handler/rfc8628/auth_handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package rfc8628 @@ -7,10 +7,13 @@ import ( "context" "time" - "github.com/ory/fosite" "github.com/ory/x/errorsx" + + "github.com/ory/fosite" ) +// DeviceAuthHandler is a response handler for the Device Authorisation Grant as +// defined in https://tools.ietf.org/html/rfc8628#section-3.1 type DeviceAuthHandler struct { Storage RFC8628CoreStorage Strategy RFC8628CodeStrategy @@ -20,8 +23,7 @@ type DeviceAuthHandler struct { } } -// DeviceAuthorizationHandler is a response handler for the Device Authorisation Grant as -// defined in https://tools.ietf.org/html/rfc8628#section-3.1 +// HandleDeviceEndpointRequest implements https://tools.ietf.org/html/rfc8628#section-3.1 func (d *DeviceAuthHandler) HandleDeviceEndpointRequest(ctx context.Context, dar fosite.DeviceRequester, resp fosite.DeviceResponder) error { deviceCode, deviceCodeSignature, err := d.Strategy.GenerateDeviceCode(ctx) if err != nil { diff --git a/handler/rfc8628/auth_handler_test.go b/handler/rfc8628/auth_handler_test.go index c29112bf6..220a03475 100644 --- a/handler/rfc8628/auth_handler_test.go +++ b/handler/rfc8628/auth_handler_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package rfc8628_test @@ -9,18 +9,19 @@ import ( "time" "github.com/golang/mock/gomock" - "github.com/ory/fosite" - . "github.com/ory/fosite/handler/rfc8628" "github.com/ory/fosite/storage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/ory/fosite" + "github.com/ory/fosite/handler/rfc8628" ) func Test_HandleDeviceEndpointRequest(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() store := storage.NewMemoryStore() - handler := DeviceAuthHandler{ + handler := rfc8628.DeviceAuthHandler{ Storage: store, Strategy: &hmacshaStrategy, Config: &fosite.Config{ @@ -35,45 +36,22 @@ func Test_HandleDeviceEndpointRequest(t *testing.T) { }, } - for _, c := range []struct { - handler DeviceAuthHandler - req *fosite.DeviceRequest - description string - expectErr error - expect func(t *testing.T, req *fosite.DeviceRequest, resp *fosite.DeviceResponse) - }{ - { - handler: handler, - req: &fosite.DeviceRequest{ - Request: fosite.Request{ - Client: &fosite.DefaultClient{ - Audience: []string{"https://www.ory.sh/api"}, - }, - Session: &fosite.DefaultSession{}, - }, - }, - expect: func(t *testing.T, req *fosite.DeviceRequest, resp *fosite.DeviceResponse) { - assert.NotEmpty(t, resp.GetDeviceCode()) - assert.NotEmpty(t, resp.GetUserCode()) - assert.Equal(t, len(resp.GetUserCode()), 8) - assert.Contains(t, resp.GetDeviceCode(), "ory_dc_") - assert.Contains(t, resp.GetDeviceCode(), ".") - assert.Equal(t, resp.GetVerificationURI(), "www.test.com") + req := &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ + Audience: []string{"https://www.ory.sh/api"}, }, + Session: &fosite.DefaultSession{}, }, - } { - t.Run("case="+c.description, func(t *testing.T) { - resp := fosite.NewDeviceResponse() - err := c.handler.HandleDeviceEndpointRequest(context.Background(), c.req, resp) - if c.expectErr != nil { - require.EqualError(t, err, c.expectErr.Error()) - } else { - require.NoError(t, err) - } - - if c.expect != nil { - c.expect(t, c.req, resp) - } - }) } + resp := fosite.NewDeviceResponse() + err := handler.HandleDeviceEndpointRequest(context.Background(), req, resp) + + require.NoError(t, err) + assert.NotEmpty(t, resp.GetDeviceCode()) + assert.NotEmpty(t, resp.GetUserCode()) + assert.Equal(t, len(resp.GetUserCode()), 8) + assert.Contains(t, resp.GetDeviceCode(), "ory_dc_") + assert.Contains(t, resp.GetDeviceCode(), ".") + assert.Equal(t, resp.GetVerificationURI(), "www.test.com") } diff --git a/handler/rfc8628/storage.go b/handler/rfc8628/storage.go index f356c6aaa..17571ab18 100644 --- a/handler/rfc8628/storage.go +++ b/handler/rfc8628/storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package rfc8628 @@ -10,6 +10,7 @@ import ( "github.com/ory/fosite/handler/oauth2" ) +// RFC8628CoreStorage is the storage needed for the DeviceAuthHandler type RFC8628CoreStorage interface { DeviceCodeStorage UserCodeStorage @@ -17,6 +18,7 @@ type RFC8628CoreStorage interface { oauth2.RefreshTokenStorage } +// DeviceCodeStorage handles the device_code storage type DeviceCodeStorage interface { // CreateDeviceCodeSession stores the device request for a given device code. CreateDeviceCodeSession(ctx context.Context, signature string, request fosite.Requester) (err error) @@ -34,6 +36,7 @@ type DeviceCodeStorage interface { InvalidateDeviceCodeSession(ctx context.Context, signature string) (err error) } +// UserCodeStorage handles the user_code storage type UserCodeStorage interface { // CreateUserCodeSession stores the device request for a given user code. CreateUserCodeSession(ctx context.Context, signature string, request fosite.Requester) (err error) diff --git a/handler/rfc8628/strategy.go b/handler/rfc8628/strategy.go index 3b0df7a71..33900a20c 100644 --- a/handler/rfc8628/strategy.go +++ b/handler/rfc8628/strategy.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package rfc8628 @@ -9,22 +9,26 @@ import ( "github.com/ory/fosite" ) +// RFC8628CodeStrategy is the code strategy needed for the DeviceAuthHandler type RFC8628CodeStrategy interface { DeviceRateLimitStrategy DeviceCodeStrategy UserCodeStrategy } +// DeviceRateLimitStrategy handles the rate limiting strategy type DeviceRateLimitStrategy interface { ShouldRateLimit(ctx context.Context, code string) bool } +// DeviceCodeStrategy handles the device_code strategy type DeviceCodeStrategy interface { DeviceCodeSignature(ctx context.Context, code string) (signature string, err error) GenerateDeviceCode(ctx context.Context) (code string, signature string, err error) ValidateDeviceCode(ctx context.Context, r fosite.Requester, code string) (err error) } +// UserCodeStrategy handles the user_code strategy type UserCodeStrategy interface { UserCodeSignature(ctx context.Context, code string) (signature string, err error) GenerateUserCode(ctx context.Context) (code string, signature string, err error) diff --git a/handler/rfc8628/strategy_hmacsha.go b/handler/rfc8628/strategy_hmacsha.go index 3e600a164..6f8068c1e 100644 --- a/handler/rfc8628/strategy_hmacsha.go +++ b/handler/rfc8628/strategy_hmacsha.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package rfc8628 @@ -14,6 +14,7 @@ import ( enigma "github.com/ory/fosite/token/hmac" ) +// DefaultDeviceStrategy implements the default device strategy type DefaultDeviceStrategy struct { Enigma *enigma.HMACStrategy RateLimiterCache *cache.Cache @@ -25,7 +26,8 @@ type DefaultDeviceStrategy struct { var _ RFC8628CodeStrategy = (*DefaultDeviceStrategy)(nil) -func (h *DefaultDeviceStrategy) GenerateUserCode(ctx context.Context) (token string, signature string, err error) { +// GenerateUserCode generates a user_code +func (h *DefaultDeviceStrategy) GenerateUserCode(ctx context.Context) (string, string, error) { seq, err := randx.RuneSequence(8, []rune(randx.AlphaUpper)) if err != nil { return "", "", err @@ -38,16 +40,19 @@ func (h *DefaultDeviceStrategy) GenerateUserCode(ctx context.Context) (token str return userCode, signUserCode, nil } -func (h *DefaultDeviceStrategy) UserCodeSignature(ctx context.Context, token string) (signature string, err error) { +// UserCodeSignature generates a user_code signature +func (h *DefaultDeviceStrategy) UserCodeSignature(ctx context.Context, token string) (string, error) { return h.Enigma.GenerateHMACForString(ctx, token) } -func (h *DefaultDeviceStrategy) ValidateUserCode(ctx context.Context, r fosite.Requester, code string) (err error) { +// ValidateUserCode validates a user_code +func (h *DefaultDeviceStrategy) ValidateUserCode(ctx context.Context, r fosite.Requester, code string) error { // TODO return nil } -func (h *DefaultDeviceStrategy) GenerateDeviceCode(ctx context.Context) (token string, signature string, err error) { +// GenerateDeviceCode generates a device_code +func (h *DefaultDeviceStrategy) GenerateDeviceCode(ctx context.Context) (string, string, error) { token, sig, err := h.Enigma.Generate(ctx) if err != nil { return "", "", err @@ -56,15 +61,18 @@ func (h *DefaultDeviceStrategy) GenerateDeviceCode(ctx context.Context) (token s return "ory_dc_" + token, sig, nil } -func (h *DefaultDeviceStrategy) DeviceCodeSignature(ctx context.Context, token string) (signature string, err error) { +// DeviceCodeSignature generates a device_code signature +func (h *DefaultDeviceStrategy) DeviceCodeSignature(ctx context.Context, token string) (string, error) { return h.Enigma.Signature(token), nil } -func (h *DefaultDeviceStrategy) ValidateDeviceCode(ctx context.Context, r fosite.Requester, code string) (err error) { +// ValidateDeviceCode validates a device_code +func (h *DefaultDeviceStrategy) ValidateDeviceCode(ctx context.Context, r fosite.Requester, code string) error { // TODO return nil } +// ShouldRateLimit is used to decide whether a request should be rate-limites func (t *DefaultDeviceStrategy) ShouldRateLimit(context context.Context, code string) bool { key := code + "_limiter" diff --git a/handler/rfc8628/strategy_hmacsha_test.go b/handler/rfc8628/strategy_hmacsha_test.go index 894fc2ae3..70b687c0c 100644 --- a/handler/rfc8628/strategy_hmacsha_test.go +++ b/handler/rfc8628/strategy_hmacsha_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package rfc8628_test diff --git a/integration/authorize_device_grant_request_test.go b/integration/authorize_device_grant_request_test.go index 2f548f147..2deb30678 100644 --- a/integration/authorize_device_grant_request_test.go +++ b/integration/authorize_device_grant_request_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test @@ -100,7 +100,7 @@ func runDeviceFlowTest(t *testing.T, strategy interface{}) { err: false, }, } { - t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { + t.Run(fmt.Sprintf("case=%d description=%s", k, c.description), func(t *testing.T) { // Restore client fositeStore.Clients["device-client"] = &fosite.DefaultClient{ ID: "device-client", diff --git a/oauth2.go b/oauth2.go index b1d737441..cad5a9550 100644 --- a/oauth2.go +++ b/oauth2.go @@ -402,23 +402,36 @@ type G11NContext interface { GetLang() language.Tag } +// DeviceResponder is the device authorization endpoint's response type DeviceResponder interface { + // GetDeviceCode returns the device_code GetDeviceCode() string + // SetDeviceCode sets the device_code SetDeviceCode(code string) + // GetUserCode returns the user_code GetUserCode() string + // SetUserCode sets the user_code SetUserCode(code string) + // GetVerificationURI returns the verification_uri GetVerificationURI() string + // SetVerificationURI sets the verification_uri SetVerificationURI(uri string) + // GetVerificationURIComplete returns the verification_uri_complete GetVerificationURIComplete() string + // SetVerificationURIComplete sets the verification_uri_complete SetVerificationURIComplete(uri string) + // GetExpiresIn returns the expires_in GetExpiresIn() int64 + // SetExpiresIn sets the expires_in SetExpiresIn(seconds int64) + // GetInterval returns the interval GetInterval() int + // SetInterval sets the interval SetInterval(seconds int) // GetHeader returns the response's header diff --git a/storage/memory.go b/storage/memory.go index b4f88aa8a..becddd515 100644 --- a/storage/memory.go +++ b/storage/memory.go @@ -514,6 +514,7 @@ func (s *MemoryStore) DeletePARSession(ctx context.Context, requestURI string) ( return nil } +// CreateDeviceCodeSession stores the device code session func (s *MemoryStore) CreateDeviceCodeSession(_ context.Context, signature string, req fosite.Requester) error { // We first lock accessTokenRequestIDsMutex and then accessTokensMutex because this is the same order // locking happens in RevokeAccessToken and using the same order prevents deadlocks. @@ -527,6 +528,7 @@ func (s *MemoryStore) CreateDeviceCodeSession(_ context.Context, signature strin return nil } +// UpdateDeviceCodeSession updates the device code session func (s *MemoryStore) UpdateDeviceCodeSession(_ context.Context, signature string, req fosite.Requester) error { s.deviceCodesRequestIDsMutex.Lock() defer s.deviceCodesRequestIDsMutex.Unlock() @@ -541,6 +543,7 @@ func (s *MemoryStore) UpdateDeviceCodeSession(_ context.Context, signature strin return nil } +// GetDeviceCodeSession gets the device code session func (s *MemoryStore) GetDeviceCodeSession(_ context.Context, signature string, _ fosite.Session) (fosite.Requester, error) { s.deviceCodesMutex.RLock() defer s.deviceCodesMutex.RUnlock() @@ -552,6 +555,7 @@ func (s *MemoryStore) GetDeviceCodeSession(_ context.Context, signature string, return rel, nil } +// InvalidateDeviceCodeSession invalidates the device code session func (s *MemoryStore) InvalidateDeviceCodeSession(_ context.Context, code string) error { s.deviceCodesRequestIDsMutex.Lock() defer s.deviceCodesRequestIDsMutex.Unlock() @@ -562,6 +566,7 @@ func (s *MemoryStore) InvalidateDeviceCodeSession(_ context.Context, code string return nil } +// CreateUserCodeSession stores the user code session func (s *MemoryStore) CreateUserCodeSession(_ context.Context, signature string, req fosite.Requester) error { s.userCodesRequestIDsMutex.Lock() defer s.userCodesRequestIDsMutex.Unlock() @@ -573,6 +578,7 @@ func (s *MemoryStore) CreateUserCodeSession(_ context.Context, signature string, return nil } +// GetUserCodeSession gets the user code session func (s *MemoryStore) GetUserCodeSession(_ context.Context, signature string, _ fosite.Session) (fosite.Requester, error) { s.userCodesMutex.RLock() defer s.userCodesMutex.RUnlock() @@ -584,6 +590,7 @@ func (s *MemoryStore) GetUserCodeSession(_ context.Context, signature string, _ return rel, nil } +// GetUserCodeSession invalidates the user code session func (s *MemoryStore) InvalidateUserCodeSession(_ context.Context, code string) error { s.userCodesRequestIDsMutex.Lock() defer s.userCodesRequestIDsMutex.Unlock() diff --git a/token/hmac/hmacsha.go b/token/hmac/hmacsha.go index 86875f6b1..cf7507ae9 100644 --- a/token/hmac/hmacsha.go +++ b/token/hmac/hmacsha.go @@ -170,6 +170,7 @@ func (c *HMACStrategy) Signature(token string) string { return split[1] } +// GenerateHMACForString returns an HMAC for a string func (c *HMACStrategy) GenerateHMACForString(ctx context.Context, text string) (string, error) { var signingKey [32]byte From 7b767271416b15ceba402aa659132ac4d1a6b9c8 Mon Sep 17 00:00:00 2001 From: Nikos Date: Mon, 12 Feb 2024 17:50:22 +0200 Subject: [PATCH 09/17] fix: update license --- access_error.go | 2 +- access_error_test.go | 2 +- access_request.go | 2 +- access_request_handler.go | 2 +- access_request_handler_test.go | 2 +- access_request_test.go | 2 +- access_response.go | 2 +- access_response_test.go | 2 +- access_response_writer.go | 2 +- access_response_writer_test.go | 2 +- access_write.go | 2 +- access_write_test.go | 2 +- arguments.go | 2 +- arguments_test.go | 2 +- audience_strategy.go | 2 +- audience_strategy_test.go | 2 +- authorize_error.go | 2 +- authorize_error_test.go | 2 +- authorize_helper.go | 2 +- authorize_helper_test.go | 2 +- authorize_helper_whitebox_test.go | 2 +- authorize_request.go | 2 +- authorize_request_handler.go | 2 +- authorize_request_handler_oidc_request_test.go | 2 +- authorize_request_handler_test.go | 2 +- authorize_request_test.go | 2 +- authorize_response.go | 2 +- authorize_response_test.go | 2 +- authorize_response_writer.go | 2 +- authorize_response_writer_test.go | 2 +- authorize_validators_test.go | 2 +- authorize_write.go | 2 +- authorize_write_test.go | 2 +- client.go | 2 +- client_authentication.go | 2 +- client_authentication_jwks_strategy.go | 2 +- client_authentication_jwks_strategy_test.go | 2 +- client_authentication_test.go | 2 +- client_manager.go | 2 +- client_test.go | 2 +- client_with_custom_token_lifespans.go | 2 +- client_with_custom_token_lifespans_test.go | 2 +- compose/compose.go | 2 +- compose/compose_oauth2.go | 2 +- compose/compose_openid.go | 2 +- compose/compose_par.go | 2 +- compose/compose_pkce.go | 2 +- compose/compose_rfc7523.go | 2 +- compose/compose_strategy.go | 2 +- compose/compose_userinfo_vc.go | 2 +- config.go | 2 +- config_default.go | 2 +- context.go | 2 +- equalKeys_test.go | 2 +- errors.go | 2 +- errors_test.go | 2 +- fosite.go | 2 +- fosite_test.go | 2 +- generate.go | 2 +- go_mod_indirect_pins.go | 2 +- handler.go | 2 +- handler/oauth2/flow_authorize_code_auth.go | 2 +- handler/oauth2/flow_authorize_code_auth_test.go | 2 +- handler/oauth2/flow_authorize_code_token.go | 2 +- handler/oauth2/flow_authorize_code_token_test.go | 2 +- handler/oauth2/flow_authorize_implicit.go | 2 +- handler/oauth2/flow_authorize_implicit_test.go | 2 +- handler/oauth2/flow_client_credentials.go | 2 +- handler/oauth2/flow_client_credentials_storage.go | 2 +- handler/oauth2/flow_client_credentials_test.go | 2 +- handler/oauth2/flow_refresh.go | 2 +- handler/oauth2/flow_refresh_test.go | 2 +- handler/oauth2/flow_resource_owner.go | 2 +- handler/oauth2/flow_resource_owner_storage.go | 2 +- handler/oauth2/flow_resource_owner_test.go | 2 +- handler/oauth2/helper.go | 2 +- handler/oauth2/helper_test.go | 2 +- handler/oauth2/introspector.go | 2 +- handler/oauth2/introspector_jwt.go | 2 +- handler/oauth2/introspector_jwt_test.go | 2 +- handler/oauth2/introspector_test.go | 2 +- handler/oauth2/revocation.go | 2 +- handler/oauth2/revocation_storage.go | 2 +- handler/oauth2/revocation_test.go | 2 +- handler/oauth2/storage.go | 2 +- handler/oauth2/strategy.go | 2 +- handler/oauth2/strategy_hmacsha.go | 2 +- handler/oauth2/strategy_hmacsha_test.go | 2 +- handler/oauth2/strategy_jwt.go | 2 +- handler/oauth2/strategy_jwt_session.go | 2 +- handler/oauth2/strategy_jwt_test.go | 2 +- handler/openid/errors.go | 2 +- handler/openid/flow_explicit_auth.go | 2 +- handler/openid/flow_explicit_auth_test.go | 2 +- handler/openid/flow_explicit_token.go | 2 +- handler/openid/flow_explicit_token_test.go | 2 +- handler/openid/flow_hybrid.go | 2 +- handler/openid/flow_hybrid_test.go | 2 +- handler/openid/flow_implicit.go | 2 +- handler/openid/flow_implicit_test.go | 2 +- handler/openid/flow_refresh_token.go | 2 +- handler/openid/flow_refresh_token_test.go | 2 +- handler/openid/helper.go | 2 +- handler/openid/helper_test.go | 2 +- handler/openid/storage.go | 2 +- handler/openid/strategy.go | 2 +- handler/openid/strategy_jwt.go | 2 +- handler/openid/strategy_jwt_test.go | 2 +- handler/openid/validator.go | 2 +- handler/openid/validator_test.go | 2 +- handler/par/flow_pushed_authorize.go | 2 +- handler/par/flow_pushed_authorize_test.go | 2 +- handler/pkce/handler.go | 2 +- handler/pkce/handler_test.go | 2 +- handler/pkce/storage.go | 2 +- handler/rfc7523/handler.go | 2 +- handler/rfc7523/handler_test.go | 2 +- handler/rfc7523/session.go | 2 +- handler/rfc7523/storage.go | 2 +- handler/verifiable/handler.go | 2 +- handler/verifiable/handler_test.go | 2 +- handler/verifiable/nonce.go | 2 +- hash.go | 2 +- hash_bcrypt.go | 2 +- hash_bcrypt_test.go | 2 +- helper.go | 2 +- helper_test.go | 2 +- i18n/default_catalog.go | 2 +- i18n/i18n.go | 2 +- i18n/i18n_test.go | 2 +- i18n_helper.go | 2 +- i18n_helper_test.go | 2 +- integration/authorize_code_grant_public_client_pkce_test.go | 2 +- integration/authorize_code_grant_public_client_test.go | 2 +- integration/authorize_code_grant_test.go | 2 +- integration/authorize_form_post_test.go | 2 +- integration/authorize_implicit_grant_test.go | 2 +- integration/authorize_jwt_bearer_required_iat_test.go | 2 +- integration/authorize_jwt_bearer_required_jti_test.go | 2 +- integration/authorize_jwt_bearer_test.go | 2 +- integration/authorize_response_mode_test.go | 2 +- integration/client_credentials_grant_test.go | 2 +- integration/clients/error.go | 2 +- integration/clients/introspect.go | 2 +- integration/clients/jwt_bearer.go | 2 +- integration/helper_endpoints_test.go | 2 +- integration/helper_setup_test.go | 2 +- integration/introspect_jwt_bearer_token_test.go | 2 +- integration/introspect_token_test.go | 2 +- integration/oidc_explicit_test.go | 2 +- integration/oidc_implicit_hybrid_public_client_pkce_test.go | 2 +- integration/oidc_implicit_hybrid_test.go | 2 +- integration/placeholder.go | 2 +- integration/pushed_authorize_code_grant_test.go | 2 +- integration/refresh_token_grant_test.go | 2 +- integration/resource_owner_password_credentials_grant_test.go | 2 +- integration/revoke_token_test.go | 2 +- internal/access_request.go | 2 +- internal/access_response.go | 2 +- internal/access_token_storage.go | 2 +- internal/access_token_strategy.go | 2 +- internal/authorize_code_storage.go | 2 +- internal/authorize_code_strategy.go | 2 +- internal/authorize_handler.go | 2 +- internal/authorize_request.go | 2 +- internal/authorize_response.go | 2 +- internal/client.go | 2 +- internal/gen/key.go | 2 +- internal/hash.go | 2 +- internal/id_token_strategy.go | 2 +- internal/introspector.go | 2 +- internal/oauth2_auth_jwt_storage.go | 2 +- internal/oauth2_client_storage.go | 2 +- internal/oauth2_explicit_storage.go | 2 +- internal/oauth2_owner_storage.go | 2 +- internal/oauth2_refresh_storage.go | 2 +- internal/oauth2_revoke_storage.go | 2 +- internal/oauth2_storage.go | 2 +- internal/oauth2_strategy.go | 2 +- internal/openid_id_token_storage.go | 2 +- internal/pkce_storage_strategy.go | 2 +- internal/pushed_authorize_handler.go | 2 +- internal/refresh_token_strategy.go | 2 +- internal/request.go | 2 +- internal/revoke_handler.go | 2 +- internal/rw.go | 2 +- internal/storage.go | 2 +- internal/test_helpers.go | 2 +- internal/token_handler.go | 2 +- internal/transactional.go | 2 +- introspect.go | 2 +- introspect_test.go | 2 +- introspection_request_handler.go | 2 +- introspection_request_handler_test.go | 2 +- introspection_response_writer.go | 2 +- introspection_response_writer_test.go | 2 +- oauth2.go | 2 +- pushed_authorize_request_handler.go | 2 +- pushed_authorize_request_handler_test.go | 2 +- pushed_authorize_response.go | 2 +- pushed_authorize_response_writer.go | 2 +- pushed_authorize_response_writer_test.go | 2 +- request.go | 2 +- request_test.go | 2 +- response_handler.go | 2 +- revoke_handler.go | 2 +- revoke_handler_test.go | 2 +- scope_strategy.go | 2 +- scope_strategy_test.go | 2 +- session.go | 2 +- session_test.go | 2 +- storage.go | 2 +- storage/memory.go | 2 +- storage/memory_test.go | 2 +- storage/transactional.go | 2 +- token/hmac/bytes.go | 2 +- token/hmac/bytes_test.go | 2 +- token/hmac/hmacsha.go | 2 +- token/hmac/hmacsha_test.go | 2 +- token/jwt/claims.go | 2 +- token/jwt/claims_id_token.go | 2 +- token/jwt/claims_id_token_test.go | 2 +- token/jwt/claims_jwt.go | 2 +- token/jwt/claims_jwt_test.go | 2 +- token/jwt/claims_test.go | 2 +- token/jwt/header.go | 2 +- token/jwt/header_test.go | 2 +- token/jwt/jwt.go | 2 +- token/jwt/jwt_test.go | 2 +- token/jwt/map_claims.go | 2 +- token/jwt/map_claims_test.go | 2 +- token/jwt/token.go | 2 +- token/jwt/token_test.go | 2 +- token/jwt/validation_error.go | 2 +- tools.go | 2 +- 235 files changed, 235 insertions(+), 235 deletions(-) diff --git a/access_error.go b/access_error.go index 8e4a00182..d4c59850e 100644 --- a/access_error.go +++ b/access_error.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/access_error_test.go b/access_error_test.go index 4b6ca2a0c..e1d4ef566 100644 --- a/access_error_test.go +++ b/access_error_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/access_request.go b/access_request.go index c03e74a6d..2ef86c3f1 100644 --- a/access_request.go +++ b/access_request.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/access_request_handler.go b/access_request_handler.go index 2e9b27b35..25dbd62a7 100644 --- a/access_request_handler.go +++ b/access_request_handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/access_request_handler_test.go b/access_request_handler_test.go index 828fc637a..01f5a6920 100644 --- a/access_request_handler_test.go +++ b/access_request_handler_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/access_request_test.go b/access_request_test.go index c275f660d..08d40656c 100644 --- a/access_request_test.go +++ b/access_request_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/access_response.go b/access_response.go index ffcb9e7ba..7e93edafb 100644 --- a/access_response.go +++ b/access_response.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/access_response_test.go b/access_response_test.go index 2379ccc8e..5dec60020 100644 --- a/access_response_test.go +++ b/access_response_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/access_response_writer.go b/access_response_writer.go index 6f28f0e24..e2a8b288a 100644 --- a/access_response_writer.go +++ b/access_response_writer.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/access_response_writer_test.go b/access_response_writer_test.go index 2303d5fb8..39e62ed64 100644 --- a/access_response_writer_test.go +++ b/access_response_writer_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/access_write.go b/access_write.go index e96cfa014..656acddd9 100644 --- a/access_write.go +++ b/access_write.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/access_write_test.go b/access_write_test.go index 111f5e263..a392aba90 100644 --- a/access_write_test.go +++ b/access_write_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/arguments.go b/arguments.go index 65e4bbbea..2fb99a8d7 100644 --- a/arguments.go +++ b/arguments.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/arguments_test.go b/arguments_test.go index 2c2399d51..9466bc95e 100644 --- a/arguments_test.go +++ b/arguments_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/audience_strategy.go b/audience_strategy.go index 5e25d8873..bcef40bbe 100644 --- a/audience_strategy.go +++ b/audience_strategy.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/audience_strategy_test.go b/audience_strategy_test.go index f6f4cc0b2..304b77b5a 100644 --- a/audience_strategy_test.go +++ b/audience_strategy_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/authorize_error.go b/authorize_error.go index 0b5db5f05..0421be6f5 100644 --- a/authorize_error.go +++ b/authorize_error.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/authorize_error_test.go b/authorize_error_test.go index 7f63da701..0db1771fc 100644 --- a/authorize_error_test.go +++ b/authorize_error_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/authorize_helper.go b/authorize_helper.go index 3293175fc..7d2a10b84 100644 --- a/authorize_helper.go +++ b/authorize_helper.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/authorize_helper_test.go b/authorize_helper_test.go index 09c7499d6..fcb799346 100644 --- a/authorize_helper_test.go +++ b/authorize_helper_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/authorize_helper_whitebox_test.go b/authorize_helper_whitebox_test.go index 13a54aa31..39ed6070c 100644 --- a/authorize_helper_whitebox_test.go +++ b/authorize_helper_whitebox_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/authorize_request.go b/authorize_request.go index a07309c27..c4526692c 100644 --- a/authorize_request.go +++ b/authorize_request.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/authorize_request_handler.go b/authorize_request_handler.go index f8df2bf4a..b3e66e393 100644 --- a/authorize_request_handler.go +++ b/authorize_request_handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/authorize_request_handler_oidc_request_test.go b/authorize_request_handler_oidc_request_test.go index 7e8b98839..982bed317 100644 --- a/authorize_request_handler_oidc_request_test.go +++ b/authorize_request_handler_oidc_request_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/authorize_request_handler_test.go b/authorize_request_handler_test.go index afdc341d6..ce835243e 100644 --- a/authorize_request_handler_test.go +++ b/authorize_request_handler_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/authorize_request_test.go b/authorize_request_test.go index aaf9e77c8..89a789be1 100644 --- a/authorize_request_test.go +++ b/authorize_request_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/authorize_response.go b/authorize_response.go index ca427893f..209716aba 100644 --- a/authorize_response.go +++ b/authorize_response.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/authorize_response_test.go b/authorize_response_test.go index f39cada1e..8a92e8ae3 100644 --- a/authorize_response_test.go +++ b/authorize_response_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/authorize_response_writer.go b/authorize_response_writer.go index afa54667b..db53ee632 100644 --- a/authorize_response_writer.go +++ b/authorize_response_writer.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/authorize_response_writer_test.go b/authorize_response_writer_test.go index 0f4b65968..59f892a56 100644 --- a/authorize_response_writer_test.go +++ b/authorize_response_writer_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/authorize_validators_test.go b/authorize_validators_test.go index 44e14b2e7..bd6422e6b 100644 --- a/authorize_validators_test.go +++ b/authorize_validators_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/authorize_write.go b/authorize_write.go index b362938a9..318bfd495 100644 --- a/authorize_write.go +++ b/authorize_write.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/authorize_write_test.go b/authorize_write_test.go index 6e630b0ba..38f01ea83 100644 --- a/authorize_write_test.go +++ b/authorize_write_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/client.go b/client.go index 9b4ce02cb..0bb2e31e7 100644 --- a/client.go +++ b/client.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/client_authentication.go b/client_authentication.go index 70e6fa199..82f3c3872 100644 --- a/client_authentication.go +++ b/client_authentication.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/client_authentication_jwks_strategy.go b/client_authentication_jwks_strategy.go index 091315b11..a1c3a0179 100644 --- a/client_authentication_jwks_strategy.go +++ b/client_authentication_jwks_strategy.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/client_authentication_jwks_strategy_test.go b/client_authentication_jwks_strategy_test.go index 275401f79..f614811d0 100644 --- a/client_authentication_jwks_strategy_test.go +++ b/client_authentication_jwks_strategy_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/client_authentication_test.go b/client_authentication_test.go index c93073ddc..3f08e6e16 100644 --- a/client_authentication_test.go +++ b/client_authentication_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/client_manager.go b/client_manager.go index 1d2828b08..60e3756ca 100644 --- a/client_manager.go +++ b/client_manager.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/client_test.go b/client_test.go index 42eb08f33..bc6ebfe6b 100644 --- a/client_test.go +++ b/client_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/client_with_custom_token_lifespans.go b/client_with_custom_token_lifespans.go index 3337a1d89..be5b0ac63 100644 --- a/client_with_custom_token_lifespans.go +++ b/client_with_custom_token_lifespans.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/client_with_custom_token_lifespans_test.go b/client_with_custom_token_lifespans_test.go index 88fd490b2..60d9018b1 100644 --- a/client_with_custom_token_lifespans_test.go +++ b/client_with_custom_token_lifespans_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/compose/compose.go b/compose/compose.go index 3d80856f9..94c7349ff 100644 --- a/compose/compose.go +++ b/compose/compose.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package compose diff --git a/compose/compose_oauth2.go b/compose/compose_oauth2.go index c71447a5c..c3f01073e 100644 --- a/compose/compose_oauth2.go +++ b/compose/compose_oauth2.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package compose diff --git a/compose/compose_openid.go b/compose/compose_openid.go index 122ddf154..29b1d3a73 100644 --- a/compose/compose_openid.go +++ b/compose/compose_openid.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package compose diff --git a/compose/compose_par.go b/compose/compose_par.go index f935d5b4a..2ed57fac0 100644 --- a/compose/compose_par.go +++ b/compose/compose_par.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package compose diff --git a/compose/compose_pkce.go b/compose/compose_pkce.go index d87a53b6f..d7df6c394 100644 --- a/compose/compose_pkce.go +++ b/compose/compose_pkce.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package compose diff --git a/compose/compose_rfc7523.go b/compose/compose_rfc7523.go index 7aff4127a..e584bad2f 100644 --- a/compose/compose_rfc7523.go +++ b/compose/compose_rfc7523.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package compose diff --git a/compose/compose_strategy.go b/compose/compose_strategy.go index 1367485a6..d9cdc39f3 100644 --- a/compose/compose_strategy.go +++ b/compose/compose_strategy.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package compose diff --git a/compose/compose_userinfo_vc.go b/compose/compose_userinfo_vc.go index da01b869e..079aa8933 100644 --- a/compose/compose_userinfo_vc.go +++ b/compose/compose_userinfo_vc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package compose diff --git a/config.go b/config.go index 86802f0e5..24c4151c4 100644 --- a/config.go +++ b/config.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/config_default.go b/config_default.go index 4d654dbc8..a73154f08 100644 --- a/config_default.go +++ b/config_default.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/context.go b/context.go index d8b2bc3fd..ae01d9b31 100644 --- a/context.go +++ b/context.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/equalKeys_test.go b/equalKeys_test.go index df9f521b6..f7fd7bb63 100644 --- a/equalKeys_test.go +++ b/equalKeys_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/errors.go b/errors.go index bf6c2b42c..0867d9373 100644 --- a/errors.go +++ b/errors.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/errors_test.go b/errors_test.go index e2a46056b..9465636cc 100644 --- a/errors_test.go +++ b/errors_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/fosite.go b/fosite.go index c8fc05b58..d5610129f 100644 --- a/fosite.go +++ b/fosite.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/fosite_test.go b/fosite_test.go index 9b87919f5..2c86b498a 100644 --- a/fosite_test.go +++ b/fosite_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/generate.go b/generate.go index 46dbe0e71..fe382602b 100644 --- a/generate.go +++ b/generate.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/go_mod_indirect_pins.go b/go_mod_indirect_pins.go index aa20f7f34..8ac71d57f 100644 --- a/go_mod_indirect_pins.go +++ b/go_mod_indirect_pins.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 //go:build tools diff --git a/handler.go b/handler.go index 6f11626f0..163f4af6f 100644 --- a/handler.go +++ b/handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/handler/oauth2/flow_authorize_code_auth.go b/handler/oauth2/flow_authorize_code_auth.go index 6bf437c96..0b03c8a78 100644 --- a/handler/oauth2/flow_authorize_code_auth.go +++ b/handler/oauth2/flow_authorize_code_auth.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/flow_authorize_code_auth_test.go b/handler/oauth2/flow_authorize_code_auth_test.go index 3dc73fa19..625ccb497 100644 --- a/handler/oauth2/flow_authorize_code_auth_test.go +++ b/handler/oauth2/flow_authorize_code_auth_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/flow_authorize_code_token.go b/handler/oauth2/flow_authorize_code_token.go index 3289cfcbf..dceb83001 100644 --- a/handler/oauth2/flow_authorize_code_token.go +++ b/handler/oauth2/flow_authorize_code_token.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/flow_authorize_code_token_test.go b/handler/oauth2/flow_authorize_code_token_test.go index 02992171f..bd854fbba 100644 --- a/handler/oauth2/flow_authorize_code_token_test.go +++ b/handler/oauth2/flow_authorize_code_token_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/flow_authorize_implicit.go b/handler/oauth2/flow_authorize_implicit.go index 846d16cb4..c5c48d8e5 100644 --- a/handler/oauth2/flow_authorize_implicit.go +++ b/handler/oauth2/flow_authorize_implicit.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/flow_authorize_implicit_test.go b/handler/oauth2/flow_authorize_implicit_test.go index 3433d133a..6bff9e89e 100644 --- a/handler/oauth2/flow_authorize_implicit_test.go +++ b/handler/oauth2/flow_authorize_implicit_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/flow_client_credentials.go b/handler/oauth2/flow_client_credentials.go index a82d0dc85..957d9b8e1 100644 --- a/handler/oauth2/flow_client_credentials.go +++ b/handler/oauth2/flow_client_credentials.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/flow_client_credentials_storage.go b/handler/oauth2/flow_client_credentials_storage.go index a008d8e7a..a8314b838 100644 --- a/handler/oauth2/flow_client_credentials_storage.go +++ b/handler/oauth2/flow_client_credentials_storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/flow_client_credentials_test.go b/handler/oauth2/flow_client_credentials_test.go index dbb5b27f6..ed16ab180 100644 --- a/handler/oauth2/flow_client_credentials_test.go +++ b/handler/oauth2/flow_client_credentials_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/flow_refresh.go b/handler/oauth2/flow_refresh.go index 02c50493f..de20b59ed 100644 --- a/handler/oauth2/flow_refresh.go +++ b/handler/oauth2/flow_refresh.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/flow_refresh_test.go b/handler/oauth2/flow_refresh_test.go index 36ee8f0fd..986ae91c0 100644 --- a/handler/oauth2/flow_refresh_test.go +++ b/handler/oauth2/flow_refresh_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/flow_resource_owner.go b/handler/oauth2/flow_resource_owner.go index d59e131a0..ad0b45a20 100644 --- a/handler/oauth2/flow_resource_owner.go +++ b/handler/oauth2/flow_resource_owner.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/flow_resource_owner_storage.go b/handler/oauth2/flow_resource_owner_storage.go index 0abef088e..ffc076428 100644 --- a/handler/oauth2/flow_resource_owner_storage.go +++ b/handler/oauth2/flow_resource_owner_storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/flow_resource_owner_test.go b/handler/oauth2/flow_resource_owner_test.go index 25fbce0be..59acb5ab1 100644 --- a/handler/oauth2/flow_resource_owner_test.go +++ b/handler/oauth2/flow_resource_owner_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/helper.go b/handler/oauth2/helper.go index 227b4a6a3..436501a64 100644 --- a/handler/oauth2/helper.go +++ b/handler/oauth2/helper.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/helper_test.go b/handler/oauth2/helper_test.go index 8cd2892ac..8f42aba99 100644 --- a/handler/oauth2/helper_test.go +++ b/handler/oauth2/helper_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/introspector.go b/handler/oauth2/introspector.go index d282c94a6..ebc69ac8b 100644 --- a/handler/oauth2/introspector.go +++ b/handler/oauth2/introspector.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/introspector_jwt.go b/handler/oauth2/introspector_jwt.go index c69cfca97..463842ae1 100644 --- a/handler/oauth2/introspector_jwt.go +++ b/handler/oauth2/introspector_jwt.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/introspector_jwt_test.go b/handler/oauth2/introspector_jwt_test.go index 58cb087e1..77482dc8e 100644 --- a/handler/oauth2/introspector_jwt_test.go +++ b/handler/oauth2/introspector_jwt_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/introspector_test.go b/handler/oauth2/introspector_test.go index 7dfb85a96..f1960d166 100644 --- a/handler/oauth2/introspector_test.go +++ b/handler/oauth2/introspector_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/revocation.go b/handler/oauth2/revocation.go index 42a7a276d..0a3e3fc44 100644 --- a/handler/oauth2/revocation.go +++ b/handler/oauth2/revocation.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/revocation_storage.go b/handler/oauth2/revocation_storage.go index 5454b7c9e..7d923bd56 100644 --- a/handler/oauth2/revocation_storage.go +++ b/handler/oauth2/revocation_storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/revocation_test.go b/handler/oauth2/revocation_test.go index ca6b7d34c..b657bed3c 100644 --- a/handler/oauth2/revocation_test.go +++ b/handler/oauth2/revocation_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/storage.go b/handler/oauth2/storage.go index 1d49ea3de..cc0b95831 100644 --- a/handler/oauth2/storage.go +++ b/handler/oauth2/storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/strategy.go b/handler/oauth2/strategy.go index 7a9b1cd41..f8ae9a289 100644 --- a/handler/oauth2/strategy.go +++ b/handler/oauth2/strategy.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/strategy_hmacsha.go b/handler/oauth2/strategy_hmacsha.go index 4b3ae0cb7..711934596 100644 --- a/handler/oauth2/strategy_hmacsha.go +++ b/handler/oauth2/strategy_hmacsha.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/strategy_hmacsha_test.go b/handler/oauth2/strategy_hmacsha_test.go index 007a8e24d..9b780d28d 100644 --- a/handler/oauth2/strategy_hmacsha_test.go +++ b/handler/oauth2/strategy_hmacsha_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/strategy_jwt.go b/handler/oauth2/strategy_jwt.go index d5cec79b2..73664eadf 100644 --- a/handler/oauth2/strategy_jwt.go +++ b/handler/oauth2/strategy_jwt.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/strategy_jwt_session.go b/handler/oauth2/strategy_jwt_session.go index 3e381f70e..e6c94b30d 100644 --- a/handler/oauth2/strategy_jwt_session.go +++ b/handler/oauth2/strategy_jwt_session.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/oauth2/strategy_jwt_test.go b/handler/oauth2/strategy_jwt_test.go index eb805afb7..cdfb9ce12 100644 --- a/handler/oauth2/strategy_jwt_test.go +++ b/handler/oauth2/strategy_jwt_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oauth2 diff --git a/handler/openid/errors.go b/handler/openid/errors.go index 73a39ad51..e6641b700 100644 --- a/handler/openid/errors.go +++ b/handler/openid/errors.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/flow_explicit_auth.go b/handler/openid/flow_explicit_auth.go index 560af1c7d..071327faf 100644 --- a/handler/openid/flow_explicit_auth.go +++ b/handler/openid/flow_explicit_auth.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/flow_explicit_auth_test.go b/handler/openid/flow_explicit_auth_test.go index bd728f2b7..2373444b7 100644 --- a/handler/openid/flow_explicit_auth_test.go +++ b/handler/openid/flow_explicit_auth_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/flow_explicit_token.go b/handler/openid/flow_explicit_token.go index 0b416d2c2..e7b3266db 100644 --- a/handler/openid/flow_explicit_token.go +++ b/handler/openid/flow_explicit_token.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/flow_explicit_token_test.go b/handler/openid/flow_explicit_token_test.go index 56325e5d9..6cb63d228 100644 --- a/handler/openid/flow_explicit_token_test.go +++ b/handler/openid/flow_explicit_token_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/flow_hybrid.go b/handler/openid/flow_hybrid.go index ce43496e6..925070a18 100644 --- a/handler/openid/flow_hybrid.go +++ b/handler/openid/flow_hybrid.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/flow_hybrid_test.go b/handler/openid/flow_hybrid_test.go index c42b97f19..3412675aa 100644 --- a/handler/openid/flow_hybrid_test.go +++ b/handler/openid/flow_hybrid_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/flow_implicit.go b/handler/openid/flow_implicit.go index 5871621ba..e71113be0 100644 --- a/handler/openid/flow_implicit.go +++ b/handler/openid/flow_implicit.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/flow_implicit_test.go b/handler/openid/flow_implicit_test.go index d67f435f9..baf282c96 100644 --- a/handler/openid/flow_implicit_test.go +++ b/handler/openid/flow_implicit_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/flow_refresh_token.go b/handler/openid/flow_refresh_token.go index de8792f60..73d15b2da 100644 --- a/handler/openid/flow_refresh_token.go +++ b/handler/openid/flow_refresh_token.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/flow_refresh_token_test.go b/handler/openid/flow_refresh_token_test.go index 11d081682..ec4e36bb1 100644 --- a/handler/openid/flow_refresh_token_test.go +++ b/handler/openid/flow_refresh_token_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/helper.go b/handler/openid/helper.go index 8833b439e..e1dc13df6 100644 --- a/handler/openid/helper.go +++ b/handler/openid/helper.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/helper_test.go b/handler/openid/helper_test.go index bab32cbd0..7ca85678a 100644 --- a/handler/openid/helper_test.go +++ b/handler/openid/helper_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/storage.go b/handler/openid/storage.go index d29f73717..dd1b4aa54 100644 --- a/handler/openid/storage.go +++ b/handler/openid/storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/strategy.go b/handler/openid/strategy.go index c61738b80..f5353d0b4 100644 --- a/handler/openid/strategy.go +++ b/handler/openid/strategy.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/strategy_jwt.go b/handler/openid/strategy_jwt.go index 66fe78143..ecd75d8f4 100644 --- a/handler/openid/strategy_jwt.go +++ b/handler/openid/strategy_jwt.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/strategy_jwt_test.go b/handler/openid/strategy_jwt_test.go index 889cb7d5d..fff455194 100644 --- a/handler/openid/strategy_jwt_test.go +++ b/handler/openid/strategy_jwt_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/validator.go b/handler/openid/validator.go index 5208d039c..f25314943 100644 --- a/handler/openid/validator.go +++ b/handler/openid/validator.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/openid/validator_test.go b/handler/openid/validator_test.go index a36d47826..d22bb33ef 100644 --- a/handler/openid/validator_test.go +++ b/handler/openid/validator_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openid diff --git a/handler/par/flow_pushed_authorize.go b/handler/par/flow_pushed_authorize.go index 4abce3837..7d6b532c2 100644 --- a/handler/par/flow_pushed_authorize.go +++ b/handler/par/flow_pushed_authorize.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package par diff --git a/handler/par/flow_pushed_authorize_test.go b/handler/par/flow_pushed_authorize_test.go index cc8c13974..7b69f9515 100644 --- a/handler/par/flow_pushed_authorize_test.go +++ b/handler/par/flow_pushed_authorize_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package par_test diff --git a/handler/pkce/handler.go b/handler/pkce/handler.go index f457b8bea..1d92b8cec 100644 --- a/handler/pkce/handler.go +++ b/handler/pkce/handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package pkce diff --git a/handler/pkce/handler_test.go b/handler/pkce/handler_test.go index 68c42b438..ced8ce8de 100644 --- a/handler/pkce/handler_test.go +++ b/handler/pkce/handler_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package pkce diff --git a/handler/pkce/storage.go b/handler/pkce/storage.go index bc44d053a..2b74bedf8 100644 --- a/handler/pkce/storage.go +++ b/handler/pkce/storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package pkce diff --git a/handler/rfc7523/handler.go b/handler/rfc7523/handler.go index 95be7caff..385549e94 100644 --- a/handler/rfc7523/handler.go +++ b/handler/rfc7523/handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package rfc7523 diff --git a/handler/rfc7523/handler_test.go b/handler/rfc7523/handler_test.go index cd7cc1534..aeb2811ba 100644 --- a/handler/rfc7523/handler_test.go +++ b/handler/rfc7523/handler_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package rfc7523 diff --git a/handler/rfc7523/session.go b/handler/rfc7523/session.go index ea347101e..43aecddb7 100644 --- a/handler/rfc7523/session.go +++ b/handler/rfc7523/session.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package rfc7523 diff --git a/handler/rfc7523/storage.go b/handler/rfc7523/storage.go index 4102729f8..5bf008ed1 100644 --- a/handler/rfc7523/storage.go +++ b/handler/rfc7523/storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package rfc7523 diff --git a/handler/verifiable/handler.go b/handler/verifiable/handler.go index feb3a7077..be7b1a16d 100644 --- a/handler/verifiable/handler.go +++ b/handler/verifiable/handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package verifiable diff --git a/handler/verifiable/handler_test.go b/handler/verifiable/handler_test.go index 4eef438c8..2b5f91b16 100644 --- a/handler/verifiable/handler_test.go +++ b/handler/verifiable/handler_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package verifiable diff --git a/handler/verifiable/nonce.go b/handler/verifiable/nonce.go index 15af54441..0e12c412e 100644 --- a/handler/verifiable/nonce.go +++ b/handler/verifiable/nonce.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package verifiable diff --git a/hash.go b/hash.go index 6422b5e35..39cb4d1f5 100644 --- a/hash.go +++ b/hash.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/hash_bcrypt.go b/hash_bcrypt.go index 44b8fcbf9..6f2675a1c 100644 --- a/hash_bcrypt.go +++ b/hash_bcrypt.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/hash_bcrypt_test.go b/hash_bcrypt_test.go index 55948281a..f7aa04fff 100644 --- a/hash_bcrypt_test.go +++ b/hash_bcrypt_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/helper.go b/helper.go index b1f92d451..6a36c77cb 100644 --- a/helper.go +++ b/helper.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/helper_test.go b/helper_test.go index c004d7b9b..c8afcf7e0 100644 --- a/helper_test.go +++ b/helper_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/i18n/default_catalog.go b/i18n/default_catalog.go index 35132f297..55914192c 100644 --- a/i18n/default_catalog.go +++ b/i18n/default_catalog.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package i18n diff --git a/i18n/i18n.go b/i18n/i18n.go index f4553cedd..ba2515331 100644 --- a/i18n/i18n.go +++ b/i18n/i18n.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package i18n diff --git a/i18n/i18n_test.go b/i18n/i18n_test.go index a711b8ea7..ffbd9d835 100644 --- a/i18n/i18n_test.go +++ b/i18n/i18n_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package i18n diff --git a/i18n_helper.go b/i18n_helper.go index a92774a11..fec72f1a7 100644 --- a/i18n_helper.go +++ b/i18n_helper.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/i18n_helper_test.go b/i18n_helper_test.go index 6f56dd7e1..a3472d23f 100644 --- a/i18n_helper_test.go +++ b/i18n_helper_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/integration/authorize_code_grant_public_client_pkce_test.go b/integration/authorize_code_grant_public_client_pkce_test.go index c3a735513..74426daed 100644 --- a/integration/authorize_code_grant_public_client_pkce_test.go +++ b/integration/authorize_code_grant_public_client_pkce_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/authorize_code_grant_public_client_test.go b/integration/authorize_code_grant_public_client_test.go index 4706311e8..24d8d64bd 100644 --- a/integration/authorize_code_grant_public_client_test.go +++ b/integration/authorize_code_grant_public_client_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/authorize_code_grant_test.go b/integration/authorize_code_grant_test.go index caa716de0..13a663c7a 100644 --- a/integration/authorize_code_grant_test.go +++ b/integration/authorize_code_grant_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/authorize_form_post_test.go b/integration/authorize_form_post_test.go index 8981a85e3..ceafd3a44 100644 --- a/integration/authorize_form_post_test.go +++ b/integration/authorize_form_post_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/authorize_implicit_grant_test.go b/integration/authorize_implicit_grant_test.go index f679af103..7ac1704c4 100644 --- a/integration/authorize_implicit_grant_test.go +++ b/integration/authorize_implicit_grant_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/authorize_jwt_bearer_required_iat_test.go b/integration/authorize_jwt_bearer_required_iat_test.go index 5bb7d4b83..6b91754cc 100644 --- a/integration/authorize_jwt_bearer_required_iat_test.go +++ b/integration/authorize_jwt_bearer_required_iat_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/authorize_jwt_bearer_required_jti_test.go b/integration/authorize_jwt_bearer_required_jti_test.go index d9c470c67..ff941d4b2 100644 --- a/integration/authorize_jwt_bearer_required_jti_test.go +++ b/integration/authorize_jwt_bearer_required_jti_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/authorize_jwt_bearer_test.go b/integration/authorize_jwt_bearer_test.go index 0f5fcdea2..844383453 100644 --- a/integration/authorize_jwt_bearer_test.go +++ b/integration/authorize_jwt_bearer_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/authorize_response_mode_test.go b/integration/authorize_response_mode_test.go index dae3d96d6..e1b7e227a 100644 --- a/integration/authorize_response_mode_test.go +++ b/integration/authorize_response_mode_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/client_credentials_grant_test.go b/integration/client_credentials_grant_test.go index d8638834a..58ca6b3ed 100644 --- a/integration/client_credentials_grant_test.go +++ b/integration/client_credentials_grant_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/clients/error.go b/integration/clients/error.go index cb7876309..db5f6be85 100644 --- a/integration/clients/error.go +++ b/integration/clients/error.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package clients diff --git a/integration/clients/introspect.go b/integration/clients/introspect.go index 62cd01018..2d77f8be7 100644 --- a/integration/clients/introspect.go +++ b/integration/clients/introspect.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package clients diff --git a/integration/clients/jwt_bearer.go b/integration/clients/jwt_bearer.go index 4b2b01127..dce715fb9 100644 --- a/integration/clients/jwt_bearer.go +++ b/integration/clients/jwt_bearer.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package clients diff --git a/integration/helper_endpoints_test.go b/integration/helper_endpoints_test.go index 91756533c..e78b6c89b 100644 --- a/integration/helper_endpoints_test.go +++ b/integration/helper_endpoints_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/helper_setup_test.go b/integration/helper_setup_test.go index c9c809202..f33b18948 100644 --- a/integration/helper_setup_test.go +++ b/integration/helper_setup_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/introspect_jwt_bearer_token_test.go b/integration/introspect_jwt_bearer_token_test.go index 9a28e5097..a5b85a69c 100644 --- a/integration/introspect_jwt_bearer_token_test.go +++ b/integration/introspect_jwt_bearer_token_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/introspect_token_test.go b/integration/introspect_token_test.go index b7363756b..5f3a14cc3 100644 --- a/integration/introspect_token_test.go +++ b/integration/introspect_token_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/oidc_explicit_test.go b/integration/oidc_explicit_test.go index 9f91ebbf4..a2df1e1ec 100644 --- a/integration/oidc_explicit_test.go +++ b/integration/oidc_explicit_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/oidc_implicit_hybrid_public_client_pkce_test.go b/integration/oidc_implicit_hybrid_public_client_pkce_test.go index 626bb8785..35becff19 100644 --- a/integration/oidc_implicit_hybrid_public_client_pkce_test.go +++ b/integration/oidc_implicit_hybrid_public_client_pkce_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/oidc_implicit_hybrid_test.go b/integration/oidc_implicit_hybrid_test.go index b45bf1bd0..7050cee80 100644 --- a/integration/oidc_implicit_hybrid_test.go +++ b/integration/oidc_implicit_hybrid_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/placeholder.go b/integration/placeholder.go index 9d1dacff5..f1b7fc193 100644 --- a/integration/placeholder.go +++ b/integration/placeholder.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration diff --git a/integration/pushed_authorize_code_grant_test.go b/integration/pushed_authorize_code_grant_test.go index a50fbf8fc..0d9a710d1 100644 --- a/integration/pushed_authorize_code_grant_test.go +++ b/integration/pushed_authorize_code_grant_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/refresh_token_grant_test.go b/integration/refresh_token_grant_test.go index d6c67c28f..e30b2bdfc 100644 --- a/integration/refresh_token_grant_test.go +++ b/integration/refresh_token_grant_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/resource_owner_password_credentials_grant_test.go b/integration/resource_owner_password_credentials_grant_test.go index 1796c1ea1..f1b45b467 100644 --- a/integration/resource_owner_password_credentials_grant_test.go +++ b/integration/resource_owner_password_credentials_grant_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/integration/revoke_token_test.go b/integration/revoke_token_test.go index 31288e5e3..bb74dae96 100644 --- a/integration/revoke_token_test.go +++ b/integration/revoke_token_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package integration_test diff --git a/internal/access_request.go b/internal/access_request.go index 57c926fc9..fd05e5420 100644 --- a/internal/access_request.go +++ b/internal/access_request.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/access_response.go b/internal/access_response.go index 7571620ca..340c2476f 100644 --- a/internal/access_response.go +++ b/internal/access_response.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/access_token_storage.go b/internal/access_token_storage.go index cefc94ae4..385424532 100644 --- a/internal/access_token_storage.go +++ b/internal/access_token_storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/access_token_strategy.go b/internal/access_token_strategy.go index a1ba9473c..d8c64c9bd 100644 --- a/internal/access_token_strategy.go +++ b/internal/access_token_strategy.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/authorize_code_storage.go b/internal/authorize_code_storage.go index c9f46949d..1543b6825 100644 --- a/internal/authorize_code_storage.go +++ b/internal/authorize_code_storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/authorize_code_strategy.go b/internal/authorize_code_strategy.go index dfbd414be..44edd6440 100644 --- a/internal/authorize_code_strategy.go +++ b/internal/authorize_code_strategy.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/authorize_handler.go b/internal/authorize_handler.go index 4468bc2ed..d7420fed5 100644 --- a/internal/authorize_handler.go +++ b/internal/authorize_handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/authorize_request.go b/internal/authorize_request.go index d3a17ef1a..5cceafff8 100644 --- a/internal/authorize_request.go +++ b/internal/authorize_request.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/authorize_response.go b/internal/authorize_response.go index eb5098772..5526adf12 100644 --- a/internal/authorize_response.go +++ b/internal/authorize_response.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/client.go b/internal/client.go index bf653cc31..2923cf70b 100644 --- a/internal/client.go +++ b/internal/client.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/gen/key.go b/internal/gen/key.go index 5ebb1702b..959032e80 100644 --- a/internal/gen/key.go +++ b/internal/gen/key.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package gen diff --git a/internal/hash.go b/internal/hash.go index 2c71db816..687984e76 100644 --- a/internal/hash.go +++ b/internal/hash.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/id_token_strategy.go b/internal/id_token_strategy.go index 4b89dac1e..330adeaee 100644 --- a/internal/id_token_strategy.go +++ b/internal/id_token_strategy.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/introspector.go b/internal/introspector.go index 875eda003..7b68fbf46 100644 --- a/internal/introspector.go +++ b/internal/introspector.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/oauth2_auth_jwt_storage.go b/internal/oauth2_auth_jwt_storage.go index 6e39295d9..80c7278e5 100644 --- a/internal/oauth2_auth_jwt_storage.go +++ b/internal/oauth2_auth_jwt_storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/oauth2_client_storage.go b/internal/oauth2_client_storage.go index 3319fea90..d33dfd777 100644 --- a/internal/oauth2_client_storage.go +++ b/internal/oauth2_client_storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/oauth2_explicit_storage.go b/internal/oauth2_explicit_storage.go index 512243d61..1bcd39226 100644 --- a/internal/oauth2_explicit_storage.go +++ b/internal/oauth2_explicit_storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Automatically generated by MockGen. DO NOT EDIT! diff --git a/internal/oauth2_owner_storage.go b/internal/oauth2_owner_storage.go index 94d249b43..79e797958 100644 --- a/internal/oauth2_owner_storage.go +++ b/internal/oauth2_owner_storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/oauth2_refresh_storage.go b/internal/oauth2_refresh_storage.go index dd559597c..73cfa8c29 100644 --- a/internal/oauth2_refresh_storage.go +++ b/internal/oauth2_refresh_storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Automatically generated by MockGen. DO NOT EDIT! diff --git a/internal/oauth2_revoke_storage.go b/internal/oauth2_revoke_storage.go index c233507de..12580b4b5 100644 --- a/internal/oauth2_revoke_storage.go +++ b/internal/oauth2_revoke_storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/oauth2_storage.go b/internal/oauth2_storage.go index 50f4b3702..a67815f7f 100644 --- a/internal/oauth2_storage.go +++ b/internal/oauth2_storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/oauth2_strategy.go b/internal/oauth2_strategy.go index 86314e34a..539202052 100644 --- a/internal/oauth2_strategy.go +++ b/internal/oauth2_strategy.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/openid_id_token_storage.go b/internal/openid_id_token_storage.go index 4d51e318b..bfcd0d628 100644 --- a/internal/openid_id_token_storage.go +++ b/internal/openid_id_token_storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/pkce_storage_strategy.go b/internal/pkce_storage_strategy.go index 3f831a4a1..46de92ea0 100644 --- a/internal/pkce_storage_strategy.go +++ b/internal/pkce_storage_strategy.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/pushed_authorize_handler.go b/internal/pushed_authorize_handler.go index 53d218637..d871469de 100644 --- a/internal/pushed_authorize_handler.go +++ b/internal/pushed_authorize_handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package internal diff --git a/internal/refresh_token_strategy.go b/internal/refresh_token_strategy.go index 350157033..5338bfb71 100644 --- a/internal/refresh_token_strategy.go +++ b/internal/refresh_token_strategy.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/request.go b/internal/request.go index c9b55e992..c74969c95 100644 --- a/internal/request.go +++ b/internal/request.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/revoke_handler.go b/internal/revoke_handler.go index fb314a34a..948178e19 100644 --- a/internal/revoke_handler.go +++ b/internal/revoke_handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/rw.go b/internal/rw.go index 1c8924b39..3d9545521 100644 --- a/internal/rw.go +++ b/internal/rw.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Automatically generated by MockGen. DO NOT EDIT! diff --git a/internal/storage.go b/internal/storage.go index 2046b4e3a..14fa7c32c 100644 --- a/internal/storage.go +++ b/internal/storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/test_helpers.go b/internal/test_helpers.go index 8495433f2..323a934bf 100644 --- a/internal/test_helpers.go +++ b/internal/test_helpers.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package internal diff --git a/internal/token_handler.go b/internal/token_handler.go index 540e72300..9eb170678 100644 --- a/internal/token_handler.go +++ b/internal/token_handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/internal/transactional.go b/internal/transactional.go index d31384db9..d98d63ab7 100644 --- a/internal/transactional.go +++ b/internal/transactional.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. diff --git a/introspect.go b/introspect.go index 095b0085a..7e6e6058d 100644 --- a/introspect.go +++ b/introspect.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/introspect_test.go b/introspect_test.go index b6d4c7083..a6b9787fe 100644 --- a/introspect_test.go +++ b/introspect_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/introspection_request_handler.go b/introspection_request_handler.go index 3ca536e3c..f1fdedd6c 100644 --- a/introspection_request_handler.go +++ b/introspection_request_handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/introspection_request_handler_test.go b/introspection_request_handler_test.go index 206ad5ecc..d589b61e0 100644 --- a/introspection_request_handler_test.go +++ b/introspection_request_handler_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/introspection_response_writer.go b/introspection_response_writer.go index 07f49fdee..5711d2b6f 100644 --- a/introspection_response_writer.go +++ b/introspection_response_writer.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/introspection_response_writer_test.go b/introspection_response_writer_test.go index f8c38b973..b1738cef6 100644 --- a/introspection_response_writer_test.go +++ b/introspection_response_writer_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/oauth2.go b/oauth2.go index cad5a9550..1b4cb3d03 100644 --- a/oauth2.go +++ b/oauth2.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/pushed_authorize_request_handler.go b/pushed_authorize_request_handler.go index d91a0e9c1..17c493b33 100644 --- a/pushed_authorize_request_handler.go +++ b/pushed_authorize_request_handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/pushed_authorize_request_handler_test.go b/pushed_authorize_request_handler_test.go index 696c6f7a9..41dd0b263 100644 --- a/pushed_authorize_request_handler_test.go +++ b/pushed_authorize_request_handler_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/pushed_authorize_response.go b/pushed_authorize_response.go index 8f3815113..3b8f9de18 100644 --- a/pushed_authorize_response.go +++ b/pushed_authorize_response.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/pushed_authorize_response_writer.go b/pushed_authorize_response_writer.go index 206970924..46ed2780c 100644 --- a/pushed_authorize_response_writer.go +++ b/pushed_authorize_response_writer.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/pushed_authorize_response_writer_test.go b/pushed_authorize_response_writer_test.go index edb60d73b..00c9d8fb6 100644 --- a/pushed_authorize_response_writer_test.go +++ b/pushed_authorize_response_writer_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/request.go b/request.go index c72c36f03..6bcfac794 100644 --- a/request.go +++ b/request.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/request_test.go b/request_test.go index 5c4e52fb5..5abe1dae9 100644 --- a/request_test.go +++ b/request_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/response_handler.go b/response_handler.go index 113e62795..c26ba85d8 100644 --- a/response_handler.go +++ b/response_handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/revoke_handler.go b/revoke_handler.go index 09ed1a79c..1bdcf17cf 100644 --- a/revoke_handler.go +++ b/revoke_handler.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/revoke_handler_test.go b/revoke_handler_test.go index 2c182545d..42a8269fd 100644 --- a/revoke_handler_test.go +++ b/revoke_handler_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite_test diff --git a/scope_strategy.go b/scope_strategy.go index 2326a5a63..0c4bbad6f 100644 --- a/scope_strategy.go +++ b/scope_strategy.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/scope_strategy_test.go b/scope_strategy_test.go index c9c7e6463..cfd9cb2b2 100644 --- a/scope_strategy_test.go +++ b/scope_strategy_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/session.go b/session.go index 9c810cd9a..6f95b2cf1 100644 --- a/session.go +++ b/session.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/session_test.go b/session_test.go index a3e1ac17d..52c5c3c64 100644 --- a/session_test.go +++ b/session_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/storage.go b/storage.go index b0f7a7bb1..11edee276 100644 --- a/storage.go +++ b/storage.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fosite diff --git a/storage/memory.go b/storage/memory.go index becddd515..499c528cd 100644 --- a/storage/memory.go +++ b/storage/memory.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package storage diff --git a/storage/memory_test.go b/storage/memory_test.go index 4183fd090..0f082ee80 100644 --- a/storage/memory_test.go +++ b/storage/memory_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package storage diff --git a/storage/transactional.go b/storage/transactional.go index 821df3489..f144340fb 100644 --- a/storage/transactional.go +++ b/storage/transactional.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package storage diff --git a/token/hmac/bytes.go b/token/hmac/bytes.go index 4ff445758..2a980f5ea 100644 --- a/token/hmac/bytes.go +++ b/token/hmac/bytes.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hmac diff --git a/token/hmac/bytes_test.go b/token/hmac/bytes_test.go index 04e04bf26..26e16d150 100644 --- a/token/hmac/bytes_test.go +++ b/token/hmac/bytes_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hmac diff --git a/token/hmac/hmacsha.go b/token/hmac/hmacsha.go index cf7507ae9..e3f0e8626 100644 --- a/token/hmac/hmacsha.go +++ b/token/hmac/hmacsha.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Package hmac is the default implementation for generating and validating challenges. It uses SHA-512/256 to diff --git a/token/hmac/hmacsha_test.go b/token/hmac/hmacsha_test.go index fb3e60945..0fe3f06e9 100644 --- a/token/hmac/hmacsha_test.go +++ b/token/hmac/hmacsha_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hmac diff --git a/token/jwt/claims.go b/token/jwt/claims.go index 92c39d30e..a03155c9e 100644 --- a/token/jwt/claims.go +++ b/token/jwt/claims.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt diff --git a/token/jwt/claims_id_token.go b/token/jwt/claims_id_token.go index ae37b4db1..b560de6e5 100644 --- a/token/jwt/claims_id_token.go +++ b/token/jwt/claims_id_token.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt diff --git a/token/jwt/claims_id_token_test.go b/token/jwt/claims_id_token_test.go index 7868037c1..e2dca9c6b 100644 --- a/token/jwt/claims_id_token_test.go +++ b/token/jwt/claims_id_token_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt_test diff --git a/token/jwt/claims_jwt.go b/token/jwt/claims_jwt.go index 9a2347e01..d8df95190 100644 --- a/token/jwt/claims_jwt.go +++ b/token/jwt/claims_jwt.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt diff --git a/token/jwt/claims_jwt_test.go b/token/jwt/claims_jwt_test.go index 69acefbf7..9e0baf303 100644 --- a/token/jwt/claims_jwt_test.go +++ b/token/jwt/claims_jwt_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt_test diff --git a/token/jwt/claims_test.go b/token/jwt/claims_test.go index 667de76cd..6041b0e43 100644 --- a/token/jwt/claims_test.go +++ b/token/jwt/claims_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt diff --git a/token/jwt/header.go b/token/jwt/header.go index 04a5699bc..a955e9f0d 100644 --- a/token/jwt/header.go +++ b/token/jwt/header.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt diff --git a/token/jwt/header_test.go b/token/jwt/header_test.go index 28cb7a6e2..f92d34c28 100644 --- a/token/jwt/header_test.go +++ b/token/jwt/header_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt diff --git a/token/jwt/jwt.go b/token/jwt/jwt.go index 83be6d811..d9f825a79 100644 --- a/token/jwt/jwt.go +++ b/token/jwt/jwt.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Package jwt is able to generate and validate json web tokens. diff --git a/token/jwt/jwt_test.go b/token/jwt/jwt_test.go index a356b7d82..b498a7e33 100644 --- a/token/jwt/jwt_test.go +++ b/token/jwt/jwt_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt diff --git a/token/jwt/map_claims.go b/token/jwt/map_claims.go index 787fd2d51..35b722a8d 100644 --- a/token/jwt/map_claims.go +++ b/token/jwt/map_claims.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt diff --git a/token/jwt/map_claims_test.go b/token/jwt/map_claims_test.go index 62d6c5bd2..a0fa00445 100644 --- a/token/jwt/map_claims_test.go +++ b/token/jwt/map_claims_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt diff --git a/token/jwt/token.go b/token/jwt/token.go index 44c20331d..2650c030f 100644 --- a/token/jwt/token.go +++ b/token/jwt/token.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt diff --git a/token/jwt/token_test.go b/token/jwt/token_test.go index f204711be..3d09a91af 100644 --- a/token/jwt/token_test.go +++ b/token/jwt/token_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt diff --git a/token/jwt/validation_error.go b/token/jwt/validation_error.go index 3d1ccd55f..d9324f61f 100644 --- a/token/jwt/validation_error.go +++ b/token/jwt/validation_error.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwt diff --git a/tools.go b/tools.go index 055ec4e38..054dc0e4e 100644 --- a/tools.go +++ b/tools.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Ory Corp +// Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 //go:build tools From 222821ea4175c94a380fa7aae21a981b94ef3ba7 Mon Sep 17 00:00:00 2001 From: Shu Date: Fri, 15 Mar 2024 15:42:25 -0600 Subject: [PATCH 10/17] feat: implement the access token handling for device authorization flow feat: add the access token endpoint handling for device authorization flow --- compose/compose.go | 4 +- compose/compose_oauth2.go | 39 +- compose/compose_openid.go | 6 +- compose/compose_rfc8628.go | 26 +- device_response_writer.go | 2 +- errors.go | 21 + fosite_test.go | 10 +- generate-mocks.sh | 4 +- generate.go | 2 + handler.go | 2 +- handler/oauth2/flow_authorize_code_auth.go | 37 +- .../oauth2/flow_authorize_code_auth_test.go | 10 +- handler/oauth2/flow_authorize_code_token.go | 212 ++---- .../oauth2/flow_authorize_code_token_test.go | 106 ++- handler/oauth2/flow_generic_code_token.go | 254 +++++++ handler/oauth2/storage.go | 3 +- handler/openid/flow_hybrid.go | 8 +- handler/openid/flow_hybrid_test.go | 10 +- handler/rfc8628/storage.go | 4 +- handler/rfc8628/strategy_hmacsha.go | 34 +- handler/rfc8628/token_handler.go | 100 +++ handler/rfc8628/token_handler_test.go | 694 ++++++++++++++++++ ...rize_code_grant_public_client_pkce_test.go | 2 +- ...authorize_code_grant_public_client_test.go | 2 +- integration/authorize_code_grant_test.go | 4 +- .../authorize_device_grant_request_test.go | 160 +++- integration/helper_setup_test.go | 28 +- .../pushed_authorize_code_grant_test.go | 2 +- internal/device_code_rate_limit_strategy.go | 52 ++ internal/device_code_storage.go | 82 +++ oauth2.go | 22 +- storage/memory.go | 1 + 32 files changed, 1620 insertions(+), 323 deletions(-) create mode 100644 handler/oauth2/flow_generic_code_token.go create mode 100644 handler/rfc8628/token_handler.go create mode 100644 handler/rfc8628/token_handler_test.go create mode 100644 internal/device_code_rate_limit_strategy.go create mode 100644 internal/device_code_storage.go diff --git a/compose/compose.go b/compose/compose.go index 94c7349ff..91dd4e6e6 100644 --- a/compose/compose.go +++ b/compose/compose.go @@ -75,7 +75,8 @@ func ComposeAllEnabled(config *fosite.Config, storage interface{}, key interface OpenIDConnectTokenStrategy: NewOpenIDConnectStrategy(keyGetter, config), Signer: &jwt.DefaultSigner{GetPrivateKey: keyGetter}, }, - OAuth2AuthorizeExplicitFactory, + OAuth2AuthorizeExplicitAuthFactory, + Oauth2AuthorizeExplicitTokenFactory, OAuth2AuthorizeImplicitFactory, OAuth2ClientCredentialsGrantFactory, OAuth2RefreshTokenGrantFactory, @@ -91,6 +92,7 @@ func ComposeAllEnabled(config *fosite.Config, storage interface{}, key interface OAuth2TokenRevocationFactory, RFC8628DeviceFactory, + RFC8628DeviceAuthorizationTokenFactory, OAuth2PKCEFactory, PushedAuthorizeHandlerFactory, diff --git a/compose/compose_oauth2.go b/compose/compose_oauth2.go index c3f01073e..9be25fb4d 100644 --- a/compose/compose_oauth2.go +++ b/compose/compose_oauth2.go @@ -9,16 +9,35 @@ import ( "github.com/ory/fosite/token/jwt" ) -// OAuth2AuthorizeExplicitFactory creates an OAuth2 authorize code grant ("authorize explicit flow") handler and registers +// OAuth2AuthorizeExplicitAuthFactory creates an OAuth2 authorize code grant ("authorize explicit flow") handler and registers // an access token, refresh token and authorize code validator. -func OAuth2AuthorizeExplicitFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} { - return &oauth2.AuthorizeExplicitGrantHandler{ - AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy), - RefreshTokenStrategy: strategy.(oauth2.RefreshTokenStrategy), - AuthorizeCodeStrategy: strategy.(oauth2.AuthorizeCodeStrategy), - CoreStorage: storage.(oauth2.CoreStorage), - TokenRevocationStorage: storage.(oauth2.TokenRevocationStorage), - Config: config, +func OAuth2AuthorizeExplicitAuthFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} { + return &oauth2.AuthorizeExplicitGrantAuthHandler{ + AuthorizeCodeStrategy: strategy.(oauth2.AuthorizeCodeStrategy), + AuthorizeCodeStorage: storage.(oauth2.AuthorizeCodeStorage), + Config: config, + } +} + +// Oauth2AuthorizeExplicitTokenFactory creates an OAuth2 authorize code grant ("authorize explicit flow") token handler and registers +// an access token, refresh token and authorize code validator. +func Oauth2AuthorizeExplicitTokenFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} { + return &oauth2.AuthorizeExplicitTokenEndpointHandler{ + GenericCodeTokenEndpointHandler: oauth2.GenericCodeTokenEndpointHandler{ + AccessRequestValidator: &oauth2.AuthorizeExplicitGrantAccessRequestValidator{}, + CodeHandler: &oauth2.AuthorizeCodeHandler{ + AuthorizeCodeStrategy: strategy.(oauth2.AuthorizeCodeStrategy), + }, + SessionHandler: &oauth2.AuthorizeExplicitGrantSessionHandler{ + AuthorizeCodeStorage: storage.(oauth2.AuthorizeCodeStorage), + }, + + AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy), + RefreshTokenStrategy: strategy.(oauth2.RefreshTokenStrategy), + CoreStorage: storage.(oauth2.CoreStorage), + TokenRevocationStorage: storage.(oauth2.TokenRevocationStorage), + Config: config, + }, } } @@ -96,7 +115,7 @@ func OAuth2TokenIntrospectionFactory(config fosite.Configurator, storage interfa // OAuth2StatelessJWTIntrospectionFactory creates an OAuth2 token introspection handler and // registers an access token validator. This can only be used to validate JWTs and does so -// statelessly, meaning it uses only the data available in the JWT itself, and does not access the +// stateless, meaning it uses only the data available in the JWT itself, and does not access the // storage implementation at all. // // Due to the stateless nature of this factory, THE BUILT-IN REVOCATION MECHANISMS WILL NOT WORK. diff --git a/compose/compose_openid.go b/compose/compose_openid.go index 29b1d3a73..68178ddc7 100644 --- a/compose/compose_openid.go +++ b/compose/compose_openid.go @@ -59,11 +59,9 @@ func OpenIDConnectImplicitFactory(config fosite.Configurator, storage interface{ // **Important note:** You must add this handler *after* you have added an OAuth2 authorize code handler! func OpenIDConnectHybridFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} { return &openid.OpenIDConnectHybridHandler{ - AuthorizeExplicitGrantHandler: &oauth2.AuthorizeExplicitGrantHandler{ - AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy), - RefreshTokenStrategy: strategy.(oauth2.RefreshTokenStrategy), + AuthorizeExplicitGrantAuthHandler: &oauth2.AuthorizeExplicitGrantAuthHandler{ AuthorizeCodeStrategy: strategy.(oauth2.AuthorizeCodeStrategy), - CoreStorage: storage.(oauth2.CoreStorage), + AuthorizeCodeStorage: storage.(oauth2.AuthorizeCodeStorage), Config: config, }, Config: config, diff --git a/compose/compose_rfc8628.go b/compose/compose_rfc8628.go index 5217aeb7f..64ccc1e4c 100644 --- a/compose/compose_rfc8628.go +++ b/compose/compose_rfc8628.go @@ -7,11 +7,12 @@ package compose import ( "github.com/ory/fosite" + "github.com/ory/fosite/handler/oauth2" "github.com/ory/fosite/handler/rfc8628" ) // RFC8628DeviceFactory creates an OAuth2 device code grant ("Device Authorization Grant") handler and registers -// an user code, device code, access token and a refresh token validator. +// a user code, device code, access token and a refresh token validator. func RFC8628DeviceFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} { return &rfc8628.DeviceAuthHandler{ Strategy: strategy.(rfc8628.RFC8628CodeStrategy), @@ -19,3 +20,26 @@ func RFC8628DeviceFactory(config fosite.Configurator, storage interface{}, strat Config: config, } } + +// RFC8628DeviceAuthorizationTokenFactory creates an OAuth2 device authorization grant ("Device Authorization Grant") handler and registers +// an access token, refresh token and authorize code validator. +func RFC8628DeviceAuthorizationTokenFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} { + return &rfc8628.DeviceCodeTokenEndpointHandler{ + GenericCodeTokenEndpointHandler: oauth2.GenericCodeTokenEndpointHandler{ + AccessRequestValidator: &rfc8628.DeviceAccessRequestValidator{}, + CodeHandler: &rfc8628.DeviceCodeHandler{ + DeviceRateLimitStrategy: strategy.(rfc8628.DeviceRateLimitStrategy), + DeviceCodeStrategy: strategy.(rfc8628.DeviceCodeStrategy), + }, + SessionHandler: &rfc8628.DeviceSessionHandler{ + DeviceCodeStorage: storage.(rfc8628.DeviceCodeStorage), + }, + + AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy), + RefreshTokenStrategy: strategy.(oauth2.RefreshTokenStrategy), + CoreStorage: storage.(oauth2.CoreStorage), + TokenRevocationStorage: storage.(oauth2.TokenRevocationStorage), + Config: config, + }, + } +} diff --git a/device_response_writer.go b/device_response_writer.go index 2cc17d096..a093ea878 100644 --- a/device_response_writer.go +++ b/device_response_writer.go @@ -9,7 +9,7 @@ import ( // NewDeviceResponse returns a new DeviceResponder func (f *Fosite) NewDeviceResponse(ctx context.Context, r DeviceRequester, session Session) (DeviceResponder, error) { - var resp = &DeviceResponse{} + resp := &DeviceResponse{} r.SetSession(session) for _, h := range f.Config.GetDeviceEndpointHandlers(ctx) { diff --git a/errors.go b/errors.go index 0867d9373..18dc1ab3b 100644 --- a/errors.go +++ b/errors.go @@ -23,6 +23,8 @@ var ( // ErrInvalidatedAuthorizeCode is an error indicating that an authorization code has been // used previously. ErrInvalidatedAuthorizeCode = errors.New("Authorization code has ben invalidated") + // ErrInvalidatedDeviceCode is an error indicating that a device code has benn used previously. + ErrInvalidatedDeviceCode = errors.New("Device code has been invalidated") // ErrSerializationFailure is an error indicating that the transactional capable storage could not guarantee // consistency of Update & Delete operations on the same rows between multiple sessions. ErrSerializationFailure = errors.New("The request could not be completed due to concurrent access") @@ -202,6 +204,22 @@ var ( ErrorField: errJTIKnownName, CodeField: http.StatusBadRequest, } + ErrAuthorizationPending = &RFC6749Error{ + DescriptionField: "The authorization request is still pending as the end user hasn't yet completed the user-interaction steps.", + ErrorField: errAuthorizationPending, + CodeField: http.StatusBadRequest, + } + ErrPollingRateLimited = &RFC6749Error{ + DescriptionField: "The authorization request was rate-limited to prevent system overload.", + HintField: "Ensure that you don't call the token endpoint sooner than the polling interval", + ErrorField: errPollingIntervalRateLimited, + CodeField: http.StatusTooManyRequests, + } + ErrDeviceExpiredToken = &RFC6749Error{ + DescriptionField: "The device_code has expired, and the device authorization session has concluded.", + ErrorField: errDeviceExpiredToken, + CodeField: http.StatusBadRequest, + } ) const ( @@ -239,6 +257,9 @@ const ( errRequestURINotSupportedName = "request_uri_not_supported" errRegistrationNotSupportedName = "registration_not_supported" errJTIKnownName = "jti_known" + errAuthorizationPending = "authorization_pending" + errPollingIntervalRateLimited = "polling_interval_rate_limited" + errDeviceExpiredToken = "expired_token" ) type ( diff --git a/fosite_test.go b/fosite_test.go index 2c86b498a..ded80b2c1 100644 --- a/fosite_test.go +++ b/fosite_test.go @@ -16,23 +16,23 @@ import ( ) func TestAuthorizeEndpointHandlers(t *testing.T) { - h := &oauth2.AuthorizeExplicitGrantHandler{} + h := &oauth2.AuthorizeExplicitGrantAuthHandler{} hs := AuthorizeEndpointHandlers{} hs.Append(h) hs.Append(h) - hs.Append(&oauth2.AuthorizeExplicitGrantHandler{}) + hs.Append(&oauth2.AuthorizeExplicitGrantAuthHandler{}) assert.Len(t, hs, 1) assert.Equal(t, hs[0], h) } func TestTokenEndpointHandlers(t *testing.T) { - h := &oauth2.AuthorizeExplicitGrantHandler{} + h := &oauth2.GenericCodeTokenEndpointHandler{} hs := TokenEndpointHandlers{} hs.Append(h) hs.Append(h) // do some crazy type things and make sure dupe detection works - var f interface{} = &oauth2.AuthorizeExplicitGrantHandler{} - hs.Append(&oauth2.AuthorizeExplicitGrantHandler{}) + var f interface{} = &oauth2.GenericCodeTokenEndpointHandler{} + hs.Append(&oauth2.GenericCodeTokenEndpointHandler{}) hs.Append(f.(TokenEndpointHandler)) require.Len(t, hs, 1) assert.Equal(t, hs[0], h) diff --git a/generate-mocks.sh b/generate-mocks.sh index d4dded4ea..2ed96b708 100755 --- a/generate-mocks.sh +++ b/generate-mocks.sh @@ -6,6 +6,7 @@ mockgen -package internal -destination internal/transactional.go github.com/ory/ mockgen -package internal -destination internal/oauth2_storage.go github.com/ory/fosite/handler/oauth2 CoreStorage mockgen -package internal -destination internal/oauth2_strategy.go github.com/ory/fosite/handler/oauth2 CoreStrategy mockgen -package internal -destination internal/authorize_code_storage.go github.com/ory/fosite/handler/oauth2 AuthorizeCodeStorage +mockgen -package internal -destination internal/device_code_storage.go github.com/ory/fosite/handler/rfc8628 DeviceCodeStorage mockgen -package internal -destination internal/oauth2_auth_jwt_storage.go github.com/ory/fosite/handler/rfc7523 RFC7523KeyStorage mockgen -package internal -destination internal/access_token_storage.go github.com/ory/fosite/handler/oauth2 AccessTokenStorage mockgen -package internal -destination internal/refresh_token_strategy.go github.com/ory/fosite/handler/oauth2 RefreshTokenStorage @@ -16,6 +17,7 @@ mockgen -package internal -destination internal/openid_id_token_storage.go githu mockgen -package internal -destination internal/access_token_strategy.go github.com/ory/fosite/handler/oauth2 AccessTokenStrategy mockgen -package internal -destination internal/refresh_token_strategy.go github.com/ory/fosite/handler/oauth2 RefreshTokenStrategy mockgen -package internal -destination internal/authorize_code_strategy.go github.com/ory/fosite/handler/oauth2 AuthorizeCodeStrategy +mockgen -package internal -destination internal/device_code_rate_limit_strategy.go github.com/ory/fosite/handler/rfc8628 DeviceRateLimitStrategy mockgen -package internal -destination internal/id_token_strategy.go github.com/ory/fosite/handler/openid OpenIDConnectTokenStrategy mockgen -package internal -destination internal/pkce_storage_strategy.go github.com/ory/fosite/handler/pkce PKCERequestStorage mockgen -package internal -destination internal/authorize_handler.go github.com/ory/fosite AuthorizeEndpointHandler @@ -29,4 +31,4 @@ mockgen -package internal -destination internal/access_response.go github.com/or mockgen -package internal -destination internal/authorize_request.go github.com/ory/fosite AuthorizeRequester mockgen -package internal -destination internal/authorize_response.go github.com/ory/fosite AuthorizeResponder -goimports -w internal/ \ No newline at end of file +goimports -w internal/ diff --git a/generate.go b/generate.go index fe382602b..0b3efaf63 100644 --- a/generate.go +++ b/generate.go @@ -9,6 +9,7 @@ package fosite //go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/oauth2_storage.go github.com/ory/fosite/handler/oauth2 CoreStorage //go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/oauth2_strategy.go github.com/ory/fosite/handler/oauth2 CoreStrategy //go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/authorize_code_storage.go github.com/ory/fosite/handler/oauth2 AuthorizeCodeStorage +//go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/device_code_storage.go github.com/ory/fosite/handler/rfc8628 DeviceCodeStorage //go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/oauth2_auth_jwt_storage.go github.com/ory/fosite/handler/rfc7523 RFC7523KeyStorage //go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/access_token_storage.go github.com/ory/fosite/handler/oauth2 AccessTokenStorage //go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/refresh_token_strategy.go github.com/ory/fosite/handler/oauth2 RefreshTokenStorage @@ -19,6 +20,7 @@ package fosite //go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/access_token_strategy.go github.com/ory/fosite/handler/oauth2 AccessTokenStrategy //go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/refresh_token_strategy.go github.com/ory/fosite/handler/oauth2 RefreshTokenStrategy //go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/authorize_code_strategy.go github.com/ory/fosite/handler/oauth2 AuthorizeCodeStrategy +//go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/device_code_rate_limit_strategy.go github.com/ory/fosite/handler/rfc8628 DeviceRateLimitStrategy //go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/id_token_strategy.go github.com/ory/fosite/handler/openid OpenIDConnectTokenStrategy //go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/pkce_storage_strategy.go github.com/ory/fosite/handler/pkce PKCERequestStorage //go:generate go run github.com/golang/mock/mockgen -package internal -destination internal/authorize_handler.go github.com/ory/fosite AuthorizeEndpointHandler diff --git a/handler.go b/handler.go index 163f4af6f..6a389b9fc 100644 --- a/handler.go +++ b/handler.go @@ -73,7 +73,7 @@ type DeviceEndpointHandler interface { // is passed along, if further information retrieval is required. If the handler feels that he is not responsible for // the device authorize request, he must return nil and NOT modify session nor responder neither requester. // - // The following spec is a good example of what HandleDeviceUserRequest should do. + // The following spec is a good example of what HandleDeviceEndpointRequest should do. // * https://tools.ietf.org/html/rfc8628#section-3.2 HandleDeviceEndpointRequest(ctx context.Context, requester DeviceRequester, responder DeviceResponder) error } diff --git a/handler/oauth2/flow_authorize_code_auth.go b/handler/oauth2/flow_authorize_code_auth.go index 0b03c8a78..ac70e883b 100644 --- a/handler/oauth2/flow_authorize_code_auth.go +++ b/handler/oauth2/flow_authorize_code_auth.go @@ -14,50 +14,39 @@ import ( "github.com/ory/fosite" ) -var _ fosite.AuthorizeEndpointHandler = (*AuthorizeExplicitGrantHandler)(nil) -var _ fosite.TokenEndpointHandler = (*AuthorizeExplicitGrantHandler)(nil) +var _ fosite.AuthorizeEndpointHandler = (*AuthorizeExplicitGrantAuthHandler)(nil) -// AuthorizeExplicitGrantHandler is a response handler for the Authorize Code grant using the explicit grant type +// AuthorizeExplicitGrantAuthHandler is a response handler for the Authorize Code grant using the explicit grant type // as defined in https://tools.ietf.org/html/rfc6749#section-4.1 -type AuthorizeExplicitGrantHandler struct { - AccessTokenStrategy AccessTokenStrategy - RefreshTokenStrategy RefreshTokenStrategy - AuthorizeCodeStrategy AuthorizeCodeStrategy - CoreStorage CoreStorage - TokenRevocationStorage TokenRevocationStorage - Config interface { +type AuthorizeExplicitGrantAuthHandler struct { + AuthorizeCodeStrategy AuthorizeCodeStrategy + AuthorizeCodeStorage AuthorizeCodeStorage + + Config interface { fosite.AuthorizeCodeLifespanProvider - fosite.AccessTokenLifespanProvider - fosite.RefreshTokenLifespanProvider fosite.ScopeStrategyProvider fosite.AudienceStrategyProvider fosite.RedirectSecureCheckerProvider - fosite.RefreshTokenScopesProvider fosite.OmitRedirectScopeParamProvider fosite.SanitationAllowedProvider } } -func (c *AuthorizeExplicitGrantHandler) secureChecker(ctx context.Context) func(context.Context, *url.URL) bool { +func (c *AuthorizeExplicitGrantAuthHandler) secureChecker(ctx context.Context) func(context.Context, *url.URL) bool { if c.Config.GetRedirectSecureChecker(ctx) == nil { return fosite.IsRedirectURISecure } return c.Config.GetRedirectSecureChecker(ctx) } -func (c *AuthorizeExplicitGrantHandler) HandleAuthorizeEndpointRequest(ctx context.Context, ar fosite.AuthorizeRequester, resp fosite.AuthorizeResponder) error { - // This let's us define multiple response types, for example open id connect's id_token +func (c *AuthorizeExplicitGrantAuthHandler) HandleAuthorizeEndpointRequest(ctx context.Context, ar fosite.AuthorizeRequester, resp fosite.AuthorizeResponder) error { + // This allows to define multiple response types, for example OpenID Connect `id_token` if !ar.GetResponseTypes().ExactOne("code") { return nil } ar.SetDefaultResponseMode(fosite.ResponseModeQuery) - // Disabled because this is already handled at the authorize_request_handler - // if !ar.GetClient().GetResponseTypes().Has("code") { - // return errorsx.WithStack(fosite.ErrInvalidGrant) - // } - if !c.secureChecker(ctx)(ctx, ar.GetRedirectURI()) { return errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Redirect URL is using an insecure protocol, http is only allowed for hosts with suffix 'localhost', for example: http://myapp.localhost/.")) } @@ -76,14 +65,14 @@ func (c *AuthorizeExplicitGrantHandler) HandleAuthorizeEndpointRequest(ctx conte return c.IssueAuthorizeCode(ctx, ar, resp) } -func (c *AuthorizeExplicitGrantHandler) IssueAuthorizeCode(ctx context.Context, ar fosite.AuthorizeRequester, resp fosite.AuthorizeResponder) error { +func (c *AuthorizeExplicitGrantAuthHandler) IssueAuthorizeCode(ctx context.Context, ar fosite.AuthorizeRequester, resp fosite.AuthorizeResponder) error { code, signature, err := c.AuthorizeCodeStrategy.GenerateAuthorizeCode(ctx, ar) if err != nil { return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } ar.GetSession().SetExpiresAt(fosite.AuthorizeCode, time.Now().UTC().Add(c.Config.GetAuthorizeCodeLifespan(ctx))) - if err := c.CoreStorage.CreateAuthorizeCodeSession(ctx, signature, ar.Sanitize(c.GetSanitationWhiteList(ctx))); err != nil { + if err = c.AuthorizeCodeStorage.CreateAuthorizeCodeSession(ctx, signature, ar.Sanitize(c.GetSanitationWhiteList(ctx))); err != nil { return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } @@ -97,7 +86,7 @@ func (c *AuthorizeExplicitGrantHandler) IssueAuthorizeCode(ctx context.Context, return nil } -func (c *AuthorizeExplicitGrantHandler) GetSanitationWhiteList(ctx context.Context) []string { +func (c *AuthorizeExplicitGrantAuthHandler) GetSanitationWhiteList(ctx context.Context) []string { if allowedList := c.Config.GetSanitationWhiteList(ctx); len(allowedList) > 0 { return allowedList } diff --git a/handler/oauth2/flow_authorize_code_auth_test.go b/handler/oauth2/flow_authorize_code_auth_test.go index 625ccb497..1776a3080 100644 --- a/handler/oauth2/flow_authorize_code_auth_test.go +++ b/handler/oauth2/flow_authorize_code_auth_test.go @@ -28,16 +28,16 @@ func TestAuthorizeCode_HandleAuthorizeEndpointRequest(t *testing.T) { } { t.Run("strategy="+k, func(t *testing.T) { store := storage.NewMemoryStore() - handler := AuthorizeExplicitGrantHandler{ - CoreStorage: store, + handler := AuthorizeExplicitGrantAuthHandler{ AuthorizeCodeStrategy: strategy, + AuthorizeCodeStorage: store, Config: &fosite.Config{ AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, ScopeStrategy: fosite.HierarchicScopeStrategy, }, } for _, c := range []struct { - handler AuthorizeExplicitGrantHandler + handler AuthorizeExplicitGrantAuthHandler areq *fosite.AuthorizeRequest description string expectErr error @@ -122,9 +122,9 @@ func TestAuthorizeCode_HandleAuthorizeEndpointRequest(t *testing.T) { }, }, { - handler: AuthorizeExplicitGrantHandler{ - CoreStorage: store, + handler: AuthorizeExplicitGrantAuthHandler{ AuthorizeCodeStrategy: strategy, + AuthorizeCodeStorage: store, Config: &fosite.Config{ ScopeStrategy: fosite.HierarchicScopeStrategy, AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, diff --git a/handler/oauth2/flow_authorize_code_token.go b/handler/oauth2/flow_authorize_code_token.go index dceb83001..96803a20c 100644 --- a/handler/oauth2/flow_authorize_code_token.go +++ b/handler/oauth2/flow_authorize_code_token.go @@ -5,197 +5,91 @@ package oauth2 import ( "context" - "time" - - "github.com/ory/x/errorsx" - - "github.com/ory/fosite/storage" "github.com/pkg/errors" + "github.com/ory/x/errorsx" + "github.com/ory/fosite" ) -// HandleTokenEndpointRequest implements -// * https://tools.ietf.org/html/rfc6749#section-4.1.3 (everything) -func (c *AuthorizeExplicitGrantHandler) HandleTokenEndpointRequest(ctx context.Context, request fosite.AccessRequester) error { - if !c.CanHandleTokenEndpointRequest(ctx, request) { - return errorsx.WithStack(errorsx.WithStack(fosite.ErrUnknownRequest)) - } - - if !request.GetClient().GetGrantTypes().Has("authorization_code") { - return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant \"authorization_code\".")) - } +type AuthorizeCodeHandler struct { + AuthorizeCodeStrategy AuthorizeCodeStrategy +} - code := request.GetRequestForm().Get("code") +func (c AuthorizeCodeHandler) Code(ctx context.Context, requester fosite.AccessRequester) (string, string, error) { + code := requester.GetRequestForm().Get("code") signature := c.AuthorizeCodeStrategy.AuthorizeCodeSignature(ctx, code) - authorizeRequest, err := c.CoreStorage.GetAuthorizeCodeSession(ctx, signature, request.GetSession()) - if errors.Is(err, fosite.ErrInvalidatedAuthorizeCode) { - if authorizeRequest == nil { - return fosite.ErrServerError. - WithHint("Misconfigured code lead to an error that prohibited the OAuth 2.0 Framework from processing this request."). - WithDebug("GetAuthorizeCodeSession must return a value for \"fosite.Requester\" when returning \"ErrInvalidatedAuthorizeCode\".") - } + return code, signature, nil +} - // If an authorize code is used twice, we revoke all refresh and access tokens associated with this request. - reqID := authorizeRequest.GetID() - hint := "The authorization code has already been used." - debug := "" - if revErr := c.TokenRevocationStorage.RevokeAccessToken(ctx, reqID); revErr != nil { - hint += " Additionally, an error occurred during processing the access token revocation." - debug += "Revocation of access_token lead to error " + revErr.Error() + "." - } - if revErr := c.TokenRevocationStorage.RevokeRefreshToken(ctx, reqID); revErr != nil { - hint += " Additionally, an error occurred during processing the refresh token revocation." - debug += "Revocation of refresh_token lead to error " + revErr.Error() + "." - } - return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint(hint).WithDebug(debug)) - } else if err != nil && errors.Is(err, fosite.ErrNotFound) { - return errorsx.WithStack(fosite.ErrInvalidGrant.WithWrap(err).WithDebug(err.Error())) - } else if err != nil { - return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) - } +func (c AuthorizeCodeHandler) ValidateCode(ctx context.Context, requester fosite.AccessRequester, code string) error { + return c.AuthorizeCodeStrategy.ValidateAuthorizeCode(ctx, requester, code) +} - // The authorization server MUST verify that the authorization code is valid - // This needs to happen after store retrieval for the session to be hydrated properly - if err := c.AuthorizeCodeStrategy.ValidateAuthorizeCode(ctx, request, code); err != nil { - return errorsx.WithStack(fosite.ErrInvalidGrant.WithWrap(err).WithDebug(err.Error())) - } +type AuthorizeExplicitGrantSessionHandler struct { + AuthorizeCodeStorage AuthorizeCodeStorage +} - // Override scopes - request.SetRequestedScopes(authorizeRequest.GetRequestedScopes()) +func (s AuthorizeExplicitGrantSessionHandler) Session(ctx context.Context, requester fosite.AccessRequester, codeSignature string) (fosite.Requester, error) { + req, err := s.AuthorizeCodeStorage.GetAuthorizeCodeSession(ctx, codeSignature, requester.GetSession()) - // Override audiences - request.SetRequestedAudience(authorizeRequest.GetRequestedAudience()) + if err != nil && errors.Is(err, fosite.ErrInvalidatedAuthorizeCode) { + if req != nil { + return req, err + } - // The authorization server MUST ensure that the authorization code was issued to the authenticated - // confidential client, or if the client is public, ensure that the - // code was issued to "client_id" in the request, - if authorizeRequest.GetClient().GetID() != request.GetClient().GetID() { - return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client ID from this request does not match the one from the authorize request.")) + return req, fosite.ErrServerError. + WithHint("Misconfigured code lead to an error that prohibited the OAuth 2.0 Framework from processing this request."). + WithDebug("\"GetAuthorizeCodeSession\" must return a value for \"fosite.Requester\" when returning \"ErrInvalidatedAuthorizeCode\".") } - // ensure that the "redirect_uri" parameter is present if the - // "redirect_uri" parameter was included in the initial authorization - // request as described in Section 4.1.1, and if included ensure that - // their values are identical. - forcedRedirectURI := authorizeRequest.GetRequestForm().Get("redirect_uri") - if forcedRedirectURI != "" && forcedRedirectURI != request.GetRequestForm().Get("redirect_uri") { - return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The \"redirect_uri\" from this request does not match the one from the authorize request.")) + if err != nil && errors.Is(err, fosite.ErrNotFound) { + return nil, errorsx.WithStack(fosite.ErrInvalidGrant.WithWrap(err).WithDebug(err.Error())) } - // Checking of POST client_id skipped, because: - // If the client type is confidential or the client was issued client - // credentials (or assigned other authentication requirements), the - // client MUST authenticate with the authorization server as described - // in Section 3.2.1. - request.SetSession(authorizeRequest.GetSession()) - request.SetID(authorizeRequest.GetID()) - - atLifespan := fosite.GetEffectiveLifespan(request.GetClient(), fosite.GrantTypeAuthorizationCode, fosite.AccessToken, c.Config.GetAccessTokenLifespan(ctx)) - request.GetSession().SetExpiresAt(fosite.AccessToken, time.Now().UTC().Add(atLifespan).Round(time.Second)) - - rtLifespan := fosite.GetEffectiveLifespan(request.GetClient(), fosite.GrantTypeAuthorizationCode, fosite.RefreshToken, c.Config.GetRefreshTokenLifespan(ctx)) - if rtLifespan > -1 { - request.GetSession().SetExpiresAt(fosite.RefreshToken, time.Now().UTC().Add(rtLifespan).Round(time.Second)) + if err != nil { + return nil, errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } - return nil + return req, err } -func canIssueRefreshToken(ctx context.Context, c *AuthorizeExplicitGrantHandler, request fosite.Requester) bool { - scope := c.Config.GetRefreshTokenScopes(ctx) - // Require one of the refresh token scopes, if set. - if len(scope) > 0 && !request.GetGrantedScopes().HasOneOf(scope...) { - return false - } - // Do not issue a refresh token to clients that cannot use the refresh token grant type. - if !request.GetClient().GetGrantTypes().Has("refresh_token") { - return false - } - return true +func (s AuthorizeExplicitGrantSessionHandler) InvalidateSession(ctx context.Context, codeSignature string) error { + return s.AuthorizeCodeStorage.InvalidateAuthorizeCodeSession(ctx, codeSignature) } -func (c *AuthorizeExplicitGrantHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) (err error) { - if !c.CanHandleTokenEndpointRequest(ctx, requester) { - return errorsx.WithStack(fosite.ErrUnknownRequest) - } - - code := requester.GetRequestForm().Get("code") - signature := c.AuthorizeCodeStrategy.AuthorizeCodeSignature(ctx, code) - authorizeRequest, err := c.CoreStorage.GetAuthorizeCodeSession(ctx, signature, requester.GetSession()) - if err != nil { - return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) - } else if err := c.AuthorizeCodeStrategy.ValidateAuthorizeCode(ctx, requester, code); err != nil { - // This needs to happen after store retrieval for the session to be hydrated properly - return errorsx.WithStack(fosite.ErrInvalidRequest.WithWrap(err).WithDebug(err.Error())) - } - - for _, scope := range authorizeRequest.GetGrantedScopes() { - requester.GrantScope(scope) - } - - for _, audience := range authorizeRequest.GetGrantedAudience() { - requester.GrantAudience(audience) - } +type AuthorizeExplicitGrantAccessRequestValidator struct{} - access, accessSignature, err := c.AccessTokenStrategy.GenerateAccessToken(ctx, requester) - if err != nil { - return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) - } - - var refresh, refreshSignature string - if canIssueRefreshToken(ctx, c, authorizeRequest) { - refresh, refreshSignature, err = c.RefreshTokenStrategy.GenerateRefreshToken(ctx, requester) - if err != nil { - return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) - } - } +func (v AuthorizeExplicitGrantAccessRequestValidator) CanHandleRequest(requester fosite.AccessRequester) bool { + return requester.GetGrantTypes().ExactOne("authorization_code") +} - ctx, err = storage.MaybeBeginTx(ctx, c.CoreStorage) - if err != nil { - return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) - } - defer func() { - if err != nil { - if rollBackTxnErr := storage.MaybeRollbackTx(ctx, c.CoreStorage); rollBackTxnErr != nil { - err = errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebugf("error: %s; rollback error: %s", err, rollBackTxnErr)) - } - } - }() - - if err = c.CoreStorage.InvalidateAuthorizeCodeSession(ctx, signature); err != nil { - return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) - } else if err = c.CoreStorage.CreateAccessTokenSession(ctx, accessSignature, requester.Sanitize([]string{})); err != nil { - return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) - } else if refreshSignature != "" { - if err = c.CoreStorage.CreateRefreshTokenSession(ctx, refreshSignature, requester.Sanitize([]string{})); err != nil { - return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) - } +func (v AuthorizeExplicitGrantAccessRequestValidator) ValidateGrantTypes(requester fosite.AccessRequester) error { + if !requester.GetClient().GetGrantTypes().Has("authorization_code") { + return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant \"authorization_code\".")) } - responder.SetAccessToken(access) - responder.SetTokenType("bearer") - atLifespan := fosite.GetEffectiveLifespan(requester.GetClient(), fosite.GrantTypeAuthorizationCode, fosite.AccessToken, c.Config.GetAccessTokenLifespan(ctx)) - responder.SetExpiresIn(getExpiresIn(requester, fosite.AccessToken, atLifespan, time.Now().UTC())) - responder.SetScopes(requester.GetGrantedScopes()) - if refresh != "" { - responder.SetExtra("refresh_token", refresh) - } + return nil +} - if err = storage.MaybeCommitTx(ctx, c.CoreStorage); err != nil { - return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) +func (v AuthorizeExplicitGrantAccessRequestValidator) ValidateRedirectURI(accessRequester fosite.AccessRequester, authorizeRequester fosite.Requester) error { + forcedRedirectURI := authorizeRequester.GetRequestForm().Get("redirect_uri") + requestedRedirectURI := accessRequester.GetRequestForm().Get("redirect_uri") + if forcedRedirectURI != "" && forcedRedirectURI != requestedRedirectURI { + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The \"redirect_uri\" from this request does not match the one from the authorize request.")) } return nil } -func (c *AuthorizeExplicitGrantHandler) CanSkipClientAuth(ctx context.Context, requester fosite.AccessRequester) bool { - return false +type AuthorizeExplicitTokenEndpointHandler struct { + GenericCodeTokenEndpointHandler } -func (c *AuthorizeExplicitGrantHandler) CanHandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) bool { - // grant_type REQUIRED. - // Value MUST be set to "authorization_code" - return requester.GetGrantTypes().ExactOne("authorization_code") -} +var ( + _ AccessRequestValidator = (*AuthorizeExplicitGrantAccessRequestValidator)(nil) + _ CodeHandler = (*AuthorizeCodeHandler)(nil) + _ SessionHandler = (*AuthorizeExplicitGrantSessionHandler)(nil) + _ fosite.TokenEndpointHandler = (*AuthorizeExplicitTokenEndpointHandler)(nil) +) diff --git a/handler/oauth2/flow_authorize_code_token_test.go b/handler/oauth2/flow_authorize_code_token_test.go index bd854fbba..59d89ab36 100644 --- a/handler/oauth2/flow_authorize_code_token_test.go +++ b/handler/oauth2/flow_authorize_code_token_test.go @@ -7,16 +7,14 @@ import ( "context" "fmt" "net/url" - "testing" //"time" - - //"github.com/golang/mock/gomock" + "testing" "time" "github.com/golang/mock/gomock" "github.com/ory/fosite/internal" - "github.com/ory/fosite" //"github.com/ory/fosite/internal" + "github.com/ory/fosite" "github.com/ory/fosite/storage" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -30,7 +28,7 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { t.Run("strategy="+k, func(t *testing.T) { store := storage.NewMemoryStore() - var h AuthorizeExplicitGrantHandler + var h GenericCodeTokenEndpointHandler for _, c := range []struct { areq *fosite.AccessRequest description string @@ -209,12 +207,18 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { AccessTokenLifespan: time.Minute, RefreshTokenScopes: []string{"offline"}, } - h = AuthorizeExplicitGrantHandler{ - CoreStorage: store, - AuthorizeCodeStrategy: strategy, - AccessTokenStrategy: strategy, - RefreshTokenStrategy: strategy, - Config: config, + h = GenericCodeTokenEndpointHandler{ + AccessRequestValidator: &AuthorizeExplicitGrantAccessRequestValidator{}, + CodeHandler: &AuthorizeCodeHandler{ + AuthorizeCodeStrategy: strategy, + }, + SessionHandler: &AuthorizeExplicitGrantSessionHandler{ + AuthorizeCodeStorage: store, + }, + AccessTokenStrategy: strategy, + RefreshTokenStrategy: strategy, + CoreStorage: store, + Config: config, } if c.setup != nil { @@ -245,16 +249,21 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { } { t.Run("strategy="+k, func(t *testing.T) { store := storage.NewMemoryStore() - - h := AuthorizeExplicitGrantHandler{ - CoreStorage: store, - AuthorizeCodeStrategy: &hmacshaStrategy, - TokenRevocationStorage: store, - Config: &fosite.Config{ - ScopeStrategy: fosite.HierarchicScopeStrategy, - AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, - AuthorizeCodeLifespan: time.Minute, + config := &fosite.Config{ + ScopeStrategy: fosite.HierarchicScopeStrategy, + AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, + AuthorizeCodeLifespan: time.Minute, + } + h := GenericCodeTokenEndpointHandler{ + AccessRequestValidator: &AuthorizeExplicitGrantAccessRequestValidator{}, + CodeHandler: &AuthorizeCodeHandler{ + AuthorizeCodeStrategy: strategy, + }, + SessionHandler: &AuthorizeExplicitGrantSessionHandler{ + AuthorizeCodeStorage: store, }, + TokenRevocationStorage: store, + Config: config, } for i, c := range []struct { areq *fosite.AccessRequest @@ -445,6 +454,7 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { var mockTransactional *internal.MockTransactional var mockCoreStore *internal.MockCoreStorage + var mockAuthorizeStore *internal.MockAuthorizeCodeStorage strategy := hmacshaStrategy request := &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, @@ -469,6 +479,11 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { CoreStorage } + type authorizeTransactionalStore struct { + storage.Transactional + AuthorizeCodeStorage + } + for _, testCase := range []struct { description string setup func() @@ -477,7 +492,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { { description: "transaction should be committed successfully if no errors occur", setup: func() { - mockCoreStore. + mockAuthorizeStore. EXPECT(). GetAuthorizeCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). Return(request, nil). @@ -486,7 +501,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { EXPECT(). BeginTX(propagatedContext). Return(propagatedContext, nil) - mockCoreStore. + mockAuthorizeStore. EXPECT(). InvalidateAuthorizeCodeSession(gomock.Any(), gomock.Any()). Return(nil). @@ -511,7 +526,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { { description: "transaction should be rolled back if `InvalidateAuthorizeCodeSession` returns an error", setup: func() { - mockCoreStore. + mockAuthorizeStore. EXPECT(). GetAuthorizeCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). Return(request, nil). @@ -520,7 +535,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { EXPECT(). BeginTX(propagatedContext). Return(propagatedContext, nil) - mockCoreStore. + mockAuthorizeStore. EXPECT(). InvalidateAuthorizeCodeSession(gomock.Any(), gomock.Any()). Return(errors.New("Whoops, a nasty database error occurred!")). @@ -536,7 +551,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { { description: "transaction should be rolled back if `CreateAccessTokenSession` returns an error", setup: func() { - mockCoreStore. + mockAuthorizeStore. EXPECT(). GetAuthorizeCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). Return(request, nil). @@ -545,7 +560,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { EXPECT(). BeginTX(propagatedContext). Return(propagatedContext, nil) - mockCoreStore. + mockAuthorizeStore. EXPECT(). InvalidateAuthorizeCodeSession(gomock.Any(), gomock.Any()). Return(nil). @@ -566,7 +581,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { { description: "should result in a server error if transaction cannot be created", setup: func() { - mockCoreStore. + mockAuthorizeStore. EXPECT(). GetAuthorizeCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). Return(request, nil). @@ -581,7 +596,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { { description: "should result in a server error if transaction cannot be rolled back", setup: func() { - mockCoreStore. + mockAuthorizeStore. EXPECT(). GetAuthorizeCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). Return(request, nil). @@ -590,7 +605,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { EXPECT(). BeginTX(propagatedContext). Return(propagatedContext, nil) - mockCoreStore. + mockAuthorizeStore. EXPECT(). InvalidateAuthorizeCodeSession(gomock.Any(), gomock.Any()). Return(errors.New("Whoops, a nasty database error occurred!")). @@ -606,7 +621,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { { description: "should result in a server error if transaction cannot be committed", setup: func() { - mockCoreStore. + mockAuthorizeStore. EXPECT(). GetAuthorizeCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). Return(request, nil). @@ -615,7 +630,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { EXPECT(). BeginTX(propagatedContext). Return(propagatedContext, nil) - mockCoreStore. + mockAuthorizeStore. EXPECT(). InvalidateAuthorizeCodeSession(gomock.Any(), gomock.Any()). Return(nil). @@ -650,21 +665,32 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockTransactional = internal.NewMockTransactional(ctrl) mockCoreStore = internal.NewMockCoreStorage(ctrl) + mockAuthorizeStore = internal.NewMockAuthorizeCodeStorage(ctrl) testCase.setup() - handler := AuthorizeExplicitGrantHandler{ + config := &fosite.Config{ + ScopeStrategy: fosite.HierarchicScopeStrategy, + AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, + AuthorizeCodeLifespan: time.Minute, + } + handler := GenericCodeTokenEndpointHandler{ + AccessRequestValidator: &AuthorizeExplicitGrantAccessRequestValidator{}, + CodeHandler: &AuthorizeCodeHandler{ + AuthorizeCodeStrategy: &strategy, + }, + SessionHandler: &AuthorizeExplicitGrantSessionHandler{ + AuthorizeCodeStorage: authorizeTransactionalStore{ + mockTransactional, + mockAuthorizeStore, + }, + }, CoreStorage: transactionalStore{ mockTransactional, mockCoreStore, }, - AccessTokenStrategy: &strategy, - RefreshTokenStrategy: &strategy, - AuthorizeCodeStrategy: &strategy, - Config: &fosite.Config{ - ScopeStrategy: fosite.HierarchicScopeStrategy, - AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, - AuthorizeCodeLifespan: time.Minute, - }, + AccessTokenStrategy: &strategy, + RefreshTokenStrategy: &strategy, + Config: config, } if err := handler.PopulateTokenEndpointResponse(propagatedContext, request, response); testCase.expectError != nil { diff --git a/handler/oauth2/flow_generic_code_token.go b/handler/oauth2/flow_generic_code_token.go new file mode 100644 index 000000000..39c40422c --- /dev/null +++ b/handler/oauth2/flow_generic_code_token.go @@ -0,0 +1,254 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package oauth2 + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/pkg/errors" + + "github.com/ory/fosite/storage" + + "github.com/ory/x/errorsx" + + "github.com/ory/fosite" +) + +// AccessRequestValidator handles various validations in the access request handling. +type AccessRequestValidator interface { + // CanHandleRequest validates if the access request should be handled. + CanHandleRequest(requester fosite.AccessRequester) bool + + // ValidateGrantTypes validates the grant types in the access request. + ValidateGrantTypes(requester fosite.AccessRequester) error + + // ValidateRedirectURI validates the redirect uri in the access request. + ValidateRedirectURI(accessRequester fosite.AccessRequester, authorizeRequester fosite.Requester) error +} + +// CodeHandler handles authorization/device code related operations. +type CodeHandler interface { + // Code fetches the code and code signature. + Code(ctx context.Context, requester fosite.AccessRequester) (code string, signature string, err error) + + // ValidateCode validates the code. + ValidateCode(ctx context.Context, requester fosite.AccessRequester, code string) error +} + +// SessionHandler handles session-related operations. +type SessionHandler interface { + // Session fetches the authorized request. + Session(ctx context.Context, requester fosite.AccessRequester, codeSignature string) (fosite.Requester, error) + + // InvalidateSession invalidates the code and session. + InvalidateSession(ctx context.Context, codeSignature string) error +} + +// GenericCodeTokenEndpointHandler is a token response handler for +// - the Authorize Code grant using the explicit grant type as defined in https://tools.ietf.org/html/rfc6749#section-4.1 +// - the Device Authorization Grant as defined in https://www.rfc-editor.org/rfc/rfc8628 +type GenericCodeTokenEndpointHandler struct { + AccessRequestValidator + CodeHandler + SessionHandler + + AccessTokenStrategy AccessTokenStrategy + RefreshTokenStrategy RefreshTokenStrategy + CoreStorage CoreStorage + TokenRevocationStorage TokenRevocationStorage + Config interface { + fosite.AccessTokenLifespanProvider + fosite.RefreshTokenLifespanProvider + fosite.RefreshTokenScopesProvider + } +} + +func (c *GenericCodeTokenEndpointHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error { + if !c.CanHandleTokenEndpointRequest(ctx, requester) { + return errorsx.WithStack(fosite.ErrUnknownRequest) + } + + var code, signature string + var err error + if code, signature, err = c.Code(ctx, requester); err != nil { + return err + } + + var ar fosite.Requester + if ar, err = c.Session(ctx, requester, signature); err != nil { + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + + if err = c.ValidateCode(ctx, requester, code); err != nil { + return errorsx.WithStack(fosite.ErrInvalidRequest.WithWrap(err).WithDebug(err.Error())) + } + + for _, scope := range ar.GetRequestedScopes() { + requester.GrantScope(scope) + } + + for _, audience := range ar.GetGrantedAudience() { + requester.GrantAudience(audience) + } + + var accessToken, accessTokenSignature string + accessToken, accessTokenSignature, err = c.AccessTokenStrategy.GenerateAccessToken(ctx, requester) + if err != nil { + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + + var refreshToken, refreshTokenSignature string + if c.canIssueRefreshToken(ctx, ar) { + refreshToken, refreshTokenSignature, err = c.RefreshTokenStrategy.GenerateRefreshToken(ctx, requester) + if err != nil { + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + } + + ctx, err = storage.MaybeBeginTx(ctx, c.CoreStorage) + if err != nil { + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + defer func() { + if err != nil { + if rollBackTxnErr := storage.MaybeRollbackTx(ctx, c.CoreStorage); rollBackTxnErr != nil { + err = errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebugf("error: %s; rollback error: %s", err, rollBackTxnErr)) + } + } + }() + + if err = c.InvalidateSession(ctx, signature); err != nil { + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + + if err = c.CoreStorage.CreateAccessTokenSession(ctx, accessTokenSignature, requester.Sanitize([]string{})); err != nil { + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + + if refreshTokenSignature != "" { + if err = c.CoreStorage.CreateRefreshTokenSession(ctx, refreshTokenSignature, requester.Sanitize([]string{})); err != nil { + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + } + + lifeSpan := fosite.GetEffectiveLifespan(requester.GetClient(), fosite.GrantTypeAuthorizationCode, fosite.AccessToken, c.Config.GetAccessTokenLifespan(ctx)) + responder.SetAccessToken(accessToken) + responder.SetTokenType("bearer") + responder.SetExpiresIn(getExpiresIn(requester, fosite.AccessToken, lifeSpan, time.Now().UTC())) + responder.SetScopes(requester.GetGrantedScopes()) + if refreshToken != "" { + responder.SetExtra("refresh_token", refreshToken) + } + + if err = storage.MaybeCommitTx(ctx, c.CoreStorage); err != nil { + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + + return nil +} + +func (c *GenericCodeTokenEndpointHandler) HandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) error { + if !c.CanHandleTokenEndpointRequest(ctx, requester) { + return errorsx.WithStack(errorsx.WithStack(fosite.ErrUnknownRequest)) + } + + var err error + if err = c.ValidateGrantTypes(requester); err != nil { + return err + } + + var code, signature string + if code, signature, err = c.Code(ctx, requester); err != nil { + return err + } + + var ar fosite.Requester + if ar, err = c.Session(ctx, requester, signature); err != nil { + if ar != nil && (errors.Is(err, fosite.ErrInvalidatedAuthorizeCode) || errors.Is(err, fosite.ErrInvalidatedDeviceCode)) { + return c.revokeTokens(ctx, requester.GetID()) + } + + return err + } + + if err = c.ValidateCode(ctx, requester, code); err != nil { + return errorsx.WithStack(err) + } + + // Override scopes + requester.SetRequestedScopes(ar.GetRequestedScopes()) + + // Override audiences + requester.SetRequestedAudience(ar.GetRequestedAudience()) + + // The authorization server MUST ensure that + // the authorization code was issued to the authenticated confidential client, + // or if the client is public, ensure that the code was issued to "client_id" in the request + if ar.GetClient().GetID() != requester.GetClient().GetID() { + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client ID from this request does not match the one from the authorize request.")) + } + + if err = c.ValidateRedirectURI(requester, ar); err != nil { + return err + } + + // Checking of POST client_id skipped, because + // if the client type is confidential or the client was issued client credentials (or assigned other authentication requirements), + // the client MUST authenticate with the authorization server as described in Section 3.2.1. + requester.SetSession(ar.GetSession()) + requester.SetID(ar.GetID()) + + atLifespan := fosite.GetEffectiveLifespan(requester.GetClient(), fosite.GrantTypeAuthorizationCode, fosite.AccessToken, c.Config.GetAccessTokenLifespan(ctx)) + requester.GetSession().SetExpiresAt(fosite.AccessToken, time.Now().UTC().Add(atLifespan).Round(time.Second)) + + rtLifespan := fosite.GetEffectiveLifespan(requester.GetClient(), fosite.GrantTypeAuthorizationCode, fosite.RefreshToken, c.Config.GetRefreshTokenLifespan(ctx)) + if rtLifespan > -1 { + requester.GetSession().SetExpiresAt(fosite.RefreshToken, time.Now().UTC().Add(rtLifespan).Round(time.Second)) + } + + return nil +} + +func (c *GenericCodeTokenEndpointHandler) CanSkipClientAuth(ctx context.Context, requester fosite.AccessRequester) bool { + return false +} + +func (c *GenericCodeTokenEndpointHandler) CanHandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) bool { + return c.CanHandleRequest(requester) +} + +func (c *GenericCodeTokenEndpointHandler) canIssueRefreshToken(ctx context.Context, requester fosite.Requester) bool { + scope := c.Config.GetRefreshTokenScopes(ctx) + if len(scope) > 0 && !requester.GetGrantedScopes().HasOneOf(scope...) { + return false + } + + if !requester.GetClient().GetGrantTypes().Has("refresh_token") { + return false + } + + return true +} + +func (c *GenericCodeTokenEndpointHandler) revokeTokens(ctx context.Context, reqId string) error { + hint := "The authorization code has already been used." + var debug strings.Builder + + revokeAndAppendErr := func(tokenType string, revokeFunc func(context.Context, string) error) { + if err := revokeFunc(ctx, reqId); err != nil { + hint += fmt.Sprintf(" Additionally, an error occurred during processing the %s token revocation.", tokenType) + debug.WriteString(fmt.Sprintf("Revocation of %s token lead to error %s.", tokenType, err.Error())) + } + } + + revokeAndAppendErr("access", c.TokenRevocationStorage.RevokeAccessToken) + revokeAndAppendErr("refresh", c.TokenRevocationStorage.RevokeRefreshToken) + + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint(hint).WithDebug(debug.String())) +} + +var _ fosite.TokenEndpointHandler = (*GenericCodeTokenEndpointHandler)(nil) diff --git a/handler/oauth2/storage.go b/handler/oauth2/storage.go index cc0b95831..5852cc174 100644 --- a/handler/oauth2/storage.go +++ b/handler/oauth2/storage.go @@ -10,14 +10,13 @@ import ( ) type CoreStorage interface { - AuthorizeCodeStorage AccessTokenStorage RefreshTokenStorage } // AuthorizeCodeStorage handles storage requests related to authorization codes. type AuthorizeCodeStorage interface { - // GetAuthorizeCodeSession stores the authorization request for a given authorization code. + // CreateAuthorizeCodeSession stores the authorization request for a given authorization code. CreateAuthorizeCodeSession(ctx context.Context, code string, request fosite.Requester) (err error) // GetAuthorizeCodeSession hydrates the session based on the given code and returns the authorization request. diff --git a/handler/openid/flow_hybrid.go b/handler/openid/flow_hybrid.go index 925070a18..9d09894f4 100644 --- a/handler/openid/flow_hybrid.go +++ b/handler/openid/flow_hybrid.go @@ -16,7 +16,7 @@ import ( type OpenIDConnectHybridHandler struct { AuthorizeImplicitGrantTypeHandler *oauth2.AuthorizeImplicitGrantTypeHandler - AuthorizeExplicitGrantHandler *oauth2.AuthorizeExplicitGrantHandler + AuthorizeExplicitGrantAuthHandler *oauth2.AuthorizeExplicitGrantAuthHandler IDTokenHandleHelper *IDTokenHandleHelper OpenIDConnectRequestValidator *OpenIDConnectRequestValidator OpenIDConnectRequestStorage OpenIDConnectRequestStorage @@ -85,7 +85,7 @@ func (c *OpenIDConnectHybridHandler) HandleAuthorizeEndpointRequest(ctx context. return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant 'authorization_code'.")) } - code, signature, err := c.AuthorizeExplicitGrantHandler.AuthorizeCodeStrategy.GenerateAuthorizeCode(ctx, ar) + code, signature, err := c.AuthorizeExplicitGrantAuthHandler.AuthorizeCodeStrategy.GenerateAuthorizeCode(ctx, ar) if err != nil { return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } @@ -98,8 +98,8 @@ func (c *OpenIDConnectHybridHandler) HandleAuthorizeEndpointRequest(ctx context. // } // This is required because we must limit the authorize code lifespan. - ar.GetSession().SetExpiresAt(fosite.AuthorizeCode, time.Now().UTC().Add(c.AuthorizeExplicitGrantHandler.Config.GetAuthorizeCodeLifespan(ctx)).Round(time.Second)) - if err := c.AuthorizeExplicitGrantHandler.CoreStorage.CreateAuthorizeCodeSession(ctx, signature, ar.Sanitize(c.AuthorizeExplicitGrantHandler.GetSanitationWhiteList(ctx))); err != nil { + ar.GetSession().SetExpiresAt(fosite.AuthorizeCode, time.Now().UTC().Add(c.AuthorizeExplicitGrantAuthHandler.Config.GetAuthorizeCodeLifespan(ctx)).Round(time.Second)) + if err := c.AuthorizeExplicitGrantAuthHandler.AuthorizeCodeStorage.CreateAuthorizeCodeSession(ctx, signature, ar.Sanitize(c.AuthorizeExplicitGrantAuthHandler.GetSanitationWhiteList(ctx))); err != nil { return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } diff --git a/handler/openid/flow_hybrid_test.go b/handler/openid/flow_hybrid_test.go index 3412675aa..fc4e9b754 100644 --- a/handler/openid/flow_hybrid_test.go +++ b/handler/openid/flow_hybrid_test.go @@ -35,7 +35,7 @@ var hmacStrategy = &oauth2.HMACSHAStrategy{ } func makeOpenIDConnectHybridHandler(minParameterEntropy int) OpenIDConnectHybridHandler { - var idStrategy = &DefaultStrategy{ + idStrategy := &DefaultStrategy{ Signer: &jwt.DefaultSigner{ GetPrivateKey: func(_ context.Context) (interface{}, error) { return gen.MustRSAKey(), nil @@ -46,7 +46,7 @@ func makeOpenIDConnectHybridHandler(minParameterEntropy int) OpenIDConnectHybrid }, } - var j = &DefaultStrategy{ + j := &DefaultStrategy{ Signer: &jwt.DefaultSigner{ GetPrivateKey: func(_ context.Context) (interface{}, error) { return key, nil @@ -64,11 +64,11 @@ func makeOpenIDConnectHybridHandler(minParameterEntropy int) OpenIDConnectHybrid AuthorizeCodeLifespan: time.Hour, RefreshTokenLifespan: time.Hour, } + store := storage.NewMemoryStore() return OpenIDConnectHybridHandler{ - AuthorizeExplicitGrantHandler: &oauth2.AuthorizeExplicitGrantHandler{ + AuthorizeExplicitGrantAuthHandler: &oauth2.AuthorizeExplicitGrantAuthHandler{ AuthorizeCodeStrategy: hmacStrategy, - AccessTokenStrategy: hmacStrategy, - CoreStorage: storage.NewMemoryStore(), + AuthorizeCodeStorage: store, Config: config, }, AuthorizeImplicitGrantTypeHandler: &oauth2.AuthorizeImplicitGrantTypeHandler{ diff --git a/handler/rfc8628/storage.go b/handler/rfc8628/storage.go index 17571ab18..8ae1b35eb 100644 --- a/handler/rfc8628/storage.go +++ b/handler/rfc8628/storage.go @@ -30,7 +30,7 @@ type DeviceCodeStorage interface { // Make sure to also return the fosite.Requester value when returning the fosite.ErrInvalidatedDeviceCode error! GetDeviceCodeSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) - // InvalidateDeviceCodeSession is called when an device code is being used. The state of the user + // InvalidateDeviceCodeSession is called when a device code is being used. The state of the user // code should be set to invalid and consecutive requests to GetDeviceCodeSession should return the // ErrInvalidatedDeviceCode error. InvalidateDeviceCodeSession(ctx context.Context, signature string) (err error) @@ -48,7 +48,7 @@ type UserCodeStorage interface { // Make sure to also return the fosite.Requester value when returning the fosite.ErrInvalidatedUserCode error! GetUserCodeSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) - // InvalidateUserCodeSession is called when an user code is being used. The state of the user + // InvalidateUserCodeSession is called when a user code is being used. The state of the user // code should be set to invalid and consecutive requests to GetUserCodeSession should return the // ErrInvalidatedUserCode error. InvalidateUserCodeSession(ctx context.Context, signature string) (err error) diff --git a/handler/rfc8628/strategy_hmacsha.go b/handler/rfc8628/strategy_hmacsha.go index 6f8068c1e..04395b009 100644 --- a/handler/rfc8628/strategy_hmacsha.go +++ b/handler/rfc8628/strategy_hmacsha.go @@ -5,6 +5,10 @@ package rfc8628 import ( "context" + "strings" + "time" + + "github.com/ory/x/errorsx" "github.com/ory/x/randx" "github.com/patrickmn/go-cache" @@ -47,7 +51,13 @@ func (h *DefaultDeviceStrategy) UserCodeSignature(ctx context.Context, token str // ValidateUserCode validates a user_code func (h *DefaultDeviceStrategy) ValidateUserCode(ctx context.Context, r fosite.Requester, code string) error { - // TODO + exp := r.GetSession().GetExpiresAt(fosite.UserCode) + if exp.IsZero() && r.GetRequestedAt().Add(h.Config.GetDeviceAndUserCodeLifespan(ctx)).Before(time.Now().UTC()) { + return errorsx.WithStack(fosite.ErrDeviceExpiredToken.WithHintf("User code expired at '%s'.", r.GetRequestedAt().Add(h.Config.GetDeviceAndUserCodeLifespan(ctx)))) + } + if !exp.IsZero() && exp.Before(time.Now().UTC()) { + return errorsx.WithStack(fosite.ErrDeviceExpiredToken.WithHintf("User code expired at '%s'.", exp)) + } return nil } @@ -68,25 +78,33 @@ func (h *DefaultDeviceStrategy) DeviceCodeSignature(ctx context.Context, token s // ValidateDeviceCode validates a device_code func (h *DefaultDeviceStrategy) ValidateDeviceCode(ctx context.Context, r fosite.Requester, code string) error { - // TODO - return nil + exp := r.GetSession().GetExpiresAt(fosite.DeviceCode) + if exp.IsZero() && r.GetRequestedAt().Add(h.Config.GetDeviceAndUserCodeLifespan(ctx)).Before(time.Now().UTC()) { + return errorsx.WithStack(fosite.ErrDeviceExpiredToken.WithHintf("Device code expired at '%s'.", r.GetRequestedAt().Add(h.Config.GetDeviceAndUserCodeLifespan(ctx)))) + } + + if !exp.IsZero() && exp.Before(time.Now().UTC()) { + return errorsx.WithStack(fosite.ErrDeviceExpiredToken.WithHintf("Device code expired at '%s'.", exp)) + } + + return h.Enigma.Validate(ctx, strings.TrimPrefix(code, "ory_dc_")) } -// ShouldRateLimit is used to decide whether a request should be rate-limites -func (t *DefaultDeviceStrategy) ShouldRateLimit(context context.Context, code string) bool { +// ShouldRateLimit is used to decide whether a request should be rate-limited +func (h *DefaultDeviceStrategy) ShouldRateLimit(context context.Context, code string) bool { key := code + "_limiter" - if x, found := t.RateLimiterCache.Get(key); found { + if x, found := h.RateLimiterCache.Get(key); found { return !x.(*rate.Limiter).Allow() } rateLimiter := rate.NewLimiter( rate.Every( - t.Config.GetDeviceAuthTokenPollingInterval(context), + h.Config.GetDeviceAuthTokenPollingInterval(context), ), 1, ) - t.RateLimiterCache.Set(key, rateLimiter, cache.DefaultExpiration) + h.RateLimiterCache.Set(key, rateLimiter, cache.DefaultExpiration) return false } diff --git a/handler/rfc8628/token_handler.go b/handler/rfc8628/token_handler.go new file mode 100644 index 000000000..3e0c3c7d8 --- /dev/null +++ b/handler/rfc8628/token_handler.go @@ -0,0 +1,100 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package rfc8628 + +import ( + "context" + + "github.com/pkg/errors" + + "github.com/ory/x/errorsx" + + "github.com/ory/fosite" + "github.com/ory/fosite/handler/oauth2" +) + +type DeviceCodeHandler struct { + DeviceRateLimitStrategy DeviceRateLimitStrategy + DeviceCodeStrategy DeviceCodeStrategy +} + +func (c DeviceCodeHandler) Code(ctx context.Context, requester fosite.AccessRequester) (code string, signature string, err error) { + code = requester.GetRequestForm().Get("device_code") + + if c.DeviceRateLimitStrategy.ShouldRateLimit(ctx, code) { + return "", "", errorsx.WithStack(fosite.ErrPollingRateLimited) + } + + signature, err = c.DeviceCodeStrategy.DeviceCodeSignature(ctx, code) + if err != nil { + return "", "", errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + + return +} + +func (c DeviceCodeHandler) ValidateCode(ctx context.Context, requester fosite.AccessRequester, code string) error { + return c.DeviceCodeStrategy.ValidateDeviceCode(ctx, requester, code) +} + +type DeviceSessionHandler struct { + DeviceCodeStorage DeviceCodeStorage +} + +func (s DeviceSessionHandler) Session(ctx context.Context, requester fosite.AccessRequester, codeSignature string) (fosite.Requester, error) { + req, err := s.DeviceCodeStorage.GetDeviceCodeSession(ctx, codeSignature, requester.GetSession()) + + if err != nil && errors.Is(err, fosite.ErrInvalidatedDeviceCode) { + if req != nil { + return req, err + } + + return req, fosite.ErrServerError. + WithHint("Misconfigured code lead to an error that prohibited the OAuth 2.0 Framework from processing this request."). + WithDebug("\"GetDeviceCodeSession\" must return a value for \"fosite.Requester\" when returning \"ErrInvalidatedDeviceCode\".") + } + + if err != nil && errors.Is(err, fosite.ErrNotFound) { + return nil, errorsx.WithStack(fosite.ErrInvalidGrant.WithWrap(err).WithDebug(err.Error())) + } + + if err != nil { + return nil, errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) + } + + return req, err +} + +func (s DeviceSessionHandler) InvalidateSession(ctx context.Context, codeSignature string) error { + return s.DeviceCodeStorage.InvalidateDeviceCodeSession(ctx, codeSignature) +} + +type DeviceAccessRequestValidator struct{} + +func (v DeviceAccessRequestValidator) CanHandleRequest(requester fosite.AccessRequester) bool { + return requester.GetGrantTypes().ExactOne(string(fosite.GrantTypeDeviceCode)) +} + +func (v DeviceAccessRequestValidator) ValidateGrantTypes(requester fosite.AccessRequester) error { + if !requester.GetClient().GetGrantTypes().Has(string(fosite.GrantTypeDeviceCode)) { + return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant \"urn:ietf:params:oauth:grant-type:device_code\".")) + } + + return nil +} + +func (v DeviceAccessRequestValidator) ValidateRedirectURI(accessRequester fosite.AccessRequester, authorizeRequester fosite.Requester) error { + return nil +} + +type DeviceCodeTokenEndpointHandler struct { + oauth2.GenericCodeTokenEndpointHandler +} + +var ( + _ oauth2.AccessRequestValidator = (*DeviceAccessRequestValidator)(nil) + _ oauth2.CodeHandler = (*DeviceCodeHandler)(nil) + _ oauth2.SessionHandler = (*DeviceSessionHandler)(nil) + _ fosite.TokenEndpointHandler = (*DeviceCodeTokenEndpointHandler)(nil) +) diff --git a/handler/rfc8628/token_handler_test.go b/handler/rfc8628/token_handler_test.go new file mode 100644 index 000000000..9be2d4d06 --- /dev/null +++ b/handler/rfc8628/token_handler_test.go @@ -0,0 +1,694 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package rfc8628 + +import ( + "context" + "fmt" + "net/url" + "testing" + "time" + + "github.com/pkg/errors" + + "github.com/golang/mock/gomock" + "github.com/ory/fosite/internal" + + "github.com/patrickmn/go-cache" + + "github.com/ory/fosite/handler/oauth2" + "github.com/ory/fosite/token/hmac" + + "github.com/ory/fosite" + "github.com/ory/fosite/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var hmacshaStrategy = oauth2.HMACSHAStrategy{ + Enigma: &hmac.HMACStrategy{Config: &fosite.Config{GlobalSecret: []byte("foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar")}}, + Config: &fosite.Config{ + AccessTokenLifespan: time.Hour * 24, + AuthorizeCodeLifespan: time.Hour * 24, + }, +} + +var RFC8628HMACSHAStrategy = DefaultDeviceStrategy{ + Enigma: &hmac.HMACStrategy{Config: &fosite.Config{GlobalSecret: []byte("foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar")}}, + RateLimiterCache: cache.New( + time.Hour*12, + time.Hour*24, + ), + Config: &fosite.Config{ + DeviceAndUserCodeLifespan: time.Hour * 24, + }, +} + +func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { + for k, strategy := range map[string]struct { + oauth2.CoreStrategy + RFC8628CodeStrategy + }{ + "hmac": {&hmacshaStrategy, &RFC8628HMACSHAStrategy}, + } { + t.Run("strategy="+k, func(t *testing.T) { + store := storage.NewMemoryStore() + + var h oauth2.GenericCodeTokenEndpointHandler + for _, c := range []struct { + areq *fosite.AccessRequest + description string + setup func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) + check func(t *testing.T, aresp *fosite.AccessResponse) + expectErr error + }{ + { + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"123"}, + }, + description: "should fail because not responsible", + expectErr: fosite.ErrUnknownRequest, + }, + { + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + }, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + description: "should fail because device code not found", + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + code, _, err := strategy.GenerateDeviceCode(context.TODO()) + require.NoError(t, err) + areq.Form.Set("device_code", code) + }, + expectErr: fosite.ErrServerError, + }, + { + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code", "refresh_token"}, + }, + GrantedScope: fosite.Arguments{"foo", "offline"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + code, sig, err := strategy.GenerateDeviceCode(context.TODO()) + require.NoError(t, err) + areq.Form.Add("device_code", code) + + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), sig, areq)) + }, + description: "should pass with offline scope and refresh token", + check: func(t *testing.T, aresp *fosite.AccessResponse) { + assert.NotEmpty(t, aresp.AccessToken) + assert.Equal(t, "bearer", aresp.TokenType) + assert.NotEmpty(t, aresp.GetExtra("refresh_token")) + assert.NotEmpty(t, aresp.GetExtra("expires_in")) + assert.Equal(t, "foo offline", aresp.GetExtra("scope")) + }, + }, + { + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code", "refresh_token"}, + }, + GrantedScope: fosite.Arguments{"foo"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + config.RefreshTokenScopes = []string{} + code, sig, err := strategy.GenerateDeviceCode(context.TODO()) + require.NoError(t, err) + areq.Form.Add("device_code", code) + + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), sig, areq)) + }, + description: "should pass with refresh token always provided", + check: func(t *testing.T, aresp *fosite.AccessResponse) { + assert.NotEmpty(t, aresp.AccessToken) + assert.Equal(t, "bearer", aresp.TokenType) + assert.NotEmpty(t, aresp.GetExtra("refresh_token")) + assert.NotEmpty(t, aresp.GetExtra("expires_in")) + assert.Equal(t, "foo", aresp.GetExtra("scope")) + }, + }, + { + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + }, + GrantedScope: fosite.Arguments{}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + config.RefreshTokenScopes = []string{} + code, sig, err := strategy.GenerateDeviceCode(context.TODO()) + require.NoError(t, err) + areq.Form.Add("device_code", code) + + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), sig, areq)) + }, + description: "should pass with no refresh token", + check: func(t *testing.T, aresp *fosite.AccessResponse) { + assert.NotEmpty(t, aresp.AccessToken) + assert.Equal(t, "bearer", aresp.TokenType) + assert.Empty(t, aresp.GetExtra("refresh_token")) + assert.NotEmpty(t, aresp.GetExtra("expires_in")) + assert.Empty(t, aresp.GetExtra("scope")) + }, + }, + { + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + }, + GrantedScope: fosite.Arguments{"foo"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + code, sig, err := strategy.GenerateDeviceCode(context.TODO()) + require.NoError(t, err) + areq.Form.Add("device_code", code) + + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), sig, areq)) + }, + description: "should not have refresh token", + check: func(t *testing.T, aresp *fosite.AccessResponse) { + assert.NotEmpty(t, aresp.AccessToken) + assert.Equal(t, "bearer", aresp.TokenType) + assert.Empty(t, aresp.GetExtra("refresh_token")) + assert.NotEmpty(t, aresp.GetExtra("expires_in")) + assert.Equal(t, "foo", aresp.GetExtra("scope")) + }, + }, + } { + t.Run("case="+c.description, func(t *testing.T) { + config := &fosite.Config{ + ScopeStrategy: fosite.HierarchicScopeStrategy, + AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, + AccessTokenLifespan: time.Minute, + RefreshTokenScopes: []string{"offline"}, + } + h = oauth2.GenericCodeTokenEndpointHandler{ + AccessRequestValidator: &DeviceAccessRequestValidator{}, + CodeHandler: &DeviceCodeHandler{ + DeviceRateLimitStrategy: strategy, + DeviceCodeStrategy: strategy, + }, + SessionHandler: &DeviceSessionHandler{ + DeviceCodeStorage: store, + }, + AccessTokenStrategy: strategy.CoreStrategy, + RefreshTokenStrategy: strategy.CoreStrategy, + Config: config, + CoreStorage: store, + TokenRevocationStorage: store, + } + + if c.setup != nil { + c.setup(t, c.areq, config) + } + + aresp := fosite.NewAccessResponse() + err := h.PopulateTokenEndpointResponse(context.TODO(), c.areq, aresp) + + if c.expectErr != nil { + require.EqualError(t, err, c.expectErr.Error(), "%+v", err) + } else { + require.NoError(t, err, "%+v", err) + } + + if c.check != nil { + c.check(t, aresp) + } + }) + } + }) + } +} + +func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { + for k, strategy := range map[string]struct { + oauth2.CoreStrategy + RFC8628CodeStrategy + }{ + "hmac": {&hmacshaStrategy, &RFC8628HMACSHAStrategy}, + } { + t.Run("strategy="+k, func(t *testing.T) { + store := storage.NewMemoryStore() + + h := oauth2.GenericCodeTokenEndpointHandler{ + AccessRequestValidator: &DeviceAccessRequestValidator{}, + CodeHandler: &DeviceCodeHandler{ + DeviceRateLimitStrategy: strategy, + DeviceCodeStrategy: strategy, + }, + SessionHandler: &DeviceSessionHandler{ + DeviceCodeStorage: store, + }, + CoreStorage: store, + AccessTokenStrategy: strategy.CoreStrategy, + RefreshTokenStrategy: strategy.CoreStrategy, + Config: &fosite.Config{ + ScopeStrategy: fosite.HierarchicScopeStrategy, + AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, + AuthorizeCodeLifespan: time.Minute, + }, + } + for i, c := range []struct { + areq *fosite.AccessRequest + authreq *fosite.DeviceRequest + description string + setup func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) + check func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) + expectErr error + }{ + { + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"12345678"}, + }, + description: "should fail because not responsible", + expectErr: fosite.ErrUnknownRequest, + }, + { + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{""}}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + description: "should fail because client is not granted this grant type", + expectErr: fosite.ErrUnauthorizedClient, + }, + { + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + Request: fosite.Request{ + Client: &fosite.DefaultClient{GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + description: "should fail because device code could not be retrieved", + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { + deviceCode, _, err := strategy.GenerateDeviceCode(context.TODO()) + require.NoError(t, err) + areq.Form = url.Values{"device_code": {deviceCode}} + }, + expectErr: fosite.ErrInvalidGrant, + }, + { + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + Request: fosite.Request{ + Form: url.Values{"device_code": {"AAAA"}}, + Client: &fosite.DefaultClient{GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + description: "should fail because device code validation failed", + expectErr: fosite.ErrInvalidGrant, + }, + { + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + authreq: &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "bar"}, + RequestedScope: fosite.Arguments{"a", "b"}, + }, + }, + description: "should fail because client mismatch", + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { + token, signature, err := strategy.GenerateDeviceCode(context.TODO()) + require.NoError(t, err) + areq.Form = url.Values{"device_code": {token}} + + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) + }, + expectErr: fosite.ErrInvalidGrant, + }, + { + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + authreq: &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, + Session: &fosite.DefaultSession{}, + RequestedScope: fosite.Arguments{"a", "b"}, + RequestedAt: time.Now().UTC(), + }, + }, + description: "should pass", + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { + token, signature, err := strategy.GenerateDeviceCode(context.TODO()) + require.NoError(t, err) + + areq.Form = url.Values{"device_code": {token}} + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) + }, + }, + { + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + }, + GrantedScope: fosite.Arguments{"foo", "offline"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + check: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { + assert.Equal(t, time.Now().Add(time.Minute).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.AccessToken)) + assert.Equal(t, time.Now().Add(time.Minute).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.RefreshToken)) + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { + code, sig, err := strategy.GenerateDeviceCode(context.TODO()) + require.NoError(t, err) + areq.Form.Add("device_code", code) + areq.GetSession().SetExpiresAt(fosite.DeviceCode, time.Now().Add(-time.Hour).UTC().Round(time.Second)) + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), sig, areq)) + }, + description: "should fail because device code has expired", + expectErr: fosite.ErrDeviceExpiredToken, + }, + } { + t.Run(fmt.Sprintf("case=%d/description=%s", i, c.description), func(t *testing.T) { + if c.setup != nil { + c.setup(t, c.areq, c.authreq) + } + + t.Logf("Processing %+v", c.areq.Client) + + err := h.HandleTokenEndpointRequest(context.Background(), c.areq) + if c.expectErr != nil { + require.EqualError(t, err, c.expectErr.Error(), "%+v", err) + } else { + require.NoError(t, err, "%+v", err) + if c.check != nil { + c.check(t, c.areq, c.authreq) + } + } + }) + } + }) + } +} + +func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { + var mockTransactional *internal.MockTransactional + var mockCoreStore *internal.MockCoreStorage + var mockDeviceCodeStore *internal.MockDeviceCodeStorage + var mockDeviceRateLimitStrategy *internal.MockDeviceRateLimitStrategy + strategy := hmacshaStrategy + deviceStrategy := RFC8628HMACSHAStrategy + request := &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + Request: fosite.Request{ + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code", "refresh_token"}, + }, + GrantedScope: fosite.Arguments{"offline"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + } + token, _, err := deviceStrategy.GenerateDeviceCode(context.Background()) + require.NoError(t, err) + request.Form = url.Values{"device_code": {token}} + response := fosite.NewAccessResponse() + propagatedContext := context.Background() + + // some storage implementation that has support for transactions, notice the embedded type `storage.Transactional` + type coreTransactionalStore struct { + storage.Transactional + oauth2.CoreStorage + } + + type deviceTransactionalStore struct { + storage.Transactional + DeviceCodeStorage + } + + for _, testCase := range []struct { + description string + setup func() + expectError error + }{ + { + description: "transaction should be committed successfully if no errors occur", + setup: func() { + mockDeviceCodeStore. + EXPECT(). + GetDeviceCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). + Return(request, nil). + Times(1) + mockTransactional. + EXPECT(). + BeginTX(propagatedContext). + Return(propagatedContext, nil) + mockDeviceCodeStore. + EXPECT(). + InvalidateDeviceCodeSession(gomock.Any(), gomock.Any()). + Return(nil). + Times(1) + mockCoreStore. + EXPECT(). + CreateAccessTokenSession(propagatedContext, gomock.Any(), gomock.Any()). + Return(nil). + Times(1) + mockCoreStore. + EXPECT(). + CreateRefreshTokenSession(propagatedContext, gomock.Any(), gomock.Any()). + Return(nil). + Times(1) + mockTransactional. + EXPECT(). + Commit(propagatedContext). + Return(nil). + Times(1) + }, + }, + { + description: "transaction should be rolled back if `InvalidateDeviceCodeSession` returns an error", + setup: func() { + mockDeviceCodeStore. + EXPECT(). + GetDeviceCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). + Return(request, nil). + Times(1) + mockTransactional. + EXPECT(). + BeginTX(propagatedContext). + Return(propagatedContext, nil) + mockDeviceCodeStore. + EXPECT(). + InvalidateDeviceCodeSession(gomock.Any(), gomock.Any()). + Return(errors.New("Whoops, a nasty database error occurred!")). + Times(1) + mockTransactional. + EXPECT(). + Rollback(propagatedContext). + Return(nil). + Times(1) + }, + expectError: fosite.ErrServerError, + }, + { + description: "transaction should be rolled back if `CreateAccessTokenSession` returns an error", + setup: func() { + mockDeviceCodeStore. + EXPECT(). + GetDeviceCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). + Return(request, nil). + Times(1) + mockTransactional. + EXPECT(). + BeginTX(propagatedContext). + Return(propagatedContext, nil) + mockDeviceCodeStore. + EXPECT(). + InvalidateDeviceCodeSession(gomock.Any(), gomock.Any()). + Return(nil). + Times(1) + mockCoreStore. + EXPECT(). + CreateAccessTokenSession(propagatedContext, gomock.Any(), gomock.Any()). + Return(errors.New("Whoops, a nasty database error occurred!")). + Times(1) + mockTransactional. + EXPECT(). + Rollback(propagatedContext). + Return(nil). + Times(1) + }, + expectError: fosite.ErrServerError, + }, + { + description: "should result in a server error if transaction cannot be created", + setup: func() { + mockDeviceCodeStore. + EXPECT(). + GetDeviceCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). + Return(request, nil). + Times(1) + mockTransactional. + EXPECT(). + BeginTX(propagatedContext). + Return(nil, errors.New("Whoops, unable to create transaction!")) + }, + expectError: fosite.ErrServerError, + }, + { + description: "should result in a server error if transaction cannot be rolled back", + setup: func() { + mockDeviceCodeStore. + EXPECT(). + GetDeviceCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). + Return(request, nil). + Times(1) + mockTransactional. + EXPECT(). + BeginTX(propagatedContext). + Return(propagatedContext, nil) + mockDeviceCodeStore. + EXPECT(). + InvalidateDeviceCodeSession(gomock.Any(), gomock.Any()). + Return(errors.New("Whoops, a nasty database error occurred!")). + Times(1) + mockTransactional. + EXPECT(). + Rollback(propagatedContext). + Return(errors.New("Whoops, unable to rollback transaction!")). + Times(1) + }, + expectError: fosite.ErrServerError, + }, + { + description: "should result in a server error if transaction cannot be committed", + setup: func() { + mockDeviceCodeStore. + EXPECT(). + GetDeviceCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). + Return(request, nil). + Times(1) + mockTransactional. + EXPECT(). + BeginTX(propagatedContext). + Return(propagatedContext, nil) + mockDeviceCodeStore. + EXPECT(). + InvalidateDeviceCodeSession(gomock.Any(), gomock.Any()). + Return(nil). + Times(1) + mockCoreStore. + EXPECT(). + CreateAccessTokenSession(propagatedContext, gomock.Any(), gomock.Any()). + Return(nil). + Times(1) + mockCoreStore. + EXPECT(). + CreateRefreshTokenSession(propagatedContext, gomock.Any(), gomock.Any()). + Return(nil). + Times(1) + mockTransactional. + EXPECT(). + Commit(propagatedContext). + Return(errors.New("Whoops, unable to commit transaction!")). + Times(1) + mockTransactional. + EXPECT(). + Rollback(propagatedContext). + Return(nil). + Times(1) + }, + expectError: fosite.ErrServerError, + }, + } { + t.Run(fmt.Sprintf("scenario=%s", testCase.description), func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockTransactional = internal.NewMockTransactional(ctrl) + mockCoreStore = internal.NewMockCoreStorage(ctrl) + mockDeviceCodeStore = internal.NewMockDeviceCodeStorage(ctrl) + mockDeviceRateLimitStrategy = internal.NewMockDeviceRateLimitStrategy(ctrl) + mockDeviceRateLimitStrategy.EXPECT().ShouldRateLimit(gomock.Any(), gomock.Any()).Return(false).Times(1) + testCase.setup() + + handler := oauth2.GenericCodeTokenEndpointHandler{ + AccessRequestValidator: &DeviceAccessRequestValidator{}, + CodeHandler: &DeviceCodeHandler{ + DeviceRateLimitStrategy: mockDeviceRateLimitStrategy, + DeviceCodeStrategy: &deviceStrategy, + }, + SessionHandler: &DeviceSessionHandler{ + DeviceCodeStorage: deviceTransactionalStore{ + mockTransactional, + mockDeviceCodeStore, + }, + }, + CoreStorage: coreTransactionalStore{ + mockTransactional, + mockCoreStore, + }, + AccessTokenStrategy: &strategy, + RefreshTokenStrategy: &strategy, + Config: &fosite.Config{ + ScopeStrategy: fosite.HierarchicScopeStrategy, + AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, + DeviceAndUserCodeLifespan: time.Minute, + }, + } + + if err = handler.PopulateTokenEndpointResponse(propagatedContext, request, response); testCase.expectError != nil { + assert.EqualError(t, err, testCase.expectError.Error()) + } + }) + } +} diff --git a/integration/authorize_code_grant_public_client_pkce_test.go b/integration/authorize_code_grant_public_client_pkce_test.go index 74426daed..14d852547 100644 --- a/integration/authorize_code_grant_public_client_pkce_test.go +++ b/integration/authorize_code_grant_public_client_pkce_test.go @@ -32,7 +32,7 @@ func runAuthorizeCodeGrantWithPublicClientAndPKCETest(t *testing.T, strategy int c := new(fosite.Config) c.EnforcePKCE = true c.EnablePKCEPlainChallengeMethod = true - f := compose.Compose(c, fositeStore, strategy, compose.OAuth2AuthorizeExplicitFactory, compose.OAuth2PKCEFactory, compose.OAuth2TokenIntrospectionFactory) + f := compose.Compose(c, fositeStore, strategy, compose.OAuth2AuthorizeExplicitAuthFactory, compose.Oauth2AuthorizeExplicitTokenFactory, compose.OAuth2PKCEFactory, compose.OAuth2TokenIntrospectionFactory) ts := mockServer(t, f, &fosite.DefaultSession{}) defer ts.Close() diff --git a/integration/authorize_code_grant_public_client_test.go b/integration/authorize_code_grant_public_client_test.go index 24d8d64bd..6a517365c 100644 --- a/integration/authorize_code_grant_public_client_test.go +++ b/integration/authorize_code_grant_public_client_test.go @@ -27,7 +27,7 @@ func TestAuthorizeCodeFlowWithPublicClient(t *testing.T) { } func runAuthorizeCodeGrantWithPublicClientTest(t *testing.T, strategy interface{}) { - f := compose.Compose(new(fosite.Config), fositeStore, strategy, compose.OAuth2AuthorizeExplicitFactory, compose.OAuth2TokenIntrospectionFactory) + f := compose.Compose(new(fosite.Config), fositeStore, strategy, compose.OAuth2AuthorizeExplicitAuthFactory, compose.Oauth2AuthorizeExplicitTokenFactory, compose.OAuth2TokenIntrospectionFactory) ts := mockServer(t, f, &fosite.DefaultSession{Subject: "foo-sub"}) defer ts.Close() diff --git a/integration/authorize_code_grant_test.go b/integration/authorize_code_grant_test.go index 13a663c7a..9ddb02cc8 100644 --- a/integration/authorize_code_grant_test.go +++ b/integration/authorize_code_grant_test.go @@ -38,7 +38,7 @@ func TestAuthorizeCodeFlowDupeCode(t *testing.T) { } func runAuthorizeCodeGrantTest(t *testing.T, strategy interface{}) { - f := compose.Compose(new(fosite.Config), fositeStore, strategy, compose.OAuth2AuthorizeExplicitFactory, compose.OAuth2TokenIntrospectionFactory) + f := compose.Compose(new(fosite.Config), fositeStore, strategy, compose.OAuth2AuthorizeExplicitAuthFactory, compose.Oauth2AuthorizeExplicitTokenFactory, compose.OAuth2TokenIntrospectionFactory) ts := mockServer(t, f, &openid.DefaultSession{Subject: "foo-sub"}) defer ts.Close() @@ -148,7 +148,7 @@ func runAuthorizeCodeGrantTest(t *testing.T, strategy interface{}) { } func runAuthorizeCodeGrantDupeCodeTest(t *testing.T, strategy interface{}) { - f := compose.Compose(new(fosite.Config), fositeStore, strategy, compose.OAuth2AuthorizeExplicitFactory, compose.OAuth2TokenIntrospectionFactory) + f := compose.Compose(new(fosite.Config), fositeStore, strategy, compose.OAuth2AuthorizeExplicitAuthFactory, compose.Oauth2AuthorizeExplicitTokenFactory, compose.OAuth2TokenIntrospectionFactory) ts := mockServer(t, f, &fosite.DefaultSession{}) defer ts.Close() diff --git a/integration/authorize_device_grant_request_test.go b/integration/authorize_device_grant_request_test.go index 2deb30678..14b1d134f 100644 --- a/integration/authorize_device_grant_request_test.go +++ b/integration/authorize_device_grant_request_test.go @@ -6,7 +6,6 @@ package integration_test import ( "context" "fmt" - "net/url" "testing" "github.com/ory/fosite" @@ -25,6 +24,7 @@ func TestDeviceFlow(t *testing.T) { hmacStrategy, } { runDeviceFlowTest(t, strategy) + runDeviceFlowAccessTokenTest(t, strategy) } } @@ -39,10 +39,12 @@ func runDeviceFlowTest(t *testing.T, strategy interface{}) { Username: "peteru", }, } - fc := new(fosite.Config) - fc.DeviceVerificationURL = "https://example.com/" - fc.RefreshTokenLifespan = -1 - fc.GlobalSecret = []byte("some-secret-thats-random-some-secret-thats-random-") + + fc := &fosite.Config{ + DeviceVerificationURL: "https://example.com/", + RefreshTokenLifespan: -1, + GlobalSecret: []byte("some-secret-thats-random-some-secret-thats-random-"), + } f := compose.ComposeAllEnabled(fc, fositeStore, gen.MustRSAKey()) ts := mockServer(t, f, session) defer ts.Close() @@ -59,9 +61,8 @@ func runDeviceFlowTest(t *testing.T, strategy interface{}) { description string setup func() err bool - client fosite.Client check func(t *testing.T, token *goauth.DeviceAuthResponse, err error) - params url.Values + cleanUp func() }{ { description: "should fail with invalid_grant", @@ -72,6 +73,9 @@ func runDeviceFlowTest(t *testing.T, strategy interface{}) { check: func(t *testing.T, token *goauth.DeviceAuthResponse, err error) { assert.ErrorContains(t, err, "invalid_grant") }, + cleanUp: func() { + fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} + }, }, { description: "should fail with invalid_scope", @@ -83,6 +87,10 @@ func runDeviceFlowTest(t *testing.T, strategy interface{}) { check: func(t *testing.T, token *goauth.DeviceAuthResponse, err error) { assert.ErrorContains(t, err, "invalid_scope") }, + cleanUp: func() { + oauthClient.Scopes = []string{} + fositeStore.Clients["device-client"].(*fosite.DefaultClient).Scopes = []string{"fosite", "offline", "openid"} + }, }, { description: "should fail with invalid_client", @@ -93,6 +101,9 @@ func runDeviceFlowTest(t *testing.T, strategy interface{}) { check: func(t *testing.T, token *goauth.DeviceAuthResponse, err error) { assert.ErrorContains(t, err, "invalid_client") }, + cleanUp: func() { + oauthClient.ClientID = "device-client" + }, }, { description: "should pass", @@ -101,24 +112,6 @@ func runDeviceFlowTest(t *testing.T, strategy interface{}) { }, } { t.Run(fmt.Sprintf("case=%d description=%s", k, c.description), func(t *testing.T) { - // Restore client - fositeStore.Clients["device-client"] = &fosite.DefaultClient{ - ID: "device-client", - Secret: []byte(`$2a$10$IxMdI6d.LIRZPpSfEwNoeu4rY3FhDREsxFJXikcgdRRAStxUlsuEO`), // = "foobar" - GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}, - Scopes: []string{"fosite", "offline", "openid"}, - Audience: []string{tokenURL}, - Public: true, - } - oauthClient = &goauth.Config{ - ClientID: "device-client", - ClientSecret: "foobar", - Endpoint: goauth.Endpoint{ - TokenURL: ts.URL + tokenRelativePath, - DeviceAuthURL: ts.URL + deviceAuthRelativePath, - }, - } - c.setup() resp, err := oauthClient.DeviceAuth(context.Background()) @@ -135,6 +128,123 @@ func runDeviceFlowTest(t *testing.T, strategy interface{}) { c.check(t, resp, err) } + if c.cleanUp != nil { + c.cleanUp() + } + + t.Logf("Passed test case %d", k) + }) + } +} + +func runDeviceFlowAccessTokenTest(t *testing.T, strategy interface{}) { + session := &defaultSession{ + DefaultSession: &openid.DefaultSession{ + Claims: &jwt.IDTokenClaims{ + Subject: "peter", + }, + Headers: &jwt.Headers{}, + Subject: "peter", + Username: "peteru", + }, + } + + fc := &fosite.Config{ + DeviceVerificationURL: "https://example.com/", + RefreshTokenLifespan: -1, + GlobalSecret: []byte("some-secret-thats-random-some-secret-thats-random-"), + DeviceAuthTokenPollingInterval: -1, + } + f := compose.ComposeAllEnabled(fc, fositeStore, gen.MustRSAKey()) + ts := mockServer(t, f, session) + defer ts.Close() + + oauthClient := &goauth.Config{ + ClientID: "device-client", + ClientSecret: "foobar", + Endpoint: goauth.Endpoint{ + TokenURL: ts.URL + tokenRelativePath, + DeviceAuthURL: ts.URL + deviceAuthRelativePath, + }, + } + resp, _ := oauthClient.DeviceAuth(context.Background()) + + for k, c := range []struct { + description string + setup func() + params []goauth.AuthCodeOption + err bool + check func(t *testing.T, token *goauth.Token, err error) + cleanUp func() + }{ + { + description: "should fail with invalid grant type", + setup: func() { + }, + params: []goauth.AuthCodeOption{goauth.SetAuthURLParam("grant_type", "invalid_grant_type")}, + err: true, + check: func(t *testing.T, token *goauth.Token, err error) { + assert.ErrorContains(t, err, "invalid_request") + }, + }, + { + description: "should fail with unauthorized client", + setup: func() { + fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{"authorization_code"} + }, + params: []goauth.AuthCodeOption{}, + err: true, + check: func(t *testing.T, token *goauth.Token, err error) { + assert.ErrorContains(t, err, "unauthorized_client") + }, + cleanUp: func() { + fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} + }, + }, + { + description: "should fail with invalid device code", + setup: func() {}, + params: []goauth.AuthCodeOption{goauth.SetAuthURLParam("device_code", "invalid_device_code")}, + err: true, + check: func(t *testing.T, token *goauth.Token, err error) { + assert.ErrorContains(t, err, "invalid_grant") + }, + }, + { + description: "should fail with invalid client id", + setup: func() { + oauthClient.ClientID = "invalid_client_id" + }, + err: true, + check: func(t *testing.T, token *goauth.Token, err error) { + assert.ErrorContains(t, err, "invalid_client") + }, + cleanUp: func() { + oauthClient.ClientID = "device-client" + }, + }, + { + description: "should pass", + setup: func() {}, + err: false, + }, + } { + t.Run(fmt.Sprintf("case=%d description=%s", k, c.description), func(t *testing.T) { + c.setup() + + token, err := oauthClient.DeviceAccessToken(context.Background(), resp, c.params...) + if !c.err { + assert.NotEmpty(t, token.AccessToken) + } + + if c.check != nil { + c.check(t, token, err) + } + + if c.cleanUp != nil { + c.cleanUp() + } + t.Logf("Passed test case %d", k) }) } diff --git a/integration/helper_setup_test.go b/integration/helper_setup_test.go index f33b18948..ab1384ec3 100644 --- a/integration/helper_setup_test.go +++ b/integration/helper_setup_test.go @@ -83,6 +83,14 @@ var fositeStore = &storage.MemoryStore{ Scopes: []string{"fosite", "offline", "openid"}, Audience: []string{tokenURL}, }, + "device-client": &fosite.DefaultClient{ + ID: "device-client", + Secret: []byte(`$2a$10$IxMdI6d.LIRZPpSfEwNoeu4rY3FhDREsxFJXikcgdRRAStxUlsuEO`), // = "foobar" + GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}, + Scopes: []string{"fosite", "offline", "openid"}, + Audience: []string{tokenURL}, + Public: true, + }, }, Users: map[string]storage.MemoryUserRelation{ "peter": { @@ -190,16 +198,18 @@ var hmacStrategy = &oauth2.HMACSHAStrategy{ }, } -var defaultRSAKey = gen.MustRSAKey() -var jwtStrategy = &oauth2.DefaultJWTStrategy{ - Signer: &jwt.DefaultSigner{ - GetPrivateKey: func(ctx context.Context) (interface{}, error) { - return defaultRSAKey, nil +var ( + defaultRSAKey = gen.MustRSAKey() + jwtStrategy = &oauth2.DefaultJWTStrategy{ + Signer: &jwt.DefaultSigner{ + GetPrivateKey: func(ctx context.Context) (interface{}, error) { + return defaultRSAKey, nil + }, }, - }, - Config: &fosite.Config{}, - HMACSHAStrategy: hmacStrategy, -} + Config: &fosite.Config{}, + HMACSHAStrategy: hmacStrategy, + } +) func mockServer(t *testing.T, f fosite.OAuth2Provider, session fosite.Session) *httptest.Server { router := mux.NewRouter() diff --git a/integration/pushed_authorize_code_grant_test.go b/integration/pushed_authorize_code_grant_test.go index 0d9a710d1..2aa9714a6 100644 --- a/integration/pushed_authorize_code_grant_test.go +++ b/integration/pushed_authorize_code_grant_test.go @@ -30,7 +30,7 @@ func TestPushedAuthorizeCodeFlow(t *testing.T) { } func runPushedAuthorizeCodeGrantTest(t *testing.T, strategy interface{}) { - f := compose.Compose(new(fosite.Config), fositeStore, strategy, compose.OAuth2AuthorizeExplicitFactory, compose.OAuth2TokenIntrospectionFactory, compose.PushedAuthorizeHandlerFactory) + f := compose.Compose(new(fosite.Config), fositeStore, strategy, compose.OAuth2AuthorizeExplicitAuthFactory, compose.Oauth2AuthorizeExplicitTokenFactory, compose.OAuth2TokenIntrospectionFactory, compose.PushedAuthorizeHandlerFactory) ts := mockServer(t, f, &fosite.DefaultSession{Subject: "foo-sub"}) defer ts.Close() diff --git a/internal/device_code_rate_limit_strategy.go b/internal/device_code_rate_limit_strategy.go new file mode 100644 index 000000000..3a3b49511 --- /dev/null +++ b/internal/device_code_rate_limit_strategy.go @@ -0,0 +1,52 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ory/fosite/handler/rfc8628 (interfaces: DeviceRateLimitStrategy) + +// Package internal is a generated GoMock package. +package internal + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockDeviceRateLimitStrategy is a mock of DeviceRateLimitStrategy interface. +type MockDeviceRateLimitStrategy struct { + ctrl *gomock.Controller + recorder *MockDeviceRateLimitStrategyMockRecorder +} + +// MockDeviceRateLimitStrategyMockRecorder is the mock recorder for MockDeviceRateLimitStrategy. +type MockDeviceRateLimitStrategyMockRecorder struct { + mock *MockDeviceRateLimitStrategy +} + +// NewMockDeviceRateLimitStrategy creates a new mock instance. +func NewMockDeviceRateLimitStrategy(ctrl *gomock.Controller) *MockDeviceRateLimitStrategy { + mock := &MockDeviceRateLimitStrategy{ctrl: ctrl} + mock.recorder = &MockDeviceRateLimitStrategyMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDeviceRateLimitStrategy) EXPECT() *MockDeviceRateLimitStrategyMockRecorder { + return m.recorder +} + +// ShouldRateLimit mocks base method. +func (m *MockDeviceRateLimitStrategy) ShouldRateLimit(arg0 context.Context, arg1 string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ShouldRateLimit", arg0, arg1) + ret0, _ := ret[0].(bool) + return ret0 +} + +// ShouldRateLimit indicates an expected call of ShouldRateLimit. +func (mr *MockDeviceRateLimitStrategyMockRecorder) ShouldRateLimit(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldRateLimit", reflect.TypeOf((*MockDeviceRateLimitStrategy)(nil).ShouldRateLimit), arg0, arg1) +} diff --git a/internal/device_code_storage.go b/internal/device_code_storage.go new file mode 100644 index 000000000..c4eee24d9 --- /dev/null +++ b/internal/device_code_storage.go @@ -0,0 +1,82 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ory/fosite/handler/rfc8628 (interfaces: DeviceCodeStorage) + +// Package internal is a generated GoMock package. +package internal + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + fosite "github.com/ory/fosite" +) + +// MockDeviceCodeStorage is a mock of DeviceCodeStorage interface. +type MockDeviceCodeStorage struct { + ctrl *gomock.Controller + recorder *MockDeviceCodeStorageMockRecorder +} + +// MockDeviceCodeStorageMockRecorder is the mock recorder for MockDeviceCodeStorage. +type MockDeviceCodeStorageMockRecorder struct { + mock *MockDeviceCodeStorage +} + +// NewMockDeviceCodeStorage creates a new mock instance. +func NewMockDeviceCodeStorage(ctrl *gomock.Controller) *MockDeviceCodeStorage { + mock := &MockDeviceCodeStorage{ctrl: ctrl} + mock.recorder = &MockDeviceCodeStorageMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDeviceCodeStorage) EXPECT() *MockDeviceCodeStorageMockRecorder { + return m.recorder +} + +// CreateDeviceCodeSession mocks base method. +func (m *MockDeviceCodeStorage) CreateDeviceCodeSession(arg0 context.Context, arg1 string, arg2 fosite.Requester) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateDeviceCodeSession", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateDeviceCodeSession indicates an expected call of CreateDeviceCodeSession. +func (mr *MockDeviceCodeStorageMockRecorder) CreateDeviceCodeSession(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateDeviceCodeSession", reflect.TypeOf((*MockDeviceCodeStorage)(nil).CreateDeviceCodeSession), arg0, arg1, arg2) +} + +// GetDeviceCodeSession mocks base method. +func (m *MockDeviceCodeStorage) GetDeviceCodeSession(arg0 context.Context, arg1 string, arg2 fosite.Session) (fosite.Requester, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDeviceCodeSession", arg0, arg1, arg2) + ret0, _ := ret[0].(fosite.Requester) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDeviceCodeSession indicates an expected call of GetDeviceCodeSession. +func (mr *MockDeviceCodeStorageMockRecorder) GetDeviceCodeSession(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDeviceCodeSession", reflect.TypeOf((*MockDeviceCodeStorage)(nil).GetDeviceCodeSession), arg0, arg1, arg2) +} + +// InvalidateDeviceCodeSession mocks base method. +func (m *MockDeviceCodeStorage) InvalidateDeviceCodeSession(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InvalidateDeviceCodeSession", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// InvalidateDeviceCodeSession indicates an expected call of InvalidateDeviceCodeSession. +func (mr *MockDeviceCodeStorageMockRecorder) InvalidateDeviceCodeSession(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InvalidateDeviceCodeSession", reflect.TypeOf((*MockDeviceCodeStorage)(nil).InvalidateDeviceCodeSession), arg0, arg1) +} diff --git a/oauth2.go b/oauth2.go index 1b4cb3d03..9fc689cd0 100644 --- a/oauth2.go +++ b/oauth2.go @@ -61,7 +61,7 @@ type OAuth2Provider interface { NewAuthorizeRequest(ctx context.Context, req *http.Request) (AuthorizeRequester, error) // NewAuthorizeResponse iterates through all response type handlers and returns their result or - // ErrUnsupportedResponseType if none of the handler's were able to handle it. + // ErrUnsupportedResponseType if none of the handlers were able to handle it. // // The following specs must be considered in any implementation of this method: // * https://tools.ietf.org/html/rfc6749#section-3.1.1 @@ -151,11 +151,11 @@ type OAuth2Provider interface { NewDeviceResponse(ctx context.Context, requester DeviceRequester, session Session) (DeviceResponder, error) // WriteDeviceResponse return to the user both codes and - // some configuration informations in a JSON formated manner + // some configuration information in a JSON formatted manner // // The following specs must be considered in any implementation of this method: // * https://www.rfc-editor.org/rfc/rfc8628#section-3.2 (everything MUST be implemented) - // Response is a HTTP response body using the + // Response is an HTTP response body using the // "application/json" format [RFC8259] with a 200 (OK) status code. WriteDeviceResponse(ctx context.Context, rw http.ResponseWriter, requester DeviceRequester, responder DeviceResponder) @@ -207,14 +207,14 @@ type IntrospectionResponder interface { // IsActive returns true if the introspected token is active and false otherwise. IsActive() bool - // AccessRequester returns nil when IsActive() is false and the original access request object otherwise. + // GetAccessRequester returns nil when IsActive() is false and the original access request object otherwise. GetAccessRequester() AccessRequester // GetTokenUse optionally returns the type of the token that was introspected. This could be "access_token", "refresh_token", // or if the type can not be determined an empty string. GetTokenUse() TokenUse - //GetAccessTokenType optionally returns the type of the access token that was introspected. This could be "bearer", "mac", + // GetAccessTokenType optionally returns the type of the access token that was introspected. This could be "bearer", "mac", // or empty string if the type of the token is refresh token. GetAccessTokenType() string } @@ -248,7 +248,7 @@ type Requester interface { // AppendRequestedScope appends a scope to the request. AppendRequestedScope(scope string) - // GetGrantScopes returns all granted scopes. + // GetGrantedScopes returns all granted scopes. GetGrantedScopes() (grantedScopes Arguments) // GetGrantedAudience returns all granted audiences. @@ -278,7 +278,7 @@ type Requester interface { // AccessRequester is a token endpoint's request context. type AccessRequester interface { - // GetGrantType returns the requests grant type. + // GetGrantTypes returns the requests grant type. GetGrantTypes() (grantTypes Arguments) Requester @@ -341,7 +341,7 @@ type AccessResponder interface { // SetTokenType set's the responses mandatory token type SetTokenType(tokenType string) - // SetAccessToken returns the responses access token. + // GetAccessToken returns the responses access token. GetAccessToken() (token string) // GetTokenType returns the responses token type. @@ -359,7 +359,7 @@ type AuthorizeResponder interface { // GetHeader returns the response's header GetHeader() (header http.Header) - // AddHeader adds an header key value pair to the response + // AddHeader adds a header key value pair to the response AddHeader(key, value string) // GetParameters returns the response's parameters @@ -383,7 +383,7 @@ type PushedAuthorizeResponder interface { // GetHeader returns the response's header GetHeader() (header http.Header) - // AddHeader adds an header key value pair to the response + // AddHeader adds a header key value pair to the response AddHeader(key, value string) // SetExtra sets a key value pair for the response. @@ -436,6 +436,6 @@ type DeviceResponder interface { // GetHeader returns the response's header GetHeader() (header http.Header) - // AddHeader adds an header key value pair to the response + // AddHeader adds a header key value pair to the response AddHeader(key, value string) } diff --git a/storage/memory.go b/storage/memory.go index 499c528cd..a416327ce 100644 --- a/storage/memory.go +++ b/storage/memory.go @@ -432,6 +432,7 @@ func (s *MemoryStore) GetPublicKey(ctx context.Context, issuer string, subject s return nil, fosite.ErrNotFound } + func (s *MemoryStore) GetPublicKeys(ctx context.Context, issuer string, subject string) (*jose.JSONWebKeySet, error) { s.issuerPublicKeysMutex.RLock() defer s.issuerPublicKeysMutex.RUnlock() From e0fcc990f723df81abb96f42fb62b5865d6f5fb7 Mon Sep 17 00:00:00 2001 From: dushu Date: Sat, 16 Mar 2024 21:12:30 -0600 Subject: [PATCH 11/17] fix: passing the correct authorization request when validating if the auth/device code is expired --- handler/oauth2/flow_authorize_code_token.go | 2 +- .../oauth2/flow_authorize_code_token_test.go | 157 +++++++++--------- handler/oauth2/flow_generic_code_token.go | 4 +- handler/rfc8628/token_handler.go | 2 +- handler/rfc8628/token_handler_test.go | 148 +++++++---------- 5 files changed, 140 insertions(+), 173 deletions(-) diff --git a/handler/oauth2/flow_authorize_code_token.go b/handler/oauth2/flow_authorize_code_token.go index 96803a20c..734e2ab07 100644 --- a/handler/oauth2/flow_authorize_code_token.go +++ b/handler/oauth2/flow_authorize_code_token.go @@ -23,7 +23,7 @@ func (c AuthorizeCodeHandler) Code(ctx context.Context, requester fosite.AccessR return code, signature, nil } -func (c AuthorizeCodeHandler) ValidateCode(ctx context.Context, requester fosite.AccessRequester, code string) error { +func (c AuthorizeCodeHandler) ValidateCode(ctx context.Context, requester fosite.Requester, code string) error { return c.AuthorizeCodeStrategy.ValidateAuthorizeCode(ctx, requester, code) } diff --git a/handler/oauth2/flow_authorize_code_token_test.go b/handler/oauth2/flow_authorize_code_token_test.go index 59d89ab36..52f1aab9b 100644 --- a/handler/oauth2/flow_authorize_code_token_test.go +++ b/handler/oauth2/flow_authorize_code_token_test.go @@ -37,13 +37,14 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { expectErr error }{ { + description: "should fail because not responsible", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"123"}, }, - description: "should fail because not responsible", - expectErr: fosite.ErrUnknownRequest, + expectErr: fosite.ErrUnknownRequest, }, { + description: "should fail because authorization code cannot be retrieved", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ @@ -55,7 +56,6 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { RequestedAt: time.Now().UTC(), }, }, - description: "should fail because authcode not found", setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { code, _, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) @@ -64,6 +64,7 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { expectErr: fosite.ErrServerError, }, { + description: "should fail because authorization code is expired", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ @@ -71,17 +72,21 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { Client: &fosite.DefaultClient{ GrantTypes: fosite.Arguments{"authorization_code"}, }, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), + Session: &fosite.DefaultSession{ + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.AuthorizeCode: time.Now().Add(-time.Hour).UTC(), + }, + }, + RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), }, }, - description: "should fail because validation failed", setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), "bar", areq)) }, expectErr: fosite.ErrInvalidRequest, }, { + description: "should pass with offline scope and refresh token", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ @@ -95,13 +100,12 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { }, }, setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - code, sig, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) areq.Form.Add("code", code) - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), sig, areq)) + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, areq)) }, - description: "should pass with offline scope and refresh token", check: func(t *testing.T, aresp *fosite.AccessResponse) { assert.NotEmpty(t, aresp.AccessToken) assert.Equal(t, "bearer", aresp.TokenType) @@ -111,6 +115,7 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { }, }, { + description: "should pass with refresh token always provided", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ @@ -125,13 +130,12 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { }, setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { config.RefreshTokenScopes = []string{} - code, sig, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) areq.Form.Add("code", code) - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), sig, areq)) + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, areq)) }, - description: "should pass with refresh token always provided", check: func(t *testing.T, aresp *fosite.AccessResponse) { assert.NotEmpty(t, aresp.AccessToken) assert.Equal(t, "bearer", aresp.TokenType) @@ -141,36 +145,7 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { }, }, { - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, - Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"authorization_code"}, - }, - GrantedScope: fosite.Arguments{}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - config.RefreshTokenScopes = []string{} - code, sig, err := strategy.GenerateAuthorizeCode(context.Background(), nil) - require.NoError(t, err) - areq.Form.Add("code", code) - - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), sig, areq)) - }, - description: "should pass with no refresh token", - check: func(t *testing.T, aresp *fosite.AccessResponse) { - assert.NotEmpty(t, aresp.AccessToken) - assert.Equal(t, "bearer", aresp.TokenType) - assert.Empty(t, aresp.GetExtra("refresh_token")) - assert.NotEmpty(t, aresp.GetExtra("expires_in")) - assert.Empty(t, aresp.GetExtra("scope")) - }, - }, - { + description: "pass and response should not have refresh token", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ @@ -190,7 +165,6 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), sig, areq)) }, - description: "should not have refresh token", check: func(t *testing.T, aresp *fosite.AccessResponse) { assert.NotEmpty(t, aresp.AccessToken) assert.Equal(t, "bearer", aresp.TokenType) @@ -266,21 +240,22 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { Config: config, } for i, c := range []struct { + description string areq *fosite.AccessRequest authreq *fosite.AuthorizeRequest - description string setup func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) check func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) expectErr error }{ { + description: "should fail because not responsible", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"12345678"}, }, - description: "should fail because not responsible", - expectErr: fosite.ErrUnknownRequest, + expectErr: fosite.ErrUnknownRequest, }, { + description: "should fail because client is not granted the correct grant type", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ @@ -289,10 +264,10 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { RequestedAt: time.Now().UTC(), }, }, - description: "should fail because client is not granted this grant type", - expectErr: fosite.ErrUnauthorizedClient, + expectErr: fosite.ErrUnauthorizedClient, }, { + description: "should fail because authorization code cannot be retrieved", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ @@ -301,7 +276,6 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { RequestedAt: time.Now().UTC(), }, }, - description: "should fail because authcode could not be retrieved (1)", setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) { token, _, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) @@ -310,19 +284,38 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { expectErr: fosite.ErrInvalidGrant, }, { + description: "should fail because authorization code is expired", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ Form: url.Values{"code": {"foo.bar"}}, - Client: &fosite.DefaultClient{GrantTypes: []string{"authorization_code"}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, }, - description: "should fail because authcode validation failed", - expectErr: fosite.ErrInvalidGrant, + authreq: &fosite.AuthorizeRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo"}, + Session: &fosite.DefaultSession{ + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.AuthorizeCode: time.Now().Add(-time.Hour).UTC(), + }, + }, + RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) { + token, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + require.NoError(t, err) + areq.Form = url.Values{"code": {token}} + + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, authreq)) + }, + expectErr: fosite.ErrTokenExpired, }, { + description: "should fail because client mismatch", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ @@ -333,11 +326,14 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "bar"}, - RequestedScope: fosite.Arguments{"a", "b"}, + Client: &fosite.DefaultClient{ID: "bar"}, + Session: &fosite.DefaultSession{ + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.AuthorizeCode: time.Now().Add(time.Hour).UTC(), + }, + }, }, }, - description: "should fail because client mismatch", setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) { token, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) @@ -348,6 +344,7 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { expectErr: fosite.ErrInvalidGrant, }, { + description: "should fail because redirect uri was set during /authorize call, but not in /token call", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ @@ -358,12 +355,15 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, - Form: url.Values{"redirect_uri": []string{"request-redir"}}, - Session: &fosite.DefaultSession{}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Form: url.Values{"redirect_uri": []string{"request-redir"}}, + Session: &fosite.DefaultSession{ + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.AuthorizeCode: time.Now().Add(time.Hour).UTC(), + }, + }, }, }, - description: "should fail because redirect uri was set during /authorize call, but not in /token call", setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) { token, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) @@ -374,6 +374,7 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { expectErr: fosite.ErrInvalidGrant, }, { + description: "should pass", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ @@ -385,13 +386,11 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, - Session: &fosite.DefaultSession{}, - RequestedScope: fosite.Arguments{"a", "b"}, - RequestedAt: time.Now().UTC(), + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), }, }, - description: "should pass", setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) { token, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) @@ -401,32 +400,32 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { }, }, { + description: "should fail because code has been used already", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"authorization_code"}, - }, - GrantedScope: fosite.Arguments{"foo", "offline"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), + Form: url.Values{}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: fosite.Arguments{"authorization_code"}}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), }, }, - check: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) { - assert.Equal(t, time.Now().Add(time.Minute).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.AccessToken)) - assert.Equal(t, time.Now().Add(time.Minute).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.RefreshToken)) + authreq: &fosite.AuthorizeRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, }, setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) { - code, sig, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) areq.Form.Add("code", code) - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), sig, areq)) - require.NoError(t, store.InvalidateAuthorizeCodeSession(context.Background(), sig)) + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, authreq)) + require.NoError(t, store.InvalidateAuthorizeCodeSession(context.Background(), signature)) }, - description: "should fail because code has been used already", - expectErr: fosite.ErrInvalidGrant, + expectErr: fosite.ErrInvalidGrant, }, } { t.Run(fmt.Sprintf("case=%d/description=%s", i, c.description), func(t *testing.T) { diff --git a/handler/oauth2/flow_generic_code_token.go b/handler/oauth2/flow_generic_code_token.go index 39c40422c..756507c2b 100644 --- a/handler/oauth2/flow_generic_code_token.go +++ b/handler/oauth2/flow_generic_code_token.go @@ -36,7 +36,7 @@ type CodeHandler interface { Code(ctx context.Context, requester fosite.AccessRequester) (code string, signature string, err error) // ValidateCode validates the code. - ValidateCode(ctx context.Context, requester fosite.AccessRequester, code string) error + ValidateCode(ctx context.Context, requester fosite.Requester, code string) error } // SessionHandler handles session-related operations. @@ -175,7 +175,7 @@ func (c *GenericCodeTokenEndpointHandler) HandleTokenEndpointRequest(ctx context return err } - if err = c.ValidateCode(ctx, requester, code); err != nil { + if err = c.ValidateCode(ctx, ar, code); err != nil { return errorsx.WithStack(err) } diff --git a/handler/rfc8628/token_handler.go b/handler/rfc8628/token_handler.go index 3e0c3c7d8..9279a3809 100644 --- a/handler/rfc8628/token_handler.go +++ b/handler/rfc8628/token_handler.go @@ -34,7 +34,7 @@ func (c DeviceCodeHandler) Code(ctx context.Context, requester fosite.AccessRequ return } -func (c DeviceCodeHandler) ValidateCode(ctx context.Context, requester fosite.AccessRequester, code string) error { +func (c DeviceCodeHandler) ValidateCode(ctx context.Context, requester fosite.Requester, code string) error { return c.DeviceCodeStrategy.ValidateDeviceCode(ctx, requester, code) } diff --git a/handler/rfc8628/token_handler_test.go b/handler/rfc8628/token_handler_test.go index 9be2d4d06..33e1c7686 100644 --- a/handler/rfc8628/token_handler_test.go +++ b/handler/rfc8628/token_handler_test.go @@ -64,13 +64,14 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { expectErr error }{ { + description: "should fail because not responsible", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"123"}, }, - description: "should fail because not responsible", - expectErr: fosite.ErrUnknownRequest, + expectErr: fosite.ErrUnknownRequest, }, { + description: "should fail because device code cannot be retrieved", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ @@ -82,7 +83,6 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { RequestedAt: time.Now().UTC(), }, }, - description: "should fail because device code not found", setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { code, _, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) @@ -91,6 +91,7 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { expectErr: fosite.ErrServerError, }, { + description: "should pass with offline scope and refresh token", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ @@ -104,13 +105,12 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { }, }, setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - code, sig, err := strategy.GenerateDeviceCode(context.TODO()) + code, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) areq.Form.Add("device_code", code) - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), sig, areq)) + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, areq)) }, - description: "should pass with offline scope and refresh token", check: func(t *testing.T, aresp *fosite.AccessResponse) { assert.NotEmpty(t, aresp.AccessToken) assert.Equal(t, "bearer", aresp.TokenType) @@ -120,6 +120,7 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { }, }, { + description: "should pass with refresh token always provided", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ @@ -134,13 +135,12 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { }, setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { config.RefreshTokenScopes = []string{} - code, sig, err := strategy.GenerateDeviceCode(context.TODO()) + code, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) areq.Form.Add("device_code", code) - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), sig, areq)) + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, areq)) }, - description: "should pass with refresh token always provided", check: func(t *testing.T, aresp *fosite.AccessResponse) { assert.NotEmpty(t, aresp.AccessToken) assert.Equal(t, "bearer", aresp.TokenType) @@ -150,36 +150,7 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { }, }, { - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, - Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, - }, - GrantedScope: fosite.Arguments{}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - config.RefreshTokenScopes = []string{} - code, sig, err := strategy.GenerateDeviceCode(context.TODO()) - require.NoError(t, err) - areq.Form.Add("device_code", code) - - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), sig, areq)) - }, - description: "should pass with no refresh token", - check: func(t *testing.T, aresp *fosite.AccessResponse) { - assert.NotEmpty(t, aresp.AccessToken) - assert.Equal(t, "bearer", aresp.TokenType) - assert.Empty(t, aresp.GetExtra("refresh_token")) - assert.NotEmpty(t, aresp.GetExtra("expires_in")) - assert.Empty(t, aresp.GetExtra("scope")) - }, - }, - { + description: "pass and response should not have refresh token", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ @@ -199,7 +170,6 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), sig, areq)) }, - description: "should not have refresh token", check: func(t *testing.T, aresp *fosite.AccessResponse) { assert.NotEmpty(t, aresp.AccessToken) assert.Equal(t, "bearer", aresp.TokenType) @@ -277,27 +247,28 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { AccessTokenStrategy: strategy.CoreStrategy, RefreshTokenStrategy: strategy.CoreStrategy, Config: &fosite.Config{ - ScopeStrategy: fosite.HierarchicScopeStrategy, - AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, - AuthorizeCodeLifespan: time.Minute, + ScopeStrategy: fosite.HierarchicScopeStrategy, + AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, + DeviceAndUserCodeLifespan: time.Minute, }, } for i, c := range []struct { + description string areq *fosite.AccessRequest authreq *fosite.DeviceRequest - description string setup func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) check func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) expectErr error }{ { + description: "should fail because not responsible", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"12345678"}, }, - description: "should fail because not responsible", - expectErr: fosite.ErrUnknownRequest, + expectErr: fosite.ErrUnknownRequest, }, { + description: "should fail because client is not granted the correct grant type", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ @@ -306,10 +277,10 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { RequestedAt: time.Now().UTC(), }, }, - description: "should fail because client is not granted this grant type", - expectErr: fosite.ErrUnauthorizedClient, + expectErr: fosite.ErrUnauthorizedClient, }, { + description: "should fail because device code could not be retrieved", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ @@ -318,7 +289,6 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { RequestedAt: time.Now().UTC(), }, }, - description: "should fail because device code could not be retrieved", setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { deviceCode, _, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) @@ -327,19 +297,42 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { expectErr: fosite.ErrInvalidGrant, }, { + description: "should fail because device code has expired", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ - Form: url.Values{"device_code": {"AAAA"}}, - Client: &fosite.DefaultClient{GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), + Form: url.Values{}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + }, + GrantedScope: fosite.Arguments{"foo", "offline"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + authreq: &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, + Session: &fosite.DefaultSession{ + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.DeviceCode: time.Now().Add(-time.Hour).UTC(), + }, + }, + RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), }, }, - description: "should fail because device code validation failed", - expectErr: fosite.ErrInvalidGrant, + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { + code, signature, err := strategy.GenerateDeviceCode(context.TODO()) + require.NoError(t, err) + areq.Form.Add("device_code", code) + + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) + }, + expectErr: fosite.ErrDeviceExpiredToken, }, { + description: "should fail because client mismatch", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ @@ -350,11 +343,14 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { }, authreq: &fosite.DeviceRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "bar"}, - RequestedScope: fosite.Arguments{"a", "b"}, + Client: &fosite.DefaultClient{ID: "bar"}, + Session: &fosite.DefaultSession{ + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.DeviceCode: time.Now().Add(time.Hour).UTC(), + }, + }, }, }, - description: "should fail because client mismatch", setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { token, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) @@ -365,6 +361,7 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { expectErr: fosite.ErrInvalidGrant, }, { + description: "should pass", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ @@ -375,13 +372,11 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { }, authreq: &fosite.DeviceRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, - Session: &fosite.DefaultSession{}, - RequestedScope: fosite.Arguments{"a", "b"}, - RequestedAt: time.Now().UTC(), + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), }, }, - description: "should pass", setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { token, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) @@ -390,33 +385,6 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) }, }, - { - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, - Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, - }, - GrantedScope: fosite.Arguments{"foo", "offline"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - check: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { - assert.Equal(t, time.Now().Add(time.Minute).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.AccessToken)) - assert.Equal(t, time.Now().Add(time.Minute).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.RefreshToken)) - }, - setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { - code, sig, err := strategy.GenerateDeviceCode(context.TODO()) - require.NoError(t, err) - areq.Form.Add("device_code", code) - areq.GetSession().SetExpiresAt(fosite.DeviceCode, time.Now().Add(-time.Hour).UTC().Round(time.Second)) - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), sig, areq)) - }, - description: "should fail because device code has expired", - expectErr: fosite.ErrDeviceExpiredToken, - }, } { t.Run(fmt.Sprintf("case=%d/description=%s", i, c.description), func(t *testing.T) { if c.setup != nil { From aba04a669aff0527bd45aac091fca7df694c79bc Mon Sep 17 00:00:00 2001 From: dushu Date: Sun, 17 Mar 2024 18:31:06 -0600 Subject: [PATCH 12/17] feat: error handling for authorization pending in device flow --- handler/rfc8628/auth_handler.go | 2 +- handler/rfc8628/strategy_hmacsha.go | 79 +++++++++ handler/rfc8628/token_handler.go | 13 ++ handler/rfc8628/token_handler_test.go | 151 +++++++++++++----- .../authorize_device_grant_request_test.go | 47 ++---- 5 files changed, 217 insertions(+), 75 deletions(-) diff --git a/handler/rfc8628/auth_handler.go b/handler/rfc8628/auth_handler.go index 6d97da3aa..aa31ff520 100644 --- a/handler/rfc8628/auth_handler.go +++ b/handler/rfc8628/auth_handler.go @@ -36,7 +36,7 @@ func (d *DeviceAuthHandler) HandleDeviceEndpointRequest(ctx context.Context, dar } // Store the User Code session (this has no real data other that the user and device code), can be converted into a 'full' session after user auth - dar.GetSession().SetExpiresAt(fosite.AuthorizeCode, time.Now().UTC().Add(d.Config.GetDeviceAndUserCodeLifespan(ctx))) + dar.GetSession().SetExpiresAt(fosite.DeviceCode, time.Now().UTC().Add(d.Config.GetDeviceAndUserCodeLifespan(ctx))) if err := d.Storage.CreateDeviceCodeSession(ctx, deviceCodeSignature, dar.Sanitize(nil)); err != nil { return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } diff --git a/handler/rfc8628/strategy_hmacsha.go b/handler/rfc8628/strategy_hmacsha.go index 04395b009..7c41b1d19 100644 --- a/handler/rfc8628/strategy_hmacsha.go +++ b/handler/rfc8628/strategy_hmacsha.go @@ -8,6 +8,8 @@ import ( "strings" "time" + "github.com/mohae/deepcopy" + "github.com/ory/x/errorsx" "github.com/ory/x/randx" @@ -18,6 +20,83 @@ import ( enigma "github.com/ory/fosite/token/hmac" ) +// DeviceFlowSession is a fosite.Session container specific for the device flow. +type DeviceFlowSession interface { + // BrowserFlowCompleted returns the flag indicating whether user has completed the browser flow or not. + BrowserFlowCompleted() bool + + // SetBrowserFlowCompleted allows client to mark user has completed the browser flow. + SetBrowserFlowCompleted(flag bool) + + fosite.Session +} + +// DefaultDeviceFlowSession is a DeviceFlowSession implementation for the device flow. +type DefaultDeviceFlowSession struct { + ExpiresAt map[fosite.TokenType]time.Time `json:"expires_at"` + Username string `json:"username"` + Subject string `json:"subject"` + Extra map[string]interface{} `json:"extra"` + BrowserFlowCompleted bool `json:"browser_flow_completed"` +} + +func (s *DefaultDeviceFlowSession) SetExpiresAt(key fosite.TokenType, exp time.Time) { + if s.ExpiresAt == nil { + s.ExpiresAt = make(map[fosite.TokenType]time.Time) + } + s.ExpiresAt[key] = exp +} + +func (s *DefaultDeviceFlowSession) GetExpiresAt(key fosite.TokenType) time.Time { + if s.ExpiresAt == nil { + s.ExpiresAt = make(map[fosite.TokenType]time.Time) + } + + if _, ok := s.ExpiresAt[key]; !ok { + return time.Time{} + } + return s.ExpiresAt[key] +} + +func (s *DefaultDeviceFlowSession) GetUsername() string { + if s == nil { + return "" + } + return s.Username +} + +func (s *DefaultDeviceFlowSession) SetSubject(subject string) { + s.Subject = subject +} + +func (s *DefaultDeviceFlowSession) GetSubject() string { + if s == nil { + return "" + } + + return s.Subject +} + +func (s *DefaultDeviceFlowSession) Clone() fosite.Session { + if s == nil { + return nil + } + + return deepcopy.Copy(s).(fosite.Session) +} + +func (s *DefaultDeviceFlowSession) GetBrowserFlowCompleted() bool { + if s == nil { + return false + } + + return s.BrowserFlowCompleted +} + +func (s *DefaultDeviceFlowSession) SetBrowserFlowCompleted(flag bool) { + s.BrowserFlowCompleted = flag +} + // DefaultDeviceStrategy implements the default device strategy type DefaultDeviceStrategy struct { Enigma *enigma.HMACStrategy diff --git a/handler/rfc8628/token_handler.go b/handler/rfc8628/token_handler.go index 9279a3809..dd71a7e83 100644 --- a/handler/rfc8628/token_handler.go +++ b/handler/rfc8628/token_handler.go @@ -55,6 +55,10 @@ func (s DeviceSessionHandler) Session(ctx context.Context, requester fosite.Acce WithDebug("\"GetDeviceCodeSession\" must return a value for \"fosite.Requester\" when returning \"ErrInvalidatedDeviceCode\".") } + if err != nil && errors.Is(err, fosite.ErrAuthorizationPending) { + return nil, err + } + if err != nil && errors.Is(err, fosite.ErrNotFound) { return nil, errorsx.WithStack(fosite.ErrInvalidGrant.WithWrap(err).WithDebug(err.Error())) } @@ -63,6 +67,15 @@ func (s DeviceSessionHandler) Session(ctx context.Context, requester fosite.Acce return nil, errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } + session, ok := req.GetSession().(*DefaultDeviceFlowSession) + if !ok { + return nil, fosite.ErrServerError.WithHint("Wrong authorization request session.") + } + + if !session.GetBrowserFlowCompleted() { + return nil, fosite.ErrAuthorizationPending + } + return req, err } diff --git a/handler/rfc8628/token_handler_test.go b/handler/rfc8628/token_handler_test.go index 33e1c7686..8da59e80f 100644 --- a/handler/rfc8628/token_handler_test.go +++ b/handler/rfc8628/token_handler_test.go @@ -73,13 +73,13 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { { description: "should fail because device code cannot be retrieved", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, }, - Session: &fosite.DefaultSession{}, + Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, }, @@ -90,18 +90,47 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { }, expectErr: fosite.ErrServerError, }, + { + description: "should fail because device code is expired", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + }, + Session: &DefaultDeviceFlowSession{ + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.DeviceCode: time.Now().Add(-time.Hour).UTC(), + }, + BrowserFlowCompleted: true, + }, + RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + code, signature, err := strategy.GenerateDeviceCode(context.TODO()) + require.NoError(t, err) + areq.Form.Add("device_code", code) + + require.NoError(t, store.CreateDeviceCodeSession(context.Background(), signature, areq)) + }, + expectErr: fosite.ErrInvalidRequest, + }, { description: "should pass with offline scope and refresh token", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code", "refresh_token"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode), string(fosite.GrantTypeRefreshToken)}, }, GrantedScope: fosite.Arguments{"foo", "offline"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), + Session: &DefaultDeviceFlowSession{ + BrowserFlowCompleted: true, + }, + RequestedAt: time.Now().UTC(), }, }, setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { @@ -122,15 +151,17 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { { description: "should pass with refresh token always provided", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code", "refresh_token"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode), string(fosite.GrantTypeRefreshToken)}, }, GrantedScope: fosite.Arguments{"foo"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), + Session: &DefaultDeviceFlowSession{ + BrowserFlowCompleted: true, + }, + RequestedAt: time.Now().UTC(), }, }, setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { @@ -152,23 +183,25 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { { description: "pass and response should not have refresh token", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, }, GrantedScope: fosite.Arguments{"foo"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), + Session: &DefaultDeviceFlowSession{ + BrowserFlowCompleted: true, + }, + RequestedAt: time.Now().UTC(), }, }, setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - code, sig, err := strategy.GenerateDeviceCode(context.TODO()) + code, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) areq.Form.Add("device_code", code) - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), sig, areq)) + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, areq)) }, check: func(t *testing.T, aresp *fosite.AccessResponse) { assert.NotEmpty(t, aresp.AccessToken) @@ -270,10 +303,10 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because client is not granted the correct grant type", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{""}}, - Session: &fosite.DefaultSession{}, + Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, }, @@ -282,10 +315,10 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because device code could not be retrieved", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ - Client: &fosite.DefaultClient{GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, - Session: &fosite.DefaultSession{}, + Client: &fosite.DefaultClient{GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, }, @@ -296,28 +329,61 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { }, expectErr: fosite.ErrInvalidGrant, }, + { + description: "should fail because user has not completed the browser flow", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Session: &DefaultDeviceFlowSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + authreq: &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Session: &DefaultDeviceFlowSession{ + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.DeviceCode: time.Now().Add(-time.Hour).UTC(), + }, + BrowserFlowCompleted: false, + }, + RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { + code, signature, err := strategy.GenerateDeviceCode(context.TODO()) + require.NoError(t, err) + areq.Form.Add("device_code", code) + + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) + }, + expectErr: fosite.ErrAuthorizationPending, + }, { description: "should fail because device code has expired", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ ID: "foo", - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, }, GrantedScope: fosite.Arguments{"foo", "offline"}, - Session: &fosite.DefaultSession{}, + Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.DeviceRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, - Session: &fosite.DefaultSession{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Session: &DefaultDeviceFlowSession{ ExpiresAt: map[fosite.TokenType]time.Time{ fosite.DeviceCode: time.Now().Add(-time.Hour).UTC(), }, + BrowserFlowCompleted: true, }, RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), }, @@ -334,20 +400,21 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because client mismatch", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, - Session: &fosite.DefaultSession{}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.DeviceRequest{ Request: fosite.Request{ Client: &fosite.DefaultClient{ID: "bar"}, - Session: &fosite.DefaultSession{ + Session: &DefaultDeviceFlowSession{ ExpiresAt: map[fosite.TokenType]time.Time{ fosite.DeviceCode: time.Now().Add(time.Hour).UTC(), }, + BrowserFlowCompleted: true, }, }, }, @@ -363,17 +430,19 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should pass", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, - Session: &fosite.DefaultSession{}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.DeviceRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, - Session: &fosite.DefaultSession{}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Session: &DefaultDeviceFlowSession{ + BrowserFlowCompleted: true, + }, RequestedAt: time.Now().UTC(), }, }, @@ -416,14 +485,16 @@ func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { strategy := hmacshaStrategy deviceStrategy := RFC8628HMACSHAStrategy request := &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code", "refresh_token"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode), string(fosite.GrantTypeRefreshToken)}, }, GrantedScope: fosite.Arguments{"offline"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), + Session: &DefaultDeviceFlowSession{ + BrowserFlowCompleted: true, + }, + RequestedAt: time.Now().UTC(), }, } token, _, err := deviceStrategy.GenerateDeviceCode(context.Background()) diff --git a/integration/authorize_device_grant_request_test.go b/integration/authorize_device_grant_request_test.go index 14b1d134f..170c906fa 100644 --- a/integration/authorize_device_grant_request_test.go +++ b/integration/authorize_device_grant_request_test.go @@ -8,37 +8,23 @@ import ( "fmt" "testing" + "github.com/ory/fosite/handler/rfc8628" + "github.com/ory/fosite" "github.com/ory/fosite/compose" - "github.com/ory/fosite/handler/oauth2" - "github.com/ory/fosite/handler/openid" "github.com/ory/fosite/internal/gen" - "github.com/ory/fosite/token/jwt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" goauth "golang.org/x/oauth2" ) func TestDeviceFlow(t *testing.T) { - for _, strategy := range []oauth2.AccessTokenStrategy{ - hmacStrategy, - } { - runDeviceFlowTest(t, strategy) - runDeviceFlowAccessTokenTest(t, strategy) - } + runDeviceFlowTest(t) + runDeviceFlowAccessTokenTest(t) } -func runDeviceFlowTest(t *testing.T, strategy interface{}) { - session := &defaultSession{ - DefaultSession: &openid.DefaultSession{ - Claims: &jwt.IDTokenClaims{ - Subject: "peter", - }, - Headers: &jwt.Headers{}, - Subject: "peter", - Username: "peteru", - }, - } +func runDeviceFlowTest(t *testing.T) { + session := &rfc8628.DefaultDeviceFlowSession{} fc := &fosite.Config{ DeviceVerificationURL: "https://example.com/", @@ -67,14 +53,14 @@ func runDeviceFlowTest(t *testing.T, strategy interface{}) { { description: "should fail with invalid_grant", setup: func() { - fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{"authorization_code"} + fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{string(fosite.GrantTypeAuthorizationCode)} }, err: true, check: func(t *testing.T, token *goauth.DeviceAuthResponse, err error) { assert.ErrorContains(t, err, "invalid_grant") }, cleanUp: func() { - fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} + fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{string(fosite.GrantTypeDeviceCode)} }, }, { @@ -137,16 +123,9 @@ func runDeviceFlowTest(t *testing.T, strategy interface{}) { } } -func runDeviceFlowAccessTokenTest(t *testing.T, strategy interface{}) { - session := &defaultSession{ - DefaultSession: &openid.DefaultSession{ - Claims: &jwt.IDTokenClaims{ - Subject: "peter", - }, - Headers: &jwt.Headers{}, - Subject: "peter", - Username: "peteru", - }, +func runDeviceFlowAccessTokenTest(t *testing.T) { + session := &rfc8628.DefaultDeviceFlowSession{ + BrowserFlowCompleted: true, } fc := &fosite.Config{ @@ -190,7 +169,7 @@ func runDeviceFlowAccessTokenTest(t *testing.T, strategy interface{}) { { description: "should fail with unauthorized client", setup: func() { - fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{"authorization_code"} + fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{string(fosite.GrantTypeAuthorizationCode)} }, params: []goauth.AuthCodeOption{}, err: true, @@ -198,7 +177,7 @@ func runDeviceFlowAccessTokenTest(t *testing.T, strategy interface{}) { assert.ErrorContains(t, err, "unauthorized_client") }, cleanUp: func() { - fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} + fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{string(fosite.GrantTypeDeviceCode)} }, }, { From 88af7ea482cfab0cec2a23dc5e1513ae429c02b3 Mon Sep 17 00:00:00 2001 From: dushu Date: Mon, 18 Mar 2024 09:32:39 -0600 Subject: [PATCH 13/17] test: reorganize the testcases --- .../oauth2/flow_authorize_code_token_test.go | 462 +++++++++--------- handler/rfc8628/token_handler_test.go | 454 ++++++++--------- 2 files changed, 466 insertions(+), 450 deletions(-) diff --git a/handler/oauth2/flow_authorize_code_token_test.go b/handler/oauth2/flow_authorize_code_token_test.go index 52f1aab9b..df15fae81 100644 --- a/handler/oauth2/flow_authorize_code_token_test.go +++ b/handler/oauth2/flow_authorize_code_token_test.go @@ -21,202 +21,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { - for k, strategy := range map[string]CoreStrategy{ - "hmac": &hmacshaStrategy, - } { - t.Run("strategy="+k, func(t *testing.T) { - store := storage.NewMemoryStore() - - var h GenericCodeTokenEndpointHandler - for _, c := range []struct { - areq *fosite.AccessRequest - description string - setup func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) - check func(t *testing.T, aresp *fosite.AccessResponse) - expectErr error - }{ - { - description: "should fail because not responsible", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"123"}, - }, - expectErr: fosite.ErrUnknownRequest, - }, - { - description: "should fail because authorization code cannot be retrieved", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, - Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"authorization_code"}, - }, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - code, _, err := strategy.GenerateAuthorizeCode(context.Background(), nil) - require.NoError(t, err) - areq.Form.Set("code", code) - }, - expectErr: fosite.ErrServerError, - }, - { - description: "should fail because authorization code is expired", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, - Request: fosite.Request{ - Form: url.Values{"code": []string{"foo.bar"}}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"authorization_code"}, - }, - Session: &fosite.DefaultSession{ - ExpiresAt: map[fosite.TokenType]time.Time{ - fosite.AuthorizeCode: time.Now().Add(-time.Hour).UTC(), - }, - }, - RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), - }, - }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), "bar", areq)) - }, - expectErr: fosite.ErrInvalidRequest, - }, - { - description: "should pass with offline scope and refresh token", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, - Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"authorization_code", "refresh_token"}, - }, - GrantedScope: fosite.Arguments{"foo", "offline"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) - require.NoError(t, err) - areq.Form.Add("code", code) - - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, areq)) - }, - check: func(t *testing.T, aresp *fosite.AccessResponse) { - assert.NotEmpty(t, aresp.AccessToken) - assert.Equal(t, "bearer", aresp.TokenType) - assert.NotEmpty(t, aresp.GetExtra("refresh_token")) - assert.NotEmpty(t, aresp.GetExtra("expires_in")) - assert.Equal(t, "foo offline", aresp.GetExtra("scope")) - }, - }, - { - description: "should pass with refresh token always provided", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, - Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"authorization_code", "refresh_token"}, - }, - GrantedScope: fosite.Arguments{"foo"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - config.RefreshTokenScopes = []string{} - code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) - require.NoError(t, err) - areq.Form.Add("code", code) - - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, areq)) - }, - check: func(t *testing.T, aresp *fosite.AccessResponse) { - assert.NotEmpty(t, aresp.AccessToken) - assert.Equal(t, "bearer", aresp.TokenType) - assert.NotEmpty(t, aresp.GetExtra("refresh_token")) - assert.NotEmpty(t, aresp.GetExtra("expires_in")) - assert.Equal(t, "foo", aresp.GetExtra("scope")) - }, - }, - { - description: "pass and response should not have refresh token", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, - Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"authorization_code"}, - }, - GrantedScope: fosite.Arguments{"foo"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - code, sig, err := strategy.GenerateAuthorizeCode(context.Background(), nil) - require.NoError(t, err) - areq.Form.Add("code", code) - - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), sig, areq)) - }, - check: func(t *testing.T, aresp *fosite.AccessResponse) { - assert.NotEmpty(t, aresp.AccessToken) - assert.Equal(t, "bearer", aresp.TokenType) - assert.Empty(t, aresp.GetExtra("refresh_token")) - assert.NotEmpty(t, aresp.GetExtra("expires_in")) - assert.Equal(t, "foo", aresp.GetExtra("scope")) - }, - }, - } { - t.Run("case="+c.description, func(t *testing.T) { - config := &fosite.Config{ - ScopeStrategy: fosite.HierarchicScopeStrategy, - AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, - AccessTokenLifespan: time.Minute, - RefreshTokenScopes: []string{"offline"}, - } - h = GenericCodeTokenEndpointHandler{ - AccessRequestValidator: &AuthorizeExplicitGrantAccessRequestValidator{}, - CodeHandler: &AuthorizeCodeHandler{ - AuthorizeCodeStrategy: strategy, - }, - SessionHandler: &AuthorizeExplicitGrantSessionHandler{ - AuthorizeCodeStorage: store, - }, - AccessTokenStrategy: strategy, - RefreshTokenStrategy: strategy, - CoreStorage: store, - Config: config, - } - - if c.setup != nil { - c.setup(t, c.areq, config) - } - - aresp := fosite.NewAccessResponse() - err := h.PopulateTokenEndpointResponse(context.Background(), c.areq, aresp) - - if c.expectErr != nil { - require.EqualError(t, err, c.expectErr.Error(), "%+v", err) - } else { - require.NoError(t, err, "%+v", err) - } - - if c.check != nil { - c.check(t, aresp) - } - }) - } - }) - } -} - func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { for k, strategy := range map[string]CoreStrategy{ "hmac": &hmacshaStrategy, @@ -239,7 +43,8 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { TokenRevocationStorage: store, Config: config, } - for i, c := range []struct { + + testCases := []struct { description string areq *fosite.AccessRequest authreq *fosite.AuthorizeRequest @@ -257,7 +62,7 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because client is not granted the correct grant type", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, Request: fosite.Request{ Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{""}}, Session: &fosite.DefaultSession{}, @@ -269,9 +74,9 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because authorization code cannot be retrieved", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, Request: fosite.Request{ - Client: &fosite.DefaultClient{GrantTypes: []string{"authorization_code"}}, + Client: &fosite.DefaultClient{GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, @@ -286,10 +91,10 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because authorization code is expired", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, Request: fosite.Request{ Form: url.Values{"code": {"foo.bar"}}, - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, @@ -317,9 +122,9 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because client mismatch", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, @@ -346,16 +151,16 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because redirect uri was set during /authorize call, but not in /token call", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, Form: url.Values{"redirect_uri": []string{"request-redir"}}, Session: &fosite.DefaultSession{ ExpiresAt: map[fosite.TokenType]time.Time{ @@ -376,9 +181,9 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should pass", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, Form: url.Values{"redirect_uri": []string{"request-redir"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), @@ -386,7 +191,7 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, @@ -402,17 +207,17 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because code has been used already", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, Request: fosite.Request{ Form: url.Values{}, - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: fosite.Arguments{"authorization_code"}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, @@ -427,21 +232,23 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { }, expectErr: fosite.ErrInvalidGrant, }, - } { - t.Run(fmt.Sprintf("case=%d/description=%s", i, c.description), func(t *testing.T) { - if c.setup != nil { - c.setup(t, c.areq, c.authreq) + } + + for i, testCase := range testCases { + t.Run(fmt.Sprintf("case=%d/description=%s", i, testCase.description), func(t *testing.T) { + if testCase.setup != nil { + testCase.setup(t, testCase.areq, testCase.authreq) } - t.Logf("Processing %+v", c.areq.Client) + t.Logf("Processing %+v", testCase.areq.Client) - err := h.HandleTokenEndpointRequest(context.Background(), c.areq) - if c.expectErr != nil { - require.EqualError(t, err, c.expectErr.Error(), "%+v", err) + err := h.HandleTokenEndpointRequest(context.Background(), testCase.areq) + if testCase.expectErr != nil { + require.EqualError(t, err, testCase.expectErr.Error(), "%+v", err) } else { require.NoError(t, err, "%+v", err) - if c.check != nil { - c.check(t, c.areq, c.authreq) + if testCase.check != nil { + testCase.check(t, testCase.areq, testCase.authreq) } } }) @@ -450,16 +257,215 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { } } +func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { + for k, strategy := range map[string]CoreStrategy{ + "hmac": &hmacshaStrategy, + } { + t.Run("strategy="+k, func(t *testing.T) { + store := storage.NewMemoryStore() + + var h GenericCodeTokenEndpointHandler + + testCases := []struct { + areq *fosite.AccessRequest + description string + setup func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) + check func(t *testing.T, aresp *fosite.AccessResponse) + expectErr error + }{ + { + description: "should fail because not responsible", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"123"}, + }, + expectErr: fosite.ErrUnknownRequest, + }, + { + description: "should fail because authorization code cannot be retrieved", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + }, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + code, _, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + require.NoError(t, err) + areq.Form.Set("code", code) + }, + expectErr: fosite.ErrServerError, + }, + { + description: "should fail because authorization code is expired", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + Request: fosite.Request{ + Form: url.Values{"code": []string{"foo.bar"}}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + }, + Session: &fosite.DefaultSession{ + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.AuthorizeCode: time.Now().Add(-time.Hour).UTC(), + }, + }, + RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), "bar", areq)) + }, + expectErr: fosite.ErrInvalidRequest, + }, + { + description: "should pass with offline scope and refresh token", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode), string(fosite.GrantTypeRefreshToken)}, + }, + GrantedScope: fosite.Arguments{"foo", "offline"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + require.NoError(t, err) + areq.Form.Add("code", code) + + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, areq)) + }, + check: func(t *testing.T, aresp *fosite.AccessResponse) { + assert.NotEmpty(t, aresp.AccessToken) + assert.Equal(t, "bearer", aresp.TokenType) + assert.NotEmpty(t, aresp.GetExtra("refresh_token")) + assert.NotEmpty(t, aresp.GetExtra("expires_in")) + assert.Equal(t, "foo offline", aresp.GetExtra("scope")) + }, + }, + { + description: "should pass with refresh token always provided", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode), string(fosite.GrantTypeRefreshToken)}, + }, + GrantedScope: fosite.Arguments{"foo"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + config.RefreshTokenScopes = []string{} + code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + require.NoError(t, err) + areq.Form.Add("code", code) + + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, areq)) + }, + check: func(t *testing.T, aresp *fosite.AccessResponse) { + assert.NotEmpty(t, aresp.AccessToken) + assert.Equal(t, "bearer", aresp.TokenType) + assert.NotEmpty(t, aresp.GetExtra("refresh_token")) + assert.NotEmpty(t, aresp.GetExtra("expires_in")) + assert.Equal(t, "foo", aresp.GetExtra("scope")) + }, + }, + { + description: "pass and response should not have refresh token", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + }, + GrantedScope: fosite.Arguments{"foo"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + code, sig, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + require.NoError(t, err) + areq.Form.Add("code", code) + + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), sig, areq)) + }, + check: func(t *testing.T, aresp *fosite.AccessResponse) { + assert.NotEmpty(t, aresp.AccessToken) + assert.Equal(t, "bearer", aresp.TokenType) + assert.Empty(t, aresp.GetExtra("refresh_token")) + assert.NotEmpty(t, aresp.GetExtra("expires_in")) + assert.Equal(t, "foo", aresp.GetExtra("scope")) + }, + }, + } + + for _, testCase := range testCases { + t.Run("case="+testCase.description, func(t *testing.T) { + config := &fosite.Config{ + ScopeStrategy: fosite.HierarchicScopeStrategy, + AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, + AccessTokenLifespan: time.Minute, + RefreshTokenScopes: []string{"offline"}, + } + h = GenericCodeTokenEndpointHandler{ + AccessRequestValidator: &AuthorizeExplicitGrantAccessRequestValidator{}, + CodeHandler: &AuthorizeCodeHandler{ + AuthorizeCodeStrategy: strategy, + }, + SessionHandler: &AuthorizeExplicitGrantSessionHandler{ + AuthorizeCodeStorage: store, + }, + AccessTokenStrategy: strategy, + RefreshTokenStrategy: strategy, + CoreStorage: store, + Config: config, + } + + if testCase.setup != nil { + testCase.setup(t, testCase.areq, config) + } + + aresp := fosite.NewAccessResponse() + err := h.PopulateTokenEndpointResponse(context.Background(), testCase.areq, aresp) + + if testCase.expectErr != nil { + require.EqualError(t, err, testCase.expectErr.Error(), "%+v", err) + } else { + require.NoError(t, err, "%+v", err) + } + + if testCase.check != nil { + testCase.check(t, aresp) + } + }) + } + }) + } +} + func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { var mockTransactional *internal.MockTransactional var mockCoreStore *internal.MockCoreStorage var mockAuthorizeStore *internal.MockAuthorizeCodeStorage strategy := hmacshaStrategy request := &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"authorization_code"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, Request: fosite.Request{ Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"authorization_code", "refresh_token"}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode), string(fosite.GrantTypeRefreshToken)}, }, GrantedScope: fosite.Arguments{"offline"}, Session: &fosite.DefaultSession{}, @@ -483,7 +489,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { AuthorizeCodeStorage } - for _, testCase := range []struct { + testCases := []struct { description string setup func() expectError error @@ -657,7 +663,9 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { }, expectError: fosite.ErrServerError, }, - } { + } + + for _, testCase := range testCases { t.Run(fmt.Sprintf("scenario=%s", testCase.description), func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() diff --git a/handler/rfc8628/token_handler_test.go b/handler/rfc8628/token_handler_test.go index 8da59e80f..28556f0bc 100644 --- a/handler/rfc8628/token_handler_test.go +++ b/handler/rfc8628/token_handler_test.go @@ -45,7 +45,7 @@ var RFC8628HMACSHAStrategy = DefaultDeviceStrategy{ }, } -func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { +func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { for k, strategy := range map[string]struct { oauth2.CoreStrategy RFC8628CodeStrategy @@ -55,201 +55,212 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { t.Run("strategy="+k, func(t *testing.T) { store := storage.NewMemoryStore() - var h oauth2.GenericCodeTokenEndpointHandler - for _, c := range []struct { - areq *fosite.AccessRequest + h := oauth2.GenericCodeTokenEndpointHandler{ + AccessRequestValidator: &DeviceAccessRequestValidator{}, + CodeHandler: &DeviceCodeHandler{ + DeviceRateLimitStrategy: strategy, + DeviceCodeStrategy: strategy, + }, + SessionHandler: &DeviceSessionHandler{ + DeviceCodeStorage: store, + }, + CoreStorage: store, + AccessTokenStrategy: strategy.CoreStrategy, + RefreshTokenStrategy: strategy.CoreStrategy, + Config: &fosite.Config{ + ScopeStrategy: fosite.HierarchicScopeStrategy, + AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, + DeviceAndUserCodeLifespan: time.Minute, + }, + } + + testCases := []struct { description string - setup func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) - check func(t *testing.T, aresp *fosite.AccessResponse) + areq *fosite.AccessRequest + authreq *fosite.DeviceRequest + setup func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) + check func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) expectErr error }{ { description: "should fail because not responsible", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"123"}, + GrantTypes: fosite.Arguments{"12345678"}, }, expectErr: fosite.ErrUnknownRequest, }, { - description: "should fail because device code cannot be retrieved", + description: "should fail because client is not granted the correct grant type", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, - }, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{""}}, Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - code, _, err := strategy.GenerateDeviceCode(context.TODO()) + expectErr: fosite.ErrUnauthorizedClient, + }, + { + description: "should fail because device code could not be retrieved", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + Request: fosite.Request{ + Client: &fosite.DefaultClient{GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Session: &DefaultDeviceFlowSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { + deviceCode, _, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) - areq.Form.Set("device_code", code) + areq.Form = url.Values{"device_code": {deviceCode}} }, - expectErr: fosite.ErrServerError, + expectErr: fosite.ErrInvalidGrant, }, { - description: "should fail because device code is expired", + description: "should fail because user has not completed the browser flow", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, - }, + Form: url.Values{}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Session: &DefaultDeviceFlowSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + authreq: &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, Session: &DefaultDeviceFlowSession{ ExpiresAt: map[fosite.TokenType]time.Time{ fosite.DeviceCode: time.Now().Add(-time.Hour).UTC(), }, - BrowserFlowCompleted: true, + BrowserFlowCompleted: false, }, RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { code, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) areq.Form.Add("device_code", code) - require.NoError(t, store.CreateDeviceCodeSession(context.Background(), signature, areq)) + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) }, - expectErr: fosite.ErrInvalidRequest, + expectErr: fosite.ErrAuthorizationPending, }, { - description: "should pass with offline scope and refresh token", + description: "should fail because device code has expired", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode), string(fosite.GrantTypeRefreshToken)}, + ID: "foo", + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, }, GrantedScope: fosite.Arguments{"foo", "offline"}, + Session: &DefaultDeviceFlowSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + authreq: &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, Session: &DefaultDeviceFlowSession{ + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.DeviceCode: time.Now().Add(-time.Hour).UTC(), + }, BrowserFlowCompleted: true, }, - RequestedAt: time.Now().UTC(), + RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { code, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) areq.Form.Add("device_code", code) - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, areq)) - }, - check: func(t *testing.T, aresp *fosite.AccessResponse) { - assert.NotEmpty(t, aresp.AccessToken) - assert.Equal(t, "bearer", aresp.TokenType) - assert.NotEmpty(t, aresp.GetExtra("refresh_token")) - assert.NotEmpty(t, aresp.GetExtra("expires_in")) - assert.Equal(t, "foo offline", aresp.GetExtra("scope")) + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) }, + expectErr: fosite.ErrDeviceExpiredToken, }, { - description: "should pass with refresh token always provided", + description: "should fail because client mismatch", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode), string(fosite.GrantTypeRefreshToken)}, - }, - GrantedScope: fosite.Arguments{"foo"}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Session: &DefaultDeviceFlowSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + authreq: &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "bar"}, Session: &DefaultDeviceFlowSession{ + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.DeviceCode: time.Now().Add(time.Hour).UTC(), + }, BrowserFlowCompleted: true, }, - RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - config.RefreshTokenScopes = []string{} - code, signature, err := strategy.GenerateDeviceCode(context.TODO()) + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { + token, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) - areq.Form.Add("device_code", code) + areq.Form = url.Values{"device_code": {token}} - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, areq)) - }, - check: func(t *testing.T, aresp *fosite.AccessResponse) { - assert.NotEmpty(t, aresp.AccessToken) - assert.Equal(t, "bearer", aresp.TokenType) - assert.NotEmpty(t, aresp.GetExtra("refresh_token")) - assert.NotEmpty(t, aresp.GetExtra("expires_in")) - assert.Equal(t, "foo", aresp.GetExtra("scope")) + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) }, + expectErr: fosite.ErrInvalidGrant, }, { - description: "pass and response should not have refresh token", + description: "should pass", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, - }, - GrantedScope: fosite.Arguments{"foo"}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Session: &DefaultDeviceFlowSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + authreq: &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, Session: &DefaultDeviceFlowSession{ BrowserFlowCompleted: true, }, RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - code, signature, err := strategy.GenerateDeviceCode(context.TODO()) + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { + token, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) - areq.Form.Add("device_code", code) - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, areq)) - }, - check: func(t *testing.T, aresp *fosite.AccessResponse) { - assert.NotEmpty(t, aresp.AccessToken) - assert.Equal(t, "bearer", aresp.TokenType) - assert.Empty(t, aresp.GetExtra("refresh_token")) - assert.NotEmpty(t, aresp.GetExtra("expires_in")) - assert.Equal(t, "foo", aresp.GetExtra("scope")) + areq.Form = url.Values{"device_code": {token}} + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) }, }, - } { - t.Run("case="+c.description, func(t *testing.T) { - config := &fosite.Config{ - ScopeStrategy: fosite.HierarchicScopeStrategy, - AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, - AccessTokenLifespan: time.Minute, - RefreshTokenScopes: []string{"offline"}, - } - h = oauth2.GenericCodeTokenEndpointHandler{ - AccessRequestValidator: &DeviceAccessRequestValidator{}, - CodeHandler: &DeviceCodeHandler{ - DeviceRateLimitStrategy: strategy, - DeviceCodeStrategy: strategy, - }, - SessionHandler: &DeviceSessionHandler{ - DeviceCodeStorage: store, - }, - AccessTokenStrategy: strategy.CoreStrategy, - RefreshTokenStrategy: strategy.CoreStrategy, - Config: config, - CoreStorage: store, - TokenRevocationStorage: store, - } + } - if c.setup != nil { - c.setup(t, c.areq, config) + for i, testCase := range testCases { + t.Run(fmt.Sprintf("case=%d/description=%s", i, testCase.description), func(t *testing.T) { + if testCase.setup != nil { + testCase.setup(t, testCase.areq, testCase.authreq) } - aresp := fosite.NewAccessResponse() - err := h.PopulateTokenEndpointResponse(context.TODO(), c.areq, aresp) + t.Logf("Processing %+v", testCase.areq.Client) - if c.expectErr != nil { - require.EqualError(t, err, c.expectErr.Error(), "%+v", err) + err := h.HandleTokenEndpointRequest(context.Background(), testCase.areq) + if testCase.expectErr != nil { + require.EqualError(t, err, testCase.expectErr.Error(), "%+v", err) } else { require.NoError(t, err, "%+v", err) - } - - if c.check != nil { - c.check(t, aresp) + if testCase.check != nil { + testCase.check(t, testCase.areq, testCase.authreq) + } } }) } @@ -257,7 +268,7 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { } } -func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { +func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { for k, strategy := range map[string]struct { oauth2.CoreStrategy RFC8628CodeStrategy @@ -267,209 +278,204 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { t.Run("strategy="+k, func(t *testing.T) { store := storage.NewMemoryStore() - h := oauth2.GenericCodeTokenEndpointHandler{ - AccessRequestValidator: &DeviceAccessRequestValidator{}, - CodeHandler: &DeviceCodeHandler{ - DeviceRateLimitStrategy: strategy, - DeviceCodeStrategy: strategy, - }, - SessionHandler: &DeviceSessionHandler{ - DeviceCodeStorage: store, - }, - CoreStorage: store, - AccessTokenStrategy: strategy.CoreStrategy, - RefreshTokenStrategy: strategy.CoreStrategy, - Config: &fosite.Config{ - ScopeStrategy: fosite.HierarchicScopeStrategy, - AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, - DeviceAndUserCodeLifespan: time.Minute, - }, - } - for i, c := range []struct { - description string + var h oauth2.GenericCodeTokenEndpointHandler + + testCases := []struct { areq *fosite.AccessRequest - authreq *fosite.DeviceRequest - setup func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) - check func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) + description string + setup func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) + check func(t *testing.T, aresp *fosite.AccessResponse) expectErr error }{ { description: "should fail because not responsible", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"12345678"}, + GrantTypes: fosite.Arguments{"123"}, }, expectErr: fosite.ErrUnknownRequest, }, { - description: "should fail because client is not granted the correct grant type", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, - Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{""}}, - Session: &DefaultDeviceFlowSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - expectErr: fosite.ErrUnauthorizedClient, - }, - { - description: "should fail because device code could not be retrieved", + description: "should fail because device code cannot be retrieved", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ - Client: &fosite.DefaultClient{GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + }, Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { - deviceCode, _, err := strategy.GenerateDeviceCode(context.TODO()) + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + code, _, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) - areq.Form = url.Values{"device_code": {deviceCode}} + areq.Form.Set("device_code", code) }, - expectErr: fosite.ErrInvalidGrant, + expectErr: fosite.ErrServerError, }, { - description: "should fail because user has not completed the browser flow", + description: "should fail because device code is expired", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, - Session: &DefaultDeviceFlowSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - authreq: &fosite.DeviceRequest{ - Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + }, Session: &DefaultDeviceFlowSession{ ExpiresAt: map[fosite.TokenType]time.Time{ fosite.DeviceCode: time.Now().Add(-time.Hour).UTC(), }, - BrowserFlowCompleted: false, + BrowserFlowCompleted: true, }, RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { code, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) areq.Form.Add("device_code", code) - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) + require.NoError(t, store.CreateDeviceCodeSession(context.Background(), signature, areq)) }, - expectErr: fosite.ErrAuthorizationPending, + expectErr: fosite.ErrInvalidRequest, }, { - description: "should fail because device code has expired", + description: "should pass with offline scope and refresh token", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ - ID: "foo", - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode), string(fosite.GrantTypeRefreshToken)}, }, GrantedScope: fosite.Arguments{"foo", "offline"}, - Session: &DefaultDeviceFlowSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - authreq: &fosite.DeviceRequest{ - Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, Session: &DefaultDeviceFlowSession{ - ExpiresAt: map[fosite.TokenType]time.Time{ - fosite.DeviceCode: time.Now().Add(-time.Hour).UTC(), - }, BrowserFlowCompleted: true, }, - RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), + RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { code, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) areq.Form.Add("device_code", code) - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, areq)) + }, + check: func(t *testing.T, aresp *fosite.AccessResponse) { + assert.NotEmpty(t, aresp.AccessToken) + assert.Equal(t, "bearer", aresp.TokenType) + assert.NotEmpty(t, aresp.GetExtra("refresh_token")) + assert.NotEmpty(t, aresp.GetExtra("expires_in")) + assert.Equal(t, "foo offline", aresp.GetExtra("scope")) }, - expectErr: fosite.ErrDeviceExpiredToken, }, { - description: "should fail because client mismatch", + description: "should pass with refresh token always provided", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, - Session: &DefaultDeviceFlowSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - authreq: &fosite.DeviceRequest{ - Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "bar"}, + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode), string(fosite.GrantTypeRefreshToken)}, + }, + GrantedScope: fosite.Arguments{"foo"}, Session: &DefaultDeviceFlowSession{ - ExpiresAt: map[fosite.TokenType]time.Time{ - fosite.DeviceCode: time.Now().Add(time.Hour).UTC(), - }, BrowserFlowCompleted: true, }, + RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { - token, signature, err := strategy.GenerateDeviceCode(context.TODO()) + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + config.RefreshTokenScopes = []string{} + code, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) - areq.Form = url.Values{"device_code": {token}} + areq.Form.Add("device_code", code) - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, areq)) + }, + check: func(t *testing.T, aresp *fosite.AccessResponse) { + assert.NotEmpty(t, aresp.AccessToken) + assert.Equal(t, "bearer", aresp.TokenType) + assert.NotEmpty(t, aresp.GetExtra("refresh_token")) + assert.NotEmpty(t, aresp.GetExtra("expires_in")) + assert.Equal(t, "foo", aresp.GetExtra("scope")) }, - expectErr: fosite.ErrInvalidGrant, }, { - description: "should pass", + description: "pass and response should not have refresh token", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, - Session: &DefaultDeviceFlowSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - authreq: &fosite.DeviceRequest{ - Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + }, + GrantedScope: fosite.Arguments{"foo"}, Session: &DefaultDeviceFlowSession{ BrowserFlowCompleted: true, }, RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { - token, signature, err := strategy.GenerateDeviceCode(context.TODO()) + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + code, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) + areq.Form.Add("device_code", code) - areq.Form = url.Values{"device_code": {token}} - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, areq)) + }, + check: func(t *testing.T, aresp *fosite.AccessResponse) { + assert.NotEmpty(t, aresp.AccessToken) + assert.Equal(t, "bearer", aresp.TokenType) + assert.Empty(t, aresp.GetExtra("refresh_token")) + assert.NotEmpty(t, aresp.GetExtra("expires_in")) + assert.Equal(t, "foo", aresp.GetExtra("scope")) }, }, - } { - t.Run(fmt.Sprintf("case=%d/description=%s", i, c.description), func(t *testing.T) { - if c.setup != nil { - c.setup(t, c.areq, c.authreq) + } + + for _, testCase := range testCases { + t.Run("case="+testCase.description, func(t *testing.T) { + config := &fosite.Config{ + ScopeStrategy: fosite.HierarchicScopeStrategy, + AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, + AccessTokenLifespan: time.Minute, + RefreshTokenScopes: []string{"offline"}, + } + h = oauth2.GenericCodeTokenEndpointHandler{ + AccessRequestValidator: &DeviceAccessRequestValidator{}, + CodeHandler: &DeviceCodeHandler{ + DeviceRateLimitStrategy: strategy, + DeviceCodeStrategy: strategy, + }, + SessionHandler: &DeviceSessionHandler{ + DeviceCodeStorage: store, + }, + AccessTokenStrategy: strategy.CoreStrategy, + RefreshTokenStrategy: strategy.CoreStrategy, + Config: config, + CoreStorage: store, + TokenRevocationStorage: store, } - t.Logf("Processing %+v", c.areq.Client) + if testCase.setup != nil { + testCase.setup(t, testCase.areq, config) + } + + aresp := fosite.NewAccessResponse() + err := h.PopulateTokenEndpointResponse(context.TODO(), testCase.areq, aresp) - err := h.HandleTokenEndpointRequest(context.Background(), c.areq) - if c.expectErr != nil { - require.EqualError(t, err, c.expectErr.Error(), "%+v", err) + if testCase.expectErr != nil { + require.EqualError(t, err, testCase.expectErr.Error(), "%+v", err) } else { require.NoError(t, err, "%+v", err) - if c.check != nil { - c.check(t, c.areq, c.authreq) - } + } + + if testCase.check != nil { + testCase.check(t, aresp) } }) } @@ -514,7 +520,7 @@ func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { DeviceCodeStorage } - for _, testCase := range []struct { + testCases := []struct { description string setup func() expectError error @@ -688,7 +694,9 @@ func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { }, expectError: fosite.ErrServerError, }, - } { + } + + for _, testCase := range testCases { t.Run(fmt.Sprintf("scenario=%s", testCase.description), func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() From 4078ede70ad209d32f9e329b81ad5db9faa9c526 Mon Sep 17 00:00:00 2001 From: dushu Date: Tue, 19 Mar 2024 11:50:42 -0600 Subject: [PATCH 14/17] chore: resolve comments --- .../oauth2/flow_authorize_code_token_test.go | 434 +++++++++--------- 1 file changed, 217 insertions(+), 217 deletions(-) diff --git a/handler/oauth2/flow_authorize_code_token_test.go b/handler/oauth2/flow_authorize_code_token_test.go index df15fae81..dd01a7fab 100644 --- a/handler/oauth2/flow_authorize_code_token_test.go +++ b/handler/oauth2/flow_authorize_code_token_test.go @@ -21,6 +21,205 @@ import ( "github.com/stretchr/testify/require" ) +func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { + for k, strategy := range map[string]CoreStrategy{ + "hmac": &hmacshaStrategy, + } { + t.Run("strategy="+k, func(t *testing.T) { + store := storage.NewMemoryStore() + + var h GenericCodeTokenEndpointHandler + + testCases := []struct { + areq *fosite.AccessRequest + description string + setup func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) + check func(t *testing.T, aresp *fosite.AccessResponse) + expectErr error + }{ + { + description: "should fail because not responsible", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"123"}, + }, + expectErr: fosite.ErrUnknownRequest, + }, + { + description: "should fail because authorization code cannot be retrieved", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"authorization_code"}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"authorization_code"}, + }, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + code, _, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + require.NoError(t, err) + areq.Form.Set("code", code) + }, + expectErr: fosite.ErrServerError, + }, + { + description: "should fail because authorization code is expired", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"authorization_code"}, + Request: fosite.Request{ + Form: url.Values{"code": []string{"foo.bar"}}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"authorization_code"}, + }, + Session: &fosite.DefaultSession{ + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.AuthorizeCode: time.Now().Add(-time.Hour).UTC(), + }, + }, + RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), "bar", areq)) + }, + expectErr: fosite.ErrInvalidRequest, + }, + { + description: "should pass with offline scope and refresh token", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"authorization_code"}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"authorization_code", "refresh_token"}, + }, + GrantedScope: fosite.Arguments{"foo", "offline"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + require.NoError(t, err) + areq.Form.Add("code", code) + + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, areq)) + }, + check: func(t *testing.T, aresp *fosite.AccessResponse) { + assert.NotEmpty(t, aresp.AccessToken) + assert.Equal(t, "bearer", aresp.TokenType) + assert.NotEmpty(t, aresp.GetExtra("refresh_token")) + assert.NotEmpty(t, aresp.GetExtra("expires_in")) + assert.Equal(t, "foo offline", aresp.GetExtra("scope")) + }, + }, + { + description: "should pass with refresh token always provided", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"authorization_code"}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"authorization_code", "refresh_token"}, + }, + GrantedScope: fosite.Arguments{"foo"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + config.RefreshTokenScopes = []string{} + code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + require.NoError(t, err) + areq.Form.Add("code", code) + + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, areq)) + }, + check: func(t *testing.T, aresp *fosite.AccessResponse) { + assert.NotEmpty(t, aresp.AccessToken) + assert.Equal(t, "bearer", aresp.TokenType) + assert.NotEmpty(t, aresp.GetExtra("refresh_token")) + assert.NotEmpty(t, aresp.GetExtra("expires_in")) + assert.Equal(t, "foo", aresp.GetExtra("scope")) + }, + }, + { + description: "pass and response should not have refresh token", + areq: &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"authorization_code"}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"authorization_code"}, + }, + GrantedScope: fosite.Arguments{"foo"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + code, sig, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + require.NoError(t, err) + areq.Form.Add("code", code) + + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), sig, areq)) + }, + check: func(t *testing.T, aresp *fosite.AccessResponse) { + assert.NotEmpty(t, aresp.AccessToken) + assert.Equal(t, "bearer", aresp.TokenType) + assert.Empty(t, aresp.GetExtra("refresh_token")) + assert.NotEmpty(t, aresp.GetExtra("expires_in")) + assert.Equal(t, "foo", aresp.GetExtra("scope")) + }, + }, + } + + for _, testCase := range testCases { + t.Run("case="+testCase.description, func(t *testing.T) { + config := &fosite.Config{ + ScopeStrategy: fosite.HierarchicScopeStrategy, + AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, + AccessTokenLifespan: time.Minute, + RefreshTokenScopes: []string{"offline"}, + } + h = GenericCodeTokenEndpointHandler{ + AccessRequestValidator: &AuthorizeExplicitGrantAccessRequestValidator{}, + CodeHandler: &AuthorizeCodeHandler{ + AuthorizeCodeStrategy: strategy, + }, + SessionHandler: &AuthorizeExplicitGrantSessionHandler{ + AuthorizeCodeStorage: store, + }, + AccessTokenStrategy: strategy, + RefreshTokenStrategy: strategy, + CoreStorage: store, + Config: config, + } + + if testCase.setup != nil { + testCase.setup(t, testCase.areq, config) + } + + aresp := fosite.NewAccessResponse() + err := h.PopulateTokenEndpointResponse(context.Background(), testCase.areq, aresp) + + if testCase.expectErr != nil { + require.EqualError(t, err, testCase.expectErr.Error(), "%+v", err) + } else { + require.NoError(t, err, "%+v", err) + } + + if testCase.check != nil { + testCase.check(t, aresp) + } + }) + } + }) + } +} + func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { for k, strategy := range map[string]CoreStrategy{ "hmac": &hmacshaStrategy, @@ -62,7 +261,7 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because client is not granted the correct grant type", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{""}}, Session: &fosite.DefaultSession{}, @@ -74,9 +273,9 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because authorization code cannot be retrieved", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ - Client: &fosite.DefaultClient{GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, + Client: &fosite.DefaultClient{GrantTypes: []string{"authorization_code"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, @@ -91,10 +290,10 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because authorization code is expired", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ Form: url.Values{"code": {"foo.bar"}}, - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, @@ -122,9 +321,9 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because client mismatch", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, @@ -151,16 +350,16 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because redirect uri was set during /authorize call, but not in /token call", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, Form: url.Values{"redirect_uri": []string{"request-redir"}}, Session: &fosite.DefaultSession{ ExpiresAt: map[fosite.TokenType]time.Time{ @@ -181,9 +380,9 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should pass", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, Form: url.Values{"redirect_uri": []string{"request-redir"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), @@ -191,7 +390,7 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, @@ -207,17 +406,17 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because code has been used already", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ Form: url.Values{}, - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: fosite.Arguments{"authorization_code"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, @@ -257,215 +456,16 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { } } -func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { - for k, strategy := range map[string]CoreStrategy{ - "hmac": &hmacshaStrategy, - } { - t.Run("strategy="+k, func(t *testing.T) { - store := storage.NewMemoryStore() - - var h GenericCodeTokenEndpointHandler - - testCases := []struct { - areq *fosite.AccessRequest - description string - setup func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) - check func(t *testing.T, aresp *fosite.AccessResponse) - expectErr error - }{ - { - description: "should fail because not responsible", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"123"}, - }, - expectErr: fosite.ErrUnknownRequest, - }, - { - description: "should fail because authorization code cannot be retrieved", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, - Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, - }, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - code, _, err := strategy.GenerateAuthorizeCode(context.Background(), nil) - require.NoError(t, err) - areq.Form.Set("code", code) - }, - expectErr: fosite.ErrServerError, - }, - { - description: "should fail because authorization code is expired", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, - Request: fosite.Request{ - Form: url.Values{"code": []string{"foo.bar"}}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, - }, - Session: &fosite.DefaultSession{ - ExpiresAt: map[fosite.TokenType]time.Time{ - fosite.AuthorizeCode: time.Now().Add(-time.Hour).UTC(), - }, - }, - RequestedAt: time.Now().Add(-2 * time.Hour).UTC(), - }, - }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), "bar", areq)) - }, - expectErr: fosite.ErrInvalidRequest, - }, - { - description: "should pass with offline scope and refresh token", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, - Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode), string(fosite.GrantTypeRefreshToken)}, - }, - GrantedScope: fosite.Arguments{"foo", "offline"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) - require.NoError(t, err) - areq.Form.Add("code", code) - - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, areq)) - }, - check: func(t *testing.T, aresp *fosite.AccessResponse) { - assert.NotEmpty(t, aresp.AccessToken) - assert.Equal(t, "bearer", aresp.TokenType) - assert.NotEmpty(t, aresp.GetExtra("refresh_token")) - assert.NotEmpty(t, aresp.GetExtra("expires_in")) - assert.Equal(t, "foo offline", aresp.GetExtra("scope")) - }, - }, - { - description: "should pass with refresh token always provided", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, - Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode), string(fosite.GrantTypeRefreshToken)}, - }, - GrantedScope: fosite.Arguments{"foo"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - config.RefreshTokenScopes = []string{} - code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) - require.NoError(t, err) - areq.Form.Add("code", code) - - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, areq)) - }, - check: func(t *testing.T, aresp *fosite.AccessResponse) { - assert.NotEmpty(t, aresp.AccessToken) - assert.Equal(t, "bearer", aresp.TokenType) - assert.NotEmpty(t, aresp.GetExtra("refresh_token")) - assert.NotEmpty(t, aresp.GetExtra("expires_in")) - assert.Equal(t, "foo", aresp.GetExtra("scope")) - }, - }, - { - description: "pass and response should not have refresh token", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, - Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, - }, - GrantedScope: fosite.Arguments{"foo"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), - }, - }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { - code, sig, err := strategy.GenerateAuthorizeCode(context.Background(), nil) - require.NoError(t, err) - areq.Form.Add("code", code) - - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), sig, areq)) - }, - check: func(t *testing.T, aresp *fosite.AccessResponse) { - assert.NotEmpty(t, aresp.AccessToken) - assert.Equal(t, "bearer", aresp.TokenType) - assert.Empty(t, aresp.GetExtra("refresh_token")) - assert.NotEmpty(t, aresp.GetExtra("expires_in")) - assert.Equal(t, "foo", aresp.GetExtra("scope")) - }, - }, - } - - for _, testCase := range testCases { - t.Run("case="+testCase.description, func(t *testing.T) { - config := &fosite.Config{ - ScopeStrategy: fosite.HierarchicScopeStrategy, - AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, - AccessTokenLifespan: time.Minute, - RefreshTokenScopes: []string{"offline"}, - } - h = GenericCodeTokenEndpointHandler{ - AccessRequestValidator: &AuthorizeExplicitGrantAccessRequestValidator{}, - CodeHandler: &AuthorizeCodeHandler{ - AuthorizeCodeStrategy: strategy, - }, - SessionHandler: &AuthorizeExplicitGrantSessionHandler{ - AuthorizeCodeStorage: store, - }, - AccessTokenStrategy: strategy, - RefreshTokenStrategy: strategy, - CoreStorage: store, - Config: config, - } - - if testCase.setup != nil { - testCase.setup(t, testCase.areq, config) - } - - aresp := fosite.NewAccessResponse() - err := h.PopulateTokenEndpointResponse(context.Background(), testCase.areq, aresp) - - if testCase.expectErr != nil { - require.EqualError(t, err, testCase.expectErr.Error(), "%+v", err) - } else { - require.NoError(t, err, "%+v", err) - } - - if testCase.check != nil { - testCase.check(t, aresp) - } - }) - } - }) - } -} - func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { var mockTransactional *internal.MockTransactional var mockCoreStore *internal.MockCoreStorage var mockAuthorizeStore *internal.MockAuthorizeCodeStorage strategy := hmacshaStrategy request := &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode), string(fosite.GrantTypeRefreshToken)}, + GrantTypes: fosite.Arguments{"authorization_code", "refresh_token"}, }, GrantedScope: fosite.Arguments{"offline"}, Session: &fosite.DefaultSession{}, From 91d5a1165185cdd489c17b1ee1c351701c0ecbd7 Mon Sep 17 00:00:00 2001 From: dushu Date: Sat, 23 Mar 2024 18:09:11 -0600 Subject: [PATCH 15/17] fix: fix oauth2 core storage interface and device flow session type assertion --- handler/oauth2/flow_authorize_code_token_test.go | 2 +- handler/oauth2/storage.go | 1 + handler/rfc8628/strategy_hmacsha.go | 4 ++-- handler/rfc8628/token_handler.go | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/handler/oauth2/flow_authorize_code_token_test.go b/handler/oauth2/flow_authorize_code_token_test.go index dd01a7fab..aac54dfe2 100644 --- a/handler/oauth2/flow_authorize_code_token_test.go +++ b/handler/oauth2/flow_authorize_code_token_test.go @@ -31,8 +31,8 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { var h GenericCodeTokenEndpointHandler testCases := []struct { - areq *fosite.AccessRequest description string + areq *fosite.AccessRequest setup func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) check func(t *testing.T, aresp *fosite.AccessResponse) expectErr error diff --git a/handler/oauth2/storage.go b/handler/oauth2/storage.go index 5852cc174..fe121a9cc 100644 --- a/handler/oauth2/storage.go +++ b/handler/oauth2/storage.go @@ -10,6 +10,7 @@ import ( ) type CoreStorage interface { + AuthorizeCodeStorage AccessTokenStorage RefreshTokenStorage } diff --git a/handler/rfc8628/strategy_hmacsha.go b/handler/rfc8628/strategy_hmacsha.go index 7c41b1d19..65e65fa61 100644 --- a/handler/rfc8628/strategy_hmacsha.go +++ b/handler/rfc8628/strategy_hmacsha.go @@ -22,8 +22,8 @@ import ( // DeviceFlowSession is a fosite.Session container specific for the device flow. type DeviceFlowSession interface { - // BrowserFlowCompleted returns the flag indicating whether user has completed the browser flow or not. - BrowserFlowCompleted() bool + // GetBrowserFlowCompleted returns the flag indicating whether user has completed the browser flow or not. + GetBrowserFlowCompleted() bool // SetBrowserFlowCompleted allows client to mark user has completed the browser flow. SetBrowserFlowCompleted(flag bool) diff --git a/handler/rfc8628/token_handler.go b/handler/rfc8628/token_handler.go index dd71a7e83..4cb318834 100644 --- a/handler/rfc8628/token_handler.go +++ b/handler/rfc8628/token_handler.go @@ -67,7 +67,7 @@ func (s DeviceSessionHandler) Session(ctx context.Context, requester fosite.Acce return nil, errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } - session, ok := req.GetSession().(*DefaultDeviceFlowSession) + session, ok := req.GetSession().(DeviceFlowSession) if !ok { return nil, fosite.ErrServerError.WithHint("Wrong authorization request session.") } From 9994a1559b5e9f4f83aaf28c0e8f7b917b74d9b2 Mon Sep 17 00:00:00 2001 From: Nikos Date: Thu, 28 Mar 2024 13:47:50 +0200 Subject: [PATCH 16/17] fix: implement rate limiting --- compose/compose_strategy.go | 11 ++-- errors.go | 2 +- go.mod | 3 +- go.sum | 7 +-- handler/rfc8628/strategy.go | 2 +- handler/rfc8628/strategy_hmacsha.go | 79 +++++++++++++++++++----- handler/rfc8628/strategy_hmacsha_test.go | 33 +++++++--- handler/rfc8628/token_handler.go | 7 ++- handler/rfc8628/token_handler_test.go | 78 ++++++++++++++++++++--- 9 files changed, 174 insertions(+), 48 deletions(-) diff --git a/compose/compose_strategy.go b/compose/compose_strategy.go index d9cdc39f3..7316942ed 100644 --- a/compose/compose_strategy.go +++ b/compose/compose_strategy.go @@ -6,13 +6,13 @@ package compose import ( "context" + "github.com/coocood/freecache" "github.com/ory/fosite" "github.com/ory/fosite/handler/oauth2" "github.com/ory/fosite/handler/openid" "github.com/ory/fosite/handler/rfc8628" "github.com/ory/fosite/token/hmac" "github.com/ory/fosite/token/jwt" - "github.com/patrickmn/go-cache" ) type CommonStrategy struct { @@ -58,11 +58,8 @@ func NewOpenIDConnectStrategy(keyGetter func(context.Context) (interface{}, erro // Create a new device strategy func NewDeviceStrategy(config fosite.Configurator) *rfc8628.DefaultDeviceStrategy { return &rfc8628.DefaultDeviceStrategy{ - Enigma: &hmac.HMACStrategy{Config: config}, - RateLimiterCache: cache.New( - config.GetDeviceAndUserCodeLifespan(context.TODO()), - config.GetDeviceAndUserCodeLifespan(context.TODO())*2, - ), - Config: config, + Enigma: &hmac.HMACStrategy{Config: config}, + RateLimiterCache: freecache.NewCache(1024 * 1024), + Config: config, } } diff --git a/errors.go b/errors.go index 18dc1ab3b..a9f13493e 100644 --- a/errors.go +++ b/errors.go @@ -258,7 +258,7 @@ const ( errRegistrationNotSupportedName = "registration_not_supported" errJTIKnownName = "jti_known" errAuthorizationPending = "authorization_pending" - errPollingIntervalRateLimited = "polling_interval_rate_limited" + errPollingIntervalRateLimited = "slow_down" errDeviceExpiredToken = "expired_token" ) diff --git a/go.mod b/go.mod index ff3a6610c..8ab92e783 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ replace github.com/gorilla/sessions => github.com/gorilla/sessions v1.2.1 require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 + github.com/coocood/freecache v1.2.4 github.com/cristalhq/jwt/v4 v4.0.2 github.com/dgraph-io/ristretto v0.1.1 github.com/ecordell/optgen v0.0.9 @@ -25,7 +26,6 @@ require ( github.com/ory/go-convenience v0.1.0 github.com/ory/x v0.0.613 github.com/parnurzeal/gorequest v0.2.15 - github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.4 github.com/tidwall/gjson v1.14.3 @@ -34,7 +34,6 @@ require ( golang.org/x/net v0.20.0 golang.org/x/oauth2 v0.15.0 golang.org/x/text v0.14.0 - golang.org/x/time v0.4.0 ) require ( diff --git a/go.sum b/go.sum index 13b97e2f1..45cc21f4a 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,7 @@ github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqy github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -56,6 +57,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coocood/freecache v1.2.4 h1:UdR6Yz/X1HW4fZOuH0Z94KwG851GWOSknua5VUbb/5M= +github.com/coocood/freecache v1.2.4/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -344,8 +347,6 @@ github.com/ory/x v0.0.613 h1:MHT0scH7hcrOkc3aH7qqYLzXVJkjhB0szWTwpD2lh8Q= github.com/ory/x v0.0.613/go.mod h1:uH065puz8neija0neqwIN3PmXXfDsB9VbZTZ20Znoos= github.com/parnurzeal/gorequest v0.2.15 h1:oPjDCsF5IkD4gUk6vIgsxYNaSgvAnIh1EJeROn3HdJU= github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -675,8 +676,6 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY= -golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/handler/rfc8628/strategy.go b/handler/rfc8628/strategy.go index 33900a20c..67ca28084 100644 --- a/handler/rfc8628/strategy.go +++ b/handler/rfc8628/strategy.go @@ -18,7 +18,7 @@ type RFC8628CodeStrategy interface { // DeviceRateLimitStrategy handles the rate limiting strategy type DeviceRateLimitStrategy interface { - ShouldRateLimit(ctx context.Context, code string) bool + ShouldRateLimit(ctx context.Context, code string) (bool, error) } // DeviceCodeStrategy handles the device_code strategy diff --git a/handler/rfc8628/strategy_hmacsha.go b/handler/rfc8628/strategy_hmacsha.go index 65e65fa61..4bc1ae224 100644 --- a/handler/rfc8628/strategy_hmacsha.go +++ b/handler/rfc8628/strategy_hmacsha.go @@ -5,16 +5,16 @@ package rfc8628 import ( "context" + "encoding/json" "strings" "time" + "github.com/coocood/freecache" "github.com/mohae/deepcopy" "github.com/ory/x/errorsx" "github.com/ory/x/randx" - "github.com/patrickmn/go-cache" - "golang.org/x/time/rate" "github.com/ory/fosite" enigma "github.com/ory/fosite/token/hmac" @@ -100,7 +100,7 @@ func (s *DefaultDeviceFlowSession) SetBrowserFlowCompleted(flag bool) { // DefaultDeviceStrategy implements the default device strategy type DefaultDeviceStrategy struct { Enigma *enigma.HMACStrategy - RateLimiterCache *cache.Cache + RateLimiterCache *freecache.Cache Config interface { fosite.DeviceProvider fosite.DeviceAndUserCodeLifespanProvider @@ -170,20 +170,71 @@ func (h *DefaultDeviceStrategy) ValidateDeviceCode(ctx context.Context, r fosite } // ShouldRateLimit is used to decide whether a request should be rate-limited -func (h *DefaultDeviceStrategy) ShouldRateLimit(context context.Context, code string) bool { +func (h *DefaultDeviceStrategy) ShouldRateLimit(context context.Context, code string) (bool, error) { key := code + "_limiter" - if x, found := h.RateLimiterCache.Get(key); found { - return !x.(*rate.Limiter).Allow() + keyBytes := []byte(key) + object, err := h.RateLimiterCache.Get(keyBytes) + // This code is not in the cache, so we just add it + if err != nil { + timer := new(expirationTimer) + timer.Counter = 1 + timer.NotUntil = h.getExpirationTime(context, 1) + exp, err := h.serializeExpiration(timer) + if err != nil { + return false, err + } + // Set the expiration time as value, and use the lifespan of the device code as TTL. + h.RateLimiterCache.Set(keyBytes, exp, int(h.Config.GetDeviceAndUserCodeLifespan(context))) + return false, nil + } + + expiration, err := h.deserializeExpiration(object) + if err != nil { + return false, errorsx.WithStack(fosite.ErrServerError.WithHintf("Failed to store to rate limit cache: %s", err)) + } + + // The code is valid and enough time has passed since the last call. + if expiration.NotUntil.Before(time.Now()) { + expiration.NotUntil = h.getExpirationTime(context, expiration.Counter) + exp, err := h.serializeExpiration(expiration) + if err != nil { + return false, err + } + h.RateLimiterCache.Set(keyBytes, exp, int(h.Config.GetDeviceAndUserCodeLifespan(context))) + return false, nil + } + + // The token calls were made too fast, we need to double the interval period + expiration.NotUntil = h.getExpirationTime(context, expiration.Counter+1) + expiration.Counter += 1 + exp, err := h.serializeExpiration(expiration) + if err != nil { + return false, err } + h.RateLimiterCache.Set(keyBytes, exp, int(h.Config.GetDeviceAndUserCodeLifespan(context))) - rateLimiter := rate.NewLimiter( - rate.Every( - h.Config.GetDeviceAuthTokenPollingInterval(context), - ), - 1, - ) + return true, nil +} + +func (h *DefaultDeviceStrategy) getExpirationTime(context context.Context, multiplier int) time.Time { + duration := h.Config.GetDeviceAuthTokenPollingInterval(context) + expiration := time.Now().Add(duration * time.Duration(multiplier)) + return expiration +} + +type expirationTimer struct { + NotUntil time.Time + Counter int +} + +func (h *DefaultDeviceStrategy) serializeExpiration(exp *expirationTimer) ([]byte, error) { + b, err := json.Marshal(exp) + return b, err +} - h.RateLimiterCache.Set(key, rateLimiter, cache.DefaultExpiration) - return false +func (h *DefaultDeviceStrategy) deserializeExpiration(b []byte) (*expirationTimer, error) { + timer := new(expirationTimer) + err := json.Unmarshal(b, timer) + return timer, err } diff --git a/handler/rfc8628/strategy_hmacsha_test.go b/handler/rfc8628/strategy_hmacsha_test.go index 70b687c0c..08286532c 100644 --- a/handler/rfc8628/strategy_hmacsha_test.go +++ b/handler/rfc8628/strategy_hmacsha_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/patrickmn/go-cache" + "github.com/coocood/freecache" "github.com/stretchr/testify/assert" "github.com/ory/fosite" @@ -21,7 +21,7 @@ import ( var hmacshaStrategy = DefaultDeviceStrategy{ Enigma: &hmac.HMACStrategy{Config: &fosite.Config{GlobalSecret: []byte("foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar")}}, - RateLimiterCache: cache.New(24*time.Minute, 2*24*time.Minute), + RateLimiterCache: freecache.NewCache(16384 * 64), Config: &fosite.Config{ AccessTokenLifespan: time.Minute * 24, AuthorizeCodeLifespan: time.Minute * 24, @@ -115,17 +115,30 @@ func TestHMACDeviceCode(t *testing.T) { func TestRateLimit(t *testing.T) { t.Run("ratelimit no-wait", func(t *testing.T) { - hmacshaStrategy.RateLimiterCache.Flush() - assert.False(t, hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA")) - assert.False(t, hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA")) - assert.True(t, hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA")) + hmacshaStrategy.RateLimiterCache.Clear() + b, err := hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA") + assert.NoError(t, err) + assert.False(t, b) + b, err = hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA") + assert.NoError(t, err) + assert.True(t, b) }) t.Run("ratelimit wait", func(t *testing.T) { - hmacshaStrategy.RateLimiterCache.Flush() - assert.False(t, hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA")) - assert.False(t, hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA")) + hmacshaStrategy.RateLimiterCache.Clear() + b, err := hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA") + assert.NoError(t, err) + assert.False(t, b) time.Sleep(500 * time.Millisecond) - assert.False(t, hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA")) + b, err = hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA") + assert.NoError(t, err) + assert.False(t, b) + time.Sleep(500 * time.Millisecond) + b, err = hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA") + assert.NoError(t, err) + assert.False(t, b) + b, err = hmacshaStrategy.ShouldRateLimit(context.TODO(), "AAA") + assert.NoError(t, err) + assert.True(t, b) }) } diff --git a/handler/rfc8628/token_handler.go b/handler/rfc8628/token_handler.go index 4cb318834..a8d0f8bbc 100644 --- a/handler/rfc8628/token_handler.go +++ b/handler/rfc8628/token_handler.go @@ -22,7 +22,12 @@ type DeviceCodeHandler struct { func (c DeviceCodeHandler) Code(ctx context.Context, requester fosite.AccessRequester) (code string, signature string, err error) { code = requester.GetRequestForm().Get("device_code") - if c.DeviceRateLimitStrategy.ShouldRateLimit(ctx, code) { + shouldRateLimit, err := c.DeviceRateLimitStrategy.ShouldRateLimit(ctx, code) + // TODO(nsklikas) : should we error out or just silently log it? + if err != nil { + return "", "", err + } + if shouldRateLimit { return "", "", errorsx.WithStack(fosite.ErrPollingRateLimited) } diff --git a/handler/rfc8628/token_handler_test.go b/handler/rfc8628/token_handler_test.go index 28556f0bc..01d55d8b0 100644 --- a/handler/rfc8628/token_handler_test.go +++ b/handler/rfc8628/token_handler_test.go @@ -10,13 +10,12 @@ import ( "testing" "time" + "github.com/coocood/freecache" "github.com/pkg/errors" "github.com/golang/mock/gomock" "github.com/ory/fosite/internal" - "github.com/patrickmn/go-cache" - "github.com/ory/fosite/handler/oauth2" "github.com/ory/fosite/token/hmac" @@ -35,11 +34,8 @@ var hmacshaStrategy = oauth2.HMACSHAStrategy{ } var RFC8628HMACSHAStrategy = DefaultDeviceStrategy{ - Enigma: &hmac.HMACStrategy{Config: &fosite.Config{GlobalSecret: []byte("foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar")}}, - RateLimiterCache: cache.New( - time.Hour*12, - time.Hour*24, - ), + Enigma: &hmac.HMACStrategy{Config: &fosite.Config{GlobalSecret: []byte("foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar")}}, + RateLimiterCache: freecache.NewCache(16384 * 64), Config: &fosite.Config{ DeviceAndUserCodeLifespan: time.Hour * 24, }, @@ -268,6 +264,72 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { } } +func TestDeviceUserCode_HandleTokenEndpointRequest_Ratelimitting(t *testing.T) { + for k, strategy := range map[string]struct { + oauth2.CoreStrategy + RFC8628CodeStrategy + }{ + "hmac": {&hmacshaStrategy, &RFC8628HMACSHAStrategy}, + } { + t.Run("strategy="+k, func(t *testing.T) { + store := storage.NewMemoryStore() + + h := oauth2.GenericCodeTokenEndpointHandler{ + AccessRequestValidator: &DeviceAccessRequestValidator{}, + CodeHandler: &DeviceCodeHandler{ + DeviceRateLimitStrategy: strategy, + DeviceCodeStrategy: strategy, + }, + SessionHandler: &DeviceSessionHandler{ + DeviceCodeStorage: store, + }, + CoreStorage: store, + AccessTokenStrategy: strategy.CoreStrategy, + RefreshTokenStrategy: strategy.CoreStrategy, + Config: &fosite.Config{ + ScopeStrategy: fosite.HierarchicScopeStrategy, + AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, + DeviceAndUserCodeLifespan: time.Minute, + }, + } + areq := &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + Request: fosite.Request{ + Form: url.Values{}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + }, + GrantedScope: fosite.Arguments{"foo", "offline"}, + Session: &DefaultDeviceFlowSession{}, + RequestedAt: time.Now().UTC(), + }, + } + authreq := &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Session: &DefaultDeviceFlowSession{ + BrowserFlowCompleted: true, + }, + RequestedAt: time.Now().UTC(), + }, + } + + token, signature, err := strategy.GenerateDeviceCode(context.TODO()) + require.NoError(t, err) + + areq.Form = url.Values{"device_code": {token}} + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) + err = h.HandleTokenEndpointRequest(context.Background(), areq) + require.NoError(t, err, "%+v", err) + err = h.HandleTokenEndpointRequest(context.Background(), areq) + require.Error(t, fosite.ErrPollingRateLimited, err) + time.Sleep(10 * time.Second) + err = h.HandleTokenEndpointRequest(context.Background(), areq) + require.NoError(t, err, "%+v", err) + }) + } +} func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { for k, strategy := range map[string]struct { oauth2.CoreStrategy @@ -705,7 +767,7 @@ func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockCoreStore = internal.NewMockCoreStorage(ctrl) mockDeviceCodeStore = internal.NewMockDeviceCodeStorage(ctrl) mockDeviceRateLimitStrategy = internal.NewMockDeviceRateLimitStrategy(ctrl) - mockDeviceRateLimitStrategy.EXPECT().ShouldRateLimit(gomock.Any(), gomock.Any()).Return(false).Times(1) + mockDeviceRateLimitStrategy.EXPECT().ShouldRateLimit(gomock.Any(), gomock.Any()).Return(false, nil).Times(1) testCase.setup() handler := oauth2.GenericCodeTokenEndpointHandler{ From f80621e02c6f06f2b95d6730565b080da304080e Mon Sep 17 00:00:00 2001 From: Nikos Date: Thu, 28 Mar 2024 14:37:42 +0200 Subject: [PATCH 17/17] fix: update license --- internal/access_request.go | 1 - internal/access_response.go | 1 - internal/access_token_storage.go | 1 - internal/access_token_strategy.go | 1 - internal/authorize_code_storage.go | 1 - internal/authorize_code_strategy.go | 1 - internal/authorize_handler.go | 1 - internal/authorize_request.go | 1 - internal/client.go | 28 --------------------- internal/device_code_rate_limit_strategy.go | 5 ++-- internal/id_token_strategy.go | 1 - internal/introspector.go | 1 - internal/oauth2_client_storage.go | 1 - internal/oauth2_owner_storage.go | 1 - internal/oauth2_revoke_storage.go | 1 - internal/oauth2_storage.go | 1 - internal/oauth2_strategy.go | 1 - internal/openid_id_token_storage.go | 1 - internal/pkce_storage_strategy.go | 1 - internal/refresh_token_strategy.go | 1 - internal/request.go | 1 - internal/revoke_handler.go | 1 - internal/storage.go | 1 - internal/token_handler.go | 1 - 24 files changed, 3 insertions(+), 52 deletions(-) diff --git a/internal/access_request.go b/internal/access_request.go index fd05e5420..d2f42e326 100644 --- a/internal/access_request.go +++ b/internal/access_request.go @@ -13,7 +13,6 @@ import ( time "time" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/access_response.go b/internal/access_response.go index 340c2476f..5b3f42f4c 100644 --- a/internal/access_response.go +++ b/internal/access_response.go @@ -12,7 +12,6 @@ import ( time "time" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/access_token_storage.go b/internal/access_token_storage.go index 385424532..6d0a00512 100644 --- a/internal/access_token_storage.go +++ b/internal/access_token_storage.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/access_token_strategy.go b/internal/access_token_strategy.go index d8c64c9bd..24e95187e 100644 --- a/internal/access_token_strategy.go +++ b/internal/access_token_strategy.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/authorize_code_storage.go b/internal/authorize_code_storage.go index 1543b6825..4c80e3117 100644 --- a/internal/authorize_code_storage.go +++ b/internal/authorize_code_storage.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/authorize_code_strategy.go b/internal/authorize_code_strategy.go index 44edd6440..cc0250a7a 100644 --- a/internal/authorize_code_strategy.go +++ b/internal/authorize_code_strategy.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/authorize_handler.go b/internal/authorize_handler.go index d7420fed5..bbc919d8e 100644 --- a/internal/authorize_handler.go +++ b/internal/authorize_handler.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/authorize_request.go b/internal/authorize_request.go index 5cceafff8..7e85bb057 100644 --- a/internal/authorize_request.go +++ b/internal/authorize_request.go @@ -13,7 +13,6 @@ import ( time "time" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/client.go b/internal/client.go index 2923cf70b..e53cebf57 100644 --- a/internal/client.go +++ b/internal/client.go @@ -9,10 +9,8 @@ package internal import ( reflect "reflect" - time "time" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) @@ -137,20 +135,6 @@ func (mr *MockClientMockRecorder) GetScopes() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetScopes", reflect.TypeOf((*MockClient)(nil).GetScopes)) } -// GetTokenLifespan mocks base method. -func (m *MockClient) GetTokenLifespan(arg0 fosite.GrantType, arg1 fosite.TokenType, arg2 time.Duration) time.Duration { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetTokenLifespan", arg0, arg1, arg2) - ret0, _ := ret[0].(time.Duration) - return ret0 -} - -// GetTokenLifespan indicates an expected call of GetTokenLifespan. -func (mr *MockClientMockRecorder) GetTokenLifespan(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTokenLifespan", reflect.TypeOf((*MockClient)(nil).GetTokenLifespan), arg0, arg1, arg2) -} - // IsPublic mocks base method. func (m *MockClient) IsPublic() bool { m.ctrl.T.Helper() @@ -164,15 +148,3 @@ func (mr *MockClientMockRecorder) IsPublic() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsPublic", reflect.TypeOf((*MockClient)(nil).IsPublic)) } - -// SetTokenLifespans mocks base method. -func (m *MockClient) SetTokenLifespans(arg0 map[fosite.TokenType]time.Duration) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "SetTokenLifespans", arg0) -} - -// SetTokenLifespans indicates an expected call of SetTokenLifespans. -func (mr *MockClientMockRecorder) SetTokenLifespans(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTokenLifespans", reflect.TypeOf((*MockClient)(nil).SetTokenLifespans), arg0) -} diff --git a/internal/device_code_rate_limit_strategy.go b/internal/device_code_rate_limit_strategy.go index 3a3b49511..fe18b9e6e 100644 --- a/internal/device_code_rate_limit_strategy.go +++ b/internal/device_code_rate_limit_strategy.go @@ -38,11 +38,12 @@ func (m *MockDeviceRateLimitStrategy) EXPECT() *MockDeviceRateLimitStrategyMockR } // ShouldRateLimit mocks base method. -func (m *MockDeviceRateLimitStrategy) ShouldRateLimit(arg0 context.Context, arg1 string) bool { +func (m *MockDeviceRateLimitStrategy) ShouldRateLimit(arg0 context.Context, arg1 string) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ShouldRateLimit", arg0, arg1) ret0, _ := ret[0].(bool) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // ShouldRateLimit indicates an expected call of ShouldRateLimit. diff --git a/internal/id_token_strategy.go b/internal/id_token_strategy.go index 330adeaee..d953d339b 100644 --- a/internal/id_token_strategy.go +++ b/internal/id_token_strategy.go @@ -13,7 +13,6 @@ import ( time "time" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/introspector.go b/internal/introspector.go index 7b68fbf46..7122b93e2 100644 --- a/internal/introspector.go +++ b/internal/introspector.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/oauth2_client_storage.go b/internal/oauth2_client_storage.go index d33dfd777..96eb41711 100644 --- a/internal/oauth2_client_storage.go +++ b/internal/oauth2_client_storage.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/oauth2_owner_storage.go b/internal/oauth2_owner_storage.go index 79e797958..db20fbb12 100644 --- a/internal/oauth2_owner_storage.go +++ b/internal/oauth2_owner_storage.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/oauth2_revoke_storage.go b/internal/oauth2_revoke_storage.go index 12580b4b5..8cfc7fc26 100644 --- a/internal/oauth2_revoke_storage.go +++ b/internal/oauth2_revoke_storage.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/oauth2_storage.go b/internal/oauth2_storage.go index a67815f7f..21ae2f666 100644 --- a/internal/oauth2_storage.go +++ b/internal/oauth2_storage.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/oauth2_strategy.go b/internal/oauth2_strategy.go index 539202052..aeb473fe5 100644 --- a/internal/oauth2_strategy.go +++ b/internal/oauth2_strategy.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/openid_id_token_storage.go b/internal/openid_id_token_storage.go index bfcd0d628..2aa736b7c 100644 --- a/internal/openid_id_token_storage.go +++ b/internal/openid_id_token_storage.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/pkce_storage_strategy.go b/internal/pkce_storage_strategy.go index 46de92ea0..bdaf4c624 100644 --- a/internal/pkce_storage_strategy.go +++ b/internal/pkce_storage_strategy.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/refresh_token_strategy.go b/internal/refresh_token_strategy.go index 5338bfb71..2bafb3c90 100644 --- a/internal/refresh_token_strategy.go +++ b/internal/refresh_token_strategy.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/request.go b/internal/request.go index c74969c95..762a43b38 100644 --- a/internal/request.go +++ b/internal/request.go @@ -13,7 +13,6 @@ import ( time "time" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/revoke_handler.go b/internal/revoke_handler.go index 948178e19..0599be852 100644 --- a/internal/revoke_handler.go +++ b/internal/revoke_handler.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/storage.go b/internal/storage.go index 14fa7c32c..44b199ac5 100644 --- a/internal/storage.go +++ b/internal/storage.go @@ -13,7 +13,6 @@ import ( time "time" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" ) diff --git a/internal/token_handler.go b/internal/token_handler.go index 9eb170678..519afa8aa 100644 --- a/internal/token_handler.go +++ b/internal/token_handler.go @@ -12,7 +12,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - fosite "github.com/ory/fosite" )