rename public_key in public_keys

remove compatibility layer to convert public keys newline delimited
in json list
This commit is contained in:
Nicola Murino 2019-08-07 23:41:10 +02:00
parent 5ad222fc53
commit 2aca4479a5
15 changed files with 61 additions and 49 deletions

View file

@ -11,7 +11,7 @@ env:
- GO111MODULE=on
before_script:
- sqlite3 sftpgo.db 'CREATE TABLE "users" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "username" varchar(255) NOT NULL UNIQUE, "password" varchar(255) NULL, "public_key" text NULL, "home_dir" varchar(255) NOT NULL, "uid" integer NOT NULL, "gid" integer NOT NULL, "max_sessions" integer NOT NULL, "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL, "permissions" text NOT NULL, "used_quota_size" bigint NOT NULL, "used_quota_files" integer NOT NULL, "last_quota_update" bigint NOT NULL, "upload_bandwidth" integer NOT NULL, "download_bandwidth" integer NOT NULL);'
- sqlite3 sftpgo.db 'CREATE TABLE "users" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "username" varchar(255) NOT NULL UNIQUE, "password" varchar(255) NULL, "public_keys" text NULL, "home_dir" varchar(255) NOT NULL, "uid" integer NOT NULL, "gid" integer NOT NULL, "max_sessions" integer NOT NULL, "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL, "permissions" text NOT NULL, "used_quota_size" bigint NOT NULL, "used_quota_files" integer NOT NULL, "last_quota_update" bigint NOT NULL, "upload_bandwidth" integer NOT NULL, "download_bandwidth" integer NOT NULL);'
install:
- go get -v -t ./...

View file

