From 64c77d7d5bf053b352568c36930e15c134386b37 Mon Sep 17 00:00:00 2001 From: Arne Luenser Date: Fri, 30 Jun 2023 19:03:46 +0200 Subject: [PATCH] feat: expand parameter for /sessions/whoami --- identity/identity.go | 3 ++ persistence/sql/persister_session.go | 3 +- session/expand.go | 43 ++++++++++++++------- session/handler.go | 56 +++++++++++++++++----------- session/manager.go | 4 +- session/manager_http.go | 11 ++++-- 6 files changed, 76 insertions(+), 44 deletions(-) diff --git a/identity/identity.go b/identity/identity.go index 59ce0daedab5..52695e53fbeb 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -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 diff --git a/persistence/sql/persister_session.go b/persistence/sql/persister_session.go index f8f6b13e8a9e..1a7b0c4d786d 100644 --- a/persistence/sql/persister_session.go +++ b/persistence/sql/persister_session.go @@ -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 { diff --git a/session/expand.go b/session/expand.go index 33b75476cc5b..0f2299e16566 100644 --- a/session/expand.go +++ b/session/expand.go @@ -4,8 +4,10 @@ package session import ( + "fmt" "strings" + "github.com/ory/kratos/identity" "github.com/ory/x/sqlxx" ) @@ -13,25 +15,32 @@ import ( 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 @@ -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, } diff --git a/session/handler.go b/session/handler.go index 36afabe768e7..ef67eefc030c 100644 --- a/session/handler.go +++ b/session/handler.go @@ -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 @@ -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. @@ -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()) { @@ -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))) } } @@ -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))) } } diff --git a/session/manager.go b/session/manager.go index d82cf002c5b4..489a698109fe 100644 --- a/session/manager.go +++ b/session/manager.go @@ -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 diff --git a/session/manager_http.go b/session/manager_http.go index bb21b0125c40..9ba91f85802b 100644 --- a/session/manager_http.go +++ b/session/manager_http.go @@ -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) { @@ -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())