document-portal: Add AddFull() operation

This allows you to add multiple paths at the same time, plus
grant an app access to it, plus it returns the fuse mount path.

This allows you to avoid a lot of roundtrip in common cases.
tingping/wmclass
Alexander Larsson 2017-05-19 17:01:46 +02:00
parent bda7575e79
commit 6ce8521b64
5 changed files with 301 additions and 33 deletions

View File

@ -96,6 +96,32 @@
<arg type='s' name='doc_id' direction='out'/>
</method>
<!--
AddFull:
@o_path_fds: open file descriptors for the files to export
@flags: flags, 1 == reuse_existing, 2 == persistent
@app_id: an application ID, or empty string
@permissions: the permissions to grant, possible values are 'read', 'write', 'grant-permissions' and 'delete'
@doc_ids: the IDs of the files in the document store
@extra_info: Extra info returned
Adds multiple files to the document store. The file is passed in the
form of an open file descriptor to prove that the caller has
access to the file.
Additionally, if app_id is specified, it will be given the permissions
listed in GrantPermission.
The method also returns some extra info that can be used to avoid
multiple roundtrips. For now it only contains as "mountpoint", the
fuse mountpoint of the document portal.
-->
<method name="AddFull">
<arg type='ah' name='o_path_fds' direction='in'/>
<arg type='u' name='flags' direction='in'/>
<arg type='s' name='app_id' direction='in'/>
<arg type='as' name='permissions' direction='in'/>
<arg type='as' name='doc_ids' direction='out'/>
<arg type='a{sv}' name='extra_out' direction='out'/>
</method>
<!--
GrantPermissions:
@doc_id: the ID of the file in the document store

View File

@ -12,6 +12,7 @@ $(xdp_dbus_built_sources) : data/org.freedesktop.portal.Documents.xml
--c-namespace XdpDbus \
--generate-c-code $(builddir)/document-portal/xdp-dbus \
--annotate "org.freedesktop.portal.Documents.Add()" org.gtk.GDBus.C.UnixFD "yes" \
--annotate "org.freedesktop.portal.Documents.AddFull()" org.gtk.GDBus.C.UnixFD "yes" \
$(srcdir)/data/org.freedesktop.portal.Documents.xml \
$(NULL)

View File

@ -12,6 +12,13 @@ typedef enum {
XDP_PERMISSION_FLAGS_ALL = ((1 << 4) - 1)
} XdpPermissionFlags;
typedef enum {
XDP_ADD_FLAGS_REUSE_EXISTING = (1 << 0),
XDP_ADD_FLAGS_PERSISTENT = (1 << 1),
XDP_ADD_FLAGS_FLAGS_ALL = ((1 << 2) - 1)
} XdpAddFullFlags;
G_END_DECLS
#endif /* XDP_ENUMS_H */

View File

