diff --git a/client/.snapshots/TestHandler-common-case=create_clients-case=0-description=basic_dynamic_client_registration.json b/client/.snapshots/TestHandler-common-case=create_clients-case=0-description=basic_dynamic_client_registration.json index d402913453f..f3861abd417 100644 --- a/client/.snapshots/TestHandler-common-case=create_clients-case=0-description=basic_dynamic_client_registration.json +++ b/client/.snapshots/TestHandler-common-case=create_clients-case=0-description=basic_dynamic_client_registration.json @@ -30,5 +30,8 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null } diff --git a/client/.snapshots/TestHandler-common-case=create_clients-case=1-description=basic_admin_registration.json b/client/.snapshots/TestHandler-common-case=create_clients-case=1-description=basic_admin_registration.json index 3d01d99c968..bad8f965c5f 100644 --- a/client/.snapshots/TestHandler-common-case=create_clients-case=1-description=basic_admin_registration.json +++ b/client/.snapshots/TestHandler-common-case=create_clients-case=1-description=basic_admin_registration.json @@ -33,5 +33,8 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null } diff --git a/client/.snapshots/TestHandler-common-case=create_clients-case=10-description=empty_ID_succeeds.json b/client/.snapshots/TestHandler-common-case=create_clients-case=10-description=empty_ID_succeeds.json index bf89ac9fbb8..19b5e5afae5 100644 --- a/client/.snapshots/TestHandler-common-case=create_clients-case=10-description=empty_ID_succeeds.json +++ b/client/.snapshots/TestHandler-common-case=create_clients-case=10-description=empty_ID_succeeds.json @@ -31,5 +31,8 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null } diff --git a/client/.snapshots/TestHandler-common-case=create_clients-case=2-description=empty_ID_succeeds.json b/client/.snapshots/TestHandler-common-case=create_clients-case=2-description=empty_ID_succeeds.json index c21aa5b3710..e23aa7bed82 100644 --- a/client/.snapshots/TestHandler-common-case=create_clients-case=2-description=empty_ID_succeeds.json +++ b/client/.snapshots/TestHandler-common-case=create_clients-case=2-description=empty_ID_succeeds.json @@ -30,5 +30,8 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null } diff --git a/client/.snapshots/TestHandler-common-case=create_clients-case=4-description=non-uuid_works.json b/client/.snapshots/TestHandler-common-case=create_clients-case=4-description=non-uuid_works.json index de17dbecbf4..93accecbcf1 100644 --- a/client/.snapshots/TestHandler-common-case=create_clients-case=4-description=non-uuid_works.json +++ b/client/.snapshots/TestHandler-common-case=create_clients-case=4-description=non-uuid_works.json @@ -33,5 +33,8 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null } diff --git a/client/.snapshots/TestHandler-common-case=create_clients-case=5-description=setting_client_id_as_uuid_works.json b/client/.snapshots/TestHandler-common-case=create_clients-case=5-description=setting_client_id_as_uuid_works.json index c1688b1c48a..059d8813c51 100644 --- a/client/.snapshots/TestHandler-common-case=create_clients-case=5-description=setting_client_id_as_uuid_works.json +++ b/client/.snapshots/TestHandler-common-case=create_clients-case=5-description=setting_client_id_as_uuid_works.json @@ -33,5 +33,8 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null } diff --git a/client/.snapshots/TestHandler-common-case=create_clients-case=7-description=setting_skip_consent_suceeds_for_admin_registration.json b/client/.snapshots/TestHandler-common-case=create_clients-case=7-description=setting_skip_consent_suceeds_for_admin_registration.json index 96fa08bab16..91e85c55a58 100644 --- a/client/.snapshots/TestHandler-common-case=create_clients-case=7-description=setting_skip_consent_suceeds_for_admin_registration.json +++ b/client/.snapshots/TestHandler-common-case=create_clients-case=7-description=setting_skip_consent_suceeds_for_admin_registration.json @@ -31,5 +31,8 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null } diff --git a/client/.snapshots/TestHandler-common-case=create_clients-case=8-description=empty_ID_succeeds.json b/client/.snapshots/TestHandler-common-case=create_clients-case=8-description=empty_ID_succeeds.json index c21aa5b3710..e23aa7bed82 100644 --- a/client/.snapshots/TestHandler-common-case=create_clients-case=8-description=empty_ID_succeeds.json +++ b/client/.snapshots/TestHandler-common-case=create_clients-case=8-description=empty_ID_succeeds.json @@ -30,5 +30,8 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null } diff --git a/client/.snapshots/TestHandler-common-case=create_clients-case=8-description=setting_skip_consent_suceeds_for_admin_registration.json b/client/.snapshots/TestHandler-common-case=create_clients-case=8-description=setting_skip_consent_suceeds_for_admin_registration.json index 96fa08bab16..91e85c55a58 100644 --- a/client/.snapshots/TestHandler-common-case=create_clients-case=8-description=setting_skip_consent_suceeds_for_admin_registration.json +++ b/client/.snapshots/TestHandler-common-case=create_clients-case=8-description=setting_skip_consent_suceeds_for_admin_registration.json @@ -31,5 +31,8 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null } diff --git a/client/.snapshots/TestHandler-common-case=create_clients-case=9-description=empty_ID_succeeds.json b/client/.snapshots/TestHandler-common-case=create_clients-case=9-description=empty_ID_succeeds.json index bf89ac9fbb8..19b5e5afae5 100644 --- a/client/.snapshots/TestHandler-common-case=create_clients-case=9-description=empty_ID_succeeds.json +++ b/client/.snapshots/TestHandler-common-case=create_clients-case=9-description=empty_ID_succeeds.json @@ -31,5 +31,8 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null } diff --git a/client/.snapshots/TestHandler-common-case=fetching_existing_client-endpoint=admin.json b/client/.snapshots/TestHandler-common-case=fetching_existing_client-endpoint=admin.json index a48c8c9851e..e19f722b885 100644 --- a/client/.snapshots/TestHandler-common-case=fetching_existing_client-endpoint=admin.json +++ b/client/.snapshots/TestHandler-common-case=fetching_existing_client-endpoint=admin.json @@ -31,7 +31,10 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null }, "status": 200 } diff --git a/client/.snapshots/TestHandler-common-case=fetching_existing_client-endpoint=selfservice.json b/client/.snapshots/TestHandler-common-case=fetching_existing_client-endpoint=selfservice.json index 9ecd6e81401..4fc3e539fc4 100644 --- a/client/.snapshots/TestHandler-common-case=fetching_existing_client-endpoint=selfservice.json +++ b/client/.snapshots/TestHandler-common-case=fetching_existing_client-endpoint=selfservice.json @@ -30,7 +30,10 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null }, "status": 200 } diff --git a/client/.snapshots/TestHandler-common-case=update_the_lifespans_of_an_OAuth2_client.json b/client/.snapshots/TestHandler-common-case=update_the_lifespans_of_an_OAuth2_client.json index f9f149a6b61..3cf4efc2090 100644 --- a/client/.snapshots/TestHandler-common-case=update_the_lifespans_of_an_OAuth2_client.json +++ b/client/.snapshots/TestHandler-common-case=update_the_lifespans_of_an_OAuth2_client.json @@ -31,7 +31,10 @@ "jwt_bearer_grant_access_token_lifespan": "37h0m0s", "refresh_token_grant_id_token_lifespan": "40h0m0s", "refresh_token_grant_access_token_lifespan": "41h0m0s", - "refresh_token_grant_refresh_token_lifespan": "42h0m0s" + "refresh_token_grant_refresh_token_lifespan": "42h0m0s", + "device_authorization_grant_id_token_lifespan": "45h0m0s", + "device_authorization_grant_access_token_lifespan": "46h0m0s", + "device_authorization_grant_refresh_token_lifespan": "47h0m0s" }, "status": 200 } diff --git a/client/.snapshots/TestHandler-common-case=updating_existing_client-endpoint=admin.json b/client/.snapshots/TestHandler-common-case=updating_existing_client-endpoint=admin.json index f009bd97d7e..292f831d0e3 100644 --- a/client/.snapshots/TestHandler-common-case=updating_existing_client-endpoint=admin.json +++ b/client/.snapshots/TestHandler-common-case=updating_existing_client-endpoint=admin.json @@ -33,7 +33,10 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null }, "status": 200 } diff --git a/client/.snapshots/TestHandler-common-case=updating_existing_client-endpoint=dynamic_client_registration.json b/client/.snapshots/TestHandler-common-case=updating_existing_client-endpoint=dynamic_client_registration.json index 0892b6a9b4e..983ebc06943 100644 --- a/client/.snapshots/TestHandler-common-case=updating_existing_client-endpoint=dynamic_client_registration.json +++ b/client/.snapshots/TestHandler-common-case=updating_existing_client-endpoint=dynamic_client_registration.json @@ -32,7 +32,10 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null }, "status": 200 } diff --git a/client/.snapshots/TestHandler-create_client_registration_tokens-case=0-dynamic=true.json b/client/.snapshots/TestHandler-create_client_registration_tokens-case=0-dynamic=true.json index 578eb529c1f..4711dfe6f84 100644 --- a/client/.snapshots/TestHandler-create_client_registration_tokens-case=0-dynamic=true.json +++ b/client/.snapshots/TestHandler-create_client_registration_tokens-case=0-dynamic=true.json @@ -26,5 +26,8 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null } diff --git a/client/.snapshots/TestHandler-create_client_registration_tokens-case=1-dynamic=false.json b/client/.snapshots/TestHandler-create_client_registration_tokens-case=1-dynamic=false.json index 578eb529c1f..4711dfe6f84 100644 --- a/client/.snapshots/TestHandler-create_client_registration_tokens-case=1-dynamic=false.json +++ b/client/.snapshots/TestHandler-create_client_registration_tokens-case=1-dynamic=false.json @@ -26,5 +26,8 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null } diff --git a/client/.snapshots/TestHandler-create_client_registration_tokens-case=2-dynamic=false.json b/client/.snapshots/TestHandler-create_client_registration_tokens-case=2-dynamic=false.json index 080a2092914..16ee45e265a 100644 --- a/client/.snapshots/TestHandler-create_client_registration_tokens-case=2-dynamic=false.json +++ b/client/.snapshots/TestHandler-create_client_registration_tokens-case=2-dynamic=false.json @@ -27,5 +27,8 @@ "jwt_bearer_grant_access_token_lifespan": null, "refresh_token_grant_id_token_lifespan": null, "refresh_token_grant_access_token_lifespan": null, - "refresh_token_grant_refresh_token_lifespan": null + "refresh_token_grant_refresh_token_lifespan": null, + "device_authorization_grant_id_token_lifespan": null, + "device_authorization_grant_access_token_lifespan": null, + "device_authorization_grant_refresh_token_lifespan": null } diff --git a/client/client.go b/client/client.go index 3d588b45e2d..094bd10b3bc 100644 --- a/client/client.go +++ b/client/client.go @@ -376,6 +376,21 @@ type Lifespans struct { // // The lifespan of a refresh token issued by the OAuth2 2.0 Refresh Token Grant for this OAuth 2.0 Client. RefreshTokenGrantRefreshTokenLifespan x.NullDuration `json:"refresh_token_grant_refresh_token_lifespan,omitempty" db:"refresh_token_grant_refresh_token_lifespan"` + + // OAuth2 2.0 Device Authorization Grant ID Token Lifespan + // + // The lifespan of an ID token issued by the OAuth2 2.0 Device Authorization Grant for this OAuth 2.0 Client. + DeviceAuthorizationGrantIDTokenLifespan x.NullDuration `json:"device_authorization_grant_id_token_lifespan,omitempty" db:"device_authorization_grant_id_token_lifespan"` + + // OAuth2 2.0 Device Authorization Grant Access Token Lifespan + // + // The lifespan of an access token issued by the OAuth2 2.0 Device Authorization Grant for this OAuth 2.0 Client. + DeviceAuthorizationGrantAccessTokenLifespan x.NullDuration `json:"device_authorization_grant_access_token_lifespan,omitempty" db:"device_authorization_grant_access_token_lifespan"` + + // OAuth2 2.0 Device Authorization Grant Device Authorization Lifespan + // + // The lifespan of a Device Authorization issued by the OAuth2 2.0 Device Authorization Grant for this OAuth 2.0 Client. + DeviceAuthorizationGrantRefreshTokenLifespan x.NullDuration `json:"device_authorization_grant_refresh_token_lifespan,omitempty" db:"device_authorization_grant_refresh_token_lifespan"` } func (Client) TableName() string { @@ -546,6 +561,14 @@ func (c *Client) GetEffectiveLifespan(gt fosite.GrantType, tt fosite.TokenType, } else if tt == fosite.RefreshToken && c.RefreshTokenGrantRefreshTokenLifespan.Valid { cl = &c.RefreshTokenGrantRefreshTokenLifespan.Duration } + } else if gt == fosite.GrantTypeDeviceCode { + if tt == fosite.AccessToken && c.DeviceAuthorizationGrantAccessTokenLifespan.Valid { + cl = &c.DeviceAuthorizationGrantAccessTokenLifespan.Duration + } else if tt == fosite.IDToken && c.DeviceAuthorizationGrantIDTokenLifespan.Valid { + cl = &c.DeviceAuthorizationGrantIDTokenLifespan.Duration + } else if tt == fosite.RefreshToken && c.DeviceAuthorizationGrantRefreshTokenLifespan.Valid { + cl = &c.DeviceAuthorizationGrantRefreshTokenLifespan.Duration + } } if cl == nil { diff --git a/consent/handler.go b/consent/handler.go index d17d6542680..1d16c5d67e1 100644 --- a/consent/handler.go +++ b/consent/handler.go @@ -1124,12 +1124,6 @@ func (h *Handler) acceptUserCodeRequest(w http.ResponseWriter, r *http.Request, return } - err = h.r.OAuth2Storage().UpdateAndInvalidateUserCodeSession(r.Context(), userCodeSignature, f.ID) - if err != nil { - h.r.Writer().WriteError(w, r, errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithHint(`Could not invalidate 'user_code'`))) - return - } - p := flow.HandledDeviceUserAuthRequest{ ID: f.DeviceChallengeID.String(), RequestedAt: cr.RequestedAt, diff --git a/consent/handler_test.go b/consent/handler_test.go index 5195e62715e..1db0f53f6ec 100644 --- a/consent/handler_test.go +++ b/consent/handler_test.go @@ -424,14 +424,19 @@ func TestAcceptDuplicateDeviceRequest(t *testing.T) { require.Contains(t, result.RedirectTo, requestURL) require.Contains(t, result.RedirectTo, "device_verifier") - // set the HTTP method, url, and request body req2, err := http.NewRequest(http.MethodPut, ts.URL+"/admin"+DevicePath+"/accept?challenge="+challenge, bytes.NewBuffer(acceptUserCodeJson)) if err != nil { panic(err) } resp2, err := c.Do(req2) require.NoError(t, err) - require.EqualValues(t, http.StatusNotFound, resp2.StatusCode) + require.EqualValues(t, http.StatusOK, resp2.StatusCode) + + var result2 flow.OAuth2RedirectTo + require.NoError(t, json.NewDecoder(resp2.Body).Decode(&result2)) + require.NotNil(t, result2.RedirectTo) + require.Contains(t, result2.RedirectTo, requestURL) + require.Contains(t, result2.RedirectTo, "device_verifier") } func TestAcceptCodeDeviceRequestFailure(t *testing.T) { diff --git a/consent/strategy_default.go b/consent/strategy_default.go index 19f2bb240f9..1a5f846f36c 100644 --- a/consent/strategy_default.go +++ b/consent/strategy_default.go @@ -1233,7 +1233,15 @@ func (s *DefaultStrategy) HandleOAuth2DeviceAuthorizationRequest( ar.RequestedAudience = fosite.Arguments(deviceFlow.RequestedAudience) } + // TODO(nsklikas): wrap these 2 function calls in a transaction (one persists the flow and the other invalidates the user_code) consentSession, f, err := s.handleOAuth2AuthorizationRequest(ctx, w, r, ar, deviceFlow) + if err != nil { + return nil, nil, err + } + err = s.r.OAuth2Storage().UpdateAndInvalidateUserCodeSessionByRequestID(r.Context(), string(f.DeviceCodeRequestID), f.ID) + if err != nil { + return nil, nil, err + } return consentSession, f, err } diff --git a/go.mod b/go.mod index 5534d5d0c6b..9d7e0c45f00 100644 --- a/go.mod +++ b/go.mod @@ -255,4 +255,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/ory/fosite => github.com/canonical/fosite v0.0.0-20240412170332-7fe9b8979dd3 +replace github.com/ory/fosite => github.com/canonical/fosite v0.0.0-20240429123227-550839b153b3 diff --git a/go.sum b/go.sum index 97eb49b7d2d..bf5a399194f 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4Yn github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= -github.com/canonical/fosite v0.0.0-20240412170332-7fe9b8979dd3 h1:ZDkf+uEuw7eOY/JcRUoncTbt+WWG0TwoIjJ8hHU5Uuw= -github.com/canonical/fosite v0.0.0-20240412170332-7fe9b8979dd3/go.mod h1:G5iZOjyC42o5uZaZK4GQdrqQeLxWZ4NZpD3rDRYM0Mc= +github.com/canonical/fosite v0.0.0-20240429123227-550839b153b3 h1:SprW/xD/qL7w03vFvtzBwYJOB3gAwSXSz3lVmqsDikw= +github.com/canonical/fosite v0.0.0-20240429123227-550839b153b3/go.mod h1:G5iZOjyC42o5uZaZK4GQdrqQeLxWZ4NZpD3rDRYM0Mc= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= diff --git a/internal/testhelpers/lifespans.go b/internal/testhelpers/lifespans.go index 86477c90b09..e2ba8a218c4 100644 --- a/internal/testhelpers/lifespans.go +++ b/internal/testhelpers/lifespans.go @@ -11,16 +11,19 @@ import ( ) var TestLifespans = client.Lifespans{ - AuthorizationCodeGrantAccessTokenLifespan: x.NullDuration{Duration: 31 * time.Hour, Valid: true}, - AuthorizationCodeGrantIDTokenLifespan: x.NullDuration{Duration: 32 * time.Hour, Valid: true}, - AuthorizationCodeGrantRefreshTokenLifespan: x.NullDuration{Duration: 33 * time.Hour, Valid: true}, - ClientCredentialsGrantAccessTokenLifespan: x.NullDuration{Duration: 34 * time.Hour, Valid: true}, - ImplicitGrantAccessTokenLifespan: x.NullDuration{Duration: 35 * time.Hour, Valid: true}, - ImplicitGrantIDTokenLifespan: x.NullDuration{Duration: 36 * time.Hour, Valid: true}, - JwtBearerGrantAccessTokenLifespan: x.NullDuration{Duration: 37 * time.Hour, Valid: true}, - PasswordGrantAccessTokenLifespan: x.NullDuration{Duration: 38 * time.Hour, Valid: true}, - PasswordGrantRefreshTokenLifespan: x.NullDuration{Duration: 39 * time.Hour, Valid: true}, - RefreshTokenGrantIDTokenLifespan: x.NullDuration{Duration: 40 * time.Hour, Valid: true}, - RefreshTokenGrantAccessTokenLifespan: x.NullDuration{Duration: 41 * time.Hour, Valid: true}, - RefreshTokenGrantRefreshTokenLifespan: x.NullDuration{Duration: 42 * time.Hour, Valid: true}, + AuthorizationCodeGrantAccessTokenLifespan: x.NullDuration{Duration: 31 * time.Hour, Valid: true}, + AuthorizationCodeGrantIDTokenLifespan: x.NullDuration{Duration: 32 * time.Hour, Valid: true}, + AuthorizationCodeGrantRefreshTokenLifespan: x.NullDuration{Duration: 33 * time.Hour, Valid: true}, + ClientCredentialsGrantAccessTokenLifespan: x.NullDuration{Duration: 34 * time.Hour, Valid: true}, + ImplicitGrantAccessTokenLifespan: x.NullDuration{Duration: 35 * time.Hour, Valid: true}, + ImplicitGrantIDTokenLifespan: x.NullDuration{Duration: 36 * time.Hour, Valid: true}, + JwtBearerGrantAccessTokenLifespan: x.NullDuration{Duration: 37 * time.Hour, Valid: true}, + PasswordGrantAccessTokenLifespan: x.NullDuration{Duration: 38 * time.Hour, Valid: true}, + PasswordGrantRefreshTokenLifespan: x.NullDuration{Duration: 39 * time.Hour, Valid: true}, + RefreshTokenGrantIDTokenLifespan: x.NullDuration{Duration: 40 * time.Hour, Valid: true}, + RefreshTokenGrantAccessTokenLifespan: x.NullDuration{Duration: 41 * time.Hour, Valid: true}, + RefreshTokenGrantRefreshTokenLifespan: x.NullDuration{Duration: 42 * time.Hour, Valid: true}, + DeviceAuthorizationGrantIDTokenLifespan: x.NullDuration{Duration: 45 * time.Hour, Valid: true}, + DeviceAuthorizationGrantAccessTokenLifespan: x.NullDuration{Duration: 46 * time.Hour, Valid: true}, + DeviceAuthorizationGrantRefreshTokenLifespan: x.NullDuration{Duration: 47 * time.Hour, Valid: true}, } diff --git a/oauth2/oauth2_auth_code_test.go b/oauth2/oauth2_auth_code_test.go index cc5fccdffbb..8f33b16f835 100644 --- a/oauth2/oauth2_auth_code_test.go +++ b/oauth2/oauth2_auth_code_test.go @@ -1945,39 +1945,3 @@ func newOAuth2Client( Scopes: strings.Split(c.Scope, " "), } } - -func newDeviceClient( - t *testing.T, - reg interface { - config.Provider - client.Registry - }, - opts ...func(*client.Client), -) (*client.Client, *oauth2.Config) { - ctx := context.Background() - c := &client.Client{ - GrantTypes: []string{ - "refresh_token", - "urn:ietf:params:oauth:grant-type:device_code", - }, - Scope: "hydra offline openid", - Audience: []string{"https://api.ory.sh/"}, - TokenEndpointAuthMethod: "none", - } - - // apply options - for _, o := range opts { - o(c) - } - - require.NoError(t, reg.ClientManager().CreateClient(ctx, c)) - return c, &oauth2.Config{ - ClientID: c.GetID(), - Endpoint: oauth2.Endpoint{ - DeviceAuthURL: reg.Config().OAuth2DeviceAuthorisationURL(ctx).String(), - TokenURL: reg.Config().OAuth2TokenURL(ctx).String(), - AuthStyle: oauth2.AuthStyleInHeader, - }, - Scopes: strings.Split(c.Scope, " "), - } -} diff --git a/oauth2/oauth2_device_code_test.go b/oauth2/oauth2_device_code_test.go index aacdddf1d8d..1a768a3eed3 100644 --- a/oauth2/oauth2_device_code_test.go +++ b/oauth2/oauth2_device_code_test.go @@ -442,7 +442,7 @@ func TestDeviceCodeWithDefaultStrategy(t *testing.T) { acceptConsentHandler(t, c, subject, nil), ) - resp, err := getDeviceCode(t, conf, nil, oauth2.SetAuthURLParam("nonce", nonce)) + resp, err := getDeviceCode(t, conf, nil) require.NoError(t, err) require.NotEmpty(t, resp.DeviceCode) require.NotEmpty(t, resp.UserCode) @@ -508,4 +508,188 @@ func TestDeviceCodeWithDefaultStrategy(t *testing.T) { run(t, "opaque") }) }) + t.Run("case=perform flow with audience", func(t *testing.T) { + expectAud := "https://api.ory.sh/" + c, conf := newDeviceClient(t, reg) + testhelpers.NewDeviceLoginConsentUI( + t, + reg.Config(), + acceptDeviceHandler(t, c), + acceptLoginHandler(t, c, subject, func(r *hydra.OAuth2LoginRequest) *hydra.AcceptOAuth2LoginRequest { + assert.False(t, r.Skip) + assert.EqualValues(t, []string{expectAud}, r.RequestedAccessTokenAudience) + return nil + }), + acceptConsentHandler(t, c, subject, func(r *hydra.OAuth2ConsentRequest) { + assert.False(t, *r.Skip) + assert.EqualValues(t, []string{expectAud}, r.RequestedAccessTokenAudience) + }), + ) + + resp, err := getDeviceCode(t, conf, nil, oauth2.SetAuthURLParam("audience", "https://api.ory.sh/")) + require.NoError(t, err) + require.NotEmpty(t, resp.DeviceCode) + require.NotEmpty(t, resp.UserCode) + loginFlowResp := acceptUserCode(t, conf, nil, resp) + require.NotNil(t, loginFlowResp) + + token, err := conf.DeviceAccessToken(context.Background(), resp) + require.NoError(t, err) + + claims := introspectAccessToken(t, conf, token, subject) + aud := claims.Get("aud").Array() + require.Len(t, aud, 1) + assert.EqualValues(t, aud[0].String(), expectAud) + + assertIDToken(t, token, conf, subject, nonce, time.Now().Add(reg.Config().GetIDTokenLifespan(ctx))) + }) + + t.Run("case=respects client token lifespan configuration", func(t *testing.T) { + run := func(t *testing.T, strategy string, c *client.Client, conf *oauth2.Config, expectedLifespans client.Lifespans) { + testhelpers.NewDeviceLoginConsentUI( + t, + reg.Config(), + acceptDeviceHandler(t, c), + acceptLoginHandler(t, c, subject, nil), + acceptConsentHandler(t, c, subject, nil), + ) + + resp, err := getDeviceCode(t, conf, nil) + require.NoError(t, err) + require.NotEmpty(t, resp.DeviceCode) + require.NotEmpty(t, resp.UserCode) + loginFlowResp := acceptUserCode(t, conf, nil, resp) + require.NotNil(t, loginFlowResp) + + token, err := conf.DeviceAccessToken(context.Background(), resp) + iat := time.Now() + require.NoError(t, err) + + body := introspectAccessToken(t, conf, token, subject) + requirex.EqualTime(t, iat.Add(expectedLifespans.DeviceAuthorizationGrantAccessTokenLifespan.Duration), time.Unix(body.Get("exp").Int(), 0), time.Second) + + assertJWTAccessToken(t, strategy, conf, token, subject, iat.Add(expectedLifespans.DeviceAuthorizationGrantAccessTokenLifespan.Duration), `["hydra","offline","openid"]`) + assertIDToken(t, token, conf, subject, nonce, iat.Add(expectedLifespans.DeviceAuthorizationGrantIDTokenLifespan.Duration)) + assertRefreshToken(t, token, conf, iat.Add(expectedLifespans.DeviceAuthorizationGrantRefreshTokenLifespan.Duration)) + + t.Run("followup=successfully perform refresh token flow", func(t *testing.T) { + require.NotEmpty(t, token.RefreshToken) + token.Expiry = token.Expiry.Add(-time.Hour * 24) + refreshedToken, err := conf.TokenSource(context.Background(), token).Token() + iat = time.Now() + require.NoError(t, err) + assertRefreshToken(t, refreshedToken, conf, iat.Add(expectedLifespans.RefreshTokenGrantRefreshTokenLifespan.Duration)) + assertJWTAccessToken(t, strategy, conf, refreshedToken, subject, iat.Add(expectedLifespans.RefreshTokenGrantAccessTokenLifespan.Duration), `["hydra","offline","openid"]`) + assertIDToken(t, refreshedToken, conf, subject, nonce, iat.Add(expectedLifespans.RefreshTokenGrantIDTokenLifespan.Duration)) + + require.NotEqual(t, token.AccessToken, refreshedToken.AccessToken) + require.NotEqual(t, token.RefreshToken, refreshedToken.RefreshToken) + require.NotEqual(t, token.Extra("id_token"), refreshedToken.Extra("id_token")) + + body := introspectAccessToken(t, conf, refreshedToken, subject) + requirex.EqualTime(t, iat.Add(expectedLifespans.RefreshTokenGrantAccessTokenLifespan.Duration), time.Unix(body.Get("exp").Int(), 0), time.Second) + + t.Run("followup=original access token is no longer valid", func(t *testing.T) { + i := testhelpers.IntrospectToken(t, conf, token.AccessToken, adminTS) + assert.False(t, i.Get("active").Bool(), "%s", i) + }) + + t.Run("followup=original refresh token is no longer valid", func(t *testing.T) { + _, err := conf.TokenSource(context.Background(), token).Token() + assert.Error(t, err) + }) + }) + } + + t.Run("case=custom-lifespans-active-jwt", func(t *testing.T) { + c, conf := newDeviceClient(t, reg) + ls := testhelpers.TestLifespans + ls.DeviceAuthorizationGrantAccessTokenLifespan = x.NullDuration{Valid: true, Duration: 6 * time.Second} + testhelpers.UpdateClientTokenLifespans( + t, + &oauth2.Config{ClientID: c.GetID(), ClientSecret: conf.ClientSecret}, + c.GetID(), + ls, adminTS, + ) + reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, "jwt") + run(t, "jwt", c, conf, ls) + }) + + t.Run("case=custom-lifespans-active-opaque", func(t *testing.T) { + c, conf := newDeviceClient(t, reg) + ls := testhelpers.TestLifespans + ls.DeviceAuthorizationGrantAccessTokenLifespan = x.NullDuration{Valid: true, Duration: 6 * time.Second} + testhelpers.UpdateClientTokenLifespans( + t, + &oauth2.Config{ClientID: c.GetID(), ClientSecret: conf.ClientSecret}, + c.GetID(), + ls, adminTS, + ) + reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, "opaque") + run(t, "opaque", c, conf, ls) + }) + + t.Run("case=custom-lifespans-unset", func(t *testing.T) { + c, conf := newDeviceClient(t, reg) + testhelpers.UpdateClientTokenLifespans(t, &oauth2.Config{ClientID: c.GetID(), ClientSecret: conf.ClientSecret}, c.GetID(), testhelpers.TestLifespans, adminTS) + testhelpers.UpdateClientTokenLifespans(t, &oauth2.Config{ClientID: c.GetID(), ClientSecret: conf.ClientSecret}, c.GetID(), client.Lifespans{}, adminTS) + reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, "opaque") + + //goland:noinspection GoDeprecation + expectedLifespans := client.Lifespans{ + AuthorizationCodeGrantAccessTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetAccessTokenLifespan(ctx)}, + AuthorizationCodeGrantIDTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetIDTokenLifespan(ctx)}, + AuthorizationCodeGrantRefreshTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetRefreshTokenLifespan(ctx)}, + ClientCredentialsGrantAccessTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetAccessTokenLifespan(ctx)}, + ImplicitGrantAccessTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetAccessTokenLifespan(ctx)}, + ImplicitGrantIDTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetIDTokenLifespan(ctx)}, + JwtBearerGrantAccessTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetAccessTokenLifespan(ctx)}, + PasswordGrantAccessTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetAccessTokenLifespan(ctx)}, + PasswordGrantRefreshTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetRefreshTokenLifespan(ctx)}, + RefreshTokenGrantIDTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetIDTokenLifespan(ctx)}, + RefreshTokenGrantAccessTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetAccessTokenLifespan(ctx)}, + RefreshTokenGrantRefreshTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetRefreshTokenLifespan(ctx)}, + DeviceAuthorizationGrantIDTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetIDTokenLifespan(ctx)}, + DeviceAuthorizationGrantAccessTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetAccessTokenLifespan(ctx)}, + DeviceAuthorizationGrantRefreshTokenLifespan: x.NullDuration{Valid: true, Duration: reg.Config().GetRefreshTokenLifespan(ctx)}, + } + run(t, "opaque", c, conf, expectedLifespans) + }) + }) +} + +func newDeviceClient( + t *testing.T, + reg interface { + config.Provider + client.Registry + }, + opts ...func(*client.Client), +) (*client.Client, *oauth2.Config) { + ctx := context.Background() + c := &client.Client{ + GrantTypes: []string{ + "refresh_token", + "urn:ietf:params:oauth:grant-type:device_code", + }, + Scope: "hydra offline openid", + Audience: []string{"https://api.ory.sh/"}, + TokenEndpointAuthMethod: "none", + } + + // apply options + for _, o := range opts { + o(c) + } + + require.NoError(t, reg.ClientManager().CreateClient(ctx, c)) + return c, &oauth2.Config{ + ClientID: c.GetID(), + Endpoint: oauth2.Endpoint{ + DeviceAuthURL: reg.Config().OAuth2DeviceAuthorisationURL(ctx).String(), + TokenURL: reg.Config().OAuth2TokenURL(ctx).String(), + AuthStyle: oauth2.AuthStyleInHeader, + }, + Scopes: strings.Split(c.Scope, " "), + } } diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0001.json b/persistence/sql/migratest/fixtures/hydra_client/client-0001.json index 0f7229417b1..93b4cfde17a 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0001.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0001.json @@ -36,6 +36,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0002.json b/persistence/sql/migratest/fixtures/hydra_client/client-0002.json index bd2d7bda658..f1e4d3f7353 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0002.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0002.json @@ -36,6 +36,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0003.json b/persistence/sql/migratest/fixtures/hydra_client/client-0003.json index ea3f0d95301..655277e2b69 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0003.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0003.json @@ -36,6 +36,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0004.json b/persistence/sql/migratest/fixtures/hydra_client/client-0004.json index 82fd03429ab..3895ebd6a62 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0004.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0004.json @@ -36,6 +36,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0005.json b/persistence/sql/migratest/fixtures/hydra_client/client-0005.json index c89be3f4bb6..b38c60e0553 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0005.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0005.json @@ -36,6 +36,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0006.json b/persistence/sql/migratest/fixtures/hydra_client/client-0006.json index a07a5da9e37..dc7b86e5367 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0006.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0006.json @@ -36,6 +36,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0007.json b/persistence/sql/migratest/fixtures/hydra_client/client-0007.json index 7b5d580f877..a0823c96486 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0007.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0007.json @@ -36,6 +36,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0008.json b/persistence/sql/migratest/fixtures/hydra_client/client-0008.json index 2544f493e6c..ac2db2fa9d2 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0008.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0008.json @@ -38,6 +38,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0009.json b/persistence/sql/migratest/fixtures/hydra_client/client-0009.json index 0cd3e722bab..30c46226447 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0009.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0009.json @@ -38,6 +38,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0010.json b/persistence/sql/migratest/fixtures/hydra_client/client-0010.json index 6d853edd8fe..b9e15e1e6f2 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0010.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0010.json @@ -38,6 +38,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0011.json b/persistence/sql/migratest/fixtures/hydra_client/client-0011.json index f87f5d936e8..0f014cc6e4d 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0011.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0011.json @@ -40,6 +40,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0012.json b/persistence/sql/migratest/fixtures/hydra_client/client-0012.json index 31596741085..c9a6ffb0718 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0012.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0012.json @@ -40,6 +40,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0013.json b/persistence/sql/migratest/fixtures/hydra_client/client-0013.json index 6e8db49ad17..19db3c20cad 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0013.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0013.json @@ -40,6 +40,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0014.json b/persistence/sql/migratest/fixtures/hydra_client/client-0014.json index 6bc35306d1f..ff52afbdb4e 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0014.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0014.json @@ -40,6 +40,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-0015.json b/persistence/sql/migratest/fixtures/hydra_client/client-0015.json index 68e599cb13c..f0616fc067f 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-0015.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-0015.json @@ -40,6 +40,18 @@ "Duration": 154000000000, "Valid": true }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 155000000000, "Valid": true diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-20.json b/persistence/sql/migratest/fixtures/hydra_client/client-20.json index 46f83b5b308..0aa3634ed69 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-20.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-20.json @@ -40,6 +40,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-2005.json b/persistence/sql/migratest/fixtures/hydra_client/client-2005.json index cc18982abf5..0f1cccb1512 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-2005.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-2005.json @@ -40,6 +40,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migratest/fixtures/hydra_client/client-21.json b/persistence/sql/migratest/fixtures/hydra_client/client-21.json index c867fe47587..f0328055006 100644 --- a/persistence/sql/migratest/fixtures/hydra_client/client-21.json +++ b/persistence/sql/migratest/fixtures/hydra_client/client-21.json @@ -44,6 +44,18 @@ "Duration": 0, "Valid": false }, + "DeviceAuthorizationGrantAccessTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantIDTokenLifespan": { + "Duration": 0, + "Valid": false + }, + "DeviceAuthorizationGrantRefreshTokenLifespan": { + "Duration": 0, + "Valid": false + }, "ImplicitGrantAccessTokenLifespan": { "Duration": 0, "Valid": false diff --git a/persistence/sql/migrations/20240202000001000000_device_flow.cockroach.up.sql b/persistence/sql/migrations/20240202000001000000_device_flow.cockroach.up.sql index b6973e6fe05..71b3dbcdcf1 100644 --- a/persistence/sql/migrations/20240202000001000000_device_flow.cockroach.up.sql +++ b/persistence/sql/migrations/20240202000001000000_device_flow.cockroach.up.sql @@ -60,3 +60,7 @@ ALTER TABLE hydra_oauth2_flow ADD COLUMN device_handled_at TIMESTAMP NULL; ALTER TABLE hydra_oauth2_flow ADD COLUMN device_error TEXT NULL; CREATE INDEX hydra_oauth2_flow_device_challenge_idx ON hydra_oauth2_flow (device_challenge_id); + +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_id_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_access_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_refresh_token_lifespan BIGINT NULL DEFAULT NULL; diff --git a/persistence/sql/migrations/20240202000001000000_device_flow.down.sql b/persistence/sql/migrations/20240202000001000000_device_flow.down.sql index 8f7cdcf7799..54ceefff83b 100644 --- a/persistence/sql/migrations/20240202000001000000_device_flow.down.sql +++ b/persistence/sql/migrations/20240202000001000000_device_flow.down.sql @@ -20,3 +20,8 @@ ALTER TABLE hydra_oauth2_flow DROP COLUMN IF EXISTS device_user_code_accepted_at ALTER TABLE hydra_oauth2_flow DROP COLUMN IF EXISTS device_was_used; ALTER TABLE hydra_oauth2_flow DROP COLUMN IF EXISTS device_handled_at; ALTER TABLE hydra_oauth2_flow DROP COLUMN IF EXISTS device_error; + + +ALTER TABLE hydra_client DROP COLUMN device_authorization_grant_id_token_lifespan; +ALTER TABLE hydra_client DROP COLUMN device_authorization_grant_access_token_lifespan; +ALTER TABLE hydra_client DROP COLUMN device_authorization_grant_refresh_token_lifespan; \ No newline at end of file diff --git a/persistence/sql/migrations/20240202000001000000_device_flow.mysql.up.sql b/persistence/sql/migrations/20240202000001000000_device_flow.mysql.up.sql index f27750ce750..82221b44faa 100644 --- a/persistence/sql/migrations/20240202000001000000_device_flow.mysql.up.sql +++ b/persistence/sql/migrations/20240202000001000000_device_flow.mysql.up.sql @@ -60,3 +60,7 @@ ALTER TABLE hydra_oauth2_flow ADD COLUMN device_handled_at TIMESTAMP NULL; ALTER TABLE hydra_oauth2_flow ADD COLUMN device_error TEXT NULL; CREATE INDEX hydra_oauth2_flow_device_challenge_idx ON hydra_oauth2_flow (device_challenge_id); + +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_id_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_access_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_refresh_token_lifespan BIGINT NULL DEFAULT NULL; diff --git a/persistence/sql/migrations/20240202000001000000_device_flow.postgres.up.sql b/persistence/sql/migrations/20240202000001000000_device_flow.postgres.up.sql index 46fdb8829ba..35cb837b1b4 100644 --- a/persistence/sql/migrations/20240202000001000000_device_flow.postgres.up.sql +++ b/persistence/sql/migrations/20240202000001000000_device_flow.postgres.up.sql @@ -58,3 +58,7 @@ ALTER TABLE hydra_oauth2_flow ADD COLUMN device_handled_at TIMESTAMP NULL; ALTER TABLE hydra_oauth2_flow ADD COLUMN device_error TEXT NULL; CREATE INDEX hydra_oauth2_flow_device_challenge_idx ON hydra_oauth2_flow (device_challenge_id); + +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_id_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_access_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_refresh_token_lifespan BIGINT NULL DEFAULT NULL; diff --git a/persistence/sql/migrations/20240202000001000000_device_flow.up.sql b/persistence/sql/migrations/20240202000001000000_device_flow.up.sql index dc298394680..d3e145ae0d7 100644 --- a/persistence/sql/migrations/20240202000001000000_device_flow.up.sql +++ b/persistence/sql/migrations/20240202000001000000_device_flow.up.sql @@ -52,3 +52,7 @@ ALTER TABLE hydra_oauth2_flow ADD COLUMN device_handled_at TIMESTAMP NULL; ALTER TABLE hydra_oauth2_flow ADD COLUMN device_error TEXT NULL; CREATE INDEX hydra_oauth2_flow_device_challenge_idx ON hydra_oauth2_flow (device_challenge_id); + +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_id_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_access_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_refresh_token_lifespan BIGINT NULL DEFAULT NULL; diff --git a/persistence/sql/persister_oauth2.go b/persistence/sql/persister_oauth2.go index c9d970f22c4..a1d8d42712c 100644 --- a/persistence/sql/persister_oauth2.go +++ b/persistence/sql/persister_oauth2.go @@ -692,18 +692,20 @@ func (p *Persister) InvalidateUserCodeSession(ctx context.Context, signature str ) } -// UpdateAndInvalidateUserCodeSession invalidates a user code session and connects it with the device flow challenge ID -func (p *Persister) UpdateAndInvalidateUserCodeSession(ctx context.Context, signature, challenge_id string) (err error) { +// UpdateAndInvalidateUserCodeSession invalidates a user code session and connects it with the device flow request ID +func (p *Persister) UpdateAndInvalidateUserCodeSessionByRequestID(ctx context.Context, request_id, challenge_id string) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UpdateAndInvalidateUserCodeSession") defer otelx.End(span, &err) + // TODO(nsklikas): afaict this is supposed to return an error if no rows were updated, but this is not the actual behavior. + // We need to either fix this OR do a select -> check -> update (this would require 2 queries instead of 1). /* #nosec G201 table is static */ return sqlcon.HandleError( p.Connection(ctx). RawQuery( - fmt.Sprintf("UPDATE %s SET active=false, challenge_id=? WHERE signature=? AND nid = ?", OAuth2RequestSQL{Table: sqlTableUserCode}.TableName()), + fmt.Sprintf("UPDATE %s SET active=false, challenge_id=? WHERE request_id=? AND nid = ? AND active=true", OAuth2RequestSQL{Table: sqlTableUserCode}.TableName()), challenge_id, - signature, + request_id, p.NetworkID(ctx), ). Exec(), diff --git a/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.cockroach.up.sql b/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.cockroach.up.sql index 0dfaef14c2d..ebcb1929702 100644 --- a/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.cockroach.up.sql +++ b/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.cockroach.up.sql @@ -58,3 +58,7 @@ ALTER TABLE hydra_oauth2_flow ADD COLUMN device_handled_at TIMESTAMP NULL; ALTER TABLE hydra_oauth2_flow ADD COLUMN device_error TEXT NULL; CREATE INDEX hydra_oauth2_flow_device_challenge_idx ON hydra_oauth2_flow (device_challenge_id); + +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_id_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_access_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_refresh_token_lifespan BIGINT NULL DEFAULT NULL; diff --git a/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.down.sql b/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.down.sql index b7cb314a3d3..2547f10e767 100644 --- a/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.down.sql +++ b/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.down.sql @@ -18,3 +18,8 @@ ALTER TABLE hydra_oauth2_flow DROP COLUMN IF EXISTS device_user_code_accepted_at ALTER TABLE hydra_oauth2_flow DROP COLUMN IF EXISTS device_was_used; ALTER TABLE hydra_oauth2_flow DROP COLUMN IF EXISTS device_handled_at; ALTER TABLE hydra_oauth2_flow DROP COLUMN IF EXISTS device_error; + + +ALTER TABLE hydra_client DROP COLUMN device_authorization_grant_id_token_lifespan; +ALTER TABLE hydra_client DROP COLUMN device_authorization_grant_access_token_lifespan; +ALTER TABLE hydra_client DROP COLUMN device_authorization_grant_refresh_token_lifespan; \ No newline at end of file diff --git a/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.mysql.up.sql b/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.mysql.up.sql index 22c5205e415..0494de00165 100644 --- a/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.mysql.up.sql +++ b/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.mysql.up.sql @@ -58,3 +58,7 @@ ALTER TABLE hydra_oauth2_flow ADD COLUMN device_handled_at TIMESTAMP NULL; ALTER TABLE hydra_oauth2_flow ADD COLUMN device_error TEXT NULL; CREATE INDEX hydra_oauth2_flow_device_challenge_idx ON hydra_oauth2_flow (device_challenge_id); + +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_id_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_access_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_refresh_token_lifespan BIGINT NULL DEFAULT NULL; diff --git a/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.postgres.up.sql b/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.postgres.up.sql index 94dbcf69108..d6e3143d9eb 100644 --- a/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.postgres.up.sql +++ b/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.postgres.up.sql @@ -56,3 +56,7 @@ ALTER TABLE hydra_oauth2_flow ADD COLUMN device_handled_at TIMESTAMP NULL; ALTER TABLE hydra_oauth2_flow ADD COLUMN device_error TEXT NULL; CREATE INDEX hydra_oauth2_flow_device_challenge_idx ON hydra_oauth2_flow (device_challenge_id); + +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_id_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_access_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_refresh_token_lifespan BIGINT NULL DEFAULT NULL; diff --git a/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.up.sql b/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.up.sql index e9dd361f23b..559860a5d3b 100644 --- a/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.up.sql +++ b/persistence/sql/src/YYYYMMDD000001_device_flow/20240202000001000000_device_flow.up.sql @@ -50,3 +50,7 @@ ALTER TABLE hydra_oauth2_flow ADD COLUMN device_handled_at TIMESTAMP NULL; ALTER TABLE hydra_oauth2_flow ADD COLUMN device_error TEXT NULL; CREATE INDEX hydra_oauth2_flow_device_challenge_idx ON hydra_oauth2_flow (device_challenge_id); + +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_id_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_access_token_lifespan BIGINT NULL DEFAULT NULL; +ALTER TABLE hydra_client ADD COLUMN device_authorization_grant_refresh_token_lifespan BIGINT NULL DEFAULT NULL; diff --git a/x/fosite_storer.go b/x/fosite_storer.go index 26c9b7232c7..7e0f8758a70 100644 --- a/x/fosite_storer.go +++ b/x/fosite_storer.go @@ -51,5 +51,5 @@ type FositeStorer interface { GetDeviceCodeSessionByRequestID(ctx context.Context, requestID string, requester fosite.Session) (fosite.Requester, error) UpdateDeviceCodeSessionByRequestID(ctx context.Context, requestID string, requester fosite.Requester) error - UpdateAndInvalidateUserCodeSession(ctx context.Context, signature, challenge_id string) (err error) + UpdateAndInvalidateUserCodeSessionByRequestID(ctx context.Context, signature, request_id string) (err error) }