Skip to content

Commit

Permalink
refactor: adopt new request parser
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr committed May 15, 2020
1 parent ad6c402 commit ad16cc9
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 107 deletions.
1 change: 1 addition & 0 deletions selfservice/strategy/password/hasher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func TestHasher(t *testing.T) {
require.NoError(t, err)
assert.NotEqual(t, pw, hs)

t.Logf("hash: %s", hs)
require.NoError(t, h.Compare(pw, hs))

mod := make([]byte, len(pw))
Expand Down
127 changes: 43 additions & 84 deletions selfservice/strategy/password/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/url"
"time"

"github.com/gofrs/uuid"
"github.com/julienschmidt/httprouter"
"github.com/pkg/errors"

Expand All @@ -14,7 +15,6 @@ import (
"github.com/ory/herodot"
"github.com/ory/x/urlx"

"github.com/ory/kratos/continuity"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/schema"
"github.com/ory/kratos/selfservice/flow/settings"
Expand All @@ -25,11 +25,13 @@ import (

const (
SettingsPath = "/self-service/browser/flows/settings/strategies/password"

continuityPrefix = "ory_kratos_settings_password"
)

func continuityKeySettings(rid string) string {
// Use one individual container per request ID to prevent resuming other request IDs.
return "settings_password." + rid
return continuityPrefix + "." + rid
}

func (s *Strategy) RegisterSettingsRoutes(router *x.RouterPublic) {
Expand All @@ -54,6 +56,16 @@ type completeSelfServiceBrowserSettingsPasswordFlowPayload struct {
//
// in: query
RequestID string `json:"request_id"`

rid uuid.UUID
}

func (p *completeSelfServiceBrowserSettingsPasswordFlowPayload) GetRequestID() uuid.UUID {
return x.ParseUUID(p.RequestID)
}

func (p *completeSelfServiceBrowserSettingsPasswordFlowPayload) SetRequestID(rid uuid.UUID) {
p.RequestID = rid.String()
}

// swagger:route POST /self-service/browser/flows/settings/strategies/password public completeSelfServiceBrowserSettingsPasswordStrategyFlow
Expand All @@ -77,96 +89,50 @@ type completeSelfServiceBrowserSettingsPasswordFlowPayload struct {
// 302: emptyResponse
// 500: genericError
func (s *Strategy) submitSettingsFlow(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
ss, err := s.d.SessionManager().FetchFromRequest(r.Context(), r)
if err != nil {
s.handleSettingsError(w, r, nil, ss, nil, err)
return
}

rid := r.URL.Query().Get("request")
if len(rid) == 0 {
s.handleSettingsError(w, r, nil, ss, nil, errors.WithStack(herodot.ErrBadRequest.WithReasonf("The request query parameter is missing.")))
return
}

var p completeSelfServiceBrowserSettingsPasswordFlowPayload
if _, err := s.d.ContinuityManager().Continue(r.Context(), r,
continuityKeySettings(r.URL.Query().Get("request")),
continuity.WithIdentity(ss.Identity),
continuity.WithPayload(&p)); err == nil {
if p.RequestID == r.URL.Query().Get("request") {
s.completeSettingsFlow(w, r, ss, &p)
return
}
ctxUpdate, err := settings.PrepareUpdate(s.d, r, continuityPrefix, &p)
if errors.Is(err, settings.ErrContinuePreviousAction) {
s.continueSettingsFlow(w, r, ctxUpdate, &p)
return
} else if !errors.Is(err, &continuity.ErrNotResumable) {
s.handleSettingsError(w, r, nil, ss, &p, err)
} else if err != nil {
s.handleSettingsError(w, r, ctxUpdate, &p, err)
return
}

if err := r.ParseForm(); err != nil {
s.handleSettingsError(w, r, nil, ss, &p, errors.WithStack(err))
return
}

p = completeSelfServiceBrowserSettingsPasswordFlowPayload{
RequestID: rid,
Password: r.PostFormValue("password"),
}
if err := s.d.ContinuityManager().Pause(
r.Context(), w, r,
continuityKeySettings(r.URL.Query().Get("request")),
continuity.WithPayload(&p),
continuity.WithIdentity(ss.Identity),
continuity.WithLifespan(time.Minute*15),
); err != nil {
s.handleSettingsError(w, r, nil, ss, &p, errors.WithStack(err))
return
}

s.completeSettingsFlow(w, r, ss, &p)
p.RequestID = ctxUpdate.Request.ID.String()
p.Password = r.PostFormValue("password")
s.continueSettingsFlow(w, r, ctxUpdate, &p)
}

func (s *Strategy) completeSettingsFlow(
func (s *Strategy) continueSettingsFlow(
w http.ResponseWriter, r *http.Request,
ss *session.Session, p *completeSelfServiceBrowserSettingsPasswordFlowPayload,
ctxUpdate *settings.UpdateContext, p *completeSelfServiceBrowserSettingsPasswordFlowPayload,
) {
ar, err := s.d.SettingsRequestPersister().GetSettingsRequest(r.Context(), x.ParseUUID(p.RequestID))
if err != nil {
s.handleSettingsError(w, r, nil, ss, p, err)
return
}

if err := ar.Valid(ss); err != nil {
s.handleSettingsError(w, r, ar, ss, p, err)
return
}

if ss.AuthenticatedAt.Add(s.c.SelfServicePrivilegedSessionMaxAge()).Before(time.Now()) {
s.handleSettingsError(w, r, ar, ss, p, errors.WithStack(settings.ErrRequestNeedsReAuthentication))
if ctxUpdate.Session.AuthenticatedAt.Add(s.c.SelfServicePrivilegedSessionMaxAge()).Before(time.Now()) {
s.handleSettingsError(w, r, ctxUpdate, p, errors.WithStack(settings.ErrRequestNeedsReAuthentication))
return
}

if len(p.Password) == 0 {
s.handleSettingsError(w, r, ar, ss, p, schema.NewRequiredError("#/", "password"))
s.handleSettingsError(w, r, ctxUpdate, p, schema.NewRequiredError("#/", "password"))
return
}

hpw, err := s.d.PasswordHasher().Generate([]byte(p.Password))
if err != nil {
s.handleSettingsError(w, r, ar, ss, p, err)
s.handleSettingsError(w, r, ctxUpdate, p, err)
return
}

co, err := json.Marshal(&CredentialsConfig{HashedPassword: string(hpw)})
if err != nil {
s.handleSettingsError(w, r, ar, ss, p, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to encode password options to JSON: %s", err)))
s.handleSettingsError(w, r, ctxUpdate, p, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to encode password options to JSON: %s", err)))
return
}

i, err := s.d.PrivilegedIdentityPool().GetIdentityConfidential(r.Context(), ss.Identity.ID)
i, err := s.d.PrivilegedIdentityPool().GetIdentityConfidential(r.Context(), ctxUpdate.Session.Identity.ID)
if err != nil {
s.handleSettingsError(w, r, ar, ss, p, err)
s.handleSettingsError(w, r, ctxUpdate, p, err)
return
}

Expand All @@ -180,21 +146,19 @@ func (s *Strategy) completeSettingsFlow(
c.Config = co
i.SetCredentials(s.ID(), *c)
if err := s.validateCredentials(i, p.Password); err != nil {
s.handleSettingsError(w, r, ar, ss, p, err)
s.handleSettingsError(w, r, ctxUpdate, p, err)
return
}

if err := s.d.SettingsHookExecutor().PostSettingsHook(w, r,
s.SettingsStrategyID(),
ar, ss, i,
); err != nil {
s.handleSettingsError(w, r, ar, ss, p, err)
s.SettingsStrategyID(), ctxUpdate, i); err != nil {
s.handleSettingsError(w, r, ctxUpdate, p, err)
return
}

if len(w.Header().Get("Location")) == 0 {
http.Redirect(w, r,
urlx.CopyWithQuery(s.c.SettingsURL(), url.Values{"request": {ar.ID.String()}}).String(),
urlx.CopyWithQuery(s.c.SettingsURL(), url.Values{"request": {ctxUpdate.Request.ID.String()}}).String(),
http.StatusFound,
)
}
Expand All @@ -217,24 +181,19 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, ss *session.Session,
return nil
}

func (s *Strategy) handleSettingsError(w http.ResponseWriter, r *http.Request, rr *settings.Request, ss *session.Session, p *completeSelfServiceBrowserSettingsPasswordFlowPayload, err error) {
func (s *Strategy) handleSettingsError(w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, p *completeSelfServiceBrowserSettingsPasswordFlowPayload, err error) {
if errors.Is(err, settings.ErrRequestNeedsReAuthentication) {
if err := s.d.ContinuityManager().Pause(
r.Context(), w, r,
continuityKeySettings(r.URL.Query().Get("request")),
continuity.WithPayload(p),
continuity.WithIdentity(ss.Identity),
continuity.WithLifespan(time.Minute*15),
); err != nil {
s.d.SettingsRequestErrorHandler().HandleSettingsError(w, r, rr, err, s.SettingsStrategyID())
if err := s.d.ContinuityManager().Pause(r.Context(), w, r,
continuityKeySettings(r.URL.Query().Get("request")), settings.ContinuityOptions(p, ctxUpdate.Session.Identity)...); err != nil {
s.d.SettingsRequestErrorHandler().HandleSettingsError(w, r, ctxUpdate.Request, err, s.SettingsStrategyID())
return
}
}

if rr != nil {
rr.Methods[s.SettingsStrategyID()].Config.Reset()
rr.Methods[s.SettingsStrategyID()].Config.SetCSRF(s.d.GenerateCSRFToken(r))
if ctxUpdate.Request != nil {
ctxUpdate.Request.Methods[s.SettingsStrategyID()].Config.Reset()
ctxUpdate.Request.Methods[s.SettingsStrategyID()].Config.SetCSRF(s.d.GenerateCSRFToken(r))
}

s.d.SettingsRequestErrorHandler().HandleSettingsError(w, r, rr, err, s.SettingsStrategyID())
s.d.SettingsRequestErrorHandler().HandleSettingsError(w, r, ctxUpdate.Request, err, s.SettingsStrategyID())
}
4 changes: 2 additions & 2 deletions selfservice/strategy/password/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func init() {
internal.RegisterFakes()
}

func TestProfile(t *testing.T) {
func TestSettings(t *testing.T) {
_, reg := internal.NewFastRegistryWithMocks(t)
viper.Set(configuration.ViperKeyURLsDefaultReturnTo, "https://www.ory.sh/")
viper.Set(configuration.ViperKeyDefaultIdentityTraitsSchemaURL, "file://./stub/profile.schema.json")
Expand All @@ -50,7 +50,7 @@ func TestProfile(t *testing.T) {
Traits: identity.Traits(`{}`),
TraitsSchemaID: configuration.DefaultIdentityTraitsSchemaID,
}
publicTS, adminTS := testhelpers.NewSettingsAPIServer(t, reg, []identity.Identity{*primaryIdentity, *secondaryIdentity})
publicTS, adminTS := testhelpers.NewSettingsAPIServer(t, reg, []*identity.Identity{primaryIdentity, secondaryIdentity})
primaryUser := testhelpers.NewSessionClient(t, publicTS.URL+"/sessions/set/0")
secondaryUser := testhelpers.NewSessionClient(t, publicTS.URL+"/sessions/set/1")
adminClient := testhelpers.NewSDKClient(adminTS)
Expand Down
22 changes: 22 additions & 0 deletions selfservice/strategy/password/strategy.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package password

import (
"encoding/json"
"strings"

"github.com/pkg/errors"
"gopkg.in/go-playground/validator.v9"

"github.com/ory/kratos/continuity"
Expand All @@ -16,6 +20,7 @@ import (

var _ login.Strategy = new(Strategy)
var _ registration.Strategy = new(Strategy)
var _ identity.ActiveCredentialsCounter = new(Strategy)

type registrationStrategyDependencies interface {
x.LoggingProvider
Expand Down Expand Up @@ -58,6 +63,23 @@ type Strategy struct {
v *validator.Validate
}

func (s *Strategy) CountActiveCredentials(cc map[identity.CredentialsType]identity.Credentials) (count int, err error) {
for _, c := range cc {
if c.Type == s.ID() && len(c.Config) > 0 {
var conf CredentialsConfig
if err = json.Unmarshal(c.Config, &conf); err != nil {
return 0, errors.WithStack(err)
}

if len(c.Identifiers) > 0 && len(c.Identifiers[0]) > 0 &&
strings.HasPrefix(conf.HashedPassword, "$argon2id$") {
count++
}
}
}
return
}

func NewStrategy(
d registrationStrategyDependencies,
c configuration.Provider,
Expand Down

0 comments on commit ad16cc9

Please sign in to comment.