Skip to content

Commit

Permalink
feat: expand parameter for /sessions/whoami
Browse files Browse the repository at this point in the history
  • Loading branch information
alnr committed Jun 30, 2023
1 parent 08fed36 commit 64c77d7
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 44 deletions.
3 changes: 3 additions & 0 deletions identity/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ func (i *Identity) ParseCredentials(t CredentialsType, config interface{}) (*Cre
}

func (i *Identity) CopyWithoutCredentials() *Identity {
if i == nil {
return nil
}
i.lock().RLock()
defer i.lock().RUnlock()
ii := *i
Expand Down
3 changes: 1 addition & 2 deletions persistence/sql/persister_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@ func (p *Persister) GetSession(ctx context.Context, sid uuid.UUID, expandables s
nid := p.NetworkID(ctx)

q := p.GetConnection(ctx).Q()
// if len(expandables) > 0 {
if expandables.Has(session.ExpandSessionDevices) {
q = q.Eager(expandables.ToEager()...)
q = q.Eager(session.ExpandSessionDevices.String())
}

if err := q.Where("id = ? AND nid = ?", sid, nid).First(&s); err != nil {
Expand Down
43 changes: 29 additions & 14 deletions session/expand.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,43 @@
package session

import (
"fmt"
"strings"

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

// Expandable controls what fields to expand for sessions.
type Expandable = sqlxx.Expandable

const (
// ExpandSessionDevices expands devices related to the session
ExpandSessionDevices Expandable = "Devices"
// ExpandSessionIdentity expands Identity related to the session
ExpandSessionIdentity Expandable = "Identity"
ExpandSessionIdentityRecoveryAddress Expandable = "Identity.RecoveryAddresses"
ExpandSessionIdentityVerifiableAddress Expandable = "Identity.VerifiableAddresses"
ExpandSessionDevices Expandable = "Devices"
ExpandSessionIdentity Expandable = "Identity"
)

var expandablesMap = map[string]Expandable{
"devices": ExpandSessionDevices,
"identity": ExpandSessionIdentity,
}

// Expandables is a list of Expandable values.
type Expandables = sqlxx.Expandables

func ParseExpandable(in string) (Expandable, bool) {
e, ok := expandablesMap[strings.ToLower(in)]
return e, ok
func ParseExpandables(in []string) (Expandables, error) {
es := ExpandNothing
for _, s := range in {
switch strings.ToLower(s) {
case "":
continue
case "devices":
es = append(es, ExpandSessionDevices)
case "identity":
es = append(es, ExpandSessionIdentity)
case "addresses":
es = append(es, ExpandSessionIdentity, identity.ExpandFieldRecoveryAddresses, identity.ExpandFieldVerifiableAddresses)
case "credentials":
es = append(es, ExpandSessionIdentity, identity.ExpandFieldCredentials)
default:
return nil, fmt.Errorf("unknown expand value: %q", s)
}
}
return es, nil
}

// ExpandNothing expands nothing
Expand All @@ -40,11 +49,17 @@ var ExpandNothing Expandables
// ExpandDefault expands the default fields of a session
// - Associated Identity
var ExpandDefault = Expandables{
ExpandSessionDevices,
ExpandSessionIdentity,
identity.ExpandFieldRecoveryAddresses,
identity.ExpandFieldVerifiableAddresses,
}

// ExpandEverything expands all the fields of a session.
var ExpandEverything = Expandables{
ExpandSessionDevices,
ExpandSessionIdentity,
identity.ExpandFieldRecoveryAddresses,
identity.ExpandFieldVerifiableAddresses,
identity.ExpandFieldCredentials,
}
56 changes: 34 additions & 22 deletions session/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ type toSession struct {
//
// in: header
Cookie string `json:"Cookie"`

// ExpandOptions is a query parameter encoded list of all properties that should be included in the session information response.
// Example - ?expand=identity&expand=devices
// Defaults to expand=identity.
// Querying for PII (personally identifiable information) always reaches to the identity's home region, which incurs additional latency.
// To check for session validity and nothing else, use `?expand=`.
//
// required: false
// enum: identity,devices
// in: query
ExpandOptions []string `json:"expand"`
}

// swagger:route GET /sessions/whoami frontend toSession
Expand Down Expand Up @@ -190,8 +201,17 @@ type toSession struct {
// 401: errorGeneric
// 403: errorGeneric
// default: errorGeneric
func (h *Handler) whoami(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
s, err := h.r.SessionManager().FetchFromRequest(r.Context(), r)
func (h *Handler) whoami(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
expandables := ExpandDefault
if es, ok := r.URL.Query()["expand"]; ok {
var err error
expandables, err = ParseExpandables(es)
if err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Could not parse expand option: %v", err)))
return
}
}
s, err := h.r.SessionManager().FetchFromRequest(r.Context(), r, expandables...)
c := h.r.Config()
if err != nil {
// We cache errors (and set cache header only when configured) where no session was found.
Expand Down Expand Up @@ -219,7 +239,7 @@ func (h *Handler) whoami(w http.ResponseWriter, r *http.Request, ps httprouter.P
s.Identity = s.Identity.CopyWithoutCredentials()

// Set userId as the X-Kratos-Authenticated-Identity-Id header.
w.Header().Set("X-Kratos-Authenticated-Identity-Id", s.Identity.ID.String())
w.Header().Set("X-Kratos-Authenticated-Identity-Id", s.IdentityID.String())

// Set Cache header only when configured
if c.SessionWhoAmICaching(r.Context()) {
Expand Down Expand Up @@ -357,15 +377,12 @@ func (h *Handler) adminListSessions(w http.ResponseWriter, r *http.Request, ps h
return
}

var expandables Expandables
expandables := ExpandNothing
if es, ok := r.URL.Query()["expand"]; ok {
for _, e := range es {
expand, ok := ParseExpandable(e)
if !ok {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Could not parse expand option: %s", e)))
return
}
expandables = append(expandables, expand)
var err error
expandables, err = ParseExpandables(es)
if err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Could not parse expand option: %v", err)))
}
}

Expand Down Expand Up @@ -435,17 +452,12 @@ func (h *Handler) getSession(w http.ResponseWriter, r *http.Request, ps httprout
return
}

var expandables Expandables

urlValues := r.URL.Query()
if es, ok := urlValues["expand"]; ok {
for _, e := range es {
expand, ok := ParseExpandable(e)
if !ok {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Could not parse expand option: %s", e)))
return
}
expandables = append(expandables, expand)
expandables := Expandables{ExpandSessionIdentity}
if es, ok := r.URL.Query()["expand"]; ok {
var err error
expandables, err = ParseExpandables(es)
if err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Could not parse expand option: %v", err)))
}
}

Expand Down
4 changes: 2 additions & 2 deletions session/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ type Manager interface {
// RefreshCookie checks if the request uses an outdated cookie and refreshes the cookie if needed.
RefreshCookie(context.Context, http.ResponseWriter, *http.Request, *Session) error

// FetchFromRequest creates an HTTP session using cookies.
FetchFromRequest(context.Context, *http.Request) (*Session, error)
// FetchFromRequest inspects the request cookies and retrieves the associated session from the database.
FetchFromRequest(context.Context, *http.Request, ...Expandable) (*Session, error)

// PurgeFromRequest removes an HTTP session.
PurgeFromRequest(context.Context, http.ResponseWriter, *http.Request) error
Expand Down
11 changes: 7 additions & 4 deletions session/manager_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ func (s *ManagerHTTP) extractToken(r *http.Request) string {
return token
}

func (s *ManagerHTTP) FetchFromRequest(ctx context.Context, r *http.Request) (_ *Session, err error) {
func (s *ManagerHTTP) FetchFromRequest(ctx context.Context, r *http.Request, sessionExpand ...Expandable) (_ *Session, err error) {
ctx, span := s.r.Tracer(ctx).Tracer().Start(ctx, "sessions.ManagerHTTP.FetchFromRequest")
defer func() {
if e := new(ErrNoActiveSessionFound); errors.As(err, &e) {
Expand All @@ -236,15 +236,18 @@ func (s *ManagerHTTP) FetchFromRequest(ctx context.Context, r *http.Request) (_
return nil, errors.WithStack(NewErrNoCredentialsForSession())
}

expand := identity.ExpandDefault
identityExpand := identity.ExpandDefault
if len(sessionExpand) > 0 {
identityExpand = sessionExpand
}
if s.r.Config().SessionWhoAmIAAL(r.Context()) == config.HighestAvailableAAL {
// When the session endpoint requires the highest AAL, we fetch all credentials immediately to save a
// query later in "DoesSessionSatisfy". This is a SQL optimization, because the identity manager fetches
// the data in parallel, which is a bit faster than fetching it in sequence.
expand = identity.ExpandEverything
identityExpand = append(identityExpand, identity.ExpandFieldCredentials)
}

se, err := s.r.SessionPersister().GetSessionByToken(ctx, token, ExpandEverything, expand)
se, err := s.r.SessionPersister().GetSessionByToken(ctx, token, sessionExpand, identityExpand)
if err != nil {
if errors.Is(err, herodot.ErrNotFound) || errors.Is(err, sqlcon.ErrNoRows) {
return nil, errors.WithStack(NewErrNoActiveSessionFound())
Expand Down

0 comments on commit 64c77d7

Please sign in to comment.