@@ -31,97 +31,55 @@ import (
31
31
"code.gitea.io/gitea/modules/web/middleware"
32
32
)
33
33
34
- // CSRF represents a CSRF service and is used to get the current token and validate a suspect token.
35
- type CSRF interface {
36
- // Return HTTP header to search for token.
34
+ // CSRFProtector represents a CSRF protector and is used to get the current token and validate the token.
35
+ type CSRFProtector interface {
36
+ // GetHeaderName returns HTTP header to search for token.
37
37
GetHeaderName () string
38
- // Return form value to search for token.
38
+ // GetFormName returns form value to search for token.
39
39
GetFormName () string
40
- // Return cookie name to search for token.
41
- GetCookieName () string
42
- // Return cookie path
43
- GetCookiePath () string
44
- // Return the flag value used for the csrf token.
45
- GetCookieHTTPOnly () bool
46
- // Return cookie domain
47
- GetCookieDomain () string
48
- // Return the token.
40
+ // GetToken returns the token.
49
41
GetToken () string
50
- // Validate by token.
51
- ValidToken (t string ) bool
52
- // Error replies to the request with a custom function when ValidToken fails.
53
- Error (w http.ResponseWriter )
42
+ // Validate validates the token in http context.
43
+ Validate (ctx * Context )
54
44
}
55
45
56
- type csrf struct {
57
- // Header name value for setting and getting csrf token.
46
+ type csrfProtector struct {
47
+ // Header name value for setting and getting CSRF token.
58
48
Header string
59
- // Form name value for setting and getting csrf token.
49
+ // Form name value for setting and getting CSRF token.
60
50
Form string
61
- // Cookie name value for setting and getting csrf token.
51
+ // Cookie name value for setting and getting CSRF token.
62
52
Cookie string
63
53
// Cookie domain
64
54
CookieDomain string
65
55
// Cookie path
66
56
CookiePath string
67
- // Cookie HttpOnly flag value used for the csrf token.
57
+ // Cookie HttpOnly flag value used for the CSRF token.
68
58
CookieHTTPOnly bool
69
59
// Token generated to pass via header, cookie, or hidden form value.
70
60
Token string
71
61
// This value must be unique per user.
72
62
ID string
73
63
// Secret used along with the unique id above to generate the Token.
74
64
Secret string
75
- // ErrorFunc is the custom function that replies to the request when ValidToken fails.
76
- ErrorFunc func (w http.ResponseWriter )
77
65
}
78
66
79
- // GetHeaderName returns the name of the HTTP header for csrf token.
80
- func (c * csrf ) GetHeaderName () string {
67
+ // GetHeaderName returns the name of the HTTP header for CSRF token.
68
+ func (c * csrfProtector ) GetHeaderName () string {
81
69
return c .Header
82
70
}
83
71
84
- // GetFormName returns the name of the form value for csrf token.
85
- func (c * csrf ) GetFormName () string {
72
+ // GetFormName returns the name of the form value for CSRF token.
73
+ func (c * csrfProtector ) GetFormName () string {
86
74
return c .Form
87
75
}
88
76
89
- // GetCookieName returns the name of the cookie for csrf token.
90
- func (c * csrf ) GetCookieName () string {
91
- return c .Cookie
92
- }
93
-
94
- // GetCookiePath returns the path of the cookie for csrf token.
95
- func (c * csrf ) GetCookiePath () string {
96
- return c .CookiePath
97
- }
98
-
99
- // GetCookieHTTPOnly returns the flag value used for the csrf token.
100
- func (c * csrf ) GetCookieHTTPOnly () bool {
101
- return c .CookieHTTPOnly
102
- }
103
-
104
- // GetCookieDomain returns the flag value used for the csrf token.
105
- func (c * csrf ) GetCookieDomain () string {
106
- return c .CookieDomain
107
- }
108
-
109
77
// GetToken returns the current token. This is typically used
110
78
// to populate a hidden form in an HTML template.
111
- func (c * csrf ) GetToken () string {
79
+ func (c * csrfProtector ) GetToken () string {
112
80
return c .Token
113
81
}
114
82
115
- // ValidToken validates the passed token against the existing Secret and ID.
116
- func (c * csrf ) ValidToken (t string ) bool {
117
- return ValidToken (t , c .Secret , c .ID , "POST" )
118
- }
119
-
120
- // Error replies to the request when ValidToken fails.
121
- func (c * csrf ) Error (w http.ResponseWriter ) {
122
- c .ErrorFunc (w )
123
- }
124
-
125
83
// CsrfOptions maintains options to manage behavior of Generate.
126
84
type CsrfOptions struct {
127
85
// The global secret value used to generate Tokens.
@@ -143,73 +101,58 @@ type CsrfOptions struct {
143
101
SessionKey string
144
102
// oldSessionKey saves old value corresponding to SessionKey.
145
103
oldSessionKey string
146
- // If true, send token via X-CSRFToken header.
104
+ // If true, send token via X-Csrf-Token header.
147
105
SetHeader bool
148
106
// If true, send token via _csrf cookie.
149
107
SetCookie bool
150
108
// Set the Secure flag to true on the cookie.
151
109
Secure bool
152
110
// Disallow Origin appear in request header.
153
111
Origin bool
154
- // The function called when Validate fails.
155
- ErrorFunc func (w http.ResponseWriter )
156
- // Cookie life time. Default is 0
112
+ // Cookie lifetime. Default is 0
157
113
CookieLifeTime int
158
114
}
159
115
160
- func prepareOptions (options []CsrfOptions ) CsrfOptions {
161
- var opt CsrfOptions
162
- if len (options ) > 0 {
163
- opt = options [0 ]
164
- }
165
-
166
- // Defaults.
167
- if len (opt .Secret ) == 0 {
116
+ func prepareDefaultCsrfOptions (opt CsrfOptions ) CsrfOptions {
117
+ if opt .Secret == "" {
168
118
randBytes , err := util .CryptoRandomBytes (8 )
169
119
if err != nil {
170
120
// this panic can be handled by the recover() in http handlers
171
121
panic (fmt .Errorf ("failed to generate random bytes: %w" , err ))
172
122
}
173
123
opt .Secret = base32 .StdEncoding .EncodeToString (randBytes )
174
124
}
175
- if len ( opt .Header ) == 0 {
176
- opt .Header = "X-CSRFToken "
125
+ if opt .Header == "" {
126
+ opt .Header = "X-Csrf-Token "
177
127
}
178
- if len ( opt .Form ) == 0 {
128
+ if opt .Form == "" {
179
129
opt .Form = "_csrf"
180
130
}
181
- if len ( opt .Cookie ) == 0 {
131
+ if opt .Cookie == "" {
182
132
opt .Cookie = "_csrf"
183
133
}
184
- if len ( opt .CookiePath ) == 0 {
134
+ if opt .CookiePath == "" {
185
135
opt .CookiePath = "/"
186
136
}
187
- if len ( opt .SessionKey ) == 0 {
137
+ if opt .SessionKey == "" {
188
138
opt .SessionKey = "uid"
189
139
}
190
140
opt .oldSessionKey = "_old_" + opt .SessionKey
191
- if opt .ErrorFunc == nil {
192
- opt .ErrorFunc = func (w http.ResponseWriter ) {
193
- http .Error (w , "Invalid csrf token." , http .StatusBadRequest )
194
- }
195
- }
196
-
197
141
return opt
198
142
}
199
143
200
- // Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token .
144
+ // NewCSRFProtector returns a CSRFProtector to be used for every request .
201
145
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
202
- func Csrfer (opt CsrfOptions , ctx * Context ) CSRF {
203
- opt = prepareOptions ([] CsrfOptions { opt } )
204
- x := & csrf {
146
+ func NewCSRFProtector (opt CsrfOptions , ctx * Context ) CSRFProtector {
147
+ opt = prepareDefaultCsrfOptions ( opt )
148
+ x := & csrfProtector {
205
149
Secret : opt .Secret ,
206
150
Header : opt .Header ,
207
151
Form : opt .Form ,
208
152
Cookie : opt .Cookie ,
209
153
CookieDomain : opt .CookieDomain ,
210
154
CookiePath : opt .CookiePath ,
211
155
CookieHTTPOnly : opt .CookieHTTPOnly ,
212
- ErrorFunc : opt .ErrorFunc ,
213
156
}
214
157
215
158
if opt .Origin && len (ctx .Req .Header .Get ("Origin" )) > 0 {
@@ -236,17 +179,25 @@ func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
236
179
_ = ctx .Session .Set (opt .oldSessionKey , x .ID )
237
180
} else {
238
181
// If cookie present, map existing token, else generate a new one.
239
- if val := ctx .GetCookie (opt .Cookie ); len (val ) > 0 {
240
- // FIXME: test coverage.
241
- x .Token = val
182
+ if val := ctx .GetCookie (opt .Cookie ); val != "" {
183
+ x .Token = val // FIXME: test coverage.
242
184
} else {
243
185
needsNew = true
244
186
}
245
187
}
246
188
189
+ if ! needsNew {
190
+ if issueTime , ok := ParseCsrfToken (x .Token ); ok {
191
+ dur := time .Since (issueTime )
192
+ if dur < - CsrfTokenRegenerationDuration || dur > CsrfTokenRegenerationDuration {
193
+ needsNew = true
194
+ }
195
+ }
196
+ }
197
+
247
198
if needsNew {
248
199
// FIXME: actionId.
249
- x .Token = GenerateToken (x .Secret , x .ID , "POST" )
200
+ x .Token = GenerateCsrfToken (x .Secret , x .ID , "POST" , time . Now () )
250
201
if opt .SetCookie {
251
202
var expires interface {}
252
203
if opt .CookieLifeTime == 0 {
@@ -270,47 +221,39 @@ func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
270
221
return x
271
222
}
272
223
273
- // Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken"
274
- // HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated
275
- // using ValidToken. If this validation fails, custom Error is sent in the reply.
276
- // If neither a header or form value is found, http.StatusBadRequest is sent.
277
- func Validate (ctx * Context , x CSRF ) {
278
- if token := ctx .Req .Header .Get (x .GetHeaderName ()); len (token ) > 0 {
279
- if ! x .ValidToken (token ) {
280
- // Delete the cookie
281
- middleware .SetCookie (ctx .Resp , x .GetCookieName (), "" ,
282
- - 1 ,
283
- x .GetCookiePath (),
284
- x .GetCookieDomain ()) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too?
285
- if middleware .IsAPIPath (ctx .Req ) {
286
- x .Error (ctx .Resp )
287
- return
288
- }
224
+ func (c * csrfProtector ) validateToken (ctx * Context , token string ) bool {
225
+ if ! ValidCsrfToken (token , c .Secret , c .ID , "POST" , time .Now ()) {
226
+ // Delete the cookie
227
+ middleware .SetCookie (ctx .Resp , c .Cookie , "" ,
228
+ - 1 ,
229
+ c .CookiePath ,
230
+ c .CookieDomain ) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too?
231
+
232
+ if middleware .IsAPIPath (ctx .Req ) {
233
+ http .Error (ctx .Resp , "Invalid CSRF token." , http .StatusBadRequest )
234
+ } else {
289
235
ctx .Flash .Error (ctx .Tr ("error.invalid_csrf" ))
290
236
ctx .Redirect (setting .AppSubURL + "/" )
291
237
}
292
- return
238
+ return false
293
239
}
294
- if token := ctx .Req .FormValue (x .GetFormName ()); len (token ) > 0 {
295
- if ! x .ValidToken (token ) {
296
- // Delete the cookie
297
- middleware .SetCookie (ctx .Resp , x .GetCookieName (), "" ,
298
- - 1 ,
299
- x .GetCookiePath (),
300
- x .GetCookieDomain ()) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too?
301
- if middleware .IsAPIPath (ctx .Req ) {
302
- x .Error (ctx .Resp )
303
- return
304
- }
305
- ctx .Flash .Error (ctx .Tr ("error.invalid_csrf" ))
306
- ctx .Redirect (setting .AppSubURL + "/" )
240
+ return true
241
+ }
242
+
243
+ // Validate should be used as a per route middleware. It attempts to get a token from an "X-Csrf-Token"
244
+ // HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated
245
+ // using ValidToken. If this validation fails, custom Error is sent in the reply.
246
+ // If neither a header nor form value is found, http.StatusBadRequest is sent.
247
+ func (c * csrfProtector ) Validate (ctx * Context ) {
248
+ if token := ctx .Req .Header .Get (c .GetHeaderName ()); token != "" {
249
+ if c .validateToken (ctx , token ) {
250
+ return
307
251
}
308
- return
309
252
}
310
- if middleware .IsAPIPath (ctx .Req ) {
311
- http .Error (ctx .Resp , "Bad Request: no CSRF token present" , http .StatusBadRequest )
312
- return
253
+ if token := ctx .Req .FormValue (c .GetFormName ()); token != "" {
254
+ if c .validateToken (ctx , token ) {
255
+ return
256
+ }
313
257
}
314
- ctx .Flash .Error (ctx .Tr ("error.missing_csrf" ))
315
- ctx .Redirect (setting .AppSubURL + "/" )
258
+ c .validateToken (ctx , "" ) // no csrf token, use an empty token to respond error
316
259
}
0 commit comments