2014-12-01 04:12:33 +03:00
package main
import (
2020-06-27 14:07:24 +03:00
"bufio"
2019-04-27 05:16:45 +03:00
"context"
2015-11-16 06:08:30 +03:00
"crypto"
2015-07-24 12:17:43 +03:00
"encoding/base64"
2019-04-24 18:25:29 +03:00
"fmt"
2015-11-16 06:08:30 +03:00
"io"
2014-12-01 04:12:33 +03:00
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
2020-06-27 14:07:24 +03:00
"os"
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
2019-04-27 05:47:53 +03:00
"github.com/coreos/go-oidc"
2017-09-13 01:59:00 +03:00
"github.com/mbland/hmacauth"
2020-09-29 19:44:42 +03:00
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
"github.com/oauth2-proxy/oauth2-proxy/v7/providers"
2017-10-23 19:23:46 +03:00
"github.com/stretchr/testify/assert"
2019-01-29 15:13:02 +03:00
"github.com/stretchr/testify/require"
2014-12-01 04:12:33 +03:00
)
2020-02-24 14:23:35 +03:00
const (
// The rawCookieSecret is 32 bytes and the base64CookieSecret is the base64
// encoded version of this.
rawCookieSecret = "secretthirtytwobytes+abcdefghijk"
2020-05-25 16:00:49 +03:00
base64CookieSecret = "c2VjcmV0dGhpcnR5dHdvYnl0ZXMrYWJjZGVmZ2hpams"
2020-07-13 22:56:05 +03:00
clientID = "3984n253984d7348dm8234yf982t"
clientSecret = "gv3498mfc9t23y23974dm2394dm9"
2020-02-24 14:23:35 +03:00
)
2015-05-21 06:23:48 +03:00
func init ( ) {
2019-02-10 19:37:45 +03:00
logger . SetFlags ( logger . Lshortfile )
2015-05-21 06:23:48 +03:00
}
2015-05-10 22:15:52 +03:00
func TestRobotsTxt ( t * testing . T ) {
2020-05-25 16:00:49 +03:00
opts := baseTestOptions ( )
2020-07-13 22:56:05 +03:00
err := validation . Validate ( opts )
assert . NoError ( t , err )
2015-05-10 22:15:52 +03:00
2020-05-25 16:00:49 +03:00
proxy , err := NewOAuthProxy ( opts , func ( string ) bool { return true } )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
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 ( ) )
}
2017-10-02 12:19:24 +03:00
func TestIsValidRedirect ( t * testing . T ) {
2020-05-25 16:00:49 +03:00
opts := baseTestOptions ( )
2017-12-11 12:33:52 +03:00
// Should match domains that are exactly foo.bar and any subdomain of bar.foo
2019-10-23 16:38:44 +03:00
opts . WhitelistDomains = [ ] string {
"foo.bar" ,
".bar.foo" ,
"port.bar:8080" ,
".sub.port.bar:8080" ,
"anyport.bar:*" ,
".sub.anyport.bar:*" ,
}
2020-07-13 22:56:05 +03:00
err := validation . Validate ( opts )
assert . NoError ( t , err )
2017-10-02 12:19:24 +03:00
2020-05-25 16:00:49 +03:00
proxy , err := NewOAuthProxy ( opts , func ( string ) bool { return true } )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
2017-10-02 12:19:24 +03:00
2019-10-23 16:38:44 +03:00
testCases := [ ] struct {
Desc , Redirect string
ExpectedResult bool
} {
{
Desc : "noRD" ,
Redirect : "" ,
ExpectedResult : false ,
} ,
{
Desc : "singleSlash" ,
Redirect : "/redirect" ,
ExpectedResult : true ,
} ,
{
Desc : "doubleSlash" ,
Redirect : "//redirect" ,
ExpectedResult : false ,
} ,
{
Desc : "validHTTP" ,
Redirect : "http://foo.bar/redirect" ,
ExpectedResult : true ,
} ,
{
Desc : "validHTTPS" ,
Redirect : "https://foo.bar/redirect" ,
ExpectedResult : true ,
} ,
{
Desc : "invalidHTTPSubdomain" ,
Redirect : "http://baz.foo.bar/redirect" ,
ExpectedResult : false ,
} ,
{
Desc : "invalidHTTPSSubdomain" ,
Redirect : "https://baz.foo.bar/redirect" ,
ExpectedResult : false ,
} ,
{
Desc : "validHTTPSubdomain" ,
Redirect : "http://baz.bar.foo/redirect" ,
ExpectedResult : true ,
} ,
{
Desc : "validHTTPSSubdomain" ,
Redirect : "https://baz.bar.foo/redirect" ,
ExpectedResult : true ,
} ,
{
Desc : "validHTTPDomain" ,
Redirect : "http://bar.foo/redirect" ,
ExpectedResult : true ,
} ,
{
Desc : "invalidHTTP1" ,
Redirect : "http://foo.bar.evil.corp/redirect" ,
ExpectedResult : false ,
} ,
{
Desc : "invalidHTTPS1" ,
Redirect : "https://foo.bar.evil.corp/redirect" ,
ExpectedResult : false ,
} ,
{
Desc : "invalidHTTP2" ,
Redirect : "http://evil.corp/redirect?rd=foo.bar" ,
ExpectedResult : false ,
} ,
{
Desc : "invalidHTTPS2" ,
Redirect : "https://evil.corp/redirect?rd=foo.bar" ,
ExpectedResult : false ,
} ,
{
Desc : "invalidPort" ,
Redirect : "https://evil.corp:3838/redirect" ,
ExpectedResult : false ,
} ,
{
Desc : "invalidEmptyPort" ,
Redirect : "http://foo.bar:3838/redirect" ,
ExpectedResult : false ,
} ,
{
Desc : "invalidEmptyPortSubdomain" ,
Redirect : "http://baz.bar.foo:3838/redirect" ,
ExpectedResult : false ,
} ,
{
Desc : "validSpecificPort" ,
Redirect : "http://port.bar:8080/redirect" ,
ExpectedResult : true ,
} ,
{
Desc : "invalidSpecificPort" ,
Redirect : "http://port.bar:3838/redirect" ,
ExpectedResult : false ,
} ,
{
Desc : "validSpecificPortSubdomain" ,
Redirect : "http://foo.sub.port.bar:8080/redirect" ,
ExpectedResult : true ,
} ,
{
Desc : "invalidSpecificPortSubdomain" ,
Redirect : "http://foo.sub.port.bar:3838/redirect" ,
ExpectedResult : false ,
} ,
{
Desc : "validAnyPort1" ,
Redirect : "http://anyport.bar:8080/redirect" ,
ExpectedResult : true ,
} ,
{
Desc : "validAnyPort2" ,
Redirect : "http://anyport.bar:8081/redirect" ,
ExpectedResult : true ,
} ,
{
Desc : "validAnyPortSubdomain1" ,
Redirect : "http://a.sub.anyport.bar:8080/redirect" ,
ExpectedResult : true ,
} ,
{
Desc : "validAnyPortSubdomain2" ,
Redirect : "http://a.sub.anyport.bar:8081/redirect" ,
ExpectedResult : true ,
} ,
2020-05-06 14:42:02 +03:00
{
Desc : "openRedirect1" ,
Redirect : "/\\evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectSpace1" ,
Redirect : "/ /evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectSpace2" ,
Redirect : "/ \\evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectTab1" ,
Redirect : "/\t/evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectTab2" ,
Redirect : "/\t\\evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectVerticalTab1" ,
Redirect : "/\v/evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectVerticalTab2" ,
Redirect : "/\v\\evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectNewLine1" ,
Redirect : "/\n/evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectNewLine2" ,
Redirect : "/\n\\evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectCarriageReturn1" ,
Redirect : "/\r/evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectCarriageReturn2" ,
Redirect : "/\r\\evil.com" ,
ExpectedResult : false ,
} ,
2020-06-27 14:07:24 +03:00
{
Desc : "openRedirectTripleTab" ,
Redirect : "/\t\t/\t/evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectTripleTab2" ,
Redirect : "/\t\t\\\t/evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectQuadTab1" ,
Redirect : "/\t\t/\t\t\\evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectQuadTab2" ,
Redirect : "/\t\t\\\t\t/evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectPeriod1" ,
Redirect : "/./\\evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectPeriod2" ,
Redirect : "/./../../\\evil.com" ,
ExpectedResult : false ,
} ,
{
Desc : "openRedirectDoubleTab" ,
Redirect : "/\t/\t\\evil.com" ,
ExpectedResult : false ,
} ,
2019-10-23 16:38:44 +03:00
}
2019-10-12 23:47:23 +03:00
2019-10-23 16:38:44 +03:00
for _ , tc := range testCases {
t . Run ( tc . Desc , func ( t * testing . T ) {
result := proxy . IsValidRedirect ( tc . Redirect )
2019-10-12 23:47:23 +03:00
2019-10-23 16:38:44 +03:00
if result != tc . ExpectedResult {
t . Errorf ( "expected %t got %t" , tc . ExpectedResult , result )
}
} )
}
2017-10-02 12:19:24 +03:00
}
2020-06-27 14:07:24 +03:00
func TestOpenRedirects ( t * testing . T ) {
2020-07-13 22:56:05 +03:00
opts := baseTestOptions ( )
2020-06-27 14:07:24 +03:00
// Should match domains that are exactly foo.bar and any subdomain of bar.foo
opts . WhitelistDomains = [ ] string {
"foo.bar" ,
".bar.foo" ,
"port.bar:8080" ,
".sub.port.bar:8080" ,
"anyport.bar:*" ,
".sub.anyport.bar:*" ,
"www.whitelisteddomain.tld" ,
}
2020-06-27 14:41:46 +03:00
err := validation . Validate ( opts )
2020-05-25 16:00:49 +03:00
assert . NoError ( t , err )
2020-06-27 14:07:24 +03:00
2020-05-25 16:00:49 +03:00
proxy , err := NewOAuthProxy ( opts , func ( string ) bool { return true } )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
2020-06-27 14:07:24 +03:00
2020-10-06 15:34:32 +03:00
file , err := os . Open ( "./testdata/openredirects.txt" )
2020-06-27 14:07:24 +03:00
if err != nil {
t . Fatal ( err )
}
2020-07-13 22:56:05 +03:00
defer func ( t * testing . T ) {
if err := file . Close ( ) ; err != nil {
t . Fatal ( err )
}
} ( t )
2020-06-27 14:07:24 +03:00
scanner := bufio . NewScanner ( file )
for scanner . Scan ( ) {
rd := scanner . Text ( )
t . Run ( rd , func ( t * testing . T ) {
rdUnescaped , err := url . QueryUnescape ( rd )
if err != nil {
t . Fatal ( err )
}
if proxy . IsValidRedirect ( rdUnescaped ) {
t . Errorf ( "Expected %q to not be valid (unescaped: %q)" , rd , rdUnescaped )
}
} )
}
if err := scanner . Err ( ) ; err != nil {
t . Fatal ( err )
}
}
2015-11-16 06:08:30 +03:00
type TestProvider struct {
* providers . ProviderData
2019-06-21 02:57:20 +03:00
EmailAddress string
ValidToken bool
GroupValidator func ( string ) bool
2015-11-16 06:08:30 +03:00
}
2020-05-05 18:53:33 +03:00
var _ providers . Provider = ( * TestProvider ) ( nil )
2018-11-29 17:26:41 +03:00
func NewTestProvider ( providerURL * url . URL , emailAddress string ) * TestProvider {
2015-11-16 06:08:30 +03:00
return & TestProvider {
ProviderData : & providers . ProviderData {
ProviderName : "Test Provider" ,
LoginURL : & url . URL {
Scheme : "http" ,
2018-11-29 17:26:41 +03:00
Host : providerURL . Host ,
2015-11-16 06:08:30 +03:00
Path : "/oauth/authorize" ,
} ,
RedeemURL : & url . URL {
Scheme : "http" ,
2018-11-29 17:26:41 +03:00
Host : providerURL . Host ,
2015-11-16 06:08:30 +03:00
Path : "/oauth/token" ,
} ,
ProfileURL : & url . URL {
Scheme : "http" ,
2018-11-29 17:26:41 +03:00
Host : providerURL . Host ,
2015-11-16 06:08:30 +03:00
Path : "/api/v1/profile" ,
} ,
Scope : "profile.email" ,
} ,
2018-11-29 17:26:41 +03:00
EmailAddress : emailAddress ,
2019-06-21 02:57:20 +03:00
GroupValidator : func ( s string ) bool {
return true
} ,
2015-11-16 06:08:30 +03:00
}
}
2020-09-27 21:46:29 +03:00
func ( tp * TestProvider ) GetEmailAddress ( _ context . Context , _ * sessions . SessionState ) ( string , error ) {
2015-11-16 06:08:30 +03:00
return tp . EmailAddress , nil
}
2020-09-27 21:46:29 +03:00
func ( tp * TestProvider ) ValidateSessionState ( _ context . Context , _ * sessions . SessionState ) bool {
2015-11-16 06:08:30 +03:00
return tp . ValidToken
}
2020-09-27 01:17:26 +03:00
func Test_redeemCode ( t * testing . T ) {
opts := baseTestOptions ( )
err := validation . Validate ( opts )
assert . NoError ( t , err )
proxy , err := NewOAuthProxy ( opts , func ( string ) bool { return true } )
if err != nil {
t . Fatal ( err )
}
_ , err = proxy . redeemCode ( context . Background ( ) , "www.example.com" , "" )
assert . Error ( t , err )
}
func Test_enrichSession ( t * testing . T ) {
const (
sessionUser = "Mr Session"
sessionEmail = "session@example.com"
providerEmail = "provider@example.com"
)
testCases := map [ string ] struct {
session * sessions . SessionState
expectedUser string
expectedEmail string
} {
"Session already has enrichable fields" : {
session : & sessions . SessionState {
User : sessionUser ,
Email : sessionEmail ,
} ,
expectedUser : sessionUser ,
expectedEmail : sessionEmail ,
} ,
"Session is missing Email and GetEmailAddress is implemented" : {
session : & sessions . SessionState {
User : sessionUser ,
} ,
expectedUser : sessionUser ,
expectedEmail : providerEmail ,
} ,
"Session is missing User and GetUserName is not implemented" : {
session : & sessions . SessionState {
Email : sessionEmail ,
} ,
expectedUser : "" ,
expectedEmail : sessionEmail ,
} ,
}
for name , tc := range testCases {
t . Run ( name , func ( t * testing . T ) {
opts := baseTestOptions ( )
err := validation . Validate ( opts )
assert . NoError ( t , err )
// intentionally set after validation.Validate(opts) since it will clobber
// our TestProvider and call `providers.New` defaulting to `providers.GoogleProvider`
opts . SetProvider ( NewTestProvider ( & url . URL { Host : "www.example.com" } , providerEmail ) )
proxy , err := NewOAuthProxy ( opts , func ( string ) bool { return true } )
if err != nil {
t . Fatal ( err )
}
2020-09-27 21:46:29 +03:00
err = proxy . enrichSessionState ( context . Background ( ) , tc . session )
2020-09-27 01:17:26 +03:00
assert . NoError ( t , err )
assert . Equal ( t , tc . expectedUser , tc . session . User )
assert . Equal ( t , tc . expectedEmail , tc . session . Email )
} )
}
}
2015-07-24 12:17:43 +03:00
func TestBasicAuthPassword ( t * testing . T ) {
2018-11-29 17:26:41 +03:00
providerServer := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2019-02-10 19:37:45 +03:00
logger . Printf ( "%#v" , r )
2018-11-29 17:26:41 +03:00
var payload string
switch r . URL . Path {
2015-07-24 12:17:43 +03:00
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 )
2020-07-13 22:56:05 +03:00
_ , err := w . Write ( [ ] byte ( payload ) )
if err != nil {
t . Fatal ( err )
}
2015-07-24 12:17:43 +03:00
} ) )
2020-07-29 22:10:14 +03:00
basicAuthPassword := "This is a secure password"
2020-05-25 16:00:49 +03:00
opts := baseTestOptions ( )
2020-05-26 22:06:27 +03:00
opts . UpstreamServers = options . Upstreams {
{
ID : providerServer . URL ,
Path : "/" ,
URI : providerServer . URL ,
} ,
}
2020-04-12 16:00:59 +03:00
opts . Cookie . Secure = false
2020-07-29 22:10:14 +03:00
opts . InjectRequestHeaders = [ ] options . Header {
{
Name : "Authorization" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "email" ,
BasicAuthPassword : & options . SecretSource {
Value : [ ] byte ( base64 . StdEncoding . EncodeToString ( [ ] byte ( basicAuthPassword ) ) ) ,
} ,
} ,
} ,
} ,
} ,
}
2020-07-13 22:56:05 +03:00
err := validation . Validate ( opts )
assert . NoError ( t , err )
2015-07-24 12:17:43 +03:00
2018-11-29 17:26:41 +03:00
providerURL , _ := url . Parse ( providerServer . URL )
2019-05-07 12:36:00 +03:00
const emailAddress = "john.doe@example.com"
2015-07-24 12:17:43 +03:00
2020-04-13 15:50:34 +03:00
opts . SetProvider ( NewTestProvider ( providerURL , emailAddress ) )
2020-05-25 16:00:49 +03:00
proxy , err := NewOAuthProxy ( opts , func ( email string ) bool {
2018-11-29 17:26:41 +03:00
return email == emailAddress
2015-07-24 12:17:43 +03:00
} )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
2015-07-24 12:17:43 +03:00
2020-07-29 22:10:14 +03:00
// Save the required session
2015-07-24 12:17:43 +03:00
rw := httptest . NewRecorder ( )
2020-07-29 22:10:14 +03:00
req , _ := http . NewRequest ( "GET" , "/" , nil )
err = proxy . sessionStore . Save ( rw , req , & sessions . SessionState {
Email : emailAddress ,
2015-07-24 12:17:43 +03:00
} )
2020-07-29 22:10:14 +03:00
assert . NoError ( t , err )
// Extract the cookie value to inject into the test request
cookie := rw . Header ( ) . Values ( "Set-Cookie" ) [ 0 ]
2015-07-24 12:17:43 +03:00
2020-07-29 22:10:14 +03:00
req , _ = http . NewRequest ( "GET" , "/" , nil )
req . Header . Set ( "Cookie" , cookie )
2015-07-24 12:17:43 +03:00
rw = httptest . NewRecorder ( )
proxy . ServeHTTP ( rw , req )
2017-03-28 04:14:38 +03:00
2019-05-09 12:14:01 +03:00
// The username in the basic auth credentials is expected to be equal to the email address from the
// auth response, so we use the same variable here.
2020-07-29 22:10:14 +03:00
expectedHeader := "Basic " + base64 . StdEncoding . EncodeToString ( [ ] byte ( emailAddress + ":" + basicAuthPassword ) )
2015-07-24 12:17:43 +03:00
assert . Equal ( t , expectedHeader , rw . Body . String ( ) )
2018-11-29 17:26:41 +03:00
providerServer . Close ( )
2015-07-24 12:17:43 +03:00
}
2020-07-29 22:10:14 +03:00
func TestPassGroupsHeadersWithGroups ( t * testing . T ) {
2020-05-25 16:00:49 +03:00
opts := baseTestOptions ( )
2020-07-29 22:10:14 +03:00
opts . InjectRequestHeaders = [ ] options . Header {
{
Name : "X-Forwarded-Groups" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "groups" ,
} ,
} ,
} ,
} ,
2020-03-04 01:27:43 +03:00
}
2020-02-29 20:38:32 +03:00
2020-07-28 21:42:09 +03:00
err := validation . Validate ( opts )
assert . NoError ( t , err )
const emailAddress = "john.doe@example.com"
const userName = "9fcab5c9b889a557"
groups := [ ] string { "a" , "b" }
created := time . Now ( )
session := & sessions . SessionState {
User : userName ,
Groups : groups ,
Email : emailAddress ,
AccessToken : "oauth_token" ,
CreatedAt : & created ,
}
2020-07-29 22:10:14 +03:00
proxy , err := NewOAuthProxy ( opts , func ( email string ) bool {
return email == emailAddress
} )
assert . NoError ( t , err )
2020-07-15 01:46:44 +03:00
2020-07-29 22:10:14 +03:00
// Save the required session
rw := httptest . NewRecorder ( )
req , _ := http . NewRequest ( "GET" , "/" , nil )
err = proxy . sessionStore . Save ( rw , req , session )
assert . NoError ( t , err )
2020-07-15 01:46:44 +03:00
2020-07-29 22:10:14 +03:00
// Extract the cookie value to inject into the test request
cookie := rw . Header ( ) . Values ( "Set-Cookie" ) [ 0 ]
2020-07-15 01:46:44 +03:00
2020-07-29 22:10:14 +03:00
req , _ = http . NewRequest ( "GET" , "/" , nil )
req . Header . Set ( "Cookie" , cookie )
rw = httptest . NewRecorder ( )
proxy . ServeHTTP ( rw , req )
2020-07-15 01:46:44 +03:00
2020-07-29 22:10:14 +03:00
assert . Equal ( t , groups , req . Header [ "X-Forwarded-Groups" ] )
2020-07-15 01:46:44 +03:00
}
2015-04-03 03:57:17 +03:00
type PassAccessTokenTest struct {
2018-11-29 17:26:41 +03:00
providerServer * httptest . Server
proxy * OAuthProxy
2020-04-13 15:50:34 +03:00
opts * options . Options
2015-04-03 03:57:17 +03:00
}
type PassAccessTokenTestOptions struct {
PassAccessToken bool
2020-05-26 22:06:27 +03:00
ProxyUpstream options . Upstream
2015-04-03 03:57:17 +03:00
}
2020-07-13 22:56:05 +03:00
func NewPassAccessTokenTest ( opts PassAccessTokenTestOptions ) ( * PassAccessTokenTest , error ) {
patt := & PassAccessTokenTest { }
2015-04-03 03:57:17 +03:00
2020-07-13 22:56:05 +03:00
patt . providerServer = httptest . NewServer (
2015-04-03 03:57:17 +03:00
http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2018-11-29 17:26:41 +03:00
var payload string
switch r . URL . Path {
2015-04-03 03:57:17 +03:00
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 )
2020-07-13 22:56:05 +03:00
_ , err := w . Write ( [ ] byte ( payload ) )
if err != nil {
panic ( err )
}
2015-04-03 03:57:17 +03:00
} ) )
2020-07-13 22:56:05 +03:00
patt . opts = baseTestOptions ( )
2020-05-26 22:06:27 +03:00
patt . opts . UpstreamServers = options . Upstreams {
{
ID : patt . providerServer . URL ,
Path : "/" ,
URI : patt . providerServer . URL ,
} ,
2020-07-13 22:56:05 +03:00
}
2020-05-26 22:06:27 +03:00
if opts . ProxyUpstream . ID != "" {
patt . opts . UpstreamServers = append ( patt . opts . UpstreamServers , opts . ProxyUpstream )
}
2020-07-13 22:56:05 +03:00
patt . opts . Cookie . Secure = false
2020-07-29 22:10:14 +03:00
if opts . PassAccessToken {
patt . opts . InjectRequestHeaders = [ ] options . Header {
{
Name : "X-Forwarded-Access-Token" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "access_token" ,
} ,
} ,
} ,
} ,
}
}
2020-07-13 22:56:05 +03:00
err := validation . Validate ( patt . opts )
if err != nil {
return nil , err
}
providerURL , _ := url . Parse ( patt . providerServer . URL )
2018-11-29 17:26:41 +03:00
const emailAddress = "michael.bland@gsa.gov"
2015-04-03 03:57:17 +03:00
2020-07-13 22:56:05 +03:00
patt . opts . SetProvider ( NewTestProvider ( providerURL , emailAddress ) )
patt . proxy , err = NewOAuthProxy ( patt . opts , func ( email string ) bool {
2018-11-29 17:26:41 +03:00
return email == emailAddress
2015-04-03 03:57:17 +03:00
} )
2020-05-25 16:00:49 +03:00
if err != nil {
2020-07-13 22:56:05 +03:00
return nil , err
2020-05-25 16:00:49 +03:00
}
2020-07-13 22:56:05 +03:00
return patt , nil
2015-04-03 03:57:17 +03:00
}
2018-11-29 17:26:41 +03:00
func ( patTest * PassAccessTokenTest ) Close ( ) {
patTest . providerServer . Close ( )
2015-04-03 03:57:17 +03:00
}
2018-11-29 17:26:41 +03:00
func ( patTest * PassAccessTokenTest ) getCallbackEndpoint ( ) ( httpCode int ,
2015-04-07 04:35:58 +03:00
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 , ""
}
2018-11-29 17:26:41 +03:00
req . AddCookie ( patTest . proxy . MakeCSRFCookie ( req , "nonce" , time . Hour , time . Now ( ) ) )
patTest . proxy . ServeHTTP ( rw , req )
2020-04-14 11:36:44 +03:00
return rw . Code , rw . Header ( ) . Values ( "Set-Cookie" ) [ 1 ]
2015-04-03 03:57:17 +03:00
}
2019-10-04 16:39:31 +03:00
// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
// and cookie and returns body and status code.
func ( patTest * PassAccessTokenTest ) getEndpointWithCookie ( cookie string , endpoint string ) ( httpCode int , accessToken string ) {
2018-11-29 17:26:41 +03:00
cookieName := patTest . proxy . CookieName
2015-04-03 03:57:17 +03:00
var value string
2018-11-29 17:26:41 +03:00
keyPrefix := cookieName + "="
2015-04-03 03:57:17 +03:00
for _ , field := range strings . Split ( cookie , "; " ) {
2018-11-29 17:26:41 +03:00
value = strings . TrimPrefix ( field , keyPrefix )
2015-04-03 03:57:17 +03:00
if value != field {
break
} else {
value = ""
}
}
if value == "" {
return 0 , ""
}
2019-10-04 16:39:31 +03:00
req , err := http . NewRequest ( "GET" , endpoint , strings . NewReader ( "" ) )
2015-04-03 03:57:17 +03:00
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 ( )
2018-11-29 17:26:41 +03:00
patTest . proxy . ServeHTTP ( rw , req )
2015-04-03 03:57:17 +03:00
return rw . Code , rw . Body . String ( )
}
func TestForwardAccessTokenUpstream ( t * testing . T ) {
2020-07-13 22:56:05 +03:00
patTest , err := NewPassAccessTokenTest ( PassAccessTokenTestOptions {
2015-04-03 03:57:17 +03:00
PassAccessToken : true ,
} )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
t . Cleanup ( patTest . Close )
2015-04-03 03:57:17 +03:00
// A successful validation will redirect and set the auth cookie.
2018-11-29 17:26:41 +03:00
code , cookie := patTest . getCallbackEndpoint ( )
2017-03-28 04:14:38 +03:00
if code != 302 {
t . Fatalf ( "expected 302; got %d" , code )
}
2020-07-13 22:56:05 +03:00
assert . NotNil ( t , cookie )
2015-04-03 03:57:17 +03:00
// 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.
2019-10-04 16:39:31 +03:00
code , payload := patTest . getEndpointWithCookie ( 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 )
}
2019-09-19 12:03:38 +03:00
func TestStaticProxyUpstream ( t * testing . T ) {
2020-07-13 22:56:05 +03:00
patTest , err := NewPassAccessTokenTest ( PassAccessTokenTestOptions {
2019-09-19 12:03:38 +03:00
PassAccessToken : true ,
2020-05-26 22:06:27 +03:00
ProxyUpstream : options . Upstream {
ID : "static-proxy" ,
Path : "/static-proxy" ,
Static : true ,
} ,
2019-09-19 12:03:38 +03:00
} )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
t . Cleanup ( patTest . Close )
2019-09-19 12:03:38 +03:00
// A successful validation will redirect and set the auth cookie.
code , cookie := patTest . getCallbackEndpoint ( )
if code != 302 {
t . Fatalf ( "expected 302; got %d" , code )
}
assert . NotEqual ( t , nil , cookie )
2020-04-14 11:36:44 +03:00
// Now we make a regular request against the upstream proxy; And validate
2019-10-04 16:39:31 +03:00
// the returned status code through the static proxy.
code , payload := patTest . getEndpointWithCookie ( cookie , "/static-proxy" )
2019-09-19 12:03:38 +03:00
if code != 200 {
t . Fatalf ( "expected 200; got %d" , code )
}
assert . Equal ( t , "Authenticated" , payload )
}
2015-04-03 03:57:17 +03:00
func TestDoNotForwardAccessTokenUpstream ( t * testing . T ) {
2020-07-13 22:56:05 +03:00
patTest , err := NewPassAccessTokenTest ( PassAccessTokenTestOptions {
2015-04-03 03:57:17 +03:00
PassAccessToken : false ,
} )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
t . Cleanup ( patTest . Close )
2015-04-03 03:57:17 +03:00
// A successful validation will redirect and set the auth cookie.
2018-11-29 17:26:41 +03:00
code , cookie := patTest . 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.
2019-10-04 16:39:31 +03:00
code , payload := patTest . getEndpointWithCookie ( 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 {
2020-04-13 15:50:34 +03:00
opts * options . Options
2018-11-29 17:26:41 +03:00
proxy * OAuthProxy
signInRegexp * regexp . Regexp
signInProviderRegexp * 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
2020-07-13 22:56:05 +03:00
func NewSignInPageTest ( skipProvider bool ) ( * SignInPageTest , error ) {
2018-11-29 17:26:41 +03:00
var sipTest SignInPageTest
2015-04-07 05:10:03 +03:00
2020-05-25 16:00:49 +03:00
sipTest . opts = baseTestOptions ( )
2018-11-29 17:26:41 +03:00
sipTest . opts . SkipProviderButton = skipProvider
2020-07-13 22:56:05 +03:00
err := validation . Validate ( sipTest . opts )
if err != nil {
return nil , err
}
2015-04-07 05:10:03 +03:00
2020-05-25 16:00:49 +03:00
sipTest . proxy , err = NewOAuthProxy ( sipTest . opts , func ( email string ) bool {
2015-04-07 05:10:03 +03:00
return true
} )
2020-05-25 16:00:49 +03:00
if err != nil {
2020-07-13 22:56:05 +03:00
return nil , err
2020-05-25 16:00:49 +03:00
}
2018-11-29 17:26:41 +03:00
sipTest . signInRegexp = regexp . MustCompile ( signInRedirectPattern )
sipTest . signInProviderRegexp = regexp . MustCompile ( signInSkipProvider )
2015-04-07 05:10:03 +03:00
2020-07-13 22:56:05 +03:00
return & sipTest , nil
2015-04-07 05:10:03 +03:00
}
2018-11-29 17:26:41 +03:00
func ( sipTest * SignInPageTest ) GetEndpoint ( endpoint string ) ( int , string ) {
2015-04-07 05:10:03 +03:00
rw := httptest . NewRecorder ( )
req , _ := http . NewRequest ( "GET" , endpoint , strings . NewReader ( "" ) )
2018-11-29 17:26:41 +03:00
sipTest . proxy . ServeHTTP ( rw , req )
2015-04-07 05:10:03 +03:00
return rw . Code , rw . Body . String ( )
}
func TestSignInPageIncludesTargetRedirect ( t * testing . T ) {
2020-07-13 22:56:05 +03:00
sipTest , err := NewSignInPageTest ( false )
if err != nil {
t . Fatal ( err )
}
2015-04-07 05:10:03 +03:00
const endpoint = "/some/random/endpoint"
2018-11-29 17:26:41 +03:00
code , body := sipTest . GetEndpoint ( endpoint )
2015-04-07 05:10:03 +03:00
assert . Equal ( t , 403 , code )
2018-11-29 17:26:41 +03:00
match := sipTest . signInRegexp . FindStringSubmatch ( body )
2015-04-07 05:10:03 +03:00
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 ) {
2020-07-13 22:56:05 +03:00
sipTest , err := NewSignInPageTest ( false )
if err != nil {
t . Fatal ( err )
}
2018-11-29 17:26:41 +03:00
code , body := sipTest . GetEndpoint ( "/oauth2/sign_in" )
2015-04-07 05:10:03 +03:00
assert . Equal ( t , 200 , code )
2018-11-29 17:26:41 +03:00
match := sipTest . signInRegexp . FindStringSubmatch ( body )
2015-04-07 05:10:03 +03:00
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 ) {
2020-07-13 22:56:05 +03:00
sipTest , err := NewSignInPageTest ( true )
if err != nil {
t . Fatal ( err )
}
endpoint := "/some/random/endpoint"
2017-06-22 01:02:34 +03:00
2018-11-29 17:26:41 +03:00
code , body := sipTest . GetEndpoint ( endpoint )
2017-06-22 01:02:34 +03:00
assert . Equal ( t , 302 , code )
2018-11-29 17:26:41 +03:00
match := sipTest . signInProviderRegexp . FindStringSubmatch ( body )
2017-06-22 01:02:34 +03:00
if match == nil {
t . Fatal ( "Did not find pattern in body: " +
signInSkipProvider + "\nBody:\n" + body )
}
}
func TestSignInPageSkipProviderDirect ( t * testing . T ) {
2020-07-13 22:56:05 +03:00
sipTest , err := NewSignInPageTest ( true )
if err != nil {
t . Fatal ( err )
}
endpoint := "/sign_in"
2017-06-22 01:02:34 +03:00
2018-11-29 17:26:41 +03:00
code , body := sipTest . GetEndpoint ( endpoint )
2017-06-22 01:02:34 +03:00
assert . Equal ( t , 302 , code )
2018-11-29 17:26:41 +03:00
match := sipTest . signInProviderRegexp . FindStringSubmatch ( body )
2017-06-22 01:02:34 +03:00
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 {
2020-04-13 15:50:34 +03:00
opts * options . Options
2018-11-29 17:26:41 +03:00
proxy * OAuthProxy
rw * httptest . ResponseRecorder
req * http . Request
validateUser bool
2015-05-08 18:52:03 +03:00
}
2015-05-13 04:48:13 +03:00
type ProcessCookieTestOpts struct {
2018-11-29 17:26:41 +03:00
providerValidateCookieResponse bool
2015-05-13 04:48:13 +03:00
}
2020-04-13 15:50:34 +03:00
type OptionsModifier func ( * options . Options )
2019-05-07 18:13:55 +03:00
2020-07-13 22:56:05 +03:00
func NewProcessCookieTest ( opts ProcessCookieTestOpts , modifiers ... OptionsModifier ) ( * ProcessCookieTest , error ) {
2018-11-29 17:26:41 +03:00
var pcTest ProcessCookieTest
2015-05-08 18:52:03 +03:00
2020-05-25 16:00:49 +03:00
pcTest . opts = baseTestOptions ( )
2019-05-07 18:13:55 +03:00
for _ , modifier := range modifiers {
modifier ( pcTest . opts )
}
2015-05-09 23:08:55 +03:00
// First, set the CookieRefresh option so proxy.AesCipher is created,
// needed to encrypt the access_token.
2020-04-12 16:00:59 +03:00
pcTest . opts . Cookie . Refresh = time . Hour
2020-07-13 22:56:05 +03:00
err := validation . Validate ( pcTest . opts )
if err != nil {
return nil , err
}
2015-05-08 18:52:03 +03:00
2020-05-25 16:00:49 +03:00
pcTest . proxy , err = NewOAuthProxy ( pcTest . opts , func ( email string ) bool {
2018-11-29 17:26:41 +03:00
return pcTest . validateUser
2015-05-08 18:52:03 +03:00
} )
2020-05-25 16:00:49 +03:00
if err != nil {
2020-07-13 22:56:05 +03:00
return nil , err
2020-05-25 16:00:49 +03:00
}
2018-11-29 17:26:41 +03:00
pcTest . proxy . provider = & TestProvider {
2020-09-27 05:00:44 +03:00
ProviderData : & providers . ProviderData { } ,
ValidToken : opts . providerValidateCookieResponse ,
2015-05-13 04:48:13 +03:00
}
2020-09-27 05:00:44 +03:00
pcTest . proxy . provider . ( * TestProvider ) . SetAllowedGroups ( pcTest . opts . AllowedGroups )
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.
2018-11-29 17:26:41 +03:00
pcTest . proxy . CookieRefresh = time . Duration ( 0 )
pcTest . rw = httptest . NewRecorder ( )
pcTest . req , _ = http . NewRequest ( "GET" , "/" , strings . NewReader ( "" ) )
pcTest . validateUser = true
2020-07-13 22:56:05 +03:00
return & pcTest , nil
2015-05-08 18:52:03 +03:00
}
2020-07-13 22:56:05 +03:00
func NewProcessCookieTestWithDefaults ( ) ( * ProcessCookieTest , error ) {
2015-05-13 04:48:13 +03:00
return NewProcessCookieTest ( ProcessCookieTestOpts {
2018-11-29 17:26:41 +03:00
providerValidateCookieResponse : true ,
2015-05-13 04:48:13 +03:00
} )
2015-05-09 22:09:31 +03:00
}
2020-07-13 22:56:05 +03:00
func NewProcessCookieTestWithOptionsModifiers ( modifiers ... OptionsModifier ) ( * ProcessCookieTest , error ) {
2019-05-07 18:13:55 +03:00
return NewProcessCookieTest ( ProcessCookieTestOpts {
providerValidateCookieResponse : true ,
} , modifiers ... )
}
func ( p * ProcessCookieTest ) SaveSession ( s * sessions . SessionState ) error {
err := p . proxy . SaveSession ( p . rw , p . req , s )
2015-06-23 14:23:39 +03:00
if err != nil {
return err
}
2019-05-07 18:13:55 +03:00
for _ , cookie := range p . rw . Result ( ) . Cookies ( ) {
p . req . AddCookie ( cookie )
2018-01-28 01:48:52 +03:00
}
2015-06-23 14:23:39 +03:00
return nil
2015-05-08 18:52:03 +03:00
}
2019-05-07 18:13:55 +03:00
func ( p * ProcessCookieTest ) LoadCookiedSession ( ) ( * sessions . SessionState , error ) {
2015-06-23 14:23:39 +03:00
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 ) {
2020-07-13 22:56:05 +03:00
pcTest , err := NewProcessCookieTestWithDefaults ( )
if err != nil {
t . Fatal ( err )
}
2015-05-09 22:09:31 +03:00
2020-05-30 10:53:38 +03:00
created := time . Now ( )
startSession := & sessions . SessionState { Email : "john.doe@example.com" , AccessToken : "my_access_token" , CreatedAt : & created }
2020-07-13 22:56:05 +03:00
err = pcTest . SaveSession ( startSession )
assert . NoError ( t , err )
2015-06-23 14:23:39 +03:00
2019-05-07 18:13:55 +03:00
session , err := pcTest . LoadCookiedSession ( )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
2015-06-23 14:23:39 +03:00
assert . Equal ( t , startSession . Email , session . Email )
2020-05-12 18:04:51 +03:00
assert . Equal ( t , "" , session . User )
2015-06-23 14:23:39 +03:00
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 ) {
2020-07-13 22:56:05 +03:00
pcTest , err := NewProcessCookieTestWithDefaults ( )
if err != nil {
t . Fatal ( err )
}
2015-05-08 17:00:57 +03:00
2019-05-07 18:13:55 +03:00
session , err := pcTest . LoadCookiedSession ( )
2020-07-13 22:56:05 +03:00
assert . Error ( t , err , "cookie \"_oauth2_proxy\" not present" )
2015-06-23 14:23:39 +03:00
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 ) {
2020-07-13 22:56:05 +03:00
pcTest , err := NewProcessCookieTestWithOptionsModifiers ( func ( opts * options . Options ) {
2020-04-12 16:00:59 +03:00
opts . Cookie . Expire = time . Duration ( 23 ) * time . Hour
2019-05-07 18:13:55 +03:00
} )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
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
2020-05-30 10:53:38 +03:00
startSession := & sessions . SessionState { Email : "michael.bland@gsa.gov" , AccessToken : "my_access_token" , CreatedAt : & reference }
2020-07-13 22:56:05 +03:00
err = pcTest . SaveSession ( startSession )
assert . NoError ( t , err )
2015-05-09 22:09:31 +03:00
2019-05-07 18:13:55 +03:00
session , err := pcTest . LoadCookiedSession ( )
2015-06-23 14:23:39 +03:00
assert . Equal ( t , nil , err )
2019-05-07 18:13:55 +03:00
if session . Age ( ) < time . Duration ( - 2 ) * time . Hour {
t . Errorf ( "cookie too young %v" , session . Age ( ) )
2015-06-23 14:23:39 +03:00
}
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 ) {
2020-07-13 22:56:05 +03:00
pcTest , err := NewProcessCookieTestWithOptionsModifiers ( func ( opts * options . Options ) {
2020-04-12 16:00:59 +03:00
opts . Cookie . Expire = time . Duration ( 24 ) * time . Hour
2019-05-07 18:13:55 +03:00
} )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
2015-06-22 22:10:08 +03:00
reference := time . Now ( ) . Add ( time . Duration ( 25 ) * time . Hour * - 1 )
2020-05-30 10:53:38 +03:00
startSession := & sessions . SessionState { Email : "michael.bland@gsa.gov" , AccessToken : "my_access_token" , CreatedAt : & reference }
2020-07-13 22:56:05 +03:00
err = pcTest . SaveSession ( startSession )
assert . NoError ( t , err )
2015-06-22 22:10:08 +03:00
2019-05-07 18:13:55 +03:00
session , err := pcTest . LoadCookiedSession ( )
2015-06-23 14:23:39 +03:00
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 ) {
2020-07-13 22:56:05 +03:00
pcTest , err := NewProcessCookieTestWithOptionsModifiers ( func ( opts * options . Options ) {
2020-04-12 16:00:59 +03:00
opts . Cookie . Expire = time . Duration ( 24 ) * time . Hour
2019-05-07 18:13:55 +03:00
} )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
2015-06-22 22:10:08 +03:00
reference := time . Now ( ) . Add ( time . Duration ( 25 ) * time . Hour * - 1 )
2020-05-30 10:53:38 +03:00
startSession := & sessions . SessionState { Email : "michael.bland@gsa.gov" , AccessToken : "my_access_token" , CreatedAt : & reference }
2020-07-13 22:56:05 +03:00
err = pcTest . SaveSession ( startSession )
assert . NoError ( t , err )
2015-06-22 22:10:08 +03:00
2018-11-29 17:26:41 +03:00
pcTest . proxy . CookieRefresh = time . Hour
2019-05-07 18:13:55 +03:00
session , err := pcTest . LoadCookiedSession ( )
2015-06-23 14:23:39 +03:00
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
2020-07-13 22:56:05 +03:00
func NewUserInfoEndpointTest ( ) ( * ProcessCookieTest , error ) {
pcTest , err := NewProcessCookieTestWithDefaults ( )
if err != nil {
return nil , err
}
2019-11-08 01:38:36 +03:00
pcTest . req , _ = http . NewRequest ( "GET" ,
pcTest . opts . ProxyPrefix + "/userinfo" , nil )
2020-07-13 22:56:05 +03:00
return pcTest , nil
2019-11-08 01:38:36 +03:00
}
func TestUserInfoEndpointAccepted ( t * testing . T ) {
2020-11-26 05:55:16 +03:00
testCases := [ ] struct {
name string
session * sessions . SessionState
expectedResponse string
} {
{
name : "Full session" ,
session : & sessions . SessionState {
User : "john.doe" ,
Email : "john.doe@example.com" ,
Groups : [ ] string { "example" , "groups" } ,
AccessToken : "my_access_token" ,
} ,
expectedResponse : "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n" ,
} ,
{
name : "Minimal session" ,
session : & sessions . SessionState {
User : "john.doe" ,
Email : "john.doe@example.com" ,
Groups : [ ] string { "example" , "groups" } ,
} ,
expectedResponse : "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n" ,
} ,
{
name : "No groups" ,
session : & sessions . SessionState {
User : "john.doe" ,
Email : "john.doe@example.com" ,
AccessToken : "my_access_token" ,
} ,
expectedResponse : "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\"}\n" ,
} ,
{
name : "With Preferred Username" ,
session : & sessions . SessionState {
User : "john.doe" ,
PreferredUsername : "john" ,
Email : "john.doe@example.com" ,
Groups : [ ] string { "example" , "groups" } ,
AccessToken : "my_access_token" ,
} ,
expectedResponse : "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"],\"preferredUsername\":\"john\"}\n" ,
} ,
2020-07-13 22:56:05 +03:00
}
2020-11-26 05:55:16 +03:00
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
test , err := NewUserInfoEndpointTest ( )
if err != nil {
t . Fatal ( err )
}
err = test . SaveSession ( tc . session )
assert . NoError ( t , err )
2020-10-24 05:35:15 +03:00
2020-11-26 05:55:16 +03:00
test . proxy . ServeHTTP ( test . rw , test . req )
assert . Equal ( t , http . StatusOK , test . rw . Code )
bodyBytes , _ := ioutil . ReadAll ( test . rw . Body )
assert . Equal ( t , tc . expectedResponse , string ( bodyBytes ) )
} )
}
2019-11-08 01:38:36 +03:00
}
func TestUserInfoEndpointUnauthorizedOnNoCookieSetError ( t * testing . T ) {
2020-07-13 22:56:05 +03:00
test , err := NewUserInfoEndpointTest ( )
if err != nil {
t . Fatal ( err )
}
2019-11-08 01:38:36 +03:00
test . proxy . ServeHTTP ( test . rw , test . req )
assert . Equal ( t , http . StatusUnauthorized , test . rw . Code )
}
2020-07-13 22:56:05 +03:00
func NewAuthOnlyEndpointTest ( modifiers ... OptionsModifier ) ( * ProcessCookieTest , error ) {
pcTest , err := NewProcessCookieTestWithOptionsModifiers ( modifiers ... )
if err != nil {
return nil , err
}
2018-11-29 17:26:41 +03:00
pcTest . req , _ = http . NewRequest ( "GET" ,
pcTest . opts . ProxyPrefix + "/auth" , nil )
2020-07-13 22:56:05 +03:00
return pcTest , nil
2015-10-08 16:27:00 +03:00
}
func TestAuthOnlyEndpointAccepted ( t * testing . T ) {
2020-07-13 22:56:05 +03:00
test , err := NewAuthOnlyEndpointTest ( )
if err != nil {
t . Fatal ( err )
}
2020-05-30 10:53:38 +03:00
created := time . Now ( )
2019-05-05 15:33:13 +03:00
startSession := & sessions . SessionState {
2020-05-30 10:53:38 +03:00
Email : "michael.bland@gsa.gov" , AccessToken : "my_access_token" , CreatedAt : & created }
2020-07-13 22:56:05 +03:00
err = test . SaveSession ( startSession )
assert . NoError ( t , err )
2015-10-08 16:27:00 +03:00
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 ) {
2020-07-13 22:56:05 +03:00
test , err := NewAuthOnlyEndpointTest ( )
if err != nil {
t . Fatal ( err )
}
2015-10-08 16:27:00 +03:00
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 ) {
2020-07-13 22:56:05 +03:00
test , err := NewAuthOnlyEndpointTest ( func ( opts * options . Options ) {
2020-04-12 16:00:59 +03:00
opts . Cookie . Expire = time . Duration ( 24 ) * time . Hour
2019-05-07 18:13:55 +03:00
} )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
2015-10-08 16:27:00 +03:00
reference := time . Now ( ) . Add ( time . Duration ( 25 ) * time . Hour * - 1 )
2019-05-05 15:33:13 +03:00
startSession := & sessions . SessionState {
2020-05-30 10:53:38 +03:00
Email : "michael.bland@gsa.gov" , AccessToken : "my_access_token" , CreatedAt : & reference }
2020-07-13 22:56:05 +03:00
err = test . SaveSession ( startSession )
assert . NoError ( t , err )
2015-10-08 16:27:00 +03:00
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 ) {
2020-07-13 22:56:05 +03:00
test , err := NewAuthOnlyEndpointTest ( )
if err != nil {
t . Fatal ( err )
}
2020-05-30 10:53:38 +03:00
created := time . Now ( )
2019-05-05 15:33:13 +03:00
startSession := & sessions . SessionState {
2020-05-30 10:53:38 +03:00
Email : "michael.bland@gsa.gov" , AccessToken : "my_access_token" , CreatedAt : & created }
2020-07-13 22:56:05 +03:00
err = test . SaveSession ( startSession )
assert . NoError ( t , err )
2018-11-29 17:26:41 +03:00
test . validateUser = false
2015-10-08 16:27:00 +03:00
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 ) {
2018-11-29 17:26:41 +03:00
var pcTest ProcessCookieTest
2016-10-20 15:19:59 +03:00
2020-05-25 16:00:49 +03:00
pcTest . opts = baseTestOptions ( )
2020-07-29 22:10:14 +03:00
pcTest . opts . InjectResponseHeaders = [ ] options . Header {
{
Name : "X-Auth-Request-User" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "user" ,
} ,
} ,
} ,
} ,
{
Name : "X-Auth-Request-Email" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "email" ,
} ,
} ,
} ,
} ,
{
Name : "X-Auth-Request-Groups" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "groups" ,
} ,
} ,
} ,
} ,
{
Name : "X-Forwarded-Preferred-Username" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "preferred_username" ,
} ,
} ,
} ,
} ,
}
2020-07-28 21:42:09 +03:00
pcTest . opts . AllowedGroups = [ ] string { "oauth_groups" }
2020-05-25 16:00:49 +03:00
err := validation . Validate ( pcTest . opts )
2020-07-13 22:56:05 +03:00
assert . NoError ( t , err )
2016-10-20 15:19:59 +03:00
2020-05-25 16:00:49 +03:00
pcTest . proxy , err = NewOAuthProxy ( pcTest . opts , func ( email string ) bool {
2018-11-29 17:26:41 +03:00
return pcTest . validateUser
2016-10-20 15:19:59 +03:00
} )
2020-05-25 16:00:49 +03:00
if err != nil {
2020-07-13 22:56:05 +03:00
t . Fatal ( err )
2020-05-25 16:00:49 +03:00
}
2018-11-29 17:26:41 +03:00
pcTest . proxy . provider = & TestProvider {
2020-09-27 05:00:44 +03:00
ProviderData : & providers . ProviderData { } ,
ValidToken : true ,
2016-10-20 15:19:59 +03:00
}
2018-11-29 17:26:41 +03:00
pcTest . validateUser = true
2016-10-20 15:19:59 +03:00
2018-11-29 17:26:41 +03:00
pcTest . rw = httptest . NewRecorder ( )
pcTest . req , _ = http . NewRequest ( "GET" ,
pcTest . opts . ProxyPrefix + "/auth" , nil )
2016-10-20 15:19:59 +03:00
2020-05-30 10:53:38 +03:00
created := time . Now ( )
2019-05-05 15:33:13 +03:00
startSession := & sessions . SessionState {
2020-07-28 21:42:09 +03:00
User : "oauth_user" , Groups : [ ] string { "oauth_groups" } , Email : "oauth_user@example.com" , AccessToken : "oauth_token" , CreatedAt : & created }
2020-07-13 22:56:05 +03:00
err = pcTest . SaveSession ( startSession )
assert . NoError ( t , err )
2016-10-20 15:19:59 +03:00
2018-11-29 17:26:41 +03:00
pcTest . proxy . ServeHTTP ( pcTest . rw , pcTest . req )
assert . Equal ( t , http . StatusAccepted , pcTest . rw . Code )
2020-04-14 11:36:44 +03:00
assert . Equal ( t , "oauth_user" , pcTest . rw . Header ( ) . Get ( "X-Auth-Request-User" ) )
2020-07-28 21:42:09 +03:00
assert . Equal ( t , startSession . Groups , pcTest . rw . Header ( ) . Values ( "X-Auth-Request-Groups" ) )
2020-04-14 11:36:44 +03:00
assert . Equal ( t , "oauth_user@example.com" , pcTest . rw . Header ( ) . Get ( "X-Auth-Request-Email" ) )
2016-10-20 15:19:59 +03:00
}
2020-04-10 16:41:28 +03:00
func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders ( t * testing . T ) {
var pcTest ProcessCookieTest
2020-05-25 16:00:49 +03:00
pcTest . opts = baseTestOptions ( )
2020-07-29 22:10:14 +03:00
pcTest . opts . InjectResponseHeaders = [ ] options . Header {
{
Name : "X-Auth-Request-User" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "user" ,
} ,
} ,
} ,
} ,
{
Name : "X-Auth-Request-Email" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "email" ,
} ,
} ,
} ,
} ,
{
Name : "X-Auth-Request-Groups" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "groups" ,
} ,
} ,
} ,
} ,
{
Name : "X-Forwarded-Preferred-Username" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "preferred_username" ,
} ,
} ,
} ,
} ,
{
Name : "Authorization" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "user" ,
BasicAuthPassword : & options . SecretSource {
Value : [ ] byte ( base64 . StdEncoding . EncodeToString ( [ ] byte ( "This is a secure password" ) ) ) ,
} ,
} ,
} ,
} ,
} ,
}
2020-07-13 22:56:05 +03:00
err := validation . Validate ( pcTest . opts )
assert . NoError ( t , err )
2020-04-10 16:41:28 +03:00
2020-05-25 16:00:49 +03:00
pcTest . proxy , err = NewOAuthProxy ( pcTest . opts , func ( email string ) bool {
2020-04-10 16:41:28 +03:00
return pcTest . validateUser
} )
2020-05-25 16:00:49 +03:00
if err != nil {
2020-07-13 22:56:05 +03:00
t . Fatal ( err )
2020-05-25 16:00:49 +03:00
}
2020-04-10 16:41:28 +03:00
pcTest . proxy . provider = & TestProvider {
2020-09-27 05:00:44 +03:00
ProviderData : & providers . ProviderData { } ,
ValidToken : true ,
2020-04-10 16:41:28 +03:00
}
pcTest . validateUser = true
pcTest . rw = httptest . NewRecorder ( )
pcTest . req , _ = http . NewRequest ( "GET" ,
pcTest . opts . ProxyPrefix + "/auth" , nil )
2020-05-30 10:53:38 +03:00
created := time . Now ( )
2020-04-10 16:41:28 +03:00
startSession := & sessions . SessionState {
2020-05-30 10:53:38 +03:00
User : "oauth_user" , Email : "oauth_user@example.com" , AccessToken : "oauth_token" , CreatedAt : & created }
2020-07-13 22:56:05 +03:00
err = pcTest . SaveSession ( startSession )
assert . NoError ( t , err )
2020-04-10 16:41:28 +03:00
pcTest . proxy . ServeHTTP ( pcTest . rw , pcTest . req )
assert . Equal ( t , http . StatusAccepted , pcTest . rw . Code )
2020-04-14 11:36:44 +03:00
assert . Equal ( t , "oauth_user" , pcTest . rw . Header ( ) . Values ( "X-Auth-Request-User" ) [ 0 ] )
assert . Equal ( t , "oauth_user@example.com" , pcTest . rw . Header ( ) . Values ( "X-Auth-Request-Email" ) [ 0 ] )
2020-07-29 22:10:14 +03:00
expectedHeader := "Basic " + base64 . StdEncoding . EncodeToString ( [ ] byte ( "oauth_user:This is a secure password" ) )
2020-04-14 11:36:44 +03:00
assert . Equal ( t , expectedHeader , pcTest . rw . Header ( ) . Values ( "Authorization" ) [ 0 ] )
2020-04-10 16:41:28 +03:00
}
func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders ( t * testing . T ) {
var pcTest ProcessCookieTest
2020-05-25 16:00:49 +03:00
pcTest . opts = baseTestOptions ( )
2020-07-29 22:10:14 +03:00
pcTest . opts . InjectResponseHeaders = [ ] options . Header {
{
Name : "X-Auth-Request-User" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "user" ,
} ,
} ,
} ,
} ,
{
Name : "X-Auth-Request-Email" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "email" ,
} ,
} ,
} ,
} ,
{
Name : "X-Auth-Request-Groups" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "groups" ,
} ,
} ,
} ,
} ,
{
Name : "X-Forwarded-Preferred-Username" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "preferred_username" ,
} ,
} ,
} ,
} ,
}
2020-07-13 22:56:05 +03:00
err := validation . Validate ( pcTest . opts )
assert . NoError ( t , err )
2020-04-10 16:41:28 +03:00
2020-05-25 16:00:49 +03:00
pcTest . proxy , err = NewOAuthProxy ( pcTest . opts , func ( email string ) bool {
2020-04-10 16:41:28 +03:00
return pcTest . validateUser
} )
2020-05-25 16:00:49 +03:00
if err != nil {
2020-07-13 22:56:05 +03:00
t . Fatal ( err )
2020-05-25 16:00:49 +03:00
}
2020-04-10 16:41:28 +03:00
pcTest . proxy . provider = & TestProvider {
2020-09-27 05:00:44 +03:00
ProviderData : & providers . ProviderData { } ,
ValidToken : true ,
2020-04-10 16:41:28 +03:00
}
pcTest . validateUser = true
pcTest . rw = httptest . NewRecorder ( )
pcTest . req , _ = http . NewRequest ( "GET" ,
pcTest . opts . ProxyPrefix + "/auth" , nil )
2020-05-30 10:53:38 +03:00
created := time . Now ( )
2020-04-10 16:41:28 +03:00
startSession := & sessions . SessionState {
2020-05-30 10:53:38 +03:00
User : "oauth_user" , Email : "oauth_user@example.com" , AccessToken : "oauth_token" , CreatedAt : & created }
2020-07-13 22:56:05 +03:00
err = pcTest . SaveSession ( startSession )
assert . NoError ( t , err )
2020-04-10 16:41:28 +03:00
pcTest . proxy . ServeHTTP ( pcTest . rw , pcTest . req )
assert . Equal ( t , http . StatusAccepted , pcTest . rw . Code )
2020-04-14 11:36:44 +03:00
assert . Equal ( t , "oauth_user" , pcTest . rw . Header ( ) . Values ( "X-Auth-Request-User" ) [ 0 ] )
assert . Equal ( t , "oauth_user@example.com" , pcTest . rw . Header ( ) . Values ( "X-Auth-Request-Email" ) [ 0 ] )
assert . Equal ( t , 0 , len ( pcTest . rw . Header ( ) . Values ( "Authorization" ) ) , "should not have Authorization header entries" )
2020-04-10 16:41:28 +03:00
}
2017-04-07 14:55:48 +03:00
func TestAuthSkippedForPreflightRequests ( t * testing . T ) {
2020-09-24 06:16:05 +03:00
upstreamServer := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2017-04-07 14:55:48 +03:00
w . WriteHeader ( 200 )
2020-07-13 22:56:05 +03:00
_ , err := w . Write ( [ ] byte ( "response" ) )
if err != nil {
t . Fatal ( err )
}
2017-04-07 14:55:48 +03:00
} ) )
2020-09-24 06:16:05 +03:00
t . Cleanup ( upstreamServer . Close )
2017-04-07 14:55:48 +03:00
2020-05-25 16:00:49 +03:00
opts := baseTestOptions ( )
2020-05-26 22:06:27 +03:00
opts . UpstreamServers = options . Upstreams {
{
2020-09-24 06:16:05 +03:00
ID : upstreamServer . URL ,
2020-05-26 22:06:27 +03:00
Path : "/" ,
2020-09-24 06:16:05 +03:00
URI : upstreamServer . URL ,
2020-05-26 22:06:27 +03:00
} ,
}
2017-04-07 14:55:48 +03:00
opts . SkipAuthPreflight = true
2020-07-13 22:56:05 +03:00
err := validation . Validate ( opts )
assert . NoError ( t , err )
2017-04-07 14:55:48 +03:00
2020-09-24 06:16:05 +03:00
upstreamURL , _ := url . Parse ( upstreamServer . URL )
2020-04-13 15:50:34 +03:00
opts . SetProvider ( NewTestProvider ( upstreamURL , "" ) )
2017-04-07 14:55:48 +03:00
2020-05-25 16:00:49 +03:00
proxy , err := NewOAuthProxy ( opts , func ( string ) bool { return false } )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
2017-04-07 14:55:48 +03:00
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 )
2020-07-13 22:56:05 +03:00
var msg string
switch result {
case hmacauth . ResultNoSignature :
msg = "no signature received"
case hmacauth . ResultMatch :
msg = "signatures match"
case hmacauth . ResultMismatch :
msg = fmt . Sprintf (
"signatures do not match:\n received: %s\n computed: %s" ,
headerSig ,
computedSig )
default :
panic ( "unknown result value: " + result . String ( ) )
}
_ , err := w . Write ( [ ] byte ( msg ) )
if err != nil {
panic ( err )
2015-11-16 06:08:30 +03:00
}
}
type SignatureTest struct {
2020-04-13 15:50:34 +03:00
opts * options . Options
2015-11-16 06:08:30 +03:00
upstream * httptest . Server
2018-11-29 17:26:41 +03:00
upstreamHost string
2015-11-16 06:08:30 +03:00
provider * httptest . Server
header http . Header
rw * httptest . ResponseRecorder
authenticator * SignatureAuthenticator
}
2020-07-13 22:56:05 +03:00
func NewSignatureTest ( ) ( * SignatureTest , error ) {
2020-05-25 16:00:49 +03:00
opts := baseTestOptions ( )
2015-11-16 06:08:30 +03:00
opts . EmailDomains = [ ] string { "acm.org" }
authenticator := & SignatureAuthenticator { }
2020-09-24 06:16:05 +03:00
upstreamServer := httptest . NewServer (
2015-11-16 06:08:30 +03:00
http . HandlerFunc ( authenticator . Authenticate ) )
2020-09-24 06:16:05 +03:00
upstreamURL , err := url . Parse ( upstreamServer . URL )
2020-07-13 22:56:05 +03:00
if err != nil {
return nil , err
}
2020-05-26 22:06:27 +03:00
opts . UpstreamServers = options . Upstreams {
{
2020-09-24 06:16:05 +03:00
ID : upstreamServer . URL ,
2020-05-26 22:06:27 +03:00
Path : "/" ,
2020-09-24 06:16:05 +03:00
URI : upstreamServer . URL ,
2020-05-26 22:06:27 +03:00
} ,
}
2015-11-16 06:08:30 +03:00
providerHandler := func ( w http . ResponseWriter , r * http . Request ) {
2020-07-13 22:56:05 +03:00
_ , err := w . Write ( [ ] byte ( ` { "access_token": "my_auth_token"} ` ) )
if err != nil {
panic ( err )
}
2015-11-16 06:08:30 +03:00
}
provider := httptest . NewServer ( http . HandlerFunc ( providerHandler ) )
2020-07-13 22:56:05 +03:00
providerURL , err := url . Parse ( provider . URL )
if err != nil {
return nil , err
}
2020-04-13 15:50:34 +03:00
opts . SetProvider ( NewTestProvider ( providerURL , "mbland@acm.org" ) )
2015-11-16 06:08:30 +03:00
return & SignatureTest {
opts ,
2020-09-24 06:16:05 +03:00
upstreamServer ,
2018-11-29 17:26:41 +03:00
upstreamURL . Host ,
2015-11-16 06:08:30 +03:00
provider ,
make ( http . Header ) ,
httptest . NewRecorder ( ) ,
authenticator ,
2020-07-13 22:56:05 +03:00
} , nil
2015-11-16 06:08:30 +03:00
}
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
}
2020-07-13 22:56:05 +03:00
func ( st * SignatureTest ) MakeRequestWithExpectedKey ( method , body , key string ) error {
2020-04-13 15:50:34 +03:00
err := validation . Validate ( st . opts )
2015-11-16 06:08:30 +03:00
if err != nil {
2020-07-13 22:56:05 +03:00
return err
2015-11-16 06:08:30 +03:00
}
2020-05-25 16:00:49 +03:00
proxy , err := NewOAuthProxy ( st . opts , func ( email string ) bool { return true } )
if err != nil {
2020-07-13 22:56:05 +03:00
return err
2020-05-25 16:00:49 +03:00
}
2015-11-16 06:08:30 +03:00
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
2019-05-05 15:33:13 +03:00
state := & sessions . SessionState {
2015-11-16 06:08:30 +03:00
Email : "mbland@acm.org" , AccessToken : "my_access_token" }
2019-05-07 19:01:52 +03:00
err = proxy . SaveSession ( st . rw , req , state )
2015-11-16 06:08:30 +03:00
if err != nil {
2020-07-13 22:56:05 +03:00
return err
2015-11-16 06:08:30 +03:00
}
2019-05-07 19:01:52 +03:00
for _ , c := range st . rw . Result ( ) . Cookies ( ) {
2018-01-28 01:48:52 +03:00
req . AddCookie ( c )
}
2015-11-16 06:08:30 +03:00
// This is used by the upstream to validate the signature.
st . authenticator . auth = hmacauth . NewHmacAuth (
2020-05-26 22:06:27 +03:00
crypto . SHA1 , [ ] byte ( key ) , upstream . SignatureHeader , upstream . SignatureHeaders )
2015-11-16 06:08:30 +03:00
proxy . ServeHTTP ( st . rw , req )
2020-07-13 22:56:05 +03:00
return nil
2015-11-16 06:08:30 +03:00
}
2020-07-13 22:56:05 +03:00
func TestRequestSignature ( t * testing . T ) {
testCases := map [ string ] struct {
method string
body string
key string
resp string
} {
"No request signature" : {
method : "GET" ,
body : "" ,
key : "" ,
resp : "no signature received" ,
} ,
"Get request" : {
method : "GET" ,
body : "" ,
key : "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d" ,
resp : "signatures match" ,
} ,
"Post request" : {
method : "POST" ,
body : ` { "hello": "world!" } ` ,
key : "d90df39e2d19282840252612dd7c81421a372f61" ,
resp : "signatures match" ,
} ,
}
for name , tc := range testCases {
t . Run ( name , func ( t * testing . T ) {
st , err := NewSignatureTest ( )
if err != nil {
t . Fatal ( err )
}
t . Cleanup ( st . Close )
if tc . key != "" {
st . opts . SignatureKey = fmt . Sprintf ( "sha1:%s" , tc . key )
}
err = st . MakeRequestWithExpectedKey ( tc . method , tc . body , tc . key )
assert . NoError ( t , err )
assert . Equal ( t , 200 , st . rw . Code )
assert . Equal ( t , tc . resp , st . rw . Body . String ( ) )
} )
}
2015-11-16 06:08:30 +03:00
}
2019-01-29 15:13:02 +03:00
func TestGetRedirect ( t * testing . T ) {
2020-05-25 16:00:49 +03:00
opts := baseTestOptions ( )
err := validation . Validate ( opts )
assert . NoError ( t , err )
require . NotEmpty ( t , opts . ProxyPrefix )
proxy , err := NewOAuthProxy ( opts , func ( s string ) bool { return false } )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
2019-01-29 15:13:02 +03:00
tests := [ ] struct {
name string
url string
expectedRedirect string
} {
{
name : "request outside of ProxyPrefix redirects to original URL" ,
url : "/foo/bar" ,
expectedRedirect : "/foo/bar" ,
} ,
2020-07-21 18:38:13 +03:00
{
name : "request with query preserves query" ,
url : "/foo?bar" ,
expectedRedirect : "/foo?bar" ,
} ,
2019-01-29 15:13:02 +03:00
{
name : "request under ProxyPrefix redirects to root" ,
url : proxy . ProxyPrefix + "/foo/bar" ,
expectedRedirect : "/" ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
req , _ := http . NewRequest ( "GET" , tt . url , nil )
redirect , err := proxy . GetRedirect ( req )
assert . NoError ( t , err )
assert . Equal ( t , tt . expectedRedirect , redirect )
} )
}
}
2019-01-30 13:13:12 +03:00
type ajaxRequestTest struct {
2020-04-13 15:50:34 +03:00
opts * options . Options
2019-01-30 13:13:12 +03:00
proxy * OAuthProxy
}
2020-07-13 22:56:05 +03:00
func newAjaxRequestTest ( ) ( * ajaxRequestTest , error ) {
2019-01-30 13:13:12 +03:00
test := & ajaxRequestTest { }
2020-05-25 16:00:49 +03:00
test . opts = baseTestOptions ( )
2020-07-13 22:56:05 +03:00
err := validation . Validate ( test . opts )
if err != nil {
return nil , err
}
2020-05-25 16:00:49 +03:00
test . proxy , err = NewOAuthProxy ( test . opts , func ( email string ) bool {
2019-01-30 13:13:12 +03:00
return true
} )
2020-05-25 16:00:49 +03:00
if err != nil {
2020-07-13 22:56:05 +03:00
return nil , err
2020-05-25 16:00:49 +03:00
}
2020-07-13 22:56:05 +03:00
return test , nil
2019-01-30 13:13:12 +03:00
}
func ( test * ajaxRequestTest ) getEndpoint ( endpoint string , header http . Header ) ( int , http . Header , error ) {
rw := httptest . NewRecorder ( )
req , err := http . NewRequest ( http . MethodGet , endpoint , strings . NewReader ( "" ) )
if err != nil {
return 0 , nil , err
}
req . Header = header
test . proxy . ServeHTTP ( rw , req )
return rw . Code , rw . Header ( ) , nil
}
func testAjaxUnauthorizedRequest ( t * testing . T , header http . Header ) {
2020-07-13 22:56:05 +03:00
test , err := newAjaxRequestTest ( )
if err != nil {
t . Fatal ( err )
}
2019-01-31 18:22:30 +03:00
endpoint := "/test"
2019-01-30 13:13:12 +03:00
code , rh , err := test . getEndpoint ( endpoint , header )
assert . NoError ( t , err )
assert . Equal ( t , http . StatusUnauthorized , code )
mime := rh . Get ( "Content-Type" )
2019-01-31 18:22:30 +03:00
assert . Equal ( t , applicationJSON , mime )
2019-01-30 13:13:12 +03:00
}
func TestAjaxUnauthorizedRequest1 ( t * testing . T ) {
header := make ( http . Header )
2019-01-31 18:22:30 +03:00
header . Add ( "accept" , applicationJSON )
2019-01-30 13:13:12 +03:00
testAjaxUnauthorizedRequest ( t , header )
}
func TestAjaxUnauthorizedRequest2 ( t * testing . T ) {
header := make ( http . Header )
2019-01-31 18:22:30 +03:00
header . Add ( "Accept" , applicationJSON )
2019-01-30 13:13:12 +03:00
testAjaxUnauthorizedRequest ( t , header )
}
func TestAjaxForbiddendRequest ( t * testing . T ) {
2020-07-13 22:56:05 +03:00
test , err := newAjaxRequestTest ( )
if err != nil {
t . Fatal ( err )
}
2019-01-31 18:22:30 +03:00
endpoint := "/test"
2019-01-30 13:13:12 +03:00
header := make ( http . Header )
code , rh , err := test . getEndpoint ( endpoint , header )
assert . NoError ( t , err )
assert . Equal ( t , http . StatusForbidden , code )
mime := rh . Get ( "Content-Type" )
2019-01-31 18:22:30 +03:00
assert . NotEqual ( t , applicationJSON , mime )
2019-01-30 13:13:12 +03:00
}
2019-03-15 10:18:37 +03:00
func TestClearSplitCookie ( t * testing . T ) {
2020-05-25 16:00:49 +03:00
opts := baseTestOptions ( )
opts . Cookie . Secret = base64CookieSecret
2020-04-12 16:00:59 +03:00
opts . Cookie . Name = "oauth2"
opts . Cookie . Domains = [ ] string { "abc" }
2020-07-13 22:56:05 +03:00
err := validation . Validate ( opts )
assert . NoError ( t , err )
store , err := sessionscookie . NewCookieSessionStore ( & opts . Session , & opts . Cookie )
if err != nil {
t . Fatal ( err )
}
2020-04-12 16:00:59 +03:00
p := OAuthProxy { CookieName : opts . Cookie . Name , CookieDomains : opts . Cookie . Domains , sessionStore : store }
2019-03-15 10:18:37 +03:00
var rw = httptest . NewRecorder ( )
req := httptest . NewRequest ( "get" , "/" , nil )
req . AddCookie ( & http . Cookie {
Name : "test1" ,
Value : "test1" ,
} )
req . AddCookie ( & http . Cookie {
Name : "oauth2_0" ,
Value : "oauth2_0" ,
} )
req . AddCookie ( & http . Cookie {
Name : "oauth2_1" ,
Value : "oauth2_1" ,
} )
2020-07-13 22:56:05 +03:00
err = p . ClearSessionCookie ( rw , req )
assert . NoError ( t , err )
2019-03-15 10:18:37 +03:00
header := rw . Header ( )
assert . Equal ( t , 2 , len ( header [ "Set-Cookie" ] ) , "should have 3 set-cookie header entries" )
}
func TestClearSingleCookie ( t * testing . T ) {
2020-05-25 16:00:49 +03:00
opts := baseTestOptions ( )
2020-04-12 16:00:59 +03:00
opts . Cookie . Name = "oauth2"
opts . Cookie . Domains = [ ] string { "abc" }
2020-07-13 22:56:05 +03:00
store , err := sessionscookie . NewCookieSessionStore ( & opts . Session , & opts . Cookie )
if err != nil {
t . Fatal ( err )
}
2020-04-12 16:00:59 +03:00
p := OAuthProxy { CookieName : opts . Cookie . Name , CookieDomains : opts . Cookie . Domains , sessionStore : store }
2019-03-15 10:18:37 +03:00
var rw = httptest . NewRecorder ( )
req := httptest . NewRequest ( "get" , "/" , nil )
req . AddCookie ( & http . Cookie {
Name : "test1" ,
Value : "test1" ,
} )
req . AddCookie ( & http . Cookie {
Name : "oauth2" ,
Value : "oauth2" ,
} )
2020-07-13 22:56:05 +03:00
err = p . ClearSessionCookie ( rw , req )
assert . NoError ( t , err )
2019-03-15 10:18:37 +03:00
header := rw . Header ( )
assert . Equal ( t , 1 , len ( header [ "Set-Cookie" ] ) , "should have 1 set-cookie header entries" )
}
2019-04-24 18:25:29 +03:00
2019-04-27 05:16:45 +03:00
type NoOpKeySet struct {
}
2020-09-27 21:46:29 +03:00
func ( NoOpKeySet ) VerifySignature ( _ context . Context , jwt string ) ( payload [ ] byte , err error ) {
2019-04-27 05:16:45 +03:00
splitStrings := strings . Split ( jwt , "." )
payloadString := splitStrings [ 1 ]
2020-04-14 11:36:44 +03:00
return base64 . RawURLEncoding . DecodeString ( payloadString )
2019-04-27 05:16:45 +03:00
}
func TestGetJwtSession ( t * testing . T ) {
/ * token payload :
{
"sub" : "1234567890" ,
"aud" : "https://test.myapp.com" ,
"name" : "John Doe" ,
"email" : "john@example.com" ,
"iss" : "https://issuer.example.com" ,
"iat" : 1553691215 ,
"exp" : 1912151821
}
* /
goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
"eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
"WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
"E1LCJleHAiOjE5MTIxNTE4MjF9." +
"rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
"OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
keyset := NoOpKeySet { }
verifier := oidc . NewVerifier ( "https://issuer.example.com" , keyset ,
& oidc . Config { ClientID : "https://test.myapp.com" , SkipExpiryCheck : true } )
2020-07-13 22:56:05 +03:00
test , err := NewAuthOnlyEndpointTest ( func ( opts * options . Options ) {
2020-07-29 22:10:14 +03:00
opts . InjectRequestHeaders = [ ] options . Header {
{
Name : "Authorization" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "id_token" ,
Prefix : "Bearer " ,
} ,
} ,
} ,
} ,
{
Name : "X-Forwarded-User" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "user" ,
} ,
} ,
} ,
} ,
{
Name : "X-Forwarded-Email" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "email" ,
} ,
} ,
} ,
} ,
}
opts . InjectResponseHeaders = [ ] options . Header {
{
Name : "Authorization" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "id_token" ,
Prefix : "Bearer " ,
} ,
} ,
} ,
} ,
{
Name : "X-Auth-Request-User" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "user" ,
} ,
} ,
} ,
} ,
{
Name : "X-Auth-Request-Email" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "email" ,
} ,
} ,
} ,
} ,
}
2019-06-21 02:57:20 +03:00
opts . SkipJwtBearerTokens = true
2020-04-13 15:50:34 +03:00
opts . SetJWTBearerVerifiers ( append ( opts . GetJWTBearerVerifiers ( ) , verifier ) )
2019-06-21 02:57:20 +03:00
} )
2020-07-13 22:56:05 +03:00
if err != nil {
t . Fatal ( err )
}
2019-06-21 02:57:20 +03:00
tp , _ := test . proxy . provider . ( * TestProvider )
tp . GroupValidator = func ( s string ) bool {
return true
}
2019-05-01 00:06:11 +03:00
authHeader := fmt . Sprintf ( "Bearer %s" , goodJwt )
2019-06-21 02:57:20 +03:00
test . req . Header = map [ string ] [ ] string {
2019-05-01 00:06:11 +03:00
"Authorization" : { authHeader } ,
2019-04-27 05:16:45 +03:00
}
2019-05-01 00:06:11 +03:00
2019-06-21 02:57:20 +03:00
test . proxy . ServeHTTP ( test . rw , test . req )
if test . rw . Code >= 400 {
t . Fatalf ( "expected 3xx got %d" , test . rw . Code )
}
2019-05-01 00:06:11 +03:00
2019-06-21 02:57:20 +03:00
// Check PassAuthorization, should overwrite Basic header
assert . Equal ( t , test . req . Header . Get ( "Authorization" ) , authHeader )
2020-06-01 18:56:50 +03:00
assert . Equal ( t , test . req . Header . Get ( "X-Forwarded-User" ) , "1234567890" )
2019-06-21 02:57:20 +03:00
assert . Equal ( t , test . req . Header . Get ( "X-Forwarded-Email" ) , "john@example.com" )
2019-05-01 00:06:11 +03:00
2019-06-21 02:57:20 +03:00
// SetAuthorization and SetXAuthRequest
assert . Equal ( t , test . rw . Header ( ) . Get ( "Authorization" ) , authHeader )
2020-06-01 18:56:50 +03:00
assert . Equal ( t , test . rw . Header ( ) . Get ( "X-Auth-Request-User" ) , "1234567890" )
2019-06-21 02:57:20 +03:00
assert . Equal ( t , test . rw . Header ( ) . Get ( "X-Auth-Request-Email" ) , "john@example.com" )
}
2019-05-01 00:06:11 +03:00
2020-04-09 17:39:07 +03:00
func Test_prepareNoCache ( t * testing . T ) {
handler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
prepareNoCache ( w )
} )
mux := http . NewServeMux ( )
mux . Handle ( "/" , handler )
rec := httptest . NewRecorder ( )
req := httptest . NewRequest ( http . MethodGet , "/" , nil )
mux . ServeHTTP ( rec , req )
for k , v := range noCacheHeaders {
assert . Equal ( t , rec . Header ( ) . Get ( k ) , v )
}
}
2020-07-06 13:04:31 +03:00
func Test_noCacheHeaders ( t * testing . T ) {
2020-09-24 06:16:05 +03:00
upstreamServer := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2020-07-13 22:56:05 +03:00
_ , err := w . Write ( [ ] byte ( "upstream" ) )
if err != nil {
t . Error ( err )
}
2020-04-09 17:39:07 +03:00
} ) )
2020-09-24 06:16:05 +03:00
t . Cleanup ( upstreamServer . Close )
2020-04-09 17:39:07 +03:00
2020-05-25 16:00:49 +03:00
opts := baseTestOptions ( )
2020-05-26 22:06:27 +03:00
opts . UpstreamServers = options . Upstreams {
{
2020-09-24 06:16:05 +03:00
ID : upstreamServer . URL ,
2020-05-26 22:06:27 +03:00
Path : "/" ,
2020-09-24 06:16:05 +03:00
URI : upstreamServer . URL ,
2020-05-26 22:06:27 +03:00
} ,
}
2020-04-09 17:39:07 +03:00
opts . SkipAuthRegex = [ ] string { ".*" }
2020-07-13 22:56:05 +03:00
err := validation . Validate ( opts )
2020-05-25 16:00:49 +03:00
assert . NoError ( t , err )
2020-07-13 22:56:05 +03:00
proxy , err := NewOAuthProxy ( opts , func ( _ string ) bool { return true } )
if err != nil {
t . Fatal ( err )
}
2020-04-09 17:39:07 +03:00
2020-07-06 13:04:31 +03:00
t . Run ( "not exist in response from upstream" , func ( t * testing . T ) {
rec := httptest . NewRecorder ( )
req := httptest . NewRequest ( http . MethodGet , "/upstream" , nil )
proxy . ServeHTTP ( rec , req )
2020-04-09 17:39:07 +03:00
2020-07-06 13:04:31 +03:00
assert . Equal ( t , http . StatusOK , rec . Code )
assert . Equal ( t , "upstream" , rec . Body . String ( ) )
2020-04-09 17:39:07 +03:00
2020-07-06 13:04:31 +03:00
// checking noCacheHeaders does not exists in response headers from upstream
for k := range noCacheHeaders {
assert . Equal ( t , "" , rec . Header ( ) . Get ( k ) )
}
} )
t . Run ( "has no-cache" , func ( t * testing . T ) {
tests := [ ] struct {
path string
hasNoCache bool
} {
{
path : "/oauth2/sign_in" ,
hasNoCache : true ,
} ,
{
path : "/oauth2/sign_out" ,
hasNoCache : true ,
} ,
{
path : "/oauth2/start" ,
hasNoCache : true ,
} ,
{
path : "/oauth2/callback" ,
hasNoCache : true ,
} ,
{
path : "/oauth2/auth" ,
hasNoCache : false ,
} ,
{
path : "/oauth2/userinfo" ,
hasNoCache : true ,
} ,
{
path : "/upstream" ,
hasNoCache : false ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . path , func ( t * testing . T ) {
rec := httptest . NewRecorder ( )
req := httptest . NewRequest ( http . MethodGet , tt . path , nil )
proxy . ServeHTTP ( rec , req )
cacheControl := rec . Result ( ) . Header . Get ( "Cache-Control" )
if tt . hasNoCache != ( strings . Contains ( cacheControl , "no-cache" ) ) {
t . Errorf ( ` unexpected "Cache-Control" header: %s ` , cacheControl )
}
} )
}
} )
2020-04-09 17:39:07 +03:00
}
2020-05-25 16:00:49 +03:00
func baseTestOptions ( ) * options . Options {
opts := options . NewOptions ( )
opts . Cookie . Secret = rawCookieSecret
2020-07-13 22:56:05 +03:00
opts . ClientID = clientID
opts . ClientSecret = clientSecret
2020-05-25 16:00:49 +03:00
opts . EmailDomains = [ ] string { "*" }
2020-07-29 22:10:14 +03:00
// Default injected headers for legacy configuration
opts . InjectRequestHeaders = [ ] options . Header {
{
Name : "Authorization" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "user" ,
BasicAuthPassword : & options . SecretSource {
Value : [ ] byte ( base64 . StdEncoding . EncodeToString ( [ ] byte ( "This is a secure password" ) ) ) ,
} ,
} ,
} ,
} ,
} ,
{
Name : "X-Forwarded-User" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "user" ,
} ,
} ,
} ,
} ,
{
Name : "X-Forwarded-Email" ,
Values : [ ] options . HeaderValue {
{
ClaimSource : & options . ClaimSource {
Claim : "email" ,
} ,
} ,
} ,
} ,
}
2020-05-25 16:00:49 +03:00
return opts
}
2020-07-11 13:10:58 +03:00
func TestTrustedIPs ( t * testing . T ) {
tests := [ ] struct {
name string
trustedIPs [ ] string
reverseProxy bool
realClientIPHeader string
req * http . Request
expectTrusted bool
} {
// Check unconfigured behavior.
{
name : "Default" ,
trustedIPs : nil ,
reverseProxy : false ,
realClientIPHeader : "X-Real-IP" , // Default value
req : func ( ) * http . Request {
req , _ := http . NewRequest ( "GET" , "/" , nil )
return req
} ( ) ,
expectTrusted : false ,
} ,
// Check using req.RemoteAddr (Options.ReverseProxy == false).
{
name : "WithRemoteAddr" ,
trustedIPs : [ ] string { "127.0.0.1" } ,
reverseProxy : false ,
realClientIPHeader : "X-Real-IP" , // Default value
req : func ( ) * http . Request {
req , _ := http . NewRequest ( "GET" , "/" , nil )
req . RemoteAddr = "127.0.0.1:43670"
return req
} ( ) ,
expectTrusted : true ,
} ,
// Check ignores req.RemoteAddr match when behind a reverse proxy / missing header.
{
name : "IgnoresRemoteAddrInReverseProxyMode" ,
trustedIPs : [ ] string { "127.0.0.1" } ,
reverseProxy : true ,
realClientIPHeader : "X-Real-IP" , // Default value
req : func ( ) * http . Request {
req , _ := http . NewRequest ( "GET" , "/" , nil )
req . RemoteAddr = "127.0.0.1:44324"
return req
} ( ) ,
expectTrusted : false ,
} ,
// Check successful trusting of localhost in IPv4.
{
name : "TrustsLocalhostInReverseProxyMode" ,
trustedIPs : [ ] string { "127.0.0.0/8" , "::1" } ,
reverseProxy : true ,
realClientIPHeader : "X-Forwarded-For" ,
req : func ( ) * http . Request {
req , _ := http . NewRequest ( "GET" , "/" , nil )
req . Header . Add ( "X-Forwarded-For" , "127.0.0.1" )
return req
} ( ) ,
expectTrusted : true ,
} ,
// Check successful trusting of localhost in IPv6.
{
name : "TrustsIP6LocalostInReverseProxyMode" ,
trustedIPs : [ ] string { "127.0.0.0/8" , "::1" } ,
reverseProxy : true ,
realClientIPHeader : "X-Forwarded-For" ,
req : func ( ) * http . Request {
req , _ := http . NewRequest ( "GET" , "/" , nil )
req . Header . Add ( "X-Forwarded-For" , "::1" )
return req
} ( ) ,
expectTrusted : true ,
} ,
// Check does not trust random IPv4 address.
{
name : "DoesNotTrustRandomIP4Address" ,
trustedIPs : [ ] string { "127.0.0.0/8" , "::1" } ,
reverseProxy : true ,
realClientIPHeader : "X-Forwarded-For" ,
req : func ( ) * http . Request {
req , _ := http . NewRequest ( "GET" , "/" , nil )
req . Header . Add ( "X-Forwarded-For" , "12.34.56.78" )
return req
} ( ) ,
expectTrusted : false ,
} ,
// Check does not trust random IPv6 address.
{
name : "DoesNotTrustRandomIP6Address" ,
trustedIPs : [ ] string { "127.0.0.0/8" , "::1" } ,
reverseProxy : true ,
realClientIPHeader : "X-Forwarded-For" ,
req : func ( ) * http . Request {
req , _ := http . NewRequest ( "GET" , "/" , nil )
req . Header . Add ( "X-Forwarded-For" , "::2" )
return req
} ( ) ,
expectTrusted : false ,
} ,
// Check respects correct header.
{
name : "RespectsCorrectHeaderInReverseProxyMode" ,
trustedIPs : [ ] string { "127.0.0.0/8" , "::1" } ,
reverseProxy : true ,
realClientIPHeader : "X-Forwarded-For" ,
req : func ( ) * http . Request {
req , _ := http . NewRequest ( "GET" , "/" , nil )
req . Header . Add ( "X-Real-IP" , "::1" )
return req
} ( ) ,
expectTrusted : false ,
} ,
// Check doesn't trust if garbage is provided.
{
name : "DoesNotTrustGarbageInReverseProxyMode" ,
trustedIPs : [ ] string { "127.0.0.0/8" , "::1" } ,
reverseProxy : true ,
realClientIPHeader : "X-Forwarded-For" ,
req : func ( ) * http . Request {
req , _ := http . NewRequest ( "GET" , "/" , nil )
req . Header . Add ( "X-Forwarded-For" , "adsfljk29242as!!" )
return req
} ( ) ,
expectTrusted : false ,
} ,
// Check doesn't trust if garbage is provided (no reverse-proxy).
{
name : "DoesNotTrustGarbage" ,
trustedIPs : [ ] string { "127.0.0.0/8" , "::1" } ,
reverseProxy : false ,
realClientIPHeader : "X-Real-IP" ,
req : func ( ) * http . Request {
req , _ := http . NewRequest ( "GET" , "/" , nil )
req . RemoteAddr = "adsfljk29242as!!"
return req
} ( ) ,
expectTrusted : false ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
opts := baseTestOptions ( )
2020-05-26 22:06:27 +03:00
opts . UpstreamServers = options . Upstreams {
{
ID : "static" ,
Path : "/" ,
Static : true ,
} ,
}
2020-07-11 13:10:58 +03:00
opts . TrustedIPs = tt . trustedIPs
opts . ReverseProxy = tt . reverseProxy
opts . RealClientIPHeader = tt . realClientIPHeader
2020-09-24 06:16:05 +03:00
err := validation . Validate ( opts )
assert . NoError ( t , err )
2020-07-11 13:10:58 +03:00
proxy , err := NewOAuthProxy ( opts , func ( string ) bool { return true } )
assert . NoError ( t , err )
rw := httptest . NewRecorder ( )
proxy . ServeHTTP ( rw , tt . req )
if tt . expectTrusted {
assert . Equal ( t , 200 , rw . Code )
} else {
assert . Equal ( t , 403 , rw . Code )
}
} )
}
}
2020-07-28 21:42:09 +03:00
2020-09-24 06:16:05 +03:00
func Test_buildRoutesAllowlist ( t * testing . T ) {
2020-10-05 22:39:44 +03:00
type expectedAllowedRoute struct {
method string
regexString string
}
2020-09-24 06:16:05 +03:00
testCases := [ ] struct {
2020-10-05 22:39:44 +03:00
name string
skipAuthRegex [ ] string
skipAuthRoutes [ ] string
expectedRoutes [ ] expectedAllowedRoute
shouldError bool
2020-09-24 06:16:05 +03:00
} {
{
2020-10-05 22:39:44 +03:00
name : "No skip auth configured" ,
skipAuthRegex : [ ] string { } ,
skipAuthRoutes : [ ] string { } ,
expectedRoutes : [ ] expectedAllowedRoute { } ,
shouldError : false ,
2020-09-24 06:16:05 +03:00
} ,
{
name : "Only skipAuthRegex configured" ,
skipAuthRegex : [ ] string {
"^/foo/bar" ,
"^/baz/[0-9]+/thing" ,
} ,
skipAuthRoutes : [ ] string { } ,
2020-10-05 22:39:44 +03:00
expectedRoutes : [ ] expectedAllowedRoute {
{
method : "" ,
regexString : "^/foo/bar" ,
} ,
{
method : "" ,
regexString : "^/baz/[0-9]+/thing" ,
} ,
2020-09-24 06:16:05 +03:00
} ,
shouldError : false ,
} ,
{
name : "Only skipAuthRoutes configured" ,
skipAuthRegex : [ ] string { } ,
skipAuthRoutes : [ ] string {
"GET=^/foo/bar" ,
"POST=^/baz/[0-9]+/thing" ,
"^/all/methods$" ,
"WEIRD=^/methods/are/allowed" ,
"PATCH=/second/equals?are=handled&just=fine" ,
} ,
2020-10-05 22:39:44 +03:00
expectedRoutes : [ ] expectedAllowedRoute {
{
method : "GET" ,
regexString : "^/foo/bar" ,
} ,
{
method : "POST" ,
regexString : "^/baz/[0-9]+/thing" ,
} ,
{
method : "" ,
regexString : "^/all/methods$" ,
} ,
{
method : "WEIRD" ,
regexString : "^/methods/are/allowed" ,
} ,
{
method : "PATCH" ,
regexString : "/second/equals?are=handled&just=fine" ,
} ,
2020-09-24 06:16:05 +03:00
} ,
shouldError : false ,
} ,
{
name : "Both skipAuthRegexes and skipAuthRoutes configured" ,
skipAuthRegex : [ ] string {
"^/foo/bar/regex" ,
"^/baz/[0-9]+/thing/regex" ,
} ,
skipAuthRoutes : [ ] string {
"GET=^/foo/bar" ,
"POST=^/baz/[0-9]+/thing" ,
"^/all/methods$" ,
} ,
2020-10-05 22:39:44 +03:00
expectedRoutes : [ ] expectedAllowedRoute {
{
method : "" ,
regexString : "^/foo/bar/regex" ,
} ,
{
method : "" ,
regexString : "^/baz/[0-9]+/thing/regex" ,
} ,
{
method : "GET" ,
regexString : "^/foo/bar" ,
} ,
{
method : "POST" ,
regexString : "^/baz/[0-9]+/thing" ,
} ,
{
method : "" ,
regexString : "^/all/methods$" ,
} ,
2020-09-24 06:16:05 +03:00
} ,
shouldError : false ,
} ,
{
name : "Invalid skipAuthRegex entry" ,
skipAuthRegex : [ ] string {
"^/foo/bar" ,
"^/baz/[0-9]+/thing" ,
"(bad[regex" ,
} ,
2020-10-05 22:39:44 +03:00
skipAuthRoutes : [ ] string { } ,
expectedRoutes : [ ] expectedAllowedRoute { } ,
shouldError : true ,
2020-09-24 06:16:05 +03:00
} ,
{
name : "Invalid skipAuthRoutes entry" ,
skipAuthRegex : [ ] string { } ,
skipAuthRoutes : [ ] string {
"GET=^/foo/bar" ,
"POST=^/baz/[0-9]+/thing" ,
"^/all/methods$" ,
"PUT=(bad[regex" ,
} ,
2020-10-05 22:39:44 +03:00
expectedRoutes : [ ] expectedAllowedRoute { } ,
shouldError : true ,
2020-09-24 06:16:05 +03:00
} ,
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
opts := & options . Options {
SkipAuthRegex : tc . skipAuthRegex ,
SkipAuthRoutes : tc . skipAuthRoutes ,
}
routes , err := buildRoutesAllowlist ( opts )
if tc . shouldError {
assert . Error ( t , err )
return
}
2020-09-26 22:38:01 +03:00
assert . NoError ( t , err )
2020-09-24 06:16:05 +03:00
for i , route := range routes {
2020-10-05 22:39:44 +03:00
assert . Greater ( t , len ( tc . expectedRoutes ) , i )
assert . Equal ( t , route . method , tc . expectedRoutes [ i ] . method )
assert . Equal ( t , route . pathRegex . String ( ) , tc . expectedRoutes [ i ] . regexString )
2020-09-24 06:16:05 +03:00
}
} )
}
}
func TestAllowedRequest ( t * testing . T ) {
upstreamServer := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( 200 )
_ , err := w . Write ( [ ] byte ( "Allowed Request" ) )
if err != nil {
t . Fatal ( err )
}
} ) )
t . Cleanup ( upstreamServer . Close )
opts := baseTestOptions ( )
opts . UpstreamServers = options . Upstreams {
{
ID : upstreamServer . URL ,
Path : "/" ,
URI : upstreamServer . URL ,
} ,
}
opts . SkipAuthRegex = [ ] string {
"^/skip/auth/regex$" ,
}
opts . SkipAuthRoutes = [ ] string {
"GET=^/skip/auth/routes/get" ,
}
err := validation . Validate ( opts )
assert . NoError ( t , err )
proxy , err := NewOAuthProxy ( opts , func ( _ string ) bool { return true } )
if err != nil {
t . Fatal ( err )
}
testCases := [ ] struct {
name string
method string
url string
allowed bool
} {
{
name : "Regex GET allowed" ,
method : "GET" ,
url : "/skip/auth/regex" ,
allowed : true ,
} ,
{
name : "Regex POST allowed " ,
method : "POST" ,
url : "/skip/auth/regex" ,
allowed : true ,
} ,
{
name : "Regex denied" ,
method : "GET" ,
url : "/wrong/denied" ,
allowed : false ,
} ,
{
name : "Route allowed" ,
method : "GET" ,
url : "/skip/auth/routes/get" ,
allowed : true ,
} ,
{
name : "Route denied with wrong method" ,
method : "PATCH" ,
url : "/skip/auth/routes/get" ,
allowed : false ,
} ,
{
name : "Route denied with wrong path" ,
method : "GET" ,
url : "/skip/auth/routes/wrong/path" ,
allowed : false ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
req , err := http . NewRequest ( tc . method , tc . url , nil )
assert . NoError ( t , err )
assert . Equal ( t , tc . allowed , proxy . isAllowedRoute ( req ) )
rw := httptest . NewRecorder ( )
proxy . ServeHTTP ( rw , req )
if tc . allowed {
assert . Equal ( t , 200 , rw . Code )
assert . Equal ( t , "Allowed Request" , rw . Body . String ( ) )
} else {
assert . Equal ( t , 403 , rw . Code )
}
} )
}
}
2020-07-28 21:42:09 +03:00
func TestProxyAllowedGroups ( t * testing . T ) {
tests := [ ] struct {
name string
allowedGroups [ ] string
groups [ ] string
expectUnauthorized bool
} {
{ "NoAllowedGroups" , [ ] string { } , [ ] string { } , false } ,
{ "NoAllowedGroupsUserHasGroups" , [ ] string { } , [ ] string { "a" , "b" } , false } ,
{ "UserInAllowedGroup" , [ ] string { "a" } , [ ] string { "a" , "b" } , false } ,
{ "UserNotInAllowedGroup" , [ ] string { "a" } , [ ] string { "c" } , true } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
emailAddress := "test"
created := time . Now ( )
session := & sessions . SessionState {
Groups : tt . groups ,
Email : emailAddress ,
AccessToken : "oauth_token" ,
CreatedAt : & created ,
}
2020-09-24 06:16:05 +03:00
upstreamServer := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2020-07-28 21:42:09 +03:00
w . WriteHeader ( 200 )
} ) )
2020-09-24 06:16:05 +03:00
t . Cleanup ( upstreamServer . Close )
2020-07-28 21:42:09 +03:00
test , err := NewProcessCookieTestWithOptionsModifiers ( func ( opts * options . Options ) {
opts . AllowedGroups = tt . allowedGroups
opts . UpstreamServers = options . Upstreams {
{
2020-09-24 06:16:05 +03:00
ID : upstreamServer . URL ,
2020-07-28 21:42:09 +03:00
Path : "/" ,
2020-09-24 06:16:05 +03:00
URI : upstreamServer . URL ,
2020-07-28 21:42:09 +03:00
} ,
}
} )
if err != nil {
t . Fatal ( err )
}
test . req , _ = http . NewRequest ( "GET" , "/" , nil )
test . req . Header . Add ( "accept" , applicationJSON )
2020-09-24 06:16:05 +03:00
err = test . SaveSession ( session )
assert . NoError ( t , err )
2020-07-28 21:42:09 +03:00
test . proxy . ServeHTTP ( test . rw , test . req )
if tt . expectUnauthorized {
assert . Equal ( t , http . StatusUnauthorized , test . rw . Code )
} else {
assert . Equal ( t , http . StatusOK , test . rw . Code )
}
} )
}
}
func TestAuthOnlyAllowedGroups ( t * testing . T ) {
tests := [ ] struct {
name string
allowedGroups [ ] string
groups [ ] string
expectUnauthorized bool
} {
{ "NoAllowedGroups" , [ ] string { } , [ ] string { } , false } ,
{ "NoAllowedGroupsUserHasGroups" , [ ] string { } , [ ] string { "a" , "b" } , false } ,
{ "UserInAllowedGroup" , [ ] string { "a" } , [ ] string { "a" , "b" } , false } ,
{ "UserNotInAllowedGroup" , [ ] string { "a" } , [ ] string { "c" } , true } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
emailAddress := "test"
created := time . Now ( )
session := & sessions . SessionState {
Groups : tt . groups ,
Email : emailAddress ,
AccessToken : "oauth_token" ,
CreatedAt : & created ,
}
test , err := NewAuthOnlyEndpointTest ( func ( opts * options . Options ) {
opts . AllowedGroups = tt . allowedGroups
} )
if err != nil {
t . Fatal ( err )
}
err = test . SaveSession ( session )
assert . NoError ( t , err )
test . proxy . ServeHTTP ( test . rw , test . req )
if tt . expectUnauthorized {
assert . Equal ( t , http . StatusUnauthorized , test . rw . Code )
} else {
assert . Equal ( t , http . StatusAccepted , test . rw . Code )
}
} )
}
}