Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement RFC 8628 #826

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6c51acf
fix: fix tests
nsklikas Oct 15, 2024
e108bd6
fix: Use Requester param in WriteAccessError
nsklikas Feb 5, 2024
5f32488
fix: generalize validateAuthorizeAudience method
nsklikas Feb 6, 2024
aa92163
feat: add device flow base logic
nsklikas Feb 6, 2024
49c6e74
fix: add handler for device authorization req
nsklikas Feb 6, 2024
88c0480
fix: add device flow handlers to compose
nsklikas Feb 6, 2024
1630554
fix: update memory storage
nsklikas Feb 6, 2024
179f16c
chore: update integration tests
nsklikas Feb 7, 2024
9489ba3
fix: review comments
nsklikas Feb 9, 2024
4bb5b4b
feat: implement the access token handling for device authorization flow
wood-push-melon Mar 15, 2024
f909adf
fix: passing the correct authorization request when validating if the…
wood-push-melon Mar 17, 2024
8fd8db7
feat: error handling for authorization pending in device flow
wood-push-melon Mar 18, 2024
8687892
test: reorganize the testcases
wood-push-melon Mar 18, 2024
e901cb5
chore: resolve comments
wood-push-melon Mar 19, 2024
47842ce
fix: fix oauth2 core storage interface and device flow session type a…
wood-push-melon Mar 24, 2024
5b64880
fix: implement rate limiting
nsklikas Mar 28, 2024
3d40798
fix: do not validate request when creating response
nsklikas Mar 28, 2024
2504862
fix: add the OIDC handler for device flow (#13)
wood-push-melon Apr 5, 2024
9e13f85
fix: fix the refresh token issue (#14)
wood-push-melon Apr 12, 2024
10cfcbd
fix: use correct grant lifespan to issue tokens
nsklikas Apr 29, 2024
af6c343
fix: handle the user code generation duplication
wood-push-melon Apr 29, 2024
95d0f11
chore: migrate to uber/gomock
nsklikas Sep 12, 2024
b088391
refactor: revert oauth handler changes
nsklikas Oct 16, 2024
18f35d9
ci: use hydra from branch
nsklikas Oct 16, 2024
6814a9b
fix: remove rate limiting implementation
nsklikas Oct 16, 2024
2020325
fix: make user code creation configurable
nsklikas Oct 16, 2024
56fa0af
refactor: simplify handler and test logic
nsklikas Oct 17, 2024
37e00d5
refactor: merge user and device code storage
nsklikas Nov 12, 2024
ca04a4a
refactor: enhance deviceRequest struct
nsklikas Nov 15, 2024
a5ad00e
fix: do not create openid session on device auth request
nsklikas Nov 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/oidc-conformity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ jobs:
uses: actions/checkout@v2
with:
fetch-depth: 2
repository: ory/hydra
ref: 2866a0499d02341ed0603601cfe4e63b24506fcb
repository: canonical/hydra
ref: canonical-master
- uses: actions/setup-go@v2
with:
go-version: "1.21"
Expand Down
5 changes: 3 additions & 2 deletions access_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion access_error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (
"net/http/httptest"
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gomock "go.uber.org/mock/gomock"

. "github.com/ory/fosite"
. "github.com/ory/fosite/internal"
Expand Down
2 changes: 1 addition & 1 deletion access_request_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import (
"net/url"
"testing"

"github.com/golang/mock/gomock"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gomock "go.uber.org/mock/gomock"

. "github.com/ory/fosite"
"github.com/ory/fosite/internal"
Expand Down
2 changes: 1 addition & 1 deletion access_response_writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (
"fmt"
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gomock "go.uber.org/mock/gomock"

. "github.com/ory/fosite"
"github.com/ory/fosite/internal"
Expand Down
2 changes: 1 addition & 1 deletion access_write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"net/http"
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
gomock "go.uber.org/mock/gomock"

. "github.com/ory/fosite"
. "github.com/ory/fosite/internal"
Expand Down
6 changes: 3 additions & 3 deletions audience_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion authorize_error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
"net/url"
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
gomock "go.uber.org/mock/gomock"

. "github.com/ory/fosite"
. "github.com/ory/fosite/internal"
Expand Down
2 changes: 1 addition & 1 deletion authorize_request_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,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
}

Expand Down
2 changes: 1 addition & 1 deletion authorize_request_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import (
"net/url"
"testing"

"github.com/golang/mock/gomock"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gomock "go.uber.org/mock/gomock"

. "github.com/ory/fosite"
. "github.com/ory/fosite/internal"
Expand Down
2 changes: 1 addition & 1 deletion authorize_response_writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
"context"
"testing"

"github.com/golang/mock/gomock"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
gomock "go.uber.org/mock/gomock"

"github.com/ory/fosite"
. "github.com/ory/fosite"
Expand Down
2 changes: 1 addition & 1 deletion authorize_write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
"net/url"
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
gomock "go.uber.org/mock/gomock"

. "github.com/ory/fosite"
. "github.com/ory/fosite/internal"
Expand Down
7 changes: 7 additions & 0 deletions compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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},
},
Expand All @@ -77,11 +81,14 @@ func ComposeAllEnabled(config *fosite.Config, storage interface{}, key interface
OAuth2RefreshTokenGrantFactory,
OAuth2ResourceOwnerPasswordCredentialsFactory,
RFC7523AssertionGrantFactory,
RFC8628DeviceFactory,
RFC8628DeviceAuthorizationTokenFactory,

OpenIDConnectExplicitFactory,
OpenIDConnectImplicitFactory,
OpenIDConnectHybridFactory,
OpenIDConnectRefreshFactory,
OpenIDConnectDeviceFactory,

OAuth2TokenIntrospectionFactory,
OAuth2TokenRevocationFactory,
Expand Down
15 changes: 15 additions & 0 deletions compose/compose_openid.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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/jwt"
)

Expand Down Expand Up @@ -79,3 +80,17 @@ func OpenIDConnectHybridFactory(config fosite.Configurator, storage interface{},
OpenIDConnectRequestValidator: openid.NewOpenIDConnectRequestValidator(strategy.(jwt.Signer), config),
}
}

// OpenIDConnectDeviceFactory creates an OpenID Connect device ("device code flow") grant handler.
//
// **Important note:** You must add this handler *after* you have added an OAuth2 device authorization handler!
func OpenIDConnectDeviceFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} {
return &openid.OpenIDConnectDeviceHandler{
OpenIDConnectRequestStorage: storage.(openid.OpenIDConnectRequestStorage),
IDTokenHandleHelper: &openid.IDTokenHandleHelper{
IDTokenStrategy: strategy.(openid.OpenIDConnectTokenStrategy),
},
DeviceCodeStrategy: strategy.(rfc8628.DeviceCodeStrategy),
Config: config,
}
}
37 changes: 37 additions & 0 deletions compose/compose_rfc8628.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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 (
"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
// 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),
Storage: storage.(rfc8628.RFC8628CoreStorage),
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{
DeviceRateLimitStrategy: strategy.(rfc8628.DeviceRateLimitStrategy),
DeviceCodeStrategy: strategy.(rfc8628.DeviceCodeStrategy),
UserCodeStrategy: strategy.(rfc8628.UserCodeStrategy),
AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
RefreshTokenStrategy: strategy.(oauth2.RefreshTokenStrategy),
CoreStorage: storage.(rfc8628.RFC8628CoreStorage),
TokenRevocationStorage: storage.(oauth2.TokenRevocationStorage),
Config: config,
}
}
11 changes: 11 additions & 0 deletions compose/compose_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ 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"
)

