mirror of
https://github.com/pomerium/pomerium.git
synced 2025-06-23 13:08:13 +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/config"
|
||||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
"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/ecjson"
|
||||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||||
"github.com/pomerium/pomerium/internal/identity"
|
"github.com/pomerium/pomerium/internal/identity"
|
||||||
|
@ -59,7 +60,7 @@ type Authenticate struct {
|
||||||
sharedCipher cipher.AEAD
|
sharedCipher cipher.AEAD
|
||||||
// sharedEncoder is the encoder to use to serialize data to be consumed
|
// sharedEncoder is the encoder to use to serialize data to be consumed
|
||||||
// by other services
|
// by other services
|
||||||
sharedEncoder sessions.Encoder
|
sharedEncoder encoding.MarshalUnmarshaler
|
||||||
|
|
||||||
// data related to this service only
|
// data related to this service only
|
||||||
cookieOptions *sessions.CookieOptions
|
cookieOptions *sessions.CookieOptions
|
||||||
|
@ -68,7 +69,7 @@ type Authenticate struct {
|
||||||
// is the cipher to use to encrypt data for this service
|
// is the cipher to use to encrypt data for this service
|
||||||
cookieCipher cipher.AEAD
|
cookieCipher cipher.AEAD
|
||||||
sessionStore sessions.SessionStore
|
sessionStore sessions.SessionStore
|
||||||
encryptedEncoder sessions.Encoder
|
encryptedEncoder encoding.MarshalUnmarshaler
|
||||||
sessionStores []sessions.SessionStore
|
sessionStores []sessions.SessionStore
|
||||||
sessionLoaders []sessions.SessionLoader
|
sessionLoaders []sessions.SessionLoader
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ func (a *Authenticate) VerifySession(next http.Handler) http.Handler {
|
||||||
state, err := sessions.FromContext(r.Context())
|
state, err := sessions.FromContext(r.Context())
|
||||||
if errors.Is(err, sessions.ErrExpired) {
|
if errors.Is(err, sessions.ErrExpired) {
|
||||||
if err := a.refresh(w, r, state); err != nil {
|
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)
|
a.redirectToIdentityProvider(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ func (a *Authenticate) VerifySession(next http.Handler) http.Handler {
|
||||||
http.Redirect(w, r, urlutil.GetAbsoluteURL(r).String(), http.StatusFound)
|
http.Redirect(w, r, urlutil.GetAbsoluteURL(r).String(), http.StatusFound)
|
||||||
return
|
return
|
||||||
} else if err != nil {
|
} 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)
|
a.redirectToIdentityProvider(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||||
"github.com/pomerium/pomerium/internal/encoding"
|
"github.com/pomerium/pomerium/internal/encoding"
|
||||||
|
"github.com/pomerium/pomerium/internal/encoding/mock"
|
||||||
"github.com/pomerium/pomerium/internal/identity"
|
"github.com/pomerium/pomerium/internal/identity"
|
||||||
"github.com/pomerium/pomerium/internal/sessions"
|
"github.com/pomerium/pomerium/internal/sessions"
|
||||||
"github.com/pomerium/pomerium/internal/templates"
|
"github.com/pomerium/pomerium/internal/templates"
|
||||||
|
@ -85,17 +86,17 @@ func TestAuthenticate_SignIn(t *testing.T) {
|
||||||
|
|
||||||
session sessions.SessionStore
|
session sessions.SessionStore
|
||||||
provider identity.MockProvider
|
provider identity.MockProvider
|
||||||
encoder sessions.Encoder
|
encoder encoding.MarshalUnmarshaler
|
||||||
wantCode int
|
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},
|
{"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{}, &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{}, &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{}, &encoding.MockEncoder{}, http.StatusBadRequest},
|
{"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{}, &encoding.MockEncoder{MarshalError: errors.New("error")}, 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{}, &encoding.MockEncoder{}, 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{}, &encoding.MockEncoder{}, http.StatusFound},
|
{"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{}, &encoding.MockEncoder{MarshalError: errors.New("error")}, http.StatusBadRequest},
|
{"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{}, &encoding.MockEncoder{}, 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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -349,16 +350,16 @@ func TestAuthenticate_RefreshAPI(t *testing.T) {
|
||||||
ctxError error
|
ctxError error
|
||||||
|
|
||||||
provider identity.Authenticator
|
provider identity.Authenticator
|
||||||
secretEncoder sessions.Encoder
|
secretEncoder encoding.MarshalUnmarshaler
|
||||||
sharedEncoder sessions.Encoder
|
sharedEncoder encoding.MarshalUnmarshaler
|
||||||
|
|
||||||
wantStatus int
|
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},
|
{"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")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, http.StatusInternalServerError},
|
{"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)}}}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, http.StatusBadRequest},
|
{"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)}}}, encoding.MockEncoder{MarshalError: errors.New("error")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, http.StatusInternalServerError},
|
{"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)}}}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, encoding.MockEncoder{MarshalError: errors.New("error")}, 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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
|
@ -25,6 +25,10 @@
|
||||||
- Force refresh has been removed from the dashboard.
|
- Force refresh has been removed from the dashboard.
|
||||||
- Previous programmatic authentication endpoints (`/api/v1/token`) has been removed and is no longer supported.
|
- 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
|
## v0.4.2
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
@ -338,3 +342,4 @@
|
||||||
[gh-332]: https://github.com/pomerium/pomerium/pull/332/
|
[gh-332]: https://github.com/pomerium/pomerium/pull/332/
|
||||||
[gh-338]: https://github.com/pomerium/pomerium/issues/338
|
[gh-338]: https://github.com/pomerium/pomerium/issues/338
|
||||||
[gh-363]: https://github.com/pomerium/pomerium/issues/363
|
[gh-363]: https://github.com/pomerium/pomerium/issues/363
|
||||||
|
[gh-376]: https://github.com/pomerium/pomerium/pull/376/
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||||
|
"github.com/pomerium/pomerium/internal/encoding"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EncryptedCompressedJSON implements SecureEncoder for JSON using an AEAD cipher.
|
// 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.
|
// 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}
|
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 (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||||
|
"github.com/pomerium/pomerium/internal/encoding"
|
||||||
|
|
||||||
jose "gopkg.in/square/go-jose.v2"
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
"gopkg.in/square/go-jose.v2/jwt"
|
"gopkg.in/square/go-jose.v2/jwt"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// JSONWebSigner is the struct representing a signed JWT.
|
// 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.
|
// 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},
|
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: key},
|
||||||
(&jose.SignerOptions{}).WithType("JWT"))
|
(&jose.SignerOptions{}).WithType("JWT"))
|
||||||
if err != nil {
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
func TestMockEncoder(t *testing.T) {
|
func TestMockEncoder(t *testing.T) {
|
||||||
e := errors.New("err")
|
e := errors.New("err")
|
||||||
mc := MockEncoder{
|
mc := Encoder{
|
||||||
MarshalResponse: []byte("MarshalResponse"),
|
MarshalResponse: []byte("MarshalResponse"),
|
||||||
MarshalError: e,
|
MarshalError: e,
|
||||||
UnmarshalError: 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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/encoding"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -31,8 +32,9 @@ type CookieStore struct {
|
||||||
Expire time.Duration
|
Expire time.Duration
|
||||||
HTTPOnly bool
|
HTTPOnly bool
|
||||||
Secure bool
|
Secure bool
|
||||||
encoder Marshaler
|
|
||||||
decoder Unmarshaler
|
encoder encoding.Marshaler
|
||||||
|
decoder encoding.Unmarshaler
|
||||||
}
|
}
|
||||||
|
|
||||||
// CookieOptions holds options for CookieStore
|
// 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
|
// NewCookieStore returns a new session with ciphers for each of the cookie secrets
|
||||||
func NewCookieStore(opts *CookieOptions, encoder Encoder) (*CookieStore, error) {
|
func NewCookieStore(opts *CookieOptions, encoder encoding.MarshalUnmarshaler) (*CookieStore, error) {
|
||||||
if opts.Name == "" {
|
cs, err := NewCookieLoader(opts, encoder)
|
||||||
return nil, fmt.Errorf("internal/sessions: cookie name cannot be empty")
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
if encoder == nil {
|
cs.encoder = encoder
|
||||||
return nil, fmt.Errorf("internal/sessions: decoder cannot be nil")
|
return cs, nil
|
||||||
}
|
|
||||||
return &CookieStore{
|
|
||||||
Name: opts.Name,
|
|
||||||
Secure: opts.Secure,
|
|
||||||
HTTPOnly: opts.HTTPOnly,
|
|
||||||
Domain: opts.Domain,
|
|
||||||
Expire: opts.Expire,
|
|
||||||
encoder: encoder,
|
|
||||||
decoder: encoder,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCookieLoader returns a new session with ciphers for each of the cookie secrets
|
// 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 == "" {
|
if opts.Name == "" {
|
||||||
return nil, fmt.Errorf("internal/sessions: cookie name cannot be empty")
|
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{
|
return &CookieStore{
|
||||||
Name: opts.Name,
|
Name: opts.Name,
|
||||||
Secure: opts.Secure,
|
Secure: opts.Secure,
|
||||||
HTTPOnly: opts.HTTPOnly,
|
HTTPOnly: opts.HTTPOnly,
|
||||||
Domain: opts.Domain,
|
Domain: opts.Domain,
|
||||||
Expire: opts.Expire,
|
Expire: opts.Expire,
|
||||||
decoder: decoder,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *CookieStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
func (cs *CookieStore) makeCookie(value string) *http.Cookie {
|
||||||
domain := req.Host
|
return &http.Cookie{
|
||||||
|
Name: cs.Name,
|
||||||
if cs.Domain != "" {
|
|
||||||
domain = cs.Domain
|
|
||||||
}
|
|
||||||
|
|
||||||
if h, _, err := net.SplitHostPort(domain); err == nil {
|
|
||||||
domain = h
|
|
||||||
}
|
|
||||||
c := &http.Cookie{
|
|
||||||
Name: name,
|
|
||||||
Value: value,
|
Value: value,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
Domain: domain,
|
Domain: cs.Domain,
|
||||||
HttpOnly: cs.HTTPOnly,
|
HttpOnly: cs.HTTPOnly,
|
||||||
Secure: cs.Secure,
|
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
|
// ClearSession clears the session cookie from a request
|
||||||
func (cs *CookieStore) ClearSession(w http.ResponseWriter, req *http.Request) {
|
func (cs *CookieStore) ClearSession(w http.ResponseWriter, _ *http.Request) {
|
||||||
http.SetCookie(w, cs.makeCookie(req, cs.Name, "", time.Hour*-1, time.Now()))
|
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.
|
// 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.
|
// 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
|
var value string
|
||||||
if cs.encoder != nil {
|
if cs.encoder != nil {
|
||||||
data, err := cs.encoder.Marshal(x)
|
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")
|
return errors.New("internal/sessions: cannot save non-string type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cs.setSessionCookie(w, req, value)
|
cs.setSessionCookie(w, value)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeSessionCookie constructs a session cookie given the request, an expiration time and the current time.
|
func (cs *CookieStore) setSessionCookie(w http.ResponseWriter, val string) {
|
||||||
func (cs *CookieStore) makeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
cs.setCookie(w, cs.makeCookie(val))
|
||||||
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) setCookie(w http.ResponseWriter, cookie *http.Cookie) {
|
func (cs *CookieStore) setCookie(w http.ResponseWriter, cookie *http.Cookie) {
|
||||||
|
|
|
@ -4,17 +4,18 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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/cryptutil"
|
||||||
"github.com/pomerium/pomerium/internal/encoding"
|
"github.com/pomerium/pomerium/internal/encoding"
|
||||||
"github.com/pomerium/pomerium/internal/encoding/ecjson"
|
"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) {
|
func TestNewCookieStore(t *testing.T) {
|
||||||
|
@ -26,7 +27,7 @@ func TestNewCookieStore(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
opts *CookieOptions
|
opts *CookieOptions
|
||||||
encoder Encoder
|
encoder encoding.MarshalUnmarshaler
|
||||||
want *CookieStore
|
want *CookieStore
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
|
@ -60,7 +61,7 @@ func TestNewCookieLoader(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
opts *CookieOptions
|
opts *CookieOptions
|
||||||
encoder Encoder
|
encoder encoding.MarshalUnmarshaler
|
||||||
want *CookieStore
|
want *CookieStore
|
||||||
wantErr bool
|
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) {
|
func TestCookieStore_SaveSession(t *testing.T) {
|
||||||
c, err := cryptutil.NewAEADCipher(cryptutil.NewKey())
|
c, err := cryptutil.NewAEADCipher(cryptutil.NewKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -149,18 +98,21 @@ func TestCookieStore_SaveSession(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
State *State
|
// State *State
|
||||||
encoder Encoder
|
State interface{}
|
||||||
decoder Encoder
|
encoder encoding.Marshaler
|
||||||
|
decoder encoding.Unmarshaler
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantLoadErr bool
|
wantLoadErr bool
|
||||||
}{
|
}{
|
||||||
{"good", &State{Email: "user@domain.com", User: "user"}, ecjson.New(c), ecjson.New(c), false, false},
|
{"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},
|
{"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},
|
{"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},
|
{"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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -171,7 +123,7 @@ func TestCookieStore_SaveSession(t *testing.T) {
|
||||||
Domain: "pomerium.io",
|
Domain: "pomerium.io",
|
||||||
Expire: 10 * time.Second,
|
Expire: 10 * time.Second,
|
||||||
encoder: tt.encoder,
|
encoder: tt.encoder,
|
||||||
decoder: tt.encoder,
|
decoder: tt.decoder,
|
||||||
}
|
}
|
||||||
|
|
||||||
r := httptest.NewRequest("GET", "/", nil)
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
@ -182,7 +134,6 @@ func TestCookieStore_SaveSession(t *testing.T) {
|
||||||
}
|
}
|
||||||
r = httptest.NewRequest("GET", "/", nil)
|
r = httptest.NewRequest("GET", "/", nil)
|
||||||
for _, cookie := range w.Result().Cookies() {
|
for _, cookie := range w.Result().Cookies() {
|
||||||
// t.Log(cookie)
|
|
||||||
r.AddCookie(cookie)
|
r.AddCookie(cookie)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ package sessions // import "github.com/pomerium/pomerium/internal/sessions"
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/encoding"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -15,7 +17,7 @@ const (
|
||||||
type HeaderStore struct {
|
type HeaderStore struct {
|
||||||
authHeader string
|
authHeader string
|
||||||
authType string
|
authType string
|
||||||
encoder Unmarshaler
|
encoder encoding.Unmarshaler
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHeaderStore returns a new header store for loading sessions from
|
// 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,
|
// 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.
|
// 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 == "" {
|
if headerType == "" {
|
||||||
headerType = defaultAuthType
|
headerType = defaultAuthType
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package sessions // import "github.com/pomerium/pomerium/internal/sessions"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/encoding"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -12,8 +14,8 @@ const (
|
||||||
// query strings / query parameters.
|
// query strings / query parameters.
|
||||||
type QueryParamStore struct {
|
type QueryParamStore struct {
|
||||||
queryParamKey string
|
queryParamKey string
|
||||||
encoder Marshaler
|
encoder encoding.Marshaler
|
||||||
decoder Unmarshaler
|
decoder encoding.Unmarshaler
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewQueryParamStore returns a new query param store for loading sessions from
|
// 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
|
// NOTA BENE: By default, most servers _DO_ log query params, the leaking or
|
||||||
// accidental logging of which should be considered a security issue.
|
// 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 == "" {
|
if qp == "" {
|
||||||
qp = defaultQueryParamKey
|
qp = defaultQueryParamKey
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/pomerium/pomerium/internal/encoding"
|
"github.com/pomerium/pomerium/internal/encoding"
|
||||||
|
"github.com/pomerium/pomerium/internal/encoding/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewQueryParamStore(t *testing.T) {
|
func TestNewQueryParamStore(t *testing.T) {
|
||||||
|
@ -16,13 +17,13 @@ func TestNewQueryParamStore(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
State *State
|
State *State
|
||||||
|
|
||||||
enc Encoder
|
enc encoding.MarshalUnmarshaler
|
||||||
qp string
|
qp string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantURL *url.URL
|
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"}},
|
{"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"}, encoding.MockEncoder{MarshalError: errors.New("error")}, "", true, &url.URL{Path: "/"}},
|
{"marshall error", &State{Email: "user@domain.com", User: "user"}, mock.Encoder{MarshalError: errors.New("error")}, "", true, &url.URL{Path: "/"}},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
|
@ -37,19 +37,3 @@ type SessionStore interface {
|
||||||
type SessionLoader interface {
|
type SessionLoader interface {
|
||||||
LoadSession(*http.Request) (*State, error)
|
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/config"
|
||||||
"github.com/pomerium/pomerium/internal/encoding"
|
"github.com/pomerium/pomerium/internal/encoding"
|
||||||
|
"github.com/pomerium/pomerium/internal/encoding/mock"
|
||||||
"github.com/pomerium/pomerium/internal/httputil"
|
"github.com/pomerium/pomerium/internal/httputil"
|
||||||
"github.com/pomerium/pomerium/internal/sessions"
|
"github.com/pomerium/pomerium/internal/sessions"
|
||||||
"github.com/pomerium/pomerium/proxy/clients"
|
"github.com/pomerium/pomerium/proxy/clients"
|
||||||
|
@ -65,17 +66,17 @@ func TestProxy_UserDashboard(t *testing.T) {
|
||||||
ctxError error
|
ctxError error
|
||||||
options config.Options
|
options config.Options
|
||||||
method string
|
method string
|
||||||
cipher sessions.Encoder
|
cipher encoding.MarshalUnmarshaler
|
||||||
session sessions.SessionStore
|
session sessions.SessionStore
|
||||||
authorizer clients.Authorizer
|
authorizer clients.Authorizer
|
||||||
|
|
||||||
wantAdminForm bool
|
wantAdminForm bool
|
||||||
wantStatus int
|
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},
|
{"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, &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},
|
{"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, &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},
|
{"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, &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},
|
{"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 {
|
for _, tt := range tests {
|
||||||
|
@ -124,17 +125,17 @@ func TestProxy_Impersonate(t *testing.T) {
|
||||||
email string
|
email string
|
||||||
groups string
|
groups string
|
||||||
csrf string
|
csrf string
|
||||||
cipher sessions.Encoder
|
cipher encoding.MarshalUnmarshaler
|
||||||
sessionStore sessions.SessionStore
|
sessionStore sessions.SessionStore
|
||||||
authorizer clients.Authorizer
|
authorizer clients.Authorizer
|
||||||
wantStatus int
|
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, 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", "", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError},
|
{"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", "", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{LoadError: errors.New("err"), Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
{"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", "", "", &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", 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", "", "", &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},
|
{"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", "", &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},
|
{"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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -228,24 +229,24 @@ func TestProxy_VerifyWithMiddleware(t *testing.T) {
|
||||||
path string
|
path string
|
||||||
verifyURI string
|
verifyURI string
|
||||||
|
|
||||||
cipher sessions.Encoder
|
cipher encoding.MarshalUnmarshaler
|
||||||
sessionStore sessions.SessionStore
|
sessionStore sessions.SessionStore
|
||||||
authorizer clients.Authorizer
|
authorizer clients.Authorizer
|
||||||
wantStatus int
|
wantStatus int
|
||||||
wantBody string
|
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", 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", &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", &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", &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", 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", &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", &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, "", "/", " ", &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, "", "/", " ", &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", " ", &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", " ", &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", &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", 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", &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", &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", &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, 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", &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 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", &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 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", &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 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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -312,17 +313,17 @@ func TestProxy_Callback(t *testing.T) {
|
||||||
|
|
||||||
qp map[string]string
|
qp map[string]string
|
||||||
|
|
||||||
cipher sessions.Encoder
|
cipher encoding.MarshalUnmarshaler
|
||||||
sessionStore sessions.SessionStore
|
sessionStore sessions.SessionStore
|
||||||
authorizer clients.Authorizer
|
authorizer clients.Authorizer
|
||||||
wantStatus int
|
wantStatus int
|
||||||
wantBody string
|
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, ""},
|
{"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="}, &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 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="}, &encoding.MockEncoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{SaveError: errors.New("hi")}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusInternalServerError, ""},
|
{"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": "^"}, &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 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, &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, ""},
|
{"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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/encoding"
|
||||||
"github.com/pomerium/pomerium/internal/httputil"
|
"github.com/pomerium/pomerium/internal/httputil"
|
||||||
"github.com/pomerium/pomerium/internal/log"
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
"github.com/pomerium/pomerium/internal/sessions"
|
"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,
|
// 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
|
// 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 func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx, span := trace.StartSpan(r.Context(), "proxy.SignRequest")
|
ctx, span := trace.StartSpan(r.Context(), "proxy.SignRequest")
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/config"
|
"github.com/pomerium/pomerium/internal/config"
|
||||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||||
|
"github.com/pomerium/pomerium/internal/encoding"
|
||||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||||
"github.com/pomerium/pomerium/internal/httputil"
|
"github.com/pomerium/pomerium/internal/httputil"
|
||||||
"github.com/pomerium/pomerium/internal/log"
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
|
@ -75,7 +76,7 @@ type Proxy struct {
|
||||||
|
|
||||||
AuthorizeClient clients.Authorizer
|
AuthorizeClient clients.Authorizer
|
||||||
|
|
||||||
encoder sessions.Encoder
|
encoder encoding.Unmarshaler
|
||||||
cookieOptions *sessions.CookieOptions
|
cookieOptions *sessions.CookieOptions
|
||||||
cookieSecret []byte
|
cookieSecret []byte
|
||||||
defaultUpstreamTimeout time.Duration
|
defaultUpstreamTimeout time.Duration
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue