Browse Source

rename public_key in public_keys

remove compatibility layer to convert public keys newline delimited
in json list
Nicola Murino 6 years ago
parent
commit
2aca4479a5

+ 1 - 1
.travis.yml

@@ -11,7 +11,7 @@ env:
   - GO111MODULE=on
   - GO111MODULE=on
 
 
 before_script:
 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:
 install:
   - go get -v -t ./...
   - go get -v -t ./...

+ 6 - 6
api/api_test.go

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

+ 2 - 2
api/api_utils.go

@@ -257,8 +257,8 @@ func checkUser(expected dataprovider.User, actual dataprovider.User) error {
 	if len(actual.Password) > 0 {
 	if len(actual.Password) > 0 {
 		return errors.New("User password must not be visible")
 		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 expected.ID <= 0 {
 		if actual.ID <= 0 {
 		if actual.ID <= 0 {

+ 2 - 2
api/internal_test.go

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

+ 1 - 1
api/schema/openapi.yaml

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

+ 2 - 2
api/user.go

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

+ 3 - 3
dataprovider/dataprovider.go

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

+ 4 - 10
dataprovider/sqlcommon.go

@@ -78,11 +78,11 @@ func sqlCommonValidateUserAndPubKey(username string, pubKey string) (User, error
 		logger.Warn(logSender, "error authenticating user: %v, error: %v", username, err)
 		logger.Warn(logSender, "error authenticating user: %v, error: %v", username, err)
 		return user, err
 		return user, err
 	}
 	}
-	if len(user.PublicKey) == 0 {
+	if len(user.PublicKeys) == 0 {
 		return user, errors.New("Invalid credentials")
 		return user, errors.New("Invalid credentials")
 	}
 	}
 
 
-	for i, k := range user.PublicKey {
+	for i, k := range user.PublicKeys {
 		storedPubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
 		storedPubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
 		if err != nil {
 		if err != nil {
 			logger.Warn(logSender, "error parsing stored public key %d for user %v: %v", i, username, err)
 			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)
 			u, err := getUserFromDbRow(nil, rows)
 			// hide password and public key
 			// hide password and public key
 			u.Password = ""
 			u.Password = ""
-			u.PublicKey = []string{}
+			u.PublicKeys = []string{}
 			if err == nil {
 			if err == nil {
 				users = append(users, u)
 				users = append(users, u)
 			} else {
 			} else {
@@ -280,13 +280,7 @@ func getUserFromDbRow(row *sql.Row, rows *sql.Rows) (User, error) {
 		var list []string
 		var list []string
 		err = json.Unmarshal([]byte(publicKey.String), &list)
 		err = json.Unmarshal([]byte(publicKey.String), &list)
 		if err == nil {
 		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 {
 	if permissions.Valid {

+ 3 - 3
dataprovider/sqlqueries.go

@@ -3,7 +3,7 @@ package dataprovider
 import "fmt"
 import "fmt"
 
 
 const (
 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"
 		"used_quota_size,used_quota_files,last_quota_update,upload_bandwidth,download_bandwidth"
 )
 )
 
 
@@ -51,7 +51,7 @@ func getQuotaQuery() string {
 }
 }
 
 
 func getAddUserQuery() 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) 
 		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],
 		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],
 		sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7],
@@ -59,7 +59,7 @@ func getAddUserQuery() string {
 }
 }
 
 
 func getUpdateUserQuery() 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,
 		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[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5],
 		sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8], sqlPlaceholders[9], sqlPlaceholders[10], sqlPlaceholders[11])
 		sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8], sqlPlaceholders[9], sqlPlaceholders[10], sqlPlaceholders[11])

+ 3 - 3
dataprovider/user.go

@@ -39,8 +39,8 @@ type User struct {
 	// Currently, as fallback, there is a clear text password checking but you should not store passwords
 	// 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.
 	// as clear text and this support could be removed at any time, so please don't depend on it.
 	Password string `json:"password,omitempty"`
 	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
 	// The user cannot upload or download files outside this directory. Must be an absolute path
 	HomeDir string `json:"home_dir"`
 	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
 	// 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
 // GetPublicKeysAsJSON returns the public keys as json byte array
 func (u *User) GetPublicKeysAsJSON() ([]byte, error) {
 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
 // GetUID returns a validate uid, suitable for use with os.Chown

+ 10 - 10
scripts/sftpgo_api_cli.py

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

+ 6 - 6
sftpd/sftpd_test.go

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

+ 6 - 0
sql/mysql/20190807.sql

@@ -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 - 0
sql/pgsql/20190807.sql

@@ -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 - 0
sql/sqlite/20190807.sql

@@ -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;