Implement SaveSession in Cookie SessionStore

This commit is contained in:
Joel Speed 2019-05-07 12:18:23 +01:00
parent 15a2cf8b9e
commit b965f25c10
No known key found for this signature in database
GPG Key ID: 6E80578D6751DEFB
2 changed files with 82 additions and 4 deletions

View File

@ -15,6 +15,12 @@ import (
"github.com/pusher/oauth2_proxy/pkg/sessions/utils"
)
const (
// Cookies are limited to 4kb including the length of the cookie name,
// the cookie name can be up to 256 bytes
maxCookieLength = 3840
)
// Ensure CookieSessionStore implements the interface
var _ sessions.SessionStore = &SessionStore{}
@ -34,7 +40,12 @@ type SessionStore struct {
// SaveSession takes a sessions.SessionState and stores the information from it
// within Cookies set on the HTTP response writer
func (s *SessionStore) SaveSession(rw http.ResponseWriter, req *http.Request, ss *sessions.SessionState) error {
return fmt.Errorf("method not implemented")
value, err := utils.CookieForSession(ss, s.CookieCipher)
if err != nil {
return err
}
s.setSessionCookie(rw, req, value)
return nil
}
// LoadSession reads sessions.SessionState information from Cookies within the
@ -77,6 +88,26 @@ func (s *SessionStore) ClearSession(rw http.ResponseWriter, req *http.Request) e
return nil
}
// 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()) {
http.SetCookie(rw, c)
}
}
// makeSessionCookie creates an http.Cookie containing the authenticated user's
// authentication details
func (s *SessionStore) makeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) []*http.Cookie {
if value != "" {
value = cookie.SignedValue(s.CookieSecret, s.CookieName, value, now)
}
c := s.makeCookie(req, s.CookieName, value, expiration)
if len(c.Value) > 4096-len(s.CookieName) {
return splitCookie(c)
}
return []*http.Cookie{c}
}
func (s *SessionStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration) *http.Cookie {
return cookies.MakeCookie(
req,
@ -115,6 +146,33 @@ func NewCookieSessionStore(opts options.CookieStoreOptions, cookieOpts *options.
}, nil
}
// splitCookie reads the full cookie generated to store the session and splits
// it into a slice of cookies which fit within the 4kb cookie limit indexing
// the cookies from 0
func splitCookie(c *http.Cookie) []*http.Cookie {
if len(c.Value) < maxCookieLength {
return []*http.Cookie{c}
}
cookies := []*http.Cookie{}
valueBytes := []byte(c.Value)
count := 0
for len(valueBytes) > 0 {
new := copyCookie(c)
new.Name = fmt.Sprintf("%s_%d", c.Name, count)
count++
if len(valueBytes) < maxCookieLength {
new.Value = string(valueBytes)
valueBytes = []byte{}
} else {
newValue := valueBytes[:maxCookieLength]
valueBytes = valueBytes[maxCookieLength:]
new.Value = string(newValue)
}
cookies = append(cookies, new)
}
return cookies
}
// loadCookie retreieves the sessions state cookie from the http request.
// If a single cookie is present this will be returned, otherwise it attempts
// to reconstruct a cookie split up by splitCookie

View File

@ -89,7 +89,7 @@ var _ = Describe("NewSessionStore", func() {
})
It("sets a `set-cookie` header in the response", func() {
Expect(response.Header().Get("Set-Cookie")).ToNot(BeEmpty())
Expect(response.Header().Get("set-cookie")).ToNot(BeEmpty())
})
CheckCookieOptions()
@ -144,7 +144,7 @@ var _ = Describe("NewSessionStore", func() {
})
It("sets a `set-cookie` header in the response", func() {
Expect(response.Header().Get("Set-Cookie")).ToNot(BeEmpty())
Expect(response.Header().Get("set-cookie")).ToNot(BeEmpty())
})
CheckCookieOptions()
@ -152,12 +152,23 @@ var _ = Describe("NewSessionStore", func() {
Context("when ClearSession is called", func() {
BeforeEach(func() {
cookie := cookies.MakeCookie(request,
cookieOpts.CookieName,
"foo",
cookieOpts.CookiePath,
cookieOpts.CookieDomain,
cookieOpts.CookieHTTPOnly,
cookieOpts.CookieSecure,
cookieOpts.CookieExpire,
time.Now(),
)
request.AddCookie(cookie)
err := ss.ClearSession(response, request)
Expect(err).ToNot(HaveOccurred())
})
It("sets a `set-cookie` header in the response", func() {
Expect(response.Header().Get("set-cookie")).ToNot(BeEmpty())
Expect(response.Header().Get("Set-Cookie")).ToNot(BeEmpty())
})
CheckCookieOptions()
@ -178,6 +189,15 @@ var _ = Describe("NewSessionStore", func() {
CookieHTTPOnly: true,
}
session = &sessionsapi.SessionState{
AccessToken: "AccessToken",
IDToken: "IDToken",
ExpiresOn: time.Now().Add(1 * time.Hour),
RefreshToken: "RefreshToken",
Email: "john.doe@example.com",
User: "john.doe",
}
request = httptest.NewRequest("GET", "http://example.com/", nil)
response = httptest.NewRecorder()
})