mirror of
https://github.com/haiwen/seafile-server.git
synced 2025-04-27 19:15:07 +00:00
Add upgrade repo enc algo RPC (#743)
* Add upgrade repo enc algo RPC * Add test upgrade repo enc algo * Add test upgrade repo enc algo --------- Co-authored-by: Heran Yang <heran.yang@seafile.com>
This commit is contained in:
parent
7753e8e02a
commit
24b61c929c
@ -15,6 +15,7 @@
|
||||
#include "seafile-error.h"
|
||||
#include "seafile-rpc.h"
|
||||
#include "mq-mgr.h"
|
||||
#include "password-hash.h"
|
||||
|
||||
#ifdef SEAFILE_SERVER
|
||||
#include "web-accesstoken-mgr.h"
|
||||
@ -1131,6 +1132,181 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
set_pwd_hash_to_commit (SeafCommit *commit,
|
||||
SeafRepo *repo,
|
||||
const char *pwd_hash,
|
||||
const char *pwd_hash_algo,
|
||||
const char *pwd_hash_params)
|
||||
{
|
||||
commit->repo_name = g_strdup (repo->name);
|
||||
commit->repo_desc = g_strdup (repo->desc);
|
||||
commit->encrypted = repo->encrypted;
|
||||
commit->repaired = repo->repaired;
|
||||
if (commit->encrypted) {
|
||||
commit->enc_version = repo->enc_version;
|
||||
if (commit->enc_version == 2) {
|
||||
commit->random_key = g_strdup (repo->random_key);
|
||||
} else if (commit->enc_version == 3) {
|
||||
commit->random_key = g_strdup (repo->random_key);
|
||||
commit->salt = g_strdup (repo->salt);
|
||||
} else if (commit->enc_version == 4) {
|
||||
commit->random_key = g_strdup (repo->random_key);
|
||||
commit->salt = g_strdup (repo->salt);
|
||||
}
|
||||
commit->pwd_hash = g_strdup (pwd_hash);
|
||||
commit->pwd_hash_algo = g_strdup (pwd_hash_algo);
|
||||
commit->pwd_hash_params = g_strdup (pwd_hash_params);
|
||||
}
|
||||
commit->no_local_history = repo->no_local_history;
|
||||
commit->version = repo->version;
|
||||
}
|
||||
|
||||
int
|
||||
seafile_upgrade_repo_enc_algorithm (const char *repo_id,
|
||||
const char *user,
|
||||
const char *passwd,
|
||||
const char *pwd_hash_algo,
|
||||
const char *pwd_hash_params,
|
||||
GError **error)
|
||||
{
|
||||
SeafRepo *repo = NULL;
|
||||
SeafCommit *commit = NULL, *parent = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (!user) {
|
||||
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
||||
"No user given");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!passwd || passwd[0] == 0) {
|
||||
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
||||
"Empty passwd");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!is_uuid_valid (repo_id)) {
|
||||
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid repo id");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!pwd_hash_algo) {
|
||||
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid pwd hash algorithm");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (g_strcmp0 (pwd_hash_algo, PWD_HASH_PDKDF2) != 0 &&
|
||||
g_strcmp0 (pwd_hash_algo, PWD_HASH_ARGON2ID) != 0) {
|
||||
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Unsupported pwd hash algorithm");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!pwd_hash_params) {
|
||||
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid pwd hash params");
|
||||
return -1;
|
||||
}
|
||||
|
||||
retry:
|
||||
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
|
||||
if (!repo) {
|
||||
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "No such library");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (g_strcmp0 (pwd_hash_algo, repo->pwd_hash_algo) == 0 &&
|
||||
g_strcmp0 (pwd_hash_params, repo->pwd_hash_params) == 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!repo->encrypted) {
|
||||
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Repo not encrypted");
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (repo->pwd_hash_algo) {
|
||||
if (seafile_pwd_hash_verify_repo_passwd (repo->enc_version, repo_id, passwd, repo->salt,
|
||||
repo->pwd_hash, repo->pwd_hash_algo, repo->pwd_hash_params) < 0) {
|
||||
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Incorrect password");
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
if (seafile_verify_repo_passwd (repo_id, passwd, repo->magic,
|
||||
repo->enc_version, repo->salt) < 0) {
|
||||
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Incorrect password");
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
parent = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
||||
repo->id, repo->version,
|
||||
repo->head->commit_id);
|
||||
if (!parent) {
|
||||
seaf_warning ("Failed to get commit %s:%s.\n",
|
||||
repo->id, repo->head->commit_id);
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
char new_pwd_hash[65]= {0};
|
||||
|
||||
seafile_generate_pwd_hash (repo->enc_version, repo_id, passwd, repo->salt,
|
||||
pwd_hash_algo, pwd_hash_params, new_pwd_hash);
|
||||
|
||||
// To prevent clients that have already synced this repo from overwriting the modified encryption algorithm,
|
||||
// delete all sync tokens.
|
||||
if (seaf_delete_repo_tokens (repo) < 0) {
|
||||
seaf_warning ("Failed to delete repo sync tokens, abort change pwd hash algorithm.\n");
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
memcpy (repo->pwd_hash, new_pwd_hash, 64);
|
||||
|
||||
commit = seaf_commit_new (NULL,
|
||||
repo->id,
|
||||
parent->root_id,
|
||||
user,
|
||||
EMPTY_SHA1,
|
||||
"Changed library password hash algorithm",
|
||||
0);
|
||||
commit->parent_id = g_strdup(parent->commit_id);
|
||||
set_pwd_hash_to_commit (commit, repo, new_pwd_hash, pwd_hash_algo, pwd_hash_params);
|
||||
|
||||
if (seaf_commit_manager_add_commit (seaf->commit_mgr, commit) < 0) {
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
seaf_branch_set_commit (repo->head, commit->commit_id);
|
||||
if (seaf_branch_manager_test_and_update_branch (seaf->branch_mgr,
|
||||
repo->head,
|
||||
parent->commit_id,
|
||||
FALSE, NULL, NULL, NULL) < 0) {
|
||||
seaf_repo_unref (repo);
|
||||
seaf_commit_unref (commit);
|
||||
seaf_commit_unref (parent);
|
||||
repo = NULL;
|
||||
commit = NULL;
|
||||
parent = NULL;
|
||||
goto retry;
|
||||
}
|
||||
|
||||
if (seaf_passwd_manager_is_passwd_set (seaf->passwd_mgr, repo_id, user))
|
||||
seaf_passwd_manager_set_passwd (seaf->passwd_mgr, repo_id,
|
||||
user, passwd, error);
|
||||
|
||||
out:
|
||||
seaf_commit_unref (commit);
|
||||
seaf_commit_unref (parent);
|
||||
seaf_repo_unref (repo);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
seafile_is_repo_owner (const char *email,
|
||||
const char *repo_id,
|
||||
|
@ -512,3 +512,75 @@ split_filename (const char *filename, char **name, char **ext)
|
||||
*ext = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
collect_token_list (SeafDBRow *row, void *data)
|
||||
{
|
||||
GList **p_tokens = data;
|
||||
const char *token;
|
||||
|
||||
token = seaf_db_row_get_column_text (row, 0);
|
||||
*p_tokens = g_list_prepend (*p_tokens, g_strdup(token));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
seaf_delete_repo_tokens (SeafRepo *repo)
|
||||
{
|
||||
int ret = 0;
|
||||
const char *template;
|
||||
GList *token_list = NULL;
|
||||
GList *ptr;
|
||||
GString *token_list_str = g_string_new ("");
|
||||
GString *sql = g_string_new ("");
|
||||
int rc;
|
||||
|
||||
template = "SELECT u.token FROM RepoUserToken as u WHERE u.repo_id=?";
|
||||
rc = seaf_db_statement_foreach_row (seaf->db, template,
|
||||
collect_token_list, &token_list,
|
||||
1, "string", repo->id);
|
||||
if (rc < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (rc == 0)
|
||||
goto out;
|
||||
|
||||
for (ptr = token_list; ptr; ptr = ptr->next) {
|
||||
const char *token = (char *)ptr->data;
|
||||
seaf_message ("delete token: %s\n", token);
|
||||
if (token_list_str->len == 0)
|
||||
g_string_append_printf (token_list_str, "'%s'", token);
|
||||
else
|
||||
g_string_append_printf (token_list_str, ",'%s'", token);
|
||||
}
|
||||
|
||||
/* Note that there is a size limit on sql query. In MySQL it's 1MB by default.
|
||||
* Normally the token_list won't be that long.
|
||||
*/
|
||||
g_string_printf (sql, "DELETE FROM RepoUserToken WHERE token in (%s)",
|
||||
token_list_str->str);
|
||||
rc = seaf_db_statement_query (seaf->db, sql->str, 0);
|
||||
if (rc < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_string_printf (sql, "DELETE FROM RepoTokenPeerInfo WHERE token in (%s)",
|
||||
token_list_str->str);
|
||||
rc = seaf_db_statement_query (seaf->db, sql->str, 0);
|
||||
if (rc < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
g_string_free (token_list_str, TRUE);
|
||||
g_string_free (sql, TRUE);
|
||||
g_list_free_full (token_list, (GDestroyNotify)g_free);
|
||||
|
||||
if (rc < 0) {
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -28,4 +28,7 @@ seaf_parse_auth_token (const char *auth_token);
|
||||
void
|
||||
split_filename (const char *filename, char **name, char **ext);
|
||||
|
||||
int
|
||||
seaf_delete_repo_tokens (SeafRepo *repo);
|
||||
|
||||
#endif
|
||||
|
@ -150,6 +150,14 @@ seafile_change_repo_passwd (const char *repo_id,
|
||||
const char *user,
|
||||
GError **error);
|
||||
|
||||
int
|
||||
seafile_upgrade_repo_enc_algorithm (const char *repo_id,
|
||||
const char *user,
|
||||
const char *passwd,
|
||||
const char *pwd_hash_algo,
|
||||
const char *pwd_hash_params,
|
||||
GError **error);
|
||||
|
||||
/**
|
||||
* seafile_repo_size:
|
||||
*
|
||||
|
@ -640,6 +640,12 @@ class SeafServerThreadedRpcClient(NamedPipeClient):
|
||||
pass
|
||||
change_repo_passwd = seafile_change_repo_passwd
|
||||
|
||||
# Upgrade repo enc algorithm
|
||||
@searpc_func("int", ["string", "string", "string", "string", "string"])
|
||||
def seafile_upgrade_repo_enc_algorithm (repo_id, user, passwd, pwd_hash_algo, pwd_hash_params):
|
||||
pass
|
||||
upgrade_repo_enc_algorithm = seafile_upgrade_repo_enc_algorithm
|
||||
|
||||
# Clean trash
|
||||
@searpc_func("int", ["string", "int"])
|
||||
def clean_up_repo_history(repo_id, keep_days):
|
||||
|
@ -72,6 +72,10 @@ class SeafileAPI(object):
|
||||
def change_repo_passwd(self, repo_id, old_passwd, new_passwd, user):
|
||||
return seafserv_threaded_rpc.change_repo_passwd(repo_id, old_passwd,
|
||||
new_passwd, user)
|
||||
|
||||
def upgrade_repo_enc_algorithm (self, repo_id, user, passwd, pwd_hash_algo, pwd_hash_params):
|
||||
return seafserv_threaded_rpc.upgrade_repo_enc_algorithm (repo_id, user, passwd,
|
||||
pwd_hash_algo, pwd_hash_params)
|
||||
def check_passwd(self, repo_id, magic):
|
||||
return seafserv_threaded_rpc.check_passwd(repo_id, magic)
|
||||
|
||||
|
@ -281,77 +281,6 @@ out:
|
||||
return dir_id;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
collect_token_list (SeafDBRow *row, void *data)
|
||||
{
|
||||
GList **p_tokens = data;
|
||||
const char *token;
|
||||
|
||||
token = seaf_db_row_get_column_text (row, 0);
|
||||
*p_tokens = g_list_prepend (*p_tokens, g_strdup(token));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
delete_repo_tokens (SeafRepo *repo)
|
||||
{
|
||||
int ret = 0;
|
||||
const char *template;
|
||||
GList *token_list = NULL;
|
||||
GList *ptr;
|
||||
GString *token_list_str = g_string_new ("");
|
||||
GString *sql = g_string_new ("");
|
||||
int rc;
|
||||
|
||||
template = "SELECT u.token FROM RepoUserToken as u WHERE u.repo_id=?";
|
||||
rc = seaf_db_statement_foreach_row (seaf->db, template,
|
||||
collect_token_list, &token_list,
|
||||
1, "string", repo->id);
|
||||
if (rc < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (rc == 0)
|
||||
goto out;
|
||||
|
||||
for (ptr = token_list; ptr; ptr = ptr->next) {
|
||||
const char *token = (char *)ptr->data;
|
||||
if (token_list_str->len == 0)
|
||||
g_string_append_printf (token_list_str, "'%s'", token);
|
||||
else
|
||||
g_string_append_printf (token_list_str, ",'%s'", token);
|
||||
}
|
||||
|
||||
/* Note that there is a size limit on sql query. In MySQL it's 1MB by default.
|
||||
* Normally the token_list won't be that long.
|
||||
*/
|
||||
g_string_printf (sql, "DELETE FROM RepoUserToken WHERE token in (%s)",
|
||||
token_list_str->str);
|
||||
rc = seaf_db_statement_query (seaf->db, sql->str, 0);
|
||||
if (rc < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_string_printf (sql, "DELETE FROM RepoTokenPeerInfo WHERE token in (%s)",
|
||||
token_list_str->str);
|
||||
rc = seaf_db_statement_query (seaf->db, sql->str, 0);
|
||||
if (rc < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
g_string_free (token_list_str, TRUE);
|
||||
g_string_free (sql, TRUE);
|
||||
g_list_free_full (token_list, (GDestroyNotify)g_free);
|
||||
|
||||
if (rc < 0) {
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *
|
||||
gen_repair_commit_desc (GList *repaired_files, GList *repaired_folders)
|
||||
{
|
||||
@ -385,7 +314,7 @@ static void
|
||||
reset_commit_to_repair (SeafRepo *repo, SeafCommit *parent, char *new_root_id,
|
||||
GList *repaired_files, GList *repaired_folders)
|
||||
{
|
||||
if (delete_repo_tokens (repo) < 0) {
|
||||
if (seaf_delete_repo_tokens (repo) < 0) {
|
||||
seaf_warning ("Failed to delete repo sync tokens, abort repair.\n");
|
||||
return;
|
||||
}
|
||||
|
@ -120,6 +120,11 @@ static void start_rpc_service (const char *seafile_dir,
|
||||
"seafile_change_repo_passwd",
|
||||
searpc_signature_int__string_string_string_string());
|
||||
|
||||
searpc_server_register_function ("seafserv-threaded-rpcserver",
|
||||
seafile_upgrade_repo_enc_algorithm,
|
||||
"seafile_upgrade_repo_enc_algorithm",
|
||||
searpc_signature_int__string_string_string_string_string());
|
||||
|
||||
searpc_server_register_function ("seafserv-threaded-rpcserver",
|
||||
seafile_is_repo_owner,
|
||||
"seafile_is_repo_owner",
|
||||
|
@ -79,3 +79,36 @@ def test_pwd_hash(rpc, enc_version, algo, params):
|
||||
assert api.is_password_set(repo.id, USER) == 0
|
||||
|
||||
api.remove_repo(repo_id)
|
||||
|
||||
@pytest.mark.parametrize('enc_version, algo, params',
|
||||
[(2, 'pbkdf2_sha256', '1000'), (3, 'pbkdf2_sha256', '1000'), ( 4, 'pbkdf2_sha256', '1000'),
|
||||
(2, 'argon2id', '2,102400,8'), (3, 'argon2id', '2,102400,8'), (4, 'argon2id', '2,102400,8')])
|
||||
def test_upgrade_pwd_hash(enc_version, algo, params):
|
||||
test_repo_name = 'test_enc_repo'
|
||||
test_repo_desc = 'test_enc_repo'
|
||||
test_repo_passwd = 'test_enc_repo'
|
||||
repo_id = api.create_repo(test_repo_name, test_repo_desc, USER,
|
||||
test_repo_passwd, enc_version)
|
||||
assert repo_id
|
||||
|
||||
repo = api.get_repo(repo_id)
|
||||
assert repo
|
||||
assert repo.enc_version == enc_version
|
||||
assert len(repo.random_key) == 96
|
||||
if enc_version > 2:
|
||||
assert len(repo.salt) == 64
|
||||
|
||||
api.upgrade_repo_enc_algorithm (repo.repo_id, USER, test_repo_passwd, algo, params) == 0
|
||||
|
||||
repo = api.get_repo(repo_id)
|
||||
assert repo.pwd_hash_algo == algo;
|
||||
assert repo.pwd_hash_params == params;
|
||||
assert repo.pwd_hash
|
||||
|
||||
assert api.set_passwd(repo.id, USER, test_repo_passwd) == 0
|
||||
assert api.get_decrypt_key(repo.id, USER)
|
||||
assert api.is_password_set(repo.id, USER)
|
||||
assert api.unset_passwd(repo.id, USER) == 0
|
||||
assert api.is_password_set(repo.id, USER) == 0
|
||||
|
||||
api.remove_repo(repo_id)
|
||||
|
Loading…
Reference in New Issue
Block a user