folder.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. // Copyright (C) 2019-2023 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 vfs
  15. import (
  16. "errors"
  17. "fmt"
  18. "strconv"
  19. "strings"
  20. "github.com/rs/xid"
  21. "github.com/sftpgo/sdk"
  22. "github.com/drakkan/sftpgo/v2/internal/util"
  23. )
  24. // BaseVirtualFolder defines the path for the virtual folder and the used quota limits.
  25. // The same folder can be shared among multiple users and each user can have different
  26. // quota limits or a different virtual path.
  27. type BaseVirtualFolder struct {
  28. ID int64 `json:"id"`
  29. Name string `json:"name"`
  30. MappedPath string `json:"mapped_path,omitempty"`
  31. Description string `json:"description,omitempty"`
  32. UsedQuotaSize int64 `json:"used_quota_size"`
  33. // Used quota as number of files
  34. UsedQuotaFiles int `json:"used_quota_files"`
  35. // Last quota update as unix timestamp in milliseconds
  36. LastQuotaUpdate int64 `json:"last_quota_update"`
  37. // list of usernames associated with this virtual folder
  38. Users []string `json:"users,omitempty"`
  39. // list of group names associated with this virtual folder
  40. Groups []string `json:"groups,omitempty"`
  41. // Filesystem configuration details
  42. FsConfig Filesystem `json:"filesystem"`
  43. }
  44. // GetEncryptionAdditionalData returns the additional data to use for AEAD
  45. func (v *BaseVirtualFolder) GetEncryptionAdditionalData() string {
  46. return fmt.Sprintf("folder_%v", v.Name)
  47. }
  48. // GetACopy returns a copy
  49. func (v *BaseVirtualFolder) GetACopy() BaseVirtualFolder {
  50. users := make([]string, len(v.Users))
  51. copy(users, v.Users)
  52. groups := make([]string, len(v.Groups))
  53. copy(groups, v.Groups)
  54. return BaseVirtualFolder{
  55. ID: v.ID,
  56. Name: v.Name,
  57. Description: v.Description,
  58. MappedPath: v.MappedPath,
  59. UsedQuotaSize: v.UsedQuotaSize,
  60. UsedQuotaFiles: v.UsedQuotaFiles,
  61. LastQuotaUpdate: v.LastQuotaUpdate,
  62. Users: users,
  63. Groups: v.Groups,
  64. FsConfig: v.FsConfig.GetACopy(),
  65. }
  66. }
  67. // GetUsersAsString returns the list of users as comma separated string
  68. func (v *BaseVirtualFolder) GetUsersAsString() string {
  69. return strings.Join(v.Users, ",")
  70. }
  71. // GetGroupsAsString returns the list of groups as comma separated string
  72. func (v *BaseVirtualFolder) GetGroupsAsString() string {
  73. return strings.Join(v.Groups, ",")
  74. }
  75. // GetLastQuotaUpdateAsString returns the last quota update as string
  76. func (v *BaseVirtualFolder) GetLastQuotaUpdateAsString() string {
  77. if v.LastQuotaUpdate > 0 {
  78. return util.GetTimeFromMsecSinceEpoch(v.LastQuotaUpdate).UTC().Format("2006-01-02 15:04:05Z")
  79. }
  80. return ""
  81. }
  82. // GetQuotaSummary returns used quota and last update as string
  83. func (v *BaseVirtualFolder) GetQuotaSummary() string {
  84. var result string
  85. result = "Files: " + strconv.Itoa(v.UsedQuotaFiles)
  86. if v.UsedQuotaSize > 0 {
  87. result += ". Size: " + util.ByteCountIEC(v.UsedQuotaSize)
  88. }
  89. return result
  90. }
  91. // GetStorageDescrition returns the storage description
  92. func (v *BaseVirtualFolder) GetStorageDescrition() string {
  93. switch v.FsConfig.Provider {
  94. case sdk.LocalFilesystemProvider:
  95. return fmt.Sprintf("Local: %s", v.MappedPath)
  96. case sdk.S3FilesystemProvider:
  97. return fmt.Sprintf("S3: %s", v.FsConfig.S3Config.Bucket)
  98. case sdk.GCSFilesystemProvider:
  99. return fmt.Sprintf("GCS: %s", v.FsConfig.GCSConfig.Bucket)
  100. case sdk.AzureBlobFilesystemProvider:
  101. return fmt.Sprintf("AzBlob: %s", v.FsConfig.AzBlobConfig.Container)
  102. case sdk.CryptedFilesystemProvider:
  103. return fmt.Sprintf("Encrypted: %s", v.MappedPath)
  104. case sdk.SFTPFilesystemProvider:
  105. return fmt.Sprintf("SFTP: %s", v.FsConfig.SFTPConfig.Endpoint)
  106. case sdk.HTTPFilesystemProvider:
  107. return fmt.Sprintf("HTTP: %s", v.FsConfig.HTTPConfig.Endpoint)
  108. default:
  109. return ""
  110. }
  111. }
  112. // IsLocalOrLocalCrypted returns true if the folder provider is local or local encrypted
  113. func (v *BaseVirtualFolder) IsLocalOrLocalCrypted() bool {
  114. return v.FsConfig.Provider == sdk.LocalFilesystemProvider || v.FsConfig.Provider == sdk.CryptedFilesystemProvider
  115. }
  116. // hideConfidentialData hides folder confidential data
  117. func (v *BaseVirtualFolder) hideConfidentialData() {
  118. switch v.FsConfig.Provider {
  119. case sdk.S3FilesystemProvider:
  120. v.FsConfig.S3Config.HideConfidentialData()
  121. case sdk.GCSFilesystemProvider:
  122. v.FsConfig.GCSConfig.HideConfidentialData()
  123. case sdk.AzureBlobFilesystemProvider:
  124. v.FsConfig.AzBlobConfig.HideConfidentialData()
  125. case sdk.CryptedFilesystemProvider:
  126. v.FsConfig.CryptConfig.HideConfidentialData()
  127. case sdk.SFTPFilesystemProvider:
  128. v.FsConfig.SFTPConfig.HideConfidentialData()
  129. case sdk.HTTPFilesystemProvider:
  130. v.FsConfig.HTTPConfig.HideConfidentialData()
  131. }
  132. }
  133. // PrepareForRendering prepares a folder for rendering.
  134. // It hides confidential data and set to nil the empty secrets
  135. // so they are not serialized
  136. func (v *BaseVirtualFolder) PrepareForRendering() {
  137. v.hideConfidentialData()
  138. v.FsConfig.SetEmptySecretsIfNil()
  139. }
  140. // HasRedactedSecret returns true if the folder has a redacted secret
  141. func (v *BaseVirtualFolder) HasRedactedSecret() bool {
  142. return v.FsConfig.HasRedactedSecret()
  143. }
  144. // hasPathPlaceholder returns true if the folder has a path placeholder
  145. func (v *BaseVirtualFolder) hasPathPlaceholder() bool {
  146. placeholder := "%username%"
  147. switch v.FsConfig.Provider {
  148. case sdk.S3FilesystemProvider:
  149. return strings.Contains(v.FsConfig.S3Config.KeyPrefix, placeholder)
  150. case sdk.GCSFilesystemProvider:
  151. return strings.Contains(v.FsConfig.GCSConfig.KeyPrefix, placeholder)
  152. case sdk.AzureBlobFilesystemProvider:
  153. return strings.Contains(v.FsConfig.AzBlobConfig.KeyPrefix, placeholder)
  154. case sdk.SFTPFilesystemProvider:
  155. return strings.Contains(v.FsConfig.SFTPConfig.Prefix, placeholder)
  156. case sdk.LocalFilesystemProvider, sdk.CryptedFilesystemProvider:
  157. return strings.Contains(v.MappedPath, placeholder)
  158. }
  159. return false
  160. }
  161. // VirtualFolder defines a mapping between an SFTPGo virtual path and a
  162. // filesystem path outside the user home directory.
  163. // The specified paths must be absolute and the virtual path cannot be "/",
  164. // it must be a sub directory. The parent directory for the specified virtual
  165. // path must exist. SFTPGo will try to automatically create any missing
  166. // parent directory for the configured virtual folders at user login.
  167. type VirtualFolder struct {
  168. BaseVirtualFolder
  169. VirtualPath string `json:"virtual_path"`
  170. // Maximum size allowed as bytes. 0 means unlimited, -1 included in user quota
  171. QuotaSize int64 `json:"quota_size"`
  172. // Maximum number of files allowed. 0 means unlimited, -1 included in user quota
  173. QuotaFiles int `json:"quota_files"`
  174. }
  175. // GetFilesystem returns the filesystem for this folder
  176. func (v *VirtualFolder) GetFilesystem(connectionID string, forbiddenSelfUsers []string) (Fs, error) {
  177. switch v.FsConfig.Provider {
  178. case sdk.S3FilesystemProvider:
  179. return NewS3Fs(connectionID, v.MappedPath, v.VirtualPath, v.FsConfig.S3Config)
  180. case sdk.GCSFilesystemProvider:
  181. return NewGCSFs(connectionID, v.MappedPath, v.VirtualPath, v.FsConfig.GCSConfig)
  182. case sdk.AzureBlobFilesystemProvider:
  183. return NewAzBlobFs(connectionID, v.MappedPath, v.VirtualPath, v.FsConfig.AzBlobConfig)
  184. case sdk.CryptedFilesystemProvider:
  185. return NewCryptFs(connectionID, v.MappedPath, v.VirtualPath, v.FsConfig.CryptConfig)
  186. case sdk.SFTPFilesystemProvider:
  187. return NewSFTPFs(connectionID, v.VirtualPath, v.MappedPath, forbiddenSelfUsers, v.FsConfig.SFTPConfig)
  188. case sdk.HTTPFilesystemProvider:
  189. return NewHTTPFs(connectionID, v.MappedPath, v.VirtualPath, v.FsConfig.HTTPConfig)
  190. default:
  191. return NewOsFs(connectionID, v.MappedPath, v.VirtualPath, &v.FsConfig.OSConfig), nil
  192. }
  193. }
  194. // CheckMetadataConsistency checks the consistency between the metadata stored
  195. // in the configured metadata plugin and the filesystem
  196. func (v *VirtualFolder) CheckMetadataConsistency() error {
  197. fs, err := v.GetFilesystem(xid.New().String(), nil)
  198. if err != nil {
  199. return err
  200. }
  201. defer fs.Close()
  202. return fs.CheckMetadata()
  203. }
  204. // ScanQuota scans the folder and returns the number of files and their size
  205. func (v *VirtualFolder) ScanQuota() (int, int64, error) {
  206. if v.hasPathPlaceholder() {
  207. return 0, 0, errors.New("cannot scan quota: this folder has a path placeholder")
  208. }
  209. fs, err := v.GetFilesystem(xid.New().String(), nil)
  210. if err != nil {
  211. return 0, 0, err
  212. }
  213. defer fs.Close()
  214. return fs.ScanRootDirContents()
  215. }
  216. // IsIncludedInUserQuota returns true if the virtual folder is included in user quota
  217. func (v *VirtualFolder) IsIncludedInUserQuota() bool {
  218. return v.QuotaFiles == -1 && v.QuotaSize == -1
  219. }
  220. // HasNoQuotaRestrictions returns true if no quota restrictions need to be applyed
  221. func (v *VirtualFolder) HasNoQuotaRestrictions(checkFiles bool) bool {
  222. if v.QuotaSize == 0 && (!checkFiles || v.QuotaFiles == 0) {
  223. return true
  224. }
  225. return false
  226. }
  227. // GetACopy returns a copy
  228. func (v *VirtualFolder) GetACopy() VirtualFolder {
  229. return VirtualFolder{
  230. BaseVirtualFolder: v.BaseVirtualFolder.GetACopy(),
  231. VirtualPath: v.VirtualPath,
  232. QuotaSize: v.QuotaSize,
  233. QuotaFiles: v.QuotaFiles,
  234. }
  235. }