283 lines
7.7 KiB
Go
283 lines
7.7 KiB
Go
package cookie
|
|
|
|
import (
|
|
"fmt"
|
|
mathrand "math/rand"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
|
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/tests"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestSessionStore(t *testing.T) {
|
|
logger.SetOutput(GinkgoWriter)
|
|
logger.SetErrOutput(GinkgoWriter)
|
|
|
|
RegisterFailHandler(Fail)
|
|
RunSpecs(t, "Cookie SessionStore")
|
|
}
|
|
|
|
var _ = Describe("Cookie SessionStore Tests", func() {
|
|
tests.RunSessionStoreTests(
|
|
func(opts *options.SessionOptions, cookieOpts *options.Cookie) (sessionsapi.SessionStore, error) {
|
|
// Set the connection URL
|
|
opts.Type = options.CookieSessionStoreType
|
|
return NewCookieSessionStore(opts, cookieOpts)
|
|
}, nil)
|
|
})
|
|
|
|
func Test_copyCookie(t *testing.T) {
|
|
expire, _ := time.Parse(time.RFC3339, "2020-03-17T00:00:00Z")
|
|
c := &http.Cookie{
|
|
Name: "name",
|
|
Value: "value",
|
|
Path: "/path",
|
|
Domain: "x.y.z",
|
|
Expires: expire,
|
|
RawExpires: "rawExpire",
|
|
MaxAge: 1,
|
|
Secure: true,
|
|
HttpOnly: true,
|
|
Raw: "raw",
|
|
Unparsed: []string{"unparsed"},
|
|
SameSite: http.SameSiteLaxMode,
|
|
}
|
|
|
|
got := copyCookie(c)
|
|
assert.Equal(t, c, got)
|
|
}
|
|
|
|
func Test_splitCookie(t *testing.T) {
|
|
testCases := map[string]*http.Cookie{
|
|
"Short cookie name": {
|
|
Name: "short",
|
|
Value: strings.Repeat("v", 10000),
|
|
},
|
|
"Long cookie name": {
|
|
Name: strings.Repeat("n", 251),
|
|
Value: strings.Repeat("a", 10000),
|
|
},
|
|
"Max cookie name": {
|
|
Name: strings.Repeat("n", 256),
|
|
Value: strings.Repeat("a", 10000),
|
|
},
|
|
"Suffix overflow cookie name": {
|
|
Name: strings.Repeat("n", 255),
|
|
Value: strings.Repeat("a", 10000),
|
|
},
|
|
"Double digit suffix cookie name overflow": {
|
|
Name: strings.Repeat("n", 253),
|
|
Value: strings.Repeat("a", 50000),
|
|
},
|
|
"With short name and attributes": {
|
|
Name: "short",
|
|
Value: strings.Repeat("v", 10000),
|
|
Path: "/path",
|
|
Domain: "x.y.z",
|
|
Secure: true,
|
|
HttpOnly: true,
|
|
SameSite: http.SameSiteLaxMode,
|
|
},
|
|
"With max length name and attributes": {
|
|
Name: strings.Repeat("n", 256),
|
|
Value: strings.Repeat("v", 10000),
|
|
Path: "/path",
|
|
Domain: "x.y.z",
|
|
Secure: true,
|
|
HttpOnly: true,
|
|
SameSite: http.SameSiteLaxMode,
|
|
},
|
|
}
|
|
for testName, tc := range testCases {
|
|
t.Run(testName, func(t *testing.T) {
|
|
splitCookies := splitCookie(tc)
|
|
for i, cookie := range splitCookies {
|
|
if i < len(splitCookies)-1 {
|
|
assert.Equal(t, 4000, len(cookie.String()))
|
|
} else {
|
|
assert.GreaterOrEqual(t, 4000, len(cookie.String()))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_splitCookieName(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
Name string
|
|
Count int
|
|
Output string
|
|
}{
|
|
"Standard length": {
|
|
Name: "IAmSoNormal",
|
|
Count: 2,
|
|
Output: "IAmSoNormal_2",
|
|
},
|
|
"Max length": {
|
|
Name: strings.Repeat("n", 256),
|
|
Count: 1,
|
|
Output: fmt.Sprintf("%s_%d", strings.Repeat("n", 254), 1),
|
|
},
|
|
"Large count overflow": {
|
|
Name: strings.Repeat("n", 253),
|
|
Count: 1000,
|
|
Output: fmt.Sprintf("%s_%d", strings.Repeat("n", 251), 1000),
|
|
},
|
|
}
|
|
for testName, tc := range testCases {
|
|
t.Run(testName, func(t *testing.T) {
|
|
splitName := splitCookieName(tc.Name, tc.Count)
|
|
assert.Equal(t, tc.Output, splitName)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_setSessionCookieClearsStaleCookies(t *testing.T) {
|
|
store := &SessionStore{
|
|
Cookie: &options.Cookie{
|
|
Name: "_oauth2_proxy",
|
|
Secret: "0123456789abcdef",
|
|
Path: "/",
|
|
Expire: time.Hour,
|
|
},
|
|
}
|
|
now := time.Now()
|
|
|
|
testCases := map[string]struct {
|
|
value []byte
|
|
existingCookies []string
|
|
wantSessionParts int
|
|
wantClearedCookie []string
|
|
}{
|
|
"split session becomes a single cookie": {
|
|
value: []byte("small session"),
|
|
existingCookies: []string{"_oauth2_proxy_0", "_oauth2_proxy_1", "_oauth2_proxy_csrf"},
|
|
wantSessionParts: 1,
|
|
wantClearedCookie: []string{"_oauth2_proxy_0", "_oauth2_proxy_1"},
|
|
},
|
|
"split session uses fewer parts": {
|
|
value: []byte(strings.Repeat("v", 4500)),
|
|
existingCookies: []string{"_oauth2_proxy_0", "_oauth2_proxy_1", "_oauth2_proxy_2", "_oauth2_proxy_3"},
|
|
wantSessionParts: 2,
|
|
wantClearedCookie: []string{"_oauth2_proxy_2", "_oauth2_proxy_3"},
|
|
},
|
|
}
|
|
|
|
for name, tc := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "https://example.com/", nil)
|
|
for _, cookieName := range tc.existingCookies {
|
|
req.AddCookie(&http.Cookie{Name: cookieName, Value: "old"})
|
|
}
|
|
|
|
newCookies, err := store.makeSessionCookie(req, tc.value, now)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, newCookies, tc.wantSessionParts)
|
|
|
|
rw := httptest.NewRecorder()
|
|
err = store.setSessionCookie(rw, req, tc.value, now)
|
|
assert.NoError(t, err)
|
|
|
|
responseCookies := make(map[string]*http.Cookie)
|
|
for _, cookie := range rw.Result().Cookies() {
|
|
responseCookies[cookie.Name] = cookie
|
|
}
|
|
for _, cookieName := range tc.wantClearedCookie {
|
|
cookie := responseCookies[cookieName]
|
|
if assert.NotNil(t, cookie, "stale cookie %q was not cleared", cookieName) {
|
|
assert.Equal(t, -1, cookie.MaxAge)
|
|
}
|
|
}
|
|
for _, wantCookie := range newCookies {
|
|
cookie := responseCookies[wantCookie.Name]
|
|
if assert.NotNil(t, cookie, "current cookie %q was not set", wantCookie.Name) {
|
|
assert.Greater(t, cookie.MaxAge, 0)
|
|
}
|
|
}
|
|
assert.Len(t, responseCookies, len(newCookies)+len(tc.wantClearedCookie))
|
|
assert.NotContains(t, responseCookies, "_oauth2_proxy_csrf")
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_splitCookie_joinCookies(t *testing.T) {
|
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
|
|
v := make([]byte, 251)
|
|
for i := range v {
|
|
v[i] = charset[mathrand.Intn(len(charset))]
|
|
}
|
|
value := strings.Repeat(string(v), 1000)
|
|
|
|
for _, nameSize := range []int{1, 10, 50, 100, 200, 254} {
|
|
t.Run(fmt.Sprintf("%d length cookie name", nameSize), func(t *testing.T) {
|
|
cookie := &http.Cookie{
|
|
Name: strings.Repeat("n", nameSize),
|
|
Value: value,
|
|
}
|
|
splitCookies := splitCookie(cookie)
|
|
joinedCookie, err := joinCookies(splitCookies, cookie.Name)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, *cookie, *joinedCookie)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_joinCookies_withUnderlineSuffix(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
CookieName string
|
|
SplitOrder []int
|
|
}{
|
|
"Ascending order split with \"_\" suffix": {
|
|
CookieName: "_cookie_name_",
|
|
SplitOrder: []int{0, 1, 2, 3, 4},
|
|
},
|
|
"Descending order split with \"_\" suffix": {
|
|
CookieName: "_cookie_name_",
|
|
SplitOrder: []int{4, 3, 2, 1, 0},
|
|
},
|
|
"Arbitrary order split with \"_\" suffix": {
|
|
CookieName: "_cookie_name_",
|
|
SplitOrder: []int{3, 1, 2, 0, 4},
|
|
},
|
|
"Arbitrary order split with \"_0\" suffix": {
|
|
CookieName: "_cookie_name_0",
|
|
SplitOrder: []int{1, 3, 0, 2, 4},
|
|
},
|
|
"Arbitrary order split with \"_1\" suffix": {
|
|
CookieName: "_cookie_name_1",
|
|
SplitOrder: []int{4, 1, 3, 0, 2},
|
|
},
|
|
"Arbitrary order split with \"__\" suffix": {
|
|
CookieName: "_cookie_name__",
|
|
SplitOrder: []int{1, 0, 4, 3, 2},
|
|
},
|
|
}
|
|
|
|
for testName, testCase := range testCases {
|
|
t.Run(testName, func(t *testing.T) {
|
|
cookieName := testCase.CookieName
|
|
var splitCookies []*http.Cookie
|
|
for _, splitSuffix := range testCase.SplitOrder {
|
|
cookie := &http.Cookie{
|
|
Name: splitCookieName(cookieName, splitSuffix),
|
|
Value: strings.Repeat("v", 1000),
|
|
}
|
|
splitCookies = append(splitCookies, cookie)
|
|
}
|
|
joinedCookie, err := joinCookies(splitCookies, cookieName)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, cookieName, joinedCookie.Name)
|
|
})
|
|
}
|
|
}
|