@@ -26,6 +26,10 @@ const (
2626
2727 httpScheme = "http"
2828 httpsScheme = "https"
29+
30+ // Cookies are limited to 4kb including the length of the cookie name,
31+ // the cookie name can be up to 256 bytes
32+ maxCookieLength = 3840
2933)
3034
3135// SignatureHeaders contains the headers to be signed by the hmac algorithm
@@ -282,15 +286,100 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *providers.SessionState, e
282286
283287// MakeSessionCookie creates an http.Cookie containing the authenticated user's
284288// authentication details
285−func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
289+func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) []*http.Cookie {
286290 if value != "" {
287291 value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
288− if len(value) > 4096 {
289− // Cookies cannot be larger than 4kb
290− log.Printf("WARNING - Cookie Size: %d bytes", len(value))
292+ }
293+ c := p.makeCookie(req, p.CookieName, value, expiration, now)
294+ if len(c.Value) > 4096 {
295+ return splitCookie(c)
296+ }
297+ return []*http.Cookie{c}
298+}
299+
300+func copyCookie(c *http.Cookie) *http.Cookie {
301+ return &http.Cookie{
302+ Name: c.Name,
303+ Value: c.Value,
304+ Path: c.Path,
305+ Domain: c.Domain,
306+ Expires: c.Expires,
307+ RawExpires: c.RawExpires,
308+ MaxAge: c.MaxAge,
309+ Secure: c.Secure,
310+ HttpOnly: c.HttpOnly,
311+ Raw: c.Raw,
312+ Unparsed: c.Unparsed,
313+ }
314+}
315+
316+// splitCookie reads the full cookie generated to store the session and splits
317+// it into a slice of cookies which fit within the 4kb cookie limit indexing
318+// the cookies from 0
319+func splitCookie(c *http.Cookie) []*http.Cookie {
320+ if len(c.Value) < maxCookieLength {
321+ return []*http.Cookie{c}
322+ }
323+ cookies := []*http.Cookie{}
324+ valueBytes := []byte(c.Value)
325+ count := 0
326+ for len(valueBytes) > 0 {
327+ new := copyCookie(c)
328+ new.Name = fmt.Sprintf("%s-%d", c.Name, count)
329+ count++
330+ if len(valueBytes) < maxCookieLength {
331+ new.Value = string(valueBytes)
332+ valueBytes = []byte{}
333+ } else {
334+ newValue := valueBytes[:maxCookieLength]
335+ valueBytes = valueBytes[maxCookieLength:]
336+ new.Value = string(newValue)
337+ }
338+ cookies = append(cookies, new)
339+ }
340+ return cookies
341+}
342+
343+// joinCookies takes a slice of cookies from the request and reconstructs the
344+// full session cookie
345+func joinCookies(cookies []*http.Cookie) (*http.Cookie, error) {
346+ if len(cookies) == 0 {
347+ return nil, fmt.Errorf("list of cookies must be > 0")
348+ }
349+ if len(cookies) == 1 {
350+ return cookies[0], nil
351+ }
352+ c := copyCookie(cookies[0])
353+ for i := 1; i < len(cookies); i++ {
354+ c.Value += cookies[i].Value
355+ }
356+ c.Name = strings.TrimRight(c.Name, "-0")
357+ return c, nil
358+}
359+
360+// loadCookie retreieves the sessions state cookie from the http request.
361+// If a single cookie is present this will be returned, otherwise it attempts
362+// to reconstruct a cookie split up by splitCookie
363+func loadCookie(req *http.Request, cookieName string) (*http.Cookie, error) {
364+ c, err := req.Cookie(cookieName)
365+ if err == nil {
366+ return c, nil
367+ }
368+ cookies := []*http.Cookie{}
369+ err = nil
370+ count := 0
371+ for err == nil {
372+ var c *http.Cookie
373+ c, err = req.Cookie(fmt.Sprintf("%s-%d", cookieName, count))
374+ if err == nil {
375+ cookies = append(cookies, c)
376+ count++
291377 }
292378 }
293− return p.makeCookie(req, p.CookieName, value, expiration, now)
379+ if len(cookies) == 0 {
380+ return nil, fmt.Errorf("Could not find cookie %s", cookieName)
381+ }
382+ return joinCookies(cookies)
294383}
295384
296385// MakeCSRFCookie creates a cookie for CSRF
@@ -334,12 +423,14 @@ func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, va
334423// ClearSessionCookie creates a cookie to unset the user's authentication cookie
335424// stored in the user's session
336425func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) {
337− clr := p.MakeSessionCookie(req, "", time.Hour*-1, time.Now())
338− http.SetCookie(rw, clr)
426+ cookies := p.MakeSessionCookie(req, "", time.Hour*-1, time.Now())
427+ for _, clr := range cookies {
428+ http.SetCookie(rw, clr)
429+ }
339430
340431 // ugly hack because default domain changed
341− if p.CookieDomain == "" {
342− clr2 := *clr
432+ if p.CookieDomain == "" && len(cookies) > 0 {
433+ clr2 := *cookies[0]
343434 clr2.Domain = req.Host
344435 http.SetCookie(rw, &clr2)
345436 }
@@ -347,13 +438,15 @@ func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Reques
347438
348439// SetSessionCookie adds the user's session cookie to the response
349440func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
350− http.SetCookie(rw, p.MakeSessionCookie(req, val, p.CookieExpire, time.Now()))
441+ for _, c := range p.MakeSessionCookie(req, val, p.CookieExpire, time.Now()) {
442+ http.SetCookie(rw, c)
443+ }
351444}
352445
353446// LoadCookiedSession reads the user's authentication details from the request
354447func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionState, time.Duration, error) {
355448 var age time.Duration
356− c, err := req.Cookie(p.CookieName)
449+ c, err := loadCookie(req, p.CookieName)
357450 if err != nil {
358451 // always http.ErrNoCookie
359452 return nil, age, fmt.Errorf("Cookie %q not present", p.CookieName)