1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-05-21 22:33:38 +02:00

Merge pull request #2697 from matpen-wi/feat/max-age-instead-of-expires

pkg/cookies: use 'Max-Age' instead of 'Expires' for cookie expiration
This commit is contained in:
Joel Speed 2025-02-24 18:27:22 +07:00 committed by GitHub
commit a25fef7cbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 40 additions and 32 deletions

View File

@ -9,6 +9,7 @@
## Changes since v7.8.1 ## Changes since v7.8.1
- [#2927](https://github.com/oauth2-proxy/oauth2-proxy/pull/2927) chore(deps/build): bump golang to 1.23 and use go.mod as single point of truth for all build files (@tuunit) - [#2927](https://github.com/oauth2-proxy/oauth2-proxy/pull/2927) chore(deps/build): bump golang to 1.23 and use go.mod as single point of truth for all build files (@tuunit)
- [#2697](https://github.com/oauth2-proxy/oauth2-proxy/pull/2697) Use `Max-Age` instead of `Expires` for cookie expiration (@matpen-wi)
# V7.8.1 # V7.8.1

View File

@ -14,7 +14,7 @@ import (
// MakeCookieFromOptions constructs a cookie based on the given *options.CookieOptions, // MakeCookieFromOptions constructs a cookie based on the given *options.CookieOptions,
// value and creation time // value and creation time
func MakeCookieFromOptions(req *http.Request, name string, value string, opts *options.Cookie, expiration time.Duration, now time.Time) *http.Cookie { func MakeCookieFromOptions(req *http.Request, name string, value string, opts *options.Cookie, expiration time.Duration) *http.Cookie {
domain := GetCookieDomain(req, opts.Domains) domain := GetCookieDomain(req, opts.Domains)
// If nothing matches, create the cookie with the shortest domain // If nothing matches, create the cookie with the shortest domain
if domain == "" && len(opts.Domains) > 0 { if domain == "" && len(opts.Domains) > 0 {
@ -35,8 +35,10 @@ func MakeCookieFromOptions(req *http.Request, name string, value string, opts *o
SameSite: ParseSameSite(opts.SameSite), SameSite: ParseSameSite(opts.SameSite),
} }
if expiration != time.Duration(0) { if expiration > time.Duration(0) {
c.Expires = now.Add(expiration) c.MaxAge = int(expiration.Seconds())
} else if expiration < time.Duration(0) {
c.MaxAge = -1
} }
warnInvalidDomain(c, req) warnInvalidDomain(c, req)

View File

@ -1,9 +1,7 @@
package cookies package cookies
import ( import (
"net/http"
"testing" "testing"
"time"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
@ -28,8 +26,3 @@ func TestProviderSuite(t *testing.T) {
RegisterFailHandler(Fail) RegisterFailHandler(Fail)
RunSpecs(t, "Cookies") RunSpecs(t, "Cookies")
} }
func testCookieExpires(exp time.Time) string {
var buf [len(http.TimeFormat)]byte
return string(exp.UTC().AppendFormat(buf[:0], http.TimeFormat))
}

View File

@ -87,7 +87,7 @@ var _ = Describe("Cookie Tests", func() {
opts options.Cookie opts options.Cookie
expiration time.Duration expiration time.Duration
now time.Time now time.Time
expectedOutput time.Time expectedOutput int
} }
validName := "_oauth2_proxy" validName := "_oauth2_proxy"
@ -95,7 +95,7 @@ var _ = Describe("Cookie Tests", func() {
domains := []string{"www.cookies.test"} domains := []string{"www.cookies.test"}
now := time.Now() now := time.Now()
var expectedExpires time.Time var expectedMaxAge int
DescribeTable("should return cookies with or without expiration", DescribeTable("should return cookies with or without expiration",
func(in makeCookieFromOptionsTableInput) { func(in makeCookieFromOptionsTableInput) {
@ -106,7 +106,7 @@ var _ = Describe("Cookie Tests", func() {
) )
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(MakeCookieFromOptions(req, in.name, in.value, &in.opts, in.expiration, in.now).Expires).To(Equal(in.expectedOutput)) Expect(MakeCookieFromOptions(req, in.name, in.value, &in.opts, in.expiration).MaxAge).To(Equal(in.expectedOutput))
}, },
Entry("persistent cookie", makeCookieFromOptionsTableInput{ Entry("persistent cookie", makeCookieFromOptionsTableInput{
host: "www.cookies.test", host: "www.cookies.test",
@ -125,7 +125,26 @@ var _ = Describe("Cookie Tests", func() {
}, },
expiration: 15 * time.Minute, expiration: 15 * time.Minute,
now: now, now: now,
expectedOutput: now.Add(15 * time.Minute), 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: "",
},
expiration: time.Hour * -1,
now: now,
expectedOutput: -1,
}), }),
Entry("session cookie", makeCookieFromOptionsTableInput{ Entry("session cookie", makeCookieFromOptionsTableInput{
host: "www.cookies.test", host: "www.cookies.test",
@ -144,7 +163,7 @@ var _ = Describe("Cookie Tests", func() {
}, },
expiration: 0, expiration: 0,
now: now, now: now,
expectedOutput: expectedExpires, expectedOutput: expectedMaxAge,
}), }),
) )
}) })

View File

@ -145,7 +145,6 @@ func (c *csrf) SetCookie(rw http.ResponseWriter, req *http.Request) (*http.Cooki
encoded, encoded,
c.cookieOpts, c.cookieOpts,
c.cookieOpts.CSRFExpire, c.cookieOpts.CSRFExpire,
c.time.Now(),
) )
http.SetCookie(rw, cookie) http.SetCookie(rw, cookie)
@ -160,7 +159,6 @@ func (c *csrf) ClearCookie(rw http.ResponseWriter, req *http.Request) {
"", "",
c.cookieOpts, c.cookieOpts,
time.Hour*-1, time.Hour*-1,
c.time.Now(),
)) ))
} }

