pomerium/internal/sessions/state.go
Bobby DeSimone dc12947241
all: refactor handler logic
- all: prefer `FormValues` to `ParseForm` with subsequent `Form.Get`s
- all: refactor authentication stack to be checked by middleware, and accessible via request context.
- all: replace http.ServeMux with gorilla/mux’s router
- all: replace custom CSRF checks with gorilla/csrf middleware
- authenticate: extract callback path as constant.
- internal/config: implement stringer interface for policy
- internal/cryptutil: add helper func `NewBase64Key`
- internal/cryptutil: rename `GenerateKey` to `NewKey`
- internal/cryptutil: rename `GenerateRandomString` to `NewRandomStringN`
- internal/middleware: removed alice in favor of gorilla/mux
- internal/sessions: remove unused `ValidateRedirectURI` and `ValidateClientSecret`
- internal/sessions: replace custom CSRF with gorilla/csrf fork that supports custom handler protection
- internal/urlutil: add `SignedRedirectURL` to create hmac'd URLs
- internal/urlutil: add `ValidateURL` helper to parse URL options
- internal/urlutil: add `GetAbsoluteURL` which takes a request and returns its absolute URL.
- proxy: remove holdover state verification checks; we no longer are setting sessions in any proxy routes so we don’t need them.
- proxy: replace un-named http.ServeMux with named domain routes.

Signed-off-by: Bobby DeSimone <bobbydesimone@gmail.com>
2019-09-16 18:01:14 -07:00

143 lines
3.7 KiB
Go

package sessions // import "github.com/pomerium/pomerium/internal/sessions"
import (
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/pomerium/pomerium/internal/cryptutil"
)
// State is our object that keeps track of a user's session state
type State struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
IDToken string `json:"id_token"`
RefreshDeadline time.Time `json:"refresh_deadline"`
Email string `json:"email"`
User string `json:"user"`
Groups []string `json:"groups"`
ImpersonateEmail string
ImpersonateGroups []string
}
// Valid returns an error if the users's session state is not valid.
func (s *State) Valid() error {
if s.Expired() {
return ErrExpired
}
return nil
}
// ForceRefresh sets the refresh deadline to now.
func (s *State) ForceRefresh() {
s.RefreshDeadline = time.Now().Truncate(time.Second)
}
// Expired returns true if the refresh period has expired
func (s *State) Expired() bool {
return s.RefreshDeadline.Before(time.Now())
}
// Impersonating returns if the request is impersonating.
func (s *State) Impersonating() bool {
return s.ImpersonateEmail != "" || len(s.ImpersonateGroups) != 0
}
// RequestEmail is the email to make the request as.
func (s *State) RequestEmail() string {
if s.ImpersonateEmail != "" {
return s.ImpersonateEmail
}
return s.Email
}
// RequestGroups returns the groups of the Groups making the request; uses
// impersonating user if set.
func (s *State) RequestGroups() string {
if len(s.ImpersonateGroups) != 0 {
return strings.Join(s.ImpersonateGroups, ",")
}
return strings.Join(s.Groups, ",")
}
type idToken struct {
Issuer string `json:"iss"`
Subject string `json:"sub"`
Expiry jsonTime `json:"exp"`
IssuedAt jsonTime `json:"iat"`
Nonce string `json:"nonce"`
AtHash string `json:"at_hash"`
}
// IssuedAt parses the IDToken's issue date and returns a valid go time.Time.
func (s *State) IssuedAt() (time.Time, error) {
payload, err := parseJWT(s.IDToken)
if err != nil {
return time.Time{}, fmt.Errorf("internal/sessions: malformed jwt: %v", err)
}
var token idToken
if err := json.Unmarshal(payload, &token); err != nil {
return time.Time{}, fmt.Errorf("internal/sessions: failed to unmarshal claims: %v", err)
}
return time.Time(token.IssuedAt), nil
}
// MarshalSession marshals the session state as JSON, encrypts the JSON using the
// given cipher, and base64-encodes the result
func MarshalSession(s *State, c cryptutil.Cipher) (string, error) {
v, err := c.Marshal(s)
if err != nil {
return "", err
}
return v, nil
}
// UnmarshalSession takes the marshaled string, base64-decodes into a byte slice, decrypts the
// byte slice using the passed cipher, and unmarshals the resulting JSON into a session state struct
func UnmarshalSession(value string, c cryptutil.Cipher) (*State, error) {
s := &State{}
err := c.Unmarshal(value, s)
if err != nil {
return nil, err
}
return s, nil
}
func parseJWT(p string) ([]byte, error) {
parts := strings.Split(p, ".")
if len(parts) < 2 {
return nil, fmt.Errorf("internal/sessions: malformed jwt, expected 3 parts got %d", len(parts))
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return nil, fmt.Errorf("internal/sessions: malformed jwt payload: %v", err)
}
return payload, nil
}
type jsonTime time.Time
func (j *jsonTime) UnmarshalJSON(b []byte) error {
var n json.Number
if err := json.Unmarshal(b, &n); err != nil {
return err
}
var unix int64
if t, err := n.Int64(); err == nil {
unix = t
} else {
f, err := n.Float64()
if err != nil {
return err
}
unix = int64(f)
}
*j = jsonTime(time.Unix(unix, 0))
return nil
}