csrf.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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. "github.com/Unknwon/com"
  21. "github.com/go-macaron/session"
  22. "gopkg.in/macaron.v1"
  23. )
  24. const _VERSION = "0.1.0"
  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 token.
  39. GetToken() string
  40. // Validate by token.
  41. ValidToken(t string) bool
  42. // Error replies to the request with a custom function when ValidToken fails.
  43. Error(w http.ResponseWriter)
  44. }
  45. type csrf struct {
  46. // Header name value for setting and getting csrf token.
  47. Header string
  48. // Form name value for setting and getting csrf token.
  49. Form string
  50. // Cookie name value for setting and getting csrf token.
  51. Cookie string
  52. //Cookie path
  53. CookiePath string
  54. // Token generated to pass via header, cookie, or hidden form value.
  55. Token string
  56. // This value must be unique per user.
  57. ID string
  58. // Secret used along with the unique id above to generate the Token.
  59. Secret string
  60. // ErrorFunc is the custom function that replies to the request when ValidToken fails.
  61. ErrorFunc func(w http.ResponseWriter)
  62. }
  63. // GetHeaderName returns the name of the HTTP header for csrf token.
  64. func (c *csrf) GetHeaderName() string {
  65. return c.Header
  66. }
  67. // GetFormName returns the name of the form value for csrf token.
  68. func (c *csrf) GetFormName() string {
  69. return c.Form
  70. }
  71. // GetCookieName returns the name of the cookie for csrf token.
  72. func (c *csrf) GetCookieName() string {
  73. return c.Cookie
  74. }
  75. // GetCookiePath returns the path of the cookie for csrf token.
  76. func (c *csrf) GetCookiePath() string {
  77. return c.CookiePath
  78. }
  79. // GetToken returns the current token. This is typically used
  80. // to populate a hidden form in an HTML template.
  81. func (c *csrf) GetToken() string {
  82. return c.Token
  83. }
  84. // ValidToken validates the passed token against the existing Secret and ID.
  85. func (c *csrf) ValidToken(t string) bool {
  86. return ValidToken(t, c.Secret, c.ID, "POST")
  87. }
  88. // Error replies to the request when ValidToken fails.
  89. func (c *csrf) Error(w http.ResponseWriter) {
  90. c.ErrorFunc(w)
  91. }
  92. // Options maintains options to manage behavior of Generate.
  93. type Options struct {
  94. // The global secret value used to generate Tokens.
  95. Secret string
  96. // HTTP header used to set and get token.
  97. Header string
  98. // Form value used to set and get token.
  99. Form string
  100. // Cookie value used to set and get token.
  101. Cookie string
  102. // Cookie path.
  103. CookiePath string
  104. // Key used for getting the unique ID per user.
  105. SessionKey string
  106. // oldSeesionKey saves old value corresponding to SessionKey.
  107. oldSeesionKey string
  108. // If true, send token via X-CSRFToken header.
  109. SetHeader bool
  110. // If true, send token via _csrf cookie.
  111. SetCookie bool
  112. // Set the Secure flag to true on the cookie.
  113. Secure bool
  114. // Disallow Origin appear in request header.
  115. Origin bool
  116. // The function called when Validate fails.
  117. ErrorFunc func(w http.ResponseWriter)
  118. }
  119. func prepareOptions(options []Options) Options {
  120. var opt Options
  121. if len(options) > 0 {
  122. opt = options[0]
  123. }
  124. // Defaults.
  125. if len(opt.Secret) == 0 {
  126. opt.Secret = string(com.RandomCreateBytes(10))
  127. }
  128. if len(opt.Header) == 0 {
  129. opt.Header = "X-CSRFToken"
  130. }
  131. if len(opt.Form) == 0 {
  132. opt.Form = "_csrf"
  133. }
  134. if len(opt.Cookie) == 0 {
  135. opt.Cookie = "_csrf"
  136. }
  137. if len(opt.CookiePath) == 0 {
  138. opt.CookiePath = "/"
  139. }
  140. if len(opt.SessionKey) == 0 {
  141. opt.SessionKey = "uid"
  142. }
  143. opt.oldSeesionKey = "_old_" + opt.SessionKey
  144. if opt.ErrorFunc == nil {
  145. opt.ErrorFunc = func(w http.ResponseWriter) {
  146. http.Error(w, "Invalid csrf token.", http.StatusBadRequest)
  147. }
  148. }
  149. return opt
  150. }
  151. // Generate maps CSRF to each request. If this request is a Get request, it will generate a new token.
  152. // Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
  153. func Generate(options ...Options) macaron.Handler {
  154. opt := prepareOptions(options)
  155. return func(ctx *macaron.Context, sess session.Store) {
  156. x := &csrf{
  157. Secret: opt.Secret,
  158. Header: opt.Header,
  159. Form: opt.Form,
  160. Cookie: opt.Cookie,
  161. CookiePath: opt.CookiePath,
  162. ErrorFunc: opt.ErrorFunc,
  163. }
  164. ctx.MapTo(x, (*CSRF)(nil))
  165. if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
  166. return
  167. }
  168. x.ID = "0"
  169. uid := sess.Get(opt.SessionKey)
  170. if uid != nil {
  171. x.ID = com.ToStr(uid)
  172. }
  173. needsNew := false
  174. oldUid := sess.Get(opt.oldSeesionKey)
  175. if oldUid == nil || oldUid.(string) != x.ID {
  176. needsNew = true
  177. sess.Set(opt.oldSeesionKey, x.ID)
  178. } else {
  179. // If cookie present, map existing token, else generate a new one.
  180. if val := ctx.GetCookie(opt.Cookie); len(val) > 0 {
  181. // FIXME: test coverage.
  182. x.Token = val
  183. } else {
  184. needsNew = true
  185. }
  186. }
  187. if needsNew {
  188. // FIXME: actionId.
  189. x.Token = GenerateToken(x.Secret, x.ID, "POST")
  190. if opt.SetCookie {
  191. ctx.SetCookie(opt.Cookie, x.Token, 0, opt.CookiePath, "", false, true, time.Now().AddDate(0, 0, 1))
  192. }
  193. }
  194. if opt.SetHeader {
  195. ctx.Resp.Header().Add(opt.Header, x.Token)
  196. }
  197. }
  198. }
  199. // Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token.
  200. // Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
  201. func Csrfer(options ...Options) macaron.Handler {
  202. return Generate(options...)
  203. }
  204. // Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken"
  205. // HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated
  206. // using ValidToken. If this validation fails, custom Error is sent in the reply.
  207. // If neither a header or form value is found, http.StatusBadRequest is sent.
  208. func Validate(ctx *macaron.Context, x CSRF) {
  209. if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 {
  210. if !x.ValidToken(token) {
  211. ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())
  212. x.Error(ctx.Resp)
  213. }
  214. return
  215. }
  216. if token := ctx.Req.FormValue(x.GetFormName()); len(token) > 0 {
  217. if !x.ValidToken(token) {
  218. ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())
  219. x.Error(ctx.Resp)
  220. }
  221. return
  222. }
  223. http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest)
  224. }