api_csrf.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package api
  7. import (
  8. "net/http"
  9. "strings"
  10. "time"
  11. "github.com/syncthing/syncthing/lib/db"
  12. )
  13. const (
  14. maxCSRFTokenLifetime = time.Hour
  15. maxActiveCSRFTokens = 25
  16. )
  17. type csrfManager struct {
  18. unique string
  19. prefix string
  20. apiKeyValidator apiKeyValidator
  21. next http.Handler
  22. tokens *tokenManager
  23. }
  24. type apiKeyValidator interface {
  25. IsValidAPIKey(key string) bool
  26. }
  27. // Check for CSRF token on /rest/ URLs. If a correct one is not given, reject
  28. // the request with 403. For / and /index.html, set a new CSRF cookie if none
  29. // is currently set.
  30. func newCsrfManager(unique string, prefix string, apiKeyValidator apiKeyValidator, next http.Handler, miscDB *db.NamespacedKV) *csrfManager {
  31. m := &csrfManager{
  32. unique: unique,
  33. prefix: prefix,
  34. apiKeyValidator: apiKeyValidator,
  35. next: next,
  36. tokens: newTokenManager("csrfTokens", miscDB, maxCSRFTokenLifetime, maxActiveCSRFTokens),
  37. }
  38. return m
  39. }
  40. func (m *csrfManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  41. // Allow requests carrying a valid API key
  42. if hasValidAPIKeyHeader(r, m.apiKeyValidator) {
  43. // Set the access-control-allow-origin header for CORS requests
  44. // since a valid API key has been provided
  45. w.Header().Add("Access-Control-Allow-Origin", "*")
  46. m.next.ServeHTTP(w, r)
  47. return
  48. }
  49. if strings.HasPrefix(r.URL.Path, "/rest/debug") {
  50. // Debugging functions are only available when explicitly
  51. // enabled, and can be accessed without a CSRF token
  52. m.next.ServeHTTP(w, r)
  53. return
  54. }
  55. // Allow requests for anything not under the protected path prefix,
  56. // and set a CSRF cookie if there isn't already a valid one.
  57. if !strings.HasPrefix(r.URL.Path, m.prefix) {
  58. cookie, err := r.Cookie("CSRF-Token-" + m.unique)
  59. if err != nil || !m.tokens.Check(cookie.Value) {
  60. l.Debugln("new CSRF cookie in response to request for", r.URL)
  61. cookie = &http.Cookie{
  62. Name: "CSRF-Token-" + m.unique,
  63. Value: m.tokens.New(),
  64. }
  65. http.SetCookie(w, cookie)
  66. }
  67. m.next.ServeHTTP(w, r)
  68. return
  69. }
  70. if isNoAuthPath(r.URL.Path) {
  71. // REST calls that don't require authentication also do not
  72. // need a CSRF token.
  73. m.next.ServeHTTP(w, r)
  74. return
  75. }
  76. // Verify the CSRF token
  77. token := r.Header.Get("X-CSRF-Token-" + m.unique)
  78. if !m.tokens.Check(token) {
  79. http.Error(w, "CSRF Error", http.StatusForbidden)
  80. return
  81. }
  82. m.next.ServeHTTP(w, r)
  83. }
  84. func hasValidAPIKeyHeader(r *http.Request, validator apiKeyValidator) bool {
  85. if key := r.Header.Get("X-API-Key"); validator.IsValidAPIKey(key) {
  86. return true
  87. }
  88. if auth := r.Header.Get("Authorization"); strings.HasPrefix(strings.ToLower(auth), "bearer ") {
  89. bearerToken := auth[len("bearer "):]
  90. return validator.IsValidAPIKey(bearerToken)
  91. }
  92. return false
  93. }