guiconfiguration.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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 config
  7. import (
  8. "net/url"
  9. "os"
  10. "regexp"
  11. "strconv"
  12. "strings"
  13. "golang.org/x/crypto/bcrypt"
  14. "github.com/syncthing/syncthing/lib/rand"
  15. )
  16. type GUIConfiguration struct {
  17. Enabled bool `json:"enabled" xml:"enabled,attr" default:"true"`
  18. RawAddress string `json:"address" xml:"address" default:"127.0.0.1:8384"`
  19. RawUnixSocketPermissions string `json:"unixSocketPermissions" xml:"unixSocketPermissions,omitempty"`
  20. User string `json:"user" xml:"user,omitempty"`
  21. Password string `json:"password" xml:"password,omitempty"`
  22. AuthMode AuthMode `json:"authMode" xml:"authMode,omitempty"`
  23. MetricsWithoutAuth bool `json:"metricsWithoutAuth" xml:"metricsWithoutAuth" default:"false"`
  24. RawUseTLS bool `json:"useTLS" xml:"tls,attr"`
  25. APIKey string `json:"apiKey" xml:"apikey,omitempty"`
  26. InsecureAdminAccess bool `json:"insecureAdminAccess" xml:"insecureAdminAccess,omitempty"`
  27. Theme string `json:"theme" xml:"theme" default:"default"`
  28. Debugging bool `json:"debugging" xml:"debugging,attr"`
  29. InsecureSkipHostCheck bool `json:"insecureSkipHostcheck" xml:"insecureSkipHostcheck,omitempty"`
  30. InsecureAllowFrameLoading bool `json:"insecureAllowFrameLoading" xml:"insecureAllowFrameLoading,omitempty"`
  31. SendBasicAuthPrompt bool `json:"sendBasicAuthPrompt" xml:"sendBasicAuthPrompt,attr"`
  32. }
  33. func (c GUIConfiguration) IsAuthEnabled() bool {
  34. // This function should match isAuthEnabled() in syncthingController.js
  35. return c.AuthMode == AuthModeLDAP || (len(c.User) > 0 && len(c.Password) > 0)
  36. }
  37. func (GUIConfiguration) IsOverridden() bool {
  38. return os.Getenv("STGUIADDRESS") != ""
  39. }
  40. func (c GUIConfiguration) Address() string {
  41. if override := os.Getenv("STGUIADDRESS"); override != "" {
  42. // This value may be of the form "scheme://address:port" or just
  43. // "address:port". We need to chop off the scheme. We try to parse it as
  44. // an URL if it contains a slash. If that fails, return it as is and let
  45. // some other error handling handle it.
  46. if strings.Contains(override, "/") {
  47. url, err := url.Parse(override)
  48. if err != nil {
  49. return override
  50. }
  51. if strings.HasPrefix(url.Scheme, "unix") {
  52. return url.Path
  53. }
  54. return url.Host
  55. }
  56. return override
  57. }
  58. return c.RawAddress
  59. }
  60. func (c GUIConfiguration) UnixSocketPermissions() os.FileMode {
  61. perm, err := strconv.ParseUint(c.RawUnixSocketPermissions, 8, 32)
  62. if err != nil {
  63. // ignore incorrectly formatted permissions
  64. return 0
  65. }
  66. return os.FileMode(perm) & os.ModePerm
  67. }
  68. func (c GUIConfiguration) Network() string {
  69. if override := os.Getenv("STGUIADDRESS"); override != "" {
  70. url, err := url.Parse(override)
  71. if err == nil && strings.HasPrefix(url.Scheme, "unix") {
  72. return "unix"
  73. }
  74. return "tcp"
  75. }
  76. if strings.HasPrefix(c.RawAddress, "/") {
  77. return "unix"
  78. }
  79. return "tcp"
  80. }
  81. func (c GUIConfiguration) UseTLS() bool {
  82. if override := os.Getenv("STGUIADDRESS"); override != "" {
  83. return strings.HasPrefix(override, "https:") || strings.HasPrefix(override, "unixs:")
  84. }
  85. return c.RawUseTLS
  86. }
  87. func (c GUIConfiguration) URL() string {
  88. if c.Network() == "unix" {
  89. if c.UseTLS() {
  90. return "unixs://" + c.Address()
  91. }
  92. return "unix://" + c.Address()
  93. }
  94. u := url.URL{
  95. Scheme: "http",
  96. Host: c.Address(),
  97. Path: "/",
  98. }
  99. if c.UseTLS() {
  100. u.Scheme = "https"
  101. }
  102. if strings.HasPrefix(u.Host, ":") {
  103. // Empty host, i.e. ":port", use IPv4 localhost
  104. u.Host = "127.0.0.1" + u.Host
  105. } else if strings.HasPrefix(u.Host, "0.0.0.0:") {
  106. // IPv4 all zeroes host, convert to IPv4 localhost
  107. u.Host = "127.0.0.1" + u.Host[7:]
  108. } else if strings.HasPrefix(u.Host, "[::]:") {
  109. // IPv6 all zeroes host, convert to IPv6 localhost
  110. u.Host = "[::1]" + u.Host[4:]
  111. }
  112. return u.String()
  113. }
  114. // matches a bcrypt hash and not too much else
  115. var bcryptExpr = regexp.MustCompile(`^\$2[aby]\$\d+\$.{50,}`)
  116. // SetPassword takes a bcrypt hash or a plaintext password and stores it.
  117. // Plaintext passwords are hashed. Returns an error if the password is not
  118. // valid.
  119. func (c *GUIConfiguration) SetPassword(password string) error {
  120. if bcryptExpr.MatchString(password) {
  121. // Already hashed
  122. c.Password = password
  123. return nil
  124. }
  125. hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
  126. if err != nil {
  127. return err
  128. }
  129. c.Password = string(hash)
  130. return nil
  131. }
  132. // CompareHashedPassword returns nil when the given plaintext password matches the stored hash.
  133. func (c GUIConfiguration) CompareHashedPassword(password string) error {
  134. configPasswordBytes := []byte(c.Password)
  135. passwordBytes := []byte(password)
  136. return bcrypt.CompareHashAndPassword(configPasswordBytes, passwordBytes)
  137. }
  138. // IsValidAPIKey returns true when the given API key is valid, including both
  139. // the value in config and any overrides
  140. func (c GUIConfiguration) IsValidAPIKey(apiKey string) bool {
  141. switch apiKey {
  142. case "":
  143. return false
  144. case c.APIKey, os.Getenv("STGUIAPIKEY"):
  145. return true
  146. default:
  147. return false
  148. }
  149. }
  150. func (c *GUIConfiguration) prepare() {
  151. if c.APIKey == "" {
  152. c.APIKey = rand.String(32)
  153. }
  154. }
  155. func (c GUIConfiguration) Copy() GUIConfiguration {
  156. return c
  157. }