Skip to content

Commit

Permalink
Device grant flow (migrate to master)
Browse files Browse the repository at this point in the history
  • Loading branch information
BuzzBumbleBee committed Sep 5, 2022
1 parent 575ae6d commit ce02ff3
Show file tree
Hide file tree
Showing 58 changed files with 2,002 additions and 70 deletions.
34 changes: 19 additions & 15 deletions compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,24 @@ type Factory func(config fosite.Configurator, storage interface{}, strategy inte

// Compose takes a config, a storage, a strategy and handlers to instantiate an OAuth2Provider:
//
// import "github.com/ory/fosite/compose"
// import "github.com/ory/fosite/compose"
//
// // var storage = new(MyFositeStorage)
// var config = Config {
// AccessTokenLifespan: time.Minute * 30,
// // check Config for further configuration options
// }
// // var storage = new(MyFositeStorage)
// var config = Config {
// AccessTokenLifespan: time.Minute * 30,
// // check Config for further configuration options
// }
//
// var strategy = NewOAuth2HMACStrategy(config)
// var strategy = NewOAuth2HMACStrategy(config)
//
// var oauth2Provider = Compose(
// config,
// storage,
// strategy,
// NewOAuth2AuthorizeExplicitHandler,
// OAuth2ClientCredentialsGrantFactory,
// // for a complete list refer to the docs of this package
// )
// var oauth2Provider = Compose(
// config,
// storage,
// strategy,
// NewOAuth2AuthorizeExplicitHandler,
// OAuth2ClientCredentialsGrantFactory,
// // for a complete list refer to the docs of this package
// )
//
// Compose makes use of interface{} types in order to be able to handle a all types of stores, strategies and handlers.
func Compose(config *fosite.Config, storage interface{}, strategy interface{}, factories ...Factory) fosite.OAuth2Provider {
Expand Down Expand Up @@ -91,20 +91,24 @@ func ComposeAllEnabled(config *fosite.Config, storage interface{}, key interface
},
OAuth2AuthorizeExplicitFactory,
OAuth2AuthorizeImplicitFactory,
OAuth2AuthorizeDeviceFactory,
OAuth2ClientCredentialsGrantFactory,
OAuth2RefreshTokenGrantFactory,
OAuth2DeviceAuthorizeFactory,
OAuth2ResourceOwnerPasswordCredentialsFactory,
RFC7523AssertionGrantFactory,

OpenIDConnectExplicitFactory,
OpenIDConnectImplicitFactory,
OpenIDConnectHybridFactory,
OpenIDConnectRefreshFactory,
OpenIDConnectDeviceFactory,

OAuth2TokenIntrospectionFactory,
OAuth2TokenRevocationFactory,

OAuth2PKCEFactory,
PushedAuthorizeHandlerFactory,
OAuth2DevicePKCEFactory,
)
}
22 changes: 22 additions & 0 deletions compose/compose_oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,25 @@ func OAuth2StatelessJWTIntrospectionFactory(config fosite.Configurator, storage
Config: config,
}
}

func OAuth2AuthorizeDeviceFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} {
return &oauth2.AuthorizeDeviceGrantTypeHandler{
DeviceCodeStrategy: strategy.(oauth2.DeviceCodeStrategy),
UserCodeStrategy: strategy.(oauth2.UserCodeStrategy),
AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
RefreshTokenStrategy: strategy.(oauth2.RefreshTokenStrategy),
AuthorizeCodeStrategy: strategy.(oauth2.AuthorizeCodeStrategy),
CoreStorage: storage.(oauth2.CoreStorage),
Config: config,
}
}

