group.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // Copyright (C) 2019-2022 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.NewValidationError("name is mandatory")
  122. }
  123. if config.NamingRules&1 == 0 && !usernameRegex.MatchString(g.Name) {
  124. return util.NewValidationError(fmt.Sprintf("name %#v is not valid, the following characters are allowed: a-zA-Z0-9-_.~", g.Name))
  125. }
  126. if g.hasRedactedSecret() {
  127. return util.NewValidationError("cannot save a user with a redacted secret")
  128. }
  129. vfolders, err := validateAssociatedVirtualFolders(g.VirtualFolders)
  130. if err != nil {
  131. return err
  132. }
  133. g.VirtualFolders = vfolders
  134. return g.validateUserSettings()
  135. }
  136. func (g *Group) validateUserSettings() error {
  137. if g.UserSettings.HomeDir != "" {
  138. g.UserSettings.HomeDir = filepath.Clean(g.UserSettings.HomeDir)
  139. if !filepath.IsAbs(g.UserSettings.HomeDir) {
  140. return util.NewValidationError(fmt.Sprintf("home_dir must be an absolute path, actual value: %v",
  141. g.UserSettings.HomeDir))
  142. }
  143. }
  144. if err := g.UserSettings.FsConfig.Validate(g.GetEncryptionAdditionalData()); err != nil {
  145. return err
  146. }
  147. if g.UserSettings.TotalDataTransfer > 0 {
  148. // if a total data transfer is defined we reset the separate upload and download limits
  149. g.UserSettings.UploadDataTransfer = 0
  150. g.UserSettings.DownloadDataTransfer = 0
  151. }
  152. if len(g.UserSettings.Permissions) > 0 {
  153. permissions, err := validateUserPermissions(g.UserSettings.Permissions)
  154. if err != nil {
  155. return err
  156. }
  157. g.UserSettings.Permissions = permissions
  158. }
  159. if err := validateBaseFilters(&g.UserSettings.Filters); err != nil {
  160. return err
  161. }
  162. if !g.HasExternalAuth() {
  163. g.UserSettings.Filters.ExternalAuthCacheTime = 0
  164. }
  165. g.UserSettings.Filters.UserType = ""
  166. return nil
  167. }
  168. func (g *Group) getACopy() Group {
  169. users := make([]string, len(g.Users))
  170. copy(users, g.Users)
  171. admins := make([]string, len(g.Admins))
  172. copy(admins, g.Admins)
  173. virtualFolders := make([]vfs.VirtualFolder, 0, len(g.VirtualFolders))
  174. for idx := range g.VirtualFolders {
  175. vfolder := g.VirtualFolders[idx].GetACopy()
  176. virtualFolders = append(virtualFolders, vfolder)
  177. }
  178. permissions := make(map[string][]string)
  179. for k, v := range g.UserSettings.Permissions {
  180. perms := make([]string, len(v))
  181. copy(perms, v)
  182. permissions[k] = perms
  183. }
  184. return Group{
  185. BaseGroup: sdk.BaseGroup{
  186. ID: g.ID,
  187. Name: g.Name,
  188. Description: g.Description,
  189. CreatedAt: g.CreatedAt,
  190. UpdatedAt: g.UpdatedAt,
  191. Users: users,
  192. Admins: admins,
  193. },
  194. UserSettings: GroupUserSettings{
  195. BaseGroupUserSettings: sdk.BaseGroupUserSettings{
  196. HomeDir: g.UserSettings.HomeDir,
  197. MaxSessions: g.UserSettings.MaxSessions,
  198. QuotaSize: g.UserSettings.QuotaSize,
  199. QuotaFiles: g.UserSettings.QuotaFiles,
  200. Permissions: permissions,
  201. UploadBandwidth: g.UserSettings.UploadBandwidth,
  202. DownloadBandwidth: g.UserSettings.DownloadBandwidth,
  203. UploadDataTransfer: g.UserSettings.UploadDataTransfer,
  204. DownloadDataTransfer: g.UserSettings.DownloadDataTransfer,
  205. TotalDataTransfer: g.UserSettings.TotalDataTransfer,
  206. Filters: copyBaseUserFilters(g.UserSettings.Filters),
  207. },
  208. FsConfig: g.UserSettings.FsConfig.GetACopy(),
  209. },
  210. VirtualFolders: virtualFolders,
  211. }
  212. }
  213. // GetMembersAsString returns a string representation for the group members
  214. func (g *Group) GetMembersAsString() string {
  215. var sb strings.Builder
  216. if len(g.Users) > 0 {
  217. sb.WriteString(fmt.Sprintf("Users: %d. ", len(g.Users)))
  218. }
  219. if len(g.Admins) > 0 {
  220. sb.WriteString(fmt.Sprintf("Admins: %d. ", len(g.Admins)))
  221. }
  222. return sb.String()
  223. }