399 lines
11 KiB
Go
Raw Normal View History

package tfa
import (
2019-01-30 16:52:47 +00:00
"fmt"
2019-01-22 10:50:55 +00:00
"net/http"
"net/url"
"strings"
2019-01-22 10:50:55 +00:00
"testing"
"time"
2019-01-30 16:52:47 +00:00
2019-04-23 17:49:16 +01:00
"github.com/stretchr/testify/assert"
"github.com/thomseddon/traefik-forward-auth/internal/provider"
)
2019-01-30 16:52:47 +00:00
/**
* Tests
*/
func TestAuthValidateCookie(t *testing.T) {
2019-04-23 17:49:16 +01:00
assert := assert.New(t)
config, _ = NewConfig([]string{})
2019-01-22 10:50:55 +00:00
r, _ := http.NewRequest("GET", "http://example.com", nil)
c := &http.Cookie{}
// Should require 3 parts
c.Value = ""
valid, _, err := ValidateCookie(r, c)
2019-04-23 17:49:16 +01:00
assert.False(valid)
if assert.Error(err) {
assert.Equal("Invalid cookie format", err.Error())
2019-01-22 10:50:55 +00:00
}
c.Value = "1|2"
valid, _, err = ValidateCookie(r, c)
2019-04-23 17:49:16 +01:00
assert.False(valid)
if assert.Error(err) {
assert.Equal("Invalid cookie format", err.Error())
2019-01-22 10:50:55 +00:00
}
c.Value = "1|2|3|4"
valid, _, err = ValidateCookie(r, c)
2019-04-23 17:49:16 +01:00
assert.False(valid)
if assert.Error(err) {
assert.Equal("Invalid cookie format", err.Error())
2019-01-22 10:50:55 +00:00
}
// Should catch invalid mac
c.Value = "MQ==|2|3"
valid, _, err = ValidateCookie(r, c)
2019-04-23 17:49:16 +01:00
assert.False(valid)
if assert.Error(err) {
assert.Equal("Invalid cookie mac", err.Error())
2019-01-22 10:50:55 +00:00
}
// Should catch expired
2019-01-30 16:52:47 +00:00
config.Lifetime = time.Second * time.Duration(-1)
c = MakeCookie(r, "test@test.com")
valid, _, err = ValidateCookie(r, c)
2019-04-23 17:49:16 +01:00
assert.False(valid)
if assert.Error(err) {
assert.Equal("Cookie has expired", err.Error())
2019-01-22 10:50:55 +00:00
}
// Should accept valid cookie
2019-01-30 16:52:47 +00:00
config.Lifetime = time.Second * time.Duration(10)
c = MakeCookie(r, "test@test.com")
valid, email, err := ValidateCookie(r, c)
2019-04-23 17:49:16 +01:00
assert.True(valid, "valid request should return valid")
assert.Nil(err, "valid request should not return an error")
assert.Equal("test@test.com", email, "valid request should return user email")
}
func TestAuthValidateEmail(t *testing.T) {
2019-04-23 17:49:16 +01:00
assert := assert.New(t)
config, _ = NewConfig([]string{})
2019-01-22 10:50:55 +00:00
// Should allow any
2019-04-23 17:49:16 +01:00
v := ValidateEmail("test@test.com")
assert.True(v, "should allow any domain if email domain is not defined")
v = ValidateEmail("one@two.com")
assert.True(v, "should allow any domain if email domain is not defined")
2019-01-22 10:50:55 +00:00
// Should block non matching domain
config.Domains = []string{"test.com"}
2019-04-23 17:49:16 +01:00
v = ValidateEmail("one@two.com")
assert.False(v, "should not allow user from another domain")
2019-01-22 10:50:55 +00:00
// Should allow matching domain
config.Domains = []string{"test.com"}
2019-04-23 17:49:16 +01:00
v = ValidateEmail("test@test.com")
assert.True(v, "should allow user from allowed domain")
2019-01-22 10:50:55 +00:00
// Should block non whitelisted email address
config.Domains = []string{}
2019-01-30 16:52:47 +00:00
config.Whitelist = []string{"test@test.com"}
2019-04-23 17:49:16 +01:00
v = ValidateEmail("one@two.com")
assert.False(v, "should not allow user not in whitelist")
2019-01-22 10:50:55 +00:00
// Should allow matching whitelisted email address
config.Domains = []string{}
2019-01-30 16:52:47 +00:00
config.Whitelist = []string{"test@test.com"}
2019-04-23 17:49:16 +01:00
v = ValidateEmail("test@test.com")
assert.True(v, "should allow user in whitelist")
}
// TODO: Split google tests out
func TestAuthGetLoginURL(t *testing.T) {
2019-04-23 17:49:16 +01:00
assert := assert.New(t)
google := provider.Google{
ClientId: "idtest",
ClientSecret: "sectest",
Scope: "scopetest",
Prompt: "consent select_account",
LoginURL: &url.URL{
Scheme: "https",
Host: "test.com",
Path: "/auth",
},
}
config, _ = NewConfig([]string{})
config.Providers.Google = google
2019-01-22 10:50:55 +00:00
r, _ := http.NewRequest("GET", "http://example.com", nil)
r.Header.Add("X-Forwarded-Proto", "http")
r.Header.Add("X-Forwarded-Host", "example.com")
r.Header.Add("X-Forwarded-Uri", "/hello")
// Check url
uri, err := url.Parse(GetLoginURL(r, "nonce"))
2019-04-23 17:49:16 +01:00
assert.Nil(err)
assert.Equal("https", uri.Scheme)
assert.Equal("test.com", uri.Host)
assert.Equal("/auth", uri.Path)
2019-01-22 10:50:55 +00:00
// Check query string
qs := uri.Query()
expectedQs := url.Values{
"client_id": []string{"idtest"},
"redirect_uri": []string{"http://example.com/_oauth"},
"response_type": []string{"code"},
"scope": []string{"scopetest"},
"prompt": []string{"consent select_account"},
2019-01-22 10:50:55 +00:00
"state": []string{"nonce:http://example.com/hello"},
}
2019-04-23 17:49:16 +01:00
assert.Equal(expectedQs, qs)
2019-01-22 10:50:55 +00:00
//
// With Auth URL but no matching cookie domain
// - will not use auth host
//
config, _ = NewConfig([]string{})
config.AuthHost = "auth.example.com"
config.Providers.Google = google
2019-01-22 10:50:55 +00:00
// Check url
uri, err = url.Parse(GetLoginURL(r, "nonce"))
2019-04-23 17:49:16 +01:00
assert.Nil(err)
assert.Equal("https", uri.Scheme)
assert.Equal("test.com", uri.Host)
assert.Equal("/auth", uri.Path)
2019-01-22 10:50:55 +00:00
// Check query string
qs = uri.Query()
expectedQs = url.Values{
"client_id": []string{"idtest"},
"redirect_uri": []string{"http://example.com/_oauth"},
"response_type": []string{"code"},
"scope": []string{"scopetest"},
"prompt": []string{"consent select_account"},
"state": []string{"nonce:http://example.com/hello"},
}
2019-04-23 17:49:16 +01:00
assert.Equal(expectedQs, qs)
2019-01-22 10:50:55 +00:00
//
// With correct Auth URL + cookie domain
//
config, _ = NewConfig([]string{})
config.AuthHost = "auth.example.com"
config.CookieDomains = []CookieDomain{*NewCookieDomain("example.com")}
config.Providers.Google = google
2019-01-22 10:50:55 +00:00
// Check url
uri, err = url.Parse(GetLoginURL(r, "nonce"))
2019-04-23 17:49:16 +01:00
assert.Nil(err)
assert.Equal("https", uri.Scheme)
assert.Equal("test.com", uri.Host)
assert.Equal("/auth", uri.Path)
2019-01-22 10:50:55 +00:00
// Check query string
qs = uri.Query()
expectedQs = url.Values{
"client_id": []string{"idtest"},
"redirect_uri": []string{"http://auth.example.com/_oauth"},
"response_type": []string{"code"},
"scope": []string{"scopetest"},
"state": []string{"nonce:http://example.com/hello"},
2019-01-30 16:52:47 +00:00
"prompt": []string{"consent select_account"},
2019-01-22 10:50:55 +00:00
}
2019-04-23 17:49:16 +01:00
assert.Equal(expectedQs, qs)
2019-01-22 10:50:55 +00:00
//
// With Auth URL + cookie domain, but from different domain
// - will not use auth host
//
r, _ = http.NewRequest("GET", "http://another.com", nil)
r.Header.Add("X-Forwarded-Proto", "http")
r.Header.Add("X-Forwarded-Host", "another.com")
r.Header.Add("X-Forwarded-Uri", "/hello")
// Check url
uri, err = url.Parse(GetLoginURL(r, "nonce"))
2019-04-23 17:49:16 +01:00
assert.Nil(err)
assert.Equal("https", uri.Scheme)
assert.Equal("test.com", uri.Host)
assert.Equal("/auth", uri.Path)
2019-01-22 10:50:55 +00:00
// Check query string
qs = uri.Query()
expectedQs = url.Values{
"client_id": []string{"idtest"},
"redirect_uri": []string{"http://another.com/_oauth"},
"response_type": []string{"code"},
"scope": []string{"scopetest"},
"state": []string{"nonce:http://another.com/hello"},
2019-01-30 16:52:47 +00:00
"prompt": []string{"consent select_account"},
2019-01-22 10:50:55 +00:00
}
2019-04-23 17:49:16 +01:00
assert.Equal(expectedQs, qs)
}
// TODO
// func TestAuthExchangeCode(t *testing.T) {
// }
// TODO
// func TestAuthGetUser(t *testing.T) {
// }
func TestAuthMakeCookie(t *testing.T) {
2019-04-23 17:49:16 +01:00
assert := assert.New(t)
config, _ = NewConfig([]string{})
r, _ := http.NewRequest("GET", "http://app.example.com", nil)
r.Header.Add("X-Forwarded-Host", "app.example.com")
c := MakeCookie(r, "test@example.com")
2019-04-23 17:49:16 +01:00
assert.Equal("_forward_auth", c.Name)
parts := strings.Split(c.Value, "|")
2019-04-23 17:49:16 +01:00
assert.Len(parts, 3, "cookie should be 3 parts")
valid, _, _ := ValidateCookie(r, c)
2019-04-23 17:49:16 +01:00
assert.True(valid, "should generate valid cookie")
assert.Equal("/", c.Path)
assert.Equal("app.example.com", c.Domain)
assert.True(c.Secure)
expires := time.Now().Local().Add(config.Lifetime)
assert.WithinDuration(expires, c.Expires, 10*time.Second)
config.CookieName = "testname"
config.InsecureCookie = true
c = MakeCookie(r, "test@example.com")
2019-04-23 17:49:16 +01:00
assert.Equal("testname", c.Name)
assert.False(c.Secure)
}
func TestAuthMakeCSRFCookie(t *testing.T) {
2019-04-23 17:49:16 +01:00
assert := assert.New(t)
config, _ = NewConfig([]string{})
2019-01-22 10:50:55 +00:00
r, _ := http.NewRequest("GET", "http://app.example.com", nil)
r.Header.Add("X-Forwarded-Host", "app.example.com")
// No cookie domain or auth url
c := MakeCSRFCookie(r, "12345678901234567890123456789012")
2019-04-23 17:49:16 +01:00
assert.Equal("app.example.com", c.Domain)
2019-01-22 10:50:55 +00:00
// With cookie domain but no auth url
config = Config{
CookieDomains: []CookieDomain{*NewCookieDomain("example.com")},
2019-01-30 16:52:47 +00:00
}
c = MakeCSRFCookie(r, "12345678901234567890123456789012")
2019-04-23 17:49:16 +01:00
assert.Equal("app.example.com", c.Domain)
2019-01-22 10:50:55 +00:00
// With cookie domain and auth url
config = Config{
2019-01-22 10:50:55 +00:00
AuthHost: "auth.example.com",
CookieDomains: []CookieDomain{*NewCookieDomain("example.com")},
2019-01-22 10:50:55 +00:00
}
c = MakeCSRFCookie(r, "12345678901234567890123456789012")
2019-04-23 17:49:16 +01:00
assert.Equal("example.com", c.Domain)
}
func TestAuthClearCSRFCookie(t *testing.T) {
config, _ = NewConfig([]string{})
2019-01-22 10:50:55 +00:00
r, _ := http.NewRequest("GET", "http://example.com", nil)
c := ClearCSRFCookie(r)
2019-01-22 10:50:55 +00:00
if c.Value != "" {
t.Error("ClearCSRFCookie should create cookie with empty value")
}
}
func TestAuthValidateCSRFCookie(t *testing.T) {
2019-04-23 17:49:16 +01:00
assert := assert.New(t)
config, _ = NewConfig([]string{})
2019-01-22 10:50:55 +00:00
c := &http.Cookie{}
2019-01-30 16:52:47 +00:00
newCsrfRequest := func(state string) *http.Request {
u := fmt.Sprintf("http://example.com?state=%s", state)
r, _ := http.NewRequest("GET", u, nil)
return r
}
2019-01-22 10:50:55 +00:00
// Should require 32 char string
2019-01-30 16:52:47 +00:00
r := newCsrfRequest("")
2019-01-22 10:50:55 +00:00
c.Value = ""
valid, _, err := ValidateCSRFCookie(r, c)
2019-04-23 17:49:16 +01:00
assert.False(valid)
if assert.Error(err) {
assert.Equal("Invalid CSRF cookie value", err.Error())
2019-01-22 10:50:55 +00:00
}
c.Value = "123456789012345678901234567890123"
valid, _, err = ValidateCSRFCookie(r, c)
2019-04-23 17:49:16 +01:00
assert.False(valid)
if assert.Error(err) {
assert.Equal("Invalid CSRF cookie value", err.Error())
2019-01-22 10:50:55 +00:00
}
// Should require valid state
2019-01-30 16:52:47 +00:00
r = newCsrfRequest("12345678901234567890123456789012:")
2019-01-22 10:50:55 +00:00
c.Value = "12345678901234567890123456789012"
valid, _, err = ValidateCSRFCookie(r, c)
2019-04-23 17:49:16 +01:00
assert.False(valid)
if assert.Error(err) {
assert.Equal("Invalid CSRF state value", err.Error())
2019-01-22 10:50:55 +00:00
}
// Should allow valid state
2019-01-30 16:52:47 +00:00
r = newCsrfRequest("12345678901234567890123456789012:99")
2019-01-22 10:50:55 +00:00
c.Value = "12345678901234567890123456789012"
valid, state, err := ValidateCSRFCookie(r, c)
2019-04-23 17:49:16 +01:00
assert.True(valid, "valid request should return valid")
assert.Nil(err, "valid request should not return an error")
assert.Equal("99", state, "valid request should return correct state")
}
func TestAuthNonce(t *testing.T) {
2019-04-23 17:49:16 +01:00
assert := assert.New(t)
err, nonce1 := Nonce()
2019-04-23 17:49:16 +01:00
assert.Nil(err, "error generating nonce")
assert.Len(nonce1, 32, "length should be 32 chars")
2019-01-22 10:50:55 +00:00
err, nonce2 := Nonce()
2019-04-23 17:49:16 +01:00
assert.Nil(err, "error generating nonce")
assert.Len(nonce2, 32, "length should be 32 chars")
2019-01-22 10:50:55 +00:00
2019-04-23 17:49:16 +01:00
assert.NotEqual(nonce1, nonce2, "nonce should not be equal")
}
func TestAuthCookieDomainMatch(t *testing.T) {
2019-04-23 17:49:16 +01:00
assert := assert.New(t)
2019-01-22 10:50:55 +00:00
cd := NewCookieDomain("example.com")
// Exact should match
2019-04-23 17:49:16 +01:00
assert.True(cd.Match("example.com"), "exact domain should match")
2019-01-22 10:50:55 +00:00
// Subdomain should match
2019-04-23 17:49:16 +01:00
assert.True(cd.Match("test.example.com"), "subdomain should match")
2019-01-22 10:50:55 +00:00
// Derived domain should not match
2019-04-23 17:49:16 +01:00
assert.False(cd.Match("testexample.com"), "derived domain should not match")
2019-01-22 10:50:55 +00:00
// Other domain should not match
2019-04-23 17:49:16 +01:00
assert.False(cd.Match("test.com"), "other domain should not match")
}
func TestAuthCookieDomains(t *testing.T) {
2019-04-23 17:49:16 +01:00
assert := assert.New(t)
cds := CookieDomains{}
err := cds.UnmarshalFlag("one.com,two.org")
2019-04-23 17:49:16 +01:00
assert.Nil(err)
expected := CookieDomains{
CookieDomain{
Domain: "one.com",
DomainLen: 7,
SubDomain: ".one.com",
SubDomainLen: 8,
},
CookieDomain{
Domain: "two.org",
DomainLen: 7,
SubDomain: ".two.org",
SubDomainLen: 8,
},
}
2019-04-23 17:49:16 +01:00
assert.Equal(expected, cds)
marshal, err := cds.MarshalFlag()
2019-04-23 17:49:16 +01:00
assert.Nil(err)
assert.Equal("one.com,two.org", marshal)
}