2020package context
2121
2222import (
23- "encoding/base32"
24- "fmt"
23+ "html/template"
2524 "net/http"
2625 "strconv"
2726 "time"
2827
2928 "code.gitea.io/gitea/modules/log"
30- "code.gitea.io/gitea/modules/setting"
3129 "code.gitea.io/gitea/modules/util"
32- "code.gitea.io/gitea/modules/web/middleware"
30+ )
31+
32+ const (
33+ CsrfHeaderName = "X-Csrf-Token"
34+ CsrfFormName = "_csrf"
3335)
3436
3537// CSRFProtector represents a CSRF protector and is used to get the current token and validate the token.
3638type CSRFProtector interface {
37- // GetHeaderName returns HTTP header to search for token.
38- GetHeaderName () string
39- // GetFormName returns form value to search for token.
40- GetFormName () string
41- // GetToken returns the token.
42- GetToken () string
43- // Validate validates the token in http context.
39+ // PrepareForSessionUser prepares the csrf protector for the current session user.
40+ PrepareForSessionUser (ctx * Context )
41+ // Validate validates the csrf token in http context.
4442 Validate (ctx * Context )
45- // DeleteCookie deletes the cookie
43+ // DeleteCookie deletes the csrf cookie
4644 DeleteCookie (ctx * Context )
4745}
4846
4947type csrfProtector struct {
5048 opt CsrfOptions
51- // Token generated to pass via header, cookie, or hidden form value.
52- Token string
53- // This value must be unique per user.
54- ID string
55- }
56-
57- // GetHeaderName returns the name of the HTTP header for csrf token.
58- func (c * csrfProtector ) GetHeaderName () string {
59- return c .opt .Header
60- }
61-
62- // GetFormName returns the name of the form value for csrf token.
63- func (c * csrfProtector ) GetFormName () string {
64- return c .opt .Form
65- }
66-
67- // GetToken returns the current token. This is typically used
68- // to populate a hidden form in an HTML template.
69- func (c * csrfProtector ) GetToken () string {
70- return c .Token
49+ // id must be unique per user.
50+ id string
51+ // token is the valid one which wil be used by end user and passed via header, cookie, or hidden form value.
52+ token string
7153}
7254
7355// CsrfOptions maintains options to manage behavior of Generate.
7456type CsrfOptions struct {
7557 // The global secret value used to generate Tokens.
7658 Secret string
77- // HTTP header used to set and get token.
78- Header string
79- // Form value used to set and get token.
80- Form string
8159 // Cookie value used to set and get token.
8260 Cookie string
8361 // Cookie domain.
@@ -87,156 +65,106 @@ type CsrfOptions struct {
8765 CookieHTTPOnly bool
8866 // SameSite set the cookie SameSite type
8967 SameSite http.SameSite
90- // Key used for getting the unique ID per user.
91- SessionKey string
92- // oldSessionKey saves old value corresponding to SessionKey.
93- oldSessionKey string
94- // If true, send token via X-Csrf-Token header.
95- SetHeader bool
96- // If true, send token via _csrf cookie.
97- SetCookie bool
9868 // Set the Secure flag to true on the cookie.
9969 Secure bool
100- // Disallow Origin appear in request header.
101- Origin bool
102- // Cookie lifetime. Default is 0
103- CookieLifeTime int
104- }
105-
106- func prepareDefaultCsrfOptions (opt CsrfOptions ) CsrfOptions {
107- if opt .Secret == "" {
108- randBytes , err := util .CryptoRandomBytes (8 )
109- if err != nil {
110- // this panic can be handled by the recover() in http handlers
111- panic (fmt .Errorf ("failed to generate random bytes: %w" , err ))
112- }
113- opt .Secret = base32 .StdEncoding .EncodeToString (randBytes )
114- }
115- if opt .Header == "" {
116- opt .Header = "X-Csrf-Token"
117- }
118- if opt .Form == "" {
119- opt .Form = "_csrf"
120- }
121- if opt .Cookie == "" {
122- opt .Cookie = "_csrf"
123- }
124- if opt .CookiePath == "" {
125- opt .CookiePath = "/"
126- }
127- if opt .SessionKey == "" {
128- opt .SessionKey = "uid"
129- }
130- if opt .CookieLifeTime == 0 {
131- opt .CookieLifeTime = int (CsrfTokenTimeout .Seconds ())
132- }
133-
134- opt .oldSessionKey = "_old_" + opt .SessionKey
135- return opt
70+ // sessionKey is the key used for getting the unique ID per user.
71+ sessionKey string
72+ // oldSessionKey saves old value corresponding to sessionKey.
73+ oldSessionKey string
13674}
13775
138- func newCsrfCookie (c * csrfProtector , value string ) * http.Cookie {
76+ func newCsrfCookie (opt * CsrfOptions , value string ) * http.Cookie {
13977 return & http.Cookie {
140- Name : c . opt .Cookie ,
78+ Name : opt .Cookie ,
14179 Value : value ,
142- Path : c . opt .CookiePath ,
143- Domain : c . opt .CookieDomain ,
144- MaxAge : c . opt . CookieLifeTime ,
145- Secure : c . opt .Secure ,
146- HttpOnly : c . opt .CookieHTTPOnly ,
147- SameSite : c . opt .SameSite ,
80+ Path : opt .CookiePath ,
81+ Domain : opt .CookieDomain ,
82+ MaxAge : int ( CsrfTokenTimeout . Seconds ()) ,
83+ Secure : opt .Secure ,
84+ HttpOnly : opt .CookieHTTPOnly ,
85+ SameSite : opt .SameSite ,
14886 }
14987}
15088
151- // PrepareCSRFProtector returns a CSRFProtector to be used for every request.
152- // Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
153- func PrepareCSRFProtector (opt CsrfOptions , ctx * Context ) CSRFProtector {
154- opt = prepareDefaultCsrfOptions (opt )
155- x := & csrfProtector {opt : opt }
156-
157- if opt .Origin && len (ctx .Req .Header .Get ("Origin" )) > 0 {
158- return x
89+ func NewCSRFProtector (opt CsrfOptions ) CSRFProtector {
90+ if opt .Secret == "" {
91+ panic ("CSRF secret is empty but it must be set" ) // it shouldn't happen because it is always set in code
15992 }
93+ opt .Cookie = util .IfZero (opt .Cookie , "_csrf" )
94+ opt .CookiePath = util .IfZero (opt .CookiePath , "/" )
95+ opt .sessionKey = "uid"
96+ opt .oldSessionKey = "_old_" + opt .sessionKey
97+ return & csrfProtector {opt : opt }
98+ }
16099
161- x . ID = "0"
162- uidAny := ctx . Session . Get ( opt . SessionKey )
163- if uidAny != nil {
100+ func ( c * csrfProtector ) PrepareForSessionUser ( ctx * Context ) {
101+ c . id = "0"
102+ if uidAny := ctx . Session . Get ( c . opt . sessionKey ); uidAny != nil {
164103 switch uidVal := uidAny .(type ) {
165104 case string :
166- x . ID = uidVal
105+ c . id = uidVal
167106 case int64 :
168- x . ID = strconv .FormatInt (uidVal , 10 )
107+ c . id = strconv .FormatInt (uidVal , 10 )
169108 default :
170109 log .Error ("invalid uid type in session: %T" , uidAny )
171110 }
172111 }
173112
174- oldUID := ctx .Session .Get (opt .oldSessionKey )
175- uidChanged := oldUID == nil || oldUID .(string ) != x . ID
176- cookieToken := ctx .GetSiteCookie (opt .Cookie )
113+ oldUID := ctx .Session .Get (c . opt .oldSessionKey )
114+ uidChanged := oldUID == nil || oldUID .(string ) != c . id
115+ cookieToken := ctx .GetSiteCookie (c . opt .Cookie )
177116
178117 needsNew := true
179118 if uidChanged {
180- _ = ctx .Session .Set (opt .oldSessionKey , x . ID )
119+ _ = ctx .Session .Set (c . opt .oldSessionKey , c . id )
181120 } else if cookieToken != "" {
182121 // If cookie token presents, re-use existing unexpired token, else generate a new one.
183122 if issueTime , ok := ParseCsrfToken (cookieToken ); ok {
184123 dur := time .Since (issueTime ) // issueTime is not a monotonic-clock, the server time may change a lot to an early time.
185124 if dur >= - CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval {
186- x . Token = cookieToken
125+ c . token = cookieToken
187126 needsNew = false
188127 }
189128 }
190129 }
191130
192131 if needsNew {
193132 // FIXME: actionId.
194- x .Token = GenerateCsrfToken (x .opt .Secret , x .ID , "POST" , time .Now ())
195- if opt .SetCookie {
196- cookie := newCsrfCookie (x , x .Token )
197- ctx .Resp .Header ().Add ("Set-Cookie" , cookie .String ())
198- }
133+ c .token = GenerateCsrfToken (c .opt .Secret , c .id , "POST" , time .Now ())
134+ cookie := newCsrfCookie (& c .opt , c .token )
135+ ctx .Resp .Header ().Add ("Set-Cookie" , cookie .String ())
199136 }
200137
201- if opt .SetHeader {
202- ctx .Resp .Header ().Add (opt .Header , x .Token )
203- }
204- return x
138+ ctx .Data ["CsrfToken" ] = c .token
139+ ctx .Data ["CsrfTokenHtml" ] = template .HTML (`<input type="hidden" name="_csrf" value="` + template .HTMLEscapeString (c .token ) + `">` )
205140}
206141
207142func (c * csrfProtector ) validateToken (ctx * Context , token string ) {
208- if ! ValidCsrfToken (token , c .opt .Secret , c .ID , "POST" , time .Now ()) {
143+ if ! ValidCsrfToken (token , c .opt .Secret , c .id , "POST" , time .Now ()) {
209144 c .DeleteCookie (ctx )
210- if middleware .IsAPIPath (ctx .Req ) {
211- // currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
212- http .Error (ctx .Resp , "Invalid CSRF token." , http .StatusBadRequest )
213- } else {
214- ctx .Flash .Error (ctx .Tr ("error.invalid_csrf" ))
215- ctx .Redirect (setting .AppSubURL + "/" )
216- }
145+ // currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
146+ // FIXME: distinguish what the response is for: HTML (web page) or JSON (fetch)
147+ http .Error (ctx .Resp , "Invalid CSRF token." , http .StatusBadRequest )
217148 }
218149}
219150
220151// Validate should be used as a per route middleware. It attempts to get a token from an "X-Csrf-Token"
221152// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated.
222- // If this validation fails, custom Error is sent in the reply.
223- // If neither a header nor form value is found, http.StatusBadRequest is sent.
153+ // If this validation fails, http.StatusBadRequest is sent.
224154func (c * csrfProtector ) Validate (ctx * Context ) {
225- if token := ctx .Req .Header .Get (c . GetHeaderName () ); token != "" {
155+ if token := ctx .Req .Header .Get (CsrfHeaderName ); token != "" {
226156 c .validateToken (ctx , token )
227157 return
228158 }
229- if token := ctx .Req .FormValue (c . GetFormName () ); token != "" {
159+ if token := ctx .Req .FormValue (CsrfFormName ); token != "" {
230160 c .validateToken (ctx , token )
231161 return
232162 }
233163 c .validateToken (ctx , "" ) // no csrf token, use an empty token to respond error
234164}
235165
236166func (c * csrfProtector ) DeleteCookie (ctx * Context ) {
237- if c .opt .SetCookie {
238- cookie := newCsrfCookie (c , "" )
239- cookie .MaxAge = - 1
240- ctx .Resp .Header ().Add ("Set-Cookie" , cookie .String ())
241- }
167+ cookie := newCsrfCookie (& c .opt , "" )
168+ cookie .MaxAge = - 1
169+ ctx .Resp .Header ().Add ("Set-Cookie" , cookie .String ())
242170}
0 commit comments