folder.go 7.8 KB

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