func OAuth2DeviceAuthorizeFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} {
return &oauth2.DeviceAuthorizationHandler{
DeviceCodeStorage: storage.(oauth2.DeviceCodeStorage),
UserCodeStorage: storage.(oauth2.UserCodeStorage),
DeviceCodeStrategy: strategy.(oauth2.DeviceCodeStrategy),
UserCodeStrategy: strategy.(oauth2.UserCodeStrategy),
Config: config,
}
}
20 changes: 19 additions & 1 deletion compose/compose_openid.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ func OpenIDConnectImplicitFactory(config fosite.Configurator, storage interface{
AuthorizeImplicitGrantTypeHandler: &oauth2.AuthorizeImplicitGrantTypeHandler{
AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
AccessTokenStorage: storage.(oauth2.AccessTokenStorage),
Config: config,

Config: config,
},
Config: config,
IDTokenHandleHelper: &openid.IDTokenHandleHelper{
Expand Down Expand Up @@ -97,3 +98,20 @@ 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 authorize code handler!
func OpenIDConnectDeviceFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} {
return &openid.OpenIDConnectDeviceHandler{
CoreStorage: storage.(oauth2.CoreStorage),
DeviceCodeStrategy: strategy.(oauth2.DeviceCodeStrategy),
UserCodeStrategy: strategy.(oauth2.UserCodeStrategy),
OpenIDConnectRequestStorage: storage.(openid.OpenIDConnectRequestStorage),
IDTokenHandleHelper: &openid.IDTokenHandleHelper{
IDTokenStrategy: strategy.(openid.OpenIDConnectTokenStrategy),
},
OpenIDConnectRequestValidator: openid.NewOpenIDConnectRequestValidator(strategy.(jwt.Signer), config),
Config: config,
}
}
11 changes: 11 additions & 0 deletions compose/compose_pkce.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,14 @@ func OAuth2PKCEFactory(config fosite.Configurator, storage interface{}, strategy
Config: config,
}
}

func OAuth2DevicePKCEFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} {
return &pkce.HandlerDevice{
CoreStorage: storage.(oauth2.CoreStorage),
DeviceCodeStrategy: strategy.(oauth2.DeviceCodeStrategy),
UserCodeStrategy: strategy.(oauth2.UserCodeStrategy),
AuthorizeCodeStrategy: strategy.(oauth2.AuthorizeCodeStrategy),
Storage: storage.(pkce.PKCERequestStorage),
Config: config,
}
}
1 change: 1 addition & 0 deletions compose/compose_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type HMACSHAStrategyConfigurator interface {
fosite.GlobalSecretProvider
fosite.RotatedGlobalSecretsProvider
fosite.HMACHashingProvider
fosite.DeviceAndUserCodeLifespanProvider
}