@ -124,7 +124,7 @@ func TestBasicUserHandling(t *testing.T) {
func TestAddUserNoCredentials(t *testing.T) {
u := getTestUser()
u.Password = ""
u.PublicKey = []string{}
u.PublicKeys = []string{}
_, _, err := api.AddUser(u, http.StatusBadRequest)
if err != nil {
t.Errorf("unexpected error adding user with no credentials: %v", err)
@ -180,22 +180,22 @@ func TestUserPublicKey(t *testing.T) {
u := getTestUser()
invalidPubKey := "invalid"
validPubKey := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0= nicola@p1"
u.PublicKey = []string{invalidPubKey}
u.PublicKeys = []string{invalidPubKey}
_, _, err := api.AddUser(u, http.StatusBadRequest)
if err != nil {
t.Errorf("unexpected error adding user with invalid pub key: %v", err)
}
u.PublicKey = []string{validPubKey}
u.PublicKeys = []string{validPubKey}
user, _, err := api.AddUser(u, http.StatusOK)
if err != nil {
t.Errorf("unable to add user: %v", err)
}
user.PublicKey = []string{validPubKey, invalidPubKey}
user.PublicKeys = []string{validPubKey, invalidPubKey}
_, _, err = api.UpdateUser(user, http.StatusBadRequest)
if err != nil {
t.Errorf("update user with invalid public key must fail: %v", err)
}
user.PublicKey = []string{validPubKey, validPubKey, validPubKey}
user.PublicKeys = []string{validPubKey, validPubKey, validPubKey}
_, _, err = api.UpdateUser(user, http.StatusOK)
if err != nil {
t.Errorf("unable to update user: %v", err)
@ -236,7 +236,7 @@ func TestUpdateUserNoCredentials(t *testing.T) {
t.Errorf("unable to add user: %v", err)
}
user.Password = ""
user.PublicKey = []string{}
user.PublicKeys = []string{}
// password and public key will be omitted from json serialization if empty and so they will remain unchanged
// and no validation error will be raised
_, _, err = api.UpdateUser(user, http.StatusOK)

View file

@ -257,8 +257,8 @@ func checkUser(expected dataprovider.User, actual dataprovider.User) error {
if len(actual.Password) > 0 {
return errors.New("User password must not be visible")
}
if len(actual.PublicKey) > 0 {
return errors.New("User public key must not be visible")
if len(actual.PublicKeys) > 0 {
return errors.New("User public keys must not be visible")
}
if expected.ID <= 0 {
if actual.ID <= 0 {

View file

@ -47,12 +47,12 @@ func TestCheckUser(t *testing.T) {
t.Errorf("actual password must be nil")
}
actual.Password = ""
actual.PublicKey = []string{"pub key"}
actual.PublicKeys = []string{"pub key"}
err = checkUser(expected, actual)
if err == nil {
t.Errorf("actual public key must be nil")
}
actual.PublicKey = []string{}
actual.PublicKeys = []string{}
err = checkUser(expected, actual)
if err == nil {
t.Errorf("actual ID must be > 0")

View file

@ -523,7 +523,7 @@ components:
type: string
nullable: true
description: password or public key are mandatory. For security reasons this field is omitted when you search/get users
public_key:
public_keys:
type: array
items:
type: string

View file

@ -65,7 +65,7 @@ func getUserByID(w http.ResponseWriter, r *http.Request) {
user, err := dataprovider.GetUserByID(dataProvider, userID)
if err == nil {
user.Password = ""
user.PublicKey = []string{}
user.PublicKeys = []string{}
render.JSON(w, r, user)
} else if err == sql.ErrNoRows {
sendAPIResponse(w, r, err, "", http.StatusNotFound)
@ -86,7 +86,7 @@ func addUser(w http.ResponseWriter, r *http.Request) {
user, err = dataprovider.UserExists(dataProvider, user.Username)
if err == nil {
user.Password = ""
user.PublicKey = []string{}
user.PublicKeys = []string{}
render.JSON(w, r, user)
} else {
sendAPIResponse(w, r, err, "", http.StatusInternalServerError)

View file

@ -212,8 +212,8 @@ func validateUser(user *User) error {
if len(user.Username) == 0 || len(user.HomeDir) == 0 {
return &ValidationError{err: "Mandatory parameters missing"}
}
if len(user.Password) == 0 && len(user.PublicKey) == 0 {
return &ValidationError{err: "Please set password or public_key"}
if len(user.Password) == 0 && len(user.PublicKeys) == 0 {
return &ValidationError{err: "Please set password or at least a public_key"}
}
if len(user.Permissions) == 0 {
return &ValidationError{err: "Please grant some permissions to this user"}
@ -233,7 +233,7 @@ func validateUser(user *User) error {
}
user.Password = pwd
}
for i, k := range user.PublicKey {
for i, k := range user.PublicKeys {
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
if err != nil {
return &ValidationError{err: fmt.Sprintf("Could not parse key nr. %d: %s", i, err)}

View file

@ -78,11 +78,11 @@ func sqlCommonValidateUserAndPubKey(username string, pubKey string) (User, error
logger.Warn(logSender, "error authenticating user: %v, error: %v", username, err)
return user, err
}
if len(user.PublicKey) == 0 {
if len(user.PublicKeys) == 0 {
return user, errors.New("Invalid credentials")
}
for i, k := range user.PublicKey {
for i, k := range user.PublicKeys {
storedPubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
if err != nil {
logger.Warn(logSender, "error parsing stored public key %d for user %v: %v", i, username, err)
@ -242,7 +242,7 @@ func sqlCommonGetUsers(limit int, offset int, order string, username string) ([]
u, err := getUserFromDbRow(nil, rows)
// hide password and public key
u.Password = ""
u.PublicKey = []string{}
u.PublicKeys = []string{}
if err == nil {
users = append(users, u)
} else {
@ -280,13 +280,7 @@ func getUserFromDbRow(row *sql.Row, rows *sql.Rows) (User, error) {
var list []string
err = json.Unmarshal([]byte(publicKey.String), &list)
if err == nil {
user.PublicKey = list
} else {
// compatibility layer: initially we store public keys as string newline delimited
// we need to remove this code in future
user.PublicKey = strings.Split(publicKey.String, "\n")
logger.Warn(logSender, "public keys loaded using compatibility mode, this will not work in future versions! "+
"Number of public keys loaded: %v, username: %v", len(user.PublicKey), user.Username)
user.PublicKeys = list
}
}
if permissions.Valid {

View file

@ -3,7 +3,7 @@ package dataprovider
import "fmt"
const (
selectUserFields = "id,username,password,public_key,home_dir,uid,gid,max_sessions,quota_size,quota_files,permissions," +
selectUserFields = "id,username,password,public_keys,home_dir,uid,gid,max_sessions,quota_size,quota_files,permissions," +
"used_quota_size,used_quota_files,last_quota_update,upload_bandwidth,download_bandwidth"
)
@ -51,7 +51,7 @@ func getQuotaQuery() string {
}
func getAddUserQuery() string {
return fmt.Sprintf(`INSERT INTO %v (username,password,public_key,home_dir,uid,gid,max_sessions,quota_size,quota_files,permissions,
return fmt.Sprintf(`INSERT INTO %v (username,password,public_keys,home_dir,uid,gid,max_sessions,quota_size,quota_files,permissions,
used_quota_size,used_quota_files,last_quota_update,upload_bandwidth,download_bandwidth)
VALUES (%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,0,0,0,%v,%v)`, config.UsersTable, sqlPlaceholders[0], sqlPlaceholders[1],
sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7],
@ -59,7 +59,7 @@ func getAddUserQuery() string {
}
func getUpdateUserQuery() string {
return fmt.Sprintf(`UPDATE %v SET password=%v,public_key=%v,home_dir=%v,uid=%v,gid=%v,max_sessions=%v,quota_size=%v,
return fmt.Sprintf(`UPDATE %v SET password=%v,public_keys=%v,home_dir=%v,uid=%v,gid=%v,max_sessions=%v,quota_size=%v,
quota_files=%v,permissions=%v,upload_bandwidth=%v,download_bandwidth=%v WHERE id = %v`, config.UsersTable,
sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5],
sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8], sqlPlaceholders[9], sqlPlaceholders[10], sqlPlaceholders[11])

View file

@ -39,8 +39,8 @@ type User struct {
// Currently, as fallback, there is a clear text password checking but you should not store passwords
// as clear text and this support could be removed at any time, so please don't depend on it.
Password string `json:"password,omitempty"`
// PublicKey used for public key authentication. At least one between password and a public key is mandatory
PublicKey []string `json:"public_key,omitempty"`
// PublicKeys used for public key authentication. At least one between password and a public key is mandatory
PublicKeys []string `json:"public_keys,omitempty"`
// The user cannot upload or download files outside this directory. Must be an absolute path
HomeDir string `json:"home_dir"`
// If sftpgo runs as root system user then the created files and directories will be assigned to this system UID
@ -82,7 +82,7 @@ func (u *User) GetPermissionsAsJSON() ([]byte, error) {
// GetPublicKeysAsJSON returns the public keys as json byte array
func (u *User) GetPublicKeysAsJSON() ([]byte, error) {
return json.Marshal(u.PublicKey)
return json.Marshal(u.PublicKeys)
}
// GetUID returns a validate uid, suitable for use with os.Chown

20
scripts/sftpgo_api_cli.py Normal file → Executable file
View file

@ -40,7 +40,7 @@ class SFTPGoApiRequests:
else:
print(r.text)
def buildUserObject(self, user_id=0, username="", password="", public_key="", home_dir="", uid=0,
def buildUserObject(self, user_id=0, username="", password="", public_keys="", home_dir="", uid=0,
gid=0, max_sessions=0, quota_size=0, quota_files=0, permissions=[], upload_bandwidth=0,
download_bandwidth=0):
user = {"id":user_id, "username":username, "home_dir":home_dir, "uid":uid, "gid":gid,
@ -49,8 +49,8 @@ class SFTPGoApiRequests:
"download_bandwidth":download_bandwidth}
if password:
user.update({"password":password})
if public_key:
user.update({"public_key":public_key})
if public_keys:
user.update({"public_keys":public_keys})
return user
def getUsers(self, limit=100, offset=0, order="ASC", username=""):
@ -62,17 +62,17 @@ class SFTPGoApiRequests:
r = requests.get(urlparse.urljoin(self.userPath, "user/" + str(user_id)), auth=self.auth, verify=self.verify)
self.printResponse(r)
def addUser(self, username="", password="", public_key="", home_dir="", uid=0, gid=0, max_sessions=0,
def addUser(self, username="", password="", public_keys="", home_dir="", uid=0, gid=0, max_sessions=0,
quota_size=0, quota_files=0, permissions=[], upload_bandwidth=0, download_bandwidth=0):
u = self.buildUserObject(0, username, password, public_key, home_dir, uid, gid, max_sessions,
u = self.buildUserObject(0, username, password, public_keys, home_dir, uid, gid, max_sessions,
quota_size, quota_files, permissions, upload_bandwidth, download_bandwidth)
r = requests.post(self.userPath, json=u, auth=self.auth, verify=self.verify)
self.printResponse(r)
def updateUser(self, user_id, username="", password="", public_key="", home_dir="", uid=0, gid=0,
def updateUser(self, user_id, username="", password="", public_keys="", home_dir="", uid=0, gid=0,
max_sessions=0, quota_size=0, quota_files=0, permissions=[], upload_bandwidth=0,
download_bandwidth=0):
u = self.buildUserObject(user_id, username, password, public_key, home_dir, uid, gid, max_sessions,
u = self.buildUserObject(user_id, username, password, public_keys, home_dir, uid, gid, max_sessions,
quota_size, quota_files, permissions, upload_bandwidth, download_bandwidth)
r = requests.put(urlparse.urljoin(self.userPath, "user/" + str(user_id)), json=u, auth=self.auth, verify=self.verify)
self.printResponse(r)
@ -102,7 +102,7 @@ class SFTPGoApiRequests:
def addCommonUserArguments(parser):
parser.add_argument('username', type=str)
parser.add_argument('--password', type=str, default="", help="default: %(default)s")
parser.add_argument('--public_key', type=str, nargs='+', default=[], help="default: %(default)s")
parser.add_argument('--public_keys', type=str, nargs='+', default=[], help="default: %(default)s")
parser.add_argument('--home_dir', type=str, default="", help="default: %(default)s")
parser.add_argument('--uid', type=int, default=0, help="default: %(default)s")
parser.add_argument('--gid', type=int, default=0, help="default: %(default)s")
@ -170,11 +170,11 @@ if __name__ == '__main__':
api = SFTPGoApiRequests(args.debug, args.base_url, args.auth_type, args.auth_user, args.auth_password, args.verify)
if args.command == "add_user":
api.addUser(args.username, args.password, args.public_key, args.home_dir,
api.addUser(args.username, args.password, args.public_keys, args.home_dir,
args.uid, args.gid, args.max_sessions, args.quota_size, args.quota_files,
args.permissions, args.upload_bandwidth, args.download_bandwidth)
elif args.command == "update_user":
api.updateUser(args.id, args.username, args.password, args.public_key, args.home_dir,
api.updateUser(args.id, args.username, args.password, args.public_keys, args.home_dir,
args.uid, args.gid, args.max_sessions, args.quota_size, args.quota_files,
args.permissions, args.upload_bandwidth, args.download_bandwidth)
elif args.command == "delete_user":

View file

@ -499,7 +499,7 @@ func TestHomeSpecialChars(t *testing.T) {
func TestLogin(t *testing.T) {
u := getTestUser(false)
u.PublicKey = []string{testPubKey}
u.PublicKeys = []string{testPubKey}
user, _, err := api.AddUser(u, http.StatusOK)
if err != nil {
t.Errorf("unable to add user: %v", err)
@ -531,7 +531,7 @@ func TestLogin(t *testing.T) {
defer client.Close()
}
// testPubKey1 is not authorized
user.PublicKey = []string{testPubKey1}
user.PublicKeys = []string{testPubKey1}
user.Password = ""
_, _, err = api.UpdateUser(user, http.StatusOK)
if err != nil {
@ -543,7 +543,7 @@ func TestLogin(t *testing.T) {
defer client.Close()
}
// login a user with multiple public keys, only the second one is valid
user.PublicKey = []string{testPubKey1, testPubKey}
user.PublicKeys = []string{testPubKey1, testPubKey}
user.Password = ""
_, _, err = api.UpdateUser(user, http.StatusOK)
if err != nil {
@ -572,7 +572,7 @@ func TestLoginAfterUserUpdateEmptyPwd(t *testing.T) {
t.Errorf("unable to add user: %v", err)
}
user.Password = ""
user.PublicKey = []string{}
user.PublicKeys = []string{}
// password and public key should remain unchanged
_, _, err = api.UpdateUser(user, http.StatusOK)
if err != nil {
@ -605,7 +605,7 @@ func TestLoginAfterUserUpdateEmptyPubKey(t *testing.T) {
t.Errorf("unable to add user: %v", err)
}
user.Password = ""
user.PublicKey = []string{}
user.PublicKeys = []string{}
// password and public key should remain unchanged
_, _, err = api.UpdateUser(user, http.StatusOK)
if err != nil {
@ -1287,7 +1287,7 @@ func getTestUser(usePubKey bool) dataprovider.User {
Permissions: allPerms,
}
if usePubKey {
user.PublicKey = []string{testPubKey}
user.PublicKeys = []string{testPubKey}
user.Password = ""
}
return user

6
sql/mysql/20190807.sql Normal file
View file

@ -0,0 +1,6 @@
BEGIN;
--
-- Rename field public_key on user to public_keys
--
ALTER TABLE `users` CHANGE `public_key` `public_keys` longtext NULL;
COMMIT;

6
sql/pgsql/20190807.sql Normal file
View file

@ -0,0 +1,6 @@
BEGIN;
--
-- Rename field public_key on user to public_keys
--
ALTER TABLE "users" RENAME COLUMN "public_key" TO "public_keys";
COMMIT;

6
sql/sqlite/20190807.sql Normal file
View file

@ -0,0 +1,6 @@
BEGIN;
--
-- Rename field public_key on user to public_keys
--
ALTER TABLE "users" RENAME COLUMN "public_key" TO "public_keys";
COMMIT;