REST API: remove merging of fields on updates

we use PUT verb not PATCH. We keep merging only to allow to preserve
hidden/encrypted fields.

This is a backward incompatible change, but is necessary to avoid unexpected
issues.
You have to pass complete objects on updates.

Fixes #1088

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-12-23 09:36:20 +01:00
parent e17975ed7d
commit 0841c7d7bd
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
12 changed files with 173 additions and 231 deletions

14
go.mod
View file

@ -9,13 +9,13 @@ require (
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
github.com/aws/aws-sdk-go-v2 v1.17.3
github.com/aws/aws-sdk-go-v2/config v1.18.6
github.com/aws/aws-sdk-go-v2/credentials v1.13.6
github.com/aws/aws-sdk-go-v2/config v1.18.7
github.com/aws/aws-sdk-go-v2/credentials v1.13.7
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.45
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.46
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.26
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.6
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.10
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.11
github.com/aws/aws-sdk-go-v2/service/sts v1.17.7
github.com/cockroachdb/cockroach-go/v2 v2.2.19
github.com/coreos/go-oidc/v3 v3.4.0
@ -92,7 +92,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.22 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.21 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.27 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
@ -144,7 +144,7 @@ require (
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.39.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
@ -171,5 +171,5 @@ require (
replace (
github.com/fclairamb/ftpserverlib => github.com/drakkan/ftpserverlib v0.0.0-20221203115213-ba73c775a9fd
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20221220153730-5f47589cce28
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20221223081523-be6917ff6f72
)

28
go.sum
View file

@ -233,17 +233,17 @@ github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.3/go.mod h1:gNsR5CaXK
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno=
github.com/aws/aws-sdk-go-v2/config v1.15.15/go.mod h1:A1Lzyy/o21I5/s2FbyX5AevQfSVXpvvIDCoVFD0BC4E=
github.com/aws/aws-sdk-go-v2/config v1.18.6 h1:iSuEAeervBWMHA7Aaq5hCNfwuN2m7x2VuQCnEbbQg68=
github.com/aws/aws-sdk-go-v2/config v1.18.6/go.mod h1:qyjgnyqpKnNGT+C62zMsrZ/Mn2OodYqwIH0DpXiW8f8=
github.com/aws/aws-sdk-go-v2/config v1.18.7 h1:V94lTcix6jouwmAsgQMAEBozVAGJMFhVj+6/++xfe3E=
github.com/aws/aws-sdk-go-v2/config v1.18.7/go.mod h1:OZYsyHFL5PB9UpyS78NElgKs11qI/B5KJau2XOJDXHA=
github.com/aws/aws-sdk-go-v2/credentials v1.12.10/go.mod h1:g5eIM5XRs/OzIIK81QMBl+dAuDyoLN0VYaLP+tBqEOk=
github.com/aws/aws-sdk-go-v2/credentials v1.13.6 h1:BXOMvv3O82/4JLggIi67WKlTO56f0rliCKBT4CKyf0o=
github.com/aws/aws-sdk-go-v2/credentials v1.13.6/go.mod h1:VbnUvhw31DUu6aiubViixQwWCBNO/st84dhPeOkmdls=
github.com/aws/aws-sdk-go-v2/credentials v1.13.7 h1:qUUcNS5Z1092XBFT66IJM7mYkMwgZ8fcC8YDIbEwXck=
github.com/aws/aws-sdk-go-v2/credentials v1.13.7/go.mod h1:AdCcbZXHQCjJh6NaH3pFaw8LUeBFn5+88BZGMVGuBT8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.9/go.mod h1:KDCCm4ONIdHtUloDcFvK2+vshZvx4Zmj7UMDfusuz5s=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 h1:j9wi1kQ8b+e0FBVHxCqCGo4kxDU175hoDHcWAi0sauU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21/go.mod h1:ugwW57Z5Z48bpvUyZuaPy4Kv+vEfJWnIrky7RmkBvJg=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.21/go.mod h1:iIYPrQ2rYfZiB/iADYlhj9HHZ9TTi6PqKQPAqygohbE=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.45 h1:ckFtXy51PT613d/KLKPxFiwRqgGIxDhVbNLof6x/XLo=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.45/go.mod h1:xar61xizdVU4pQygvQrNdZY1VCLNcOIvm87KzdZmWrE=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.46 h1:OCX1pQ4pcqhsDV7B92HzdLWjHWOQsILvjLinpaUWhcc=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.46/go.mod h1:MxCBOcyNXGJRvfpPiH+L6n/BF9zbowthGSUZdDvQF/c=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.15/go.mod h1:pWrr2OoHlT7M/Pd2y4HV3gJyPb3qj5qMmnPkKSNPYK4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 h1:I3cakv2Uy1vNmmhRQmFptYDxOvBnwCdNwyw63N0RaRU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI=
@ -275,14 +275,14 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.27.2/go.mod h1:u+566cosFI+d+motIz3USX
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.6 h1:W8pLcSn6Uy0eXgDBUUl8M8Kxv7JCoP68ZKTD04OXLEA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.6/go.mod h1:L2l2/q76teehcW7YEsgsDjqdsDTERJeX3nOMIFlgGUE=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.14/go.mod h1:xakbH8KMsQQKqzX87uyyzTHshc/0/Df8bsTneTS5pFU=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.10 h1:6obimjQAiRlEUZT7a2Q1ikH7ck4cPO3phGz4wqI5f2w=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.10/go.mod h1:jAeo/PdIJZuDSwsvxJS94G4d6h8tStj7WXVuKwLHWU8=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.11 h1:77V7vnw/NC4DORHVgA97+Ky2p1ri0+ZVYXh6ordUZU0=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.11/go.mod h1:jAeo/PdIJZuDSwsvxJS94G4d6h8tStj7WXVuKwLHWU8=
github.com/aws/aws-sdk-go-v2/service/sns v1.17.10/go.mod h1:uITsRNVMeCB3MkWpXxXw0eDz8pW4TYLzj+eyQtbhSxM=
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.1/go.mod h1:A94o564Gj+Yn+7QO1eLFeI7UVv3riy/YBFOfICVqFvU=
github.com/aws/aws-sdk-go-v2/service/ssm v1.27.6/go.mod h1:fiFzQgj4xNOg4/wqmAiPvzgDMXPD+cUEplX/CYn+0j0=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.13/go.mod h1:d7ptRksDDgvXaUvxyHZ9SYh+iMDymm94JbVcgvSYSzU=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.27 h1:Nmvn0DJKg00TBmoBweK253Kdsuy4V5Rs68yL/H15uBQ=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.27/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 h1:gItLq3zBYyRDPmqAClgzTH8PBjDQGeyptYGHIwtYYNA=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.28/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 h1:KCacyVSs/wlcPGx37hcbT3IGYO8P8Jx+TgSDhAXtQMY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8=
github.com/aws/aws-sdk-go-v2/service/sts v1.16.10/go.mod h1:cftkHYN6tCDNfkSasAmclSfl4l7cySoay8vz7p/ce0E=
@ -538,8 +538,8 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/drakkan/crypto v0.0.0-20221220153730-5f47589cce28 h1:QTEjJpZpyqRFlN4Yh9GIumPHDJ9LYWka9aWRPz1RiOk=
github.com/drakkan/crypto v0.0.0-20221220153730-5f47589cce28/go.mod h1:cy6DFZ6nHFw1bTHZksT/gYKmdxPdzr7Rw7xcJFSayo4=
github.com/drakkan/crypto v0.0.0-20221223081523-be6917ff6f72 h1:Ivant8yrd81A5y3tQOS7vqwL9QaOdlGonHNOfRR3rsQ=
github.com/drakkan/crypto v0.0.0-20221223081523-be6917ff6f72/go.mod h1:cy6DFZ6nHFw1bTHZksT/gYKmdxPdzr7Rw7xcJFSayo4=
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHPMtBLXhQmjaga91/DDjWk9jWA=
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
github.com/drakkan/ftpserverlib v0.0.0-20221203115213-ba73c775a9fd h1:wu/ys+33GwD9PyRO8QDCUpI2WBZtwFiDk8QkFPW8rhQ=
@ -1409,8 +1409,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/prometheus/prometheus v0.35.0/go.mod h1:7HaLx5kEPKJ0GDgbODG0fZgXbQ8K/XjZNJXQmbmgQlY=
github.com/prometheus/prometheus v0.37.0/go.mod h1:egARUgz+K93zwqsVIAneFlLZefyGOON44WyAp4Xqbbk=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=

View file

@ -116,13 +116,8 @@ func updateAdmin(w http.ResponseWriter, r *http.Request) {
return
}
adminID := admin.ID
username = admin.Username
totpConfig := admin.Filters.TOTPConfig
recoveryCodes := admin.Filters.RecoveryCodes
admin.Filters.TOTPConfig = dataprovider.AdminTOTPConfig{}
admin.Filters.RecoveryCodes = nil
err = render.DecodeJSON(r.Body, &admin)
var updatedAdmin dataprovider.Admin
err = render.DecodeJSON(r.Body, &updatedAdmin)
if err != nil {
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return
@ -139,24 +134,28 @@ func updateAdmin(w http.ResponseWriter, r *http.Request) {
http.StatusBadRequest)
return
}
if claims.isCriticalPermRemoved(admin.Permissions) {
if claims.isCriticalPermRemoved(updatedAdmin.Permissions) {
sendAPIResponse(w, r, errors.New("you cannot remove these permissions to yourself"), "", http.StatusBadRequest)
return
}
if admin.Status == 0 {
if updatedAdmin.Status == 0 {
sendAPIResponse(w, r, errors.New("you cannot disable yourself"), "", http.StatusBadRequest)
return
}
if admin.Role != claims.Role {
if updatedAdmin.Role != claims.Role {
sendAPIResponse(w, r, errors.New("you cannot add/change your role"), "", http.StatusBadRequest)
return
}
}
admin.ID = adminID
admin.Username = username
admin.Filters.TOTPConfig = totpConfig
admin.Filters.RecoveryCodes = recoveryCodes
if err := dataprovider.UpdateAdmin(&admin, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role); err != nil {
updatedAdmin.ID = admin.ID
updatedAdmin.Username = admin.Username
if updatedAdmin.Password == "" {
updatedAdmin.Password = admin.Password
}
updatedAdmin.Filters.TOTPConfig = admin.Filters.TOTPConfig
updatedAdmin.Filters.RecoveryCodes = admin.Filters.RecoveryCodes
err = dataprovider.UpdateAdmin(&updatedAdmin, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}

View file

@ -96,32 +96,30 @@ func updateEventAction(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}
actionID := action.ID
name = action.Name
currentHTTPPassword := action.Options.HTTPConfig.Password
action.Options = dataprovider.BaseEventActionOptions{}
err = render.DecodeJSON(r.Body, &action)
var updatedAction dataprovider.BaseEventAction
err = render.DecodeJSON(r.Body, &updatedAction)
if err != nil {
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return
}
action.ID = actionID
action.Name = name
action.Options.SetEmptySecretsIfNil()
switch action.Type {
updatedAction.ID = action.ID
updatedAction.Name = action.Name
updatedAction.Options.SetEmptySecretsIfNil()
switch updatedAction.Type {
case dataprovider.ActionTypeHTTP:
if action.Options.HTTPConfig.Password.IsNotPlainAndNotEmpty() {
action.Options.HTTPConfig.Password = currentHTTPPassword
if updatedAction.Options.HTTPConfig.Password.IsNotPlainAndNotEmpty() {
updatedAction.Options.HTTPConfig.Password = action.Options.HTTPConfig.Password
}
}
err = dataprovider.UpdateEventAction(&action, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
err = dataprovider.UpdateEventAction(&updatedAction, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}
sendAPIResponse(w, r, nil, "Event target updated", http.StatusOK)
sendAPIResponse(w, r, nil, "Event action updated", http.StatusOK)
}
func deleteEventAction(w http.ResponseWriter, r *http.Request) {
@ -206,25 +204,22 @@ func updateEventRule(w http.ResponseWriter, r *http.Request) {
return
}
name := getURLParam(r, "name")
rule, err := dataprovider.EventRuleExists(name)
rule, err := dataprovider.EventRuleExists(getURLParam(r, "name"))
if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}
ruleID := rule.ID
name = rule.Name
rule.Actions = nil
err = render.DecodeJSON(r.Body, &rule)
var updatedRule dataprovider.EventRule
err = render.DecodeJSON(r.Body, &updatedRule)
if err != nil {
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return
}
rule.ID = ruleID
rule.Name = name
updatedRule.ID = rule.ID
updatedRule.Name = rule.Name
err = dataprovider.UpdateEventRule(&rule, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
err = dataprovider.UpdateEventRule(&updatedRule, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return

View file

@ -76,39 +76,23 @@ func updateFolder(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}
users := folder.Users
groups := folder.Groups
folderID := folder.ID
name = folder.Name
currentS3AccessSecret := folder.FsConfig.S3Config.AccessSecret
currentAzAccountKey := folder.FsConfig.AzBlobConfig.AccountKey
currentAzSASUrl := folder.FsConfig.AzBlobConfig.SASURL
currentGCSCredentials := folder.FsConfig.GCSConfig.Credentials
currentCryptoPassphrase := folder.FsConfig.CryptConfig.Passphrase
currentSFTPPassword := folder.FsConfig.SFTPConfig.Password
currentSFTPKey := folder.FsConfig.SFTPConfig.PrivateKey
currentSFTPKeyPassphrase := folder.FsConfig.SFTPConfig.KeyPassphrase
currentHTTPPassword := folder.FsConfig.HTTPConfig.Password
currentHTTPAPIKey := folder.FsConfig.HTTPConfig.APIKey
folder.FsConfig.S3Config = vfs.S3FsConfig{}
folder.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
folder.FsConfig.GCSConfig = vfs.GCSFsConfig{}
folder.FsConfig.CryptConfig = vfs.CryptFsConfig{}
folder.FsConfig.SFTPConfig = vfs.SFTPFsConfig{}
folder.FsConfig.HTTPConfig = vfs.HTTPFsConfig{}
err = render.DecodeJSON(r.Body, &folder)
var updatedFolder vfs.BaseVirtualFolder
err = render.DecodeJSON(r.Body, &updatedFolder)
if err != nil {
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return
}
folder.ID = folderID
folder.Name = name
folder.FsConfig.SetEmptySecretsIfNil()
updateEncryptedSecrets(&folder.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl, currentGCSCredentials,
currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey, currentSFTPKeyPassphrase, currentHTTPPassword,
currentHTTPAPIKey)
err = dataprovider.UpdateFolder(&folder, users, groups, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
updatedFolder.ID = folder.ID
updatedFolder.Name = folder.Name
updatedFolder.FsConfig.SetEmptySecretsIfNil()
updateEncryptedSecrets(&updatedFolder.FsConfig, folder.FsConfig.S3Config.AccessSecret, folder.FsConfig.AzBlobConfig.AccountKey,
folder.FsConfig.AzBlobConfig.SASURL, folder.FsConfig.GCSConfig.Credentials, folder.FsConfig.CryptConfig.Passphrase,
folder.FsConfig.SFTPConfig.Password, folder.FsConfig.SFTPConfig.PrivateKey, folder.FsConfig.SFTPConfig.KeyPassphrase,
folder.FsConfig.HTTPConfig.Password, folder.FsConfig.HTTPConfig.APIKey)
err = dataprovider.UpdateFolder(&updatedFolder, folder.Users, folder.Groups, claims.Username,
util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return

View file

@ -22,7 +22,6 @@ import (
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
"github.com/drakkan/sftpgo/v2/internal/util"
"github.com/drakkan/sftpgo/v2/internal/vfs"
)
func getGroups(w http.ResponseWriter, r *http.Request) {
@ -76,9 +75,7 @@ func updateGroup(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}
users := group.Users
groupID := group.ID
name = group.Name
currentS3AccessSecret := group.UserSettings.FsConfig.S3Config.AccessSecret
currentAzAccountKey := group.UserSettings.FsConfig.AzBlobConfig.AccountKey
currentAzSASUrl := group.UserSettings.FsConfig.AzBlobConfig.SASURL
@ -90,24 +87,20 @@ func updateGroup(w http.ResponseWriter, r *http.Request) {
currentHTTPPassword := group.UserSettings.FsConfig.HTTPConfig.Password
currentHTTPAPIKey := group.UserSettings.FsConfig.HTTPConfig.APIKey
group.UserSettings.FsConfig.S3Config = vfs.S3FsConfig{}
group.UserSettings.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
group.UserSettings.FsConfig.GCSConfig = vfs.GCSFsConfig{}
group.UserSettings.FsConfig.CryptConfig = vfs.CryptFsConfig{}
group.UserSettings.FsConfig.SFTPConfig = vfs.SFTPFsConfig{}
group.UserSettings.FsConfig.HTTPConfig = vfs.HTTPFsConfig{}
err = render.DecodeJSON(r.Body, &group)
var updatedGroup dataprovider.Group
err = render.DecodeJSON(r.Body, &updatedGroup)
if err != nil {
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return
}
group.ID = groupID
group.Name = name
group.UserSettings.FsConfig.SetEmptySecretsIfNil()
updateEncryptedSecrets(&group.UserSettings.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl,
updatedGroup.ID = group.ID
updatedGroup.Name = group.Name
updatedGroup.UserSettings.FsConfig.SetEmptySecretsIfNil()
updateEncryptedSecrets(&updatedGroup.UserSettings.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl,
currentGCSCredentials, currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey, currentSFTPKeyPassphrase,
currentHTTPPassword, currentHTTPAPIKey)
err = dataprovider.UpdateGroup(&group, users, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
err = dataprovider.UpdateGroup(&updatedGroup, group.Users, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr),
claims.Role)
if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return

View file

@ -98,14 +98,17 @@ func updateAPIKey(w http.ResponseWriter, r *http.Request) {
return
}
err = render.DecodeJSON(r.Body, &apiKey)
var updatedAPIKey dataprovider.APIKey
err = render.DecodeJSON(r.Body, &updatedAPIKey)
if err != nil {
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return
}
apiKey.KeyID = keyID
if err := dataprovider.UpdateAPIKey(&apiKey, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role); err != nil {
updatedAPIKey.KeyID = keyID
updatedAPIKey.Key = apiKey.Key
err = dataprovider.UpdateAPIKey(&updatedAPIKey, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}

View file

@ -75,16 +75,17 @@ func updateRole(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}
roleID := role.ID
name = role.Name
err = render.DecodeJSON(r.Body, &role)
var updatedRole dataprovider.Role
err = render.DecodeJSON(r.Body, &updatedRole)
if err != nil {
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return
}
role.ID = roleID
role.Name = name
err = dataprovider.UpdateRole(&role, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
updatedRole.ID = role.ID
updatedRole.Name = role.Name
err = dataprovider.UpdateRole(&updatedRole, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return

View file

@ -131,26 +131,27 @@ func updateShare(w http.ResponseWriter, r *http.Request) {
return
}
oldPassword := share.Password
err = render.DecodeJSON(r.Body, &share)
var updatedShare dataprovider.Share
err = render.DecodeJSON(r.Body, &updatedShare)
if err != nil {
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return
}
share.ShareID = shareID
share.Username = claims.Username
if share.Password == redactedSecret {
share.Password = oldPassword
updatedShare.ShareID = shareID
updatedShare.Username = claims.Username
if updatedShare.Password == redactedSecret {
updatedShare.Password = share.Password
}
if share.Password == "" {
if updatedShare.Password == "" {
if util.Contains(claims.Permissions, sdk.WebClientShareNoPasswordDisabled) {
sendAPIResponse(w, r, nil, "You are not authorized to share files/folders without a password",
http.StatusForbidden)
return
}
}
if err := dataprovider.UpdateShare(&share, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role); err != nil {
err = dataprovider.UpdateShare(&updatedShare, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}

View file

@ -163,55 +163,28 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}
userID := user.ID
username = user.Username
lastPwdChange := user.LastPasswordChange
totpConfig := user.Filters.TOTPConfig
recoveryCodes := user.Filters.RecoveryCodes
currentPermissions := user.Permissions
currentS3AccessSecret := user.FsConfig.S3Config.AccessSecret
currentAzAccountKey := user.FsConfig.AzBlobConfig.AccountKey
currentAzSASUrl := user.FsConfig.AzBlobConfig.SASURL
currentGCSCredentials := user.FsConfig.GCSConfig.Credentials
currentCryptoPassphrase := user.FsConfig.CryptConfig.Passphrase
currentSFTPPassword := user.FsConfig.SFTPConfig.Password
currentSFTPKey := user.FsConfig.SFTPConfig.PrivateKey
currentSFTPKeyPassphrase := user.FsConfig.SFTPConfig.KeyPassphrase
currentHTTPPassword := user.FsConfig.HTTPConfig.Password
currentHTTPAPIKey := user.FsConfig.HTTPConfig.APIKey
user.Permissions = make(map[string][]string)
user.FsConfig.S3Config = vfs.S3FsConfig{}
user.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
user.FsConfig.GCSConfig = vfs.GCSFsConfig{}
user.FsConfig.CryptConfig = vfs.CryptFsConfig{}
user.FsConfig.SFTPConfig = vfs.SFTPFsConfig{}
user.FsConfig.HTTPConfig = vfs.HTTPFsConfig{}
user.Filters.TOTPConfig = dataprovider.UserTOTPConfig{}
user.Filters.RecoveryCodes = nil
user.VirtualFolders = nil
err = render.DecodeJSON(r.Body, &user)
var updatedUser dataprovider.User
updatedUser.Password = user.Password
err = render.DecodeJSON(r.Body, &updatedUser)
if err != nil {
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return
}
user.ID = userID
user.Username = username
user.Filters.TOTPConfig = totpConfig
user.Filters.RecoveryCodes = recoveryCodes
user.LastPasswordChange = lastPwdChange
user.SetEmptySecretsIfNil()
// we use new Permissions if passed otherwise the old ones
if len(user.Permissions) == 0 {
user.Permissions = currentPermissions
}
updateEncryptedSecrets(&user.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl,
currentGCSCredentials, currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey, currentSFTPKeyPassphrase,
currentHTTPPassword, currentHTTPAPIKey)
updatedUser.ID = user.ID
updatedUser.Username = user.Username
updatedUser.Filters.RecoveryCodes = user.Filters.RecoveryCodes
updatedUser.Filters.TOTPConfig = user.Filters.TOTPConfig
updatedUser.LastPasswordChange = user.LastPasswordChange
updatedUser.SetEmptySecretsIfNil()
updateEncryptedSecrets(&updatedUser.FsConfig, user.FsConfig.S3Config.AccessSecret, user.FsConfig.AzBlobConfig.AccountKey,
user.FsConfig.AzBlobConfig.SASURL, user.FsConfig.GCSConfig.Credentials, user.FsConfig.CryptConfig.Passphrase,
user.FsConfig.SFTPConfig.Password, user.FsConfig.SFTPConfig.PrivateKey, user.FsConfig.SFTPConfig.KeyPassphrase,
user.FsConfig.HTTPConfig.Password, user.FsConfig.HTTPConfig.APIKey)
if claims.Role != "" {
user.Role = claims.Role
updatedUser.Role = claims.Role
}
err = dataprovider.UpdateUser(&user, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
err = dataprovider.UpdateUser(&updatedUser, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return

View file

@ -668,8 +668,8 @@ func TestRoleRelations(t *testing.T) {
admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
assert.NoError(t, err)
admin.Role = "invalid role"
_, _, err = httpdtest.UpdateAdmin(admin, http.StatusInternalServerError)
assert.NoError(t, err)
_, resp, err = httpdtest.UpdateAdmin(admin, http.StatusInternalServerError)
assert.NoError(t, err, string(resp))
admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, role.Name, admin.Role)
@ -813,21 +813,38 @@ func TestBasicGroupHandling(t *testing.T) {
group.UserSettings.FsConfig.SFTPConfig.Endpoint = sftpServerAddr
group.UserSettings.FsConfig.SFTPConfig.Username = defaultUsername
group.UserSettings.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
group.UserSettings.Permissions = map[string][]string{
"/": {dataprovider.PermAny},
}
group.UserSettings.Filters.AllowedIP = []string{"10.0.0.0/8"}
group, _, err = httpdtest.UpdateGroup(group, http.StatusOK)
assert.NoError(t, err)
groupGet, _, err = httpdtest.GetGroupByName(group.Name, http.StatusOK)
assert.NoError(t, err)
assert.Len(t, groupGet.UserSettings.Permissions, 1)
assert.Len(t, groupGet.UserSettings.Filters.AllowedIP, 1)
// update again and check that the password was preserved
dbGroup, err := dataprovider.GroupExists(group.Name)
assert.NoError(t, err)
group.UserSettings.FsConfig.SFTPConfig.Password = kms.NewSecret(
dbGroup.UserSettings.FsConfig.SFTPConfig.Password.GetStatus(),
dbGroup.UserSettings.FsConfig.SFTPConfig.Password.GetPayload(), "", "")
group.UserSettings.Permissions = nil
group.UserSettings.Filters.AllowedIP = nil
group, _, err = httpdtest.UpdateGroup(group, http.StatusOK)
assert.NoError(t, err)
assert.Len(t, group.UserSettings.Permissions, 0)
assert.Len(t, group.UserSettings.Filters.AllowedIP, 0)
dbGroup, err = dataprovider.GroupExists(group.Name)
assert.NoError(t, err)
err = dbGroup.UserSettings.FsConfig.SFTPConfig.Password.Decrypt()
assert.NoError(t, err)
assert.Equal(t, defaultPassword, dbGroup.UserSettings.FsConfig.SFTPConfig.Password.GetPayload())
// check the group permissions
groupGet, _, err = httpdtest.GetGroupByName(group.Name, http.StatusOK)
assert.NoError(t, err)
assert.Len(t, groupGet.UserSettings.Permissions, 0)
group.UserSettings.HomeDir = "relative path"
_, _, err = httpdtest.UpdateGroup(group, http.StatusBadRequest)
@ -4191,7 +4208,11 @@ func TestUpdateUserEmptyPassword(t *testing.T) {
assert.NotEmpty(t, dbUser.Password)
assert.True(t, dbUser.IsPasswordHashed())
// now update the user and set an empty password
customUser := make(map[string]any)
data, err := json.Marshal(dbUser)
assert.NoError(t, err)
var customUser map[string]any
err = json.Unmarshal(data, &customUser)
assert.NoError(t, err)
customUser["password"] = ""
asJSON, err := json.Marshal(customUser)
assert.NoError(t, err)
@ -4208,6 +4229,30 @@ func TestUpdateUserEmptyPassword(t *testing.T) {
assert.NoError(t, err)
}
func TestUpdateUserNoPassword(t *testing.T) {
u := getTestUser()
u.PublicKeys = []string{testPubKey}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
// the password is not empty
dbUser, err := dataprovider.UserExists(u.Username, "")
assert.NoError(t, err)
assert.NotEmpty(t, dbUser.Password)
assert.True(t, dbUser.IsPasswordHashed())
// now update the user and remove the password field, old password should be preserved
user.Password = "" // password has the omitempty tag
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
// the password is preserved
dbUser, err = dataprovider.UserExists(u.Username, "")
assert.NoError(t, err)
assert.NotEmpty(t, dbUser.Password)
assert.True(t, dbUser.IsPasswordHashed())
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
func TestUpdateUser(t *testing.T) {
u := getTestUser()
u.UsedQuotaFiles = 1
@ -5947,10 +5992,11 @@ func TestNamingRules(t *testing.T) {
assert.Equal(t, "文件夹ab", folder.Name)
folder.Name = f.Name
folder.Description = folder.Name
folder, resp, err = httpdtest.UpdateFolder(folder, http.StatusOK)
_, resp, err = httpdtest.UpdateFolder(folder, http.StatusOK)
assert.NoError(t, err, string(resp))
folder, resp, err = httpdtest.GetFolderByName(f.Name, http.StatusOK)
assert.NoError(t, err, string(resp))
assert.Equal(t, "文件夹AB", folder.Description)
_, err = httpdtest.RemoveFolder(f, http.StatusOK)
assert.NoError(t, err)
token, err := getJWTWebClientTokenFromTestServer(u.Username, defaultPassword)
@ -9620,7 +9666,10 @@ func TestWebAPIChangeUserProfileMock(t *testing.T) {
assert.Equal(t, email, profileReq["email"].(string))
assert.Equal(t, description, profileReq["description"].(string))
assert.True(t, profileReq["allow_api_key_auth"].(bool))
assert.Len(t, profileReq["public_keys"].([]any), 2)
val, ok := profileReq["public_keys"].([]any)
if assert.True(t, ok, profileReq) {
assert.Len(t, val, 2)
}
// set an invalid email
profileReq = make(map[string]any)
profileReq["email"] = "notavalidemail"
@ -9644,6 +9693,8 @@ func TestWebAPIChangeUserProfileMock(t *testing.T) {
checkResponseCode(t, http.StatusBadRequest, rr)
assert.Contains(t, rr.Body.String(), "Validation error: could not parse key")
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientPubKeyChangeDisabled}
user.Email = email
user.Description = description
@ -9678,8 +9729,13 @@ func TestWebAPIChangeUserProfileMock(t *testing.T) {
assert.Equal(t, email, profileReq["email"].(string))
assert.Equal(t, description+"_mod", profileReq["description"].(string))
assert.True(t, profileReq["allow_api_key_auth"].(bool))
assert.Len(t, profileReq["public_keys"].([]any), 2)
val, ok = profileReq["public_keys"].([]any)
if assert.True(t, ok, profileReq) {
assert.Len(t, val, 2)
}
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientInfoChangeDisabled}
user.Description = description + "_mod"
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
@ -10299,47 +10355,6 @@ func TestUserHandlingWithAPIKey(t *testing.T) {
assert.NoError(t, err)
}
func TestUpdateUserMock(t *testing.T) {
token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
assert.NoError(t, err)
user := getTestUser()
userAsJSON := getUserAsJSON(t, user)
req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
setBearerForReq(req, token)
rr := executeRequest(req)
checkResponseCode(t, http.StatusCreated, rr)
err = render.DecodeJSON(rr.Body, &user)
assert.NoError(t, err)
// permissions should not change if empty or nil
permissions := user.Permissions
user.Permissions = make(map[string][]string)
userAsJSON = getUserAsJSON(t, user)
req, _ = http.NewRequest(http.MethodPut, userPath+"/"+user.Username, bytes.NewBuffer(userAsJSON))
setBearerForReq(req, token)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
req, _ = http.NewRequest(http.MethodGet, userPath+"/"+user.Username, nil)
setBearerForReq(req, token)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
var updatedUser dataprovider.User
err = render.DecodeJSON(rr.Body, &updatedUser)
assert.NoError(t, err)
for dir, perms := range permissions {
if actualPerms, ok := updatedUser.Permissions[dir]; ok {
for _, v := range actualPerms {
assert.True(t, util.Contains(perms, v))
}
} else {
assert.Fail(t, "Permissions directories mismatch")
}
}
req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
setBearerForReq(req, token)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
}
func TestUpdateUserQuotaUsageMock(t *testing.T) {
token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
assert.NoError(t, err)

View file

@ -2668,29 +2668,7 @@ func TestLoginAfterUserUpdateEmptyPwd(t *testing.T) {
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
user.Password = ""
user.PublicKeys = []string{}
// password and public key should remain unchanged
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestLoginAfterUserUpdateEmptyPubKey(t *testing.T) {
usePubKey := true
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
user.Password = ""
user.PublicKeys = []string{}
// password and public key should remain unchanged
// password should remain unchanged
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)