Explorar o código

convert public key from newline delimited string to a real array

Added a compatibility layer that will convert newline delimited keys to array
when the user is fetched from the database.
This code will be removed in future versions please update your public keys,
you only need to resave the users using the REST API.
Nicola Murino %!s(int64=6) %!d(string=hai) anos
pai
achega
8d4964c16d

+ 1 - 1
README.md

@@ -162,7 +162,7 @@ For each account the following properties can be configured:
 
 
 - `username`
 - `username`
 - `password` used for password authentication. For users created using SFTPGo REST API the password will be stored using argon2id hashing algo. SFTPGo supports checking passwords stored with bcrypt too. 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` used for password authentication. For users created using SFTPGo REST API the password will be stored using argon2id hashing algo. SFTPGo supports checking passwords stored with bcrypt too. 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.
-- `public_key` used for public key authentication. At least one between password and public key is mandatory. Multiple public keys are supported, newline delimited (unix line break unescaped).
+- `public_key` array of public keys. At least one public key or the password is mandatory.
 - `home_dir` The user cannot upload or download files outside this directory. Must be an absolute path
 - `home_dir` The user cannot upload or download files outside this directory. Must be an absolute path
 - `uid`, `gid`. If sftpgo runs as root system user then the created files and directories will be assigned to this system uid/gid. Ignored on windows and if sftpgo runs as non root user: in this case files and directories for all SFTP users will be owned by the system user that runs sftpgo.
 - `uid`, `gid`. If sftpgo runs as root system user then the created files and directories will be assigned to this system uid/gid. Ignored on windows and if sftpgo runs as non root user: in this case files and directories for all SFTP users will be owned by the system user that runs sftpgo.
 - `max_sessions` maximum concurrent sessions. 0 means unlimited
 - `max_sessions` maximum concurrent sessions. 0 means unlimited

+ 6 - 6
api/api_test.go

@@ -126,7 +126,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 = ""
+	u.PublicKey = []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)
@@ -182,22 +182,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 = invalidPubKey
+	u.PublicKey = []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 = validPubKey
+	u.PublicKey = []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 = validPubKey + "\n" + invalidPubKey
+	user.PublicKey = []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 = validPubKey + "\n" + validPubKey + "\n" + validPubKey
+	user.PublicKey = []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)
@@ -238,7 +238,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 = ""
+	user.PublicKey = []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/internal_test.go

@@ -42,12 +42,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 = "pub key"
+	actual.PublicKey = []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 = ""
+	actual.PublicKey = []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")

+ 12 - 10
api/schema/openapi.yaml

@@ -12,7 +12,7 @@ paths:
       tags:
       tags:
       - connections
       - connections
       summary: Get the active sftp users and info about their uploads/downloads
       summary: Get the active sftp users and info about their uploads/downloads
-      operationId: get_connections
+      operationId: get_sftp_connections
       responses:
       responses:
         200:
         200:
           description: successful operation
           description: successful operation
@@ -27,7 +27,7 @@ paths:
       tags:
       tags:
       - connections
       - connections
       summary: Terminate an active SFTP connection
       summary: Terminate an active SFTP connection
-      operationId: close_connection
+      operationId: close_sftp_connection
       parameters: 
       parameters: 
       - name: connectionID
       - name: connectionID
         in: path
         in: path
@@ -81,7 +81,7 @@ paths:
       tags:
       tags:
       - quota
       - quota
       summary: Get the active quota scans
       summary: Get the active quota scans
-      operationId: get_quota_scan
+      operationId: get_quota_scans
       responses:
       responses:
         200:
         200:
           description: successful operation
           description: successful operation
@@ -297,7 +297,7 @@ paths:
       - users
       - users
       summary: Find user by ID
       summary: Find user by ID
       description: For security reasons password and public key are empty in the response
       description: For security reasons password and public key are empty in the response
-      operationId: getUserByID
+      operationId: get_user_by_id
       parameters: 
       parameters: 
       - name: userID
       - name: userID
         in: path
         in: path
@@ -356,8 +356,8 @@ paths:
     put:
     put:
       tags:
       tags:
       - users
       - users
-      summary: Update an user
-      operationId: updateUser
+      summary: Update an existing user
+      operationId: update_user
       parameters: 
       parameters: 
       - name: userID
       - name: userID
         in: path
         in: path
@@ -426,8 +426,8 @@ paths:
     delete:
     delete:
       tags:
       tags:
       - users
       - users
-      summary: Delete an user
-      operationId: deleteUser
+      summary: Delete an existing user
+      operationId: delete_user
       parameters: 
       parameters: 
       - name: userID
       - name: userID
         in: path
         in: path
@@ -524,9 +524,11 @@ components:
           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_key:
-          type: string
+          type: array
+          items:
+            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. Multiple public keys are supported, newline delimited, when adding or modifying an user
+          description: a password or at least one public key are mandatory. For security reasons this field is omitted when you search/get users.
         home_dir:
         home_dir:
           type: string
           type: string
           description: path to the user home directory. The user cannot upload or download files outside this directory. SFTPGo tries to automatically create this folder if missing. Must be an absolute path
           description: path to the user home directory. The user cannot upload or download files outside this directory. SFTPGo tries to automatically create this folder if missing. Must be an absolute path

+ 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 = ""
+		user.PublicKey = []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 = ""
+			user.PublicKey = []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)

+ 1 - 0
config/config.go

@@ -46,6 +46,7 @@ func init() {
 				Command:             "",
 				Command:             "",
 				HTTPNotificationURL: "",
 				HTTPNotificationURL: "",
 			},
 			},
+			Keys: []sftpd.Key{},
 		},
 		},
 		ProviderConf: dataprovider.Config{
 		ProviderConf: dataprovider.Config{
 			Driver:           "sqlite",
 			Driver:           "sqlite",

+ 5 - 8
dataprovider/dataprovider.go

@@ -226,22 +226,19 @@ func validateUser(user *User) error {
 			return &ValidationError{err: fmt.Sprintf("Invalid permission: %v", p)}
 			return &ValidationError{err: fmt.Sprintf("Invalid permission: %v", p)}
 		}
 		}
 	}
 	}
-	if !strings.HasPrefix(user.Password, argonPwdPrefix) {
+	if len(user.Password) > 0 && !strings.HasPrefix(user.Password, argonPwdPrefix) {
 		pwd, err := argon2id.CreateHash(user.Password, argon2id.DefaultParams)
 		pwd, err := argon2id.CreateHash(user.Password, argon2id.DefaultParams)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
 		user.Password = pwd
 		user.Password = pwd
 	}
 	}
-	if len(user.PublicKey) > 0 {
-		for i, k := range strings.Split(user.PublicKey, "\n") {
-			_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
-			if err != nil {
-				return &ValidationError{err: fmt.Sprintf("Could not parse key nr. %d: %s", i, err)}
-			}
+	for i, k := range user.PublicKey {
+		_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
+		if err != nil {
+			return &ValidationError{err: fmt.Sprintf("Could not parse key nr. %d: %s", i, err)}
 		}
 		}
 	}
 	}
-
 	return nil
 	return nil
 }
 }
 
 

+ 29 - 6
dataprovider/sqlcommon.go

@@ -39,6 +39,11 @@ func sqlCommonValidateUserAndPass(username string, password string) (User, error
 	if err != nil {
 	if err != nil {
 		logger.Warn(logSender, "error authenticating user: %v, error: %v", username, err)
 		logger.Warn(logSender, "error authenticating user: %v, error: %v", username, err)
 	} else {
 	} else {
+		// even if the password is empty inside the database an empty user password
+		// will be refused anyway so it cannot match, additional check to be paranoid
+		if len(user.Password) == 0 {
+			return user, errors.New("Credentials cannot be null or empty")
+		}
 		var match bool
 		var match bool
 		if strings.HasPrefix(user.Password, argonPwdPrefix) {
 		if strings.HasPrefix(user.Password, argonPwdPrefix) {
 			match, err = argon2id.ComparePasswordAndHash(password, user.Password)
 			match, err = argon2id.ComparePasswordAndHash(password, user.Password)
@@ -77,7 +82,7 @@ func sqlCommonValidateUserAndPubKey(username string, pubKey string) (User, error
 		return user, errors.New("Invalid credentials")
 		return user, errors.New("Invalid credentials")
 	}
 	}
 
 
-	for i, k := range strings.Split(user.PublicKey, "\n") {
+	for i, k := range user.PublicKey {
 		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)
@@ -170,7 +175,11 @@ func sqlCommonAddUser(user User) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	_, err = stmt.Exec(user.Username, user.Password, user.PublicKey, user.HomeDir, user.UID, user.GID, user.MaxSessions, user.QuotaSize,
+	publicKeys, err := user.GetPublicKeysAsJSON()
+	if err != nil {
+		return err
+	}
+	_, err = stmt.Exec(user.Username, user.Password, string(publicKeys), user.HomeDir, user.UID, user.GID, user.MaxSessions, user.QuotaSize,
 		user.QuotaFiles, string(permissions), user.UploadBandwidth, user.DownloadBandwidth)
 		user.QuotaFiles, string(permissions), user.UploadBandwidth, user.DownloadBandwidth)
 	return err
 	return err
 }
 }
@@ -191,8 +200,12 @@ func sqlCommonUpdateUser(user User) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	_, err = stmt.Exec(user.Password, user.PublicKey, user.HomeDir, user.UID, user.GID, user.MaxSessions, user.QuotaSize,
-		user.QuotaFiles, permissions, user.UploadBandwidth, user.DownloadBandwidth, user.ID)
+	publicKeys, err := user.GetPublicKeysAsJSON()
+	if err != nil {
+		return err
+	}
+	_, err = stmt.Exec(user.Password, string(publicKeys), user.HomeDir, user.UID, user.GID, user.MaxSessions, user.QuotaSize,
+		user.QuotaFiles, string(permissions), user.UploadBandwidth, user.DownloadBandwidth, user.ID)
 	return err
 	return err
 }
 }
 
 
@@ -229,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 = ""
+			u.PublicKey = []string{}
 			if err == nil {
 			if err == nil {
 				users = append(users, u)
 				users = append(users, u)
 			} else {
 			} else {
@@ -264,7 +277,17 @@ func getUserFromDbRow(row *sql.Row, rows *sql.Rows) (User, error) {
 		user.Password = password.String
 		user.Password = password.String
 	}
 	}
 	if publicKey.Valid {
 	if publicKey.Valid {
-		user.PublicKey = publicKey.String
+		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)
+		}
 	}
 	}
 	if permissions.Valid {
 	if permissions.Valid {
 		var list []string
 		var list []string

+ 7 - 2
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 public key is mandatory
-	PublicKey string `json:"public_key,omitempty"`
+	// PublicKey used for public key authentication. At least one between password and a public key is mandatory
+	PublicKey []string `json:"public_key,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
@@ -80,6 +80,11 @@ func (u *User) GetPermissionsAsJSON() ([]byte, error) {
 	return json.Marshal(u.Permissions)
 	return json.Marshal(u.Permissions)
 }
 }
 
 
