mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-21 23:20:24 +00:00
move Filesystem config validation to vfs
This commit is contained in:
parent
f2f612b450
commit
f19937b715
10 changed files with 234 additions and 206 deletions
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
20
utils/errors.go
Normal 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,
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue