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.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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. "net/http"
  20. "time"
  21. "code.gitea.io/gitea/modules/setting"
  22. "code.gitea.io/gitea/modules/web/middleware"
  23. "github.com/unknwon/com"
  24. )
  25. // CSRF represents a CSRF service and is used to get the current token and validate a suspect token.
  26. type CSRF interface {
  27. // Return HTTP header to search for token.
  28. GetHeaderName() string
  29. // Return form value to search for token.
  30. GetFormName() string
  31. // Return cookie name to search for token.
  32. GetCookieName() string
  33. // Return cookie path
  34. GetCookiePath() string
  35. // Return the flag value used for the csrf token.
  36. GetCookieHTTPOnly() bool
  37. // Return cookie domain
  38. GetCookieDomain() string
  39. // Return the token.
  40. GetToken() string
  41. // Validate by token.
  42. ValidToken(t string) bool
  43. // Error replies to the request with a custom function when ValidToken fails.
  44. Error(w http.ResponseWriter)
  45. }
  46. type csrf struct {
  47. // Header name value for setting and getting csrf token.
  48. Header string
  49. // Form name value for setting and getting csrf token.
  50. Form string
  51. // Cookie name value for setting and getting csrf token.
  52. Cookie string
  53. //Cookie domain
  54. CookieDomain string
  55. //Cookie path
  56. CookiePath string
  57. // Cookie HttpOnly flag value used for the csrf token.
  58. CookieHTTPOnly bool
  59. // Token generated to pass via header, cookie, or hidden form value.
  60. Token string
  61. // This value must be unique per user.
  62. ID string
  63. // Secret used along with the unique id above to generate the Token.
  64. Secret string
  65. // ErrorFunc is the custom function that replies to the request when ValidToken fails.
  66. ErrorFunc func(w http.ResponseWriter)
  67. }
  68. // GetHeaderName returns the name of the HTTP header for csrf token.
  69. func (c *csrf) GetHeaderName() string {
  70. return c.Header
  71. }
  72. // GetFormName returns the name of the form value for csrf token.
  73. func (c *csrf) GetFormName() string {
  74. return c.Form
  75. }
  76. // GetCookieName returns the name of the cookie for csrf token.
  77. func (c *csrf) GetCookieName() string {
  78. return c.Cookie
  79. }
  80. // GetCookiePath returns the path of the cookie for csrf token.
  81. func (c *csrf) GetCookiePath() string {
  82. return c.CookiePath
  83. }
  84. // GetCookieHTTPOnly returns the flag value used for the csrf token.
  85. func (c *csrf) GetCookieHTTPOnly() bool {
  86. return c.CookieHTTPOnly
  87. }
  88. // GetCookieDomain returns the flag value used for the csrf token.
  89. func (c *csrf) GetCookieDomain() string {
  90. return c.CookieDomain
  91. }
  92. // GetToken returns the current token. This is typically used
  93. // to populate a hidden form in an HTML template.
  94. func (c *csrf) GetToken() string {
  95. return c.Token
  96. }
  97. // ValidToken validates the passed token against the existing Secret and ID.
  98. func (c *csrf) ValidToken(t string) bool {
  99. return ValidToken(t, c.Secret, c.ID, "POST")
  100. }
  101. // Error replies to the request when ValidToken fails.
  102. func (c *csrf) Error(w http.ResponseWriter) {
  103. c.ErrorFunc(w)
  104. }
  105. // CsrfOptions maintains options to manage behavior of Generate.
  106. type CsrfOptions struct {
  107. // The global secret value used to generate Tokens.
  108. Secret string
  109. // HTTP header used to set and get token.
  110. Header string
  111. // Form value used to set and get token.
  112. Form string
  113. // Cookie value used to set and get token.
  114. Cookie string
  115. // Cookie domain.
  116. CookieDomain string
  117. // Cookie path.
  118. CookiePath string
  119. CookieHTTPOnly bool
  120. // SameSite set the cookie SameSite type
  121. SameSite http.SameSite
  122. // Key used for getting the unique ID per user.
  123. SessionKey string
  124. // oldSessionKey saves old value corresponding to SessionKey.
  125. oldSessionKey string
  126. // If true, send token via X-CSRFToken header.
  127. SetHeader bool
  128. // If true, send token via _csrf cookie.
  129. SetCookie bool
  130. // Set the Secure flag to true on the cookie.
  131. Secure bool
  132. // Disallow Origin appear in request header.
  133. Origin bool
  134. // The function called when Validate fails.
  135. ErrorFunc func(w http.ResponseWriter)
  136. // Cookie life time. Default is 0
  137. CookieLifeTime int
  138. }
  139. func prepareOptions(options []CsrfOptions) CsrfOptions {
  140. var opt CsrfOptions
  141. if len(options) > 0 {
  142. opt = options[0]
  143. }
  144. // Defaults.
  145. if len(opt.Secret) == 0 {
  146. opt.Secret = string(com.RandomCreateBytes(10))
  147. }
  148. if len(opt.Header) == 0 {
  149. opt.Header = "X-CSRFToken"
  150. }
  151. if len(opt.Form) == 0 {
  152. opt.Form = "_csrf"
  153. }
  154. if len(opt.Cookie) == 0 {
  155. opt.Cookie = "_csrf"
  156. }
  157. if len(opt.CookiePath) == 0 {
  158. opt.CookiePath = "/"
  159. }
  160. if len(opt.SessionKey) == 0 {
  161. opt.SessionKey = "uid"
  162. }
  163. opt.oldSessionKey = "_old_" + opt.SessionKey
  164. if opt.ErrorFunc == nil {
  165. opt.ErrorFunc = func(w http.ResponseWriter) {
  166. http.Error(w, "Invalid csrf token.", http.StatusBadRequest)
  167. }
  168. }
  169. return opt
  170. }
  171. // Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token.
  172. // Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
  173. func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
  174. opt = prepareOptions([]CsrfOptions{opt})
  175. x := &csrf{
  176. Secret: opt.Secret,
  177. Header: opt.Header,
  178. Form: opt.Form,
  179. Cookie: opt.Cookie,
  180. CookieDomain: opt.CookieDomain,
  181. CookiePath: opt.CookiePath,
  182. CookieHTTPOnly: opt.CookieHTTPOnly,
  183. ErrorFunc: opt.ErrorFunc,
  184. }
  185. if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
  186. return x
  187. }
  188. x.ID = "0"
  189. uid := ctx.Session.Get(opt.SessionKey)
  190. if uid != nil {
  191. x.ID = com.ToStr(uid)
  192. }
  193. needsNew := false
  194. oldUID := ctx.Session.Get(opt.oldSessionKey)
  195. if oldUID == nil || oldUID.(string) != x.ID {
  196. needsNew = true
  197. _ = ctx.Session.Set(opt.oldSessionKey, x.ID)
  198. } else {
  199. // If cookie present, map existing token, else generate a new one.
  200. if val := ctx.GetCookie(opt.Cookie); len(val) > 0 {
  201. // FIXME: test coverage.
  202. x.Token = val
  203. } else {
  204. needsNew = true
  205. }
  206. }
  207. if needsNew {
  208. // FIXME: actionId.
  209. x.Token = GenerateToken(x.Secret, x.ID, "POST")
  210. if opt.SetCookie {
  211. var expires interface{}
  212. if opt.CookieLifeTime == 0 {
  213. expires = time.Now().AddDate(0, 0, 1)
  214. }
  215. middleware.SetCookie(ctx.Resp, opt.Cookie, x.Token,
  216. opt.CookieLifeTime,
  217. opt.CookiePath,
  218. opt.CookieDomain,
  219. opt.Secure,
  220. opt.CookieHTTPOnly,
  221. expires,
  222. middleware.SameSite(opt.SameSite),
  223. )
  224. }
  225. }
  226. if opt.SetHeader {
  227. ctx.Resp.Header().Add(opt.Header, x.Token)
  228. }
  229. return x
  230. }
  231. // Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken"
  232. // HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated
  233. // using ValidToken. If this validation fails, custom Error is sent in the reply.
  234. // If neither a header or form value is found, http.StatusBadRequest is sent.
  235. func Validate(ctx *Context, x CSRF) {
  236. if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 {
  237. if !x.ValidToken(token) {
  238. // Delete the cookie
  239. middleware.SetCookie(ctx.Resp, x.GetCookieName(), "",
  240. -1,
  241. x.GetCookiePath(),
  242. x.GetCookieDomain()) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too?
  243. if middleware.IsAPIPath(ctx.Req) {
  244. x.Error(ctx.Resp)
  245. return
  246. }
  247. ctx.Flash.Error(ctx.Tr("error.invalid_csrf"))
  248. ctx.Redirect(setting.AppSubURL + "/")
  249. }
  250. return
  251. }
  252. if token := ctx.Req.FormValue(x.GetFormName()); 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 middleware.IsAPIPath(ctx.Req) {
  269. http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest)
  270. return
  271. }
  272. ctx.Flash.Error(ctx.Tr("error.missing_csrf"))
  273. ctx.Redirect(setting.AppSubURL + "/")
  274. }