Skip to content

Commit

Permalink
add consent label
Browse files Browse the repository at this point in the history
  • Loading branch information
hperl committed Oct 30, 2023
1 parent 39b7bb6 commit 5bff9ef
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 51 deletions.
3 changes: 3 additions & 0 deletions cmd/clidoc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,13 @@ func init() {
"NewInfoLoginTOTPLabel": text.NewInfoLoginTOTPLabel(),
"NewInfoLoginLookupLabel": text.NewInfoLoginLookupLabel(),
"NewInfoLogin": text.NewInfoLogin(),
"NewInfoLoginAndLink": text.NewInfoLoginAndLink(),
"NewInfoLoginLinkMessage": text.NewInfoLoginLinkMessage("{duplicteIdentifier}", "{provider}", "{newLoginUrl}"),
"NewInfoLoginTOTP": text.NewInfoLoginTOTP(),
"NewInfoLoginLookup": text.NewInfoLoginLookup(),
"NewInfoLoginVerify": text.NewInfoLoginVerify(),
"NewInfoLoginWith": text.NewInfoLoginWith("{provider}"),
"NewInfoLoginWithAndLink": text.NewInfoLoginWithAndLink("{provider}"),
"NewErrorValidationLoginFlowExpired": text.NewErrorValidationLoginFlowExpired(aSecondAgo),
"NewErrorValidationLoginNoStrategyFound": text.NewErrorValidationLoginNoStrategyFound(),
"NewErrorValidationRegistrationNoStrategyFound": text.NewErrorValidationRegistrationNoStrategyFound(),
Expand Down
57 changes: 57 additions & 0 deletions selfservice/flow/duplicate_credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package flow

import (
"encoding/json"

"github.com/tidwall/gjson"
"github.com/tidwall/sjson"

"github.com/ory/kratos/identity"
"github.com/ory/x/sqlxx"
)

const internalContextDuplicateCredentialsPath = "registration_duplicate_credentials"

type DuplicateCredentialsData struct {
CredentialsType identity.CredentialsType
CredentialsConfig sqlxx.JSONRawMessage
DuplicateIdentifier string
}

type InternalContexter interface {
EnsureInternalContext()
GetInternalContext() *sqlxx.JSONRawMessage
}

// SetDuplicateCredentials sets the duplicate credentials data in the flow's internal context.
func SetDuplicateCredentials(flow InternalContexter, creds DuplicateCredentialsData) error {
if flow.GetInternalContext() == nil {
flow.EnsureInternalContext()
}
bytes, err := sjson.SetBytes(
*flow.GetInternalContext(),
internalContextDuplicateCredentialsPath,
creds,
)
if err != nil {
return err
}
*flow.GetInternalContext() = bytes

return nil
}

// DuplicateCredentials returns the duplicate credentials data from the flow's internal context.
func DuplicateCredentials(flow InternalContexter) (*DuplicateCredentialsData, error) {
if flow.GetInternalContext() == nil {
flow.EnsureInternalContext()
}
raw := gjson.GetBytes(*flow.GetInternalContext(), internalContextDuplicateCredentialsPath)
if !raw.IsObject() {
return nil, nil
}
var creds DuplicateCredentialsData
err := json.Unmarshal([]byte(raw.Raw), &creds)

return &creds, err
}
10 changes: 0 additions & 10 deletions selfservice/flow/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,11 @@ import (

"github.com/ory/herodot"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/ui/container"
"github.com/ory/kratos/x"
"github.com/ory/x/sqlxx"
"github.com/ory/x/urlx"
)

const InternalContextDuplicateCredentialsPath = "registration_duplicate_credentials"

type RegistrationDuplicateCredentials struct {
CredentialsType identity.CredentialsType
CredentialsConfig sqlxx.JSONRawMessage
DuplicateIdentifier string
}

func AppendFlowTo(src *url.URL, id uuid.UUID) *url.URL {
return urlx.CopyWithQuery(src, url.Values{"flow": {id.String()}})
}
Expand Down
11 changes: 2 additions & 9 deletions selfservice/flow/login/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,8 @@ func (f *Flow) EnsureInternalContext() {
}
}

