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
247d951
fix: fix tests
nsklikas Oct 15, 2024
887d381
fix: Use Requester param in WriteAccessError
nsklikas Feb 5, 2024
57a385b
fix: generalize validateAuthorizeAudience method
nsklikas Feb 6, 2024
7173edf
feat: add device flow base logic
nsklikas Feb 6, 2024
2f216a6
fix: add handler for device authorization req
nsklikas Feb 6, 2024
6e54d93
fix: add device flow handlers to compose
nsklikas Feb 6, 2024
dfd323d
fix: update memory storage
nsklikas Feb 6, 2024
56a1e73
chore: update integration tests
nsklikas Feb 7, 2024
5e9b3a5
fix: review comments
nsklikas Feb 9, 2024
10e007b
feat: implement the access token handling for device authorization flow
wood-push-melon Mar 15, 2024
3a02a81
fix: passing the correct authorization request when validating if the…
wood-push-melon Mar 17, 2024
c3bf9e7
feat: error handling for authorization pending in device flow
wood-push-melon Mar 18, 2024
5b8a226
test: reorganize the testcases
wood-push-melon Mar 18, 2024
c5b2c8e
chore: resolve comments
wood-push-melon Mar 19, 2024
9a12286
fix: fix oauth2 core storage interface and device flow session type a…
wood-push-melon Mar 24, 2024
9b77c80
fix: implement rate limiting
nsklikas Mar 28, 2024
8151ba9
fix: do not validate request when creating response
nsklikas Mar 28, 2024
1e45ed5
fix: add the OIDC handler for device flow (#13)
wood-push-melon Apr 5, 2024
8d4aa7d
fix: fix the refresh token issue (#14)
wood-push-melon Apr 12, 2024
c7f5b1f
fix: use correct grant lifespan to issue tokens
nsklikas Apr 29, 2024
7bebad9
fix: handle the user code generation duplication
wood-push-melon Apr 29, 2024
c8e0bba
chore: migrate to uber/gomock
nsklikas Sep 12, 2024
9a50ff2
refactor: revert oauth handler changes
nsklikas Oct 16, 2024
c4fd5b2
ci: use hydra from branch
nsklikas Oct 16, 2024
01d452d
fix: remove rate limiting implementation
nsklikas Oct 16, 2024
3f599e5
fix: make user code creation configurable
nsklikas Oct 16, 2024
9646fe3
refactor: simplify handler and test logic
nsklikas Oct 17, 2024
94653ee
refactor: merge user and device code storage
nsklikas Nov 12, 2024
9753bcd
refactor: enhance deviceRequest struct
nsklikas Nov 15, 2024
ae6e4e3
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