group.go 6.9 KB

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