mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-29 18:36:30 +02:00
internal/sessions: fix cookie clear session (#376)
CookieStore's ClearSession now properly clears the user session cookie by setting MaxAge to -1. internal/sessions: move encoder interface to encoding package, and rename to MarshalUnmarshaler. internal/encoding: move mock to own package authenticate: use INFO log level for authZ error. Signed-off-by: Bobby DeSimone <bobbydesimone@gmail.com>
This commit is contained in:
parent
d3d60d1055
commit
b9ab49c32c
19 changed files with 173 additions and 217 deletions
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/pomerium/pomerium/internal/config"
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/ecjson"
|
||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||
"github.com/pomerium/pomerium/internal/identity"
|
||||
|
@ -59,7 +60,7 @@ type Authenticate struct {
|
|||
sharedCipher cipher.AEAD
|
||||
// sharedEncoder is the encoder to use to serialize data to be consumed
|
||||
// by other services
|
||||
sharedEncoder sessions.Encoder
|
||||
sharedEncoder encoding.MarshalUnmarshaler
|
||||
|
||||
// data related to this service only
|
||||
cookieOptions *sessions.CookieOptions
|
||||
|
@ -68,7 +69,7 @@ type Authenticate struct {
|
|||
// is the cipher to use to encrypt data for this service
|
||||
cookieCipher cipher.AEAD
|
||||
sessionStore sessions.SessionStore
|
||||
encryptedEncoder sessions.Encoder
|
||||
encryptedEncoder encoding.MarshalUnmarshaler
|
||||
sessionStores []sessions.SessionStore
|
||||
sessionLoaders []sessions.SessionLoader
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ func (a *Authenticate) VerifySession(next http.Handler) http.Handler {
|
|||
state, err := sessions.FromContext(r.Context())
|
||||
if errors.Is(err, sessions.ErrExpired) {
|
||||
if err := a.refresh(w, r, state); err != nil {
|
||||
log.FromRequest(r).Debug().Str("cause", err.Error()).Msg("authenticate: couldn't refresh session")
|
||||
log.FromRequest(r).Info().Err(err).Msg("authenticate: verify session, refresh")
|
||||
a.redirectToIdentityProvider(w, r)
|
||||
return
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ func (a *Authenticate) VerifySession(next http.Handler) http.Handler {
|
|||
http.Redirect(w, r, urlutil.GetAbsoluteURL(r).String(), http.StatusFound)
|
||||
return
|
||||
} else if err != nil {
|
||||
log.FromRequest(r).Err(err).Msg("authenticate: malformed session")
|
||||
log.FromRequest(r).Info().Err(err).Msg("authenticate: verify session")
|
||||
a.redirectToIdentityProvider(w, r)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/mock"
|
||||
"github.com/pomerium/pomerium/internal/identity"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/templates"
|
||||
|
@ -85,17 +86,17 @@ func TestAuthenticate_SignIn(t *testing.T) {
|
|||
|
||||
session sessions.SessionStore
|
||||
provider identity.MockProvider
|
||||
encoder sessions.Encoder
|
||||
encoder encoding.MarshalUnmarshaler
|
||||
wantCode int
|
||||
}{
|
||||
{"good", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{}, http.StatusFound},
|
||||
{"session not valid", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(-10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{}, http.StatusFound},
|
||||
{"bad redirect uri query", "", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{}, http.StatusBadRequest},
|
||||
{"bad marshal", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{MarshalError: errors.New("error")}, http.StatusBadRequest},
|
||||
{"session error", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{LoadError: errors.New("error")}, identity.MockProvider{}, &encoding.MockEncoder{}, http.StatusBadRequest},
|
||||
{"good with different programmatic redirect", "https", "corp.example.example", map[string]string{"state": "example", "pomerium_programmatic_destination_url": "https://some.example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{}, http.StatusFound},
|
||||
{"encrypted encoder error", "https", "corp.example.example", map[string]string{"state": "example", "pomerium_programmatic_destination_url": "https://some.example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{MarshalError: errors.New("error")}, http.StatusBadRequest},
|
||||
{"good with different programmatic redirect", "https", "corp.example.example", map[string]string{"state": "example", "pomerium_programmatic_destination_url": "some.example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{}, http.StatusBadRequest},
|
||||
{"good", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"session not valid", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(-10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"bad redirect uri query", "", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusBadRequest},
|
||||
{"bad marshal", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{MarshalError: errors.New("error")}, http.StatusBadRequest},
|
||||
{"session error", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{LoadError: errors.New("error")}, identity.MockProvider{}, &mock.Encoder{}, http.StatusBadRequest},
|
||||
{"good with different programmatic redirect", "https", "corp.example.example", map[string]string{"state": "example", "pomerium_programmatic_destination_url": "https://some.example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"encrypted encoder error", "https", "corp.example.example", map[string]string{"state": "example", "pomerium_programmatic_destination_url": "https://some.example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{MarshalError: errors.New("error")}, http.StatusBadRequest},
|
||||
{"good with different programmatic redirect", "https", "corp.example.example", map[string]string{"state": "example", "pomerium_programmatic_destination_url": "some.example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusBadRequest},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -349,16 +350,16 @@ func TestAuthenticate_RefreshAPI(t *testing.T) {
|
|||
ctxError error
|
||||
|
||||
provider identity.Authenticator
|
||||
secretEncoder sessions.Encoder
|
||||
sharedEncoder sessions.Encoder
|
||||
secretEncoder encoding.MarshalUnmarshaler
|
||||
sharedEncoder encoding.MarshalUnmarshaler
|
||||
|
||||
wantStatus int
|
||||
}{
|
||||
{"good", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, http.StatusOK},
|
||||
{"refresh error", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshError: errors.New("error")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, http.StatusInternalServerError},
|
||||
{"session is not refreshable error", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, errors.New("session error"), identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, http.StatusBadRequest},
|
||||
{"secret encoder failed", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, encoding.MockEncoder{MarshalError: errors.New("error")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, http.StatusInternalServerError},
|
||||
{"shared encoder failed", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, encoding.MockEncoder{MarshalError: errors.New("error")}, http.StatusInternalServerError},
|
||||
{"good", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, mock.Encoder{MarshalResponse: []byte("ok")}, mock.Encoder{MarshalResponse: []byte("ok")}, http.StatusOK},
|
||||
{"refresh error", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshError: errors.New("error")}, mock.Encoder{MarshalResponse: []byte("ok")}, mock.Encoder{MarshalResponse: []byte("ok")}, http.StatusInternalServerError},
|
||||
{"session is not refreshable error", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, errors.New("session error"), identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, mock.Encoder{MarshalResponse: []byte("ok")}, mock.Encoder{MarshalResponse: []byte("ok")}, http.StatusBadRequest},
|
||||
{"secret encoder failed", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, mock.Encoder{MarshalError: errors.New("error")}, mock.Encoder{MarshalResponse: []byte("ok")}, http.StatusInternalServerError},
|
||||
{"shared encoder failed", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, mock.Encoder{MarshalResponse: []byte("ok")}, mock.Encoder{MarshalError: errors.New("error")}, http.StatusInternalServerError},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -25,6 +25,10 @@
|
|||
- Force refresh has been removed from the dashboard.
|
||||
- Previous programmatic authentication endpoints (`/api/v1/token`) has been removed and is no longer supported.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed an issue where cookie sessions would not clear on error.[GH-376]
|
||||
|
||||
## v0.4.2
|
||||
|
||||
### Security
|
||||
|
@ -338,3 +342,4 @@
|
|||
[gh-332]: https://github.com/pomerium/pomerium/pull/332/
|
||||
[gh-338]: https://github.com/pomerium/pomerium/issues/338
|
||||
[gh-363]: https://github.com/pomerium/pomerium/issues/363
|
||||
[gh-376]: https://github.com/pomerium/pomerium/pull/376/
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"io"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
)
|
||||
|
||||
// EncryptedCompressedJSON implements SecureEncoder for JSON using an AEAD cipher.
|
||||
|
@ -21,7 +22,7 @@ type EncryptedCompressedJSON struct {
|
|||
}
|
||||
|
||||
// New takes a base64 encoded secret key and returns a new XChacha20poly1305 cipher.
|
||||
func New(aead cipher.AEAD) *EncryptedCompressedJSON {
|
||||
func New(aead cipher.AEAD) encoding.MarshalUnmarshaler {
|
||||
return &EncryptedCompressedJSON{aead: aead}
|
||||
}
|
||||
|
||||
|
|
17
internal/encoding/econding.go
Normal file
17
internal/encoding/econding.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package encoding // import "github.com/pomerium/pomerium/internal/encoding"
|
||||
|
||||
// MarshalUnmarshaler can both Marshal and Unmarshal a struct into and from a set of bytes.
|
||||
type MarshalUnmarshaler interface {
|
||||
Marshaler
|
||||
Unmarshaler
|
||||
}
|
||||
|
||||
// Marshaler encodes a struct into a set of bytes.
|
||||
type Marshaler interface {
|
||||
Marshal(interface{}) ([]byte, error)
|
||||
}
|
||||
|
||||
// Unmarshaler decodes a set of bytes and returns a struct.
|
||||
type Unmarshaler interface {
|
||||
Unmarshal([]byte, interface{}) error
|
||||
}
|
|
@ -5,10 +5,11 @@ package jws // import "github.com/pomerium/pomerium/internal/encoding/jws"
|
|||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
)
|
||||
|
||||
// JSONWebSigner is the struct representing a signed JWT.
|
||||
|
@ -21,7 +22,7 @@ type JSONWebSigner struct {
|
|||
}
|
||||
|
||||
// NewHS256Signer creates a SHA256 JWT signer from a 32 byte key.
|
||||
func NewHS256Signer(key []byte, issuer string) (*JSONWebSigner, error) {
|
||||
func NewHS256Signer(key []byte, issuer string) (encoding.MarshalUnmarshaler, error) {
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: key},
|
||||
(&jose.SignerOptions{}).WithType("JWT"))
|
||||
if err != nil {
|
||||
|
|
18
internal/encoding/mock/mock_encoder.go
Normal file
18
internal/encoding/mock/mock_encoder.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package mock // import "github.com/pomerium/pomerium/internal/encoding/mock"
|
||||
|
||||
// Encoder MockCSRFStore is a mock implementation of Cipher.
|
||||
type Encoder struct {
|
||||
MarshalResponse []byte
|
||||
MarshalError error
|
||||
UnmarshalError error
|
||||
}
|
||||
|
||||
// Marshal is a mock implementation of Encoder.
|
||||
func (mc Encoder) Marshal(i interface{}) ([]byte, error) {
|
||||
return mc.MarshalResponse, mc.MarshalError
|
||||
}
|
||||
|
||||
// Unmarshal is a mock implementation of Encoder.
|
||||
func (mc Encoder) Unmarshal(s []byte, i interface{}) error {
|
||||
return mc.UnmarshalError
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package encoding // import "github.com/pomerium/pomerium/internal/encoding"
|
||||
package mock // import "github.com/pomerium/pomerium/internal/encoding/mock"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
func TestMockEncoder(t *testing.T) {
|
||||
e := errors.New("err")
|
||||
mc := MockEncoder{
|
||||
mc := Encoder{
|
||||
MarshalResponse: []byte("MarshalResponse"),
|
||||
MarshalError: e,
|
||||
UnmarshalError: e,
|
|
@ -1,18 +0,0 @@
|
|||
package encoding // import "github.com/pomerium/pomerium/internal/encoding"
|
||||
|
||||
// MockEncoder MockCSRFStore is a mock implementation of Cipher.
|
||||
type MockEncoder struct {
|
||||
MarshalResponse []byte
|
||||
MarshalError error
|
||||
UnmarshalError error
|
||||
}
|
||||
|
||||
// Marshal is a mock implementation of MockEncoder.
|
||||
func (mc MockEncoder) Marshal(i interface{}) ([]byte, error) {
|
||||
return mc.MarshalResponse, mc.MarshalError
|
||||
}
|
||||
|
||||
// Unmarshal is a mock implementation of MockEncoder.
|
||||
func (mc MockEncoder) Unmarshal(s []byte, i interface{}) error {
|
||||
return mc.UnmarshalError
|
||||
}
|
|
@ -3,10 +3,11 @@ package sessions // import "github.com/pomerium/pomerium/internal/sessions"
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -31,8 +32,9 @@ type CookieStore struct {
|
|||
Expire time.Duration
|
||||
HTTPOnly bool
|
||||
Secure bool
|
||||
encoder Marshaler
|
||||
decoder Unmarshaler
|
||||
|
||||
encoder encoding.Marshaler
|
||||
decoder encoding.Unmarshaler
|
||||
}
|
||||
|
||||
// CookieOptions holds options for CookieStore
|
||||
|
@ -45,70 +47,60 @@ type CookieOptions struct {
|
|||
}
|
||||
|
||||
// NewCookieStore returns a new session with ciphers for each of the cookie secrets
|
||||
func NewCookieStore(opts *CookieOptions, encoder Encoder) (*CookieStore, error) {
|
||||
if opts.Name == "" {
|
||||
return nil, fmt.Errorf("internal/sessions: cookie name cannot be empty")
|
||||
func NewCookieStore(opts *CookieOptions, encoder encoding.MarshalUnmarshaler) (*CookieStore, error) {
|
||||
cs, err := NewCookieLoader(opts, encoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if encoder == nil {
|
||||
return nil, fmt.Errorf("internal/sessions: decoder cannot be nil")
|
||||
}
|
||||
return &CookieStore{
|
||||
Name: opts.Name,
|
||||
Secure: opts.Secure,
|
||||
HTTPOnly: opts.HTTPOnly,
|
||||
Domain: opts.Domain,
|
||||
Expire: opts.Expire,
|
||||
encoder: encoder,
|
||||
decoder: encoder,
|
||||
}, nil
|
||||
cs.encoder = encoder
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
// NewCookieLoader returns a new session with ciphers for each of the cookie secrets
|
||||
func NewCookieLoader(opts *CookieOptions, decoder Unmarshaler) (*CookieStore, error) {
|
||||
func NewCookieLoader(opts *CookieOptions, dencoder encoding.Unmarshaler) (*CookieStore, error) {
|
||||
if dencoder == nil {
|
||||
return nil, fmt.Errorf("internal/sessions: dencoder cannot be nil")
|
||||
}
|
||||
cs, err := newCookieStore(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs.decoder = dencoder
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
func newCookieStore(opts *CookieOptions) (*CookieStore, error) {
|
||||
if opts.Name == "" {
|
||||
return nil, fmt.Errorf("internal/sessions: cookie name cannot be empty")
|
||||
}
|
||||
if decoder == nil {
|
||||
return nil, fmt.Errorf("internal/sessions: decoder cannot be nil")
|
||||
}
|
||||
|
||||
return &CookieStore{
|
||||
Name: opts.Name,
|
||||
Secure: opts.Secure,
|
||||
HTTPOnly: opts.HTTPOnly,
|
||||
Domain: opts.Domain,
|
||||
Expire: opts.Expire,
|
||||
decoder: decoder,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cs *CookieStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
||||
domain := req.Host
|
||||
|
||||
if cs.Domain != "" {
|
||||
domain = cs.Domain
|
||||
}
|
||||
|
||||
if h, _, err := net.SplitHostPort(domain); err == nil {
|
||||
domain = h
|
||||
}
|
||||
c := &http.Cookie{
|
||||
Name: name,
|
||||
func (cs *CookieStore) makeCookie(value string) *http.Cookie {
|
||||
return &http.Cookie{
|
||||
Name: cs.Name,
|
||||
Value: value,
|
||||
Path: "/",
|
||||
Domain: domain,
|
||||
Domain: cs.Domain,
|
||||
HttpOnly: cs.HTTPOnly,
|
||||
Secure: cs.Secure,
|
||||
Expires: timeNow().Add(cs.Expire),
|
||||
}
|
||||
// only set an expiration if we want one, otherwise default to non perm session based
|
||||
if expiration != 0 {
|
||||
c.Expires = now.Add(expiration)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// ClearSession clears the session cookie from a request
|
||||
func (cs *CookieStore) ClearSession(w http.ResponseWriter, req *http.Request) {
|
||||
http.SetCookie(w, cs.makeCookie(req, cs.Name, "", time.Hour*-1, time.Now()))
|
||||
func (cs *CookieStore) ClearSession(w http.ResponseWriter, _ *http.Request) {
|
||||
c := cs.makeCookie("")
|
||||
c.MaxAge = -1
|
||||
c.Expires = timeNow().Add(-time.Hour)
|
||||
http.SetCookie(w, c)
|
||||
}
|
||||
|
||||
// LoadSession returns a State from the cookie in the request.
|
||||
|
@ -127,7 +119,7 @@ func (cs *CookieStore) LoadSession(req *http.Request) (*State, error) {
|
|||
}
|
||||
|
||||
// SaveSession saves a session state to a request's cookie store.
|
||||
func (cs *CookieStore) SaveSession(w http.ResponseWriter, req *http.Request, x interface{}) error {
|
||||
func (cs *CookieStore) SaveSession(w http.ResponseWriter, _ *http.Request, x interface{}) error {
|
||||
var value string
|
||||
if cs.encoder != nil {
|
||||
data, err := cs.encoder.Marshal(x)
|
||||
|
@ -145,17 +137,12 @@ func (cs *CookieStore) SaveSession(w http.ResponseWriter, req *http.Request, x i
|
|||
return errors.New("internal/sessions: cannot save non-string type")
|
||||
}
|
||||
}
|
||||
cs.setSessionCookie(w, req, value)
|
||||
cs.setSessionCookie(w, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeSessionCookie constructs a session cookie given the request, an expiration time and the current time.
|
||||
func (cs *CookieStore) makeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
||||
return cs.makeCookie(req, cs.Name, value, expiration, now)
|
||||
}
|
||||
|
||||
func (cs *CookieStore) setSessionCookie(w http.ResponseWriter, req *http.Request, val string) {
|
||||
cs.setCookie(w, cs.makeSessionCookie(req, val, cs.Expire, time.Now()))
|
||||
func (cs *CookieStore) setSessionCookie(w http.ResponseWriter, val string) {
|
||||
cs.setCookie(w, cs.makeCookie(val))
|
||||
}
|
||||
|
||||
func (cs *CookieStore) setCookie(w http.ResponseWriter, cookie *http.Cookie) {
|
||||
|
|
|
@ -4,17 +4,18 @@ import (
|
|||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/ecjson"
|
||||
"github.com/pomerium/pomerium/internal/encoding/mock"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
)
|
||||
|
||||
func TestNewCookieStore(t *testing.T) {
|
||||
|
@ -26,7 +27,7 @@ func TestNewCookieStore(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
opts *CookieOptions
|
||||
encoder Encoder
|
||||
encoder encoding.MarshalUnmarshaler
|
||||
want *CookieStore
|
||||
wantErr bool
|
||||
}{
|
||||
|
@ -60,7 +61,7 @@ func TestNewCookieLoader(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
opts *CookieOptions
|
||||
encoder Encoder
|
||||
encoder encoding.MarshalUnmarshaler
|
||||
want *CookieStore
|
||||
wantErr bool
|
||||
}{
|
||||
|
@ -86,58 +87,6 @@ func TestNewCookieLoader(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCookieStore_makeCookie(t *testing.T) {
|
||||
cipher, err := cryptutil.NewAEADCipher(cryptutil.NewKey())
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
domain string
|
||||
|
||||
cookieDomain string
|
||||
cookieName string
|
||||
value string
|
||||
expiration time.Duration
|
||||
want *http.Cookie
|
||||
wantCSRF *http.Cookie
|
||||
}{
|
||||
{"good", "http://httpbin.corp.pomerium.io", "", "_pomerium", "value", 0, &http.Cookie{Name: "_pomerium", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}, &http.Cookie{Name: "_pomerium_csrf", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}},
|
||||
{"domains with https", "https://httpbin.corp.pomerium.io", "", "_pomerium", "value", 0, &http.Cookie{Name: "_pomerium", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}, &http.Cookie{Name: "_pomerium_csrf", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}},
|
||||
{"domain with port", "http://httpbin.corp.pomerium.io:443", "", "_pomerium", "value", 0, &http.Cookie{Name: "_pomerium", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}, &http.Cookie{Name: "_pomerium_csrf", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}},
|
||||
{"expiration set", "http://httpbin.corp.pomerium.io:443", "", "_pomerium", "value", 10 * time.Second, &http.Cookie{Expires: now.Add(10 * time.Second), Name: "_pomerium", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}, &http.Cookie{Expires: now.Add(10 * time.Second), Name: "_pomerium_csrf", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}},
|
||||
{"good", "http://httpbin.corp.pomerium.io", "pomerium.io", "_pomerium", "value", 0, &http.Cookie{Name: "_pomerium", Value: "value", Path: "/", Domain: "pomerium.io", Secure: true, HttpOnly: true}, &http.Cookie{Name: "_pomerium_csrf", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := httptest.NewRequest("GET", tt.domain, nil)
|
||||
|
||||
s, err := NewCookieStore(
|
||||
&CookieOptions{
|
||||
Name: "_pomerium",
|
||||
Secure: true,
|
||||
HTTPOnly: true,
|
||||
Domain: tt.cookieDomain,
|
||||
Expire: 10 * time.Second,
|
||||
},
|
||||
ecjson.New(cipher))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(s.makeCookie(r, tt.cookieName, tt.value, tt.expiration, now), tt.want); diff != "" {
|
||||
t.Errorf("CookieStore.makeCookie() = \n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(s.makeSessionCookie(r, tt.value, tt.expiration, now), tt.want); diff != "" {
|
||||
t.Errorf("CookieStore.makeSessionCookie() = \n%s", diff)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCookieStore_SaveSession(t *testing.T) {
|
||||
c, err := cryptutil.NewAEADCipher(cryptutil.NewKey())
|
||||
if err != nil {
|
||||
|
@ -149,18 +98,21 @@ func TestCookieStore_SaveSession(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
State *State
|
||||
encoder Encoder
|
||||
decoder Encoder
|
||||
name string
|
||||
// State *State
|
||||
State interface{}
|
||||
encoder encoding.Marshaler
|
||||
decoder encoding.Unmarshaler
|
||||
wantErr bool
|
||||
wantLoadErr bool
|
||||
}{
|
||||
{"good", &State{Email: "user@domain.com", User: "user"}, ecjson.New(c), ecjson.New(c), false, false},
|
||||
{"bad cipher", &State{Email: "user@domain.com", User: "user"}, nil, nil, true, true},
|
||||
{"huge cookie", &State{Subject: fmt.Sprintf("%x", hugeString), Email: "user@domain.com", User: "user"}, ecjson.New(c), ecjson.New(c), false, false},
|
||||
{"marshal error", &State{Email: "user@domain.com", User: "user"}, encoding.MockEncoder{MarshalError: errors.New("error")}, ecjson.New(c), true, true},
|
||||
{"marshal error", &State{Email: "user@domain.com", User: "user"}, mock.Encoder{MarshalError: errors.New("error")}, ecjson.New(c), true, true},
|
||||
{"nil encoder cannot save non string type", &State{Email: "user@domain.com", User: "user"}, nil, ecjson.New(c), true, true},
|
||||
{"good marshal string directly", cryptutil.NewBase64Key(), nil, ecjson.New(c), false, true},
|
||||
{"good marshal bytes directly", cryptutil.NewKey(), nil, ecjson.New(c), false, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -171,7 +123,7 @@ func TestCookieStore_SaveSession(t *testing.T) {
|
|||
Domain: "pomerium.io",
|
||||
Expire: 10 * time.Second,
|
||||
encoder: tt.encoder,
|
||||
decoder: tt.encoder,
|
||||
decoder: tt.decoder,
|
||||
}
|
||||
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
|
@ -182,7 +134,6 @@ func TestCookieStore_SaveSession(t *testing.T) {
|
|||
}
|
||||
r = httptest.NewRequest("GET", "/", nil)
|
||||
for _, cookie := range w.Result().Cookies() {
|
||||
// t.Log(cookie)
|
||||
r.AddCookie(cookie)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ package sessions // import "github.com/pomerium/pomerium/internal/sessions"
|
|||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -15,7 +17,7 @@ const (
|
|||
type HeaderStore struct {
|
||||
authHeader string
|
||||
authType string
|
||||
encoder Unmarshaler
|
||||
encoder encoding.Unmarshaler
|
||||
}
|
||||
|
||||
// NewHeaderStore returns a new header store for loading sessions from
|
||||
|
@ -23,7 +25,7 @@ type HeaderStore struct {
|
|||
//
|
||||
// NOTA BENE: While most servers do not log Authorization headers by default,
|
||||
// you should ensure no other services are logging or leaking your auth headers.
|
||||
func NewHeaderStore(enc Unmarshaler, headerType string) *HeaderStore {
|
||||
func NewHeaderStore(enc encoding.Unmarshaler, headerType string) *HeaderStore {
|
||||
if headerType == "" {
|
||||
headerType = defaultAuthType
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package sessions // import "github.com/pomerium/pomerium/internal/sessions"
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -12,8 +14,8 @@ const (
|
|||
// query strings / query parameters.
|
||||
type QueryParamStore struct {
|
||||
queryParamKey string
|
||||
encoder Marshaler
|
||||
decoder Unmarshaler
|
||||
encoder encoding.Marshaler
|
||||
decoder encoding.Unmarshaler
|
||||
}
|
||||
|
||||
// NewQueryParamStore returns a new query param store for loading sessions from
|
||||
|
@ -21,7 +23,7 @@ type QueryParamStore struct {
|
|||
//
|
||||
// NOTA BENE: By default, most servers _DO_ log query params, the leaking or
|
||||
// accidental logging of which should be considered a security issue.
|
||||
func NewQueryParamStore(enc Encoder, qp string) *QueryParamStore {
|
||||
func NewQueryParamStore(enc encoding.MarshalUnmarshaler, qp string) *QueryParamStore {
|
||||
if qp == "" {
|
||||
qp = defaultQueryParamKey
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/mock"
|
||||
)
|
||||
|
||||
func TestNewQueryParamStore(t *testing.T) {
|
||||
|
@ -16,13 +17,13 @@ func TestNewQueryParamStore(t *testing.T) {
|
|||
name string
|
||||
State *State
|
||||
|
||||
enc Encoder
|
||||
enc encoding.MarshalUnmarshaler
|
||||
qp string
|
||||
wantErr bool
|
||||
wantURL *url.URL
|
||||
}{
|
||||
{"simple good", &State{Email: "user@domain.com", User: "user"}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, "", false, &url.URL{Path: "/", RawQuery: "pomerium_session=ok"}},
|
||||
{"marshall error", &State{Email: "user@domain.com", User: "user"}, encoding.MockEncoder{MarshalError: errors.New("error")}, "", true, &url.URL{Path: "/"}},
|
||||
{"simple good", &State{Email: "user@domain.com", User: "user"}, mock.Encoder{MarshalResponse: []byte("ok")}, "", false, &url.URL{Path: "/", RawQuery: "pomerium_session=ok"}},
|
||||
{"marshall error", &State{Email: "user@domain.com", User: "user"}, mock.Encoder{MarshalError: errors.New("error")}, "", true, &url.URL{Path: "/"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -37,19 +37,3 @@ type SessionStore interface {
|
|||
type SessionLoader interface {
|
||||
LoadSession(*http.Request) (*State, error)
|
||||
}
|
||||
|
||||
// Encoder can both Marshal and Unmarshal a struct into and from a set of bytes.
|
||||
type Encoder interface {
|
||||
Marshaler
|
||||
Unmarshaler
|
||||
}
|
||||
|
||||
// Marshaler encodes a struct into a set of bytes.
|
||||
type Marshaler interface {
|
||||
Marshal(interface{}) ([]byte, error)
|
||||
}
|
||||
|
||||
// Unmarshaler decodes a set of bytes and returns a struct.
|
||||
type Unmarshaler interface {
|
||||
Unmarshal([]byte, interface{}) error
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
"github.com/pomerium/pomerium/internal/config"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/mock"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/proxy/clients"
|
||||
|
@ -65,17 +66,17 @@ func TestProxy_UserDashboard(t *testing.T) {
|
|||
ctxError error
|
||||
options config.Options
|
||||
method string
|
||||
cipher sessions.Encoder
|
||||
cipher encoding.MarshalUnmarshaler
|
||||
session sessions.SessionStore
|
||||
authorizer clients.Authorizer
|
||||
|
||||
wantAdminForm bool
|
||||
wantStatus int
|
||||
}{
|
||||
{"good", nil, opts, http.MethodGet, &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{}, false, http.StatusOK},
|
||||
{"session context error", errors.New("error"), opts, http.MethodGet, &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{}, false, http.StatusInternalServerError},
|
||||
{"want admin form good admin authorization", nil, opts, http.MethodGet, &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{IsAdminResponse: true}, true, http.StatusOK},
|
||||
{"is admin but authorization fails", nil, opts, http.MethodGet, &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{IsAdminError: errors.New("err")}, false, http.StatusInternalServerError},
|
||||
{"good", nil, opts, http.MethodGet, &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{}, false, http.StatusOK},
|
||||
{"session context error", errors.New("error"), opts, http.MethodGet, &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{}, false, http.StatusInternalServerError},
|
||||
{"want admin form good admin authorization", nil, opts, http.MethodGet, &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{IsAdminResponse: true}, true, http.StatusOK},
|
||||
{"is admin but authorization fails", nil, opts, http.MethodGet, &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{IsAdminError: errors.New("err")}, false, http.StatusInternalServerError},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -124,17 +125,17 @@ func TestProxy_Impersonate(t *testing.T) {
|
|||
email string
|
||||
groups string
|
||||
csrf string
|
||||
cipher sessions.Encoder
|
||||
cipher encoding.MarshalUnmarshaler
|
||||
sessionStore sessions.SessionStore
|
||||
authorizer clients.Authorizer
|
||||
wantStatus int
|
||||
}{
|
||||
{"good", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
{"good", false, opts, errors.New("error"), http.MethodPost, "user@blah.com", "", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError},
|
||||
{"session load error", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{LoadError: errors.New("err"), Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
{"non admin users rejected", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: false}, http.StatusForbidden},
|
||||
{"non admin users rejected on error", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true, IsAdminError: errors.New("err")}, http.StatusForbidden},
|
||||
{"groups", false, opts, nil, http.MethodPost, "user@blah.com", "group1,group2", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
{"good", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
{"good", false, opts, errors.New("error"), http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError},
|
||||
{"session load error", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &sessions.MockSessionStore{LoadError: errors.New("err"), Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
{"non admin users rejected", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: false}, http.StatusForbidden},
|
||||
{"non admin users rejected on error", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true, IsAdminError: errors.New("err")}, http.StatusForbidden},
|
||||
{"groups", false, opts, nil, http.MethodPost, "user@blah.com", "group1,group2", "", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -228,24 +229,24 @@ func TestProxy_VerifyWithMiddleware(t *testing.T) {
|
|||
path string
|
||||
verifyURI string
|
||||
|
||||
cipher sessions.Encoder
|
||||
cipher encoding.MarshalUnmarshaler
|
||||
sessionStore sessions.SessionStore
|
||||
authorizer clients.Authorizer
|
||||
wantStatus int
|
||||
wantBody string
|
||||
}{
|
||||
{"good", opts, nil, http.MethodGet, "", "/", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK, ""},
|
||||
{"good verify only", opts, nil, http.MethodGet, "", "/verify", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK, ""},
|
||||
{"bad naked domain uri", opts, nil, http.MethodGet, "", "/", "a.naked.domain", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"},
|
||||
{"bad naked domain uri verify only", opts, nil, http.MethodGet, "", "/verify", "a.naked.domain", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"},
|
||||
{"bad empty verification uri", opts, nil, http.MethodGet, "", "/", " ", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"},
|
||||
{"bad empty verification uri verify only", opts, nil, http.MethodGet, "", "/verify", " ", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"},
|
||||
{"not authorized", opts, nil, http.MethodGet, "", "/", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"user@test.example is not authorized for some.domain.example\"}\n"},
|
||||
{"not authorized verify endpoint", opts, nil, http.MethodGet, "", "/verify", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"user@test.example is not authorized for some.domain.example\"}\n"},
|
||||
{"not authorized expired, redirect to auth", opts, sessions.ErrExpired, http.MethodGet, "", "/", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusFound, ""},
|
||||
{"not authorized expired, don't redirect!", opts, sessions.ErrExpired, http.MethodGet, "", "/verify", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"internal/sessions: validation failed, token is expired (exp)\"}\n"},
|
||||
{"not authorized because of error", opts, nil, http.MethodGet, "", "/", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeError: errors.New("authz error")}, http.StatusInternalServerError, "{\"error\":\"authz error\"}\n"},
|
||||
{"not authorized expired, do not redirect to auth", opts, nil, http.MethodGet, "", "/verify", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"internal/sessions: validation failed, token is expired (exp)\"}\n"},
|
||||
{"good", opts, nil, http.MethodGet, "", "/", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK, ""},
|
||||
{"good verify only", opts, nil, http.MethodGet, "", "/verify", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK, ""},
|
||||
{"bad naked domain uri", opts, nil, http.MethodGet, "", "/", "a.naked.domain", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"},
|
||||
{"bad naked domain uri verify only", opts, nil, http.MethodGet, "", "/verify", "a.naked.domain", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"},
|
||||
{"bad empty verification uri", opts, nil, http.MethodGet, "", "/", " ", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"},
|
||||
{"bad empty verification uri verify only", opts, nil, http.MethodGet, "", "/verify", " ", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"},
|
||||
{"not authorized", opts, nil, http.MethodGet, "", "/", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"user@test.example is not authorized for some.domain.example\"}\n"},
|
||||
{"not authorized verify endpoint", opts, nil, http.MethodGet, "", "/verify", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"user@test.example is not authorized for some.domain.example\"}\n"},
|
||||
{"not authorized expired, redirect to auth", opts, sessions.ErrExpired, http.MethodGet, "", "/", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusFound, ""},
|
||||
{"not authorized expired, don't redirect!", opts, sessions.ErrExpired, http.MethodGet, "", "/verify", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"internal/sessions: validation failed, token is expired (exp)\"}\n"},
|
||||
{"not authorized because of error", opts, nil, http.MethodGet, "", "/", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeError: errors.New("authz error")}, http.StatusInternalServerError, "{\"error\":\"authz error\"}\n"},
|
||||
{"not authorized expired, do not redirect to auth", opts, nil, http.MethodGet, "", "/verify", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"internal/sessions: validation failed, token is expired (exp)\"}\n"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -312,17 +313,17 @@ func TestProxy_Callback(t *testing.T) {
|
|||
|
||||
qp map[string]string
|
||||
|
||||
cipher sessions.Encoder
|
||||
cipher encoding.MarshalUnmarshaler
|
||||
sessionStore sessions.SessionStore
|
||||
authorizer clients.Authorizer
|
||||
wantStatus int
|
||||
wantBody string
|
||||
}{
|
||||
{"good", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_programmatic_destination_url": "ok", "pomerium_jwt": "KBEjQ9rnCxaAX-GOqetGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &encoding.MockEncoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"bad decrypt", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_jwt": "KBEjQ9rnCxaAX-GOqexGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &encoding.MockEncoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"bad save session", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_jwt": "KBEjQ9rnCxaAX-GOqetGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &encoding.MockEncoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{SaveError: errors.New("hi")}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusInternalServerError, ""},
|
||||
{"bad base64", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_jwt": "^"}, &encoding.MockEncoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"malformed redirect", opts, http.MethodGet, "http", "example.com", "/", nil, &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"good", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_programmatic_destination_url": "ok", "pomerium_jwt": "KBEjQ9rnCxaAX-GOqetGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &mock.Encoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"bad decrypt", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_jwt": "KBEjQ9rnCxaAX-GOqexGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &mock.Encoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"bad save session", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_jwt": "KBEjQ9rnCxaAX-GOqetGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &mock.Encoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{SaveError: errors.New("hi")}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusInternalServerError, ""},
|
||||
{"bad base64", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_jwt": "^"}, &mock.Encoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"malformed redirect", opts, http.MethodGet, "http", "example.com", "/", nil, &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
|
@ -97,7 +98,7 @@ func (p *Proxy) authorize(host string, w http.ResponseWriter, r *http.Request) e
|
|||
|
||||
// SignRequest is middleware that signs a JWT that contains a user's id,
|
||||
// email, and group. Session state is retrieved from the users's request context
|
||||
func (p *Proxy) SignRequest(signer sessions.Marshaler) func(next http.Handler) http.Handler {
|
||||
func (p *Proxy) SignRequest(signer encoding.Marshaler) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "proxy.SignRequest")
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
"github.com/pomerium/pomerium/internal/config"
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
|
@ -75,7 +76,7 @@ type Proxy struct {
|
|||
|
||||
AuthorizeClient clients.Authorizer
|
||||
|
||||
encoder sessions.Encoder
|
||||
encoder encoding.Unmarshaler
|
||||
cookieOptions *sessions.CookieOptions
|
||||
cookieSecret []byte
|
||||
defaultUpstreamTimeout time.Duration
|
||||
|
|
Loading…
Add table
Reference in a new issue