diff options
Diffstat (limited to 'lib/api/api_csrf.go')
-rw-r--r-- | lib/api/api_csrf.go | 108 |
1 files changed, 12 insertions, 96 deletions
diff --git a/lib/api/api_csrf.go b/lib/api/api_csrf.go index 2a309a4e8..e8f03418d 100644 --- a/lib/api/api_csrf.go +++ b/lib/api/api_csrf.go @@ -7,33 +7,24 @@ package api import ( - "bufio" - "fmt" "net/http" - "os" "strings" + "time" - "github.com/syncthing/syncthing/lib/osutil" - "github.com/syncthing/syncthing/lib/rand" - "github.com/syncthing/syncthing/lib/sync" + "github.com/syncthing/syncthing/lib/db" ) -const maxCsrfTokens = 25 +const ( + maxCSRFTokenLifetime = time.Hour + maxActiveCSRFTokens = 25 +) type csrfManager struct { - // tokens is a list of valid tokens. It is sorted so that the most - // recently used token is first in the list. New tokens are added to the front - // of the list (as it is the most recently used at that time). The list is - // pruned to a maximum of maxCsrfTokens, throwing away the least recently used - // tokens. - tokens []string - tokensMut sync.Mutex - unique string prefix string apiKeyValidator apiKeyValidator next http.Handler - saveLocation string + tokens *tokenManager } type apiKeyValidator interface { @@ -43,17 +34,14 @@ type apiKeyValidator interface { // Check for CSRF token on /rest/ URLs. If a correct one is not given, reject // the request with 403. For / and /index.html, set a new CSRF cookie if none // is currently set. -func newCsrfManager(unique string, prefix string, apiKeyValidator apiKeyValidator, next http.Handler, saveLocation string) *csrfManager { +func newCsrfManager(unique string, prefix string, apiKeyValidator apiKeyValidator, next http.Handler, miscDB *db.NamespacedKV) *csrfManager { m := &csrfManager{ - tokensMut: sync.NewMutex(), - tokens: make([]string, 0, maxCsrfTokens), unique: unique, prefix: prefix, apiKeyValidator: apiKeyValidator, next: next, - saveLocation: saveLocation, + tokens: newTokenManager("csrfTokens", miscDB, maxCSRFTokenLifetime, maxActiveCSRFTokens), } - m.load() return m } @@ -78,11 +66,11 @@ func (m *csrfManager) ServeHTTP(w http.ResponseWriter, r *http.Request) { // and set a CSRF cookie if there isn't already a valid one. if !strings.HasPrefix(r.URL.Path, m.prefix) { cookie, err := r.Cookie("CSRF-Token-" + m.unique) - if err != nil || !m.validToken(cookie.Value) { + if err != nil || !m.tokens.Check(cookie.Value) { l.Debugln("new CSRF cookie in response to request for", r.URL) cookie = &http.Cookie{ Name: "CSRF-Token-" + m.unique, - Value: m.newToken(), + Value: m.tokens.New(), } http.SetCookie(w, cookie) } @@ -99,7 +87,7 @@ func (m *csrfManager) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Verify the CSRF token token := r.Header.Get("X-CSRF-Token-" + m.unique) - if !m.validToken(token) { + if !m.tokens.Check(token) { http.Error(w, "CSRF Error", http.StatusForbidden) return } @@ -107,78 +95,6 @@ func (m *csrfManager) ServeHTTP(w http.ResponseWriter, r *http.Request) { m.next.ServeHTTP(w, r) } -func (m *csrfManager) validToken(token string) bool { - m.tokensMut.Lock() - defer m.tokensMut.Unlock() - for i, t := range m.tokens { - if t == token { - if i > 0 { - // Move this token to the head of the list. Copy the tokens at - // the front one step to the right and then replace the token - // at the head. - copy(m.tokens[1:], m.tokens[:i]) - m.tokens[0] = token - } - return true - } - } - return false -} - -func (m *csrfManager) newToken() string { - token := rand.String(32) - - m.tokensMut.Lock() - defer m.tokensMut.Unlock() - - if len(m.tokens) < maxCsrfTokens { - m.tokens = append(m.tokens, "") - } - copy(m.tokens[1:], m.tokens) - m.tokens[0] = token - - m.save() - - return token -} - -func (m *csrfManager) save() { - // We're ignoring errors in here. It's not super critical and there's - // nothing relevant we can do about them anyway... - - if m.saveLocation == "" { - return - } - - f, err := osutil.CreateAtomic(m.saveLocation) - if err != nil { - return - } - - for _, t := range m.tokens { - fmt.Fprintln(f, t) - } - - f.Close() -} - -func (m *csrfManager) load() { - if m.saveLocation == "" { - return - } - - f, err := os.Open(m.saveLocation) - if err != nil { - return - } - defer f.Close() - - s := bufio.NewScanner(f) - for s.Scan() { - m.tokens = append(m.tokens, s.Text()) - } -} - func hasValidAPIKeyHeader(r *http.Request, validator apiKeyValidator) bool { if key := r.Header.Get("X-API-Key"); validator.IsValidAPIKey(key) { return true |