@@ -0,0 +1,195 @@
1+package cookies
2+
3+import (
4+ "fmt"
5+ "net/http"
6+ "net/http/httptest"
7+ "net/url"
8+ "time"
9+
10+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
11+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
12+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
13+ . "github.com/onsi/ginkgo"
14+ . "github.com/onsi/gomega"
15+)
16+
17+var _ = Describe("CSRF Cookie with non-fixed name Tests", func() {
18+ var (
19+ cookieOpts *options.Cookie
20+ publicCSRF CSRF
21+ privateCSRF *csrf
22+ )
23+
24+ BeforeEach(func() {
25+ cookieOpts = &options.Cookie{
26+ Name: cookieName,
27+ Secret: cookieSecret,
28+ Domains: []string{cookieDomain},
29+ Path: cookiePath,
30+ Expire: time.Hour,
31+ Secure: true,
32+ HTTPOnly: true,
33+ CSRFPerRequest: true,
34+ CSRFExpire: time.Duration(5) * time.Minute,
35+ }
36+
37+ var err error
38+ publicCSRF, err = NewCSRF(cookieOpts, "verifier")
39+ Expect(err).ToNot(HaveOccurred())
40+
41+ privateCSRF = publicCSRF.(*csrf)
42+ })
43+
44+ Context("NewCSRF", func() {
45+ It("makes unique nonces for OAuth and OIDC", func() {
46+ Expect(privateCSRF.OAuthState).ToNot(BeEmpty())
47+ Expect(privateCSRF.OIDCNonce).ToNot(BeEmpty())
48+ Expect(privateCSRF.OAuthState).ToNot(Equal(privateCSRF.OIDCNonce))
49+ Expect(privateCSRF.CodeVerifier).To(Equal("verifier"))
50+ })
51+
52+ It("makes unique nonces between multiple CSRFs", func() {
53+ other, err := NewCSRF(cookieOpts, "verifier")
54+ Expect(err).ToNot(HaveOccurred())
55+
56+ Expect(privateCSRF.OAuthState).ToNot(Equal(other.(*csrf).OAuthState))
57+ Expect(privateCSRF.OIDCNonce).ToNot(Equal(other.(*csrf).OIDCNonce))
58+ Expect(privateCSRF.CodeVerifier).To(Equal("verifier"))
59+ })
60+ })
61+
62+ Context("CheckOAuthState and CheckOIDCNonce", func() {
63+ It("checks that hashed versions match", func() {
64+ privateCSRF.OAuthState = []byte(csrfState)
65+ privateCSRF.OIDCNonce = []byte(csrfNonce)
66+
67+ stateHashed := encryption.HashNonce([]byte(csrfState))
68+ nonceHashed := encryption.HashNonce([]byte(csrfNonce))
69+
70+ Expect(publicCSRF.CheckOAuthState(stateHashed)).To(BeTrue())
71+ Expect(publicCSRF.CheckOIDCNonce(nonceHashed)).To(BeTrue())
72+
73+ Expect(publicCSRF.CheckOAuthState(csrfNonce)).To(BeFalse())
74+ Expect(publicCSRF.CheckOIDCNonce(csrfState)).To(BeFalse())
75+ Expect(publicCSRF.CheckOAuthState(csrfState + csrfNonce)).To(BeFalse())
76+ Expect(publicCSRF.CheckOIDCNonce(csrfNonce + csrfState)).To(BeFalse())
77+ Expect(publicCSRF.CheckOAuthState("")).To(BeFalse())
78+ Expect(publicCSRF.CheckOIDCNonce("")).To(BeFalse())
79+ Expect(publicCSRF.GetCodeVerifier()).To(Equal("verifier"))
80+ })
81+ })
82+
83+ Context("SetSessionNonce", func() {
84+ It("sets the session.Nonce", func() {
85+ session := &sessions.SessionState{}
86+ publicCSRF.SetSessionNonce(session)
87+ Expect(session.Nonce).To(Equal(privateCSRF.OIDCNonce))
88+ })
89+ })
90+
91+ Context("encodeCookie and decodeCSRFCookie", func() {
92+ It("encodes and decodes to the same nonces", func() {
93+ privateCSRF.OAuthState = []byte(csrfState)
94+ privateCSRF.OIDCNonce = []byte(csrfNonce)
95+
96+ encoded, err := privateCSRF.encodeCookie()
97+ Expect(err).ToNot(HaveOccurred())
98+
99+ cookie := &http.Cookie{
100+ Name: privateCSRF.cookieName(),
101+ Value: encoded,
102+ }
103+ decoded, err := decodeCSRFCookie(cookie, cookieOpts)
104+ Expect(err).ToNot(HaveOccurred())
105+
106+ Expect(decoded).ToNot(BeNil())
107+ Expect(decoded.OAuthState).To(Equal([]byte(csrfState)))
108+ Expect(decoded.OIDCNonce).To(Equal([]byte(csrfNonce)))
109+ })
110+
111+ It("signs the encoded cookie value", func() {
112+ encoded, err := privateCSRF.encodeCookie()
113+ Expect(err).ToNot(HaveOccurred())
114+
115+ cookie := &http.Cookie{
116+ Name: privateCSRF.cookieName(),
117+ Value: encoded,
118+ }
119+
120+ _, _, valid := encryption.Validate(cookie, cookieOpts.Secret, cookieOpts.Expire)
121+ Expect(valid).To(BeTrue())
122+ })
123+ })
124+
125+ Context("Cookie Management", func() {
126+ var req *http.Request
127+
128+ testNow := time.Unix(nowEpoch, 0)
129+
130+ BeforeEach(func() {
131+ privateCSRF.time.Set(testNow)
132+
133+ req = &http.Request{
134+ Method: http.MethodGet,
135+ Proto: "HTTP/1.1",
136+ Host: cookieDomain,
137+
138+ URL: &url.URL{
139+ Scheme: "https",
140+ Host: cookieDomain,
141+ Path: cookiePath,
142+ },
143+ }
144+ })
145+
146+ AfterEach(func() {
147+ privateCSRF.time.Reset()
148+ })
149+
150+ Context("SetCookie", func() {
151+ It("adds the encoded CSRF cookie to a ResponseWriter", func() {
152+ rw := httptest.NewRecorder()
153+
154+ _, err := publicCSRF.SetCookie(rw, req)
155+ Expect(err).ToNot(HaveOccurred())
156+
157+ Expect(rw.Header().Get("Set-Cookie")).To(ContainSubstring(
158+ fmt.Sprintf("%s=", privateCSRF.cookieName()),
159+ ))
160+ Expect(rw.Header().Get("Set-Cookie")).To(ContainSubstring(
161+ fmt.Sprintf(
162+ "; Path=%s; Domain=%s; Expires=%s; HttpOnly; Secure",
163+ cookiePath,
164+ cookieDomain,
165+ testCookieExpires(testNow.Add(cookieOpts.CSRFExpire)),
166+ ),
167+ ))
168+ })
169+ })
170+
171+ Context("ClearCookie", func() {
172+ It("sets a cookie with an empty value in the past", func() {
173+ rw := httptest.NewRecorder()
174+
175+ publicCSRF.ClearCookie(rw, req)
176+
177+ Expect(rw.Header().Get("Set-Cookie")).To(Equal(
178+ fmt.Sprintf(
179+ "%s=; Path=%s; Domain=%s; Expires=%s; HttpOnly; Secure",
180+ privateCSRF.cookieName(),
181+ cookiePath,
182+ cookieDomain,
183+ testCookieExpires(testNow.Add(time.Hour*-1)),
184+ ),
185+ ))
186+ })
187+ })
188+
189+ Context("cookieName", func() {
190+ It("has the cookie options name as a base", func() {
191+ Expect(privateCSRF.cookieName()).To(ContainSubstring(cookieName))
192+ })
193+ })
194+ })
195+})