本站源代码
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.

268 lines
7.5KB

  1. // Copyright 2013 Martini Authors
  2. // Copyright 2014 The Macaron Authors
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  5. // not use this file except in compliance with the License. You may obtain
  6. // a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. // License for the specific language governing permissions and limitations
  14. // under the License.
  15. // Package csrf is a middleware that generates and validates CSRF tokens for Macaron.
  16. package csrf
  17. import (
  18. "net/http"
  19. "time"
  20. "gitea.com/macaron/macaron"
  21. "gitea.com/macaron/session"
  22. "github.com/unknwon/com"
  23. )
  24. const _VERSION = "0.1.1"
  25. func Version() string {
  26. return _VERSION
  27. }
  28. // CSRF represents a CSRF service and is used to get the current token and validate a suspect token.
  29. type CSRF interface {
  30. // Return HTTP header to search for token.
  31. GetHeaderName() string
  32. // Return form value to search for token.
  33. GetFormName() string
  34. // Return cookie name to search for token.
  35. GetCookieName() string
  36. // Return cookie path
  37. GetCookiePath() string
  38. // Return the flag value used for the csrf token.
  39. GetCookieHttpOnly() bool
  40. // Return the token.
  41. GetToken() string
  42. // Validate by token.
  43. ValidToken(t string) bool
  44. // Error replies to the request with a custom function when ValidToken fails.
  45. Error(w http.ResponseWriter)
  46. }
  47. type csrf struct {
  48. // Header name value for setting and getting csrf token.
  49. Header string
  50. // Form name value for setting and getting csrf token.
  51. Form string
  52. // Cookie name value for setting and getting csrf token.
  53. Cookie string
  54. //Cookie domain
  55. CookieDomain string
  56. //Cookie path
  57. CookiePath string
  58. // Cookie HttpOnly flag value used for the csrf token.
  59. CookieHttpOnly bool
  60. // Token generated to pass via header, cookie, or hidden form value.
  61. Token string
  62. // This value must be unique per user.
  63. ID string
  64. // Secret used along with the unique id above to generate the Token.
  65. Secret string
  66. // ErrorFunc is the custom function that replies to the request when ValidToken fails.
  67. ErrorFunc func(w http.ResponseWriter)
  68. }
  69. // GetHeaderName returns the name of the HTTP header for csrf token.
  70. func (c *csrf) GetHeaderName() string {
  71. return c.Header
  72. }
  73. // GetFormName returns the name of the form value for csrf token.
  74. func (c *csrf) GetFormName() string {
  75. return c.Form
  76. }
  77. // GetCookieName returns the name of the cookie for csrf token.
  78. func (c *csrf) GetCookieName() string {
  79. return c.Cookie
  80. }
  81. // GetCookiePath returns the path of the cookie for csrf token.
  82. func (c *csrf) GetCookiePath() string {
  83. return c.CookiePath
  84. }
  85. // GetCookieHttpOnly returns the flag value used for the csrf token.
  86. func (c *csrf) GetCookieHttpOnly() bool {
  87. return c.CookieHttpOnly
  88. }
  89. // GetToken returns the current token. This is typically used
  90. // to populate a hidden form in an HTML template.
  91. func (c *csrf) GetToken() string {
  92. return c.Token
  93. }
  94. // ValidToken validates the passed token against the existing Secret and ID.
  95. func (c *csrf) ValidToken(t string) bool {
  96. return ValidToken(t, c.Secret, c.ID, "POST")
  97. }
  98. // Error replies to the request when ValidToken fails.
  99. func (c *csrf) Error(w http.ResponseWriter) {
  100. c.ErrorFunc(w)
  101. }
  102. // Options maintains options to manage behavior of Generate.
  103. type Options struct {
  104. // The global secret value used to generate Tokens.
  105. Secret string
  106. // HTTP header used to set and get token.
  107. Header string
  108. // Form value used to set and get token.
  109. Form string
  110. // Cookie value used to set and get token.
  111. Cookie string
  112. // Cookie domain.
  113. CookieDomain string
  114. // Cookie path.
  115. CookiePath string
  116. CookieHttpOnly bool
  117. // Key used for getting the unique ID per user.
  118. SessionKey string
  119. // oldSeesionKey saves old value corresponding to SessionKey.
  120. oldSeesionKey string
  121. // If true, send token via X-CSRFToken header.
  122. SetHeader bool
  123. // If true, send token via _csrf cookie.
  124. SetCookie bool
  125. // Set the Secure flag to true on the cookie.
  126. Secure bool
  127. // Disallow Origin appear in request header.
  128. Origin bool
  129. // The function called when Validate fails.
  130. ErrorFunc func(w http.ResponseWriter)
  131. }
  132. func prepareOptions(options []Options) Options {
  133. var opt Options
  134. if len(options) > 0 {
  135. opt = options[0]
  136. }
  137. // Defaults.
  138. if len(opt.Secret) == 0 {
  139. opt.Secret = string(com.RandomCreateBytes(10))
  140. }
  141. if len(opt.Header) == 0 {
  142. opt.Header = "X-CSRFToken"
  143. }
  144. if len(opt.Form) == 0 {
  145. opt.Form = "_csrf"
  146. }
  147. if len(opt.Cookie) == 0 {
  148. opt.Cookie = "_csrf"
  149. }
  150. if len(opt.CookiePath) == 0 {
  151. opt.CookiePath = "/"
  152. }
  153. if len(opt.SessionKey) == 0 {
  154. opt.SessionKey = "uid"
  155. }
  156. opt.oldSeesionKey = "_old_" + opt.SessionKey
  157. if opt.ErrorFunc == nil {
  158. opt.ErrorFunc = func(w http.ResponseWriter) {
  159. http.Error(w, "Invalid csrf token.", http.StatusBadRequest)
  160. }
  161. }
  162. return opt
  163. }
  164. // Generate maps CSRF to each request. If this request is a Get request, it will generate a new token.
  165. // Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
  166. func Generate(options ...Options) macaron.Handler {
  167. opt := prepareOptions(options)
  168. return func(ctx *macaron.Context, sess session.Store) {
  169. x := &csrf{
  170. Secret: opt.Secret,
  171. Header: opt.Header,
  172. Form: opt.Form,
  173. Cookie: opt.Cookie,
  174. CookieDomain: opt.CookieDomain,
  175. CookiePath: opt.CookiePath,
  176. CookieHttpOnly: opt.CookieHttpOnly,
  177. ErrorFunc: opt.ErrorFunc,
  178. }
  179. ctx.MapTo(x, (*CSRF)(nil))
  180. if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
  181. return
  182. }
  183. x.ID = "0"
  184. uid := sess.Get(opt.SessionKey)
  185. if uid != nil {
  186. x.ID = com.ToStr(uid)
  187. }
  188. needsNew := false
  189. oldUid := sess.Get(opt.oldSeesionKey)
  190. if oldUid == nil || oldUid.(string) != x.ID {
  191. needsNew = true
  192. sess.Set(opt.oldSeesionKey, x.ID)
  193. } else {
  194. // If cookie present, map existing token, else generate a new one.
  195. if val := ctx.GetCookie(opt.Cookie); len(val) > 0 {
  196. // FIXME: test coverage.
  197. x.Token = val
  198. } else {
  199. needsNew = true
  200. }
  201. }
  202. if needsNew {
  203. // FIXME: actionId.
  204. x.Token = GenerateToken(x.Secret, x.ID, "POST")
  205. if opt.SetCookie {
  206. ctx.SetCookie(opt.Cookie, x.Token, 0, opt.CookiePath, opt.CookieDomain, opt.Secure, opt.CookieHttpOnly, time.Now().AddDate(0, 0, 1))
  207. }
  208. }
  209. if opt.SetHeader {
  210. ctx.Resp.Header().Add(opt.Header, x.Token)
  211. }
  212. }
  213. }
  214. // Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token.
  215. // Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
  216. func Csrfer(options ...Options) macaron.Handler {
  217. return Generate(options...)
  218. }
  219. // Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken"
  220. // HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated
  221. // using ValidToken. If this validation fails, custom Error is sent in the reply.
  222. // If neither a header or form value is found, http.StatusBadRequest is sent.
  223. func Validate(ctx *macaron.Context, x CSRF) {
  224. if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 {
  225. if !x.ValidToken(token) {
  226. ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())
  227. x.Error(ctx.Resp)
  228. }
  229. return
  230. }
  231. if token := ctx.Req.FormValue(x.GetFormName()); len(token) > 0 {
  232. if !x.ValidToken(token) {
  233. ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())
  234. x.Error(ctx.Resp)
  235. }
  236. return
  237. }
  238. http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest)
  239. }
上海开阖软件有限公司 沪ICP备12045867号-1