From 4b4a69c4f9d529d5df1577a0229815a7c261fc9c Mon Sep 17 00:00:00 2001 From: Mike Bland Date: Thu, 8 Oct 2015 09:27:00 -0400 Subject: [PATCH] Add /auth endpoint to support Nginx's auth_request Closes #152. --- README.md | 1 + oauthproxy.go | 19 ++++++++++++++++ oauthproxy_test.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/README.md b/README.md index 17c748dcc..7a5f2326b 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,7 @@ OAuth2 Proxy responds directly to the following endpoints. All other endpoints w * /oauth2/sign_in - the login page, which also doubles as a sign out page (it clears cookies) * /oauth2/start - a URL that will redirect to start the OAuth cycle * /oauth2/callback - the URL used at the end of the OAuth cycle. The oauth app will be configured with this as the callback url. +* /oauth2/auth - only returns a 202 Accepted response or a 401 Unauthorized response; for use with the [Nginx `auth_request` directive](http://nginx.org/en/docs/http/ngx_http_auth_request_module.html) ## Logging Format diff --git a/oauthproxy.go b/oauthproxy.go index 73fd6f29c..0e0896c54 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -33,6 +33,7 @@ type OAuthProxy struct { SignInPath string OAuthStartPath string OAuthCallbackPath string + AuthOnlyPath string redirectURL *url.URL // the url to receive requests at provider providers.Provider @@ -156,6 +157,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { SignInPath: fmt.Sprintf("%s/sign_in", opts.ProxyPrefix), OAuthStartPath: fmt.Sprintf("%s/start", opts.ProxyPrefix), OAuthCallbackPath: fmt.Sprintf("%s/callback", opts.ProxyPrefix), + AuthOnlyPath: fmt.Sprintf("%s/auth", opts.ProxyPrefix), ProxyPrefix: opts.ProxyPrefix, provider: opts.provider, @@ -390,6 +392,8 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { p.OAuthStart(rw, req) case path == p.OAuthCallbackPath: p.OAuthCallback(rw, req) + case path == p.AuthOnlyPath: + p.AuthenticateOnly(rw, req) default: p.Proxy(rw, req) } @@ -465,6 +469,21 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) { } } +func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) { + remoteAddr := getRemoteAddr(req) + if session, _, err := p.LoadCookiedSession(req); err != nil { + log.Printf("%s %s", remoteAddr, err) + } else if session.IsExpired() { + log.Printf("%s Expired", remoteAddr, session) + } else if !p.Validator(session.Email) { + log.Printf("%s Permission Denied", remoteAddr, session) + } else { + rw.WriteHeader(http.StatusAccepted) + return + } + http.Error(rw, "unauthorized request", http.StatusUnauthorized) +} + func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) { var saveSession, clearSession, revalidated bool remoteAddr := getRemoteAddr(req) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index cf3f5aa21..a89f041e2 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -555,3 +555,58 @@ func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) { t.Errorf("expected nil session %#v", session) } } + +func NewAuthOnlyEndpointTest() *ProcessCookieTest { + pc_test := NewProcessCookieTestWithDefaults() + pc_test.req, _ = http.NewRequest("GET", + pc_test.opts.ProxyPrefix + "/auth", nil) + return pc_test +} + +func TestAuthOnlyEndpointAccepted(t *testing.T) { + test := NewAuthOnlyEndpointTest() + startSession := &providers.SessionState{ + Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"} + test.SaveSession(startSession, time.Now()) + + test.proxy.ServeHTTP(test.rw, test.req) + assert.Equal(t, http.StatusAccepted, test.rw.Code) + bodyBytes, _ := ioutil.ReadAll(test.rw.Body) + assert.Equal(t, "", string(bodyBytes)) +} + +func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) { + test := NewAuthOnlyEndpointTest() + + test.proxy.ServeHTTP(test.rw, test.req) + assert.Equal(t, http.StatusUnauthorized, test.rw.Code) + bodyBytes, _ := ioutil.ReadAll(test.rw.Body) + assert.Equal(t, "unauthorized request\n", string(bodyBytes)) +} + +func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) { + test := NewAuthOnlyEndpointTest() + test.proxy.CookieExpire = time.Duration(24) * time.Hour + reference := time.Now().Add(time.Duration(25) * time.Hour * -1) + startSession := &providers.SessionState{ + Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"} + test.SaveSession(startSession, reference) + + test.proxy.ServeHTTP(test.rw, test.req) + assert.Equal(t, http.StatusUnauthorized, test.rw.Code) + bodyBytes, _ := ioutil.ReadAll(test.rw.Body) + assert.Equal(t, "unauthorized request\n", string(bodyBytes)) +} + +func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) { + test := NewAuthOnlyEndpointTest() + startSession := &providers.SessionState{ + Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"} + test.SaveSession(startSession, time.Now()) + test.validate_user = false + + test.proxy.ServeHTTP(test.rw, test.req) + assert.Equal(t, http.StatusUnauthorized, test.rw.Code) + bodyBytes, _ := ioutil.ReadAll(test.rw.Body) + assert.Equal(t, "unauthorized request\n", string(bodyBytes)) +}