github.com/kayoticsully/syncthing@v0.8.9-0.20140724133906-c45a2fdc03f8/cmd/syncthing/gui_csrf.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"crypto/rand"
     6  	"encoding/base64"
     7  	"fmt"
     8  	"net/http"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/calmh/syncthing/osutil"
    16  )
    17  
    18  var csrfTokens []string
    19  var csrfMut sync.Mutex
    20  
    21  // Check for CSRF token on /rest/ URLs. If a correct one is not given, reject
    22  // the request with 403. For / and /index.html, set a new CSRF cookie if none
    23  // is currently set.
    24  func csrfMiddleware(prefix string, next http.Handler) http.Handler {
    25  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    26  		// Allow requests carrying a valid API key
    27  		if validAPIKey(r.Header.Get("X-API-Key")) {
    28  			next.ServeHTTP(w, r)
    29  			return
    30  		}
    31  
    32  		// Allow requests for the front page, and set a CSRF cookie if there isn't already a valid one.
    33  		if !strings.HasPrefix(r.URL.Path, prefix) {
    34  			cookie, err := r.Cookie("CSRF-Token")
    35  			if err != nil || !validCsrfToken(cookie.Value) {
    36  				cookie = &http.Cookie{
    37  					Name:  "CSRF-Token",
    38  					Value: newCsrfToken(),
    39  				}
    40  				http.SetCookie(w, cookie)
    41  			}
    42  			next.ServeHTTP(w, r)
    43  			return
    44  		}
    45  
    46  		// Verify the CSRF token
    47  		token := r.Header.Get("X-CSRF-Token")
    48  		if !validCsrfToken(token) {
    49  			http.Error(w, "CSRF Error", 403)
    50  			return
    51  		}
    52  
    53  		next.ServeHTTP(w, r)
    54  	})
    55  }
    56  
    57  func validCsrfToken(token string) bool {
    58  	csrfMut.Lock()
    59  	defer csrfMut.Unlock()
    60  	for _, t := range csrfTokens {
    61  		if t == token {
    62  			return true
    63  		}
    64  	}
    65  	return false
    66  }
    67  
    68  func newCsrfToken() string {
    69  	bs := make([]byte, 30)
    70  	_, err := rand.Reader.Read(bs)
    71  	if err != nil {
    72  		l.Fatalln(err)
    73  	}
    74  
    75  	token := base64.StdEncoding.EncodeToString(bs)
    76  
    77  	csrfMut.Lock()
    78  	csrfTokens = append(csrfTokens, token)
    79  	if len(csrfTokens) > 10 {
    80  		csrfTokens = csrfTokens[len(csrfTokens)-10:]
    81  	}
    82  	defer csrfMut.Unlock()
    83  
    84  	saveCsrfTokens()
    85  
    86  	return token
    87  }
    88  
    89  func saveCsrfTokens() {
    90  	name := filepath.Join(confDir, "csrftokens.txt")
    91  	tmp := fmt.Sprintf("%s.tmp.%d", name, time.Now().UnixNano())
    92  
    93  	f, err := os.OpenFile(tmp, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
    94  	if err != nil {
    95  		return
    96  	}
    97  	defer os.Remove(tmp)
    98  
    99  	for _, t := range csrfTokens {
   100  		_, err := fmt.Fprintln(f, t)
   101  		if err != nil {
   102  			return
   103  		}
   104  	}
   105  
   106  	err = f.Close()
   107  	if err != nil {
   108  		return
   109  	}
   110  
   111  	osutil.Rename(tmp, name)
   112  }
   113  
   114  func loadCsrfTokens() {
   115  	name := filepath.Join(confDir, "csrftokens.txt")
   116  	f, err := os.Open(name)
   117  	if err != nil {
   118  		return
   119  	}
   120  	defer f.Close()
   121  
   122  	s := bufio.NewScanner(f)
   123  	for s.Scan() {
   124  		csrfTokens = append(csrfTokens, s.Text())
   125  	}
   126  }