+// GetPublicKeysAsJSON returns the public keys as json byte array
+func (u *User) GetPublicKeysAsJSON() ([]byte, error) {
+	return json.Marshal(u.PublicKey)
+}
+
 // GetUID returns a validate uid, suitable for use with os.Chown
 // GetUID returns a validate uid, suitable for use with os.Chown
 func (u *User) GetUID() int {
 func (u *User) GetUID() int {
 	if u.UID <= 0 || u.UID > 65535 {
 	if u.UID <= 0 || u.UID > 65535 {

+ 6 - 6
sftpd/sftpd_test.go

@@ -465,7 +465,7 @@ func TestHomeSpecialChars(t *testing.T) {
 
 
 func TestLogin(t *testing.T) {
 func TestLogin(t *testing.T) {
 	u := getTestUser(false)
 	u := getTestUser(false)
-	u.PublicKey = testPubKey
+	u.PublicKey = []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)
@@ -497,7 +497,7 @@ func TestLogin(t *testing.T) {
 		defer client.Close()
 		defer client.Close()
 	}
 	}
 	// testPubKey1 is not authorized
 	// testPubKey1 is not authorized
-	user.PublicKey = testPubKey1
+	user.PublicKey = []string{testPubKey1}
 	user.Password = ""
 	user.Password = ""
 	_, err = api.UpdateUser(user, http.StatusOK)
 	_, err = api.UpdateUser(user, http.StatusOK)
 	if err != nil {
 	if err != nil {
@@ -509,7 +509,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 = testPubKey1 + "\n" + testPubKey
+	user.PublicKey = []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 {
@@ -538,7 +538,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 = ""
+	user.PublicKey = []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 {
@@ -571,7 +571,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 = ""
+	user.PublicKey = []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 {
@@ -1178,7 +1178,7 @@ func getTestUser(usePubKey bool) dataprovider.User {
 		Permissions: allPerms,
 		Permissions: allPerms,
 	}
 	}
 	if usePubKey {
 	if usePubKey {
-		user.PublicKey = testPubKey
+		user.PublicKey = []string{testPubKey}
 		user.Password = ""
 		user.Password = ""
 	}
 	}
 	return user
 	return user