From cb444177be5b512a07d8d652e4137be1344b50b4 Mon Sep 17 00:00:00 2001 From: Romain Caire Date: Fri, 26 Aug 2022 17:06:08 +0200 Subject: [PATCH 1/5] Add table migration sqlite --- ...reate_device_link_request.postgres.up.sql} | 0 ...create_device_link_request.sqlite.down.sql | 1 + ...0_create_device_link_request.sqlite.up.sql | 21 ++++++++++++++++ ...000000_oauth2_device_code.postgres.up.sql} | 0 ...0000000_oauth2_device_code.sqlite.down.sql | 1 + ...500000000_oauth2_device_code.sqlite.up.sql | 23 ++++++++++++++++++ ...00000000_oauth2_user_code.postgres.up.sql} | 0 ...500000000_oauth2_user_code.sqlite.down.sql | 1 + ...11500000000_oauth2_user_code.sqlite.up.sql | 24 +++++++++++++++++++ 9 files changed, 71 insertions(+) rename persistence/sql/migrations/{20220728111500000000_create_device_link_request.up.sql => 20220728111500000000_create_device_link_request.postgres.up.sql} (100%) create mode 100644 persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.down.sql create mode 100644 persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.up.sql rename persistence/sql/migrations/{20220818111500000000_oauth2_device_code.up.sql => 20220818111500000000_oauth2_device_code.postgres.up.sql} (100%) create mode 100644 persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.down.sql create mode 100644 persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.up.sql rename persistence/sql/migrations/{20220818111500000000_oauth2_user_code.up.sql => 20220818111500000000_oauth2_user_code.postgres.up.sql} (100%) create mode 100644 persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.down.sql create mode 100644 persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.up.sql diff --git a/persistence/sql/migrations/20220728111500000000_create_device_link_request.up.sql b/persistence/sql/migrations/20220728111500000000_create_device_link_request.postgres.up.sql similarity index 100% rename from persistence/sql/migrations/20220728111500000000_create_device_link_request.up.sql rename to persistence/sql/migrations/20220728111500000000_create_device_link_request.postgres.up.sql diff --git a/persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.down.sql b/persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.down.sql new file mode 100644 index 00000000000..6ae609f00f3 --- /dev/null +++ b/persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS hydra_oauth2_device_link_request; \ No newline at end of file diff --git a/persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.up.sql b/persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.up.sql new file mode 100644 index 00000000000..46592d01f0a --- /dev/null +++ b/persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.up.sql @@ -0,0 +1,21 @@ +CREATE TABLE IF NOT EXISTS hydra_oauth2_device_link_request +( + challenge VARCHAR(40) NOT NULL PRIMARY KEY, + verifier VARCHAR(40) NOT NULL, + client_id VARCHAR(255) NOT NULL REFERENCES hydra_client (id) ON DELETE CASCADE, + request_url TEXT NOT NULL, + requested_scope TEXT NOT NULL, + device_code VARCHAR(255) NOT NULL, + csrf VARCHAR(40) NOT NULL, + requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + oidc_context TEXT NOT NULL, + login_challenge VARCHAR(40) NULL REFERENCES hydra_oauth2_authentication_request (challenge) ON DELETE SET NULL, + requested_at_audience TEXT NOT NULL DEFAULT '', + context TEXT NOT NULL DEFAULT '{}', + UNIQUE (challenge) +); + +CREATE INDEX hydra_oauth2_device_link_request_client_id_idx ON hydra_oauth2_consent_request (client_id); +CREATE INDEX hydra_oauth2_device_link_request_subject_idx ON hydra_oauth2_consent_request (subject); +CREATE INDEX hydra_oauth2_device_link_request_login_session_id_idx ON hydra_oauth2_consent_request (login_session_id); +CREATE INDEX hydra_oauth2_device_link_request_login_challenge_idx ON hydra_oauth2_consent_request (login_challenge); diff --git a/persistence/sql/migrations/20220818111500000000_oauth2_device_code.up.sql b/persistence/sql/migrations/20220818111500000000_oauth2_device_code.postgres.up.sql similarity index 100% rename from persistence/sql/migrations/20220818111500000000_oauth2_device_code.up.sql rename to persistence/sql/migrations/20220818111500000000_oauth2_device_code.postgres.up.sql diff --git a/persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.down.sql b/persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.down.sql new file mode 100644 index 00000000000..2df98f2f9de --- /dev/null +++ b/persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS hydra_oauth2_device_code; \ No newline at end of file diff --git a/persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.up.sql b/persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.up.sql new file mode 100644 index 00000000000..3b2d8a98bd9 --- /dev/null +++ b/persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.up.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS hydra_oauth2_device_code +( + signature VARCHAR(255) NOT NULL PRIMARY KEY, + user_code VARCHAR(40) NOT NULL, + request_id VARCHAR(40) NOT NULL, + requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + client_id VARCHAR(255) NOT NULL REFERENCES hydra_client (id) ON DELETE CASCADE, + scope TEXT NOT NULL, + granted_scope TEXT NOT NULL, + form_data TEXT NOT NULL, + session_data TEXT NOT NULL, + subject VARCHAR(255) NOT NULL DEFAULT '', + active INTEGER NOT NULL DEFAULT true, + requested_audience TEXT NULL DEFAULT '', + granted_audience TEXT NULL DEFAULT '', + challenge_id VARCHAR(40) NULL REFERENCES hydra_oauth2_device_link_request (challenge) ON DELETE CASCADE, + UNIQUE (request_id) +); + +CREATE INDEX hydra_oauth2_device_code_requested_at_idx ON hydra_oauth2_device_code (requested_at); +CREATE INDEX hydra_oauth2_device_code_client_id_idx ON hydra_oauth2_device_code (client_id); +CREATE INDEX hydra_oauth2_device_code_challenge_id_idx ON hydra_oauth2_device_code (challenge_id); +CREATE INDEX hydra_oauth2_device_code_client_id_subject_idx ON hydra_oauth2_device_code (client_id, subject); \ No newline at end of file diff --git a/persistence/sql/migrations/20220818111500000000_oauth2_user_code.up.sql b/persistence/sql/migrations/20220818111500000000_oauth2_user_code.postgres.up.sql similarity index 100% rename from persistence/sql/migrations/20220818111500000000_oauth2_user_code.up.sql rename to persistence/sql/migrations/20220818111500000000_oauth2_user_code.postgres.up.sql diff --git a/persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.down.sql b/persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.down.sql new file mode 100644 index 00000000000..32e3f163f78 --- /dev/null +++ b/persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS hydra_oauth2_user_code; \ No newline at end of file diff --git a/persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.up.sql b/persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.up.sql new file mode 100644 index 00000000000..7472a711ba9 --- /dev/null +++ b/persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.up.sql @@ -0,0 +1,24 @@ +CREATE TABLE IF NOT EXISTS hydra_oauth2_user_code +( + signature VARCHAR(255) NOT NULL PRIMARY KEY, + user_code VARCHAR(40) NOT NULL, + device_code VARCHAR(40) NOT NULL, + request_id VARCHAR(40) NOT NULL, + requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + client_id VARCHAR(255) NOT NULL REFERENCES hydra_client (id) ON DELETE CASCADE, + scope TEXT NOT NULL, + granted_scope TEXT NOT NULL, + form_data TEXT NOT NULL, + session_data TEXT NOT NULL, + subject VARCHAR(255) NOT NULL DEFAULT '', + active INTEGER NOT NULL DEFAULT true, + requested_audience TEXT NULL DEFAULT '', + granted_audience TEXT NULL DEFAULT '', + challenge_id VARCHAR(40) NULL REFERENCES hydra_oauth2_device_link_request (challenge) ON DELETE CASCADE, + UNIQUE (request_id) +); + +CREATE INDEX hydra_oauth2_user_code_requested_at_idx ON hydra_oauth2_user_code (requested_at); +CREATE INDEX hydra_oauth2_user_code_client_id_idx ON hydra_oauth2_user_code (client_id); +CREATE INDEX hydra_oauth2_user_code_challenge_id_idx ON hydra_oauth2_user_code (challenge_id); +CREATE INDEX hydra_oauth2_user_code_client_id_subject_idx ON hydra_oauth2_user_code (client_id, subject); \ No newline at end of file From af80c33033db0643b9749cf8d0cc10ef3a101844 Mon Sep 17 00:00:00 2001 From: Romain Caire Date: Mon, 29 Aug 2022 14:31:54 +0200 Subject: [PATCH 2/5] Add device_authorization_url to the settings and webfinger --- consent/handler.go | 2 +- consent/strategy_default.go | 2 +- consent/types.go | 1 + driver/config/provider.go | 7 +++---- driver/config/provider_test.go | 1 + internal/.hydra.yaml | 1 + internal/httpclient/models/well_known.go | 3 +++ oauth2/doc.go | 3 +++ oauth2/handler.go | 8 ++++++-- oauth2/handler_test.go | 1 + spec/api.json | 4 ++++ spec/config.json | 6 ++++++ 12 files changed, 31 insertions(+), 8 deletions(-) diff --git a/consent/handler.go b/consent/handler.go index ecbc50e57ed..683ccd2f9d8 100644 --- a/consent/handler.go +++ b/consent/handler.go @@ -48,7 +48,7 @@ const ( LoginPath = "/oauth2/auth/requests/login" ConsentPath = "/oauth2/auth/requests/consent" LogoutPath = "/oauth2/auth/requests/logout" - DevicePath = "/oauth2/auth/requests/device/usercode" + DevicePath = "/oauth2/auth/requests/device" SessionsPath = "/oauth2/auth/sessions" ) diff --git a/consent/strategy_default.go b/consent/strategy_default.go index 9ea5fa2de95..4db813fac02 100644 --- a/consent/strategy_default.go +++ b/consent/strategy_default.go @@ -236,7 +236,7 @@ func (s *DefaultStrategy) forwardAuthenticationRequest(w http.ResponseWriter, r if _, ok := ar.(fosite.AuthorizeRequester); ok { iu = s.c.OAuth2AuthURL() } else if _, ok := ar.(*fosite.DeviceAuthorizeRequest); ok { - iu = s.c.OAuth2DeviceAuthURL() + iu = s.c.OAuth2DeviceAuthorisationURL() } var idTokenHintClaims jwtgo.MapClaims diff --git a/consent/types.go b/consent/types.go index 1a3c63a9f4e..f4e3218653c 100644 --- a/consent/types.go +++ b/consent/types.go @@ -756,6 +756,7 @@ type DeviceLinkRequest struct { WasHandled bool `json:"-" db:"was_handled,r"` Verifier string `json:"-" db:"verifier"` + CSRF string `json:"-" db:"csrf"` // AuthenticatedAt sqlxx.NullTime `json:"-" db:"authenticated_at"` RequestedAt time.Time `json:"-" db:"requested_at"` diff --git a/driver/config/provider.go b/driver/config/provider.go index d2c3cba63eb..0f1cad168fb 100644 --- a/driver/config/provider.go +++ b/driver/config/provider.go @@ -35,7 +35,7 @@ const ( KeyOAuth2ClientRegistrationURL = "webfinger.oidc_discovery.client_registration_url" KeyOAuth2TokenURL = "webfinger.oidc_discovery.token_url" // #nosec G101 KeyOAuth2AuthURL = "webfinger.oidc_discovery.auth_url" - KeyOAuth2DeviceAuthURL = "webfinger.oidc_discovery.device_auth_url" + KeyOAuth2DeviceAuthorisationURL = "webfinger.oidc_discovery.device_authorization_url" KeyJWKSURL = "webfinger.oidc_discovery.jwks_url" KeyOIDCDiscoverySupportedClaims = "webfinger.oidc_discovery.supported_claims" KeyOIDCDiscoverySupportedScope = "webfinger.oidc_discovery.supported_scope" @@ -413,11 +413,10 @@ func (p *Provider) JWKSURL() *url.URL { return p.p.RequestURIF(KeyJWKSURL, urlx.AppendPaths(p.IssuerURL(), "/.well-known/jwks.json")) } -func (p *Provider) OAuth2DeviceAuthURL() *url.URL { - return p.p.RequestURIF(KeyOAuth2DeviceAuthURL, urlx.AppendPaths(p.PublicURL(), "/oauth2/device/auth")) +func (p *Provider) OAuth2DeviceAuthorisationURL() *url.URL { + return p.p.RequestURIF(KeyOAuth2DeviceAuthorisationURL, urlx.AppendPaths(p.PublicURL(), "/oauth2/device/auth")) } - func (p *Provider) TokenRefreshHookURL() *url.URL { return p.p.URIF(KeyRefreshTokenHookURL, nil) } diff --git a/driver/config/provider_test.go b/driver/config/provider_test.go index d6b57d288b6..0545d890950 100644 --- a/driver/config/provider_test.go +++ b/driver/config/provider_test.go @@ -266,6 +266,7 @@ func TestViperProviderValidates(t *testing.T) { // webfinger assert.Equal(t, []string{"hydra.openid.id-token"}, c.WellKnownKeys()) assert.Equal(t, urlx.ParseOrPanic("https://example.com"), c.OAuth2ClientRegistrationURL()) + assert.Equal(t, urlx.ParseOrPanic("https://example.com/device_authorization"), c.OAuth2DeviceAuthorisationURL()) assert.Equal(t, urlx.ParseOrPanic("https://example.com/jwks.json"), c.JWKSURL()) assert.Equal(t, urlx.ParseOrPanic("https://example.com/auth"), c.OAuth2AuthURL()) assert.Equal(t, urlx.ParseOrPanic("https://example.com/token"), c.OAuth2TokenURL()) diff --git a/internal/.hydra.yaml b/internal/.hydra.yaml index 63ed16b035e..d2fdd7cc80a 100644 --- a/internal/.hydra.yaml +++ b/internal/.hydra.yaml @@ -76,6 +76,7 @@ webfinger: auth_url: https://example.com/auth token_url: https://example.com/token client_registration_url: https://example.com + device_authorization_url: https://example.com/device_authorization supported_claims: - username supported_scope: diff --git a/internal/httpclient/models/well_known.go b/internal/httpclient/models/well_known.go index d06e5d74034..ae68e2cf766 100644 --- a/internal/httpclient/models/well_known.go +++ b/internal/httpclient/models/well_known.go @@ -86,6 +86,9 @@ type WellKnown struct { // Example: https://playground.ory.sh/ory-hydra/admin/client RegistrationEndpoint string `json:"registration_endpoint,omitempty"` + // URL of the authorization server's device authorization endpoint + DeviceAuthorisationEndpoint string `json:"device_authorization_endpoint"` + // JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP for Request Objects, // which are described in Section 6.1 of OpenID Connect Core 1.0 [OpenID.Core]. These algorithms are used both when // the Request Object is passed by value (using the request parameter) and when it is passed by reference diff --git a/oauth2/doc.go b/oauth2/doc.go index 12580667eaf..faea8e478d0 100644 --- a/oauth2/doc.go +++ b/oauth2/doc.go @@ -47,6 +47,9 @@ type WellKnown struct { // example: https://playground.ory.sh/ory-hydra/admin/client RegistrationEndpoint string `json:"registration_endpoint,omitempty"` + // URL of the authorization server's device authorization endpoint + DeviceAuthorisationEndpoint string `json:"device_authorization_endpoint"` + // URL of the OP's OAuth 2.0 Token Endpoint // // required: true diff --git a/oauth2/handler.go b/oauth2/handler.go index 7119441f92b..2bbd993e2fb 100644 --- a/oauth2/handler.go +++ b/oauth2/handler.go @@ -240,6 +240,7 @@ func (h *Handler) WellKnownHandler(w http.ResponseWriter, r *http.Request) { JWKsURI: h.c.JWKSURL().String(), RevocationEndpoint: urlx.AppendPaths(h.c.IssuerURL(), RevocationPath).String(), RegistrationEndpoint: h.c.OAuth2ClientRegistrationURL().String(), + DeviceAuthorisationEndpoint: h.c.OAuth2DeviceAuthorisationURL().String(), SubjectTypes: h.c.SubjectTypesSupported(), ResponseTypes: []string{"code", "code id_token", "id_token", "token id_token", "token", "token id_token code"}, ClaimsSupported: h.c.OIDCDiscoverySupportedClaims(), @@ -247,7 +248,7 @@ func (h *Handler) WellKnownHandler(w http.ResponseWriter, r *http.Request) { UserinfoEndpoint: h.c.OIDCDiscoveryUserinfoEndpoint().String(), TokenEndpointAuthMethodsSupported: []string{"client_secret_post", "client_secret_basic", "private_key_jwt", "none"}, IDTokenSigningAlgValuesSupported: []string{"RS256"}, - GrantTypesSupported: []string{"authorization_code", "implicit", "client_credentials", "refresh_token"}, + GrantTypesSupported: []string{"authorization_code", "implicit", "client_credentials", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code"}, ResponseModesSupported: []string{"query", "fragment"}, UserinfoSigningAlgValuesSupported: []string{"none", "RS256"}, RequestParameterSupported: true, @@ -849,9 +850,11 @@ func (h *Handler) DeviceAuthHandler(w http.ResponseWriter, r *http.Request, _ ht } // Generate the request URL - reqURL := h.c.OAuth2DeviceAuthURL() + reqURL := h.c.OAuth2DeviceAuthorisationURL() reqURL.RawQuery = r.URL.RawQuery + csrf := strings.Replace(uuid.New(), "-", "", -1) + // Create a Device Link Request, referencing the Device Code generated linkRequest := &consent.DeviceLinkRequest{ ID: deviceAuthorizeRequest.GetID(), @@ -860,6 +863,7 @@ func (h *Handler) DeviceAuthHandler(w http.ResponseWriter, r *http.Request, _ ht RequestedScope: []string(deviceAuthorizeRequest.GetRequestedScopes()), RequestedAudience: []string(deviceAuthorizeRequest.GetRequestedAudience()), Client: deviceAuthorizeRequest.GetClient().(*client.Client), + CSRF: csrf, RequestedAt: time.Now().Truncate(time.Second).UTC(), OpenIDConnectContext: nil, RequestURL: reqURL.String(), diff --git a/oauth2/handler_test.go b/oauth2/handler_test.go index f047719b76a..ae0b2caafad 100644 --- a/oauth2/handler_test.go +++ b/oauth2/handler_test.go @@ -431,6 +431,7 @@ func TestHandlerWellKnown(t *testing.T) { JWKsURI: conf.JWKSURL().String(), RevocationEndpoint: urlx.AppendPaths(conf.IssuerURL(), oauth2.RevocationPath).String(), RegistrationEndpoint: conf.OAuth2ClientRegistrationURL().String(), + DeviceAuthorisationEndpoint: conf.OAuth2DeviceAuthorisationURL().String(), SubjectTypes: []string{"pairwise", "public"}, ResponseTypes: []string{"code", "code id_token", "id_token", "token id_token", "token", "token id_token code"}, ClaimsSupported: conf.OIDCDiscoverySupportedClaims(), diff --git a/spec/api.json b/spec/api.json index 359a8a9b3d5..8f763843836 100755 --- a/spec/api.json +++ b/spec/api.json @@ -1506,6 +1506,10 @@ "example": "https://playground.ory.sh/ory-hydra/admin/client", "type": "string" }, + "device_authorization_endpoint": { + "description": "URL of the authorization server's device authorization endpoint", + "type": "string" + }, "request_object_signing_alg_values_supported": { "description": "JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP for Request Objects,\nwhich are described in Section 6.1 of OpenID Connect Core 1.0 [OpenID.Core]. These algorithms are used both when\nthe Request Object is passed by value (using the request parameter) and when it is passed by reference\n(using the request_uri parameter).", "items": { diff --git a/spec/config.json b/spec/config.json index cbd109f8a31..d7b57aeec2a 100644 --- a/spec/config.json +++ b/spec/config.json @@ -490,6 +490,12 @@ "format": "uri", "examples": ["https://my-service.com/clients"] }, + "device_authorization_url": { + "description": "Sets the OpenID Connect Dynamic Client Registration Endpoint", + "type": "string", + "format": "uri", + "examples": ["https://my-service.com/device_authorization"] + }, "supported_claims": { "type": "array", "description": "A list of supported claims to be broadcasted. Claim \"sub\" is always included.", From 00bd70f93ee7b72a09f689b1767e118ad9a3b248 Mon Sep 17 00:00:00 2001 From: Romain Caire Date: Mon, 29 Aug 2022 14:32:33 +0200 Subject: [PATCH 3/5] Merge sqlite migration into one big migration (instead of 3 files) --- ...create_device_link_request.sqlite.down.sql | 1 - ...0_create_device_link_request.sqlite.up.sql | 21 ------ ..._device_authorization_flow.sqlite.down.sql | 3 + ...00_device_authorization_flow.sqlite.up.sql | 70 +++++++++++++++++++ ...0000000_oauth2_device_code.sqlite.down.sql | 1 - ...500000000_oauth2_device_code.sqlite.up.sql | 23 ------ ...500000000_oauth2_user_code.sqlite.down.sql | 1 - ...11500000000_oauth2_user_code.sqlite.up.sql | 24 ------- 8 files changed, 73 insertions(+), 71 deletions(-) delete mode 100644 persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.down.sql delete mode 100644 persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.up.sql create mode 100644 persistence/sql/migrations/20220728111500000000_device_authorization_flow.sqlite.down.sql create mode 100644 persistence/sql/migrations/20220728111500000000_device_authorization_flow.sqlite.up.sql delete mode 100644 persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.down.sql delete mode 100644 persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.up.sql delete mode 100644 persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.down.sql delete mode 100644 persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.up.sql diff --git a/persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.down.sql b/persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.down.sql deleted file mode 100644 index 6ae609f00f3..00000000000 --- a/persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS hydra_oauth2_device_link_request; \ No newline at end of file diff --git a/persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.up.sql b/persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.up.sql deleted file mode 100644 index 46592d01f0a..00000000000 --- a/persistence/sql/migrations/20220728111500000000_create_device_link_request.sqlite.up.sql +++ /dev/null @@ -1,21 +0,0 @@ -CREATE TABLE IF NOT EXISTS hydra_oauth2_device_link_request -( - challenge VARCHAR(40) NOT NULL PRIMARY KEY, - verifier VARCHAR(40) NOT NULL, - client_id VARCHAR(255) NOT NULL REFERENCES hydra_client (id) ON DELETE CASCADE, - request_url TEXT NOT NULL, - requested_scope TEXT NOT NULL, - device_code VARCHAR(255) NOT NULL, - csrf VARCHAR(40) NOT NULL, - requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - oidc_context TEXT NOT NULL, - login_challenge VARCHAR(40) NULL REFERENCES hydra_oauth2_authentication_request (challenge) ON DELETE SET NULL, - requested_at_audience TEXT NOT NULL DEFAULT '', - context TEXT NOT NULL DEFAULT '{}', - UNIQUE (challenge) -); - -CREATE INDEX hydra_oauth2_device_link_request_client_id_idx ON hydra_oauth2_consent_request (client_id); -CREATE INDEX hydra_oauth2_device_link_request_subject_idx ON hydra_oauth2_consent_request (subject); -CREATE INDEX hydra_oauth2_device_link_request_login_session_id_idx ON hydra_oauth2_consent_request (login_session_id); -CREATE INDEX hydra_oauth2_device_link_request_login_challenge_idx ON hydra_oauth2_consent_request (login_challenge); diff --git a/persistence/sql/migrations/20220728111500000000_device_authorization_flow.sqlite.down.sql b/persistence/sql/migrations/20220728111500000000_device_authorization_flow.sqlite.down.sql new file mode 100644 index 00000000000..f0224174baa --- /dev/null +++ b/persistence/sql/migrations/20220728111500000000_device_authorization_flow.sqlite.down.sql @@ -0,0 +1,3 @@ +DROP TABLE IF EXISTS hydra_oauth2_device_link_request; +DROP TABLE IF EXISTS hydra_oauth2_device_code; +DROP TABLE IF EXISTS hydra_oauth2_user_code; \ No newline at end of file diff --git a/persistence/sql/migrations/20220728111500000000_device_authorization_flow.sqlite.up.sql b/persistence/sql/migrations/20220728111500000000_device_authorization_flow.sqlite.up.sql new file mode 100644 index 00000000000..4cedffd5fa4 --- /dev/null +++ b/persistence/sql/migrations/20220728111500000000_device_authorization_flow.sqlite.up.sql @@ -0,0 +1,70 @@ +CREATE TABLE IF NOT EXISTS hydra_oauth2_device_link_request +( + challenge VARCHAR(40) NOT NULL PRIMARY KEY, + verifier VARCHAR(40) NOT NULL, + client_id VARCHAR(255) NOT NULL REFERENCES hydra_client (id) ON DELETE CASCADE, + request_url TEXT NOT NULL, + requested_scope TEXT NOT NULL, + device_code VARCHAR(255) NOT NULL, + csrf VARCHAR(40) NOT NULL, + requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + oidc_context TEXT NOT NULL, + login_challenge VARCHAR(40) NULL REFERENCES hydra_oauth2_authentication_request (challenge) ON DELETE SET NULL, + requested_at_audience TEXT NOT NULL DEFAULT '', + context TEXT NOT NULL DEFAULT '{}', + UNIQUE (challenge) +); + +CREATE INDEX hydra_oauth2_device_link_request_client_id_idx ON hydra_oauth2_consent_request (client_id); +CREATE INDEX hydra_oauth2_device_link_request_subject_idx ON hydra_oauth2_consent_request (subject); +CREATE INDEX hydra_oauth2_device_link_request_login_session_id_idx ON hydra_oauth2_consent_request (login_session_id); +CREATE INDEX hydra_oauth2_device_link_request_login_challenge_idx ON hydra_oauth2_consent_request (login_challenge); + +--- +CREATE TABLE IF NOT EXISTS hydra_oauth2_device_code +( + signature VARCHAR(255) NOT NULL PRIMARY KEY, + request_id VARCHAR(40) NOT NULL, + requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + client_id VARCHAR(255) NOT NULL REFERENCES hydra_client (id) ON DELETE CASCADE, + scope TEXT NOT NULL, + granted_scope TEXT NOT NULL, + form_data TEXT NOT NULL, + session_data TEXT NOT NULL, + subject VARCHAR(255) NOT NULL DEFAULT '', + active INTEGER NOT NULL DEFAULT true, + requested_audience TEXT NULL DEFAULT '', + granted_audience TEXT NULL DEFAULT '', + challenge_id VARCHAR(40) NULL REFERENCES hydra_oauth2_device_link_request (challenge) ON DELETE CASCADE, + UNIQUE (request_id) +); + +CREATE INDEX hydra_oauth2_device_code_requested_at_idx ON hydra_oauth2_device_code (requested_at); +CREATE INDEX hydra_oauth2_device_code_client_id_idx ON hydra_oauth2_device_code (client_id); +CREATE INDEX hydra_oauth2_device_code_challenge_id_idx ON hydra_oauth2_device_code (challenge_id); +CREATE INDEX hydra_oauth2_device_code_client_id_subject_idx ON hydra_oauth2_device_code (client_id, subject); + +--- + +CREATE TABLE IF NOT EXISTS hydra_oauth2_user_code +( + signature VARCHAR(255) NOT NULL PRIMARY KEY, + request_id VARCHAR(40) NOT NULL, + requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + client_id VARCHAR(255) NOT NULL REFERENCES hydra_client (id) ON DELETE CASCADE, + scope TEXT NOT NULL, + granted_scope TEXT NOT NULL, + form_data TEXT NOT NULL, + session_data TEXT NOT NULL, + subject VARCHAR(255) NOT NULL DEFAULT '', + active INTEGER NOT NULL DEFAULT true, + requested_audience TEXT NULL DEFAULT '', + granted_audience TEXT NULL DEFAULT '', + challenge_id VARCHAR(40) NULL REFERENCES hydra_oauth2_device_link_request (challenge) ON DELETE CASCADE, + UNIQUE (request_id) +); + +CREATE INDEX hydra_oauth2_user_code_requested_at_idx ON hydra_oauth2_user_code (requested_at); +CREATE INDEX hydra_oauth2_user_code_client_id_idx ON hydra_oauth2_user_code (client_id); +CREATE INDEX hydra_oauth2_user_code_challenge_id_idx ON hydra_oauth2_user_code (challenge_id); +CREATE INDEX hydra_oauth2_user_code_client_id_subject_idx ON hydra_oauth2_user_code (client_id, subject); \ No newline at end of file diff --git a/persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.down.sql b/persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.down.sql deleted file mode 100644 index 2df98f2f9de..00000000000 --- a/persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS hydra_oauth2_device_code; \ No newline at end of file diff --git a/persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.up.sql b/persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.up.sql deleted file mode 100644 index 3b2d8a98bd9..00000000000 --- a/persistence/sql/migrations/20220818111500000000_oauth2_device_code.sqlite.up.sql +++ /dev/null @@ -1,23 +0,0 @@ -CREATE TABLE IF NOT EXISTS hydra_oauth2_device_code -( - signature VARCHAR(255) NOT NULL PRIMARY KEY, - user_code VARCHAR(40) NOT NULL, - request_id VARCHAR(40) NOT NULL, - requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - client_id VARCHAR(255) NOT NULL REFERENCES hydra_client (id) ON DELETE CASCADE, - scope TEXT NOT NULL, - granted_scope TEXT NOT NULL, - form_data TEXT NOT NULL, - session_data TEXT NOT NULL, - subject VARCHAR(255) NOT NULL DEFAULT '', - active INTEGER NOT NULL DEFAULT true, - requested_audience TEXT NULL DEFAULT '', - granted_audience TEXT NULL DEFAULT '', - challenge_id VARCHAR(40) NULL REFERENCES hydra_oauth2_device_link_request (challenge) ON DELETE CASCADE, - UNIQUE (request_id) -); - -CREATE INDEX hydra_oauth2_device_code_requested_at_idx ON hydra_oauth2_device_code (requested_at); -CREATE INDEX hydra_oauth2_device_code_client_id_idx ON hydra_oauth2_device_code (client_id); -CREATE INDEX hydra_oauth2_device_code_challenge_id_idx ON hydra_oauth2_device_code (challenge_id); -CREATE INDEX hydra_oauth2_device_code_client_id_subject_idx ON hydra_oauth2_device_code (client_id, subject); \ No newline at end of file diff --git a/persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.down.sql b/persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.down.sql deleted file mode 100644 index 32e3f163f78..00000000000 --- a/persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS hydra_oauth2_user_code; \ No newline at end of file diff --git a/persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.up.sql b/persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.up.sql deleted file mode 100644 index 7472a711ba9..00000000000 --- a/persistence/sql/migrations/20220818111500000000_oauth2_user_code.sqlite.up.sql +++ /dev/null @@ -1,24 +0,0 @@ -CREATE TABLE IF NOT EXISTS hydra_oauth2_user_code -( - signature VARCHAR(255) NOT NULL PRIMARY KEY, - user_code VARCHAR(40) NOT NULL, - device_code VARCHAR(40) NOT NULL, - request_id VARCHAR(40) NOT NULL, - requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - client_id VARCHAR(255) NOT NULL REFERENCES hydra_client (id) ON DELETE CASCADE, - scope TEXT NOT NULL, - granted_scope TEXT NOT NULL, - form_data TEXT NOT NULL, - session_data TEXT NOT NULL, - subject VARCHAR(255) NOT NULL DEFAULT '', - active INTEGER NOT NULL DEFAULT true, - requested_audience TEXT NULL DEFAULT '', - granted_audience TEXT NULL DEFAULT '', - challenge_id VARCHAR(40) NULL REFERENCES hydra_oauth2_device_link_request (challenge) ON DELETE CASCADE, - UNIQUE (request_id) -); - -CREATE INDEX hydra_oauth2_user_code_requested_at_idx ON hydra_oauth2_user_code (requested_at); -CREATE INDEX hydra_oauth2_user_code_client_id_idx ON hydra_oauth2_user_code (client_id); -CREATE INDEX hydra_oauth2_user_code_challenge_id_idx ON hydra_oauth2_user_code (challenge_id); -CREATE INDEX hydra_oauth2_user_code_client_id_subject_idx ON hydra_oauth2_user_code (client_id, subject); \ No newline at end of file From 5367ee9fc3f25c3d56bdcdfb104468fd0c7e437d Mon Sep 17 00:00:00 2001 From: Romain Caire Date: Tue, 30 Aug 2022 10:03:12 +0200 Subject: [PATCH 4/5] Update the api documentation --- consent/handler.go | 4 +-- spec/api.json | 89 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/consent/handler.go b/consent/handler.go index 683ccd2f9d8..62f3859c127 100644 --- a/consent/handler.go +++ b/consent/handler.go @@ -787,7 +787,7 @@ func (h *Handler) GetLogoutRequest(w http.ResponseWriter, r *http.Request, ps ht func (h *Handler) VerifyDeviceAuthUserCode(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { type payload struct { - UserCode string `json:"userCode"` + UserCode string `json:"user_code"` } var body payload @@ -797,7 +797,7 @@ func (h *Handler) VerifyDeviceAuthUserCode(w http.ResponseWriter, r *http.Reques } if body.UserCode == "" { - h.r.Writer().WriteError(w, r, errorsx.WithStack(fosite.ErrInvalidRequest.WithHint(`Request body parameter 'userCode' is not defined but should have been.`))) + h.r.Writer().WriteError(w, r, errorsx.WithStack(fosite.ErrInvalidRequest.WithHint(`Request body parameter 'user_code' is not defined but should have been.`))) return } diff --git a/spec/api.json b/spec/api.json index 8f763843836..a2a84d2be2a 100755 --- a/spec/api.json +++ b/spec/api.json @@ -432,6 +432,19 @@ "title": "HandledLoginRequest is the request payload used to accept a login request.", "type": "object" }, + "verifyDeviceRequest": { + "properties": { + "user_code": { + "description": "The end-user verification code.", + "type": "string" + } + }, + "required": [ + "user_code" + ], + "title": "The response payload sent when verifying a device user_code request.", + "type": "object" + }, "completedRequest": { "properties": { "redirect_to": { @@ -1507,7 +1520,8 @@ "type": "string" }, "device_authorization_endpoint": { - "description": "URL of the authorization server's device authorization endpoint", + "description": "URL of the server's device authorization authorization endpoint", + "example": "https://playground.ory.sh/ory-hydra/public/oauth2/device/auth", "type": "string" }, "request_object_signing_alg_values_supported": { @@ -3041,6 +3055,79 @@ ] } }, + "/oauth2/auth/requests/device/verify": { + "post": { + "description": "Verify a user_code to initiate a device auth flow", + "operationId": "verifyDeviceRequest", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/verifyDeviceRequest" + } + } + }, + "required": true, + "x-originalParamName": "Body" + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/completedRequest" + } + } + }, + "description": "completedRequest" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "410": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/requestWasHandledResponse" + } + } + }, + "description": "requestWasHandledResponse" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + } + }, + "summary": "POST a Device Verify Request", + "tags": [ + "admin" + ] + } + }, "/oauth2/auth/requests/logout": { "get": { "description": "Use this endpoint to fetch a logout request.", From 1f0468f093d5d46b9956048c3c36173fa9e0864a Mon Sep 17 00:00:00 2001 From: Romain Caire Date: Tue, 30 Aug 2022 15:27:50 +0200 Subject: [PATCH 5/5] Pass information via URL --- consent/strategy_default.go | 10 +++------- oauth2/handler.go | 2 +- ...11500000000_device_authorization_flow.sqlite.up.sql | 3 +-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/consent/strategy_default.go b/consent/strategy_default.go index 4db813fac02..668809b6dac 100644 --- a/consent/strategy_default.go +++ b/consent/strategy_default.go @@ -230,15 +230,11 @@ func (s *DefaultStrategy) forwardAuthenticationRequest(w http.ResponseWriter, r // Generate the request URL iu := s.c.OAuth2AuthURL() - iu.RawQuery = r.URL.RawQuery - - // Identify requester type - if _, ok := ar.(fosite.AuthorizeRequester); ok { - iu = s.c.OAuth2AuthURL() - } else if _, ok := ar.(*fosite.DeviceAuthorizeRequest); ok { + if _, ok := ar.(*fosite.DeviceAuthorizeRequest); ok { iu = s.c.OAuth2DeviceAuthorisationURL() } - + iu.RawQuery = r.URL.RawQuery + var idTokenHintClaims jwtgo.MapClaims if idTokenHint := ar.GetRequestForm().Get("id_token_hint"); len(idTokenHint) > 0 { claims, err := s.getIDTokenHintClaims(r.Context(), idTokenHint) diff --git a/oauth2/handler.go b/oauth2/handler.go index 2bbd993e2fb..fcd18a18208 100644 --- a/oauth2/handler.go +++ b/oauth2/handler.go @@ -851,7 +851,7 @@ func (h *Handler) DeviceAuthHandler(w http.ResponseWriter, r *http.Request, _ ht // Generate the request URL reqURL := h.c.OAuth2DeviceAuthorisationURL() - reqURL.RawQuery = r.URL.RawQuery + reqURL.RawQuery = deviceAuthorizeRequest.GetRequestForm().Encode() csrf := strings.Replace(uuid.New(), "-", "", -1) diff --git a/persistence/sql/migrations/20220728111500000000_device_authorization_flow.sqlite.up.sql b/persistence/sql/migrations/20220728111500000000_device_authorization_flow.sqlite.up.sql index 4cedffd5fa4..92c23f5bb75 100644 --- a/persistence/sql/migrations/20220728111500000000_device_authorization_flow.sqlite.up.sql +++ b/persistence/sql/migrations/20220728111500000000_device_authorization_flow.sqlite.up.sql @@ -11,7 +11,6 @@ CREATE TABLE IF NOT EXISTS hydra_oauth2_device_link_request oidc_context TEXT NOT NULL, login_challenge VARCHAR(40) NULL REFERENCES hydra_oauth2_authentication_request (challenge) ON DELETE SET NULL, requested_at_audience TEXT NOT NULL DEFAULT '', - context TEXT NOT NULL DEFAULT '{}', UNIQUE (challenge) ); @@ -35,7 +34,7 @@ CREATE TABLE IF NOT EXISTS hydra_oauth2_device_code active INTEGER NOT NULL DEFAULT true, requested_audience TEXT NULL DEFAULT '', granted_audience TEXT NULL DEFAULT '', - challenge_id VARCHAR(40) NULL REFERENCES hydra_oauth2_device_link_request (challenge) ON DELETE CASCADE, + challenge_id VARCHAR(40) NULL REFERENCES hydra_oauth2_consent_request (challenge) ON DELETE CASCADE, UNIQUE (request_id) );