@ -452,6 +452,36 @@ validate_fd (int fd,
return TRUE;
}
static char *
verify_existing_document (struct stat *st_buf, gboolean reuse_existing)
{
g_autoptr(FlatpakDbEntry) old_entry = NULL;
g_autofree char *id = NULL;
g_assert (st_buf->st_dev == fuse_dev);
/* The passed in fd is on the fuse filesystem itself */
id = xdp_fuse_lookup_id_for_inode (st_buf->st_ino);
g_debug ("path on fuse, id %s", id);
if (id == NULL)
return NULL;
/* Don't lock the db before doing the fuse call above, because it takes takes a lock
that can block something calling back, causing a deadlock on the db lock */
AUTOLOCK (db);
/* If the entry doesn't exist anymore, fail. Also fail if not
* reuse_existing, because otherwise the user could use this to
* get a copy with permissions and thus escape later permission
* revocations
*/
old_entry = flatpak_db_lookup (db, id);
if (old_entry == NULL || !reuse_existing)
return NULL;
return g_steal_pointer (&id);
}
static void
portal_add (GDBusMethodInvocation *invocation,
GVariant *parameters,
@ -490,10 +520,7 @@ portal_add (GDBusMethodInvocation *invocation,
if (st_buf.st_dev == fuse_dev)
{
/* The passed in fd is on the fuse filesystem itself */
g_autoptr(FlatpakDbEntry) old_entry = NULL;
id = xdp_fuse_lookup_id_for_inode (st_buf.st_ino);
g_debug ("path on fuse, id %s", id);
id = verify_existing_document (&st_buf, reuse_existing);
if (id == NULL)
{
g_dbus_method_invocation_return_error (invocation,
@ -501,26 +528,6 @@ portal_add (GDBusMethodInvocation *invocation,
"Invalid fd passed");
return;
}
/* Don't lock the db before doing the fuse call above, because it takes takes a lock
that can block something calling back, causing a deadlock on the db lock */
AUTOLOCK (db);
/* If the entry doesn't exist anymore, fail. Also fail if not
* reuse_existing, because otherwise the user could use this to
* get a copy with permissions and thus escape later permission
* revocations
*/
old_entry = flatpak_db_lookup (db, id);
if (old_entry == NULL ||
!reuse_existing)
{
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid fd passed");
return;
}
}
else
{
@ -531,21 +538,18 @@ portal_add (GDBusMethodInvocation *invocation,
if (app_id[0] != '\0')
{
g_autoptr(FlatpakDbEntry) entry = NULL;
g_autoptr(FlatpakDbEntry) entry = flatpak_db_lookup (db, id);
XdpPermissionFlags perms =
XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS |
XDP_PERMISSION_FLAGS_READ |
XDP_PERMISSION_FLAGS_WRITE;
{
entry = flatpak_db_lookup (db, id);
/* If its a unique one its safe for the creator to
delete it at will */
if (!reuse_existing)
perms |= XDP_PERMISSION_FLAGS_DELETE;
/* If its a unique one its safe for the creator to
delete it at will */
if (!reuse_existing)
perms |= XDP_PERMISSION_FLAGS_DELETE;
do_set_permissions (entry, id, app_id, perms);
}
do_set_permissions (entry, id, app_id, perms);
}
}
@ -559,6 +563,151 @@ portal_add (GDBusMethodInvocation *invocation,
g_variant_new ("(s)", id));
}
static void
portal_add_full (GDBusMethodInvocation *invocation,
GVariant *parameters,
const char *app_id)
{
GDBusMessage *message;
GUnixFDList *fd_list;
char *id;
int fd_id, fd, fds_len;
char path_buffer[PATH_MAX + 1];
const int *fds = NULL;
struct stat st_buf;
gboolean reuse_existing, persistent;
GError *error = NULL;
guint32 flags = 0;
GKeyFile *app_info = g_object_get_data (G_OBJECT (invocation), "app-info");
g_autoptr(GVariant) array = NULL;
const char *target_app_id;
g_autofree const char **permissions = NULL;
g_autofree guint32 *arg_fds = NULL;
g_autoptr(GPtrArray) ids = g_ptr_array_new_with_free_func (g_free);
g_autoptr(GPtrArray) paths = g_ptr_array_new_with_free_func (g_free);
g_autofree struct stat *real_parent_st_bufs = NULL;
int i;
gsize n_args;
XdpPermissionFlags target_perms;
GVariantBuilder builder;
g_variant_get (parameters, "(@ahus^a&s)",
&array, &flags, &target_app_id, &permissions);
if ((flags & ~XDP_ADD_FLAGS_FLAGS_ALL) != 0)
{
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid flags");
return;
}
reuse_existing = (flags & XDP_ADD_FLAGS_REUSE_EXISTING) != 0;
persistent = (flags & XDP_ADD_FLAGS_PERSISTENT) != 0;
target_perms = xdp_parse_permissions (permissions);
n_args = g_variant_n_children (array);
g_ptr_array_set_size (ids, n_args + 1);
g_ptr_array_set_size (paths, n_args + 1);
real_parent_st_bufs = g_new0 (struct stat, n_args);
message = g_dbus_method_invocation_get_message (invocation);
fd_list = g_dbus_message_get_unix_fd_list (message);
if (fd_list != NULL)
fds = g_unix_fd_list_peek_fds (fd_list, &fds_len);
for (i = 0; i < n_args; i++)
{
g_variant_get_child (array, i, "h", &fd_id);
fd = -1;
if (fds != NULL & fd_id < fds_len)
fd = fds[fd_id];
if (!validate_fd (fd, app_info, &st_buf, &real_parent_st_bufs[i], path_buffer, &error))
{
g_dbus_method_invocation_take_error (invocation, error);
return;
}
if (st_buf.st_dev == fuse_dev)
{
/* The passed in fd is on the fuse filesystem itself */
id = verify_existing_document (&st_buf, reuse_existing);
if (id == NULL)
{
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid fd passed");
return;
}
g_ptr_array_index(ids,i) = id;
}
else
{
g_ptr_array_index(paths,i) = g_strdup (path_buffer);
}
}
for (i = 0; i < n_args; i++)
{
AUTOLOCK (db);
if (g_ptr_array_index(ids,i) == NULL)
{
const char *path = g_ptr_array_index(paths,i);
g_assert (path != NULL);
id = do_create_doc (&real_parent_st_bufs[i], path, reuse_existing, persistent);
g_ptr_array_index(ids,i) = id;
if (app_id[0] != '\0' && strcmp (app_id, target_app_id) != 0)
{
g_autoptr(FlatpakDbEntry) entry = flatpak_db_lookup (db, id);;
XdpPermissionFlags perms =
XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS |
XDP_PERMISSION_FLAGS_READ |
XDP_PERMISSION_FLAGS_WRITE;
/* If its a unique one its safe for the creator to
delete it at will */
if (!reuse_existing)
perms |= XDP_PERMISSION_FLAGS_DELETE;
do_set_permissions (entry, id, app_id, perms);
}
if (target_app_id[0] != '\0' && target_perms != 0)
{
g_autoptr(FlatpakDbEntry) entry = flatpak_db_lookup (db, id);
do_set_permissions (entry, id, target_app_id, target_perms);
}
}
}
/* Invalidate with lock dropped to avoid deadlock */
for (i = 0; i < n_args; i++)
{
id = g_ptr_array_index (ids,i);
g_assert (id != NULL);
xdp_fuse_invalidate_doc_app (id, NULL);
if (app_id[0] != '\0')
xdp_fuse_invalidate_doc_app (id, app_id);
if (target_app_id[0] != '\0' && target_perms != 0)
xdp_fuse_invalidate_doc_app (id, target_app_id);
}
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&builder, "{s@v}", "mountpoint",
g_variant_new_variant (g_variant_new_string (xdp_fuse_get_mountpoint ())));
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(^as@a{sv})",
(char **)ids->pdata,
g_variant_builder_end (&builder)));
}
static void
portal_add_named (GDBusMethodInvocation *invocation,
GVariant *parameters,
@ -878,6 +1027,7 @@ on_bus_acquired (GDBusConnection *connection,
g_signal_connect_swapped (dbus_api, "handle-get-mount-point", G_CALLBACK (handle_get_mount_point), NULL);
g_signal_connect_swapped (dbus_api, "handle-add", G_CALLBACK (handle_method), portal_add);
g_signal_connect_swapped (dbus_api, "handle-add-named", G_CALLBACK (handle_method), portal_add_named);
g_signal_connect_swapped (dbus_api, "handle-add-full", G_CALLBACK (handle_method), portal_add_full);
g_signal_connect_swapped (dbus_api, "handle-grant-permissions", G_CALLBACK (handle_method), portal_grant_permissions);
g_signal_connect_swapped (dbus_api, "handle-revoke-permissions", G_CALLBACK (handle_method), portal_revoke_permissions);
g_signal_connect_swapped (dbus_api, "handle-delete", G_CALLBACK (handle_method), portal_delete);

View File

@ -328,6 +328,89 @@ test_recursive_doc (void)
g_assert_cmpstr (id, ==, id3);
}
static void
test_create_docs (void)
{
g_autofree char *doc_path = NULL;
g_autofree char *doc_app_path = NULL;
g_autofree char *host_path = NULL;
GError *error = NULL;
g_autofree char *path1 = NULL;
g_autofree char *path2 = NULL;
int fd1, fd2;
guint32 fd_ids[2];
GUnixFDList *fd_list = NULL;
gboolean res;
char **out_doc_ids;
g_autoptr(GVariant) out_extra = NULL;
const char *permissions[] = { "read", NULL };
const char *basenames[] = { "doc1", "doc2" };
int i;
if (!have_fuse)
{
g_test_skip ("this test requires FUSE");
return;
}
path1 = g_build_filename (outdir, basenames[0], NULL);
g_file_set_contents (path1, basenames[0], -1, &error);
g_assert_no_error (error);
fd1 = open (path1, O_PATH | O_CLOEXEC);
g_assert (fd1 >= 0);
path2 = g_build_filename (outdir, basenames[1], NULL);
g_file_set_contents (path2, basenames[1], -1, &error);
g_assert_no_error (error);
fd2 = open (path2, O_PATH | O_CLOEXEC);
g_assert (fd2 >= 0);
fd_list = g_unix_fd_list_new ();
fd_ids[0] = g_unix_fd_list_append (fd_list, fd1, &error);
g_assert_no_error (error);
close (fd1);
fd_ids[1] = g_unix_fd_list_append (fd_list, fd2, &error);
g_assert_no_error (error);
close (fd2);
res = xdp_dbus_documents_call_add_full_sync (documents,
g_variant_new_fixed_array (G_VARIANT_TYPE_HANDLE,
fd_ids, 2, sizeof (guint32)),
0,
"org.other.App",
permissions,
fd_list,
&out_doc_ids,
&out_extra,
NULL,
NULL, &error);
g_assert_no_error (error);
g_assert (res);
g_assert (g_strv_length (out_doc_ids) == 2);
for (i = 0; i < 2; i++)
{
const char *id = out_doc_ids[i];
/* Ensure its there and not viewable by apps */
assert_doc_has_contents (id, basenames[i], NULL, basenames[i]);
assert_host_has_contents (basenames[i], basenames[i]);
assert_doc_not_exist (id, basenames[i], "com.test.App1");
assert_doc_not_exist (id, basenames[i], "com.test.App2");
assert_doc_not_exist (id, "another-file", NULL);
assert_doc_not_exist ("anotherid", basenames[i], NULL);
assert_doc_has_contents (id, basenames[i], "org.other.App", basenames[i]);
update_doc (id, basenames[i], "org.other.App", "tmpdata2", &error);
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_ACCES);
g_clear_error (&error);
}
g_assert (g_variant_lookup_value (out_extra, "mountpoint", G_VARIANT_TYPE_VARIANT) == 0);
}
static void
global_setup (void)
{
@ -413,6 +496,7 @@ main (int argc, char **argv)
g_test_add_func ("/db/create_doc", test_create_doc);
g_test_add_func ("/db/recursive_doc", test_recursive_doc);
g_test_add_func ("/db/create_docs", test_create_docs);
global_setup ();