All cookie variables in a struct

Signed-off-by: Joost <439100+jvnoije@users.noreply.github.com>
This commit is contained in:
Joost 2026-03-16 10:56:04 +01:00
parent 0805074e25
commit 0bf690d44c
7 changed files with 144 additions and 114 deletions

View File

@ -7,14 +7,24 @@ import (
"strings"
"time"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
requestutil "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests/util"
)
type CookieOptions struct {
Name string
Value string
Domains []string
Expiration time.Duration
SameSite string
Path string
HTTPOnly bool
Secure bool
}
// MakeCookieFromOptions constructs a cookie based on the given *options.CookieOptions,
// value and creation time
func MakeCookieFromOptions(req *http.Request, name string, value string, opts *options.Cookie, expiration time.Duration, sameSite string) *http.Cookie {
func MakeCookieFromOptions(req *http.Request, opts *CookieOptions) *http.Cookie {
domain := GetCookieDomain(req, opts.Domains)
// If nothing matches, create the cookie with the shortest domain
if domain == "" && len(opts.Domains) > 0 {
@ -26,18 +36,18 @@ func MakeCookieFromOptions(req *http.Request, name string, value string, opts *o
}
c := &http.Cookie{
Name: name,
Value: value,
Name: opts.Name,
Value: opts.Value,
Path: opts.Path,
Domain: domain,
HttpOnly: opts.HTTPOnly,
Secure: opts.Secure,
SameSite: ParseSameSite(sameSite),
SameSite: ParseSameSite(opts.SameSite),
}
if expiration > time.Duration(0) {
c.MaxAge = int(expiration.Seconds())
} else if expiration < time.Duration(0) {
if opts.Expiration > time.Duration(0) {
c.MaxAge = int(opts.Expiration.Seconds())
} else if opts.Expiration < time.Duration(0) {
c.MaxAge = -1
}
@ -58,7 +68,7 @@ func GetCookieDomain(req *http.Request, cookieDomains []string) string {
return ""
}
// Parse a valid http.SameSite value from a user supplied string for use of making cookies.
// ParseSameSite a valid http.SameSite value from a user supplied string for use of making cookies.
func ParseSameSite(v string) http.SameSite {
switch v {
case "lax":

View File

@ -5,8 +5,6 @@ import (
"net/http"
"time"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@ -82,16 +80,12 @@ var _ = Describe("Cookie Tests", func() {
Context("MakeCookieFromOptions", func() {
type makeCookieFromOptionsTableInput struct {
host string
name string
value string
opts options.Cookie
expiration time.Duration
opts CookieOptions
now time.Time
expectedOutput int
}
validName := "_oauth2_proxy"
validSecret := "secretthirtytwobytes+abcdefghijk"
domains := []string{"www.cookies.test"}
now := time.Now()
@ -106,62 +100,50 @@ var _ = Describe("Cookie Tests", func() {
)
Expect(err).ToNot(HaveOccurred())
Expect(MakeCookieFromOptions(req, in.name, in.value, &in.opts, in.expiration, in.opts.SameSite).MaxAge).To(Equal(in.expectedOutput))
Expect(MakeCookieFromOptions(req, &in.opts).MaxAge).To(Equal(in.expectedOutput))
},
Entry("persistent cookie", makeCookieFromOptionsTableInput{
host: "www.cookies.test",
name: validName,
value: "1",
opts: options.Cookie{
Name: validName,
Secret: validSecret,
Domains: domains,
Path: "",
Expire: time.Hour,
Refresh: 15 * time.Minute,
Secure: true,
HTTPOnly: false,
SameSite: "",
host: "www.cookies.test",
opts: CookieOptions{
Name: validName,
Value: "1",
Domains: domains,
Expiration: 15 * time.Minute,
SameSite: "",
Path: "",
HTTPOnly: false,
Secure: true,
},
expiration: 15 * time.Minute,
now: now,
expectedOutput: int((15 * time.Minute).Seconds()),
}),
Entry("persistent cookie to be cleared", makeCookieFromOptionsTableInput{
host: "www.cookies.test",
name: validName,
value: "1",
opts: options.Cookie{
Name: validName,
Secret: validSecret,
Domains: domains,
Path: "",
Expire: time.Hour * -1,
Refresh: 15 * time.Minute,
Secure: true,
HTTPOnly: false,
SameSite: "",
host: "www.cookies.test",
opts: CookieOptions{
Name: validName,
Value: "1",
Domains: domains,
Expiration: time.Hour * -1,
SameSite: "",
Path: "",
HTTPOnly: false,
Secure: true,
},
expiration: time.Hour * -1,
now: now,
expectedOutput: -1,
}),
Entry("session cookie", makeCookieFromOptionsTableInput{
host: "www.cookies.test",
name: validName,
value: "1",
opts: options.Cookie{
Name: validName,
Secret: validSecret,
Domains: domains,
Path: "",
Expire: 0,
Refresh: 15 * time.Minute,
Secure: true,
HTTPOnly: false,
SameSite: "",
host: "www.cookies.test",
opts: CookieOptions{
Name: validName,
Value: "1",
Domains: domains,
Expiration: 0,
SameSite: "",
Path: "",
HTTPOnly: false,
Secure: true,
},
expiration: 0,
now: now,
expectedOutput: expectedMaxAge,
}),

View File

@ -150,14 +150,18 @@ func (c *csrf) SetCookie(rw http.ResponseWriter, req *http.Request) (*http.Cooki
return nil, err
}
cookie := MakeCookieFromOptions(
req,
c.cookieName(),
encoded,
c.cookieOpts,
c.cookieOpts.CSRFExpire,
getCSRFSameSite(c.cookieOpts),
)
csrfCookieOptions := &CookieOptions{
Name: c.cookieName(),
Value: encoded,
Domains: c.cookieOpts.Domains,
Expiration: c.cookieOpts.CSRFExpire,
SameSite: getCSRFSameSite(c.cookieOpts),
Path: c.cookieOpts.Path,
HTTPOnly: c.cookieOpts.HTTPOnly,
Secure: c.cookieOpts.Secure,
}
cookie := MakeCookieFromOptions(req, csrfCookieOptions)
http.SetCookie(rw, cookie)
return cookie, nil
@ -207,14 +211,18 @@ func ClearExtraCsrfCookies(opts *options.Cookie, rw http.ResponseWriter, req *ht
// ClearCookie removes the CSRF cookie
func (c *csrf) ClearCookie(rw http.ResponseWriter, req *http.Request) {
http.SetCookie(rw, MakeCookieFromOptions(
req,
c.cookieName(),
"",
c.cookieOpts,
time.Hour*-1,
getCSRFSameSite(c.cookieOpts),
))
csrfCookieOptions := &CookieOptions{
Name: c.cookieName(),
Value: "",
Domains: c.cookieOpts.Domains,
Expiration: time.Hour * -1,
SameSite: getCSRFSameSite(c.cookieOpts),
Path: c.cookieOpts.Path,
HTTPOnly: c.cookieOpts.HTTPOnly,
Secure: c.cookieOpts.Secure,
}
http.SetCookie(rw, MakeCookieFromOptions(req, csrfCookieOptions))
}
// encodeCookie MessagePack encodes and encrypts the CSRF and then creates a

View File

@ -216,14 +216,18 @@ var _ = Describe("CSRF Cookie with non-fixed name Tests", func() {
for _, csrf := range []*csrf{privateCSRF1, privateCSRF2, privateCSRF3} {
encoded, err := csrf.encodeCookie()
Expect(err).ToNot(HaveOccurred())
cookie := MakeCookieFromOptions(
req,
csrf.cookieName(),
encoded,
csrf.cookieOpts,
csrf.cookieOpts.CSRFExpire,
csrf.cookieOpts.CSRFSameSite,
)
csrfCookieOptions := &CookieOptions{
Name: csrf.cookieName(),
Value: encoded,
Domains: csrf.cookieOpts.Domains,
Expiration: csrf.cookieOpts.CSRFExpire,
SameSite: getCSRFSameSite(csrf.cookieOpts),
Path: csrf.cookieOpts.Path,
HTTPOnly: csrf.cookieOpts.HTTPOnly,
Secure: csrf.cookieOpts.Secure,
}
cookie := MakeCookieFromOptions(req, csrfCookieOptions)
cookies = append(cookies, fmt.Sprintf("%v=%v", cookie.Name, cookie.Value))
}

View File

@ -76,7 +76,17 @@ func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
for _, c := range req.Cookies() {
if cookieNameRegex.MatchString(c.Name) {
clearCookie := s.makeCookie(req, c.Name, "", time.Hour*-1)
sessionCookieOptions := &pkgcookies.CookieOptions{
Name: c.Name,
Value: "",
Domains: s.Cookie.Domains,
Expiration: time.Hour * -1,
SameSite: s.Cookie.SameSite,
Path: s.Cookie.Path,
HTTPOnly: s.Cookie.HTTPOnly,
Secure: s.Cookie.Secure,
}
clearCookie := pkgcookies.MakeCookieFromOptions(req, sessionCookieOptions)
http.SetCookie(rw, clearCookie)
}
@ -117,7 +127,7 @@ func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Reques
return nil
}
// makeSessionCookie creates an http.Cookie containing the authenticated user's
// makeSessionCookie creates a http.Cookie containing the authenticated user's
// authentication details
func (s *SessionStore) makeSessionCookie(req *http.Request, value []byte, now time.Time) ([]*http.Cookie, error) {
strValue := string(value)
@ -132,24 +142,23 @@ func (s *SessionStore) makeSessionCookie(req *http.Request, value []byte, now ti
return nil, err
}
}
c := s.makeCookie(req, s.Cookie.Name, strValue, s.Cookie.Expire)
sessionCookieOptions := &pkgcookies.CookieOptions{
Name: s.Cookie.Name,
Value: strValue,
Domains: s.Cookie.Domains,
Expiration: s.Cookie.Expire,
SameSite: s.Cookie.SameSite,
Path: s.Cookie.Path,
HTTPOnly: s.Cookie.HTTPOnly,
Secure: s.Cookie.Secure,
}
c := pkgcookies.MakeCookieFromOptions(req, sessionCookieOptions)
if len(c.String()) > maxCookieLength {
return splitCookie(c), nil
}
return []*http.Cookie{c}, nil
}
func (s *SessionStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration) *http.Cookie {
return pkgcookies.MakeCookieFromOptions(
req,
name,
value,
s.Cookie,
expiration,
s.Cookie.SameSite,
)
}
// NewCookieSessionStore initialises a new instance of the SessionStore from
// the configuration given
func NewCookieSessionStore(opts *options.SessionOptions, cookieOpts *options.Cookie) (sessions.SessionStore, error) {

View File

@ -221,14 +221,17 @@ func (t *ticket) setCookie(rw http.ResponseWriter, req *http.Request, s *session
// clearCookie removes any cookies that would be where this ticket
// would set them
func (t *ticket) clearCookie(rw http.ResponseWriter, req *http.Request) {
http.SetCookie(rw, cookies.MakeCookieFromOptions(
req,
t.options.Name,
"",
t.options,
time.Hour*-1,
t.options.SameSite,
))
cookieOptions := &cookies.CookieOptions{
Name: t.options.Name,
Value: "",
Domains: t.options.Domains,
Expiration: time.Hour * -1,
SameSite: t.options.SameSite,
Path: t.options.Path,
HTTPOnly: t.options.HTTPOnly,
Secure: t.options.Secure,
}
http.SetCookie(rw, cookies.MakeCookieFromOptions(req, cookieOptions))
}
// makeCookie makes a cookie, signing the value if present
@ -245,14 +248,18 @@ func (t *ticket) makeCookie(req *http.Request, value string, expires time.Durati
}
}
return cookies.MakeCookieFromOptions(
req,
t.options.Name,
value,
t.options,
expires,
t.options.SameSite,
), nil
cookieOptions := &cookies.CookieOptions{
Name: t.options.Name,
Value: value,
Domains: t.options.Domains,
Expiration: expires,
SameSite: t.options.SameSite,
Path: t.options.Path,
HTTPOnly: t.options.HTTPOnly,
Secure: t.options.Secure,
}
return cookies.MakeCookieFromOptions(req, cookieOptions), nil
}
// makeCipher makes a AES-GCM cipher out of the ticket's secret

View File

@ -422,7 +422,17 @@ func SessionStoreInterfaceTests(in *testInput) {
broken := "BrokenSessionFromADifferentSessionImplementation"
value, err := encryption.SignedValue(in.cookieOpts.Secret, in.cookieOpts.Name, []byte(broken), time.Now())
Expect(err).ToNot(HaveOccurred())
cookie := cookiesapi.MakeCookieFromOptions(in.request, in.cookieOpts.Name, value, in.cookieOpts, in.cookieOpts.Expire, in.cookieOpts.SameSite)
cookieOptions := &cookiesapi.CookieOptions{
Name: in.cookieOpts.Name,
Value: value,
Domains: in.cookieOpts.Domains,
Expiration: in.cookieOpts.Expire,
SameSite: in.cookieOpts.SameSite,
Path: in.cookieOpts.Path,
HTTPOnly: in.cookieOpts.HTTPOnly,
Secure: in.cookieOpts.Secure,
}
cookie := cookiesapi.MakeCookieFromOptions(in.request, cookieOptions)
in.request.AddCookie(cookie)
err = in.ss().Save(in.response, in.request, in.session)