normalize common database errors

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2024-01-17 17:36:35 +01:00
parent 5ac99ee556
commit 87451560e3
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
13 changed files with 200 additions and 68 deletions

14
go.mod
View file

@ -10,10 +10,10 @@ require (
github.com/alexedwards/argon2id v1.0.0
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964
github.com/aws/aws-sdk-go-v2 v1.24.1
github.com/aws/aws-sdk-go-v2/config v1.26.3
github.com/aws/aws-sdk-go-v2/credentials v1.16.14
github.com/aws/aws-sdk-go-v2/config v1.26.4
github.com/aws/aws-sdk-go-v2/credentials v1.16.15
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.12
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.19.6
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.2
@ -95,7 +95,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
github.com/aws/smithy-go v1.19.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
@ -171,9 +171,9 @@ require (
golang.org/x/tools v0.17.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect
google.golang.org/grpc v1.60.1 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect

28
go.sum
View file

@ -37,14 +37,14 @@ github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
github.com/aws/aws-sdk-go-v2/config v1.26.3 h1:dKuc2jdp10y13dEEvPqWxqLoc0vF3Z9FC45MvuQSxOA=
github.com/aws/aws-sdk-go-v2/config v1.26.3/go.mod h1:Bxgi+DeeswYofcYO0XyGClwlrq3DZEXli0kLf4hkGA0=
github.com/aws/aws-sdk-go-v2/credentials v1.16.14 h1:mMDTwwYO9A0/JbOCOG7EOZHtYM+o7OfGWfu0toa23VE=
github.com/aws/aws-sdk-go-v2/credentials v1.16.14/go.mod h1:cniAUh3ErQPHtCQGPT5ouvSAQ0od8caTO9OOuufZOAE=
github.com/aws/aws-sdk-go-v2/config v1.26.4 h1:Juj7LhtxNudNUlfX22K5AnLafO+v4eq9PA3VWSCIQs4=
github.com/aws/aws-sdk-go-v2/config v1.26.4/go.mod h1:tioqQ7wvxMYnTDpoTTLHhV3Zh+z261i/f2oz+ds8eNI=
github.com/aws/aws-sdk-go-v2/credentials v1.16.15 h1:P0/m1LU08MF2kRzx4P//+7lNjiJod1z4xI2WpWhdpTQ=
github.com/aws/aws-sdk-go-v2/credentials v1.16.15/go.mod h1:pgtMCf7Dx4GWw5EpHOTc2Sy17LIP0A0N2C9nQ83pQ/0=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11 h1:I6lAa3wBWfCz/cKkOpAcumsETRkFAl70sWi8ItcMEsM=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11/go.mod h1:be1NIO30kJA23ORBLqPo1LttEM6tPNSEcjkd1eKzNW0=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.12 h1:0FMZy36RSYvcvVzEf1xbNdebLHZewW40QWP+P8jCMVk=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.12/go.mod h1:+chyahvarkb3HibkNei9IQEM9P5cWD5w2kgXCa3Hh0I=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
@ -69,8 +69,8 @@ github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.2 h1:A5sGOT/mukuU+4At1
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.2/go.mod h1:qutL00aW8GSo2D0I6UEOqMvRS3ZyuBrOC1BLe5D2jPc=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 h1:dGrs+Q/WzhsiUKh82SfTVN66QzyulXuMDTV/G8ZxOac=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.6/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 h1:Yf2MIo9x+0tyv76GljxzqA3WtC5mw7NmazD2chwjxE4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
@ -531,12 +531,12 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1 h1:/IWabOtPziuXTEtI1KYCpM6Ss7vaAkeMxk+uXV/xvZs=
google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k=
google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 h1:OPXtXn7fNMaXwO3JvOmF1QyTc00jsSFFz1vXXBOdCDo=
google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg=
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k=
google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac h1:OZkkudMUu9LVQMCoRUbI/1p5VCo9BOrlvkqMvWtqa6s=
google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=

View file

@ -387,7 +387,10 @@ func (p *BoltProvider) addAdmin(admin *Admin) error {
return err
}
if a := bucket.Get([]byte(admin.Username)); a != nil {
return fmt.Errorf("admin %q already exists", admin.Username)
return util.NewI18nError(
fmt.Errorf("%w: admin %q already exists", ErrDuplicatedKey, admin.Username),
util.I18nErrorDuplicatedUsername,
)
}
id, err := bucket.NextSequence()
if err != nil {
@ -649,7 +652,10 @@ func (p *BoltProvider) addUser(user *User) error {
return err
}
if u := bucket.Get([]byte(user.Username)); u != nil {
return fmt.Errorf("username %v already exists", user.Username)
return util.NewI18nError(
fmt.Errorf("%w: username %v already exists", ErrDuplicatedKey, user.Username),
util.I18nErrorDuplicatedUsername,
)
}
id, err := bucket.NextSequence()
if err != nil {
@ -1121,7 +1127,10 @@ func (p *BoltProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
return err
}
if f := bucket.Get([]byte(folder.Name)); f != nil {
return fmt.Errorf("folder %v already exists", folder.Name)
return util.NewI18nError(
fmt.Errorf("%w: folder %q already exists", ErrDuplicatedKey, folder.Name),
util.I18nErrorDuplicatedUsername,
)
}
folder.Users = nil
folder.Groups = nil
@ -1435,7 +1444,10 @@ func (p *BoltProvider) addGroup(group *Group) error {
return err
}
if u := bucket.Get([]byte(group.Name)); u != nil {
return fmt.Errorf("group %v already exists", group.Name)
return util.NewI18nError(
fmt.Errorf("%w: group %q already exists", ErrDuplicatedKey, group.Name),
util.I18nErrorDuplicatedUsername,
)
}
id, err := bucket.NextSequence()
if err != nil {
@ -1808,7 +1820,7 @@ func (p *BoltProvider) addShare(share *Share) error {
return err
}
if a := bucket.Get([]byte(share.ShareID)); a != nil {
return fmt.Errorf("share %v already exists", share.ShareID)
return fmt.Errorf("share %q already exists", share.ShareID)
}
id, err := bucket.NextSequence()
if err != nil {
@ -2188,7 +2200,10 @@ func (p *BoltProvider) addEventAction(action *BaseEventAction) error {
return err
}
if a := bucket.Get([]byte(action.Name)); a != nil {
return fmt.Errorf("event action %s already exists", action.Name)
return util.NewI18nError(
fmt.Errorf("%w: event action %q already exists", ErrDuplicatedKey, action.Name),
util.I18nErrorDuplicatedName,
)
}
id, err := bucket.NextSequence()
if err != nil {
@ -2449,7 +2464,10 @@ func (p *BoltProvider) addEventRule(rule *EventRule) error {
return err
}
if r := bucket.Get([]byte(rule.Name)); r != nil {
return fmt.Errorf("event rule %q already exists", rule.Name)
return util.NewI18nError(
fmt.Errorf("%w: event rule %q already exists", ErrDuplicatedKey, rule.Name),
util.I18nErrorDuplicatedName,
)
}
id, err := bucket.NextSequence()
if err != nil {
@ -2618,7 +2636,10 @@ func (p *BoltProvider) addRole(role *Role) error {
return err
}
if r := bucket.Get([]byte(role.Name)); r != nil {
return fmt.Errorf("role %q already exists", role.Name)
return util.NewI18nError(
fmt.Errorf("%w: role %q already exists", ErrDuplicatedKey, role.Name),
util.I18nErrorDuplicatedName,
)
}
id, err := bucket.NextSequence()
if err != nil {

View file

@ -148,6 +148,11 @@ const (
DumpScopeConfigs = "configs"
)
const (
fieldUsername = 1
fieldName = 2
)
var (
// SupportedProviders defines the supported data providers
SupportedProviders = []string{SQLiteDataProviderName, PGSQLDataProviderName, MySQLDataProviderName,
@ -176,13 +181,17 @@ var (
ErrInvalidCredentials = errors.New("invalid credentials")
// ErrLoginNotAllowedFromIP defines the error to return if login is denied from the current IP
ErrLoginNotAllowedFromIP = errors.New("login is not allowed from this IP")
isAdminCreated atomic.Bool
validTLSUsernames = []string{string(sdk.TLSUsernameNone), string(sdk.TLSUsernameCN)}
config Config
provider Provider
sqlPlaceholders []string
internalHashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix}
hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix,
// ErrDuplicatedKey occurs when there is a unique key constraint violation
ErrDuplicatedKey = errors.New("duplicated key not allowed")
// ErrForeignKeyViolated occurs when there is a foreign key constraint violation
ErrForeignKeyViolated = errors.New("violates foreign key constraint")
isAdminCreated atomic.Bool
validTLSUsernames = []string{string(sdk.TLSUsernameNone), string(sdk.TLSUsernameCN)}
config Config
provider Provider
sqlPlaceholders []string
internalHashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix}
hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix,
pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix, md5cryptPwdPrefix, md5cryptApr1PwdPrefix, md5DigestPwdPrefix,
sha256DigestPwdPrefix, sha512DigestPwdPrefix, sha256cryptPwdPrefix, sha512cryptPwdPrefix, yescryptPwdPrefix}
pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix}

View file

@ -332,7 +332,10 @@ func (p *MemoryProvider) addUser(user *User) error {
_, err = p.userExistsInternal(user.Username)
if err == nil {
return fmt.Errorf("username %q already exists", user.Username)
return util.NewI18nError(
fmt.Errorf("%w: username %v already exists", ErrDuplicatedKey, user.Username),
util.I18nErrorDuplicatedUsername,
)
}
user.ID = p.getNextID()
user.LastQuotaUpdate = 0
@ -730,7 +733,10 @@ func (p *MemoryProvider) addAdmin(admin *Admin) error {
}
_, err = p.adminExistsInternal(admin.Username)
if err == nil {
return fmt.Errorf("admin %q already exists", admin.Username)
return util.NewI18nError(
fmt.Errorf("%w: admin %q already exists", ErrDuplicatedKey, admin.Username),
util.I18nErrorDuplicatedUsername,
)
}
admin.ID = p.getNextAdminID()
admin.CreatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
@ -1041,7 +1047,10 @@ func (p *MemoryProvider) addGroup(group *Group) error {
_, err := p.groupExistsInternal(group.Name)
if err == nil {
return fmt.Errorf("group %q already exists", group.Name)
return util.NewI18nError(
fmt.Errorf("%w: group %q already exists", ErrDuplicatedKey, group.Name),
util.I18nErrorDuplicatedUsername,
)
}
group.ID = p.getNextGroupID()
group.CreatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
@ -1512,7 +1521,10 @@ func (p *MemoryProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
_, err = p.folderExistsInternal(folder.Name)
if err == nil {
return fmt.Errorf("folder %q already exists", folder.Name)
return util.NewI18nError(
fmt.Errorf("%w: folder %q already exists", ErrDuplicatedKey, folder.Name),
util.I18nErrorDuplicatedUsername,
)
}
folder.ID = p.getNextFolderID()
folder.Users = nil
@ -2172,7 +2184,10 @@ func (p *MemoryProvider) addEventAction(action *BaseEventAction) error {
}
_, err = p.actionExistsInternal(action.Name)
if err == nil {
return fmt.Errorf("event action %q already exists", action.Name)
return util.NewI18nError(
fmt.Errorf("%w: event action %q already exists", ErrDuplicatedKey, action.Name),
util.I18nErrorDuplicatedName,
)
}
action.ID = p.getNextActionID()
action.Rules = nil
@ -2348,7 +2363,10 @@ func (p *MemoryProvider) addEventRule(rule *EventRule) error {
}
_, err := p.ruleExistsInternal(rule.Name)
if err == nil {
return fmt.Errorf("event rule %q already exists", rule.Name)
return util.NewI18nError(
fmt.Errorf("%w: event rule %q already exists", ErrDuplicatedKey, rule.Name),
util.I18nErrorDuplicatedName,
)
}
rule.ID = p.getNextRuleID()
rule.CreatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
@ -2499,7 +2517,10 @@ func (p *MemoryProvider) addRole(role *Role) error {
_, err := p.roleExistsInternal(role.Name)
if err == nil {
return fmt.Errorf("role %q already exists", role.Name)
return util.NewI18nError(
fmt.Errorf("%w: role %q already exists", ErrDuplicatedKey, role.Name),
util.I18nErrorDuplicatedName,
)
}
role.ID = p.getNextRoleID()
role.CreatedAt = util.GetTimeAsMsSinceEpoch(time.Now())

View file

@ -32,6 +32,7 @@ import (
"github.com/go-sql-driver/mysql"
"github.com/drakkan/sftpgo/v2/internal/logger"
"github.com/drakkan/sftpgo/v2/internal/util"
"github.com/drakkan/sftpgo/v2/internal/version"
"github.com/drakkan/sftpgo/v2/internal/vfs"
)
@ -341,7 +342,7 @@ func (p *MySQLProvider) userExists(username, role string) (User, error) {
}
func (p *MySQLProvider) addUser(user *User) error {
return sqlCommonAddUser(user, p.dbHandle)
return p.normalizeError(sqlCommonAddUser(user, p.dbHandle), fieldUsername)
}
func (p *MySQLProvider) updateUser(user *User) error {
@ -387,7 +388,7 @@ func (p *MySQLProvider) getFolderByName(name string) (vfs.BaseVirtualFolder, err
}
func (p *MySQLProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
return sqlCommonAddFolder(folder, p.dbHandle)
return p.normalizeError(sqlCommonAddFolder(folder, p.dbHandle), fieldName)
}
func (p *MySQLProvider) updateFolder(folder *vfs.BaseVirtualFolder) error {
@ -423,7 +424,7 @@ func (p *MySQLProvider) groupExists(name string) (Group, error) {
}
func (p *MySQLProvider) addGroup(group *Group) error {
return sqlCommonAddGroup(group, p.dbHandle)
return p.normalizeError(sqlCommonAddGroup(group, p.dbHandle), fieldName)
}
func (p *MySQLProvider) updateGroup(group *Group) error {
@ -443,7 +444,7 @@ func (p *MySQLProvider) adminExists(username string) (Admin, error) {
}
func (p *MySQLProvider) addAdmin(admin *Admin) error {
return sqlCommonAddAdmin(admin, p.dbHandle)
return p.normalizeError(sqlCommonAddAdmin(admin, p.dbHandle), fieldUsername)
}
func (p *MySQLProvider) updateAdmin(admin *Admin) error {
@ -603,7 +604,7 @@ func (p *MySQLProvider) eventActionExists(name string) (BaseEventAction, error)
}
func (p *MySQLProvider) addEventAction(action *BaseEventAction) error {
return sqlCommonAddEventAction(action, p.dbHandle)
return p.normalizeError(sqlCommonAddEventAction(action, p.dbHandle), fieldName)
}
func (p *MySQLProvider) updateEventAction(action *BaseEventAction) error {
@ -631,7 +632,7 @@ func (p *MySQLProvider) eventRuleExists(name string) (EventRule, error) {
}
func (p *MySQLProvider) addEventRule(rule *EventRule) error {
return sqlCommonAddEventRule(rule, p.dbHandle)
return p.normalizeError(sqlCommonAddEventRule(rule, p.dbHandle), fieldName)
}
func (p *MySQLProvider) updateEventRule(rule *EventRule) error {
@ -683,7 +684,7 @@ func (p *MySQLProvider) roleExists(name string) (Role, error) {
}
func (p *MySQLProvider) addRole(role *Role) error {
return sqlCommonAddRole(role, p.dbHandle)
return p.normalizeError(sqlCommonAddRole(role, p.dbHandle), fieldName)
}
func (p *MySQLProvider) updateRole(role *Role) error {
@ -824,3 +825,26 @@ func (p *MySQLProvider) resetDatabase() error {
sql := sqlReplaceAll(mysqlResetSQL)
return sqlCommonExecSQLAndUpdateDBVersion(p.dbHandle, strings.Split(sql, ";"), 0, false)
}
func (p *MySQLProvider) normalizeError(err error, fieldType int) error {
if err == nil {
return nil
}
var mysqlErr *mysql.MySQLError
if errors.As(err, &mysqlErr) {
switch mysqlErr.Number {
case 1062:
message := util.I18nErrorDuplicatedName
if fieldType == fieldUsername {
message = util.I18nErrorDuplicatedUsername
}
return util.NewI18nError(
fmt.Errorf("%w: %s", ErrDuplicatedKey, err.Error()),
message,
)
case 1452:
return fmt.Errorf("%w: %s", ErrForeignKeyViolated, err.Error())
}
}
return err
}

View file

@ -29,9 +29,11 @@ import (
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/stdlib"
"github.com/drakkan/sftpgo/v2/internal/logger"
"github.com/drakkan/sftpgo/v2/internal/util"
"github.com/drakkan/sftpgo/v2/internal/version"
"github.com/drakkan/sftpgo/v2/internal/vfs"
)
@ -353,7 +355,7 @@ func (p *PGSQLProvider) userExists(username, role string) (User, error) {
}
func (p *PGSQLProvider) addUser(user *User) error {
return sqlCommonAddUser(user, p.dbHandle)
return p.normalizeError(sqlCommonAddUser(user, p.dbHandle), fieldUsername)
}
func (p *PGSQLProvider) updateUser(user *User) error {
@ -399,7 +401,7 @@ func (p *PGSQLProvider) getFolderByName(name string) (vfs.BaseVirtualFolder, err
}
func (p *PGSQLProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
return sqlCommonAddFolder(folder, p.dbHandle)
return p.normalizeError(sqlCommonAddFolder(folder, p.dbHandle), fieldName)
}
func (p *PGSQLProvider) updateFolder(folder *vfs.BaseVirtualFolder) error {
@ -435,7 +437,7 @@ func (p *PGSQLProvider) groupExists(name string) (Group, error) {
}
func (p *PGSQLProvider) addGroup(group *Group) error {
return sqlCommonAddGroup(group, p.dbHandle)
return p.normalizeError(sqlCommonAddGroup(group, p.dbHandle), fieldName)
}
func (p *PGSQLProvider) updateGroup(group *Group) error {
@ -455,7 +457,7 @@ func (p *PGSQLProvider) adminExists(username string) (Admin, error) {
}
func (p *PGSQLProvider) addAdmin(admin *Admin) error {
return sqlCommonAddAdmin(admin, p.dbHandle)
return p.normalizeError(sqlCommonAddAdmin(admin, p.dbHandle), fieldUsername)
}
func (p *PGSQLProvider) updateAdmin(admin *Admin) error {
@ -615,7 +617,7 @@ func (p *PGSQLProvider) eventActionExists(name string) (BaseEventAction, error)
}
func (p *PGSQLProvider) addEventAction(action *BaseEventAction) error {
return sqlCommonAddEventAction(action, p.dbHandle)
return p.normalizeError(sqlCommonAddEventAction(action, p.dbHandle), fieldName)
}
func (p *PGSQLProvider) updateEventAction(action *BaseEventAction) error {
@ -643,7 +645,7 @@ func (p *PGSQLProvider) eventRuleExists(name string) (EventRule, error) {
}
func (p *PGSQLProvider) addEventRule(rule *EventRule) error {
return sqlCommonAddEventRule(rule, p.dbHandle)
return p.normalizeError(sqlCommonAddEventRule(rule, p.dbHandle), fieldName)
}
func (p *PGSQLProvider) updateEventRule(rule *EventRule) error {
@ -695,7 +697,7 @@ func (p *PGSQLProvider) roleExists(name string) (Role, error) {
}
func (p *PGSQLProvider) addRole(role *Role) error {
return sqlCommonAddRole(role, p.dbHandle)
return p.normalizeError(sqlCommonAddRole(role, p.dbHandle), fieldName)
}
func (p *PGSQLProvider) updateRole(role *Role) error {
@ -842,3 +844,26 @@ func (p *PGSQLProvider) resetDatabase() error {
sql := sqlReplaceAll(pgsqlResetSQL)
return sqlCommonExecSQLAndUpdateDBVersion(p.dbHandle, []string{sql}, 0, false)
}
func (p *PGSQLProvider) normalizeError(err error, fieldType int) error {
if err == nil {
return nil
}
var pgsqlErr *pgconn.PgError
if errors.As(err, &pgsqlErr) {
switch pgsqlErr.Code {
case "23505":
message := util.I18nErrorDuplicatedName
if fieldType == fieldUsername {
message = util.I18nErrorDuplicatedUsername
}
return util.NewI18nError(
fmt.Errorf("%w: %s", ErrDuplicatedKey, err.Error()),
message,
)
case "23503":
return fmt.Errorf("%w: %s", ErrForeignKeyViolated, err.Error())
}
}
return err
}

View file

@ -26,8 +26,7 @@ import (
"path/filepath"
"time"
// we import go-sqlite3 here to be able to disable SQLite support using a build tag
_ "github.com/mattn/go-sqlite3"
"github.com/mattn/go-sqlite3"
"github.com/drakkan/sftpgo/v2/internal/logger"
"github.com/drakkan/sftpgo/v2/internal/util"
@ -264,7 +263,7 @@ func (p *SQLiteProvider) userExists(username, role string) (User, error) {
}
func (p *SQLiteProvider) addUser(user *User) error {
return sqlCommonAddUser(user, p.dbHandle)
return p.normalizeError(sqlCommonAddUser(user, p.dbHandle), fieldUsername)
}
func (p *SQLiteProvider) updateUser(user *User) error {
@ -310,7 +309,7 @@ func (p *SQLiteProvider) getFolderByName(name string) (vfs.BaseVirtualFolder, er
}
func (p *SQLiteProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
return sqlCommonAddFolder(folder, p.dbHandle)
return p.normalizeError(sqlCommonAddFolder(folder, p.dbHandle), fieldName)
}
func (p *SQLiteProvider) updateFolder(folder *vfs.BaseVirtualFolder) error {
@ -346,7 +345,7 @@ func (p *SQLiteProvider) groupExists(name string) (Group, error) {
}
func (p *SQLiteProvider) addGroup(group *Group) error {
return sqlCommonAddGroup(group, p.dbHandle)
return p.normalizeError(sqlCommonAddGroup(group, p.dbHandle), fieldName)
}
func (p *SQLiteProvider) updateGroup(group *Group) error {
@ -366,7 +365,7 @@ func (p *SQLiteProvider) adminExists(username string) (Admin, error) {
}
func (p *SQLiteProvider) addAdmin(admin *Admin) error {
return sqlCommonAddAdmin(admin, p.dbHandle)
return p.normalizeError(sqlCommonAddAdmin(admin, p.dbHandle), fieldUsername)
}
func (p *SQLiteProvider) updateAdmin(admin *Admin) error {
@ -526,7 +525,7 @@ func (p *SQLiteProvider) eventActionExists(name string) (BaseEventAction, error)
}
func (p *SQLiteProvider) addEventAction(action *BaseEventAction) error {
return sqlCommonAddEventAction(action, p.dbHandle)
return p.normalizeError(sqlCommonAddEventAction(action, p.dbHandle), fieldName)
}
func (p *SQLiteProvider) updateEventAction(action *BaseEventAction) error {
@ -554,7 +553,7 @@ func (p *SQLiteProvider) eventRuleExists(name string) (EventRule, error) {
}
func (p *SQLiteProvider) addEventRule(rule *EventRule) error {
return sqlCommonAddEventRule(rule, p.dbHandle)
return p.normalizeError(sqlCommonAddEventRule(rule, p.dbHandle), fieldName)
}
func (p *SQLiteProvider) updateEventRule(rule *EventRule) error {
@ -606,7 +605,7 @@ func (p *SQLiteProvider) roleExists(name string) (Role, error) {
}
func (p *SQLiteProvider) addRole(role *Role) error {
return sqlCommonAddRole(role, p.dbHandle)
return p.normalizeError(sqlCommonAddRole(role, p.dbHandle), fieldName)
}
func (p *SQLiteProvider) updateRole(role *Role) error {
@ -747,6 +746,28 @@ func (p *SQLiteProvider) resetDatabase() error {
return sqlCommonExecSQLAndUpdateDBVersion(p.dbHandle, []string{sql}, 0, false)
}
func (p *SQLiteProvider) normalizeError(err error, fieldType int) error {
if err == nil {
return nil
}
if e, ok := err.(sqlite3.Error); ok {
switch e.ExtendedCode {
case 1555, 2067:
message := util.I18nErrorDuplicatedName
if fieldType == fieldUsername {
message = util.I18nErrorDuplicatedUsername
}
return util.NewI18nError(
fmt.Errorf("%w: %s", ErrDuplicatedKey, err.Error()),
message,
)
case 787:
return fmt.Errorf("%w: %s", ErrForeignKeyViolated, err.Error())
}
}
return err
}
/*func setPragmaFK(dbHandle *sql.DB, value string) error {
ctx, cancel := context.WithTimeout(context.Background(), longSQLQueryTimeout)
defer cancel()

View file

@ -105,6 +105,9 @@ func getRespStatus(err error) int {
if errors.Is(err, plugin.ErrNoSearcher) || errors.Is(err, dataprovider.ErrNotImplemented) {
return http.StatusNotImplemented
}
if errors.Is(err, dataprovider.ErrDuplicatedKey) || errors.Is(err, dataprovider.ErrForeignKeyViolated) {
return http.StatusConflict
}
return http.StatusInternalServerError
}

View file

@ -600,6 +600,8 @@ func TestBasicUserHandling(t *testing.T) {
u.Email = "user@user.com"
user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err, string(resp))
_, resp, err = httpdtest.AddUser(u, http.StatusConflict)
assert.NoError(t, err, string(resp))
lastPwdChange := user.LastPasswordChange
assert.Greater(t, lastPwdChange, int64(0))
user.MaxSessions = 10

View file

@ -189,6 +189,8 @@ const (
I18nAddFolderTitle = "title.add_folder"
I18nUpdateFolderTitle = "title.update_folder"
I18nTemplateFolderTitle = "title.template_folder"
I18nErrorDuplicatedUsername = "general.duplicated_username"
I18nErrorDuplicatedName = "general.duplicated_name"
)
// NewI18nError returns a I18nError wrappring the provided error

View file

@ -211,7 +211,9 @@
"mandatory_encryption": "Mandatory encryption",
"name_invalid": "The specified username is not valid, the following characters are allowed: a-zA-Z0-9-_.~",
"associations": "Associations",
"template_placeholders": "The following placeholders are supported"
"template_placeholders": "The following placeholders are supported",
"duplicated_username": "The specified username already exists",
"duplicated_name": "The specified name already exists"
},
"fs": {
"view_file": "View file \"{{- path}}\"",

View file

@ -211,7 +211,9 @@
"mandatory_encryption": "Crittografia obbligatoria",
"name_invalid": "Il nome specificato non è valido, sono consentiti i seguenti caratteri: a-zA-Z0-9-_.~",
"associations": "Associazioni",
"template_placeholders": "Sono supportati i seguenti segnaposto"
"template_placeholders": "Sono supportati i seguenti segnaposto",
"duplicated_username": "Il nome utente specificato esiste già",
"duplicated_name": "Il nome specificato esiste già"
},
"fs": {
"view_file": "Visualizza file \"{{- path}}\"",