move Filesystem config validation to vfs

This commit is contained in:
Manuel Reithuber 2021-06-19 12:24:43 +02:00 committed by Nicola Murino
parent f2f612b450
commit f19937b715
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
10 changed files with 234 additions and 206 deletions

View file

@ -86,36 +86,36 @@ func (a *Admin) checkPassword() error {
func (a *Admin) validate() error {
if a.Username == "" {
return &ValidationError{err: "username is mandatory"}
return utils.NewValidationError("username is mandatory")
}
if a.Password == "" {
return &ValidationError{err: "please set a password"}
return utils.NewValidationError("please set a password")
}
if !config.SkipNaturalKeysValidation && !usernameRegex.MatchString(a.Username) {
return &ValidationError{err: fmt.Sprintf("username %#v is not valid, the following characters are allowed: a-zA-Z0-9-_.~", a.Username)}
return utils.NewValidationError(fmt.Sprintf("username %#v is not valid, the following characters are allowed: a-zA-Z0-9-_.~", a.Username))
}
if err := a.checkPassword(); err != nil {
return err
}
a.Permissions = utils.RemoveDuplicates(a.Permissions)
if len(a.Permissions) == 0 {
return &ValidationError{err: "please grant some permissions to this admin"}
return utils.NewValidationError("please grant some permissions to this admin")
}
if utils.IsStringInSlice(PermAdminAny, a.Permissions) {
a.Permissions = []string{PermAdminAny}
}
for _, perm := range a.Permissions {
if !utils.IsStringInSlice(perm, validAdminPerms) {
return &ValidationError{err: fmt.Sprintf("invalid permission: %#v", perm)}
return utils.NewValidationError(fmt.Sprintf("invalid permission: %#v", perm))
}
}
if a.Email != "" && !emailRegex.MatchString(a.Email) {
return &ValidationError{err: fmt.Sprintf("email %#v is not valid", a.Email)}
return utils.NewValidationError(fmt.Sprintf("email %#v is not valid", a.Email))
}
for _, IPMask := range a.Filters.AllowList {
_, _, err := net.ParseCIDR(IPMask)
if err != nil {
return &ValidationError{err: fmt.Sprintf("could not parse allow list entry %#v : %v", IPMask, err)}
return utils.NewValidationError(fmt.Sprintf("could not parse allow list entry %#v : %v", IPMask, err))
}
}

View file

@ -366,23 +366,6 @@ type checkPasswordResponse struct {
ToVerify string `json:"to_verify"`
}
// ValidationError raised if input data is not valid
type ValidationError struct {
err string
}
// Validation error details
func (e *ValidationError) Error() string {
return fmt.Sprintf("Validation error: %s", e.err)
}
// NewValidationError returns a validation errors
func NewValidationError(error string) *ValidationError {
return &ValidationError{
err: error,
}
}
// MethodDisabledError raised if a method is disabled in config file.
// For example, if user management is disabled, this error is raised
// every time a user operation is done using the REST API
@ -453,11 +436,6 @@ type Provider interface {
revertDatabase(targetVersion int) error
}
type fsValidatorHelper interface {
GetGCSCredentialsFilePath() string
GetEncrytionAdditionalData() string
}
// SetTempPath sets the path for temporary files
func SetTempPath(fsPath string) {
tempPath = fsPath
@ -1164,14 +1142,14 @@ func isMappedDirOverlapped(dir1, dir2 string, fullCheck bool) bool {
func validateFolderQuotaLimits(folder vfs.VirtualFolder) error {
if folder.QuotaSize < -1 {
return &ValidationError{err: fmt.Sprintf("invalid quota_size: %v folder path %#v", folder.QuotaSize, folder.MappedPath)}
return utils.NewValidationError(fmt.Sprintf("invalid quota_size: %v folder path %#v", folder.QuotaSize, folder.MappedPath))
}
if folder.QuotaFiles < -1 {
return &ValidationError{err: fmt.Sprintf("invalid quota_file: %v folder path %#v", folder.QuotaFiles, folder.MappedPath)}
return utils.NewValidationError(fmt.Sprintf("invalid quota_file: %v folder path %#v", folder.QuotaFiles, folder.MappedPath))
}
if (folder.QuotaSize == -1 && folder.QuotaFiles != -1) || (folder.QuotaFiles == -1 && folder.QuotaSize != -1) {
return &ValidationError{err: fmt.Sprintf("virtual folder quota_size and quota_files must be both -1 or >= 0, quota_size: %v quota_files: %v",
folder.QuotaFiles, folder.QuotaSize)}
return utils.NewValidationError(fmt.Sprintf("virtual folder quota_size and quota_files must be both -1 or >= 0, quota_size: %v quota_files: %v",
folder.QuotaFiles, folder.QuotaSize))
}
return nil
}
@ -1207,7 +1185,7 @@ func validateUserVirtualFolders(user *User) error {
for _, v := range user.VirtualFolders {
cleanedVPath := filepath.ToSlash(path.Clean(v.VirtualPath))
if !path.IsAbs(cleanedVPath) || cleanedVPath == "/" {
return &ValidationError{err: fmt.Sprintf("invalid virtual folder %#v", v.VirtualPath)}
return utils.NewValidationError(fmt.Sprintf("invalid virtual folder %#v", v.VirtualPath))
}
if err := validateFolderQuotaLimits(v); err != nil {
return err
@ -1219,21 +1197,21 @@ func validateUserVirtualFolders(user *User) error {
cleanedMPath := folder.MappedPath
if folder.IsLocalOrLocalCrypted() {
if isMappedDirOverlapped(cleanedMPath, user.GetHomeDir(), true) {
return &ValidationError{err: fmt.Sprintf("invalid mapped folder %#v cannot be inside or contain the user home dir %#v",
folder.MappedPath, user.GetHomeDir())}
return utils.NewValidationError(fmt.Sprintf("invalid mapped folder %#v cannot be inside or contain the user home dir %#v",
folder.MappedPath, user.GetHomeDir()))
}
for mPath := range mappedPaths {
if folder.IsLocalOrLocalCrypted() && isMappedDirOverlapped(mPath, cleanedMPath, false) {
return &ValidationError{err: fmt.Sprintf("invalid mapped folder %#v overlaps with mapped folder %#v",
v.MappedPath, mPath)}
return utils.NewValidationError(fmt.Sprintf("invalid mapped folder %#v overlaps with mapped folder %#v",
v.MappedPath, mPath))
}
}
mappedPaths[cleanedMPath] = true
}
for vPath := range virtualPaths {
if isVirtualDirOverlapped(vPath, cleanedVPath, false) {
return &ValidationError{err: fmt.Sprintf("invalid virtual folder %#v overlaps with virtual folder %#v",
v.VirtualPath, vPath)}
return utils.NewValidationError(fmt.Sprintf("invalid virtual folder %#v overlaps with virtual folder %#v",
v.VirtualPath, vPath))
}
}
virtualPaths[cleanedVPath] = true
@ -1250,22 +1228,22 @@ func validateUserVirtualFolders(user *User) error {
func validatePermissions(user *User) error {
if len(user.Permissions) == 0 {
return &ValidationError{err: "please grant some permissions to this user"}
return utils.NewValidationError("please grant some permissions to this user")
}
permissions := make(map[string][]string)
if _, ok := user.Permissions["/"]; !ok {
return &ValidationError{err: "permissions for the root dir \"/\" must be set"}
return utils.NewValidationError("permissions for the root dir \"/\" must be set")
}
for dir, perms := range user.Permissions {
if len(perms) == 0 && dir == "/" {
return &ValidationError{err: fmt.Sprintf("no permissions granted for the directory: %#v", dir)}
return utils.NewValidationError(fmt.Sprintf("no permissions granted for the directory: %#v", dir))
}
if len(perms) > len(ValidPerms) {
return &ValidationError{err: "invalid permissions"}
return utils.NewValidationError("invalid permissions")
}
for _, p := range perms {
if !utils.IsStringInSlice(p, ValidPerms) {
return &ValidationError{err: fmt.Sprintf("invalid permission: %#v", p)}
return utils.NewValidationError(fmt.Sprintf("invalid permission: %#v", p))
}
}
cleanedDir := filepath.ToSlash(path.Clean(dir))
@ -1273,10 +1251,10 @@ func validatePermissions(user *User) error {
cleanedDir = strings.TrimSuffix(cleanedDir, "/")
}
if !path.IsAbs(cleanedDir) {
return &ValidationError{err: fmt.Sprintf("cannot set permissions for non absolute path: %#v", dir)}
return utils.NewValidationError(fmt.Sprintf("cannot set permissions for non absolute path: %#v", dir))
}
if dir != cleanedDir && cleanedDir == "/" {
return &ValidationError{err: fmt.Sprintf("cannot set permissions for invalid subdirectory: %#v is an alias for \"/\"", dir)}
return utils.NewValidationError(fmt.Sprintf("cannot set permissions for invalid subdirectory: %#v is an alias for \"/\"", dir))
}
if utils.IsStringInSlice(PermAny, perms) {
permissions[cleanedDir] = []string{PermAny}
@ -1299,7 +1277,7 @@ func validatePublicKeys(user *User) error {
}
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
if err != nil {
return &ValidationError{err: fmt.Sprintf("could not parse key nr. %d: %s", i+1, err)}
return utils.NewValidationError(fmt.Sprintf("could not parse key nr. %d: %s", i+1, err))
}
validatedKeys = append(validatedKeys, k)
}
@ -1317,13 +1295,13 @@ func validateFiltersPatternExtensions(user *User) error {
for _, f := range user.Filters.FilePatterns {
cleanedPath := filepath.ToSlash(path.Clean(f.Path))
if !path.IsAbs(cleanedPath) {
return &ValidationError{err: fmt.Sprintf("invalid path %#v for file patterns filter", f.Path)}
return utils.NewValidationError(fmt.Sprintf("invalid path %#v for file patterns filter", f.Path))
}
if utils.IsStringInSlice(cleanedPath, filteredPaths) {
return &ValidationError{err: fmt.Sprintf("duplicate file patterns filter for path %#v", f.Path)}
return utils.NewValidationError(fmt.Sprintf("duplicate file patterns filter for path %#v", f.Path))
}
if len(f.AllowedPatterns) == 0 && len(f.DeniedPatterns) == 0 {
return &ValidationError{err: fmt.Sprintf("empty file patterns filter for path %#v", f.Path)}
return utils.NewValidationError(fmt.Sprintf("empty file patterns filter for path %#v", f.Path))
}
f.Path = cleanedPath
allowed := make([]string, 0, len(f.AllowedPatterns))
@ -1331,14 +1309,14 @@ func validateFiltersPatternExtensions(user *User) error {
for _, pattern := range f.AllowedPatterns {
_, err := path.Match(pattern, "abc")
if err != nil {
return &ValidationError{err: fmt.Sprintf("invalid file pattern filter %#v", pattern)}
return utils.NewValidationError(fmt.Sprintf("invalid file pattern filter %#v", pattern))
}
allowed = append(allowed, strings.ToLower(pattern))
}
for _, pattern := range f.DeniedPatterns {
_, err := path.Match(pattern, "abc")
if err != nil {
return &ValidationError{err: fmt.Sprintf("invalid file pattern filter %#v", pattern)}
return utils.NewValidationError(fmt.Sprintf("invalid file pattern filter %#v", pattern))
}
denied = append(denied, strings.ToLower(pattern))
}
@ -1371,45 +1349,45 @@ func validateFilters(user *User) error {
for _, IPMask := range user.Filters.DeniedIP {
_, _, err := net.ParseCIDR(IPMask)
if err != nil {
return &ValidationError{err: fmt.Sprintf("could not parse denied IP/Mask %#v : %v", IPMask, err)}
return utils.NewValidationError(fmt.Sprintf("could not parse denied IP/Mask %#v : %v", IPMask, err))
}
}
for _, IPMask := range user.Filters.AllowedIP {
_, _, err := net.ParseCIDR(IPMask)
if err != nil {
return &ValidationError{err: fmt.Sprintf("could not parse allowed IP/Mask %#v : %v", IPMask, err)}
return utils.NewValidationError(fmt.Sprintf("could not parse allowed IP/Mask %#v : %v", IPMask, err))
}
}
if len(user.Filters.DeniedLoginMethods) >= len(ValidLoginMethods) {
return &ValidationError{err: "invalid denied_login_methods"}
return utils.NewValidationError("invalid denied_login_methods")
}
for _, loginMethod := range user.Filters.DeniedLoginMethods {
if !utils.IsStringInSlice(loginMethod, ValidLoginMethods) {
return &ValidationError{err: fmt.Sprintf("invalid login method: %#v", loginMethod)}
return utils.NewValidationError(fmt.Sprintf("invalid login method: %#v", loginMethod))
}
}
if len(user.Filters.DeniedProtocols) >= len(ValidProtocols) {
return &ValidationError{err: "invalid denied_protocols"}
return utils.NewValidationError("invalid denied_protocols")
}
for _, p := range user.Filters.DeniedProtocols {
if !utils.IsStringInSlice(p, ValidProtocols) {
return &ValidationError{err: fmt.Sprintf("invalid protocol: %#v", p)}
return utils.NewValidationError(fmt.Sprintf("invalid protocol: %#v", p))
}
}
if user.Filters.TLSUsername != "" {
if !utils.IsStringInSlice(string(user.Filters.TLSUsername), validTLSUsernames) {
return &ValidationError{err: fmt.Sprintf("invalid TLS username: %#v", user.Filters.TLSUsername)}
return utils.NewValidationError(fmt.Sprintf("invalid TLS username: %#v", user.Filters.TLSUsername))
}
}
for _, opts := range user.Filters.WebClient {
if !utils.IsStringInSlice(opts, WebClientOptions) {
return &ValidationError{err: fmt.Sprintf("invalid web client options %#v", opts)}
return utils.NewValidationError(fmt.Sprintf("invalid web client options %#v", opts))
}
}
return validateFiltersPatternExtensions(user)
}
func saveGCSCredentials(fsConfig *vfs.Filesystem, helper fsValidatorHelper) error {
func saveGCSCredentials(fsConfig *vfs.Filesystem, helper vfs.ValidatorHelper) error {
if fsConfig.Provider != vfs.GCSFilesystemProvider {
return nil
}
@ -1418,7 +1396,7 @@ func saveGCSCredentials(fsConfig *vfs.Filesystem, helper fsValidatorHelper) erro
}
if config.PreferDatabaseCredentials {
if fsConfig.GCSConfig.Credentials.IsPlain() {
fsConfig.GCSConfig.Credentials.SetAdditionalData(helper.GetEncrytionAdditionalData())
fsConfig.GCSConfig.Credentials.SetAdditionalData(helper.GetEncryptionAdditionalData())
err := fsConfig.GCSConfig.Credentials.Encrypt()
if err != nil {
return err
@ -1427,113 +1405,45 @@ func saveGCSCredentials(fsConfig *vfs.Filesystem, helper fsValidatorHelper) erro
return nil
}
if fsConfig.GCSConfig.Credentials.IsPlain() {
fsConfig.GCSConfig.Credentials.SetAdditionalData(helper.GetEncrytionAdditionalData())
fsConfig.GCSConfig.Credentials.SetAdditionalData(helper.GetEncryptionAdditionalData())
err := fsConfig.GCSConfig.Credentials.Encrypt()
if err != nil {
return &ValidationError{err: fmt.Sprintf("could not encrypt GCS credentials: %v", err)}
return utils.NewValidationError(fmt.Sprintf("could not encrypt GCS credentials: %v", err))
}
}
creds, err := json.Marshal(fsConfig.GCSConfig.Credentials)
if err != nil {
return &ValidationError{err: fmt.Sprintf("could not marshal GCS credentials: %v", err)}
return utils.NewValidationError(fmt.Sprintf("could not marshal GCS credentials: %v", err))
}
credentialsFilePath := helper.GetGCSCredentialsFilePath()
err = os.MkdirAll(filepath.Dir(credentialsFilePath), 0700)
if err != nil {
return &ValidationError{err: fmt.Sprintf("could not create GCS credentials dir: %v", err)}
return utils.NewValidationError(fmt.Sprintf("could not create GCS credentials dir: %v", err))
}
err = os.WriteFile(credentialsFilePath, creds, 0600)
if err != nil {
return &ValidationError{err: fmt.Sprintf("could not save GCS credentials: %v", err)}
return utils.NewValidationError(fmt.Sprintf("could not save GCS credentials: %v", err))
}
fsConfig.GCSConfig.Credentials = kms.NewEmptySecret()
return nil
}
func validateFilesystemConfig(fsConfig *vfs.Filesystem, helper fsValidatorHelper) error {
if fsConfig.Provider == vfs.S3FilesystemProvider {
if err := fsConfig.S3Config.Validate(); err != nil {
return &ValidationError{err: fmt.Sprintf("could not validate s3config: %v", err)}
}
if err := fsConfig.S3Config.EncryptCredentials(helper.GetEncrytionAdditionalData()); err != nil {
return &ValidationError{err: fmt.Sprintf("could not encrypt s3 access secret: %v", err)}
}
fsConfig.GCSConfig = vfs.GCSFsConfig{}
fsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
fsConfig.CryptConfig = vfs.CryptFsConfig{}
fsConfig.SFTPConfig = vfs.SFTPFsConfig{}
return nil
} else if fsConfig.Provider == vfs.GCSFilesystemProvider {
if err := fsConfig.GCSConfig.Validate(helper.GetGCSCredentialsFilePath()); err != nil {
return &ValidationError{err: fmt.Sprintf("could not validate GCS config: %v", err)}
}
fsConfig.S3Config = vfs.S3FsConfig{}
fsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
fsConfig.CryptConfig = vfs.CryptFsConfig{}
fsConfig.SFTPConfig = vfs.SFTPFsConfig{}
return nil
} else if fsConfig.Provider == vfs.AzureBlobFilesystemProvider {
if err := fsConfig.AzBlobConfig.Validate(); err != nil {
return &ValidationError{err: fmt.Sprintf("could not validate Azure Blob config: %v", err)}
}
if err := fsConfig.AzBlobConfig.EncryptCredentials(helper.GetEncrytionAdditionalData()); err != nil {
return &ValidationError{err: fmt.Sprintf("could not encrypt Azure blob account key: %v", err)}
}
fsConfig.S3Config = vfs.S3FsConfig{}
fsConfig.GCSConfig = vfs.GCSFsConfig{}
fsConfig.CryptConfig = vfs.CryptFsConfig{}
fsConfig.SFTPConfig = vfs.SFTPFsConfig{}
return nil
} else if fsConfig.Provider == vfs.CryptedFilesystemProvider {
if err := fsConfig.CryptConfig.Validate(); err != nil {
return &ValidationError{err: fmt.Sprintf("could not validate Crypt fs config: %v", err)}
}
if err := fsConfig.CryptConfig.EncryptCredentials(helper.GetEncrytionAdditionalData()); err != nil {
return &ValidationError{err: fmt.Sprintf("could not encrypt Crypt fs passphrase: %v", err)}
}
fsConfig.S3Config = vfs.S3FsConfig{}
fsConfig.GCSConfig = vfs.GCSFsConfig{}
fsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
fsConfig.SFTPConfig = vfs.SFTPFsConfig{}
return nil
} else if fsConfig.Provider == vfs.SFTPFilesystemProvider {
if err := fsConfig.SFTPConfig.Validate(); err != nil {
return &ValidationError{err: fmt.Sprintf("could not validate SFTP fs config: %v", err)}
}
if err := fsConfig.SFTPConfig.EncryptCredentials(helper.GetEncrytionAdditionalData()); err != nil {
return &ValidationError{err: fmt.Sprintf("could not encrypt SFTP fs credentials: %v", err)}
}
fsConfig.S3Config = vfs.S3FsConfig{}
fsConfig.GCSConfig = vfs.GCSFsConfig{}
fsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
fsConfig.CryptConfig = vfs.CryptFsConfig{}
return nil
}
fsConfig.Provider = vfs.LocalFilesystemProvider
fsConfig.S3Config = vfs.S3FsConfig{}
fsConfig.GCSConfig = vfs.GCSFsConfig{}
fsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
fsConfig.CryptConfig = vfs.CryptFsConfig{}
fsConfig.SFTPConfig = vfs.SFTPFsConfig{}
return nil
}
func validateBaseParams(user *User) error {
if user.Username == "" {
return &ValidationError{err: "username is mandatory"}
return utils.NewValidationError("username is mandatory")
}
if !config.SkipNaturalKeysValidation && !usernameRegex.MatchString(user.Username) {
return &ValidationError{err: fmt.Sprintf("username %#v is not valid, the following characters are allowed: a-zA-Z0-9-_.~",
user.Username)}
return utils.NewValidationError(fmt.Sprintf("username %#v is not valid, the following characters are allowed: a-zA-Z0-9-_.~",
user.Username))
}
if user.HomeDir == "" {
return &ValidationError{err: "home_dir is mandatory"}
return utils.NewValidationError("home_dir is mandatory")
}
if user.Password == "" && len(user.PublicKeys) == 0 {
return &ValidationError{err: "please set a password or at least a public_key"}
return utils.NewValidationError("please set a password or at least a public_key")
}
if !filepath.IsAbs(user.HomeDir) {
return &ValidationError{err: fmt.Sprintf("home_dir must be an absolute path, actual value: %v", user.HomeDir)}
return utils.NewValidationError(fmt.Sprintf("home_dir must be an absolute path, actual value: %v", user.HomeDir))
}
return nil
}
@ -1561,24 +1471,24 @@ func createUserPasswordHash(user *User) error {
// FIXME: this should be defined as Folder struct method
func ValidateFolder(folder *vfs.BaseVirtualFolder) error {
if folder.Name == "" {
return &ValidationError{err: "folder name is mandatory"}
return utils.NewValidationError("folder name is mandatory")
}
if !config.SkipNaturalKeysValidation && !usernameRegex.MatchString(folder.Name) {
return &ValidationError{err: fmt.Sprintf("folder name %#v is not valid, the following characters are allowed: a-zA-Z0-9-_.~",
folder.Name)}
return utils.NewValidationError(fmt.Sprintf("folder name %#v is not valid, the following characters are allowed: a-zA-Z0-9-_.~",
folder.Name))
}
if folder.FsConfig.Provider == vfs.LocalFilesystemProvider || folder.FsConfig.Provider == vfs.CryptedFilesystemProvider ||
folder.MappedPath != "" {
cleanedMPath := filepath.Clean(folder.MappedPath)
if !filepath.IsAbs(cleanedMPath) {
return &ValidationError{err: fmt.Sprintf("invalid folder mapped path %#v", folder.MappedPath)}
return utils.NewValidationError(fmt.Sprintf("invalid folder mapped path %#v", folder.MappedPath))
}
folder.MappedPath = cleanedMPath
}
if folder.HasRedactedSecret() {
return errors.New("cannot save a folder with a redacted secret")
}
if err := validateFilesystemConfig(&folder.FsConfig, folder); err != nil {
if err := folder.FsConfig.Validate(folder); err != nil {
return err
}
return saveGCSCredentials(&folder.FsConfig, folder)
@ -1598,14 +1508,14 @@ func ValidateUser(user *User) error {
if user.hasRedactedSecret() {
return errors.New("cannot save a user with a redacted secret")
}
if err := validateFilesystemConfig(&user.FsConfig, user); err != nil {
if err := user.FsConfig.Validate(user); err != nil {
return err
}
if err := validateUserVirtualFolders(user); err != nil {
return err
}
if user.Status < 0 || user.Status > 1 {
return &ValidationError{err: fmt.Sprintf("invalid user status: %v", user.Status)}
return utils.NewValidationError(fmt.Sprintf("invalid user status: %v", user.Status))
}
if err := createUserPasswordHash(user); err != nil {
return err

View file

@ -342,20 +342,7 @@ func (u *User) isFsEqual(other *User) bool {
// hideConfidentialData hides user confidential data
func (u *User) hideConfidentialData() {
u.Password = ""
switch u.FsConfig.Provider {
case vfs.S3FilesystemProvider:
u.FsConfig.S3Config.AccessSecret.Hide()
case vfs.GCSFilesystemProvider:
u.FsConfig.GCSConfig.Credentials.Hide()
case vfs.AzureBlobFilesystemProvider:
u.FsConfig.AzBlobConfig.AccountKey.Hide()
u.FsConfig.AzBlobConfig.SASURL.Hide()
case vfs.CryptedFilesystemProvider:
u.FsConfig.CryptConfig.Passphrase.Hide()
case vfs.SFTPFilesystemProvider:
u.FsConfig.SFTPConfig.Password.Hide()
u.FsConfig.SFTPConfig.PrivateKey.Hide()
}
u.FsConfig.HideConfidentialData()
}
// GetSubDirPermissions returns permissions for sub directories
@ -387,33 +374,8 @@ func (u *User) PrepareForRendering() {
}
func (u *User) hasRedactedSecret() bool {
switch u.FsConfig.Provider {
case vfs.S3FilesystemProvider:
if u.FsConfig.S3Config.AccessSecret.IsRedacted() {
return true
}
case vfs.GCSFilesystemProvider:
if u.FsConfig.GCSConfig.Credentials.IsRedacted() {
return true
}
case vfs.AzureBlobFilesystemProvider:
if u.FsConfig.AzBlobConfig.AccountKey.IsRedacted() {
return true
}
if u.FsConfig.AzBlobConfig.SASURL.IsRedacted() {
return true
}
case vfs.CryptedFilesystemProvider:
if u.FsConfig.CryptConfig.Passphrase.IsRedacted() {
return true
}
case vfs.SFTPFilesystemProvider:
if u.FsConfig.SFTPConfig.Password.IsRedacted() {
return true
}
if u.FsConfig.SFTPConfig.PrivateKey.IsRedacted() {
return true
}
if u.FsConfig.HasRedactedSecret() {
return true
}
for idx := range u.VirtualFolders {
@ -1189,8 +1151,8 @@ func (u *User) getNotificationFieldsAsSlice(action string) []string {
}
}
// GetEncrytionAdditionalData returns the additional data to use for AEAD
func (u *User) GetEncrytionAdditionalData() string {
// GetEncryptionAdditionalData returns the additional data to use for AEAD
func (u *User) GetEncryptionAdditionalData() string {
return u.Username
}

View file

@ -9,6 +9,7 @@ import (
"github.com/go-chi/render"
"github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/utils"
)
func getAdmins(w http.ResponseWriter, r *http.Request) {
@ -140,13 +141,13 @@ func changeAdminPassword(w http.ResponseWriter, r *http.Request) {
func doChangeAdminPassword(r *http.Request, currentPassword, newPassword, confirmNewPassword string) error {
if currentPassword == "" || newPassword == "" || confirmNewPassword == "" {
return dataprovider.NewValidationError("please provide the current password and the new one two times")
return utils.NewValidationError("please provide the current password and the new one two times")
}
if newPassword != confirmNewPassword {
return dataprovider.NewValidationError("the two password fields do not match")
return utils.NewValidationError("the two password fields do not match")
}
if currentPassword == newPassword {
return dataprovider.NewValidationError("the new password must be different from the current one")
return utils.NewValidationError("the new password must be different from the current one")
}
claims, err := getTokenClaims(r)
if err != nil {
@ -158,7 +159,7 @@ func doChangeAdminPassword(r *http.Request, currentPassword, newPassword, confir
}
match, err := admin.CheckPassword(currentPassword)
if !match || err != nil {
return dataprovider.NewValidationError("current password does not match")
return utils.NewValidationError("current password does not match")
}
admin.Password = newPassword

View file

@ -215,13 +215,13 @@ func changeUserPassword(w http.ResponseWriter, r *http.Request) {
func doChangeUserPassword(r *http.Request, currentPassword, newPassword, confirmNewPassword string) error {
if currentPassword == "" || newPassword == "" || confirmNewPassword == "" {
return dataprovider.NewValidationError("please provide the current password and the new one two times")
return utils.NewValidationError("please provide the current password and the new one two times")
}
if newPassword != confirmNewPassword {
return dataprovider.NewValidationError("the two password fields do not match")
return utils.NewValidationError("the two password fields do not match")
}
if currentPassword == newPassword {
return dataprovider.NewValidationError("the new password must be different from the current one")
return utils.NewValidationError("the new password must be different from the current one")
}
claims, err := getTokenClaims(r)
if err != nil || claims.Username == "" {
@ -230,7 +230,7 @@ func doChangeUserPassword(r *http.Request, currentPassword, newPassword, confirm
user, err := dataprovider.CheckUserAndPass(claims.Username, currentPassword, utils.GetIPFromRemoteAddress(r.RemoteAddr),
common.ProtocolHTTP)
if err != nil {
return dataprovider.NewValidationError("current password does not match")
return utils.NewValidationError("current password does not match")
}
user.Password = newPassword

View file

@ -16,6 +16,7 @@ import (
"github.com/drakkan/sftpgo/common"
"github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/utils"
"github.com/drakkan/sftpgo/vfs"
)
@ -104,7 +105,7 @@ func loadDataFromRequest(w http.ResponseWriter, r *http.Request) {
content, err := io.ReadAll(r.Body)
if err != nil || len(content) == 0 {
if len(content) == 0 {
err = dataprovider.NewValidationError("request body is required")
err = utils.NewValidationError("request body is required")
}
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
@ -150,7 +151,7 @@ func loadData(w http.ResponseWriter, r *http.Request) {
func restoreBackup(content []byte, inputFile string, scanQuota, mode int) error {
dump, err := dataprovider.ParseDumpData(content)
if err != nil {
return dataprovider.NewValidationError(fmt.Sprintf("Unable to parse backup content: %v", err))
return utils.NewValidationError(fmt.Sprintf("Unable to parse backup content: %v", err))
}
if err = RestoreFolders(dump.Folders, inputFile, mode, scanQuota); err != nil {

View file

@ -42,7 +42,7 @@ func sendAPIResponse(w http.ResponseWriter, r *http.Request, err error, message
}
func getRespStatus(err error) int {
if _, ok := err.(*dataprovider.ValidationError); ok {
if _, ok := err.(*utils.ValidationError); ok {
return http.StatusBadRequest
}
if _, ok := err.(*dataprovider.MethodDisabledError); ok {

20
utils/errors.go Normal file
View file

@ -0,0 +1,20 @@
package utils
import "fmt"
// ValidationError raised if input data is not valid
type ValidationError struct {
err string
}
// Validation error details
func (e *ValidationError) Error() string {
return fmt.Sprintf("Validation error: %s", e.err)
}
// NewValidationError returns a validation errors
func NewValidationError(error string) *ValidationError {
return &ValidationError{
err: error,
}
}

View file

@ -1,6 +1,11 @@
package vfs
import "github.com/drakkan/sftpgo/kms"
import (
"fmt"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/utils"
)
// FilesystemProvider defines the supported storage filesystems
type FilesystemProvider int
@ -15,6 +20,13 @@ const (
SFTPFilesystemProvider // SFTP
)
// ValidatorHelper implements methods we need for Filesystem.ValidateConfig.
// It is implemented by vfs.Folder and dataprovider.User
type ValidatorHelper interface {
GetGCSCredentialsFilePath() string
GetEncryptionAdditionalData() string
}
// Filesystem defines cloud storage filesystem details
type Filesystem struct {
RedactedSecret string `json:"-"`
@ -99,6 +111,128 @@ func (f *Filesystem) IsEqual(other *Filesystem) bool {
}
}
// Validate verifies the FsConfig matching the configured provider and sets all other
// Filesystem.*Config to their zero value if successful
func (f *Filesystem) Validate(helper ValidatorHelper) error {
switch f.Provider {
case S3FilesystemProvider:
if err := f.S3Config.Validate(); err != nil {
return utils.NewValidationError(fmt.Sprintf("could not validate s3config: %v", err))
}
if err := f.S3Config.EncryptCredentials(helper.GetEncryptionAdditionalData()); err != nil {
return utils.NewValidationError(fmt.Sprintf("could not encrypt s3 access secret: %v", err))
}
f.GCSConfig = GCSFsConfig{}
f.AzBlobConfig = AzBlobFsConfig{}
f.CryptConfig = CryptFsConfig{}
f.SFTPConfig = SFTPFsConfig{}
return nil
case GCSFilesystemProvider:
if err := f.GCSConfig.Validate(helper.GetGCSCredentialsFilePath()); err != nil {
return utils.NewValidationError(fmt.Sprintf("could not validate GCS config: %v", err))
}
f.S3Config = S3FsConfig{}
f.AzBlobConfig = AzBlobFsConfig{}
f.CryptConfig = CryptFsConfig{}
f.SFTPConfig = SFTPFsConfig{}
return nil
case AzureBlobFilesystemProvider:
if err := f.AzBlobConfig.Validate(); err != nil {
return utils.NewValidationError(fmt.Sprintf("could not validate Azure Blob config: %v", err))
}
if err := f.AzBlobConfig.EncryptCredentials(helper.GetEncryptionAdditionalData()); err != nil {
return utils.NewValidationError(fmt.Sprintf("could not encrypt Azure blob account key: %v", err))
}
f.S3Config = S3FsConfig{}
f.GCSConfig = GCSFsConfig{}
f.CryptConfig = CryptFsConfig{}
f.SFTPConfig = SFTPFsConfig{}
return nil
case CryptedFilesystemProvider:
if err := f.CryptConfig.Validate(); err != nil {
return utils.NewValidationError(fmt.Sprintf("could not validate Crypt fs config: %v", err))
}
if err := f.CryptConfig.EncryptCredentials(helper.GetEncryptionAdditionalData()); err != nil {
return utils.NewValidationError(fmt.Sprintf("could not encrypt Crypt fs passphrase: %v", err))
}
f.S3Config = S3FsConfig{}
f.GCSConfig = GCSFsConfig{}
f.AzBlobConfig = AzBlobFsConfig{}
f.SFTPConfig = SFTPFsConfig{}
return nil
case SFTPFilesystemProvider:
if err := f.SFTPConfig.Validate(); err != nil {
return utils.NewValidationError(fmt.Sprintf("could not validate SFTP fs config: %v", err))
}
if err := f.SFTPConfig.EncryptCredentials(helper.GetEncryptionAdditionalData()); err != nil {
return utils.NewValidationError(fmt.Sprintf("could not encrypt SFTP fs credentials: %v", err))
}
f.S3Config = S3FsConfig{}
f.GCSConfig = GCSFsConfig{}
f.AzBlobConfig = AzBlobFsConfig{}
f.CryptConfig = CryptFsConfig{}
return nil
default:
f.Provider = LocalFilesystemProvider
f.S3Config = S3FsConfig{}
f.GCSConfig = GCSFsConfig{}
f.AzBlobConfig = AzBlobFsConfig{}
f.CryptConfig = CryptFsConfig{}
f.SFTPConfig = SFTPFsConfig{}
return nil
}
}
// HasRedactedSecret returns true if configured the filesystem configuration has a redacted secret
func (f *Filesystem) HasRedactedSecret() bool {
// TODO move vfs specific code into each *FsConfig struct
switch f.Provider {
case S3FilesystemProvider:
if f.S3Config.AccessSecret.IsRedacted() {
return true
}
case GCSFilesystemProvider:
if f.GCSConfig.Credentials.IsRedacted() {
return true
}
case AzureBlobFilesystemProvider:
if f.AzBlobConfig.AccountKey.IsRedacted() {
return true
}
case CryptedFilesystemProvider:
if f.CryptConfig.Passphrase.IsRedacted() {
return true
}
case SFTPFilesystemProvider:
if f.SFTPConfig.Password.IsRedacted() {
return true
}
if f.SFTPConfig.PrivateKey.IsRedacted() {
return true
}
}
return false
}
// HideConfidentialData hides filesystem confidential data
func (f *Filesystem) HideConfidentialData() {
switch f.Provider {
case S3FilesystemProvider:
f.S3Config.AccessSecret.Hide()
case GCSFilesystemProvider:
f.GCSConfig.Credentials.Hide()
case AzureBlobFilesystemProvider:
f.AzBlobConfig.AccountKey.Hide()
f.AzBlobConfig.SASURL.Hide()
case CryptedFilesystemProvider:
f.CryptConfig.Passphrase.Hide()
case SFTPFilesystemProvider:
f.SFTPConfig.Password.Hide()
f.SFTPConfig.PrivateKey.Hide()
}
}
// GetACopy returns a copy
func (f *Filesystem) GetACopy() Filesystem {
f.SetEmptySecretsIfNil()

View file

@ -28,8 +28,8 @@ type BaseVirtualFolder struct {
FsConfig Filesystem `json:"filesystem"`
}
// GetEncrytionAdditionalData returns the additional data to use for AEAD
func (v *BaseVirtualFolder) GetEncrytionAdditionalData() string {
// GetEncryptionAdditionalData returns the additional data to use for AEAD
func (v *BaseVirtualFolder) GetEncryptionAdditionalData() string {
return fmt.Sprintf("folder_%v", v.Name)
}