diff --git a/server/repo-perm.c b/server/repo-perm.c index 83d73ee..74ef1e9 100644 --- a/server/repo-perm.c +++ b/server/repo-perm.c @@ -111,12 +111,96 @@ check_repo_share_permission (SeafRepoManager *mgr, return NULL; } +// get dir perm from all dir perms in parent repo +// such as path /a/b, then check /a/b, /a in parent +static char * +get_dir_perm (GHashTable *perms, const char *path) +{ + char *tmp = g_strdup (path); + char *slash; + char *perm = NULL; + + while (g_strcmp0 (tmp, "") != 0) { + perm = g_hash_table_lookup (perms, tmp); + if (perm) + break; + slash = g_strrstr (tmp, "/"); + *slash = '\0'; + } + + g_free (tmp); + + return g_strdup (perm); +} + +static char * +check_perm_on_parent_repo (const char *origin_repo_id, + const char *user, + const char *vpath) +{ + GHashTable *user_perms = NULL; + GHashTable *group_perms = NULL; + SearpcClient *rpc_client; + GList *groups = NULL; + GList *iter; + char *perm = NULL; + + rpc_client = ccnet_create_pooled_rpc_client (seaf->client_pool, + NULL, + "ccnet-threaded-rpcserver"); + if (!rpc_client) { + return NULL; + } + + user_perms = seaf_share_manager_get_shared_dirs_to_user (seaf->share_mgr, + origin_repo_id, + user); + + if (!user_perms) { + ccnet_rpc_client_free (rpc_client); + return NULL; + } + if (g_hash_table_size (user_perms) > 0) { + perm = get_dir_perm (user_perms, vpath); + if (perm) { + g_hash_table_destroy (user_perms); + ccnet_rpc_client_free (rpc_client); + return perm; + } + } + g_hash_table_destroy (user_perms); + + groups = ccnet_get_groups_by_user (rpc_client, user, 1); + ccnet_rpc_client_free (rpc_client); + if (!groups) { + return NULL; + } + + group_perms = seaf_share_manager_get_shared_dirs_to_group (seaf->share_mgr, + origin_repo_id, + groups); + + for (iter = groups; iter; iter = iter->next) + g_object_unref ((GObject *)iter->data); + g_list_free (groups); + + if (!group_perms) { + return NULL; + } + if (g_hash_table_size (group_perms) > 0) { + perm = get_dir_perm (group_perms, vpath); + } + g_hash_table_destroy (group_perms); + + return perm; +} + static char * check_virtual_repo_permission (SeafRepoManager *mgr, const char *repo_id, const char *origin_repo_id, const char *user, - GError **error) + const char *vpath) { char *owner = NULL; char *permission = NULL; @@ -134,11 +218,14 @@ check_virtual_repo_permission (SeafRepoManager *mgr, * from a shared repo by me or directly shared by others to me. * The priority of shared sub-folder is higher than top-level repo. */ - permission = check_repo_share_permission (mgr, repo_id, user); - if (permission) + permission = check_perm_on_parent_repo (origin_repo_id, + user, vpath); + if (permission) { return permission; + } permission = check_repo_share_permission (mgr, origin_repo_id, user); + return permission; } @@ -162,7 +249,7 @@ seaf_repo_manager_check_permission (SeafRepoManager *mgr, if (vinfo) { permission = check_virtual_repo_permission (mgr, repo_id, vinfo->origin_repo_id, - user, error); + user, vinfo->path); goto out; } diff --git a/server/share-mgr.c b/server/share-mgr.c index 512f114..83c40d0 100644 --- a/server/share-mgr.c +++ b/server/share-mgr.c @@ -11,6 +11,7 @@ #include "seaf-db.h" #include "log.h" #include "seafile-error.h" +#include SeafShareManager * seaf_share_manager_new (SeafileSession *seaf) @@ -520,6 +521,111 @@ seaf_share_manager_list_repo_shared_group (SeafShareManager *mgr, return shared_group; } +static gboolean +get_shared_dirs_to_user (SeafDBRow *row, void *data) +{ + GHashTable *dirs = data; + + const char *path = seaf_db_row_get_column_text (row, 0); + const char *perm = seaf_db_row_get_column_text (row, 1); + g_hash_table_replace (dirs, g_strdup (path), g_strdup (perm)); + + return TRUE; +} + +static gboolean +get_shared_dirs_to_group (SeafDBRow *row, void *data) +{ + GHashTable *dirs = data; + + const char *path = seaf_db_row_get_column_text (row, 0); + const char *perm = seaf_db_row_get_column_text (row, 1); + + char *prev_perm = g_hash_table_lookup (dirs, path); + if (g_strcmp0 (perm, prev_perm) != 0 && + (prev_perm == NULL || g_strcmp0 (prev_perm, "r") == 0)) { + g_hash_table_replace (dirs, g_strdup (path), g_strdup (perm)); + } + + return TRUE; +} + +// Conver group id list to comma separated str +// [1, 2, 3] -> 1,2,3 +static GString * +convert_group_list_to_str (GList *groups) +{ + GList *iter = groups; + CcnetGroup *group; + int group_id; + GString *group_ids = g_string_new (""); + + for (; iter; iter = iter->next) { + group = iter->data; + g_object_get (group, "id", &group_id, NULL); + g_string_append_printf (group_ids, "%d,", group_id); + } + group_ids = g_string_erase (group_ids, group_ids->len - 1, 1); + + return group_ids; +} + +GHashTable * +seaf_share_manager_get_shared_dirs_to_user (SeafShareManager *mgr, + const char *orig_repo_id, + const char *to_email) +{ + GHashTable *dirs; + char *sql; + + dirs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + sql = "SELECT v.path, s.permission FROM SharedRepo s, VirtualRepo v WHERE " + "s.repo_id = v.repo_id AND s.to_email = ? AND v.origin_repo = ?"; + + int ret = seaf_db_statement_foreach_row (mgr->seaf->db, sql, get_shared_dirs_to_user, + dirs, 2, "string", to_email, + "string", orig_repo_id); + if (ret < 0) { + seaf_warning ("Failed to get all shared folder perms " + "in parent repo %.8s for user %s.\n", orig_repo_id, to_email); + g_hash_table_destroy (dirs); + return NULL; + } + + return dirs; +} + +GHashTable * +seaf_share_manager_get_shared_dirs_to_group (SeafShareManager *mgr, + const char *orig_repo_id, + GList *groups) +{ + GHashTable *dirs; + GString *group_ids; + char *sql; + + dirs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + group_ids = convert_group_list_to_str (groups); + sql = g_strdup_printf ("SELECT v.path, s.permission " + "FROM RepoGroup s, VirtualRepo v WHERE " + "s.repo_id = v.repo_id AND v.origin_repo = ? " + "AND s.group_id in (%s)", group_ids->str); + + int ret = seaf_db_statement_foreach_row (mgr->seaf->db, sql, get_shared_dirs_to_group, + dirs, 1, "string", orig_repo_id); + g_free (sql); + g_string_free (group_ids, TRUE); + + if (ret < 0) { + seaf_warning ("Failed to get all shared folder perm from parent repo %.8s " + "to all user groups.\n", orig_repo_id); + g_hash_table_destroy (dirs); + return NULL; + } + + return dirs; +} + int seaf_share_manager_remove_share (SeafShareManager *mgr, const char *repo_id, const char *from_email, const char *to_email) diff --git a/server/share-mgr.h b/server/share-mgr.h index 1567109..6dcbbe3 100644 --- a/server/share-mgr.h +++ b/server/share-mgr.h @@ -58,6 +58,16 @@ seaf_share_manager_list_repo_shared_group (SeafShareManager *mgr, const char *repo_id, GError **error); +GHashTable * +seaf_share_manager_get_shared_dirs_to_user (SeafShareManager *mgr, + const char *orig_repo_id, + const char *to_email); + +GHashTable * +seaf_share_manager_get_shared_dirs_to_group (SeafShareManager *mgr, + const char *orig_repo_id, + GList *groups); + int seaf_share_manager_remove_share (SeafShareManager *mgr, const char *repo_id, const char *from_email, const char *to_email); diff --git a/server/virtual-repo.c b/server/virtual-repo.c index c53a9e3..463e0ef 100644 --- a/server/virtual-repo.c +++ b/server/virtual-repo.c @@ -164,32 +164,20 @@ get_existing_virtual_repo (SeafRepoManager *mgr, "string", origin_repo_id, "string", path); } -char * -seaf_repo_manager_create_virtual_repo (SeafRepoManager *mgr, - const char *origin_repo_id, - const char *path, - const char *repo_name, - const char *repo_desc, - const char *owner, - const char *passwd, - GError **error) +static char * +create_virtual_repo_common (SeafRepoManager *mgr, + const char *origin_repo_id, + const char *path, + const char *repo_name, + const char *repo_desc, + const char *owner, + const char *passwd, + GError **error) { SeafRepo *origin_repo = NULL; SeafCommit *origin_head = NULL; char *repo_id = NULL; char *dir_id = NULL; - char *orig_owner = NULL; - - if (seaf_repo_manager_is_virtual_repo (mgr, origin_repo_id)) { - g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, - "Cannot create sub-library from a sub-library"); - return NULL; - } - - repo_id = get_existing_virtual_repo (mgr, origin_repo_id, path); - if (repo_id) { - return repo_id; - } origin_repo = seaf_repo_manager_get_repo (mgr, origin_repo_id); if (!origin_repo) { @@ -261,26 +249,16 @@ seaf_repo_manager_create_virtual_repo (SeafRepoManager *mgr, goto error; } - orig_owner = seaf_repo_manager_get_repo_owner (mgr, origin_repo_id); - if (do_create_virtual_repo (mgr, origin_repo, repo_id, repo_name, repo_desc, - dir_id, orig_owner, passwd, error) < 0) + dir_id, owner, passwd, error) < 0) goto error; - if (seaf_repo_manager_set_repo_owner (mgr, repo_id, orig_owner) < 0) { - seaf_warning ("Failed to set repo owner for %.10s.\n", repo_id); - g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, - "Failed to set repo owner."); - goto error; - } - /* The size of virtual repo is non-zero at the beginning. */ update_repo_size (repo_id); seaf_repo_unref (origin_repo); seaf_commit_unref (origin_head); g_free (dir_id); - g_free (orig_owner); return repo_id; error: @@ -288,10 +266,101 @@ error: seaf_commit_unref (origin_head); g_free (repo_id); g_free (dir_id); - g_free (orig_owner); return NULL; } +static char * +canonical_vrepo_path (const char *path) +{ + char *ret = NULL; + + if (path[0] != '/') + ret = g_strconcat ("/", path, NULL); + else + ret = g_strdup(path); + + int len = strlen(ret); + int i = len - 1; + while (i >= 0 && ret[i] == '/') + ret[i--] = 0; + + return ret; +} + +char * +seaf_repo_manager_create_virtual_repo (SeafRepoManager *mgr, + const char *origin_repo_id, + const char *path, + const char *repo_name, + const char *repo_desc, + const char *owner, + const char *passwd, + GError **error) +{ + char *repo_id = NULL; + char *orig_owner = NULL; + char *canon_path = NULL; + SeafVirtRepo *vrepo = NULL; + char *r_origin_repo_id = NULL; + char *r_path = NULL; + + if (g_strcmp0 (path, "/") == 0) { + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, + "Invalid path"); + return NULL; + } + + canon_path = canonical_vrepo_path (path); + vrepo = seaf_repo_manager_get_virtual_repo_info (mgr, origin_repo_id); + if (vrepo) { + // virtual repo + r_path = g_strconcat(vrepo->path, canon_path, NULL); + r_origin_repo_id = g_strdup (vrepo->origin_repo_id); + seaf_virtual_repo_info_free (vrepo); + repo_id = get_existing_virtual_repo (mgr, r_origin_repo_id, r_path); + if (repo_id) { + g_free (r_origin_repo_id); + g_free (r_path); + g_free (canon_path); + return repo_id; + } + } else { + r_path = g_strdup (canon_path); + r_origin_repo_id = g_strdup (origin_repo_id); + repo_id = get_existing_virtual_repo (mgr, r_origin_repo_id, r_path); + if (repo_id) { + g_free (r_origin_repo_id); + g_free (r_path); + g_free (canon_path); + return repo_id; + } + } + + orig_owner = seaf_repo_manager_get_repo_owner (mgr, r_origin_repo_id); + + repo_id = create_virtual_repo_common (mgr, r_origin_repo_id, r_path, + repo_name, repo_desc, orig_owner, + passwd, error); + if (!repo_id) { + goto out; + } + + if (seaf_repo_manager_set_repo_owner (mgr, repo_id, orig_owner) < 0) { + seaf_warning ("Failed to set repo owner for %.10s.\n", repo_id); + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, + "Failed to set repo owner."); + g_free (repo_id); + repo_id = NULL; + } + +out: + g_free (orig_owner); + g_free (r_origin_repo_id); + g_free (r_path); + g_free (canon_path); + return repo_id; +} + static gboolean load_virtual_info (SeafDBRow *row, void *p_vinfo) { diff --git a/tests/test_share_and_perm/test_shared_repo_perm.py b/tests/test_share_and_perm/test_shared_repo_perm.py index 6a06ed6..8c28cfd 100644 --- a/tests/test_share_and_perm/test_shared_repo_perm.py +++ b/tests/test_share_and_perm/test_shared_repo_perm.py @@ -197,3 +197,20 @@ def test_get_shared_users_by_repo(repo, group, permission): api.remove_share(repo.id, USER, USER2) api.group_unshare_repo(repo.id, group.id, USER) + +@pytest.mark.parametrize('permission', ['r', 'rw']) +def test_subdir_permission_in_virtual_repo(repo, group, permission): + api.post_dir(repo.id, '/dir1', 'subdir1', USER) + api.post_dir(repo.id, '/dir2', 'subdir2', USER) + + v_repo_id_1 = api.share_subdir_to_user(repo.id, '/dir1', USER, USER2, permission) + v_subdir_repo_id_1 = api.create_virtual_repo(v_repo_id_1, '/subdir1', 'subdir1', 'test_desc', USER, passwd='') + assert api.check_permission(v_subdir_repo_id_1, USER2) == permission + + assert ccnet_api.group_add_member(group.id, USER, USER2) == 0 + v_repo_id_2 = api.share_subdir_to_group(repo.id, '/dir2', USER, group.id, permission) + v_subdir_repo_id_2 = api.create_virtual_repo(v_repo_id_2, '/subdir2', 'subdir2', 'test_desc', USER, passwd='') + assert api.check_permission(v_subdir_repo_id_2, USER2) == permission + + assert api.unshare_subdir_for_user(repo.id, '/dir1', USER, USER2) == 0 + assert api.unshare_subdir_for_group(repo.id, '/dir2', USER, group.id) == 0