View File

@ -159,10 +159,10 @@ var _ = Describe("CSRF Cookie with non-fixed name Tests", func() {
)) ))
Expect(rw.Header().Get("Set-Cookie")).To(ContainSubstring( Expect(rw.Header().Get("Set-Cookie")).To(ContainSubstring(
fmt.Sprintf( fmt.Sprintf(
"; Path=%s; Domain=%s; Expires=%s; HttpOnly; Secure", "; Path=%s; Domain=%s; Max-Age=%d; HttpOnly; Secure",
cookiePath, cookiePath,
cookieDomain, cookieDomain,
testCookieExpires(testNow.Add(cookieOpts.CSRFExpire)), int(cookieOpts.CSRFExpire.Seconds()),
), ),
)) ))
}) })
@ -176,11 +176,10 @@ var _ = Describe("CSRF Cookie with non-fixed name Tests", func() {
Expect(rw.Header().Get("Set-Cookie")).To(Equal( Expect(rw.Header().Get("Set-Cookie")).To(Equal(
fmt.Sprintf( fmt.Sprintf(
"%s=; Path=%s; Domain=%s; Expires=%s; HttpOnly; Secure", "%s=; Path=%s; Domain=%s; Max-Age=0; HttpOnly; Secure",
privateCSRF.cookieName(), privateCSRF.cookieName(),
cookiePath, cookiePath,
cookieDomain, cookieDomain,
testCookieExpires(testNow.Add(time.Hour*-1)),
), ),
)) ))
}) })

View File

