diff --git a/consent/csrf.go b/consent/csrf.go index 42588390a52..0a21c8e28ab 100644 --- a/consent/csrf.go +++ b/consent/csrf.go @@ -10,6 +10,8 @@ import ( "strings" "time" + "github.com/ory/hydra/v2/flow" + "github.com/gorilla/sessions" "github.com/ory/fosite" @@ -18,6 +20,8 @@ import ( "github.com/ory/x/mapx" ) +// WARNING - changes in this file need to be mirrored elsewhere. + func createCsrfSession(w http.ResponseWriter, r *http.Request, conf x.CookieConfigProvider, store sessions.Store, name string, csrfValue string, maxAge time.Duration) error { // Errors can be ignored here, because we always get a session back. Error typically means that the // session doesn't exist yet. @@ -45,7 +49,7 @@ func createCsrfSession(w http.ResponseWriter, r *http.Request, conf x.CookieConf return nil } -func validateCsrfSession(r *http.Request, conf x.CookieConfigProvider, store sessions.Store, name, expectedCSRF string) error { +func ValidateCsrfSession(r *http.Request, conf x.CookieConfigProvider, store sessions.Store, name, expectedCSRF string, f *flow.Flow) error { if cookie, err := getCsrfSession(r, store, conf, name); err != nil { return errorsx.WithStack(fosite.ErrRequestForbidden.WithHint("CSRF session cookie could not be decoded.")) } else if csrf, err := mapx.GetString(cookie.Values, "csrf"); err != nil { diff --git a/consent/handler.go b/consent/handler.go index d0d3fd2aa2b..e313b7229e6 100644 --- a/consent/handler.go +++ b/consent/handler.go @@ -492,7 +492,6 @@ func (h *Handler) acceptOAuth2LoginRequest(w http.ResponseWriter, r *http.Reques } events.Trace(ctx, events.LoginAccepted, events.WithClientID(request.Client.GetID()), events.WithSubject(request.Subject)) - h.r.Writer().Write(w, r, &flow.OAuth2RedirectTo{ RedirectTo: urlx.SetQuery(ru, url.Values{"login_verifier": {verifier}}).String(), }) diff --git a/consent/helper_test.go b/consent/helper_test.go index a5f09e81cdd..4a347575f3a 100644 --- a/consent/helper_test.go +++ b/consent/helper_test.go @@ -267,7 +267,7 @@ func TestValidateCsrfSession(t *testing.T) { assert.NoError(t, err, "failed to save cookie %s", c.name) } - err := validateCsrfSession(r, config, store, name, tc.csrfValue) + err := ValidateCsrfSession(r, config, store, name, tc.csrfValue, new(flow.Flow)) if tc.expectError { assert.Error(t, err) } else { diff --git a/consent/strategy_default.go b/consent/strategy_default.go index 28fba843443..5d8a11d58f7 100644 --- a/consent/strategy_default.go +++ b/consent/strategy_default.go @@ -370,7 +370,7 @@ func (s *DefaultStrategy) verifyAuthentication( } clientSpecificCookieNameLoginCSRF := fmt.Sprintf("%s_%s", s.r.Config().CookieNameLoginCSRF(ctx), session.LoginRequest.Client.CookieSuffix()) - if err := validateCsrfSession(r, s.r.Config(), store, clientSpecificCookieNameLoginCSRF, session.LoginRequest.CSRF); err != nil { + if err := ValidateCsrfSession(r, s.r.Config(), store, clientSpecificCookieNameLoginCSRF, session.LoginRequest.CSRF, f); err != nil { return nil, err } @@ -684,7 +684,7 @@ func (s *DefaultStrategy) verifyConsent(ctx context.Context, _ http.ResponseWrit } clientSpecificCookieNameConsentCSRF := fmt.Sprintf("%s_%s", s.r.Config().CookieNameConsentCSRF(ctx), session.ConsentRequest.Client.CookieSuffix()) - if err := validateCsrfSession(r, s.r.Config(), store, clientSpecificCookieNameConsentCSRF, session.ConsentRequest.CSRF); err != nil { + if err := ValidateCsrfSession(r, s.r.Config(), store, clientSpecificCookieNameConsentCSRF, session.ConsentRequest.CSRF, f); err != nil { return nil, nil, err } diff --git a/flow/consent_types.go b/flow/consent_types.go index 0f6237b7dc7..86c7fff7729 100644 --- a/flow/consent_types.go +++ b/flow/consent_types.go @@ -174,6 +174,11 @@ type AcceptOAuth2ConsentRequest struct { // the flow. WasHandled bool `json:"-"` + // Context is an optional object which can hold arbitrary data. The data will be made available when fetching the + // consent request under the "context" field. This is useful in scenarios where login and consent endpoints share + // data. + Context sqlxx.JSONRawMessage `json:"context"` + ConsentRequest *OAuth2ConsentRequest `json:"-"` Error *RequestDeniedError `json:"-"` RequestedAt time.Time `json:"-"` @@ -240,6 +245,11 @@ type OAuth2ConsentSession struct { // the flow. WasHandled bool `json:"-" db:"was_used"` + // Context is an optional object which can hold arbitrary data. The data will be made available when fetching the + // consent request under the "context" field. This is useful in scenarios where login and consent endpoints share + // data. + Context sqlxx.JSONRawMessage `json:"context"` + // Consent Request // // The consent request that lead to this consent session. diff --git a/flow/flow.go b/flow/flow.go index 7e8eeb077c8..b926ce5db96 100644 --- a/flow/flow.go +++ b/flow/flow.go @@ -290,6 +290,11 @@ func (f *Flow) HandleLoginRequest(h *HandledLoginRequest) error { } else { f.State = FlowStateLoginUnused } + + if f.Context != nil { + f.Context = h.Context + } + f.ID = h.ID f.Subject = h.Subject f.ForceSubjectIdentifier = h.ForceSubjectIdentifier @@ -301,7 +306,6 @@ func (f *Flow) HandleLoginRequest(h *HandledLoginRequest) error { f.LoginExtendSessionLifespan = h.ExtendSessionLifespan f.ACR = h.ACR f.AMR = h.AMR - f.Context = h.Context f.LoginWasUsed = h.WasHandled f.LoginAuthenticatedAt = h.AuthenticatedAt return nil @@ -394,6 +398,9 @@ func (f *Flow) HandleConsentRequest(r *AcceptOAuth2ConsentRequest) error { f.ConsentHandledAt = r.HandledAt f.ConsentWasHandled = r.WasHandled f.ConsentError = r.Error + if r.Context != nil { + f.Context = r.Context + } if r.Session != nil { f.SessionIDToken = r.Session.IDToken @@ -458,6 +465,7 @@ func (f *Flow) GetHandledConsentRequest() *AcceptOAuth2ConsentRequest { RememberFor: crf, HandledAt: f.ConsentHandledAt, WasHandled: f.ConsentWasHandled, + Context: f.Context, ConsentRequest: f.GetConsentRequest(), Error: f.ConsentError, RequestedAt: f.RequestedAt, diff --git a/flow/flow_test.go b/flow/flow_test.go index 25a832c780e..43a54176088 100644 --- a/flow/flow_test.go +++ b/flow/flow_test.go @@ -87,6 +87,9 @@ func (f *Flow) setHandledConsentRequest(r AcceptOAuth2ConsentRequest) { f.LoginAuthenticatedAt = r.AuthenticatedAt f.SessionIDToken = r.SessionIDToken f.SessionAccessToken = r.SessionAccessToken + if r.Context != nil { + f.Context = r.Context + } } func TestFlow_GetLoginRequest(t *testing.T) { diff --git a/internal/httpclient/api/openapi.yaml b/internal/httpclient/api/openapi.yaml index b4585938ff7..99f640a7c86 100644 --- a/internal/httpclient/api/openapi.yaml +++ b/internal/httpclient/api/openapi.yaml @@ -1926,6 +1926,9 @@ components: type: object acceptOAuth2ConsentRequest: properties: + context: + title: "JSONRawMessage represents a json.RawMessage that works well with\ + \ JSON, SQL, and Swagger." grant_access_token_audience: items: type: string @@ -3341,6 +3344,7 @@ components: session: access_token: "" id_token: "" + context: "" grant_access_token_audience: - grant_access_token_audience - grant_access_token_audience @@ -3352,6 +3356,9 @@ components: properties: consent_request: $ref: '#/components/schemas/oAuth2ConsentRequest' + context: + title: "JSONRawMessage represents a json.RawMessage that works well with\ + \ JSON, SQL, and Swagger." expires_at: $ref: '#/components/schemas/oAuth2ConsentSession_expires_at' grant_access_token_audience: diff --git a/internal/httpclient/docs/AcceptOAuth2ConsentRequest.md b/internal/httpclient/docs/AcceptOAuth2ConsentRequest.md index ec518b5d77d..c6284d0c66b 100644 --- a/internal/httpclient/docs/AcceptOAuth2ConsentRequest.md +++ b/internal/httpclient/docs/AcceptOAuth2ConsentRequest.md @@ -4,6 +4,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**Context** | Pointer to **interface{}** | | [optional] **GrantAccessTokenAudience** | Pointer to **[]string** | | [optional] **GrantScope** | Pointer to **[]string** | | [optional] **HandledAt** | Pointer to **time.Time** | | [optional] @@ -30,6 +31,41 @@ NewAcceptOAuth2ConsentRequestWithDefaults instantiates a new AcceptOAuth2Consent This constructor will only assign default values to properties that have it defined, but it doesn't guarantee that properties required by API are set +### GetContext + +`func (o *AcceptOAuth2ConsentRequest) GetContext() interface{}` + +GetContext returns the Context field if non-nil, zero value otherwise. + +### GetContextOk + +`func (o *AcceptOAuth2ConsentRequest) GetContextOk() (*interface{}, bool)` + +GetContextOk returns a tuple with the Context field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetContext + +`func (o *AcceptOAuth2ConsentRequest) SetContext(v interface{})` + +SetContext sets Context field to given value. + +### HasContext + +`func (o *AcceptOAuth2ConsentRequest) HasContext() bool` + +HasContext returns a boolean if a field has been set. + +### SetContextNil + +`func (o *AcceptOAuth2ConsentRequest) SetContextNil(b bool)` + + SetContextNil sets the value for Context to be an explicit nil + +### UnsetContext +`func (o *AcceptOAuth2ConsentRequest) UnsetContext()` + +UnsetContext ensures that no value is present for Context, not even an explicit nil ### GetGrantAccessTokenAudience `func (o *AcceptOAuth2ConsentRequest) GetGrantAccessTokenAudience() []string` diff --git a/internal/httpclient/docs/OAuth2ConsentSession.md b/internal/httpclient/docs/OAuth2ConsentSession.md index 732ecca2a3f..0399f2ab121 100644 --- a/internal/httpclient/docs/OAuth2ConsentSession.md +++ b/internal/httpclient/docs/OAuth2ConsentSession.md @@ -5,6 +5,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **ConsentRequest** | Pointer to [**OAuth2ConsentRequest**](OAuth2ConsentRequest.md) | | [optional] +**Context** | Pointer to **interface{}** | | [optional] **ExpiresAt** | Pointer to [**OAuth2ConsentSessionExpiresAt**](OAuth2ConsentSessionExpiresAt.md) | | [optional] **GrantAccessTokenAudience** | Pointer to **[]string** | | [optional] **GrantScope** | Pointer to **[]string** | | [optional] @@ -57,6 +58,41 @@ SetConsentRequest sets ConsentRequest field to given value. HasConsentRequest returns a boolean if a field has been set. +### GetContext + +`func (o *OAuth2ConsentSession) GetContext() interface{}` + +GetContext returns the Context field if non-nil, zero value otherwise. + +### GetContextOk + +`func (o *OAuth2ConsentSession) GetContextOk() (*interface{}, bool)` + +GetContextOk returns a tuple with the Context field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetContext + +`func (o *OAuth2ConsentSession) SetContext(v interface{})` + +SetContext sets Context field to given value. + +### HasContext + +`func (o *OAuth2ConsentSession) HasContext() bool` + +HasContext returns a boolean if a field has been set. + +### SetContextNil + +`func (o *OAuth2ConsentSession) SetContextNil(b bool)` + + SetContextNil sets the value for Context to be an explicit nil + +### UnsetContext +`func (o *OAuth2ConsentSession) UnsetContext()` + +UnsetContext ensures that no value is present for Context, not even an explicit nil ### GetExpiresAt `func (o *OAuth2ConsentSession) GetExpiresAt() OAuth2ConsentSessionExpiresAt` diff --git a/spec/api.json b/spec/api.json index d9e3a1e3bda..9909440957d 100644 --- a/spec/api.json +++ b/spec/api.json @@ -150,6 +150,9 @@ }, "acceptOAuth2ConsentRequest": { "properties": { + "context": { + "$ref": "#/components/schemas/JSONRawMessage" + }, "grant_access_token_audience": { "$ref": "#/components/schemas/StringSliceJSONFormat" }, @@ -918,6 +921,9 @@ "consent_request": { "$ref": "#/components/schemas/oAuth2ConsentRequest" }, + "context": { + "$ref": "#/components/schemas/JSONRawMessage" + }, "expires_at": { "properties": { "access_token": { diff --git a/spec/swagger.json b/spec/swagger.json index 529c2d12e32..9367e15859f 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -2173,6 +2173,9 @@ "type": "object", "title": "The request payload used to accept a consent request.", "properties": { + "context": { + "$ref": "#/definitions/JSONRawMessage" + }, "grant_access_token_audience": { "$ref": "#/definitions/StringSliceJSONFormat" }, @@ -2938,6 +2941,9 @@ "consent_request": { "$ref": "#/definitions/oAuth2ConsentRequest" }, + "context": { + "$ref": "#/definitions/JSONRawMessage" + }, "grant_access_token_audience": { "$ref": "#/definitions/StringSliceJSONFormat" },