Skip to content

Commit

Permalink
fix: ensure refresh issues a new session when the identity changes
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr committed Oct 19, 2021
1 parent a61ed1e commit a10b385
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 1 deletion.
8 changes: 8 additions & 0 deletions selfservice/flow/login/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"net/http"
"time"

"github.com/gofrs/uuid"

"github.com/ory/herodot"
"github.com/ory/kratos/text"
"github.com/ory/x/stringsx"
Expand Down Expand Up @@ -571,6 +573,12 @@ continueLogin:
return
}

// What can happen is that we re-authenticate as another user. In this case, we need to use a completely fresh
// session!
if sess.IdentityID != uuid.Nil && sess.IdentityID != interim.ID {
sess = session.NewInactiveSession()
}

sess.CompletedLoginFor(ss.ID())
i = interim
break
Expand Down
91 changes: 90 additions & 1 deletion selfservice/flow/login/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"testing"
"time"

"github.com/ory/x/sqlxx"

"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/ui/container"

Expand Down Expand Up @@ -46,7 +48,7 @@ func TestFlowLifecycle(t *testing.T) {

errorTS := testhelpers.NewErrorTestServer(t, reg)
conf.MustSet(config.ViperKeySelfServiceBrowserDefaultReturnTo, "https://www.ory.sh")
conf.MustSet(config.ViperKeyDefaultIdentitySchemaURL, "file://./stub/login.schema.json")
conf.MustSet(config.ViperKeyDefaultIdentitySchemaURL, "file://./stub/password.schema.json")

assertion := func(body []byte, isForced, isApi bool) {
r := gjson.GetBytes(body, "forced")
Expand Down Expand Up @@ -101,6 +103,31 @@ func TestFlowLifecycle(t *testing.T) {
return initFlowWithAccept(t, query, false, "application/json")
}

id1mail, id2mail := x.NewUUID().String(), x.NewUUID().String()
identity1 := &identity.Identity{
Credentials: map[identity.CredentialsType]identity.Credentials{
identity.CredentialsTypePassword: {
Type: identity.CredentialsTypePassword,
Identifiers: []string{id1mail},
Config: sqlxx.JSONRawMessage(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), // foobar
}},
State: identity.StateActive,
Traits: identity.Traits(`{"username":"` + id1mail + `"}`),
}
identity2 := &identity.Identity{
Credentials: map[identity.CredentialsType]identity.Credentials{
identity.CredentialsTypePassword: {
Type: identity.CredentialsTypePassword,
Identifiers: []string{id2mail},
Config: sqlxx.JSONRawMessage(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), // foobar
}},
State: identity.StateActive,
Traits: identity.Traits(`{"username":"` + id2mail + `"}`),
}

require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), identity1))
require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), identity2))

t.Run("lifecycle=submit", func(t *testing.T) {
t.Run("interaction=unauthenticated", func(t *testing.T) {
run := func(t *testing.T, tt flow.Type, aal string, values url.Values) (string, *http.Response) {
Expand Down Expand Up @@ -142,6 +169,68 @@ func TestFlowLifecycle(t *testing.T) {
assertx.EqualAsJSON(t, text.NewErrorValidationLoginNoStrategyFound().Text, gjson.Get(body, "ui.messages.0.text").String(), body)
})
})

t.Run("case=reset the session when refresh is true but identity is different", func(t *testing.T) {
testhelpers.NewRedirSessionEchoTS(t, reg)
t.Cleanup(func() {
conf.MustSet(config.ViperKeySelfServiceBrowserDefaultReturnTo, "https://www.ory.sh")
})

run := func(t *testing.T, tt flow.Type) (string, string) {
f := login.Flow{Type: tt, ExpiresAt: time.Now().Add(time.Minute), IssuedAt: time.Now(), UI: container.New(""), Refresh: false, RequestedAAL: "aal1"}
require.NoError(t, reg.LoginFlowPersister().CreateLoginFlow(context.Background(), &f))

hc := testhelpers.NewClientWithCookies(t)
res, err := hc.PostForm(ts.URL+login.RouteSubmitFlow+"?flow="+f.ID.String(), url.Values{"method": {"password"}, "password_identifier": {id1mail}, "password": {"foobar"}, "csrf_token": {x.FakeCSRFToken}})
require.NoError(t, err)
firstSession := x.MustReadAll(res.Body)
require.NoError(t, res.Body.Close())

f = login.Flow{Type: tt, ExpiresAt: time.Now().Add(time.Minute), IssuedAt: time.Now(), UI: container.New(""), Refresh: true, RequestedAAL: "aal1"}
require.NoError(t, reg.LoginFlowPersister().CreateLoginFlow(context.Background(), &f))

vv := testhelpers.EncodeFormAsJSON(t, tt == flow.TypeAPI, url.Values{"method": {"password"}, "password_identifier": {id2mail}, "password": {"foobar"}, "csrf_token": {x.FakeCSRFToken}})

req, err := http.NewRequest("POST", ts.URL+login.RouteSubmitFlow+"?flow="+f.ID.String(), strings.NewReader(vv))
require.NoError(t, err)

if tt == flow.TypeAPI {
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+gjson.GetBytes(firstSession, "session_token").String())
} else {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}

res, err = hc.Do(req)
require.NoError(t, err)
secondSession := x.MustReadAll(res.Body)
require.NoError(t, res.Body.Close())
return string(firstSession), string(secondSession)
}

t.Run("type=browser", func(t *testing.T) {
a, b := run(t, flow.TypeBrowser)

assert.Equal(t, id1mail, gjson.Get(a, "identity.traits.username").String())
assert.Equal(t, id2mail, gjson.Get(b, "identity.traits.username").String())

assert.NotEmpty(t, gjson.Get(b, "id").String())
assert.NotEqual(t, gjson.Get(b, "id").String(), gjson.Get(a, "id").String())
})

t.Run("type=api", func(t *testing.T) {
a, b := run(t, flow.TypeAPI)

assert.Equal(t, id1mail, gjson.Get(a, "session.identity.traits.username").String())
assert.Equal(t, id2mail, gjson.Get(b, "session.identity.traits.username").String())

assert.NotEmpty(t, gjson.Get(a, "session_token").String())
assert.NotEqual(t, gjson.Get(a, "session_token").String(), gjson.Get(b, "session_token").String())

assert.NotEmpty(t, gjson.Get(b, "session.id").String())
assert.NotEqual(t, gjson.Get(b, "session.id").String(), gjson.Get(a, "id").String())
})
})
})

t.Run("case=ensure aal is checked for upgradeability on session", func(t *testing.T) {
Expand Down
26 changes: 26 additions & 0 deletions selfservice/flow/login/stub/password.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"$id": "https://example.com/person.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"username": {
"type": "string",
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
}
}
}
}
},
"required": [
"username"
]
}
}
}

0 comments on commit a10b385

Please sign in to comment.