type CommonStrategy struct {
oauth2.CoreStrategy
rfc8628.RFC8628CodeStrategy
openid.OpenIDConnectTokenStrategy
jwt.Signer
}
Expand All @@ -27,6 +29,7 @@ type HMACSHAStrategyConfigurator interface {
fosite.GlobalSecretProvider
fosite.RotatedGlobalSecretsProvider
fosite.HMACHashingProvider
fosite.DeviceAndUserCodeLifespanProvider
}

func NewOAuth2HMACStrategy(config HMACSHAStrategyConfigurator) *oauth2.HMACSHAStrategy {
Expand All @@ -47,3 +50,11 @@ func NewOpenIDConnectStrategy(keyGetter func(context.Context) (interface{}, erro
Config: config,
}
}

// Create a new device strategy
func NewDeviceStrategy(config fosite.Configurator) *rfc8628.DefaultDeviceStrategy {
return &rfc8628.DefaultDeviceStrategy{
Enigma: &hmac.HMACStrategy{Config: config},
Config: config,
}
}
23 changes: 23 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ 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
}

// DeviceAndUserCodeLifespanProvider returns the provider for configuring the device and user code lifespan
type UserCodeProvider interface {
GetUserCodeLength(ctx context.Context) int
GetUserCodeSymbols(ctx context.Context) []rune
}

// ScopeStrategyProvider returns the provider for configuring the scope strategy.
type ScopeStrategyProvider interface {
// GetScopeStrategy returns the scope strategy.
Expand Down Expand Up @@ -76,6 +87,12 @@ 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
}

// BCryptCostProvider returns the provider for configuring the BCrypt hash cost.
type BCryptCostProvider interface {
// GetBCryptCost returns the BCrypt hash cost.
Expand Down Expand Up @@ -306,3 +323,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
}
Loading
Loading