group.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. // Copyright (C) 2019 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. package dataprovider
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "path/filepath"
  19. "strings"
  20. "github.com/sftpgo/sdk"
  21. "github.com/drakkan/sftpgo/v2/internal/logger"
  22. "github.com/drakkan/sftpgo/v2/internal/plugin"
  23. "github.com/drakkan/sftpgo/v2/internal/util"
  24. "github.com/drakkan/sftpgo/v2/internal/vfs"
  25. )
  26. // GroupUserSettings defines the settings to apply to users
  27. type GroupUserSettings struct {
  28. sdk.BaseGroupUserSettings
  29. // Filesystem configuration details
  30. FsConfig vfs.Filesystem `json:"filesystem"`
  31. }
  32. // Group defines an SFTPGo group.
  33. // Groups are used to easily configure similar users
  34. type Group struct {
  35. sdk.BaseGroup
  36. // settings to apply to users for whom this is a primary group
  37. UserSettings GroupUserSettings `json:"user_settings,omitempty"`
  38. // Mapping between virtual paths and virtual folders
  39. VirtualFolders []vfs.VirtualFolder `json:"virtual_folders,omitempty"`
  40. }
  41. // GetPermissions returns the permissions as list
  42. func (g *Group) GetPermissions() []sdk.DirectoryPermissions {
  43. result := make([]sdk.DirectoryPermissions, 0, len(g.UserSettings.Permissions))
  44. for k, v := range g.UserSettings.Permissions {
  45. result = append(result, sdk.DirectoryPermissions{
  46. Path: k,
  47. Permissions: v,
  48. })
  49. }
  50. return result
  51. }
  52. // GetAllowedIPAsString returns the allowed IP as comma separated string
  53. func (g *Group) GetAllowedIPAsString() string {
  54. return strings.Join(g.UserSettings.Filters.AllowedIP, ",")
  55. }
  56. // GetDeniedIPAsString returns the denied IP as comma separated string
  57. func (g *Group) GetDeniedIPAsString() string {
  58. return strings.Join(g.UserSettings.Filters.DeniedIP, ",")
  59. }
  60. // HasExternalAuth returns true if the external authentication is globally enabled
  61. // and it is not disabled for this group
  62. func (g *Group) HasExternalAuth() bool {
  63. if g.UserSettings.Filters.Hooks.ExternalAuthDisabled {
  64. return false
  65. }
  66. if config.ExternalAuthHook != "" {
  67. return true
  68. }
  69. return plugin.Handler.HasAuthenticators()
  70. }
  71. // SetEmptySecretsIfNil sets the secrets to empty if nil
  72. func (g *Group) SetEmptySecretsIfNil() {
  73. g.UserSettings.FsConfig.SetEmptySecretsIfNil()
  74. for idx := range g.VirtualFolders {
  75. vfolder := &g.VirtualFolders[idx]
  76. vfolder.FsConfig.SetEmptySecretsIfNil()
  77. }
  78. }
  79. // PrepareForRendering prepares a group for rendering.
  80. // It hides confidential data and set to nil the empty secrets
  81. // so they are not serialized
  82. func (g *Group) PrepareForRendering() {
  83. g.UserSettings.FsConfig.HideConfidentialData()
  84. g.UserSettings.FsConfig.SetNilSecretsIfEmpty()
  85. for idx := range g.VirtualFolders {
  86. folder := &g.VirtualFolders[idx]
  87. folder.PrepareForRendering()
  88. }
  89. }
  90. // RenderAsJSON implements the renderer interface used within plugins
  91. func (g *Group) RenderAsJSON(reload bool) ([]byte, error) {
  92. if reload {
  93. group, err := provider.groupExists(g.Name)
  94. if err != nil {
  95. providerLog(logger.LevelError, "unable to reload group before rendering as json: %v", err)
  96. return nil, err
  97. }
  98. group.PrepareForRendering()
  99. return json.Marshal(group)
  100. }
  101. g.PrepareForRendering()
  102. return json.Marshal(g)
  103. }
  104. // GetEncryptionAdditionalData returns the additional data to use for AEAD
  105. func (g *Group) GetEncryptionAdditionalData() string {
  106. return fmt.Sprintf("group_%v", g.Name)
  107. }
  108. // HasRedactedSecret returns true if the user has a redacted secret
  109. func (g *Group) hasRedactedSecret() bool {
  110. for idx := range g.VirtualFolders {
  111. folder := &g.VirtualFolders[idx]
  112. if folder.HasRedactedSecret() {
  113. return true
  114. }
  115. }
  116. return g.UserSettings.FsConfig.HasRedactedSecret()
  117. }
  118. func (g *Group) validate() error {
  119. g.SetEmptySecretsIfNil()
  120. if g.Name == "" {
  121. return util.NewI18nError(util.NewValidationError("name is mandatory"), util.I18nErrorNameRequired)
  122. }
  123. if config.NamingRules&1 == 0 && !usernameRegex.MatchString(g.Name) {
  124. return util.NewI18nError(
  125. util.NewValidationError(fmt.Sprintf("name %q is not valid, the following characters are allowed: a-zA-Z0-9-_.~", g.Name)),
  126. util.I18nErrorInvalidName,
  127. )
  128. }
  129. if g.hasRedactedSecret() {
  130. return util.NewValidationError("cannot save a group with a redacted secret")
  131. }
  132. vfolders, err := validateAssociatedVirtualFolders(g.VirtualFolders)
  133. if err != nil {
  134. return err
  135. }
  136. g.VirtualFolders = vfolders
  137. return g.validateUserSettings()
  138. }
  139. func (g *Group) validateUserSettings() error {
  140. if g.UserSettings.HomeDir != "" {
  141. g.UserSettings.HomeDir = filepath.Clean(g.UserSettings.HomeDir)
  142. if !filepath.IsAbs(g.UserSettings.HomeDir) {
  143. return util.NewI18nError(
  144. util.NewValidationError(fmt.Sprintf("home_dir must be an absolute path, actual value: %v", g.UserSettings.HomeDir)),
  145. util.I18nErrorInvalidHomeDir,
  146. )
  147. }
  148. }
  149. if err := g.UserSettings.FsConfig.Validate(g.GetEncryptionAdditionalData()); err != nil {
  150. return err
  151. }
  152. if g.UserSettings.TotalDataTransfer > 0 {
  153. // if a total data transfer is defined we reset the separate upload and download limits
  154. g.UserSettings.UploadDataTransfer = 0
  155. g.UserSettings.DownloadDataTransfer = 0
  156. }
  157. if len(g.UserSettings.Permissions) > 0 {
  158. permissions, err := validateUserPermissions(g.UserSettings.Permissions)
  159. if err != nil {
  160. return util.NewI18nError(err, util.I18nErrorGenericPermission)
  161. }
  162. g.UserSettings.Permissions = permissions
  163. }
  164. g.UserSettings.Filters.TLSCerts = nil
  165. if err := validateBaseFilters(&g.UserSettings.Filters); err != nil {
  166. return err
  167. }
  168. if !g.HasExternalAuth() {
  169. g.UserSettings.Filters.ExternalAuthCacheTime = 0
  170. }
  171. g.UserSettings.Filters.UserType = ""
  172. return nil
  173. }
  174. func (g *Group) getACopy() Group {
  175. users := make([]string, len(g.Users))
  176. copy(users, g.Users)
  177. admins := make([]string, len(g.Admins))
  178. copy(admins, g.Admins)
  179. virtualFolders := make([]vfs.VirtualFolder, 0, len(g.VirtualFolders))
  180. for idx := range g.VirtualFolders {
  181. vfolder := g.VirtualFolders[idx].GetACopy()
  182. virtualFolders = append(virtualFolders, vfolder)
  183. }
  184. permissions := make(map[string][]string)
  185. for k, v := range g.UserSettings.Permissions {
  186. perms := make([]string, len(v))
  187. copy(perms, v)
  188. permissions[k] = perms
  189. }
  190. return Group{
  191. BaseGroup: sdk.BaseGroup{
  192. ID: g.ID,
  193. Name: g.Name,
  194. Description: g.Description,
  195. CreatedAt: g.CreatedAt,
  196. UpdatedAt: g.UpdatedAt,
  197. Users: users,
  198. Admins: admins,
  199. },
  200. UserSettings: GroupUserSettings{
  201. BaseGroupUserSettings: sdk.BaseGroupUserSettings{
  202. HomeDir: g.UserSettings.HomeDir,
  203. MaxSessions: g.UserSettings.MaxSessions,
  204. QuotaSize: g.UserSettings.QuotaSize,
  205. QuotaFiles: g.UserSettings.QuotaFiles,
  206. Permissions: permissions,
  207. UploadBandwidth: g.UserSettings.UploadBandwidth,
  208. DownloadBandwidth: g.UserSettings.DownloadBandwidth,
  209. UploadDataTransfer: g.UserSettings.UploadDataTransfer,
  210. DownloadDataTransfer: g.UserSettings.DownloadDataTransfer,
  211. TotalDataTransfer: g.UserSettings.TotalDataTransfer,
  212. ExpiresIn: g.UserSettings.ExpiresIn,
  213. Filters: copyBaseUserFilters(g.UserSettings.Filters),
  214. },
  215. FsConfig: g.UserSettings.FsConfig.GetACopy(),
  216. },
  217. VirtualFolders: virtualFolders,
  218. }
  219. }