2020-06-07 21:30:18 +00:00
|
|
|
package vfs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2021-03-21 18:15:47 +00:00
|
|
|
"path/filepath"
|
2020-06-07 21:30:18 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2022-01-06 10:54:43 +00:00
|
|
|
"github.com/sftpgo/sdk"
|
|
|
|
|
2021-07-11 13:26:51 +00:00
|
|
|
"github.com/drakkan/sftpgo/v2/util"
|
2020-06-07 21:30:18 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// BaseVirtualFolder defines the path for the virtual folder and the used quota limits.
|
|
|
|
// The same folder can be shared among multiple users and each user can have different
|
|
|
|
// quota limits or a different virtual path.
|
|
|
|
type BaseVirtualFolder struct {
|
|
|
|
ID int64 `json:"id"`
|
2021-02-01 18:04:15 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
MappedPath string `json:"mapped_path,omitempty"`
|
2021-02-24 18:40:29 +00:00
|
|
|
Description string `json:"description,omitempty"`
|
2020-06-07 21:30:18 +00:00
|
|
|
UsedQuotaSize int64 `json:"used_quota_size"`
|
|
|
|
// Used quota as number of files
|
|
|
|
UsedQuotaFiles int `json:"used_quota_files"`
|
|
|
|
// Last quota update as unix timestamp in milliseconds
|
|
|
|
LastQuotaUpdate int64 `json:"last_quota_update"`
|
|
|
|
// list of usernames associated with this virtual folder
|
|
|
|
Users []string `json:"users,omitempty"`
|
2021-03-21 18:15:47 +00:00
|
|
|
// Filesystem configuration details
|
|
|
|
FsConfig Filesystem `json:"filesystem"`
|
|
|
|
}
|
|
|
|
|
2021-06-19 10:24:43 +00:00
|
|
|
// GetEncryptionAdditionalData returns the additional data to use for AEAD
|
|
|
|
func (v *BaseVirtualFolder) GetEncryptionAdditionalData() string {
|
2021-03-21 18:15:47 +00:00
|
|
|
return fmt.Sprintf("folder_%v", v.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetGCSCredentialsFilePath returns the path for GCS credentials
|
|
|
|
func (v *BaseVirtualFolder) GetGCSCredentialsFilePath() string {
|
|
|
|
return filepath.Join(credentialsDirPath, "folders", fmt.Sprintf("%v_gcs_credentials.json", v.Name))
|
2020-06-07 21:30:18 +00:00
|
|
|
}
|
|
|
|
|
2021-02-01 18:04:15 +00:00
|
|
|
// GetACopy returns a copy
|
|
|
|
func (v *BaseVirtualFolder) GetACopy() BaseVirtualFolder {
|
|
|
|
users := make([]string, len(v.Users))
|
|
|
|
copy(users, v.Users)
|
|
|
|
return BaseVirtualFolder{
|
|
|
|
ID: v.ID,
|
|
|
|
Name: v.Name,
|
2021-02-24 18:40:29 +00:00
|
|
|
Description: v.Description,
|
2021-02-01 18:04:15 +00:00
|
|
|
MappedPath: v.MappedPath,
|
|
|
|
UsedQuotaSize: v.UsedQuotaSize,
|
|
|
|
UsedQuotaFiles: v.UsedQuotaFiles,
|
|
|
|
LastQuotaUpdate: v.LastQuotaUpdate,
|
|
|
|
Users: users,
|
2021-03-21 18:15:47 +00:00
|
|
|
FsConfig: v.FsConfig.GetACopy(),
|
2021-02-01 18:04:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-07 21:30:18 +00:00
|
|
|
// GetUsersAsString returns the list of users as comma separated string
|
|
|
|
func (v *BaseVirtualFolder) GetUsersAsString() string {
|
|
|
|
return strings.Join(v.Users, ",")
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetQuotaSummary returns used quota and last update as string
|
|
|
|
func (v *BaseVirtualFolder) GetQuotaSummary() string {
|
|
|
|
var result string
|
|
|
|
result = "Files: " + strconv.Itoa(v.UsedQuotaFiles)
|
|
|
|
if v.UsedQuotaSize > 0 {
|
2021-07-11 13:26:51 +00:00
|
|
|
result += ". Size: " + util.ByteCountIEC(v.UsedQuotaSize)
|
2020-06-07 21:30:18 +00:00
|
|
|
}
|
|
|
|
if v.LastQuotaUpdate > 0 {
|
2021-07-11 13:26:51 +00:00
|
|
|
t := util.GetTimeFromMsecSinceEpoch(v.LastQuotaUpdate)
|
2021-03-28 09:02:11 +00:00
|
|
|
result += fmt.Sprintf(". Last update: %v ", t.Format("2006-01-02 15:04")) // YYYY-MM-DD HH:MM
|
2020-06-07 21:30:18 +00:00
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-03-27 21:23:01 +00:00
|
|
|
// GetStorageDescrition returns the storage description
|
|
|
|
func (v *BaseVirtualFolder) GetStorageDescrition() string {
|
|
|
|
switch v.FsConfig.Provider {
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.LocalFilesystemProvider:
|
2021-03-27 21:23:01 +00:00
|
|
|
return fmt.Sprintf("Local: %v", v.MappedPath)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.S3FilesystemProvider:
|
2021-03-27 21:23:01 +00:00
|
|
|
return fmt.Sprintf("S3: %v", v.FsConfig.S3Config.Bucket)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.GCSFilesystemProvider:
|
2021-03-27 21:23:01 +00:00
|
|
|
return fmt.Sprintf("GCS: %v", v.FsConfig.GCSConfig.Bucket)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.AzureBlobFilesystemProvider:
|
2021-03-27 21:23:01 +00:00
|
|
|
return fmt.Sprintf("AzBlob: %v", v.FsConfig.AzBlobConfig.Container)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.CryptedFilesystemProvider:
|
2021-03-27 21:23:01 +00:00
|
|
|
return fmt.Sprintf("Encrypted: %v", v.MappedPath)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.SFTPFilesystemProvider:
|
2021-03-27 21:23:01 +00:00
|
|
|
return fmt.Sprintf("SFTP: %v", v.FsConfig.SFTPConfig.Endpoint)
|
|
|
|
default:
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-21 18:15:47 +00:00
|
|
|
// IsLocalOrLocalCrypted returns true if the folder provider is local or local encrypted
|
|
|
|
func (v *BaseVirtualFolder) IsLocalOrLocalCrypted() bool {
|
2021-07-11 13:26:51 +00:00
|
|
|
return v.FsConfig.Provider == sdk.LocalFilesystemProvider || v.FsConfig.Provider == sdk.CryptedFilesystemProvider
|
2021-03-21 18:15:47 +00:00
|
|
|
}
|
|
|
|
|
2021-03-22 18:03:25 +00:00
|
|
|
// hideConfidentialData hides folder confidential data
|
|
|
|
func (v *BaseVirtualFolder) hideConfidentialData() {
|
2021-03-21 18:15:47 +00:00
|
|
|
switch v.FsConfig.Provider {
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.S3FilesystemProvider:
|
2021-09-11 12:19:17 +00:00
|
|
|
v.FsConfig.S3Config.HideConfidentialData()
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.GCSFilesystemProvider:
|
2021-09-11 12:19:17 +00:00
|
|
|
v.FsConfig.GCSConfig.HideConfidentialData()
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.AzureBlobFilesystemProvider:
|
2021-09-11 12:19:17 +00:00
|
|
|
v.FsConfig.AzBlobConfig.HideConfidentialData()
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.CryptedFilesystemProvider:
|
2021-09-11 12:19:17 +00:00
|
|
|
v.FsConfig.CryptConfig.HideConfidentialData()
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.SFTPFilesystemProvider:
|
2021-09-11 12:19:17 +00:00
|
|
|
v.FsConfig.SFTPConfig.HideConfidentialData()
|
2021-03-21 18:15:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-22 18:03:25 +00:00
|
|
|
// PrepareForRendering prepares a folder for rendering.
|
|
|
|
// It hides confidential data and set to nil the empty secrets
|
|
|
|
// so they are not serialized
|
|
|
|
func (v *BaseVirtualFolder) PrepareForRendering() {
|
|
|
|
v.hideConfidentialData()
|
|
|
|
v.FsConfig.SetEmptySecretsIfNil()
|
|
|
|
}
|
|
|
|
|
2021-03-21 18:15:47 +00:00
|
|
|
// HasRedactedSecret returns true if the folder has a redacted secret
|
|
|
|
func (v *BaseVirtualFolder) HasRedactedSecret() bool {
|
|
|
|
switch v.FsConfig.Provider {
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.S3FilesystemProvider:
|
2021-03-21 18:15:47 +00:00
|
|
|
if v.FsConfig.S3Config.AccessSecret.IsRedacted() {
|
|
|
|
return true
|
|
|
|
}
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.GCSFilesystemProvider:
|
2021-03-21 18:15:47 +00:00
|
|
|
if v.FsConfig.GCSConfig.Credentials.IsRedacted() {
|
|
|
|
return true
|
|
|
|
}
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.AzureBlobFilesystemProvider:
|
2021-03-21 18:15:47 +00:00
|
|
|
if v.FsConfig.AzBlobConfig.AccountKey.IsRedacted() {
|
|
|
|
return true
|
|
|
|
}
|
2021-06-11 20:27:36 +00:00
|
|
|
if v.FsConfig.AzBlobConfig.SASURL.IsRedacted() {
|
|
|
|
return true
|
|
|
|
}
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.CryptedFilesystemProvider:
|
2021-03-21 18:15:47 +00:00
|
|
|
if v.FsConfig.CryptConfig.Passphrase.IsRedacted() {
|
|
|
|
return true
|
|
|
|
}
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.SFTPFilesystemProvider:
|
2021-03-21 18:15:47 +00:00
|
|
|
if v.FsConfig.SFTPConfig.Password.IsRedacted() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if v.FsConfig.SFTPConfig.PrivateKey.IsRedacted() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-02-14 21:08:08 +00:00
|
|
|
// VirtualFolder defines a mapping between an SFTPGo exposed virtual path and a
|
2020-06-07 21:30:18 +00:00
|
|
|
// filesystem path outside the user home directory.
|
|
|
|
// The specified paths must be absolute and the virtual path cannot be "/",
|
|
|
|
// it must be a sub directory. The parent directory for the specified virtual
|
|
|
|
// path must exist. SFTPGo will try to automatically create any missing
|
|
|
|
// parent directory for the configured virtual folders at user login.
|
|
|
|
type VirtualFolder struct {
|
|
|
|
BaseVirtualFolder
|
|
|
|
VirtualPath string `json:"virtual_path"`
|
|
|
|
// Maximum size allowed as bytes. 0 means unlimited, -1 included in user quota
|
|
|
|
QuotaSize int64 `json:"quota_size"`
|
|
|
|
// Maximum number of files allowed. 0 means unlimited, -1 included in user quota
|
|
|
|
QuotaFiles int `json:"quota_files"`
|
|
|
|
}
|
|
|
|
|
2021-03-21 21:21:04 +00:00
|
|
|
// GetFilesystem returns the filesystem for this folder
|
2021-04-01 16:53:48 +00:00
|
|
|
func (v *VirtualFolder) GetFilesystem(connectionID string, forbiddenSelfUsers []string) (Fs, error) {
|
2021-03-21 18:15:47 +00:00
|
|
|
switch v.FsConfig.Provider {
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.S3FilesystemProvider:
|
2021-03-21 18:15:47 +00:00
|
|
|
return NewS3Fs(connectionID, v.MappedPath, v.VirtualPath, v.FsConfig.S3Config)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.GCSFilesystemProvider:
|
2021-03-21 18:15:47 +00:00
|
|
|
config := v.FsConfig.GCSConfig
|
|
|
|
config.CredentialFile = v.GetGCSCredentialsFilePath()
|
|
|
|
return NewGCSFs(connectionID, v.MappedPath, v.VirtualPath, config)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.AzureBlobFilesystemProvider:
|
2021-03-21 18:15:47 +00:00
|
|
|
return NewAzBlobFs(connectionID, v.MappedPath, v.VirtualPath, v.FsConfig.AzBlobConfig)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.CryptedFilesystemProvider:
|
2021-03-21 18:15:47 +00:00
|
|
|
return NewCryptFs(connectionID, v.MappedPath, v.VirtualPath, v.FsConfig.CryptConfig)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.SFTPFilesystemProvider:
|
2021-04-03 14:00:55 +00:00
|
|
|
return NewSFTPFs(connectionID, v.VirtualPath, v.MappedPath, forbiddenSelfUsers, v.FsConfig.SFTPConfig)
|
2021-03-21 18:15:47 +00:00
|
|
|
default:
|
|
|
|
return NewOsFs(connectionID, v.MappedPath, v.VirtualPath), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-16 17:18:36 +00:00
|
|
|
// CheckMetadataConsistency checks the consistency between the metadata stored
|
|
|
|
// in the configured metadata plugin and the filesystem
|
|
|
|
func (v *VirtualFolder) CheckMetadataConsistency() error {
|
|
|
|
fs, err := v.GetFilesystem("", nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer fs.Close()
|
|
|
|
|
|
|
|
return fs.CheckMetadata()
|
|
|
|
}
|
|
|
|
|
2021-03-21 18:15:47 +00:00
|
|
|
// ScanQuota scans the folder and returns the number of files and their size
|
|
|
|
func (v *VirtualFolder) ScanQuota() (int, int64, error) {
|
2021-04-01 16:53:48 +00:00
|
|
|
fs, err := v.GetFilesystem("", nil)
|
2021-03-21 18:15:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, 0, err
|
|
|
|
}
|
|
|
|
defer fs.Close()
|
|
|
|
|
|
|
|
return fs.ScanRootDirContents()
|
|
|
|
}
|
|
|
|
|
2020-06-07 21:30:18 +00:00
|
|
|
// IsIncludedInUserQuota returns true if the virtual folder is included in user quota
|
|
|
|
func (v *VirtualFolder) IsIncludedInUserQuota() bool {
|
|
|
|
return v.QuotaFiles == -1 && v.QuotaSize == -1
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasNoQuotaRestrictions returns true if no quota restrictions need to be applyed
|
|
|
|
func (v *VirtualFolder) HasNoQuotaRestrictions(checkFiles bool) bool {
|
|
|
|
if v.QuotaSize == 0 && (!checkFiles || v.QuotaFiles == 0) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2021-03-21 18:15:47 +00:00
|
|
|
|
|
|
|
// GetACopy returns a copy
|
|
|
|
func (v *VirtualFolder) GetACopy() VirtualFolder {
|
|
|
|
return VirtualFolder{
|
|
|
|
BaseVirtualFolder: v.BaseVirtualFolder.GetACopy(),
|
|
|
|
VirtualPath: v.VirtualPath,
|
|
|
|
QuotaSize: v.QuotaSize,
|
|
|
|
QuotaFiles: v.QuotaFiles,
|
|
|
|
}
|
|
|
|
}
|