1
0
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:
feiniks 2025-03-19 11:31:05 +08:00 committed by GitHub
parent 7753e8e02a
commit 24b61c929c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 308 additions and 72 deletions

View File

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

View File

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

View File

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

View File

@ -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:
*

View File

@ -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):

View File

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

View File

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

View File

@ -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",

View File

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