2014-12-01 04:12:33 +03:00
package main
import (
2015-11-16 06:08:30 +03:00
"crypto"
2015-07-24 12:17:43 +03:00
"encoding/base64"
2015-11-16 06:08:30 +03:00
"io"
2014-12-01 04:12:33 +03:00
"io/ioutil"
2015-05-21 06:23:48 +03:00
"log"
2014-12-01 04:12:33 +03:00
"net"
"net/http"
"net/http/httptest"
"net/url"
2015-04-07 05:10:03 +03:00
"regexp"
2015-04-03 03:57:17 +03:00
"strings"
2014-12-01 04:12:33 +03:00
"testing"
2015-04-03 03:57:17 +03:00
"time"
2017-06-22 01:02:34 +03:00
2018-11-27 14:45:05 +03:00
"github.com/pusher/oauth2_proxy/providers"
2017-09-13 01:59:00 +03:00
"github.com/mbland/hmacauth"
2017-10-23 19:23:46 +03:00
"github.com/stretchr/testify/assert"
2014-12-01 04:12:33 +03:00
)
2015-05-21 06:23:48 +03:00
func init ( ) {
log . SetFlags ( log . Ldate | log . Ltime | log . Lshortfile )
}
2014-12-01 04:12:33 +03:00
func TestNewReverseProxy ( t * testing . T ) {
backend := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( 200 )
2015-03-17 22:15:15 +03:00
hostname , _ , _ := net . SplitHostPort ( r . Host )
2014-12-01 04:12:33 +03:00
w . Write ( [ ] byte ( hostname ) )
} ) )
defer backend . Close ( )
backendURL , _ := url . Parse ( backend . URL )
2015-04-03 04:06:37 +03:00
backendHostname , backendPort , _ := net . SplitHostPort ( backendURL . Host )
2014-12-01 04:12:33 +03:00
backendHost := net . JoinHostPort ( backendHostname , backendPort )
proxyURL , _ := url . Parse ( backendURL . Scheme + "://" + backendHost + "/" )
proxyHandler := NewReverseProxy ( proxyURL )
2015-03-17 22:15:15 +03:00
setProxyUpstreamHostHeader ( proxyHandler , proxyURL )
2014-12-01 04:12:33 +03:00
frontend := httptest . NewServer ( proxyHandler )
defer frontend . Close ( )
getReq , _ := http . NewRequest ( "GET" , frontend . URL , nil )
res , _ := http . DefaultClient . Do ( getReq )
bodyBytes , _ := ioutil . ReadAll ( res . Body )
if g , e := string ( bodyBytes ) , backendHostname ; g != e {
t . Errorf ( "got body %q; expected %q" , g , e )
}
}
2015-03-18 00:17:40 +03:00
func TestEncodedSlashes ( t * testing . T ) {
var seen string
backend := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( 200 )
seen = r . RequestURI
} ) )
defer backend . Close ( )
b , _ := url . Parse ( backend . URL )
proxyHandler := NewReverseProxy ( b )
setProxyDirector ( proxyHandler )
frontend := httptest . NewServer ( proxyHandler )
defer frontend . Close ( )
f , _ := url . Parse ( frontend . URL )
2015-03-21 22:29:07 +03:00
encodedPath := "/a%2Fb/?c=1"
2015-03-18 00:17:40 +03:00
getReq := & http . Request { URL : & url . URL { Scheme : "http" , Host : f . Host , Opaque : encodedPath } }
_ , err := http . DefaultClient . Do ( getReq )
if err != nil {
t . Fatalf ( "err %s" , err )
}
2015-03-21 22:29:07 +03:00
if seen != encodedPath {
t . Errorf ( "got bad request %q expected %q" , seen , encodedPath )
2015-03-18 00:17:40 +03:00
}
}
2015-04-03 03:57:17 +03:00
2015-05-10 22:15:52 +03:00
func TestRobotsTxt ( t * testing . T ) {
opts := NewOptions ( )
opts . ClientID = "bazquux"
opts . ClientSecret = "foobar"
opts . CookieSecret = "xyzzyplugh"
opts . Validate ( )
2015-11-09 02:57:01 +03:00
proxy := NewOAuthProxy ( opts , func ( string ) bool { return true } )
2015-05-10 22:15:52 +03:00
rw := httptest . NewRecorder ( )
req , _ := http . NewRequest ( "GET" , "/robots.txt" , nil )
proxy . ServeHTTP ( rw , req )
assert . Equal ( t , 200 , rw . Code )
assert . Equal ( t , "User-agent: *\nDisallow: /" , rw . Body . String ( ) )
}
2015-11-16 06:08:30 +03:00
type TestProvider struct {
* providers . ProviderData
EmailAddress string
ValidToken bool
}
func NewTestProvider ( provider_url * url . URL , email_address string ) * TestProvider {
return & TestProvider {
ProviderData : & providers . ProviderData {
ProviderName : "Test Provider" ,
LoginURL : & url . URL {
Scheme : "http" ,
Host : provider_url . Host ,
Path : "/oauth/authorize" ,
} ,
RedeemURL : & url . URL {
Scheme : "http" ,
Host : provider_url . Host ,
Path : "/oauth/token" ,
} ,
ProfileURL : & url . URL {
Scheme : "http" ,
Host : provider_url . Host ,
Path : "/api/v1/profile" ,
} ,
Scope : "profile.email" ,
} ,
EmailAddress : email_address ,
}
}
func ( tp * TestProvider ) GetEmailAddress ( session * providers . SessionState ) ( string , error ) {
return tp . EmailAddress , nil
}
func ( tp * TestProvider ) ValidateSessionState ( session * providers . SessionState ) bool {
return tp . ValidToken
}
2015-07-24 12:17:43 +03:00
func TestBasicAuthPassword ( t * testing . T ) {
provider_server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
log . Printf ( "%#v" , r )
url := r . URL
payload := ""
switch url . Path {
case "/oauth/token" :
payload = ` { "access_token": "my_auth_token"} `
default :
payload = r . Header . Get ( "Authorization" )
if payload == "" {
payload = "No Authorization header found."
}
}
w . WriteHeader ( 200 )
w . Write ( [ ] byte ( payload ) )
} ) )
opts := NewOptions ( )
opts . Upstreams = append ( opts . Upstreams , provider_server . URL )
// The CookieSecret must be 32 bytes in order to create the AES
// cipher.
opts . CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
opts . ClientID = "bazquux"
opts . ClientSecret = "foobar"
opts . CookieSecure = false
opts . PassBasicAuth = true
2016-02-08 18:57:47 +03:00
opts . PassUserHeaders = true
2015-07-24 12:17:43 +03:00
opts . BasicAuthPassword = "This is a secure password"
opts . Validate ( )
provider_url , _ := url . Parse ( provider_server . URL )
const email_address = "michael.bland@gsa.gov"
const user_name = "michael.bland"
2015-11-16 06:08:30 +03:00
opts . provider = NewTestProvider ( provider_url , email_address )
2015-11-09 02:57:01 +03:00
proxy := NewOAuthProxy ( opts , func ( email string ) bool {
2015-07-24 12:17:43 +03:00
return email == email_address
} )
rw := httptest . NewRecorder ( )
2017-03-28 04:14:38 +03:00
req , _ := http . NewRequest ( "GET" , "/oauth2/callback?code=callback_code&state=nonce:" ,
2015-07-24 12:17:43 +03:00
strings . NewReader ( "" ) )
2017-03-28 04:14:38 +03:00
req . AddCookie ( proxy . MakeCSRFCookie ( req , "nonce" , proxy . CookieExpire , time . Now ( ) ) )
2015-07-24 12:17:43 +03:00
proxy . ServeHTTP ( rw , req )
2017-03-28 04:14:38 +03:00
if rw . Code >= 400 {
t . Fatalf ( "expected 3xx got %d" , rw . Code )
}
cookie := rw . HeaderMap [ "Set-Cookie" ] [ 1 ]
2015-07-24 12:17:43 +03:00
cookieName := proxy . CookieName
var value string
key_prefix := cookieName + "="
for _ , field := range strings . Split ( cookie , "; " ) {
value = strings . TrimPrefix ( field , key_prefix )
if value != field {
break
} else {
value = ""
}
}
req , _ = http . NewRequest ( "GET" , "/" , strings . NewReader ( "" ) )
req . AddCookie ( & http . Cookie {
Name : cookieName ,
Value : value ,
Path : "/" ,
Expires : time . Now ( ) . Add ( time . Duration ( 24 ) ) ,
HttpOnly : true ,
} )
2017-03-28 04:14:38 +03:00
req . AddCookie ( proxy . MakeCSRFCookie ( req , "nonce" , proxy . CookieExpire , time . Now ( ) ) )
2015-07-24 12:17:43 +03:00
rw = httptest . NewRecorder ( )
proxy . ServeHTTP ( rw , req )
2017-03-28 04:14:38 +03:00
2015-07-24 12:17:43 +03:00
expectedHeader := "Basic " + base64 . StdEncoding . EncodeToString ( [ ] byte ( user_name + ":" + opts . BasicAuthPassword ) )
assert . Equal ( t , expectedHeader , rw . Body . String ( ) )
provider_server . Close ( )
}
2015-04-03 03:57:17 +03:00
type PassAccessTokenTest struct {
provider_server * httptest . Server
2015-11-09 02:57:01 +03:00
proxy * OAuthProxy
2015-04-03 03:57:17 +03:00
opts * Options
}
type PassAccessTokenTestOptions struct {
PassAccessToken bool
}
func NewPassAccessTokenTest ( opts PassAccessTokenTestOptions ) * PassAccessTokenTest {
t := & PassAccessTokenTest { }
t . provider_server = httptest . NewServer (
http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2015-05-21 06:23:48 +03:00
log . Printf ( "%#v" , r )
2015-04-03 03:57:17 +03:00
url := r . URL
payload := ""
switch url . Path {
case "/oauth/token" :
payload = ` { "access_token": "my_auth_token"} `
default :
2015-05-21 06:23:48 +03:00
payload = r . Header . Get ( "X-Forwarded-Access-Token" )
if payload == "" {
2015-04-03 03:57:17 +03:00
payload = "No access token found."
}
}
w . WriteHeader ( 200 )
w . Write ( [ ] byte ( payload ) )
} ) )
t . opts = NewOptions ( )
t . opts . Upstreams = append ( t . opts . Upstreams , t . provider_server . URL )
// The CookieSecret must be 32 bytes in order to create the AES
// cipher.
t . opts . CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
t . opts . ClientID = "bazquux"
t . opts . ClientSecret = "foobar"
t . opts . CookieSecure = false
t . opts . PassAccessToken = opts . PassAccessToken
t . opts . Validate ( )
provider_url , _ := url . Parse ( t . provider_server . URL )
const email_address = "michael.bland@gsa.gov"
2015-11-16 06:08:30 +03:00
t . opts . provider = NewTestProvider ( provider_url , email_address )
2015-11-09 02:57:01 +03:00
t . proxy = NewOAuthProxy ( t . opts , func ( email string ) bool {
2015-04-03 03:57:17 +03:00
return email == email_address
} )
return t
}
2015-04-07 04:35:58 +03:00
func ( pat_test * PassAccessTokenTest ) Close ( ) {
pat_test . provider_server . Close ( )
2015-04-03 03:57:17 +03:00
}
2015-04-07 04:35:58 +03:00
func ( pat_test * PassAccessTokenTest ) getCallbackEndpoint ( ) ( http_code int ,
cookie string ) {
2015-04-03 03:57:17 +03:00
rw := httptest . NewRecorder ( )
2017-03-28 04:14:38 +03:00
req , err := http . NewRequest ( "GET" , "/oauth2/callback?code=callback_code&state=nonce:" ,
2015-04-03 03:57:17 +03:00
strings . NewReader ( "" ) )
if err != nil {
return 0 , ""
}
2017-03-28 04:14:38 +03:00
req . AddCookie ( pat_test . proxy . MakeCSRFCookie ( req , "nonce" , time . Hour , time . Now ( ) ) )
2015-04-07 04:35:58 +03:00
pat_test . proxy . ServeHTTP ( rw , req )
2017-03-28 04:14:38 +03:00
return rw . Code , rw . HeaderMap [ "Set-Cookie" ] [ 1 ]
2015-04-03 03:57:17 +03:00
}
2015-05-21 06:23:48 +03:00
func ( pat_test * PassAccessTokenTest ) getRootEndpoint ( cookie string ) ( http_code int , access_token string ) {
2015-06-08 06:52:28 +03:00
cookieName := pat_test . proxy . CookieName
2015-04-03 03:57:17 +03:00
var value string
2015-06-08 06:52:28 +03:00
key_prefix := cookieName + "="
2015-04-03 03:57:17 +03:00
for _ , field := range strings . Split ( cookie , "; " ) {
value = strings . TrimPrefix ( field , key_prefix )
if value != field {
break
} else {
value = ""
}
}
if value == "" {
return 0 , ""
}
req , err := http . NewRequest ( "GET" , "/" , strings . NewReader ( "" ) )
if err != nil {
return 0 , ""
}
req . AddCookie ( & http . Cookie {
2015-06-08 06:52:28 +03:00
Name : cookieName ,
2015-04-03 03:57:17 +03:00
Value : value ,
Path : "/" ,
Expires : time . Now ( ) . Add ( time . Duration ( 24 ) ) ,
HttpOnly : true ,
} )
rw := httptest . NewRecorder ( )
2015-04-07 04:35:58 +03:00
pat_test . proxy . ServeHTTP ( rw , req )
2015-04-03 03:57:17 +03:00
return rw . Code , rw . Body . String ( )
}
func TestForwardAccessTokenUpstream ( t * testing . T ) {
2015-04-07 04:35:58 +03:00
pat_test := NewPassAccessTokenTest ( PassAccessTokenTestOptions {
2015-04-03 03:57:17 +03:00
PassAccessToken : true ,
} )
2015-04-07 04:35:58 +03:00
defer pat_test . Close ( )
2015-04-03 03:57:17 +03:00
// A successful validation will redirect and set the auth cookie.
2015-04-07 04:35:58 +03:00
code , cookie := pat_test . getCallbackEndpoint ( )
2017-03-28 04:14:38 +03:00
if code != 302 {
t . Fatalf ( "expected 302; got %d" , code )
}
2015-04-03 03:57:17 +03:00
assert . NotEqual ( t , nil , cookie )
// Now we make a regular request; the access_token from the cookie is
// forwarded as the "X-Forwarded-Access-Token" header. The token is
// read by the test provider server and written in the response body.
2015-04-07 04:35:58 +03:00
code , payload := pat_test . getRootEndpoint ( cookie )
2017-03-28 04:14:38 +03:00
if code != 200 {
t . Fatalf ( "expected 200; got %d" , code )
}
2015-04-03 03:57:17 +03:00
assert . Equal ( t , "my_auth_token" , payload )
}
func TestDoNotForwardAccessTokenUpstream ( t * testing . T ) {
2015-04-07 04:35:58 +03:00
pat_test := NewPassAccessTokenTest ( PassAccessTokenTestOptions {
2015-04-03 03:57:17 +03:00
PassAccessToken : false ,
} )
2015-04-07 04:35:58 +03:00
defer pat_test . Close ( )
2015-04-03 03:57:17 +03:00
// A successful validation will redirect and set the auth cookie.
2015-04-07 04:35:58 +03:00
code , cookie := pat_test . getCallbackEndpoint ( )
2017-03-28 04:14:38 +03:00
if code != 302 {
t . Fatalf ( "expected 302; got %d" , code )
}
2015-04-03 03:57:17 +03:00
assert . NotEqual ( t , nil , cookie )
// Now we make a regular request, but the access token header should
// not be present.
2015-04-07 04:35:58 +03:00
code , payload := pat_test . getRootEndpoint ( cookie )
2017-03-28 04:14:38 +03:00
if code != 200 {
t . Fatalf ( "expected 200; got %d" , code )
}
2015-04-03 03:57:17 +03:00
assert . Equal ( t , "No access token found." , payload )
}
2015-04-07 05:10:03 +03:00
type SignInPageTest struct {
2017-06-22 01:02:34 +03:00
opts * Options
proxy * OAuthProxy
sign_in_regexp * regexp . Regexp
sign_in_provider_regexp * regexp . Regexp
2015-04-07 05:10:03 +03:00
}
const signInRedirectPattern = ` <input type="hidden" name="rd" value="(.*)"> `
2017-06-22 01:02:34 +03:00
const signInSkipProvider = ` >Found< `
2015-04-07 05:10:03 +03:00
2017-06-22 01:02:34 +03:00
func NewSignInPageTest ( skipProvider bool ) * SignInPageTest {
2015-04-07 05:10:03 +03:00
var sip_test SignInPageTest
sip_test . opts = NewOptions ( )
sip_test . opts . CookieSecret = "foobar"
sip_test . opts . ClientID = "bazquux"
sip_test . opts . ClientSecret = "xyzzyplugh"
2017-06-22 01:02:34 +03:00
sip_test . opts . SkipProviderButton = skipProvider
2015-04-07 05:10:03 +03:00
sip_test . opts . Validate ( )
2015-11-09 02:57:01 +03:00
sip_test . proxy = NewOAuthProxy ( sip_test . opts , func ( email string ) bool {
2015-04-07 05:10:03 +03:00
return true
} )
sip_test . sign_in_regexp = regexp . MustCompile ( signInRedirectPattern )
2017-06-22 01:02:34 +03:00
sip_test . sign_in_provider_regexp = regexp . MustCompile ( signInSkipProvider )
2015-04-07 05:10:03 +03:00
return & sip_test
}
func ( sip_test * SignInPageTest ) GetEndpoint ( endpoint string ) ( int , string ) {
rw := httptest . NewRecorder ( )
req , _ := http . NewRequest ( "GET" , endpoint , strings . NewReader ( "" ) )
sip_test . proxy . ServeHTTP ( rw , req )
return rw . Code , rw . Body . String ( )
}
func TestSignInPageIncludesTargetRedirect ( t * testing . T ) {
2017-06-22 01:02:34 +03:00
sip_test := NewSignInPageTest ( false )
2015-04-07 05:10:03 +03:00
const endpoint = "/some/random/endpoint"
code , body := sip_test . GetEndpoint ( endpoint )
assert . Equal ( t , 403 , code )
match := sip_test . sign_in_regexp . FindStringSubmatch ( body )
if match == nil {
t . Fatal ( "Did not find pattern in body: " +
signInRedirectPattern + "\nBody:\n" + body )
}
if match [ 1 ] != endpoint {
t . Fatal ( ` expected redirect to " ` + endpoint +
` ", but was " ` + match [ 1 ] + ` " ` )
}
}
func TestSignInPageDirectAccessRedirectsToRoot ( t * testing . T ) {
2017-06-22 01:02:34 +03:00
sip_test := NewSignInPageTest ( false )
2015-04-07 05:10:03 +03:00
code , body := sip_test . GetEndpoint ( "/oauth2/sign_in" )
assert . Equal ( t , 200 , code )
match := sip_test . sign_in_regexp . FindStringSubmatch ( body )
if match == nil {
t . Fatal ( "Did not find pattern in body: " +
signInRedirectPattern + "\nBody:\n" + body )
}
if match [ 1 ] != "/" {
t . Fatal ( ` expected redirect to "/", but was " ` + match [ 1 ] + ` " ` )
}
}
2015-05-08 18:52:03 +03:00
2017-06-22 01:02:34 +03:00
func TestSignInPageSkipProvider ( t * testing . T ) {
sip_test := NewSignInPageTest ( true )
const endpoint = "/some/random/endpoint"
code , body := sip_test . GetEndpoint ( endpoint )
assert . Equal ( t , 302 , code )
match := sip_test . sign_in_provider_regexp . FindStringSubmatch ( body )
if match == nil {
t . Fatal ( "Did not find pattern in body: " +
signInSkipProvider + "\nBody:\n" + body )
}
}
func TestSignInPageSkipProviderDirect ( t * testing . T ) {
sip_test := NewSignInPageTest ( true )
const endpoint = "/sign_in"
code , body := sip_test . GetEndpoint ( endpoint )
assert . Equal ( t , 302 , code )
match := sip_test . sign_in_provider_regexp . FindStringSubmatch ( body )
if match == nil {
t . Fatal ( "Did not find pattern in body: " +
signInSkipProvider + "\nBody:\n" + body )
}
}
2015-05-08 18:52:03 +03:00
type ProcessCookieTest struct {
2015-05-09 22:09:31 +03:00
opts * Options
2015-11-09 02:57:01 +03:00
proxy * OAuthProxy
2015-05-09 22:09:31 +03:00
rw * httptest . ResponseRecorder
req * http . Request
2015-05-13 04:48:13 +03:00
provider TestProvider
2015-05-09 22:09:31 +03:00
response_code int
2015-05-09 23:48:39 +03:00
validate_user bool
2015-05-08 18:52:03 +03:00
}
2015-05-13 04:48:13 +03:00
type ProcessCookieTestOpts struct {
provider_validate_cookie_response bool
}
func NewProcessCookieTest ( opts ProcessCookieTestOpts ) * ProcessCookieTest {
2015-05-08 18:52:03 +03:00
var pc_test ProcessCookieTest
pc_test . opts = NewOptions ( )
pc_test . opts . ClientID = "bazquux"
pc_test . opts . ClientSecret = "xyzzyplugh"
2016-06-20 14:17:39 +03:00
pc_test . opts . CookieSecret = "0123456789abcdefabcd"
2015-05-09 23:08:55 +03:00
// First, set the CookieRefresh option so proxy.AesCipher is created,
// needed to encrypt the access_token.
2015-06-22 22:10:08 +03:00
pc_test . opts . CookieRefresh = time . Hour
2015-05-08 18:52:03 +03:00
pc_test . opts . Validate ( )
2015-11-09 02:57:01 +03:00
pc_test . proxy = NewOAuthProxy ( pc_test . opts , func ( email string ) bool {
2015-05-09 23:48:39 +03:00
return pc_test . validate_user
2015-05-08 18:52:03 +03:00
} )
2015-05-13 04:48:13 +03:00
pc_test . proxy . provider = & TestProvider {
ValidToken : opts . provider_validate_cookie_response ,
}
2015-05-08 18:52:03 +03:00
2015-05-09 23:08:55 +03:00
// Now, zero-out proxy.CookieRefresh for the cases that don't involve
// access_token validation.
pc_test . proxy . CookieRefresh = time . Duration ( 0 )
2015-05-08 18:52:03 +03:00
pc_test . rw = httptest . NewRecorder ( )
pc_test . req , _ = http . NewRequest ( "GET" , "/" , strings . NewReader ( "" ) )
2015-05-09 23:48:39 +03:00
pc_test . validate_user = true
2015-05-08 18:52:03 +03:00
return & pc_test
}
2015-05-13 04:48:13 +03:00
func NewProcessCookieTestWithDefaults ( ) * ProcessCookieTest {
return NewProcessCookieTest ( ProcessCookieTestOpts {
provider_validate_cookie_response : true ,
} )
2015-05-09 22:09:31 +03:00
}
2015-06-23 14:23:39 +03:00
func ( p * ProcessCookieTest ) MakeCookie ( value string , ref time . Time ) * http . Cookie {
2017-03-28 04:14:38 +03:00
return p . proxy . MakeSessionCookie ( p . req , value , p . opts . CookieExpire , ref )
2015-05-08 18:52:03 +03:00
}
2015-06-23 14:23:39 +03:00
func ( p * ProcessCookieTest ) SaveSession ( s * providers . SessionState , ref time . Time ) error {
value , err := p . proxy . provider . CookieForSession ( s , p . proxy . CookieCipher )
if err != nil {
return err
}
2017-03-28 04:14:38 +03:00
p . req . AddCookie ( p . proxy . MakeSessionCookie ( p . req , value , p . proxy . CookieExpire , ref ) )
2015-06-23 14:23:39 +03:00
return nil
2015-05-08 18:52:03 +03:00
}
2015-06-23 14:23:39 +03:00
func ( p * ProcessCookieTest ) LoadCookiedSession ( ) ( * providers . SessionState , time . Duration , error ) {
return p . proxy . LoadCookiedSession ( p . req )
2015-05-08 18:52:03 +03:00
}
2015-06-23 14:23:39 +03:00
func TestLoadCookiedSession ( t * testing . T ) {
2015-05-13 04:48:13 +03:00
pc_test := NewProcessCookieTestWithDefaults ( )
2015-05-09 22:09:31 +03:00
2015-06-23 14:23:39 +03:00
startSession := & providers . SessionState { Email : "michael.bland@gsa.gov" , AccessToken : "my_access_token" }
pc_test . SaveSession ( startSession , time . Now ( ) )
session , _ , err := pc_test . LoadCookiedSession ( )
assert . Equal ( t , nil , err )
assert . Equal ( t , startSession . Email , session . Email )
assert . Equal ( t , "michael.bland" , session . User )
assert . Equal ( t , startSession . AccessToken , session . AccessToken )
2015-05-08 18:52:03 +03:00
}
2015-05-08 17:00:57 +03:00
func TestProcessCookieNoCookieError ( t * testing . T ) {
2015-05-13 04:48:13 +03:00
pc_test := NewProcessCookieTestWithDefaults ( )
2015-05-08 17:00:57 +03:00
2015-06-23 14:23:39 +03:00
session , _ , err := pc_test . LoadCookiedSession ( )
assert . Equal ( t , "Cookie \"_oauth2_proxy\" not present" , err . Error ( ) )
if session != nil {
t . Errorf ( "expected nil session. got %#v" , session )
}
2015-05-09 23:31:18 +03:00
}
2015-05-08 17:00:57 +03:00
func TestProcessCookieRefreshNotSet ( t * testing . T ) {
2015-05-13 04:48:13 +03:00
pc_test := NewProcessCookieTestWithDefaults ( )
2015-05-10 07:11:26 +03:00
pc_test . proxy . CookieExpire = time . Duration ( 23 ) * time . Hour
2015-06-22 22:10:08 +03:00
reference := time . Now ( ) . Add ( time . Duration ( - 2 ) * time . Hour )
2015-05-08 17:00:57 +03:00
2015-06-23 14:23:39 +03:00
startSession := & providers . SessionState { Email : "michael.bland@gsa.gov" , AccessToken : "my_access_token" }
pc_test . SaveSession ( startSession , reference )
2015-05-09 22:09:31 +03:00
2015-06-23 14:23:39 +03:00
session , age , err := pc_test . LoadCookiedSession ( )
assert . Equal ( t , nil , err )
if age < time . Duration ( - 2 ) * time . Hour {
t . Errorf ( "cookie too young %v" , age )
}
assert . Equal ( t , startSession . Email , session . Email )
2015-05-10 07:11:26 +03:00
}
2015-06-22 22:10:08 +03:00
func TestProcessCookieFailIfCookieExpired ( t * testing . T ) {
pc_test := NewProcessCookieTestWithDefaults ( )
pc_test . proxy . CookieExpire = time . Duration ( 24 ) * time . Hour
reference := time . Now ( ) . Add ( time . Duration ( 25 ) * time . Hour * - 1 )
2015-06-23 14:23:39 +03:00
startSession := & providers . SessionState { Email : "michael.bland@gsa.gov" , AccessToken : "my_access_token" }
pc_test . SaveSession ( startSession , reference )
2015-06-22 22:10:08 +03:00
2015-06-23 14:23:39 +03:00
session , _ , err := pc_test . LoadCookiedSession ( )
assert . NotEqual ( t , nil , err )
if session != nil {
t . Errorf ( "expected nil session %#v" , session )
2015-06-22 22:10:08 +03:00
}
}
func TestProcessCookieFailIfRefreshSetAndCookieExpired ( t * testing . T ) {
pc_test := NewProcessCookieTestWithDefaults ( )
pc_test . proxy . CookieExpire = time . Duration ( 24 ) * time . Hour
reference := time . Now ( ) . Add ( time . Duration ( 25 ) * time . Hour * - 1 )
2015-06-23 14:23:39 +03:00
startSession := & providers . SessionState { Email : "michael.bland@gsa.gov" , AccessToken : "my_access_token" }
pc_test . SaveSession ( startSession , reference )
2015-06-22 22:10:08 +03:00
pc_test . proxy . CookieRefresh = time . Hour
2015-06-23 14:23:39 +03:00
session , _ , err := pc_test . LoadCookiedSession ( )
assert . NotEqual ( t , nil , err )
if session != nil {
t . Errorf ( "expected nil session %#v" , session )
2015-06-22 22:10:08 +03:00
}
}
2015-10-08 16:27:00 +03:00
func NewAuthOnlyEndpointTest ( ) * ProcessCookieTest {
pc_test := NewProcessCookieTestWithDefaults ( )
pc_test . req , _ = http . NewRequest ( "GET" ,
2015-11-16 06:08:30 +03:00
pc_test . opts . ProxyPrefix + "/auth" , nil )
2015-10-08 16:27:00 +03:00
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 ) )
}
2015-11-16 06:08:30 +03:00
2016-10-20 15:19:59 +03:00
func TestAuthOnlyEndpointSetXAuthRequestHeaders ( t * testing . T ) {
var pc_test ProcessCookieTest
pc_test . opts = NewOptions ( )
pc_test . opts . SetXAuthRequest = true
pc_test . opts . Validate ( )
pc_test . proxy = NewOAuthProxy ( pc_test . opts , func ( email string ) bool {
return pc_test . validate_user
} )
pc_test . proxy . provider = & TestProvider {
ValidToken : true ,
}
pc_test . validate_user = true
pc_test . rw = httptest . NewRecorder ( )
pc_test . req , _ = http . NewRequest ( "GET" ,
pc_test . opts . ProxyPrefix + "/auth" , nil )
startSession := & providers . SessionState {
User : "oauth_user" , Email : "oauth_user@example.com" , AccessToken : "oauth_token" }
pc_test . SaveSession ( startSession , time . Now ( ) )
pc_test . proxy . ServeHTTP ( pc_test . rw , pc_test . req )
assert . Equal ( t , http . StatusAccepted , pc_test . rw . Code )
assert . Equal ( t , "oauth_user" , pc_test . rw . HeaderMap [ "X-Auth-Request-User" ] [ 0 ] )
assert . Equal ( t , "oauth_user@example.com" , pc_test . rw . HeaderMap [ "X-Auth-Request-Email" ] [ 0 ] )
}
2017-04-07 14:55:48 +03:00
func TestAuthSkippedForPreflightRequests ( t * testing . T ) {
upstream := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( 200 )
w . Write ( [ ] byte ( "response" ) )
} ) )
defer upstream . Close ( )
opts := NewOptions ( )
opts . Upstreams = append ( opts . Upstreams , upstream . URL )
opts . ClientID = "bazquux"
opts . ClientSecret = "foobar"
opts . CookieSecret = "xyzzyplugh"
opts . SkipAuthPreflight = true
opts . Validate ( )
upstream_url , _ := url . Parse ( upstream . URL )
opts . provider = NewTestProvider ( upstream_url , "" )
proxy := NewOAuthProxy ( opts , func ( string ) bool { return false } )
rw := httptest . NewRecorder ( )
req , _ := http . NewRequest ( "OPTIONS" , "/preflight-request" , nil )
proxy . ServeHTTP ( rw , req )
assert . Equal ( t , 200 , rw . Code )
assert . Equal ( t , "response" , rw . Body . String ( ) )
}
2015-11-16 06:08:30 +03:00
type SignatureAuthenticator struct {
auth hmacauth . HmacAuth
}
2017-03-23 06:18:34 +03:00
func ( v * SignatureAuthenticator ) Authenticate ( w http . ResponseWriter , r * http . Request ) {
2015-11-16 06:08:30 +03:00
result , headerSig , computedSig := v . auth . AuthenticateRequest ( r )
if result == hmacauth . ResultNoSignature {
w . Write ( [ ] byte ( "no signature received" ) )
} else if result == hmacauth . ResultMatch {
w . Write ( [ ] byte ( "signatures match" ) )
} else if result == hmacauth . ResultMismatch {
w . Write ( [ ] byte ( "signatures do not match:" +
"\n received: " + headerSig +
"\n computed: " + computedSig ) )
} else {
panic ( "Unknown result value: " + result . String ( ) )
}
}
type SignatureTest struct {
opts * Options
upstream * httptest . Server
upstream_host string
provider * httptest . Server
header http . Header
rw * httptest . ResponseRecorder
authenticator * SignatureAuthenticator
}
func NewSignatureTest ( ) * SignatureTest {
opts := NewOptions ( )
opts . CookieSecret = "cookie secret"
opts . ClientID = "client ID"
opts . ClientSecret = "client secret"
opts . EmailDomains = [ ] string { "acm.org" }
authenticator := & SignatureAuthenticator { }
upstream := httptest . NewServer (
http . HandlerFunc ( authenticator . Authenticate ) )
upstream_url , _ := url . Parse ( upstream . URL )
opts . Upstreams = append ( opts . Upstreams , upstream . URL )
providerHandler := func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( [ ] byte ( ` { "access_token": "my_auth_token"} ` ) )
}
provider := httptest . NewServer ( http . HandlerFunc ( providerHandler ) )
provider_url , _ := url . Parse ( provider . URL )
opts . provider = NewTestProvider ( provider_url , "mbland@acm.org" )
return & SignatureTest {
opts ,
upstream ,
upstream_url . Host ,
provider ,
make ( http . Header ) ,
httptest . NewRecorder ( ) ,
authenticator ,
}
}
func ( st * SignatureTest ) Close ( ) {
st . provider . Close ( )
st . upstream . Close ( )
}
// fakeNetConn simulates an http.Request.Body buffer that will be consumed
// when it is read by the hmacauth.HmacAuth if not handled properly. See:
// https://github.com/18F/hmacauth/pull/4
type fakeNetConn struct {
reqBody string
}
func ( fnc * fakeNetConn ) Read ( p [ ] byte ) ( n int , err error ) {
if bodyLen := len ( fnc . reqBody ) ; bodyLen != 0 {
copy ( p , fnc . reqBody )
fnc . reqBody = ""
return bodyLen , io . EOF
}
return 0 , io . EOF
}
func ( st * SignatureTest ) MakeRequestWithExpectedKey ( method , body , key string ) {
err := st . opts . Validate ( )
if err != nil {
panic ( err )
}
proxy := NewOAuthProxy ( st . opts , func ( email string ) bool { return true } )
var bodyBuf io . ReadCloser
if body != "" {
bodyBuf = ioutil . NopCloser ( & fakeNetConn { reqBody : body } )
}
2017-03-23 06:18:34 +03:00
req := httptest . NewRequest ( method , "/foo/bar" , bodyBuf )
2015-11-16 06:08:30 +03:00
req . Header = st . header
state := & providers . SessionState {
Email : "mbland@acm.org" , AccessToken : "my_access_token" }
value , err := proxy . provider . CookieForSession ( state , proxy . CookieCipher )
if err != nil {
panic ( err )
}
2017-03-28 04:14:38 +03:00
cookie := proxy . MakeSessionCookie ( req , value , proxy . CookieExpire , time . Now ( ) )
2015-11-16 06:08:30 +03:00
req . AddCookie ( cookie )
// This is used by the upstream to validate the signature.
st . authenticator . auth = hmacauth . NewHmacAuth (
crypto . SHA1 , [ ] byte ( key ) , SignatureHeader , SignatureHeaders )
proxy . ServeHTTP ( st . rw , req )
}
func TestNoRequestSignature ( t * testing . T ) {
st := NewSignatureTest ( )
defer st . Close ( )
st . MakeRequestWithExpectedKey ( "GET" , "" , "" )
assert . Equal ( t , 200 , st . rw . Code )
assert . Equal ( t , st . rw . Body . String ( ) , "no signature received" )
}
func TestRequestSignatureGetRequest ( t * testing . T ) {
st := NewSignatureTest ( )
defer st . Close ( )
st . opts . SignatureKey = "sha1:foobar"
st . MakeRequestWithExpectedKey ( "GET" , "" , "foobar" )
assert . Equal ( t , 200 , st . rw . Code )
assert . Equal ( t , st . rw . Body . String ( ) , "signatures match" )
}
func TestRequestSignaturePostRequest ( t * testing . T ) {
st := NewSignatureTest ( )
defer st . Close ( )
st . opts . SignatureKey = "sha1:foobar"
payload := ` { "hello": "world!" } `
st . MakeRequestWithExpectedKey ( "POST" , payload , "foobar" )
assert . Equal ( t , 200 , st . rw . Code )
assert . Equal ( t , st . rw . Body . String ( ) , "signatures match" )
}