func (f *Flow) DuplicateCredentials() (*flow.RegistrationDuplicateCredentials, error) {
raw := gjson.GetBytes(f.InternalContext, flow.InternalContextDuplicateCredentialsPath)
if !raw.IsObject() {
return nil, nil
}
var creds flow.RegistrationDuplicateCredentials
err := json.Unmarshal([]byte(raw.Raw), &creds)

return &creds, err
func (f *Flow) GetInternalContext() *sqlxx.JSONRawMessage {
return &f.InternalContext
}

func (f Flow) MarshalJSON() ([]byte, error) {
Expand Down
34 changes: 34 additions & 0 deletions selfservice/flow/login/flow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/tidwall/gjson"

"github.com/ory/x/jsonx"
"github.com/ory/x/sqlxx"

"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/identity"
Expand All @@ -35,6 +36,7 @@ import (
)

func TestFakeFlow(t *testing.T) {
t.Parallel()
var r login.Flow
require.NoError(t, faker.FakeData(&r))

Expand All @@ -47,6 +49,7 @@ func TestFakeFlow(t *testing.T) {
}

func TestNewFlow(t *testing.T) {
t.Parallel()
ctx := context.Background()
conf, _ := internal.NewFastRegistryWithMocks(t)

Expand Down Expand Up @@ -130,6 +133,7 @@ func TestNewFlow(t *testing.T) {
}

func TestFlow(t *testing.T) {
t.Parallel()
r := &login.Flow{ID: x.NewUUID()}
assert.Equal(t, r.ID, r.GetID())

Expand All @@ -154,6 +158,7 @@ func TestFlow(t *testing.T) {
}

func TestGetType(t *testing.T) {
t.Parallel()
for _, ft := range []flow.Type{
flow.TypeAPI,
flow.TypeBrowser,
Expand All @@ -166,18 +171,21 @@ func TestGetType(t *testing.T) {
}

func TestGetRequestURL(t *testing.T) {
t.Parallel()
expectedURL := "http://foo/bar/baz"
f := &login.Flow{RequestURL: expectedURL}
assert.Equal(t, expectedURL, f.GetRequestURL())
}

func TestFlowEncodeJSON(t *testing.T) {
t.Parallel()
assert.EqualValues(t, "", gjson.Get(jsonx.TestMarshalJSONString(t, &login.Flow{RequestURL: "https://foo.bar?foo=bar"}), "return_to").String())
assert.EqualValues(t, "/bar", gjson.Get(jsonx.TestMarshalJSONString(t, &login.Flow{RequestURL: "https://foo.bar?return_to=/bar"}), "return_to").String())
assert.EqualValues(t, "/bar", gjson.Get(jsonx.TestMarshalJSONString(t, login.Flow{RequestURL: "https://foo.bar?return_to=/bar"}), "return_to").String())
}

func TestFlowDontOverrideReturnTo(t *testing.T) {
t.Parallel()
f := &login.Flow{ReturnTo: "/foo"}
f.SetReturnTo()
assert.Equal(t, "/foo", f.ReturnTo)
Expand All @@ -186,3 +194,29 @@ func TestFlowDontOverrideReturnTo(t *testing.T) {
f.SetReturnTo()
assert.Equal(t, "/bar", f.ReturnTo)
}

func TestDuplicateCredentials(t *testing.T) {
t.Parallel()
t.Run("case=returns previous data", func(t *testing.T) {
t.Parallel()
f := new(login.Flow)
dc := flow.DuplicateCredentialsData{
CredentialsType: "foo",
CredentialsConfig: sqlxx.JSONRawMessage(`{"bar":"baz"}`),
DuplicateIdentifier: "bar",
}

require.NoError(t, flow.SetDuplicateCredentials(f, dc))
actual, err := flow.DuplicateCredentials(f)
require.NoError(t, err)
assert.Equal(t, dc, *actual)
})

t.Run("case=returns nil data", func(t *testing.T) {
t.Parallel()
f := new(login.Flow)
actual, err := flow.DuplicateCredentials(f)
require.NoError(t, err)
assert.Nil(t, actual)
})
}
2 changes: 1 addition & 1 deletion selfservice/flow/login/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ func (e *HookExecutor) PreLoginHook(w http.ResponseWriter, r *http.Request, a *F

// maybeLinkCredentials links the identity with the credentials of the inner context of the login flow.
func (e *HookExecutor) maybeLinkCredentials(r *http.Request, s *session.Session, i *identity.Identity, f *Flow) error {
lc, err := f.DuplicateCredentials()
lc, err := flow.DuplicateCredentials(f)
if err != nil {
return err
} else if lc == nil {
Expand Down
4 changes: 4 additions & 0 deletions selfservice/flow/registration/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ func (f *Flow) EnsureInternalContext() {
}
}

func (f *Flow) GetInternalContext() *sqlxx.JSONRawMessage {
return &f.InternalContext
}

func (f Flow) MarshalJSON() ([]byte, error) {
type local Flow
f.SetReturnTo()
Expand Down
42 changes: 20 additions & 22 deletions selfservice/flow/registration/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

"github.com/julienschmidt/httprouter"
"github.com/pkg/errors"
"github.com/tidwall/sjson"
"go.opentelemetry.io/otel/attribute"

"github.com/ory/kratos/driver/config"
Expand Down Expand Up @@ -102,7 +101,7 @@ func NewHookExecutor(d executorDependencies) *HookExecutor {
return &HookExecutor{d: d}
}

func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Request, ct identity.CredentialsType, provider string, a *Flow, i *identity.Identity) (err error) {
func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Request, ct identity.CredentialsType, provider string, registrationFlow *Flow, i *identity.Identity) (err error) {
ctx := r.Context()
ctx, span := e.d.Tracer(ctx).Tracer().Start(ctx, "HookExecutor.PostRegistrationHook")
r = r.WithContext(ctx)
Expand All @@ -114,7 +113,7 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
WithField("flow_method", ct).
Debug("Running PostRegistrationPrePersistHooks.")
for k, executor := range e.d.PostRegistrationPrePersistHooks(r.Context(), ct) {
if err := executor.ExecutePostRegistrationPrePersistHook(w, r, a, i); err != nil {
if err := executor.ExecutePostRegistrationPrePersistHook(w, r, registrationFlow, i); err != nil {
if errors.Is(err, ErrHookAbortFlow) {
e.d.Logger().
WithRequest(r).
Expand All @@ -138,7 +137,7 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
Error("ExecutePostRegistrationPostPersistHook hook failed with an error.")

traits := i.Traits
return flow.HandleHookError(w, r, a, traits, ct.ToUiNodeGroup(), err, e.d, e.d)
return flow.HandleHookError(w, r, registrationFlow, traits, ct.ToUiNodeGroup(), err, e.d, e.d)
}

e.d.Logger().WithRequest(r).
Expand Down Expand Up @@ -169,14 +168,13 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
if err != nil {
return err
}
registrationDuplicateCredentials := flow.RegistrationDuplicateCredentials{
registrationDuplicateCredentials := flow.DuplicateCredentialsData{
CredentialsType: ct,
CredentialsConfig: i.Credentials[ct].Config,
DuplicateIdentifier: duplicateIdentifier,
}

a.InternalContext, err = sjson.SetBytes(a.InternalContext, flow.InternalContextDuplicateCredentialsPath,
registrationDuplicateCredentials)
err = flow.SetDuplicateCredentials(registrationFlow, registrationDuplicateCredentials)
if err != nil {
return err
}
Expand All @@ -188,8 +186,8 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
// Verify the redirect URL before we do any other processing.
c := e.d.Config()
returnTo, err := x.SecureRedirectTo(r, c.SelfServiceBrowserDefaultReturnTo(r.Context()),
x.SecureRedirectReturnTo(a.ReturnTo),
x.SecureRedirectUseSourceURL(a.RequestURL),
x.SecureRedirectReturnTo(registrationFlow.ReturnTo),
x.SecureRedirectUseSourceURL(registrationFlow.RequestURL),
x.SecureRedirectAllowURLs(c.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
x.SecureRedirectAllowSelfServiceURLs(c.SelfPublicURL(r.Context())),
x.SecureRedirectOverrideDefaultReturnTo(c.SelfServiceFlowRegistrationReturnTo(r.Context(), ct.String())),
Expand All @@ -208,7 +206,7 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
WithField("identity_id", i.ID).
Info("A new identity has registered using self-service registration.")

span.AddEvent(events.NewRegistrationSucceeded(r.Context(), i.ID, string(a.Type), a.Active.String(), provider))
span.AddEvent(events.NewRegistrationSucceeded(r.Context(), i.ID, string(registrationFlow.Type), registrationFlow.Active.String(), provider))

s := session.NewInactiveSession()

Expand All @@ -230,7 +228,7 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
WithField("flow_method", ct).
Debug("Running PostRegistrationPostPersistHooks.")
for k, executor := range e.d.PostRegistrationPostPersistHooks(r.Context(), ct) {
if err := executor.ExecutePostRegistrationPostPersistHook(w, r, a, s); err != nil {
if err := executor.ExecutePostRegistrationPostPersistHook(w, r, registrationFlow, s); err != nil {
if errors.Is(err, ErrHookAbortFlow) {
e.d.Logger().
WithRequest(r).
Expand Down Expand Up @@ -259,7 +257,7 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
span.SetAttributes(attribute.String("redirect_reason", "hook error"), attribute.String("executor", fmt.Sprintf("%T", executor)))

traits := i.Traits
return flow.HandleHookError(w, r, a, traits, ct.ToUiNodeGroup(), err, e.d, e.d)
return flow.HandleHookError(w, r, registrationFlow, traits, ct.ToUiNodeGroup(), err, e.d, e.d)
}

e.d.Logger().WithRequest(r).
Expand All @@ -277,35 +275,35 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
WithField("identity_id", i.ID).
Debug("Post registration execution hooks completed successfully.")

if a.Type == flow.TypeAPI || x.IsJSONRequest(r) {
if registrationFlow.Type == flow.TypeAPI || x.IsJSONRequest(r) {
span.SetAttributes(attribute.String("flow_type", string(flow.TypeAPI)))

if a.IDToken != "" {
if registrationFlow.IDToken != "" {
// We don't want to redirect with the code, if the flow was submitted with an ID token.
// This is the case for Sign in with native Apple SDK or Google SDK.
} else if handled, err := e.d.SessionManager().MaybeRedirectAPICodeFlow(w, r, a, s.ID, ct.ToUiNodeGroup()); err != nil {
} else if handled, err := e.d.SessionManager().MaybeRedirectAPICodeFlow(w, r, registrationFlow, s.ID, ct.ToUiNodeGroup()); err != nil {
return errors.WithStack(err)
} else if handled {
return nil
}

e.d.Writer().Write(w, r, &APIFlowResponse{
Identity: i,
ContinueWith: a.ContinueWith(),
ContinueWith: registrationFlow.ContinueWith(),
})
return nil
}

finalReturnTo := returnTo.String()
if a.OAuth2LoginChallenge != "" {
if a.ReturnToVerification != "" {
if registrationFlow.OAuth2LoginChallenge != "" {
if registrationFlow.ReturnToVerification != "" {
// Special case: If Kratos is used as a login UI *and* we want to show the verification UI,
// redirect to the verification URL first and then return to Hydra.
finalReturnTo = a.ReturnToVerification
finalReturnTo = registrationFlow.ReturnToVerification
} else {
callbackURL, err := e.d.Hydra().AcceptLoginRequest(r.Context(),
hydra.AcceptLoginRequestParams{
LoginChallenge: string(a.OAuth2LoginChallenge),
LoginChallenge: string(registrationFlow.OAuth2LoginChallenge),
IdentityID: i.ID.String(),
SessionID: s.ID.String(),
AuthenticationMethods: s.AMR,
Expand All @@ -316,8 +314,8 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
finalReturnTo = callbackURL
}
span.SetAttributes(attribute.String("redirect_reason", "oauth2 login challenge"))
} else if a.ReturnToVerification != "" {
finalReturnTo = a.ReturnToVerification
} else if registrationFlow.ReturnToVerification != "" {
finalReturnTo = registrationFlow.ReturnToVerification
span.SetAttributes(attribute.String("redirect_reason", "verification requested"))
}
span.SetAttributes(attribute.String("return_to", finalReturnTo))
Expand Down
Loading

0 comments on commit 5bff9ef

Please sign in to comment.