@@ -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
@@ -76,6 +80,8 @@ type OAuthProxy struct {
7680 PassUserHeaders bool
7781 BasicAuthPassword string
7882 PassAccessToken bool
83+ SetAuthorization bool
84+ PassAuthorization bool
7985 CookieCipher *cookie.Cipher
8086 skipAuthRegex []string
8187 skipAuthPreflight bool
@@ -183,7 +189,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
183189 log.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, refresh)
184190
185191 var cipher *cookie.Cipher
186− if opts.PassAccessToken || (opts.CookieRefresh != time.Duration(0)) {
192+ if opts.PassAccessToken || opts.SetAuthorization || opts.PassAuthorization || (opts.CookieRefresh != time.Duration(0)) {
187193 var err error
188194 cipher, err = cookie.NewCipher(secretBytes(opts.CookieSecret))
189195 if err != nil {
@@ -222,6 +228,8 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
222228 PassUserHeaders: opts.PassUserHeaders,
223229 BasicAuthPassword: opts.BasicAuthPassword,
224230 PassAccessToken: opts.PassAccessToken,
231+ SetAuthorization: opts.SetAuthorization,
232+ PassAuthorization: opts.PassAuthorization,
225233 SkipProviderButton: opts.SkipProviderButton,
226234 CookieCipher: cipher,
227235 templates: loadTemplates(opts.CustomTemplatesDir),
@@ -278,15 +286,100 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *providers.SessionState, e
278286
279287// MakeSessionCookie creates an http.Cookie containing the authenticated user's
280288// authentication details
281−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 {
282290 if value != "" {
283291 value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
284− if len(value) > 4096 {
285− // Cookies cannot be larger than 4kb
286− 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-len(p.CookieName) {
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++
287377 }
288378 }
289− 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)
290383}
291384
292385// MakeCSRFCookie creates a cookie for CSRF
@@ -330,12 +423,14 @@ func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, va
330423// ClearSessionCookie creates a cookie to unset the user's authentication cookie
331424// stored in the user's session
332425func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) {
333− clr := p.MakeSessionCookie(req, "", time.Hour*-1, time.Now())
334− 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+ }
335430
336431 // ugly hack because default domain changed
337− if p.CookieDomain == "" {
338− clr2 := *clr
432+ if p.CookieDomain == "" && len(cookies) > 0 {
433+ clr2 := *cookies[0]
339434 clr2.Domain = req.Host
340435 http.SetCookie(rw, &clr2)
341436 }
@@ -343,13 +438,15 @@ func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Reques
343438
344439// SetSessionCookie adds the user's session cookie to the response
345440func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
346− 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+ }
347444}
348445
349446// LoadCookiedSession reads the user's authentication details from the request
350447func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionState, time.Duration, error) {
351448 var age time.Duration
352− c, err := req.Cookie(p.CookieName)
449+ c, err := loadCookie(req, p.CookieName)
353450 if err != nil {
354451 // always http.ErrNoCookie
355452 return nil, age, fmt.Errorf("Cookie %q not present", p.CookieName)
@@ -750,6 +847,12 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
750847 if p.PassAccessToken && session.AccessToken != "" {
751848 req.Header["X-Forwarded-Access-Token"] = []string{session.AccessToken}
752849 }
850+ if p.PassAuthorization && session.IDToken != "" {
851+ req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", session.IDToken)}
852+ }
853+ if p.SetAuthorization && session.IDToken != "" {
854+ rw.Header().Set("Authorization", fmt.Sprintf("Bearer %s", session.IDToken))
855+ }
753856 if session.Email == "" {
754857 rw.Header().Set("GAP-Auth", session.User)
755858 } else {