@@ -16,7 +16,7 @@ import (
1616 "github.com/mbland/hmacauth"
1717 "github.com/pusher/oauth2_proxy/cookie"
1818 "github.com/pusher/oauth2_proxy/logger"
19− "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
19+ sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
2020 "github.com/pusher/oauth2_proxy/providers"
2121 "github.com/yhat/wsutil"
2222)
@@ -29,10 +29,6 @@ const (
2929 httpScheme = "http"
3030 httpsScheme = "https"
3131
32− // Cookies are limited to 4kb including the length of the cookie name,
33− // the cookie name can be up to 256 bytes
34− maxCookieLength = 3840
35−
3632 applicationJSON = "application/json"
3733)
3834
@@ -75,6 +71,7 @@ type OAuthProxy struct {
7571 redirectURL *url.URL // the url to receive requests at
7672 whitelistDomains []string
7773 provider providers.Provider
74+ sessionStore sessionsapi.SessionStore
7875 ProxyPrefix string
7976 SignInMessage string
8077 HtpasswdFile *HtpasswdFile
@@ -88,7 +85,6 @@ type OAuthProxy struct {
8885 PassAccessToken bool
8986 SetAuthorization bool
9087 PassAuthorization bool
91− CookieCipher *cookie.Cipher
9288 skipAuthRegex []string
9389 skipAuthPreflight bool
9490 compiledRegex []*regexp.Regexp
@@ -218,15 +214,6 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
218214
219215 logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s path:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, opts.CookiePath, refresh)
220216
221− var cipher *cookie.Cipher
222− if opts.PassAccessToken || opts.SetAuthorization || opts.PassAuthorization || (opts.CookieRefresh != time.Duration(0)) {
223− var err error
224− cipher, err = cookie.NewCipher(secretBytes(opts.CookieSecret))
225− if err != nil {
226− logger.Fatal("cookie-secret error: ", err)
227− }
228− }
229−
230217 return &OAuthProxy{
231218 CookieName: opts.CookieName,
232219 CSRFCookieName: fmt.Sprintf("%v_%v", opts.CookieName, "csrf"),
@@ -249,6 +236,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
249236
250237 ProxyPrefix: opts.ProxyPrefix,
251238 provider: opts.provider,
239+ sessionStore: opts.sessionStore,
252240 serveMux: serveMux,
253241 redirectURL: redirectURL,
254242 whitelistDomains: opts.WhitelistDomains,
@@ -263,7 +251,6 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
263251 SetAuthorization: opts.SetAuthorization,
264252 PassAuthorization: opts.PassAuthorization,
265253 SkipProviderButton: opts.SkipProviderButton,
266− CookieCipher: cipher,
267254 templates: loadTemplates(opts.CustomTemplatesDir),
268255 Footer: opts.Footer,
269256 }
@@ -293,7 +280,7 @@ func (p *OAuthProxy) displayCustomLoginForm() bool {
293280 return p.HtpasswdFile != nil && p.DisplayHtpasswdForm
294281}
295282
296−func (p *OAuthProxy) redeemCode(host, code string) (s *sessions.SessionState, err error) {
283+func (p *OAuthProxy) redeemCode(host, code string) (s *sessionsapi.SessionState, err error) {
297284 if code == "" {
298285 return nil, errors.New("missing code")
299286 }
@@ -316,104 +303,6 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *sessions.SessionState, er
316303 return
317304}
318305
319−// MakeSessionCookie creates an http.Cookie containing the authenticated user's
320−// authentication details
321−func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) []*http.Cookie {
322− if value != "" {
323− value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
324− }
325− c := p.makeCookie(req, p.CookieName, value, expiration, now)
326− if len(c.Value) > 4096-len(p.CookieName) {
327− return splitCookie(c)
328− }
329− return []*http.Cookie{c}
330−}
331−
332−func copyCookie(c *http.Cookie) *http.Cookie {
333− return &http.Cookie{
334− Name: c.Name,
335− Value: c.Value,
336− Path: c.Path,
337− Domain: c.Domain,
338− Expires: c.Expires,
339− RawExpires: c.RawExpires,
340− MaxAge: c.MaxAge,
341− Secure: c.Secure,
342− HttpOnly: c.HttpOnly,
343− Raw: c.Raw,
344− Unparsed: c.Unparsed,
345− }
346−}
347−
348−// splitCookie reads the full cookie generated to store the session and splits
349−// it into a slice of cookies which fit within the 4kb cookie limit indexing
350−// the cookies from 0
351−func splitCookie(c *http.Cookie) []*http.Cookie {
352− if len(c.Value) < maxCookieLength {
353− return []*http.Cookie{c}
354− }
355− cookies := []*http.Cookie{}
356− valueBytes := []byte(c.Value)
357− count := 0
358− for len(valueBytes) > 0 {
359− new := copyCookie(c)
360− new.Name = fmt.Sprintf("%s_%d", c.Name, count)
361− count++
362− if len(valueBytes) < maxCookieLength {
363− new.Value = string(valueBytes)
364− valueBytes = []byte{}
365− } else {
366− newValue := valueBytes[:maxCookieLength]
367− valueBytes = valueBytes[maxCookieLength:]
368− new.Value = string(newValue)
369− }
370− cookies = append(cookies, new)
371− }
372− return cookies
373−}
374−
375−// joinCookies takes a slice of cookies from the request and reconstructs the
376−// full session cookie
377−func joinCookies(cookies []*http.Cookie) (*http.Cookie, error) {
378− if len(cookies) == 0 {
379− return nil, fmt.Errorf("list of cookies must be > 0")
380− }
381− if len(cookies) == 1 {
382− return cookies[0], nil
383− }
384− c := copyCookie(cookies[0])
385− for i := 1; i < len(cookies); i++ {
386− c.Value += cookies[i].Value
387− }
388− c.Name = strings.TrimRight(c.Name, "_0")
389− return c, nil
390−}
391−
392−// loadCookie retreieves the sessions state cookie from the http request.
393−// If a single cookie is present this will be returned, otherwise it attempts
394−// to reconstruct a cookie split up by splitCookie
395−func loadCookie(req *http.Request, cookieName string) (*http.Cookie, error) {
396− c, err := req.Cookie(cookieName)
397− if err == nil {
398− return c, nil
399− }
400− cookies := []*http.Cookie{}
401− err = nil
402− count := 0
403− for err == nil {
404− var c *http.Cookie
405− c, err = req.Cookie(fmt.Sprintf("%s_%d", cookieName, count))
406− if err == nil {
407− cookies = append(cookies, c)
408− count++
409− }
410− }
411− if len(cookies) == 0 {
412− return nil, fmt.Errorf("Could not find cookie %s", cookieName)
413− }
414− return joinCookies(cookies)
415−}
416−
417306// MakeCSRFCookie creates a cookie for CSRF
418307func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
419308 return p.makeCookie(req, p.CSRFCookieName, value, expiration, now)
@@ -454,66 +343,18 @@ func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, va
454343
455344// ClearSessionCookie creates a cookie to unset the user's authentication cookie
456345// stored in the user's session
457−func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) {
458− var cookies []*http.Cookie
459−
460− // matches CookieName, CookieName_<number>
461− var cookieNameRegex = regexp.MustCompile(fmt.Sprintf("^%s(_\\d+)?$", p.CookieName))
462−
463− for _, c := range req.Cookies() {
464− if cookieNameRegex.MatchString(c.Name) {
465− clearCookie := p.makeCookie(req, c.Name, "", time.Hour*-1, time.Now())
466−
467− http.SetCookie(rw, clearCookie)
468− cookies = append(cookies, clearCookie)
469− }
470− }
471−
472− // ugly hack because default domain changed
473− if p.CookieDomain == "" && len(cookies) > 0 {
474− clr2 := *cookies[0]
475− clr2.Domain = req.Host
476− http.SetCookie(rw, &clr2)
477− }
478−}
479−
480−// SetSessionCookie adds the user's session cookie to the response
481−func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
482− for _, c := range p.MakeSessionCookie(req, val, p.CookieExpire, time.Now()) {
483− http.SetCookie(rw, c)
484− }
346+func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) error {
347+ return p.sessionStore.Clear(rw, req)
485348}
486349
487350// LoadCookiedSession reads the user's authentication details from the request
488−func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*sessions.SessionState, time.Duration, error) {
489− var age time.Duration
490− c, err := loadCookie(req, p.CookieName)
491− if err != nil {
492− // always http.ErrNoCookie
493− return nil, age, fmt.Errorf("Cookie %q not present", p.CookieName)
494− }
495− val, timestamp, ok := cookie.Validate(c, p.CookieSeed, p.CookieExpire)
496− if !ok {
497− return nil, age, errors.New("Cookie Signature not valid")
498− }
499−
500− session, err := p.provider.SessionFromCookie(val, p.CookieCipher)
501− if err != nil {
502− return nil, age, err
503− }
504−
505− age = time.Now().Truncate(time.Second).Sub(timestamp)
506− return session, age, nil
351+func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*sessionsapi.SessionState, error) {
352+ return p.sessionStore.Load(req)
507353}
508354
509355// SaveSession creates a new session cookie value and sets this on the response
510−func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error {
511− value, err := p.provider.CookieForSession(s, p.CookieCipher)
512− if err != nil {
513− return err
514− }
515− p.SetSessionCookie(rw, req, value)
516− return nil
356+func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *sessionsapi.SessionState) error {
357+ return p.sessionStore.Save(rw, req, s)
517358}
518359
519360// RobotsTxt disallows scraping pages from the OAuthProxy
@@ -694,7 +535,7 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
694535
695536 user, ok := p.ManualSignIn(rw, req)
696537 if ok {
697− session := &sessions.SessionState{User: user}
538+ session := &sessionsapi.SessionState{User: user}
698539 p.SaveSession(rw, req, session)
699540 http.Redirect(rw, req, redirect, 302)
700541 } else {
@@ -833,12 +674,12 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
833674 var saveSession, clearSession, revalidated bool
834675 remoteAddr := getRemoteAddr(req)
835676
836− session, sessionAge, err := p.LoadCookiedSession(req)
677+ session, err := p.LoadCookiedSession(req)
837678 if err != nil {
838679 logger.Printf("Error loading cookied session: %s", err)
839680 }
840− if session != nil && sessionAge > p.CookieRefresh && p.CookieRefresh != time.Duration(0) {
841− logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", sessionAge, session, p.CookieRefresh)
681+ if session != nil && session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) {
682+ logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh)
842683 saveSession = true
843684 }
844685
@@ -945,7 +786,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
945786
946787// CheckBasicAuth checks the requests Authorization header for basic auth
947788// credentials and authenticates these against the proxies HtpasswdFile
948−func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessions.SessionState, error) {
789+func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessionsapi.SessionState, error) {
949790 if p.HtpasswdFile == nil {
950791 return nil, nil
951792 }
@@ -967,7 +808,7 @@ func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessions.SessionState,
967808 }
968809 if p.HtpasswdFile.Validate(pair[0], pair[1]) {
969810 logger.PrintAuthf(pair[0], req, logger.AuthSuccess, "Authenticated via basic auth and HTpasswd File")
970− return &sessions.SessionState{User: pair[0]}, nil
811+ return &sessionsapi.SessionState{User: pair[0]}, nil
971812 }
972813 logger.PrintAuthf(pair[0], req, logger.AuthFailure, "Invalid authentication via basic auth: not in Htpasswd File")
973814 return nil, nil