mirror of
https://github.com/octoleo/syncthing.git
synced 2025-01-24 07:28:27 +00:00
Merge commit 'a7a9d7d' into v0.12
* commit 'a7a9d7d': Return correct content type for /rest/events Rename RawAPIKey -> APIKey in GUIConfiguration Add -paths option to print config, key, database paths Clean up error handling a bit in protocol.readMessage Remove old reference to moved protocol Support multiple API keys (command-line and config) (fixes #2747)
This commit is contained in:
commit
194a8b0922
@ -238,7 +238,7 @@ func (s *apiService) Serve() {
|
|||||||
|
|
||||||
// Wrap everything in CSRF protection. The /rest prefix should be
|
// Wrap everything in CSRF protection. The /rest prefix should be
|
||||||
// protected, other requests will grant cookies.
|
// protected, other requests will grant cookies.
|
||||||
handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg.APIKey(), mux)
|
handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg, mux)
|
||||||
|
|
||||||
// Add the CORS handling
|
// Add the CORS handling
|
||||||
handler = corsMiddleware(handler)
|
handler = corsMiddleware(handler)
|
||||||
@ -893,8 +893,10 @@ func (s *apiService) getEvents(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
s.fss.gotEventRequest()
|
s.fss.gotEventRequest()
|
||||||
|
|
||||||
// Flush before blocking, to indicate that we've received the request
|
// Flush before blocking, to indicate that we've received the request and
|
||||||
// and that it should not be retried.
|
// that it should not be retried. Must set Content-Type header before
|
||||||
|
// flushing.
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
f := w.(http.Flusher)
|
f := w.(http.Flusher)
|
||||||
f.Flush()
|
f.Flush()
|
||||||
|
|
||||||
|
@ -33,9 +33,8 @@ func emitLoginAttempt(success bool, username string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
|
func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
|
||||||
apiKey := cfg.APIKey()
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if apiKey != "" && r.Header.Get("X-API-Key") == apiKey {
|
if cfg.IsValidAPIKey(r.Header.Get("X-API-Key")) {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
"github.com/syncthing/syncthing/lib/osutil"
|
"github.com/syncthing/syncthing/lib/osutil"
|
||||||
"github.com/syncthing/syncthing/lib/sync"
|
"github.com/syncthing/syncthing/lib/sync"
|
||||||
)
|
)
|
||||||
@ -30,11 +31,11 @@ const maxCsrfTokens = 25
|
|||||||
// Check for CSRF token on /rest/ URLs. If a correct one is not given, reject
|
// 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
|
// the request with 403. For / and /index.html, set a new CSRF cookie if none
|
||||||
// is currently set.
|
// is currently set.
|
||||||
func csrfMiddleware(unique, prefix, apiKey string, next http.Handler) http.Handler {
|
func csrfMiddleware(unique string, prefix string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
|
||||||
loadCsrfTokens()
|
loadCsrfTokens()
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Allow requests carrying a valid API key
|
// Allow requests carrying a valid API key
|
||||||
if apiKey != "" && r.Header.Get("X-API-Key") == apiKey {
|
if cfg.IsValidAPIKey(r.Header.Get("X-API-Key")) {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -122,11 +122,6 @@ var (
|
|||||||
const (
|
const (
|
||||||
usage = "syncthing [options]"
|
usage = "syncthing [options]"
|
||||||
extraUsage = `
|
extraUsage = `
|
||||||
The default configuration directory is:
|
|
||||||
|
|
||||||
%s
|
|
||||||
|
|
||||||
|
|
||||||
The -logflags value is a sum of the following:
|
The -logflags value is a sum of the following:
|
||||||
|
|
||||||
1 Date
|
1 Date
|
||||||
@ -199,6 +194,7 @@ type RuntimeOptions struct {
|
|||||||
confDir string
|
confDir string
|
||||||
reset bool
|
reset bool
|
||||||
showVersion bool
|
showVersion bool
|
||||||
|
showPaths bool
|
||||||
doUpgrade bool
|
doUpgrade bool
|
||||||
doUpgradeCheck bool
|
doUpgradeCheck bool
|
||||||
upgradeTo string
|
upgradeTo string
|
||||||
@ -260,6 +256,7 @@ func parseCommandLineOptions() RuntimeOptions {
|
|||||||
flag.BoolVar(&options.doUpgrade, "upgrade", false, "Perform upgrade")
|
flag.BoolVar(&options.doUpgrade, "upgrade", false, "Perform upgrade")
|
||||||
flag.BoolVar(&options.doUpgradeCheck, "upgrade-check", false, "Check for available upgrade")
|
flag.BoolVar(&options.doUpgradeCheck, "upgrade-check", false, "Check for available upgrade")
|
||||||
flag.BoolVar(&options.showVersion, "version", false, "Show version")
|
flag.BoolVar(&options.showVersion, "version", false, "Show version")
|
||||||
|
flag.BoolVar(&options.showPaths, "paths", false, "Show configuration paths")
|
||||||
flag.StringVar(&options.upgradeTo, "upgrade-to", options.upgradeTo, "Force upgrade directly from specified URL")
|
flag.StringVar(&options.upgradeTo, "upgrade-to", options.upgradeTo, "Force upgrade directly from specified URL")
|
||||||
flag.BoolVar(&options.auditEnabled, "audit", false, "Write events to audit file")
|
flag.BoolVar(&options.auditEnabled, "audit", false, "Write events to audit file")
|
||||||
flag.BoolVar(&options.verbose, "verbose", false, "Print verbose log output")
|
flag.BoolVar(&options.verbose, "verbose", false, "Print verbose log output")
|
||||||
@ -270,7 +267,7 @@ func parseCommandLineOptions() RuntimeOptions {
|
|||||||
flag.BoolVar(&options.hideConsole, "no-console", false, "Hide console window")
|
flag.BoolVar(&options.hideConsole, "no-console", false, "Hide console window")
|
||||||
}
|
}
|
||||||
|
|
||||||
longUsage := fmt.Sprintf(extraUsage, baseDirs["config"], debugFacilities())
|
longUsage := fmt.Sprintf(extraUsage, debugFacilities())
|
||||||
flag.Usage = usageFor(flag.CommandLine, usage, longUsage)
|
flag.Usage = usageFor(flag.CommandLine, usage, longUsage)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@ -320,6 +317,11 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.showPaths {
|
||||||
|
showPaths()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if options.browserOnly {
|
if options.browserOnly {
|
||||||
openGUI()
|
openGUI()
|
||||||
return
|
return
|
||||||
@ -478,7 +480,7 @@ func upgradeViaRest() error {
|
|||||||
cfg, _ := loadConfig()
|
cfg, _ := loadConfig()
|
||||||
target := cfg.GUI().URL()
|
target := cfg.GUI().URL()
|
||||||
r, _ := http.NewRequest("POST", target+"/rest/system/upgrade", nil)
|
r, _ := http.NewRequest("POST", target+"/rest/system/upgrade", nil)
|
||||||
r.Header.Set("X-API-Key", cfg.GUI().APIKey())
|
r.Header.Set("X-API-Key", cfg.GUI().APIKey)
|
||||||
|
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
Dial: dialer.Dial,
|
Dial: dialer.Dial,
|
||||||
@ -1216,3 +1218,13 @@ func checkShortIDs(cfg *config.Wrapper) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showPaths() {
|
||||||
|
fmt.Printf("Configuration file:\n\t%s\n\n", locations[locConfigFile])
|
||||||
|
fmt.Printf("Database directory:\n\t%s\n\n", locations[locDatabase])
|
||||||
|
fmt.Printf("Device private key & certificate files:\n\t%s\n\t%s\n\n", locations[locKeyFile], locations[locCertFile])
|
||||||
|
fmt.Printf("HTTPS private key & certificate files:\n\t%s\n\t%s\n\n", locations[locHTTPSKeyFile], locations[locHTTPSCertFile])
|
||||||
|
fmt.Printf("Log file:\n\t%s\n\n", locations[locLogFile])
|
||||||
|
fmt.Printf("GUI override directory:\n\t%s\n\n", locations[locGUIAssets])
|
||||||
|
fmt.Printf("Default sync folder directory:\n\t%s\n\n", locations[locDefFolder])
|
||||||
|
}
|
||||||
|
@ -229,8 +229,8 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
|
|||||||
cfg.Options.ReconnectIntervalS = 5
|
cfg.Options.ReconnectIntervalS = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.GUI.RawAPIKey == "" {
|
if cfg.GUI.APIKey == "" {
|
||||||
cfg.GUI.RawAPIKey = randomString(32)
|
cfg.GUI.APIKey = randomString(32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,7 +485,7 @@ func TestCopy(t *testing.T) {
|
|||||||
cfg.Devices[0].Addresses[0] = "wrong"
|
cfg.Devices[0].Addresses[0] = "wrong"
|
||||||
cfg.Folders[0].Devices[0].DeviceID = protocol.DeviceID{0, 1, 2, 3}
|
cfg.Folders[0].Devices[0].DeviceID = protocol.DeviceID{0, 1, 2, 3}
|
||||||
cfg.Options.ListenAddress[0] = "wrong"
|
cfg.Options.ListenAddress[0] = "wrong"
|
||||||
cfg.GUI.RawAPIKey = "wrong"
|
cfg.GUI.APIKey = "wrong"
|
||||||
|
|
||||||
bsChanged, err := json.MarshalIndent(cfg, "", " ")
|
bsChanged, err := json.MarshalIndent(cfg, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -18,7 +18,7 @@ type GUIConfiguration struct {
|
|||||||
User string `xml:"user,omitempty" json:"user"`
|
User string `xml:"user,omitempty" json:"user"`
|
||||||
Password string `xml:"password,omitempty" json:"password"`
|
Password string `xml:"password,omitempty" json:"password"`
|
||||||
RawUseTLS bool `xml:"tls,attr" json:"useTLS"`
|
RawUseTLS bool `xml:"tls,attr" json:"useTLS"`
|
||||||
RawAPIKey string `xml:"apikey,omitempty" json:"apiKey"`
|
APIKey string `xml:"apikey,omitempty" json:"apiKey"`
|
||||||
InsecureAdminAccess bool `xml:"insecureAdminAccess,omitempty" json:"insecureAdminAccess"`
|
InsecureAdminAccess bool `xml:"insecureAdminAccess,omitempty" json:"insecureAdminAccess"`
|
||||||
Theme string `xml:"theme" json:"theme" default:"default"`
|
Theme string `xml:"theme" json:"theme" default:"default"`
|
||||||
}
|
}
|
||||||
@ -76,9 +76,17 @@ func (c GUIConfiguration) URL() string {
|
|||||||
return u.String()
|
return u.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c GUIConfiguration) APIKey() string {
|
// IsValidAPIKey returns true when the given API key is valid, including both
|
||||||
if override := os.Getenv("STGUIAPIKEY"); override != "" {
|
// the value in config and any overrides
|
||||||
return override
|
func (c GUIConfiguration) IsValidAPIKey(apiKey string) bool {
|
||||||
|
switch apiKey {
|
||||||
|
case "":
|
||||||
|
return false
|
||||||
|
|
||||||
|
case c.APIKey, os.Getenv("STGUIAPIKEY"):
|
||||||
|
return true
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return c.RawAPIKey
|
|
||||||
}
|
}
|
||||||
|
@ -430,36 +430,20 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We check each returned error for the XDRError.IsEOF() method.
|
|
||||||
// IsEOF()==true here means that the message contained fewer fields than
|
|
||||||
// expected. It does not signify an EOF on the socket, because we've
|
|
||||||
// successfully read a size value and that many bytes already. New fields
|
|
||||||
// we expected but the other peer didn't send should be interpreted as
|
|
||||||
// zero/nil, and if that's not valid we'll verify it somewhere else.
|
|
||||||
|
|
||||||
switch hdr.msgType {
|
switch hdr.msgType {
|
||||||
case messageTypeIndex, messageTypeIndexUpdate:
|
case messageTypeIndex, messageTypeIndexUpdate:
|
||||||
var idx IndexMessage
|
var idx IndexMessage
|
||||||
err = idx.UnmarshalXDR(msgBuf)
|
err = idx.UnmarshalXDR(msgBuf)
|
||||||
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
msg = idx
|
msg = idx
|
||||||
|
|
||||||
case messageTypeRequest:
|
case messageTypeRequest:
|
||||||
var req RequestMessage
|
var req RequestMessage
|
||||||
err = req.UnmarshalXDR(msgBuf)
|
err = req.UnmarshalXDR(msgBuf)
|
||||||
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
msg = req
|
msg = req
|
||||||
|
|
||||||
case messageTypeResponse:
|
case messageTypeResponse:
|
||||||
var resp ResponseMessage
|
var resp ResponseMessage
|
||||||
err = resp.UnmarshalXDR(msgBuf)
|
err = resp.UnmarshalXDR(msgBuf)
|
||||||
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
msg = resp
|
msg = resp
|
||||||
|
|
||||||
case messageTypePing:
|
case messageTypePing:
|
||||||
@ -468,23 +452,28 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
|
|||||||
case messageTypeClusterConfig:
|
case messageTypeClusterConfig:
|
||||||
var cc ClusterConfigMessage
|
var cc ClusterConfigMessage
|
||||||
err = cc.UnmarshalXDR(msgBuf)
|
err = cc.UnmarshalXDR(msgBuf)
|
||||||
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
msg = cc
|
msg = cc
|
||||||
|
|
||||||
case messageTypeClose:
|
case messageTypeClose:
|
||||||
var cm CloseMessage
|
var cm CloseMessage
|
||||||
err = cm.UnmarshalXDR(msgBuf)
|
err = cm.UnmarshalXDR(msgBuf)
|
||||||
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
msg = cm
|
msg = cm
|
||||||
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("protocol error: %s: unknown message type %#x", c.id, hdr.msgType)
|
err = fmt.Errorf("protocol error: %s: unknown message type %#x", c.id, hdr.msgType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We check the returned error for the XDRError.IsEOF() method.
|
||||||
|
// IsEOF()==true here means that the message contained fewer fields than
|
||||||
|
// expected. It does not signify an EOF on the socket, because we've
|
||||||
|
// successfully read a size value and then that many bytes from the wire.
|
||||||
|
// New fields we expected but the other peer didn't send should be
|
||||||
|
// interpreted as zero/nil, and if that's not valid we'll verify it
|
||||||
|
// somewhere else.
|
||||||
|
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
Syncthing uses the protocols defined in
|
|
||||||
https://github.com/syncthing/specs/.
|
|
Loading…
x
Reference in New Issue
Block a user