You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

csrf.go 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. // Copyright 2013 Martini Authors
  2. // Copyright 2014 The Macaron Authors
  3. // Copyright 2021 The Gitea Authors
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  6. // not use this file except in compliance with the License. You may obtain
  7. // a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. // License for the specific language governing permissions and limitations
  15. // under the License.
  16. // a middleware that generates and validates CSRF tokens.
  17. package context
  18. import (
  19. "encoding/base32"
  20. "fmt"
  21. "net/http"
  22. "strconv"
  23. "time"
  24. "code.gitea.io/gitea/modules/log"
  25. "code.gitea.io/gitea/modules/setting"
  26. "code.gitea.io/gitea/modules/util"
  27. "code.gitea.io/gitea/modules/web/middleware"
  28. )
  29. // CSRF represents a CSRF service and is used to get the current token and validate a suspect token.
  30. type CSRF interface {
  31. // Return HTTP header to search for token.
  32. GetHeaderName() string
  33. // Return form value to search for token.
  34. GetFormName() string
  35. // Return cookie name to search for token.
  36. GetCookieName() string
  37. // Return cookie path
  38. GetCookiePath() string
  39. // Return the flag value used for the csrf token.
  40. GetCookieHTTPOnly() bool
  41. // Return cookie domain
  42. GetCookieDomain() string
  43. // Return the token.
  44. GetToken() string
  45. // Validate by token.
  46. ValidToken(t string) bool
  47. // Error replies to the request with a custom function when ValidToken fails.
  48. Error(w http.ResponseWriter)
  49. }
  50. type csrf struct {
  51. // Header name value for setting and getting csrf token.
  52. Header string
  53. // Form name value for setting and getting csrf token.
  54. Form string
  55. // Cookie name value for setting and getting csrf token.
  56. Cookie string
  57. // Cookie domain
  58. CookieDomain string
  59. // Cookie path
  60. CookiePath string
  61. // Cookie HttpOnly flag value used for the csrf token.
  62. CookieHTTPOnly bool
  63. // Token generated to pass via header, cookie, or hidden form value.
  64. Token string
  65. // This value must be unique per user.
  66. ID string
  67. // Secret used along with the unique id above to generate the Token.
  68. Secret string
  69. // ErrorFunc is the custom function that replies to the request when ValidToken fails.
  70. ErrorFunc func(w http.ResponseWriter)
  71. }
  72. // GetHeaderName returns the name of the HTTP header for csrf token.
  73. func (c *csrf) GetHeaderName() string {
  74. return c.Header
  75. }
  76. // GetFormName returns the name of the form value for csrf token.
  77. func (c *csrf) GetFormName() string {
  78. return c.Form
  79. }
  80. // GetCookieName returns the name of the cookie for csrf token.
  81. func (c *csrf) GetCookieName() string {
  82. return c.Cookie
  83. }
  84. // GetCookiePath returns the path of the cookie for csrf token.
  85. func (c *csrf) GetCookiePath() string {
  86. return c.CookiePath
  87. }
  88. // GetCookieHTTPOnly returns the flag value used for the csrf token.
  89. func (c *csrf) GetCookieHTTPOnly() bool {
  90. return c.CookieHTTPOnly
  91. }
  92. // GetCookieDomain returns the flag value used for the csrf token.
  93. func (c *csrf) GetCookieDomain() string {
  94. return c.CookieDomain
  95. }
  96. // GetToken returns the current token. This is typically used
  97. // to populate a hidden form in an HTML template.
  98. func (c *csrf) GetToken() string {
  99. return c.Token
  100. }
  101. // ValidToken validates the passed token against the existing Secret and ID.
  102. func (c *csrf) ValidToken(t string) bool {
  103. return ValidToken(t, c.Secret, c.ID, "POST")
  104. }
  105. // Error replies to the request when ValidToken fails.
  106. func (c *csrf) Error(w http.ResponseWriter) {
  107. c.ErrorFunc(w)
  108. }
  109. // CsrfOptions maintains options to manage behavior of Generate.
  110. type CsrfOptions struct {
  111. // The global secret value used to generate Tokens.
  112. Secret string
  113. // HTTP header used to set and get token.
  114. Header string
  115. // Form value used to set and get token.
  116. Form string
  117. // Cookie value used to set and get token.
  118. Cookie string
  119. // Cookie domain.
  120. CookieDomain string
  121. // Cookie path.
  122. CookiePath string
  123. CookieHTTPOnly bool
  124. // SameSite set the cookie SameSite type
  125. SameSite http.SameSite
  126. // Key used for getting the unique ID per user.
  127. SessionKey string
  128. // oldSessionKey saves old value corresponding to SessionKey.
  129. oldSessionKey string
  130. // If true, send token via X-CSRFToken header.
  131. SetHeader bool
  132. // If true, send token via _csrf cookie.
  133. SetCookie bool
  134. // Set the Secure flag to true on the cookie.
  135. Secure bool
  136. // Disallow Origin appear in request header.
  137. Origin bool
  138. // The function called when Validate fails.
  139. ErrorFunc func(w http.ResponseWriter)
  140. // Cookie life time. Default is 0
  141. CookieLifeTime int
  142. }
  143. func prepareOptions(options []CsrfOptions) CsrfOptions {
  144. var opt CsrfOptions
  145. if len(options) > 0 {
  146. opt = options[0]
  147. }
  148. // Defaults.
  149. if len(opt.Secret) == 0 {
  150. randBytes, err := util.CryptoRandomBytes(8)
  151. if err != nil {
  152. // this panic can be handled by the recover() in http handlers
  153. panic(fmt.Errorf("failed to generate random bytes: %w", err))
  154. }
  155. opt.Secret = base32.StdEncoding.EncodeToString(randBytes)
  156. }
  157. if len(opt.Header) == 0 {
  158. opt.Header = "X-CSRFToken"
  159. }
  160. if len(opt.Form) == 0 {
  161. opt.Form = "_csrf"
  162. }
  163. if len(opt.Cookie) == 0 {
  164. opt.Cookie = "_csrf"
  165. }
  166. if len(opt.CookiePath) == 0 {
  167. opt.CookiePath = "/"
  168. }
  169. if len(opt.SessionKey) == 0 {
  170. opt.SessionKey = "uid"
  171. }
  172. opt.oldSessionKey = "_old_" + opt.SessionKey
  173. if opt.ErrorFunc == nil {
  174. opt.ErrorFunc = func(w http.ResponseWriter) {
  175. http.Error(w, "Invalid csrf token.", http.StatusBadRequest)
  176. }
  177. }
  178. return opt
  179. }
  180. // Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token.
  181. // Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
  182. func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
  183. opt = prepareOptions([]CsrfOptions{opt})
  184. x := &csrf{
  185. Secret: opt.Secret,
  186. Header: opt.Header,
  187. Form: opt.Form,
  188. Cookie: opt.Cookie,
  189. CookieDomain: opt.CookieDomain,
  190. CookiePath: opt.CookiePath,
  191. CookieHTTPOnly: opt.CookieHTTPOnly,
  192. ErrorFunc: opt.ErrorFunc,
  193. }
  194. if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
  195. return x
  196. }
  197. x.ID = "0"
  198. uidAny := ctx.Session.Get(opt.SessionKey)
  199. if uidAny != nil {
  200. switch uidVal := uidAny.(type) {
  201. case string:
  202. x.ID = uidVal
  203. case int64:
  204. x.ID = strconv.FormatInt(uidVal, 10)
  205. default:
  206. log.Error("invalid uid type in session: %T", uidAny)
  207. }
  208. }
  209. needsNew := false
  210. oldUID := ctx.Session.Get(opt.oldSessionKey)
  211. if oldUID == nil || oldUID.(string) != x.ID {
  212. needsNew = true
  213. _ = ctx.Session.Set(opt.oldSessionKey, x.ID)
  214. } else {
  215. // If cookie present, map existing token, else generate a new one.
  216. if val := ctx.GetCookie(opt.Cookie); len(val) > 0 {
  217. // FIXME: test coverage.
  218. x.Token = val
  219. } else {
  220. needsNew = true
  221. }
  222. }
  223. if needsNew {
  224. // FIXME: actionId.
  225. x.Token = GenerateToken(x.Secret, x.ID, "POST")
  226. if opt.SetCookie {
  227. var expires interface{}
  228. if opt.CookieLifeTime == 0 {
  229. expires = time.Now().AddDate(0, 0, 1)
  230. }
  231. middleware.SetCookie(ctx.Resp, opt.Cookie, x.Token,
  232. opt.CookieLifeTime,
  233. opt.CookiePath,
  234. opt.CookieDomain,
  235. opt.Secure,
  236. opt.CookieHTTPOnly,
  237. expires,
  238. middleware.SameSite(opt.SameSite),
  239. )
  240. }
  241. }
  242. if opt.SetHeader {
  243. ctx.Resp.Header().Add(opt.Header, x.Token)
  244. }
  245. return x
  246. }
  247. // Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken"
  248. // HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated
  249. // using ValidToken. If this validation fails, custom Error is sent in the reply.
  250. // If neither a header or form value is found, http.StatusBadRequest is sent.
  251. func Validate(ctx *Context, x CSRF) {
  252. if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 {
  253. if !x.ValidToken(token) {
  254. // Delete the cookie
  255. middleware.SetCookie(ctx.Resp, x.GetCookieName(), "",
  256. -1,
  257. x.GetCookiePath(),
  258. x.GetCookieDomain()) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too?
  259. if middleware.IsAPIPath(ctx.Req) {
  260. x.Error(ctx.Resp)
  261. return
  262. }
  263. ctx.Flash.Error(ctx.Tr("error.invalid_csrf"))
  264. ctx.Redirect(setting.AppSubURL + "/")
  265. }
  266. return
  267. }
  268. if token := ctx.Req.FormValue(x.GetFormName()); len(token) > 0 {
  269. if !x.ValidToken(token) {
  270. // Delete the cookie
  271. middleware.SetCookie(ctx.Resp, x.GetCookieName(), "",
  272. -1,
  273. x.GetCookiePath(),
  274. x.GetCookieDomain()) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too?
  275. if middleware.IsAPIPath(ctx.Req) {
  276. x.Error(ctx.Resp)
  277. return
  278. }
  279. ctx.Flash.Error(ctx.Tr("error.invalid_csrf"))
  280. ctx.Redirect(setting.AppSubURL + "/")
  281. }
  282. return
  283. }
  284. if middleware.IsAPIPath(ctx.Req) {
  285. http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest)
  286. return
  287. }
  288. ctx.Flash.Error(ctx.Tr("error.missing_csrf"))
  289. ctx.Redirect(setting.AppSubURL + "/")
  290. }