folder.go 9.1 KB

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