func NewOAuth2HMACStrategy(config HMACSHAStrategyConfigurator) *oauth2.HMACSHAStrategy {
Expand Down
13 changes: 13 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ type AuthorizeCodeLifespanProvider interface {
GetAuthorizeCodeLifespan(ctx context.Context) time.Duration
}

type DeviceAndUserCodeLifespanProvider interface {
GetDeviceAndUserCodeLifespan(ctx context.Context) time.Duration
}

type DeviceUriProvider interface {
GetDeviceVerificationURL(ctx context.Context) string
GetDeviceAuthTokenPollingInterval(ctx context.Context) time.Duration
}

// RefreshTokenLifespanProvider returns the provider for configuring the refresh token lifespan.
type RefreshTokenLifespanProvider interface {
// GetRefreshTokenLifespan returns the refresh token lifespan.
Expand Down Expand Up @@ -272,6 +281,10 @@ type PushedAuthorizeRequestHandlersProvider interface {
GetPushedAuthorizeEndpointHandlers(ctx context.Context) PushedAuthorizeEndpointHandlers
}

type DeviceAuthorizeEndpointHandlersProvider interface {
GetDeviceAuthorizeEndpointHandlers(ctx context.Context) DeviceAuthorizeEndpointHandlers
}

// UseLegacyErrorFormatProvider returns the provider for configuring whether to use the legacy error format.
//
// DEPRECATED: Do not use this flag anymore.
Expand Down
34 changes: 34 additions & 0 deletions config_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ var (
_ RevocationHandlersProvider = (*Config)(nil)
_ PushedAuthorizeRequestHandlersProvider = (*Config)(nil)
_ PushedAuthorizeRequestConfigProvider = (*Config)(nil)
_ DeviceAuthorizeEndpointHandlersProvider = (*Config)(nil)
)

type Config struct {
Expand All @@ -93,6 +94,15 @@ type Config struct {
// AuthorizeCodeLifespan sets how long an authorize code is going to be valid. Defaults to fifteen minutes.
AuthorizeCodeLifespan time.Duration

// 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

// IDTokenLifespan sets the default id token lifetime. Defaults to one hour.
IDTokenLifespan time.Duration

Expand Down Expand Up @@ -212,6 +222,8 @@ type Config struct {
// PushedAuthorizeEndpointHandlers is a list of handlers that are called before the PAR endpoint is served.
PushedAuthorizeEndpointHandlers PushedAuthorizeEndpointHandlers

DeviceAuthorizeEndpointHandlers DeviceAuthorizeEndpointHandlers

// GlobalSecret is the global secret used to sign and verify signatures.
GlobalSecret []byte

Expand Down Expand Up @@ -260,6 +272,10 @@ func (c *Config) GetTokenIntrospectionHandlers(ctx context.Context) TokenIntrosp
return c.TokenIntrospectionHandlers
}

func (c *Config) GetDeviceAuthorizeEndpointHandlers(ctx context.Context) DeviceAuthorizeEndpointHandlers {
return c.DeviceAuthorizeEndpointHandlers
}

func (c *Config) GetRevocationHandlers(ctx context.Context) RevocationHandlers {
return c.RevocationHandlers
}
Expand Down Expand Up @@ -378,6 +394,13 @@ func (c *Config) GetAuthorizeCodeLifespan(_ context.Context) time.Duration {
return c.AuthorizeCodeLifespan
}

func (c *Config) GetDeviceAndUserCodeLifespan(_ context.Context) time.Duration {
if c.AuthorizeCodeLifespan == 0 {
return time.Minute * 10
}
return c.DeviceAndUserCodeLifespan
}

// GeIDTokenLifespan returns how long an id token should be valid. Defaults to one hour.
func (c *Config) GetIDTokenLifespan(_ context.Context) time.Duration {
if c.IDTokenLifespan == 0 {
Expand Down Expand Up @@ -506,3 +529,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 * 10
}
return c.DeviceAuthTokenPollingInterval
}
44 changes: 44 additions & 0 deletions device_authorize_request_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package fosite

import (
"context"
"net/http"
"strings"

"github.com/ory/fosite/i18n"
"github.com/ory/x/errorsx"
)

func (f *Fosite) NewDeviceAuthorizeRequest(ctx context.Context, req *http.Request) (Requester, error) {

request := NewRequest()
request.Lang = i18n.GetLangFromRequest(f.Config.GetMessageCatalog(ctx), req)

if err := req.ParseMultipartForm(1 << 20); err != nil && err != http.ErrNotMultipart {
return request, errorsx.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithWrap(err).WithDebug(err.Error()))
}
request.Form = req.PostForm

client, err := f.Store.GetClient(ctx, request.GetRequestForm().Get("client_id"))
if err != nil {
return request, errorsx.WithStack(ErrInvalidClient.WithHint("The requested OAuth 2.0 Client does not exist.").WithWrap(err).WithDebug(err.Error()))
}
request.Client = client

if err := f.validateDeviceAuthorizeScope(ctx, request); err != nil {
return request, err
}

return request, nil
}

func (f *Fosite) validateDeviceAuthorizeScope(ctx context.Context, request *Request) 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
}
71 changes: 71 additions & 0 deletions device_authorize_response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package fosite

import "context"

type DeviceAuthorizeResponse struct {
context context.Context
deviceCode string
userCode string
verificationURI string
verificationURIComplete string
interval int
expiresIn int64
}

// GetDeviceCode returns the response's device code
func NewDeviceAuthorizeResponse() *DeviceAuthorizeResponse {
return &DeviceAuthorizeResponse{}
}

func (d *DeviceAuthorizeResponse) GetDeviceCode() string {
return d.deviceCode
}

// GetUserCode returns the response's user code
func (d *DeviceAuthorizeResponse) SetDeviceCode(code string) {
d.deviceCode = code
}

func (d *DeviceAuthorizeResponse) GetUserCode() string {
return d.userCode
}

func (d *DeviceAuthorizeResponse) SetUserCode(code string) {
d.userCode = code
}

// GetVerificationURI returns the response's verification uri
func (d *DeviceAuthorizeResponse) GetVerificationURI() string {
return d.verificationURI
}

func (d *DeviceAuthorizeResponse) SetVerificationURI(uri string) {
d.verificationURI = uri
}

// GetVerificationURIComplete returns the response's complete verification uri if set
func (d *DeviceAuthorizeResponse) GetVerificationURIComplete() string {
return d.verificationURIComplete
}

func (d *DeviceAuthorizeResponse) SetVerificationURIComplete(uri string) {
d.verificationURIComplete = uri
}

// GetExpiresIn returns the response's device code and user code lifetime in seconds if set
func (d *DeviceAuthorizeResponse) GetExpiresIn() int64 {
return d.expiresIn
}

func (d *DeviceAuthorizeResponse) SetExpiresIn(seconds int64) {
d.expiresIn = seconds
}

// GetInterval returns the response's polling interval if set
func (d *DeviceAuthorizeResponse) GetInterval() int {
return d.interval
}

func (d *DeviceAuthorizeResponse) SetInterval(seconds int) {
d.interval = seconds
}
19 changes: 19 additions & 0 deletions device_authorize_response_writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package fosite

import (
"context"
"fmt"
)

func (f *Fosite) NewDeviceAuthorizeResponse(ctx context.Context, r Requester) (DeviceAuthorizeResponder, error) {
var resp = NewDeviceAuthorizeResponse()

for _, h := range f.Config.GetDeviceAuthorizeEndpointHandlers(ctx) {
fmt.Println("NewDeviceAuthorizeResponse +++")
if err := h.HandleDeviceAuthorizeEndpointRequest(ctx, r, resp); err != nil {
return nil, err
}
}

return resp, nil
}
Loading

0 comments on commit ce02ff3

Please sign in to comment.