mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 00:50:31 +00:00
rename public_key in public_keys
remove compatibility layer to convert public keys newline delimited in json list
This commit is contained in:
parent
5ad222fc53
commit
2aca4479a5
15 changed files with 61 additions and 49 deletions
|
@ -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 ./...
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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
20
scripts/sftpgo_api_cli.py
Normal file → Executable 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":
|
||||
|
|
|
@ -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
6
sql/mysql/20190807.sql
Normal 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
6
sql/pgsql/20190807.sql
Normal 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
6
sql/sqlite/20190807.sql
Normal 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;
|
Loading…
Reference in a new issue