Use SessionStore for session in proxy
This commit is contained in:
parent
34cbe0497c
commit
c61f3a1c65
@ -456,27 +456,8 @@ func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, va
|
||||
|
||||
// ClearSessionCookie creates a cookie to unset the user's authentication cookie
|
||||
// stored in the user's session
|
||||
func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) {
|
||||
var cookies []*http.Cookie
|
||||
|
||||
// matches CookieName, CookieName_<number>
|
||||
var cookieNameRegex = regexp.MustCompile(fmt.Sprintf("^%s(_\\d+)?$", p.CookieName))
|
||||
|
||||
for _, c := range req.Cookies() {
|
||||
if cookieNameRegex.MatchString(c.Name) {
|
||||
clearCookie := p.makeCookie(req, c.Name, "", time.Hour*-1, time.Now())
|
||||
|
||||
http.SetCookie(rw, clearCookie)
|
||||
cookies = append(cookies, clearCookie)
|
||||
}
|
||||
}
|
||||
|
||||
// ugly hack because default domain changed
|
||||
if p.CookieDomain == "" && len(cookies) > 0 {
|
||||
clr2 := *cookies[0]
|
||||
clr2.Domain = req.Host
|
||||
http.SetCookie(rw, &clr2)
|
||||
}
|
||||
func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) error {
|
||||
return p.sessionStore.Clear(rw, req)
|
||||
}
|
||||
|
||||
// SetSessionCookie adds the user's session cookie to the response
|
||||
@ -487,35 +468,13 @@ func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request,
|
||||
}
|
||||
|
||||
// LoadCookiedSession reads the user's authentication details from the request
|
||||
func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*sessionsapi.SessionState, time.Duration, error) {
|
||||
var age time.Duration
|
||||
c, err := loadCookie(req, p.CookieName)
|
||||
if err != nil {
|
||||
// always http.ErrNoCookie
|
||||
return nil, age, fmt.Errorf("Cookie %q not present", p.CookieName)
|
||||
}
|
||||
val, timestamp, ok := cookie.Validate(c, p.CookieSeed, p.CookieExpire)
|
||||
if !ok {
|
||||
return nil, age, errors.New("Cookie Signature not valid")
|
||||
}
|
||||
|
||||
session, err := p.provider.SessionFromCookie(val, p.CookieCipher)
|
||||
if err != nil {
|
||||
return nil, age, err
|
||||
}
|
||||
|
||||
age = time.Now().Truncate(time.Second).Sub(timestamp)
|
||||
return session, age, nil
|
||||
func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*sessionsapi.SessionState, error) {
|
||||
return p.sessionStore.Load(req)
|
||||
}
|
||||
|
||||
// SaveSession creates a new session cookie value and sets this on the response
|
||||
func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *sessionsapi.SessionState) error {
|
||||
value, err := p.provider.CookieForSession(s, p.CookieCipher)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.SetSessionCookie(rw, req, value)
|
||||
return nil
|
||||
return p.sessionStore.Save(rw, req, s)
|
||||
}
|
||||
|
||||
// RobotsTxt disallows scraping pages from the OAuthProxy
|
||||
@ -835,12 +794,12 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
|
||||
var saveSession, clearSession, revalidated bool
|
||||
remoteAddr := getRemoteAddr(req)
|
||||
|
||||
session, sessionAge, err := p.LoadCookiedSession(req)
|
||||
session, err := p.LoadCookiedSession(req)
|
||||
if err != nil {
|
||||
logger.Printf("Error loading cookied session: %s", err)
|
||||
}
|
||||
if session != nil && sessionAge > p.CookieRefresh && p.CookieRefresh != time.Duration(0) {
|
||||
logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", sessionAge, session, p.CookieRefresh)
|
||||
if session != nil && session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) {
|
||||
logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh)
|
||||
saveSession = true
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/mbland/hmacauth"
|
||||
"github.com/pusher/oauth2_proxy/logger"
|
||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||
"github.com/pusher/oauth2_proxy/pkg/sessions/cookie"
|
||||
"github.com/pusher/oauth2_proxy/providers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -600,10 +601,15 @@ type ProcessCookieTestOpts struct {
|
||||
providerValidateCookieResponse bool
|
||||
}
|
||||
|
||||
func NewProcessCookieTest(opts ProcessCookieTestOpts) *ProcessCookieTest {
|
||||
type OptionsModifier func(*Options)
|
||||
|
||||
func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) *ProcessCookieTest {
|
||||
var pcTest ProcessCookieTest
|
||||
|
||||
pcTest.opts = NewOptions()
|
||||
for _, modifier := range modifiers {
|
||||
modifier(pcTest.opts)
|
||||
}
|
||||
pcTest.opts.ClientID = "bazquux"
|
||||
pcTest.opts.ClientSecret = "xyzzyplugh"
|
||||
pcTest.opts.CookieSecret = "0123456789abcdefabcd"
|
||||
@ -634,32 +640,38 @@ func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
|
||||
})
|
||||
}
|
||||
|
||||
func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) *ProcessCookieTest {
|
||||
return NewProcessCookieTest(ProcessCookieTestOpts{
|
||||
providerValidateCookieResponse: true,
|
||||
}, modifiers...)
|
||||
}
|
||||
|
||||
func (p *ProcessCookieTest) MakeCookie(value string, ref time.Time) []*http.Cookie {
|
||||
return p.proxy.MakeSessionCookie(p.req, value, p.opts.CookieExpire, ref)
|
||||
}
|
||||
|
||||
func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState, ref time.Time) error {
|
||||
value, err := p.proxy.provider.CookieForSession(s, p.proxy.CookieCipher)
|
||||
func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
|
||||
err := p.proxy.SaveSession(p.rw, p.req, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range p.proxy.MakeSessionCookie(p.req, value, p.proxy.CookieExpire, ref) {
|
||||
p.req.AddCookie(c)
|
||||
for _, cookie := range p.rw.Result().Cookies() {
|
||||
p.req.AddCookie(cookie)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, time.Duration, error) {
|
||||
func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
|
||||
return p.proxy.LoadCookiedSession(p.req)
|
||||
}
|
||||
|
||||
func TestLoadCookiedSession(t *testing.T) {
|
||||
pcTest := NewProcessCookieTestWithDefaults()
|
||||
|
||||
startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token"}
|
||||
pcTest.SaveSession(startSession, time.Now())
|
||||
startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: time.Now()}
|
||||
pcTest.SaveSession(startSession)
|
||||
|
||||
session, _, err := pcTest.LoadCookiedSession()
|
||||
session, err := pcTest.LoadCookiedSession()
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, startSession.Email, session.Email)
|
||||
assert.Equal(t, "john.doe@example.com", session.User)
|
||||
@ -669,7 +681,7 @@ func TestLoadCookiedSession(t *testing.T) {
|
||||
func TestProcessCookieNoCookieError(t *testing.T) {
|
||||
pcTest := NewProcessCookieTestWithDefaults()
|
||||
|
||||
session, _, err := pcTest.LoadCookiedSession()
|
||||
session, err := pcTest.LoadCookiedSession()
|
||||
assert.Equal(t, "Cookie \"_oauth2_proxy\" not present", err.Error())
|
||||
if session != nil {
|
||||
t.Errorf("expected nil session. got %#v", session)
|
||||
@ -677,29 +689,31 @@ func TestProcessCookieNoCookieError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProcessCookieRefreshNotSet(t *testing.T) {
|
||||
pcTest := NewProcessCookieTestWithDefaults()
|
||||
pcTest.proxy.CookieExpire = time.Duration(23) * time.Hour
|
||||
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
|
||||
opts.CookieExpire = time.Duration(23) * time.Hour
|
||||
})
|
||||
reference := time.Now().Add(time.Duration(-2) * time.Hour)
|
||||
|
||||
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
||||
pcTest.SaveSession(startSession, reference)
|
||||
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
|
||||
pcTest.SaveSession(startSession)
|
||||
|
||||
session, age, err := pcTest.LoadCookiedSession()
|
||||
session, err := pcTest.LoadCookiedSession()
|
||||
assert.Equal(t, nil, err)
|
||||
if age < time.Duration(-2)*time.Hour {
|
||||
t.Errorf("cookie too young %v", age)
|
||||
if session.Age() < time.Duration(-2)*time.Hour {
|
||||
t.Errorf("cookie too young %v", session.Age())
|
||||
}
|
||||
assert.Equal(t, startSession.Email, session.Email)
|
||||
}
|
||||
|
||||
func TestProcessCookieFailIfCookieExpired(t *testing.T) {
|
||||
pcTest := NewProcessCookieTestWithDefaults()
|
||||
pcTest.proxy.CookieExpire = time.Duration(24) * time.Hour
|
||||
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
|
||||
opts.CookieExpire = time.Duration(24) * time.Hour
|
||||
})
|
||||
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
|
||||
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
||||
pcTest.SaveSession(startSession, reference)
|
||||
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
|
||||
pcTest.SaveSession(startSession)
|
||||
|
||||
session, _, err := pcTest.LoadCookiedSession()
|
||||
session, err := pcTest.LoadCookiedSession()
|
||||
assert.NotEqual(t, nil, err)
|
||||
if session != nil {
|
||||
t.Errorf("expected nil session %#v", session)
|
||||
@ -707,22 +721,23 @@ func TestProcessCookieFailIfCookieExpired(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
|
||||
pcTest := NewProcessCookieTestWithDefaults()
|
||||
pcTest.proxy.CookieExpire = time.Duration(24) * time.Hour
|
||||
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
|
||||
opts.CookieExpire = time.Duration(24) * time.Hour
|
||||
})
|
||||
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
|
||||
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
||||
pcTest.SaveSession(startSession, reference)
|
||||
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
|
||||
pcTest.SaveSession(startSession)
|
||||
|
||||
pcTest.proxy.CookieRefresh = time.Hour
|
||||
session, _, err := pcTest.LoadCookiedSession()
|
||||
session, err := pcTest.LoadCookiedSession()
|
||||
assert.NotEqual(t, nil, err)
|
||||
if session != nil {
|
||||
t.Errorf("expected nil session %#v", session)
|
||||
}
|
||||
}
|
||||
|
||||
func NewAuthOnlyEndpointTest() *ProcessCookieTest {
|
||||
pcTest := NewProcessCookieTestWithDefaults()
|
||||
func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) *ProcessCookieTest {
|
||||
pcTest := NewProcessCookieTestWithOptionsModifiers(modifiers...)
|
||||
pcTest.req, _ = http.NewRequest("GET",
|
||||
pcTest.opts.ProxyPrefix+"/auth", nil)
|
||||
return pcTest
|
||||
@ -731,8 +746,8 @@ func NewAuthOnlyEndpointTest() *ProcessCookieTest {
|
||||
func TestAuthOnlyEndpointAccepted(t *testing.T) {
|
||||
test := NewAuthOnlyEndpointTest()
|
||||
startSession := &sessions.SessionState{
|
||||
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
||||
test.SaveSession(startSession, time.Now())
|
||||
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()}
|
||||
test.SaveSession(startSession)
|
||||
|
||||
test.proxy.ServeHTTP(test.rw, test.req)
|
||||
assert.Equal(t, http.StatusAccepted, test.rw.Code)
|
||||
@ -750,12 +765,13 @@ func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
|
||||
test := NewAuthOnlyEndpointTest()
|
||||
test.proxy.CookieExpire = time.Duration(24) * time.Hour
|
||||
test := NewAuthOnlyEndpointTest(func(opts *Options) {
|
||||
opts.CookieExpire = time.Duration(24) * time.Hour
|
||||
})
|
||||
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
|
||||
startSession := &sessions.SessionState{
|
||||
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
||||
test.SaveSession(startSession, reference)
|
||||
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
|
||||
test.SaveSession(startSession)
|
||||
|
||||
test.proxy.ServeHTTP(test.rw, test.req)
|
||||
assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
|
||||
@ -766,8 +782,8 @@ func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
|
||||
func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
|
||||
test := NewAuthOnlyEndpointTest()
|
||||
startSession := &sessions.SessionState{
|
||||
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
||||
test.SaveSession(startSession, time.Now())
|
||||
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()}
|
||||
test.SaveSession(startSession)
|
||||
test.validateUser = false
|
||||
|
||||
test.proxy.ServeHTTP(test.rw, test.req)
|
||||
@ -797,8 +813,8 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
|
||||
pcTest.opts.ProxyPrefix+"/auth", nil)
|
||||
|
||||
startSession := &sessions.SessionState{
|
||||
User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token"}
|
||||
pcTest.SaveSession(startSession, time.Now())
|
||||
User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: time.Now()}
|
||||
pcTest.SaveSession(startSession)
|
||||
|
||||
pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
|
||||
assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
|
||||
@ -1068,7 +1084,12 @@ func TestAjaxForbiddendRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClearSplitCookie(t *testing.T) {
|
||||
p := OAuthProxy{CookieName: "oauth2", CookieDomain: "abc"}
|
||||
opts := NewOptions()
|
||||
opts.CookieName = "oauth2"
|
||||
opts.CookieDomain = "abc"
|
||||
store, err := cookie.NewCookieSessionStore(opts.SessionOptions.CookieStoreOptions, &opts.CookieOptions)
|
||||
assert.Equal(t, err, nil)
|
||||
p := OAuthProxy{CookieName: opts.CookieName, CookieDomain: opts.CookieDomain, sessionStore: store}
|
||||
var rw = httptest.NewRecorder()
|
||||
req := httptest.NewRequest("get", "/", nil)
|
||||
|
||||
@ -1092,7 +1113,12 @@ func TestClearSplitCookie(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClearSingleCookie(t *testing.T) {
|
||||
p := OAuthProxy{CookieName: "oauth2", CookieDomain: "abc"}
|
||||
opts := NewOptions()
|
||||
opts.CookieName = "oauth2"
|
||||
opts.CookieDomain = "abc"
|
||||
store, err := cookie.NewCookieSessionStore(opts.SessionOptions.CookieStoreOptions, &opts.CookieOptions)
|
||||
assert.Equal(t, err, nil)
|
||||
p := OAuthProxy{CookieName: opts.CookieName, CookieDomain: opts.CookieDomain, sessionStore: store}
|
||||
var rw = httptest.NewRecorder()
|
||||
req := httptest.NewRequest("get", "/", nil)
|
||||
|
||||
|
@ -40,11 +40,14 @@ type SessionStore struct {
|
||||
// Save takes a sessions.SessionState and stores the information from it
|
||||
// within Cookies set on the HTTP response writer
|
||||
func (s *SessionStore) Save(rw http.ResponseWriter, req *http.Request, ss *sessions.SessionState) error {
|
||||
if ss.CreatedAt.IsZero() {
|
||||
ss.CreatedAt = time.Now()
|
||||
}
|
||||
value, err := utils.CookieForSession(ss, s.CookieCipher)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.setSessionCookie(rw, req, value)
|
||||
s.setSessionCookie(rw, req, value, ss.CreatedAt)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -89,8 +92,8 @@ func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
|
||||
}
|
||||
|
||||
// setSessionCookie adds the user's session cookie to the response
|
||||
func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
|
||||
for _, c := range s.makeSessionCookie(req, val, s.CookieExpire, time.Now()) {
|
||||
func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Request, val string, created time.Time) {
|
||||
for _, c := range s.makeSessionCookie(req, val, s.CookieExpire, created) {
|
||||
http.SetCookie(rw, c)
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -72,6 +74,16 @@ var _ = Describe("NewSessionStore", func() {
|
||||
}
|
||||
})
|
||||
|
||||
It("have a signature timestamp matching session.CreatedAt", func() {
|
||||
for _, cookie := range cookies {
|
||||
if cookie.Value != "" {
|
||||
parts := strings.Split(cookie.Value, "|")
|
||||
Expect(parts).To(HaveLen(3))
|
||||
Expect(parts[1]).To(Equal(strconv.Itoa(int(session.CreatedAt.Unix()))))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@ -86,6 +98,10 @@ var _ = Describe("NewSessionStore", func() {
|
||||
Expect(response.Header().Get("set-cookie")).ToNot(BeEmpty())
|
||||
})
|
||||
|
||||
It("Ensures the session CreatedAt is not zero", func() {
|
||||
Expect(session.CreatedAt.IsZero()).To(BeFalse())
|
||||
})
|
||||
|
||||
CheckCookieOptions()
|
||||
})
|
||||
|
||||
@ -138,12 +154,15 @@ var _ = Describe("NewSessionStore", func() {
|
||||
|
||||
// Can't compare time.Time using Equal() so remove ExpiresOn from sessions
|
||||
l := *loadedSession
|
||||
l.CreatedAt = time.Time{}
|
||||
l.ExpiresOn = time.Time{}
|
||||
s := *session
|
||||
s.CreatedAt = time.Time{}
|
||||
s.ExpiresOn = time.Time{}
|
||||
Expect(l).To(Equal(s))
|
||||
|
||||
// Compare time.Time separately
|
||||
Expect(loadedSession.CreatedAt.Equal(session.CreatedAt)).To(BeTrue())
|
||||
Expect(loadedSession.ExpiresOn.Equal(session.ExpiresOn)).To(BeTrue())
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user