@ -161,10 +161,10 @@ var _ = Describe("CSRF Cookie Tests", func() {
)) ))
Expect(rw.Header().Get("Set-Cookie")).To(ContainSubstring( Expect(rw.Header().Get("Set-Cookie")).To(ContainSubstring(
fmt.Sprintf( fmt.Sprintf(
"; Path=%s; Domain=%s; Expires=%s; HttpOnly; Secure", "; Path=%s; Domain=%s; Max-Age=%d; HttpOnly; Secure",
cookiePath, cookiePath,
cookieDomain, cookieDomain,
testCookieExpires(testNow.Add(cookieOpts.CSRFExpire)), int(cookieOpts.CSRFExpire.Seconds()),
), ),
)) ))
}) })
@ -239,11 +239,10 @@ var _ = Describe("CSRF Cookie Tests", func() {
Expect(rw.Header().Get("Set-Cookie")).To(Equal( Expect(rw.Header().Get("Set-Cookie")).To(Equal(
fmt.Sprintf( fmt.Sprintf(
"%s=; Path=%s; Domain=%s; Expires=%s; HttpOnly; Secure", "%s=; Path=%s; Domain=%s; Max-Age=0; HttpOnly; Secure",
privateCSRF.cookieName(), privateCSRF.cookieName(),
cookiePath, cookiePath,
cookieDomain, cookieDomain,
testCookieExpires(testNow.Add(time.Hour*-1)),
), ),
)) ))
}) })

View File

@ -74,7 +74,7 @@ func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
for _, c := range req.Cookies() { for _, c := range req.Cookies() {
if cookieNameRegex.MatchString(c.Name) { if cookieNameRegex.MatchString(c.Name) {
clearCookie := s.makeCookie(req, c.Name, "", time.Hour*-1, time.Now()) clearCookie := s.makeCookie(req, c.Name, "", time.Hour*-1)
http.SetCookie(rw, clearCookie) http.SetCookie(rw, clearCookie)
} }
@ -126,21 +126,20 @@ func (s *SessionStore) makeSessionCookie(req *http.Request, value []byte, now ti
return nil, err return nil, err
} }
} }
c := s.makeCookie(req, s.Cookie.Name, strValue, s.Cookie.Expire, now) c := s.makeCookie(req, s.Cookie.Name, strValue, s.Cookie.Expire)
if len(c.String()) > maxCookieLength { if len(c.String()) > maxCookieLength {
return splitCookie(c), nil return splitCookie(c), nil
} }
return []*http.Cookie{c}, nil return []*http.Cookie{c}, nil
} }
func (s *SessionStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie { func (s *SessionStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration) *http.Cookie {
return pkgcookies.MakeCookieFromOptions( return pkgcookies.MakeCookieFromOptions(
req, req,
name, name,
value, value,
s.Cookie, s.Cookie,
expiration, expiration,
now,
) )
} }

View File

@ -223,7 +223,6 @@ func (t *ticket) clearCookie(rw http.ResponseWriter, req *http.Request) {
"", "",
t.options, t.options,
time.Hour*-1, time.Hour*-1,
time.Now(),
)) ))
} }
@ -242,7 +241,6 @@ func (t *ticket) makeCookie(req *http.Request, value string, expires time.Durati
value, value,
t.options, t.options,
expires, expires,
now,
), nil ), nil
} }

View File

@ -385,7 +385,7 @@ func SessionStoreInterfaceTests(in *testInput) {
broken := "BrokenSessionFromADifferentSessionImplementation" broken := "BrokenSessionFromADifferentSessionImplementation"
value, err := encryption.SignedValue(in.cookieOpts.Secret, in.cookieOpts.Name, []byte(broken), time.Now()) value, err := encryption.SignedValue(in.cookieOpts.Secret, in.cookieOpts.Name, []byte(broken), time.Now())
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
cookie := cookiesapi.MakeCookieFromOptions(in.request, in.cookieOpts.Name, value, in.cookieOpts, in.cookieOpts.Expire, time.Now()) cookie := cookiesapi.MakeCookieFromOptions(in.request, in.cookieOpts.Name, value, in.cookieOpts, in.cookieOpts.Expire)
in.request.AddCookie(cookie) in.request.AddCookie(cookie)
err = in.ss().Save(in.response, in.request, in.session) err = in.ss().Save(in.response, in.request, in.session)