@@ -1,8 +1,6 @@
11package main
22
33import (
4− "crypto/aes"
5− "crypto/cipher"
64 "encoding/base64"
75 "errors"
86 "fmt"
@@ -16,6 +14,7 @@ import (
1614 "strings"
1715 "time"
1816
17+ "github.com/bitly/oauth2_proxy/cookie"
1918 "github.com/bitly/oauth2_proxy/providers"
2019)
2120
@@ -44,7 +43,7 @@ type OauthProxy struct {
4443 serveMux http.Handler
4544 PassBasicAuth bool
4645 PassAccessToken bool
47− AesCipher cipher.Block
46+ CookieCipher *cookie.Cipher
4847 skipAuthRegex []string
4948 compiledRegex []*regexp.Regexp
5049 templates *template.Template
@@ -116,10 +115,10 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
116115
117116 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, domain, refresh)
118117
119− var aes_cipher cipher.Block
118+ var cipher *cookie.Cipher
120119 if opts.PassAccessToken || (opts.CookieRefresh != time.Duration(0)) {
121120 var err error
122− aes_cipher, err = aes.NewCipher([]byte(opts.CookieSecret))
121+ cipher, err = cookie.NewCipher(opts.CookieSecret)
123122 if err != nil {
124123 log.Fatal("error creating AES cipher with "+
125124 "cookie-secret ", opts.CookieSecret, ": ", err)
@@ -150,7 +149,7 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
150149 compiledRegex: opts.CompiledRegex,
151150 PassBasicAuth: opts.PassBasicAuth,
152151 PassAccessToken: opts.PassAccessToken,
153− AesCipher: aes_cipher,
152+ CookieCipher: cipher,
154153 templates: loadTemplates(opts.CustomTemplatesDir),
155154 }
156155}
@@ -177,22 +176,20 @@ func (p *OauthProxy) displayCustomLoginForm() bool {
177176 return p.HtpasswdFile != nil && p.DisplayHtpasswdForm
178177}
179178
180−func (p *OauthProxy) redeemCode(host, code string) (string, string, error) {
179+func (p *OauthProxy) redeemCode(host, code string) (s *providers.SessionState, err error) {
181180 if code == "" {
182− return "", "", errors.New("missing code")
181+ return nil, errors.New("missing code")
183182 }
184183 redirectUri := p.GetRedirectURI(host)
185− body, access_token, err := p.provider.Redeem(redirectUri, code)
184+ s, err = p.provider.Redeem(redirectUri, code)
186185 if err != nil {
187− return "", "", err
186+ return
188187 }
189188
190− email, err := p.provider.GetEmailAddress(body, access_token)
191− if err != nil {
192− return "", "", err
189+ if s.Email == "" {
190+ s.Email, err = p.provider.GetEmailAddress(s)
193191 }
194−
195− return access_token, email, nil
192+ return
196193}
197194
198195func (p *OauthProxy) MakeCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
@@ -208,9 +205,8 @@ func (p *OauthProxy) MakeCookie(req *http.Request, value string, expiration time
208205 }
209206
210207 if value != "" {
211− value = signedCookieValue(p.CookieSeed, p.CookieName, value, now)
208+ value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
212209 }
213−
214210 return &http.Cookie{
215211 Name: p.CookieName,
216212 Value: value,
@@ -230,35 +226,34 @@ func (p *OauthProxy) SetCookie(rw http.ResponseWriter, req *http.Request, val st
230226 http.SetCookie(rw, p.MakeCookie(req, val, p.CookieExpire, time.Now()))
231227}
232228
233−func (p *OauthProxy) ProcessCookie(rw http.ResponseWriter, req *http.Request) (email, user, access_token string, ok bool) {
234− var value string
235− var timestamp time.Time
236− cookie, err := req.Cookie(p.CookieName)
237− if err == nil {
238− value, timestamp, ok = validateCookie(cookie, p.CookieSeed, p.CookieExpire)
239− if ok {
240− email, user, access_token, err = parseCookieValue(value, p.AesCipher)
241− }
229+func (p *OauthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionState, time.Duration, error) {
230+ var age time.Duration
231+ c, err := req.Cookie(p.CookieName)
232+ if err != nil {
233+ // always http.ErrNoCookie
234+ return nil, age, fmt.Errorf("Cookie %q not present", p.CookieName)
235+ }
236+ val, timestamp, ok := cookie.Validate(c, p.CookieSeed, p.CookieExpire)
237+ if !ok {
238+ return nil, age, errors.New("Cookie Signature not valid")
242239 }
240+
241+ session, err := p.provider.SessionFromCookie(val, p.CookieCipher)
243242 if err != nil {
244− log.Printf(err.Error())
245− ok = false
246− } else if ok && p.CookieRefresh != time.Duration(0) {
247− refresh := timestamp.Add(p.CookieRefresh)
248− if refresh.Before(time.Now()) {
249− log.Printf("refreshing %s old session for %s (refresh after %s)", time.Now().Sub(timestamp), email, p.CookieRefresh)
250− ok = p.Validator(email)
251− log.Printf("re-validating %s valid:%v", email, ok)
252− if ok {
253− ok = p.provider.ValidateToken(access_token)
254− log.Printf("re-validating access token. valid:%v", ok)
255− }
256− if ok {
257− p.SetCookie(rw, req, value)
258− }
259− }
243+ return nil, age, err
260244 }
261− return
245+
246+ age = time.Now().Truncate(time.Second).Sub(timestamp)
247+ return session, age, nil
248+}
249+
250+func (p *OauthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *providers.SessionState) error {
251+ value, err := p.provider.CookieForSession(s, p.CookieCipher)
252+ if err != nil {
253+ return err
254+ }
255+ p.SetCookie(rw, req, value)
256+ return nil
262257}
263258
264259func (p *OauthProxy) RobotsTxt(rw http.ResponseWriter) {
@@ -344,156 +339,226 @@ func (p *OauthProxy) GetRedirect(req *http.Request) (string, error) {
344339 return redirect, err
345340}
346341
347−func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
348− // check if this is a redirect back at the end of oauth
349− remoteAddr := req.RemoteAddr
350− if req.Header.Get("X-Real-IP") != "" {
351− remoteAddr += fmt.Sprintf(" (%q)", req.Header.Get("X-Real-IP"))
342+func (p *OauthProxy) IsWhitelistedPath(path string) (ok bool) {
343+ for _, u := range p.compiledRegex {
344+ ok = u.MatchString(path)
345+ if ok {
346+ return
347+ }
352348 }
349+ return
350+}
353351
354− var ok bool
355− var user string
356− var email string
357− var access_token string
352+func getRemoteAddr(req *http.Request) (s string) {
353+ s = req.RemoteAddr
354+ if req.Header.Get("X-Real-IP") != "" {
355+ s += fmt.Sprintf(" (%q)", req.Header.Get("X-Real-IP"))
356+ }
357+ return
358+}
358359
359− if req.URL.Path == p.RobotsPath {
360+func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
361+ switch path := req.URL.Path; {
362+ case path == p.RobotsPath:
360363 p.RobotsTxt(rw)
361− return
364+ case path == p.PingPath:
365+ p.PingPage(rw)
366+ case p.IsWhitelistedPath(path):
367+ p.serveMux.ServeHTTP(rw, req)
368+ case path == p.SignInPath:
369+ p.SignIn(rw, req)
370+ case path == p.OauthStartPath:
371+ p.OauthStart(rw, req)
372+ case path == p.OauthCallbackPath:
373+ p.OauthCallback(rw, req)
374+ default:
375+ p.Proxy(rw, req)
362376 }
377+}
363378
364− if req.URL.Path == p.PingPath {
365− p.PingPage(rw)
379+func (p *OauthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
380+ redirect, err := p.GetRedirect(req)
381+ if err != nil {
382+ p.ErrorPage(rw, 500, "Internal Error", err.Error())
366383 return
367384 }
368385
369− for _, u := range p.compiledRegex {
370− match := u.MatchString(req.URL.Path)
371− if match {
372− p.serveMux.ServeHTTP(rw, req)
373− return
374− }
386+ user, ok := p.ManualSignIn(rw, req)
387+ if ok {
388+ session := &providers.SessionState{User: user}
389+ p.SaveSession(rw, req, session)
390+ http.Redirect(rw, req, redirect, 302)
391+ } else {
392+ p.SignInPage(rw, req, 200)
393+ }
394+}
375395
396+func (p *OauthProxy) OauthStart(rw http.ResponseWriter, req *http.Request) {
397+ redirect, err := p.GetRedirect(req)
398+ if err != nil {
399+ p.ErrorPage(rw, 500, "Internal Error", err.Error())
400+ return
376401 }
402+ redirectURI := p.GetRedirectURI(req.Host)
403+ http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, redirect), 302)
404+}
377405
378− if req.URL.Path == p.SignInPath {
379− redirect, err := p.GetRedirect(req)
380− if err != nil {
381− p.ErrorPage(rw, 500, "Internal Error", err.Error())
382− return
383− }
406+func (p *OauthProxy) OauthCallback(rw http.ResponseWriter, req *http.Request) {
407+ remoteAddr := getRemoteAddr(req)
384408
385− user, ok = p.ManualSignIn(rw, req)
386− if ok {
387− p.SetCookie(rw, req, user)
388− http.Redirect(rw, req, redirect, 302)
389− } else {
390− p.SignInPage(rw, req, 200)
391− }
409+ // finish the oauth cycle
410+ err := req.ParseForm()
411+ if err != nil {
412+ p.ErrorPage(rw, 500, "Internal Error", err.Error())
392413 return
393414 }
394− if req.URL.Path == p.OauthStartPath {
395− redirect, err := p.GetRedirect(req)
396− if err != nil {
397− p.ErrorPage(rw, 500, "Internal Error", err.Error())
398− return
399− }
400− redirectURI := p.GetRedirectURI(req.Host)
401− http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, redirect), 302)
415+ errorString := req.Form.Get("error")
416+ if errorString != "" {
417+ p.ErrorPage(rw, 403, "Permission Denied", errorString)
402418 return
403419 }
404− if req.URL.Path == p.OauthCallbackPath {
405− // finish the oauth cycle
406− err := req.ParseForm()
407− if err != nil {
408− p.ErrorPage(rw, 500, "Internal Error", err.Error())
409− return
410− }
411− errorString := req.Form.Get("error")
412− if errorString != "" {
413− p.ErrorPage(rw, 403, "Permission Denied", errorString)
414− return
415− }
416420
417− access_token, email, err = p.redeemCode(req.Host, req.Form.Get("code"))
421+ session, err := p.redeemCode(req.Host, req.Form.Get("code"))
422+ if err != nil {
423+ log.Printf("%s error redeeming code %s", remoteAddr, err)
424+ p.ErrorPage(rw, 500, "Internal Error", "Internal Error")
425+ return
426+ }
427+
428+ redirect := req.Form.Get("state")
429+ if redirect == "" {
430+ redirect = "/"
431+ }
432+
433+ // set cookie, or deny
434+ if p.Validator(session.Email) {
435+ log.Printf("%s authentication complete %s", remoteAddr, session)
436+ err := p.SaveSession(rw, req, session)
418437 if err != nil {
419− log.Printf("%s error redeeming code %s", remoteAddr, err)
420− p.ErrorPage(rw, 500, "Internal Error", err.Error())
438+ log.Printf("%s %s", remoteAddr, err)
439+ p.ErrorPage(rw, 500, "Internal Error", "Internal Error")
421440 return
422441 }
442+ http.Redirect(rw, req, redirect, 302)
443+ } else {
444+ log.Printf("%s Permission Denied: %q is unauthorized", remoteAddr, session.Email)
445+ p.ErrorPage(rw, 403, "Permission Denied", "Invalid Account")
446+ }
447+}
423448
424− redirect := req.Form.Get("state")
425− if redirect == "" {
426− redirect = "/"
449+func (p *OauthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
450+ var saveSession, clearSession, revalidated bool
451+ remoteAddr := getRemoteAddr(req)
452+
453+ session, sessionAge, err := p.LoadCookiedSession(req)
454+ if err != nil {
455+ log.Printf("%s %s", remoteAddr, err)
456+ }
457+ if session != nil && sessionAge > p.CookieRefresh && p.CookieRefresh != time.Duration(0) {
458+ log.Printf("%s refreshing %s old session cookie for %s (refresh after %s)", remoteAddr, sessionAge, session, p.CookieRefresh)
459+ saveSession = true
460+ }
461+
462+ if ok, err := p.provider.RefreshSessionIfNeeded(session); err != nil {
463+ log.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session)
464+ clearSession = true
465+ session = nil
466+ } else if ok {
467+ saveSession = true
468+ revalidated = true
469+ }
470+
471+ if session != nil && session.IsExpired() {
472+ log.Printf("%s removing session. token expired %s", remoteAddr, session)
473+ session = nil
474+ saveSession = false
475+ clearSession = true
476+ }
477+
478+ if saveSession && !revalidated && session.AccessToken != "" {
479+ if !p.provider.ValidateSessionState(session) {
480+ log.Printf("%s removing session. error validating %s", remoteAddr, session)
481+ saveSession = false
482+ session = nil
483+ clearSession = true
427484 }
485+ }
428486
429− // set cookie, or deny
430− if p.Validator(email) {
431− log.Printf("%s authenticating %s completed", remoteAddr, email)
432− value, err := buildCookieValue(
433− email, p.AesCipher, access_token)
434− if err != nil {
435− log.Printf("%s", err)
436− }
437− p.SetCookie(rw, req, value)
438− http.Redirect(rw, req, redirect, 302)
439− return
440− } else {
441− log.Printf("validating: %s is unauthorized")
442− p.ErrorPage(rw, 403, "Permission Denied", "Invalid Account")
487+ if saveSession && session.Email != "" && !p.Validator(session.Email) {
488+ log.Printf("%s Permission Denied: removing session %s", remoteAddr, session)
489+ session = nil
490+ saveSession = false
491+ clearSession = true
492+ }
493+
494+ if saveSession {
495+ err := p.SaveSession(rw, req, session)
496+ if err != nil {
497+ log.Printf("%s %s", remoteAddr, err)
498+ p.ErrorPage(rw, 500, "Internal Error", "Internal Error")
443499 return
444500 }
445501 }
446502
447− if !ok {
448− email, user, access_token, ok = p.ProcessCookie(rw, req)
503+ if clearSession {
504+ p.ClearCookie(rw, req)
449505 }
450506
451− if !ok {
452− user, ok = p.CheckBasicAuth(req)
507+ if session == nil {
508+ session, err = p.CheckBasicAuth(req)
509+ if err != nil {
510+ log.Printf("%s %s", remoteAddr, err)
511+ }
453512 }
454513
455− if !ok {
514+ if session == nil {
456515 p.SignInPage(rw, req, 403)
457516 return
458517 }
459518
460519 // At this point, the user is authenticated. proxy normally
461520 if p.PassBasicAuth {
462− req.SetBasicAuth(user, "")
463− req.Header["X-Forwarded-User"] = []string{user}
464− req.Header["X-Forwarded-Email"] = []string{email}
521+ req.SetBasicAuth(session.User, "")
522+ req.Header["X-Forwarded-User"] = []string{session.User}
523+ if session.Email != "" {
524+ req.Header["X-Forwarded-Email"] = []string{session.Email}
525+ }
465526 }
466− if p.PassAccessToken {
467− req.Header["X-Forwarded-Access-Token"] = []string{access_token}
527+ if p.PassAccessToken && session.AccessToken != "" {
528+ req.Header["X-Forwarded-Access-Token"] = []string{session.AccessToken}
468529 }
469− if email == "" {
470− rw.Header().Set("GAP-Auth", user)
530+ if session.Email == "" {
531+ rw.Header().Set("GAP-Auth", session.User)
471532 } else {
472− rw.Header().Set("GAP-Auth", email)
533+ rw.Header().Set("GAP-Auth", session.Email)
473534 }
474535
475536 p.serveMux.ServeHTTP(rw, req)
476537}
477538
478−func (p *OauthProxy) CheckBasicAuth(req *http.Request) (string, bool) {
539+func (p *OauthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState, error) {
479540 if p.HtpasswdFile == nil {
480− return "", false
541+ return nil, nil
542+ }
543+ auth := req.Header.Get("Authorization")
544+ if auth == "" {
545+ return nil, nil
481546 }
482− s := strings.SplitN(req.Header.Get("Authorization"), " ", 2)
547+ s := strings.SplitN(auth, " ", 2)
483548 if len(s) != 2 || s[0] != "Basic" {
484− return "", false
549+ return nil, fmt.Errorf("invalid Authorization header %s", req.Header.Get("Authorization"))
485550 }
486551 b, err := base64.StdEncoding.DecodeString(s[1])
487552 if err != nil {
488− return "", false
553+ return nil, err
489554 }
490555 pair := strings.SplitN(string(b), ":", 2)
491556 if len(pair) != 2 {
492− return "", false
557+ return nil, fmt.Errorf("invalid format %s", b)
493558 }
494559 if p.HtpasswdFile.Validate(pair[0], pair[1]) {
495560 log.Printf("authenticated %q via basic auth", pair[0])
496− return pair[0], true
561+ return &providers.SessionState{User: pair[0]}, nil
497562 }
498− return "", false
563+ return nil, fmt.Errorf("%s not in HtpasswdFile", pair[0])
499564}