summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon McVittie <simon.mcvittie@collabora.co.uk>2011-05-11 10:12:57 +0100
committerSimon McVittie <simon.mcvittie@collabora.co.uk>2011-05-11 10:24:32 +0100
commita4eb1c2b1403a84f593c3e5bad4622ff89f54496 (patch)
tree2b8413fb045ff1128bc0b1c2a6a1272e386d340b
parente9d5be63b54acfd7515dbf527238e97b17fbf8b2 (diff)
parenta07991817ceb17c5a48e674ef14bf112b247638b (diff)
downloadtelepathy-mission-control-a4eb1c2b1403a84f593c3e5bad4622ff89f54496.tar.gz
Merge branch 'builtin-aegis-acls'
Reviewed-by: Vivek Dasmohapatra <vivek@collabora.co.uk> Bug: https://bugs.freedesktop.org/show_bug.cgi?id=36845
-rw-r--r--mission-control-plugins/dbus-acl.c44
-rw-r--r--mission-control-plugins/dbus-acl.h4
-rw-r--r--mission-control-plugins/dispatch-operation-policy.c160
-rw-r--r--mission-control-plugins/dispatch-operation-policy.h42
-rw-r--r--plugins/Makefile.am17
-rw-r--r--plugins/mcp-dbus-aegis-acl.c284
-rw-r--r--plugins/mcp-dbus-aegis-acl.h34
-rw-r--r--src/Makefile.am4
-rw-r--r--src/mcd-dispatch-operation.c209
-rw-r--r--src/mcd-manager.c2
-rw-r--r--src/plugin-account.c3
-rw-r--r--src/plugin-loader.c19
-rw-r--r--tests/twisted/dispatcher/dispatch-delayed-by-mini-plugin.py83
-rw-r--r--tests/twisted/mcp-plugin.c167
14 files changed, 916 insertions, 156 deletions
diff --git a/mission-control-plugins/dbus-acl.c b/mission-control-plugins/dbus-acl.c
index 652f18be..246fe8c2 100644
--- a/mission-control-plugins/dbus-acl.c
+++ b/mission-control-plugins/dbus-acl.c
@@ -58,6 +58,8 @@
* A single object can implement more than one interface.
*/
+#include "config.h"
+
#include <mission-control-plugins/mission-control-plugins.h>
#include <mission-control-plugins/mcp-signals-marshal.h>
#include <glib.h>
@@ -66,8 +68,8 @@
#ifdef ENABLE_DEBUG
#define DEBUG(_p, _format, ...) \
- g_debug ("%s: %s: " _format, G_STRFUNC, \
- (_p != NULL) ? mcp_dbus_acl_name (_p) : "NULL", ##__VA_ARGS__)
+ g_debug ("dbus-acl: %s: %s: " _format, G_STRFUNC, \
+ (_p != NULL) ? mcp_dbus_acl_name (_p) : "-", ##__VA_ARGS__)
#else /* ENABLE_DEBUG */
@@ -245,7 +247,8 @@ mcp_dbus_acl_authorised (const TpDBusDaemon *dbus,
DEBUG (plugin, "checking ACL for %s", name);
- permitted = iface->authorised (plugin, dbus, context, type, name, params);
+ if (iface->authorised != NULL)
+ permitted = iface->authorised (plugin, dbus, context, type, name, params);
if (!permitted)
break;
@@ -282,31 +285,33 @@ mcp_dbus_acl_authorised_async_step (DBusAclAuthData *ad,
{
if (permitted)
{
- if (ad->next_acl != NULL && ad->next_acl->data != NULL)
+ while (ad->next_acl != NULL && ad->next_acl->data != NULL)
{
McpDBusAcl *plugin = MCP_DBUS_ACL (ad->next_acl->data);
McpDBusAclIface *iface = MCP_DBUS_ACL_GET_IFACE (plugin);
if (ad->acl != NULL)
- DEBUG (ad->acl, ":A: passed ACL for %s", ad->name);
+ DEBUG (ad->acl, "passed ACL for %s", ad->name);
/* take the next plugin off the next_acl list */
ad->next_acl = g_list_next (ad->next_acl);
ad->acl = plugin;
- /* kick off the next async authoriser in the chain */
- iface->authorised_async (plugin, ad);
+ if (iface->authorised_async != NULL)
+ {
+ /* kick off the next async authoriser in the chain */
+ iface->authorised_async (plugin, ad);
- /* don't clean up, the next async acl will call us when it's done: */
- return;
+ /* don't clean up, the next async acl will call us when it's
+ * done: */
+ return;
+ }
}
- else /* reached the end of the plugin list: call actual handler */
- {
- if (ad->acl != NULL)
- DEBUG (ad->acl, ":B: passed ACL for %s", ad->name);
- ad->handler (ad->context, ad->data);
- }
+ if (ad->acl != NULL)
+ DEBUG (ad->acl, "passed final ACL for %s", ad->name);
+
+ ad->handler (ad->context, ad->data);
}
else
{
@@ -374,6 +379,9 @@ mcp_dbus_acl_authorised_async (TpDBusDaemon *dbus,
ad->handler = handler;
ad->next_acl = acls;
+ DEBUG (NULL, "DBus access ACL verification: %u rules for %s",
+ g_list_length (acls),
+ name);
mcp_dbus_acl_authorised_async_step (ad, TRUE);
}
@@ -385,6 +393,9 @@ mcp_dbus_acl_name (const McpDBusAcl *self)
g_return_val_if_fail (iface != NULL, FALSE);
+ if (iface->name == NULL)
+ return G_OBJECT_TYPE_NAME (self);
+
return iface->name;
}
@@ -395,5 +406,8 @@ mcp_dbus_acl_description (const McpDBusAcl *self)
g_return_val_if_fail (iface != NULL, FALSE);
+ if (iface->desc == NULL)
+ return "(no description)";
+
return iface->desc;
}
diff --git a/mission-control-plugins/dbus-acl.h b/mission-control-plugins/dbus-acl.h
index 3ed3abc2..8567b18d 100644
--- a/mission-control-plugins/dbus-acl.h
+++ b/mission-control-plugins/dbus-acl.h
@@ -112,9 +112,9 @@ void mcp_dbus_acl_iface_implement_authorised (McpDBusAclIface *iface,
void mcp_dbus_acl_iface_implement_authorised_async (McpDBusAclIface *iface,
DBusAclAsyncAuthoriser method);
-const gchar *mcp_dbus_acl_name (const McpDBusAcl *storage);
+const gchar *mcp_dbus_acl_name (const McpDBusAcl *acl);
-const gchar *mcp_dbus_acl_description (const McpDBusAcl *storage);
+const gchar *mcp_dbus_acl_description (const McpDBusAcl *acl);
G_END_DECLS
diff --git a/mission-control-plugins/dispatch-operation-policy.c b/mission-control-plugins/dispatch-operation-policy.c
index b0e2f364..e31be47d 100644
--- a/mission-control-plugins/dispatch-operation-policy.c
+++ b/mission-control-plugins/dispatch-operation-policy.c
@@ -36,10 +36,7 @@
* #McpDispatchOperationPolicy, then return an instance of that subclass from
* mcp_plugin_ref_nth_object().
*
- * The contents of the #McpDispatchOperationPolicyIface struct are not public,
- * so to provide an implementation of the check method,
- * plugins should call mcp_dispatch_operation_policy_iface_implement_check()
- * from the interface initialization function, like this:
+ * A typical plugin might look like this:
*
* <example><programlisting>
* G_DEFINE_TYPE_WITH_CODE (MyPlugin, my_plugin,
@@ -53,8 +50,9 @@
* cdo_policy_iface_init (McpDispatchOperationPolicyIface *iface,
* gpointer unused G_GNUC_UNUSED)
* {
- * mcp_dispatch_operation_policy_iface_implement_check (iface,
- * my_plugin_check_cdo);
+ * iface-&gt;check = my_plugin_check_cdo;
+ * iface-&gt;handler_is_suitable_async = my_plugin_handler_is_suitable_async;
+ * iface-&gt;handler_is_suitable_finish = my_plugin_handler_is_suitable_finish;
* }
* </programlisting></example>
*
@@ -64,12 +62,6 @@
#include <mission-control-plugins/mission-control-plugins.h>
-struct _McpDispatchOperationPolicyIface {
- GTypeInterface parent;
-
- void (*check) (McpDispatchOperationPolicy *, McpDispatchOperation *);
-};
-
GType
mcp_dispatch_operation_policy_get_type (void)
{
@@ -102,6 +94,57 @@ mcp_dispatch_operation_policy_get_type (void)
}
/**
+ * McpDispatchOperationPolicyIface:
+ * @parent: the parent type
+ * @check: an implementation of mcp_dispatch_operation_policy_check();
+ * %NULL is equivalent to an implementation that does nothing
+ * @handler_is_suitable_async: an implementation of
+ * mcp_dispatch_operation_policy_handler_is_suitable_async();
+ * %NULL is treated as equivalent to an implementation that accepts
+ * every handler, i.e. always asynchronously returns %TRUE
+ * @handler_is_suitable_finish: an implementation of
+ * mcp_dispatch_operation_policy_handler_is_suitable_finish();
+ * %NULL is treated as equivalent to an implementation that accepts any
+ * #GSimpleAsyncResult
+ */
+
+/**
+ * McpDispatchOperationPolicyCb:
+ * @policy: an implementation of this interface, provided by a plugin
+ * @dispatch_operation: an object representing a dispatch operation, i.e.
+ * a bundle of channels being dispatched
+ *
+ * Signature of an implementation of mcp_dispatch_operation_policy_check().
+ */
+
+/**
+ * McpDispatchOperationPolicyHandlerIsSuitableAsync:
+ * @policy: an implementation of this interface, provided by a plugin
+ * @handler: a proxy for the Handler's D-Bus API, or %NULL if the Handler
+ * is calling Claim (so its well-known name is not immediately obvious)
+ * @unique_name: The Handler's unique name, or empty or %NULL if it has not yet
+ * been started
+ * @dispatch_operation: an object representing a dispatch operation, i.e.
+ * a bundle of channels being dispatched
+ * @callback: callback to be called on success or failure
+ * @user_data: user data for the callback
+ *
+ * Signature of mcp_dispatch_operation_policy_handler_is_suitable_async()
+ */
+
+/**
+ * McpDispatchOperationPolicyFinisher:
+ * @policy: an implementation of this interface, provided by a plugin
+ * @result: the asynchronous result passed to a #GAsyncReadyCallback
+ * @error: (allow-none): used to return an error
+ *
+ * Signature of a virtual method used to finish an asynchronous operation
+ * that succeeds or fails, but does not return any additional value.
+ *
+ * Returns: %TRUE if the operation succeeded, %FALSE on error
+ */
+
+/**
* mcp_dispatch_operation_policy_check:
* @policy: an implementation of this interface, provided by a plugin
* @dispatch_operation: an object representing a dispatch operation, i.e.
@@ -134,11 +177,102 @@ mcp_dispatch_operation_policy_check (McpDispatchOperationPolicy *policy,
* @iface: the interface
* @impl: an implementation of the virtual method
* mcp_dispatch_operation_policy_check()
+ *
+ * This method is no longer necessary: just set iface->check = impl instead.
*/
void
mcp_dispatch_operation_policy_iface_implement_check (
McpDispatchOperationPolicyIface *iface,
- void (*impl) (McpDispatchOperationPolicy *, McpDispatchOperation *))
+ McpDispatchOperationPolicyCb impl)
{
iface->check = impl;
}
+
+/**
+ * mcp_dispatch_operation_policy_handler_is_suitable_async:
+ * @policy: an implementation of this interface, provided by a plugin
+ * @handler: a proxy for the Handler's D-Bus API, or %NULL if the Handler
+ * is calling Claim (so its well-known name is not immediately obvious)
+ * @unique_name: The Handler's unique name, or empty or %NULL if it has not yet
+ * been started
+ * @dispatch_operation: an object representing a dispatch operation, i.e.
+ * a bundle of channels being dispatched
+ * @callback: callback to be called on success or failure
+ * @user_data: user data for the callback
+ *
+ * Check whether a handler is "suitable" for these channels. For instance,
+ * this could be used to ensure that only the platform's default UI can be
+ * used for particular channels, even if MC would normally consider
+ * a third-party UI to be a better match.
+ *
+ * Mission Control calls all implementations of this method in parallel
+ * and waits for them all to return. If any of them raises an error,
+ * the handler is considered to be unsuitable.
+ */
+void
+mcp_dispatch_operation_policy_handler_is_suitable_async (
+ McpDispatchOperationPolicy *policy,
+ TpClient *handler,
+ const gchar *unique_name,
+ McpDispatchOperation *dispatch_operation,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ McpDispatchOperationPolicyIface *iface =
+ MCP_DISPATCH_OPERATION_POLICY_GET_IFACE (policy);
+
+ g_return_if_fail (iface != NULL);
+
+ if (iface->handler_is_suitable_async != NULL)
+ {
+ iface->handler_is_suitable_async (policy, handler, unique_name,
+ dispatch_operation, callback, user_data);
+ }
+ else
+ {
+ /* unimplemented: the default is to succeed */
+ GSimpleAsyncResult *simple = g_simple_async_result_new (
+ (GObject *) policy, callback, user_data,
+ mcp_dispatch_operation_policy_handler_is_suitable_async);
+
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ }
+}
+
+/**
+ * @policy: an implementation of this interface, provided by a plugin
+ * @result: the asynchronous result passed to the #GAsyncReadyCallback
+ * @error: (allow-none): used to return an error
+ *
+ * Finish a call to mcp_dispatch_operation_policy_handler_is_suitable_async().
+ *
+ * Returns: %TRUE if the handler is suitable; %FALSE if the handler is
+ * unsuitable or there was an error
+ */
+gboolean
+mcp_dispatch_operation_policy_handler_is_suitable_finish (
+ McpDispatchOperationPolicy *policy,
+ GAsyncResult *result,
+ GError **error)
+{
+ McpDispatchOperationPolicyIface *iface =
+ MCP_DISPATCH_OPERATION_POLICY_GET_IFACE (policy);
+
+ g_return_val_if_fail (iface != NULL, FALSE);
+
+ if (iface->handler_is_suitable_finish != NULL)
+ {
+ return iface->handler_is_suitable_finish (policy, result, error);
+ }
+ else
+ {
+ /* accept any GSimpleAsyncResult regardless of source tag, so we can
+ * use it with the default implementation of _async or with most
+ * user-supplied implementations */
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
+
+ return !g_simple_async_result_propagate_error (
+ (GSimpleAsyncResult *) result, error);
+ }
+}
diff --git a/mission-control-plugins/dispatch-operation-policy.h b/mission-control-plugins/dispatch-operation-policy.h
index 8f0f7f4f..a5587217 100644
--- a/mission-control-plugins/dispatch-operation-policy.h
+++ b/mission-control-plugins/dispatch-operation-policy.h
@@ -27,6 +27,8 @@
#include <mission-control-plugins/dispatch-operation.h>
+#include <telepathy-glib/client.h>
+
G_BEGIN_DECLS
/* API for plugins to implement */
@@ -48,13 +50,49 @@ typedef struct _McpDispatchOperationPolicyIface McpDispatchOperationPolicyIface;
GType mcp_dispatch_operation_policy_get_type (void) G_GNUC_CONST;
/* virtual methods */
+
+typedef void (*McpDispatchOperationPolicyCb) (
+ McpDispatchOperationPolicy *policy,
+ McpDispatchOperation *dispatch_operation);
+
void mcp_dispatch_operation_policy_check (McpDispatchOperationPolicy *policy,
McpDispatchOperation *dispatch_operation);
-/* vtable manipulation - the vtable is private to allow for expansion */
+typedef void (*McpDispatchOperationPolicyHandlerIsSuitableAsync) (
+ McpDispatchOperationPolicy *policy,
+ TpClient *handler,
+ const gchar *unique_name,
+ McpDispatchOperation *dispatch_operation,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+typedef gboolean (*McpDispatchOperationPolicyFinisher) (
+ McpDispatchOperationPolicy *policy,
+ GAsyncResult *result,
+ GError **error);
+
+void mcp_dispatch_operation_policy_handler_is_suitable_async (
+ McpDispatchOperationPolicy *policy,
+ TpClient *handler,
+ const gchar *unique_name,
+ McpDispatchOperation *dispatch_operation,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mcp_dispatch_operation_policy_handler_is_suitable_finish (
+ McpDispatchOperationPolicy *policy,
+ GAsyncResult *result,
+ GError **error);
+
void mcp_dispatch_operation_policy_iface_implement_check (
McpDispatchOperationPolicyIface *iface,
- void (*impl) (McpDispatchOperationPolicy *, McpDispatchOperation *));
+ McpDispatchOperationPolicyCb impl);
+
+struct _McpDispatchOperationPolicyIface {
+ GTypeInterface parent;
+
+ McpDispatchOperationPolicyCb check;
+ McpDispatchOperationPolicyHandlerIsSuitableAsync handler_is_suitable_async;
+ McpDispatchOperationPolicyFinisher handler_is_suitable_finish;
+};
G_END_DECLS
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index d801724c..23ccc6c0 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -7,21 +7,14 @@ INCLUDES = \
-DLIBDIR="@libdir@" \
-DLIBVERSION="@MCP_ABI_VERSION@"
-EXTRA_DIST =
-
-instdir = @libdir@/mission-control-plugins.@MCP_ABI_VERSION@
-
-plugin_LDFLAGS = -module -shared -avoid-version -rpath @abs_builddir@
-
-inst_LTLIBRARIES =
+noinst_LTLIBRARIES =
if ENABLE_AEGIS
-inst_LTLIBRARIES += mcp-dbus-aegis-acl.la
-
-mcp_dbus_aegis_acl_la_SOURCES = mcp-dbus-aegis-acl.c
-mcp_dbus_aegis_acl_la_LDFLAGS = \
- $(plugin_LDFLAGS) \
+noinst_LTLIBRARIES += libmcp-aegis.la
+libmcp_aegis_la_SOURCES = mcp-dbus-aegis-acl.c
+libmcp_aegis_la_LIBADD = \
+ $(top_builddir)/mission-control-plugins/libmission-control-plugins.la \
$(TELEPATHY_LIBS) \
$(DBUS_LIBS) \
$(AEGIS_LIBS)
diff --git a/plugins/mcp-dbus-aegis-acl.c b/plugins/mcp-dbus-aegis-acl.c
index 71d953ca..1f5b1b4e 100644
--- a/plugins/mcp-dbus-aegis-acl.c
+++ b/plugins/mcp-dbus-aegis-acl.c
@@ -1,5 +1,5 @@
/*
- * An Aegis/libcreds plugin that checks the caller's permission tokens
+ * A pseudo-plugin that checks the caller's Aegis permission tokens
*
* Copyright © 2010-2011 Nokia Corporation
* Copyright © 2010-2011 Collabora Ltd.
@@ -19,11 +19,39 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#include "config.h"
+
+#ifdef G_LOG_DOMAIN
+#undef G_LOG_DOMAIN
+#endif
+#define G_LOG_DOMAIN "mission-control-DBus-Access-ACL"
+
+#ifdef ENABLE_DEBUG
+#define DEBUG(_f, ...) g_debug ("%s: " _f, G_STRLOC, ##__VA_ARGS__)
+#else
+#define DEBUG(_f, ...) do {} while (0)
+#endif
+
+#include <dbus/dbus-glib.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+#include <telepathy-glib/defs.h>
+
#include <mission-control-plugins/mission-control-plugins.h>
+
#include <sys/types.h>
#include <sys/creds.h>
-#include <telepathy-glib/interfaces.h>
-#include <telepathy-glib/defs.h>
+
+typedef struct _AegisAcl AegisAcl;
+typedef struct _AegisAclClass AegisAclClass;
+
+struct _AegisAcl {
+ GObject parent;
+};
+
+struct _AegisAclClass {
+ GObjectClass parent_class;
+};
#define CREATE_CHANNEL TP_IFACE_CONNECTION_INTERFACE_REQUESTS ".CreateChannel"
#define ENSURE_CHANNEL TP_IFACE_CONNECTION_INTERFACE_REQUESTS ".EnsureChannel"
@@ -32,7 +60,8 @@
#define AEGIS_CALL_TOKEN "Cellular"
-#define DEBUG g_debug
+/* implemented by the Aegis-patched dbus-daemon */
+#define AEGIS_INTERFACE "com.meego.DBus.Creds"
#define PLUGIN_NAME "dbus-aegis-acl"
#define PLUGIN_DESCRIPTION \
@@ -40,63 +69,52 @@
"associated with the calling process ID and determine whether " \
"the DBus call or property access should be allowed"
-static gboolean token_initialised = FALSE;
static creds_value_t aegis_token = CREDS_BAD;
static creds_type_t aegis_type = CREDS_BAD;
-static gchar *restricted[] =
- {
- CREATE_CHANNEL,
- ENSURE_CHANNEL,
- SEND_MESSAGE,
- NULL
- };
-
-static void dbus_acl_iface_init (McpDBusAclIface *,
+static void aegis_acl_iface_init (McpDBusAclIface *,
+ gpointer);
+static void aegis_cdo_policy_iface_init (McpDispatchOperationPolicyIface *,
gpointer);
-typedef struct {
- GObject parent;
-} DBusAegisAcl;
-
-typedef struct {
- GObjectClass parent_class;
- creds_value_t token;
- creds_type_t token_type;
-} DBusAegisAclClass;
-
-GType dbus_aegis_acl_get_type (void) G_GNUC_CONST;
-
-#define DBUS_AEGIS_ACL(o) \
- (G_TYPE_CHECK_INSTANCE_CAST ((o), dbus_aegis_acl_get_type (), \
- DBusAegisAcl))
+static GType aegis_acl_get_type (void);
-G_DEFINE_TYPE_WITH_CODE (DBusAegisAcl, dbus_aegis_acl,
+G_DEFINE_TYPE_WITH_CODE (AegisAcl, aegis_acl,
G_TYPE_OBJECT,
- G_IMPLEMENT_INTERFACE (MCP_TYPE_DBUS_ACL, dbus_acl_iface_init));
+ G_IMPLEMENT_INTERFACE (MCP_TYPE_DBUS_ACL, aegis_acl_iface_init);
+ G_IMPLEMENT_INTERFACE (MCP_TYPE_DISPATCH_OPERATION_POLICY,
+ aegis_cdo_policy_iface_init))
static void
-dbus_aegis_acl_init (DBusAegisAcl *self)
+aegis_acl_init (AegisAcl *self)
{
}
static void
-dbus_aegis_acl_class_init (DBusAegisAclClass *cls)
+aegis_acl_class_init (AegisAclClass *cls)
{
- if (token_initialised == TRUE)
+ if (aegis_type != CREDS_BAD)
return;
aegis_type = creds_str2creds (AEGIS_CALL_TOKEN, &aegis_token);
}
+static gchar *restricted_methods[] =
+ {
+ CREATE_CHANNEL,
+ ENSURE_CHANNEL,
+ SEND_MESSAGE,
+ NULL
+ };
+
static gboolean
method_is_filtered (const gchar *method)
{
guint i;
- for (i = 0; restricted[i] != NULL; i++)
+ for (i = 0; restricted_methods[i] != NULL; i++)
{
- if (!tp_strdiff (method, restricted[i]))
+ if (!tp_strdiff (method, restricted_methods[i]))
return TRUE;
}
@@ -139,23 +157,76 @@ is_filtered (DBusAclType type,
return FALSE;
}
+/* For simplicity we don't implement non-trivial conversion between
+ * dbus-glib's arrays of guint, and libcreds' arrays of uint32_t.
+ * If this assertion fails on your platform, you'll need to implement it. */
+G_STATIC_ASSERT (sizeof (guint) == sizeof (uint32_t));
+
static gboolean
-pid_is_permitted (const McpDBusAcl *self, pid_t pid)
+caller_creds_are_enough (const gchar *name,
+ const GArray *au)
{
- gboolean ok = FALSE;
+ creds_t caller_creds = creds_import ((const uint32_t *) au->data, au->len);
+ gboolean ok = creds_have_p (caller_creds, aegis_type, aegis_token);
- if (pid != 0)
+#ifdef ENABLE_DEBUG
+ if (ok)
+ {
+ DEBUG ("Caller %s is appropriately privileged", name);
+ }
+ else
{
- creds_t caller = creds_gettask (pid);
+ char buf[1024];
+ creds_type_t debug_type;
+ creds_value_t debug_value;
+ int i = 0;
- DEBUG ("creds_have_p (creds_gettask (%d) -> %p, %d, %ld)",
- pid, caller, aegis_type, aegis_token);
- ok = creds_have_p (caller, aegis_type, aegis_token);
- DEBUG (" --> %s", ok ? "TRUE" : "FALSE");
+ DEBUG ("Caller %s has these credentials:", name);
- creds_free (caller);
+ while ((debug_type = creds_list (caller_creds, i++, &debug_value))
+ != CREDS_BAD)
+ {
+ creds_creds2str (debug_type, debug_value, buf, sizeof (buf));
+ DEBUG ("- %s", buf);
+ }
+
+ DEBUG ("but they are insufficient");
}
+#endif
+ creds_free (caller_creds);
+ return ok;
+}
+
+static gboolean
+check_peer_creds_sync (DBusGConnection *dgc,
+ const gchar *bus_name)
+{
+ DBusGProxy *proxy = dbus_g_proxy_new_for_name (dgc,
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ AEGIS_INTERFACE);
+ GArray *au = NULL;
+ GError *error = NULL;
+ gboolean ok;
+
+ if (dbus_g_proxy_call (proxy, "GetConnectionCredentials", &error,
+ G_TYPE_STRING, bus_name,
+ G_TYPE_INVALID,
+ DBUS_TYPE_G_UINT_ARRAY, &au,
+ G_TYPE_INVALID))
+ {
+ ok = caller_creds_are_enough (bus_name, au);
+ g_array_unref (au);
+ }
+ else
+ {
+ DEBUG ("GetConnectionCredentials failed: %s", error->message);
+ g_clear_error (&error);
+ ok = FALSE;
+ }
+
+ g_object_unref (proxy);
return ok;
}
@@ -172,24 +243,11 @@ caller_authorised (const McpDBusAcl *self,
if (is_filtered (type, name, params))
{
- pid_t pid = 0;
- GError *error = NULL;
gchar *caller = dbus_g_method_get_sender ((DBusGMethodInvocation *) call);
- DBusGProxy *proxy = dbus_g_proxy_new_for_name (dgc,
- DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS,
- DBUS_INTERFACE_DBUS);
- dbus_g_proxy_call (proxy, "GetConnectionUnixProcessID", &error,
- G_TYPE_STRING, caller,
- G_TYPE_INVALID,
- G_TYPE_UINT, &pid,
- G_TYPE_INVALID);
-
- ok = pid_is_permitted (self, pid);
+ ok = check_peer_creds_sync (dgc, caller);
g_free (caller);
- g_object_unref (proxy);
}
DEBUG ("sync Aegis ACL check [%s]", ok ? "Allowed" : "Forbidden");
@@ -204,24 +262,30 @@ async_authorised_cb (DBusGProxy *proxy,
{
GError *error = NULL;
DBusAclAuthData *ad = data;
- pid_t pid = 0;
+ GArray *au = NULL;
const McpDBusAcl *self = ad->acl;
gboolean permitted = FALSE;
- /* if this returns FALSE, there's no PID, which means something bizarre *
- * and untrustowrthy is going on, which in turn means we must deny: can't *
- * authorise without first authenticating */
+ /* if this returns FALSE, there are no credentials, which means something
+ * untrustworthy is going on, which in turn means we must deny: can't
+ * authorise without first authenticating */
permitted = dbus_g_proxy_end_call (proxy, call, &error,
- G_TYPE_UINT, &pid,
+ DBUS_TYPE_G_UINT_ARRAY, &au,
G_TYPE_INVALID);
if (permitted)
- permitted = pid_is_permitted (self, pid);
+ {
+ permitted = caller_creds_are_enough (ad->name, au);
+ g_array_unref (au);
+ }
else
- g_error_free (error);
+ {
+ DEBUG ("GetConnectionCredentials failed: %s", error->message);
+ g_clear_error (&error);
+ }
- DEBUG ("finished async Aegis ACL check [%u -> %s]",
- pid, permitted ? "Allowed" : "Forbidden");
+ DEBUG ("finished async Aegis ACL check [%s]",
+ permitted ? "Allowed" : "Forbidden");
mcp_dbus_acl_authorised_async_step (ad, permitted);
@@ -244,9 +308,9 @@ caller_async_authorised (const McpDBusAcl *self,
proxy = dbus_g_proxy_new_for_name (dgc,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
- DBUS_INTERFACE_DBUS);
+ AEGIS_INTERFACE);
- dbus_g_proxy_begin_call (proxy, "GetConnectionUnixProcessID",
+ dbus_g_proxy_begin_call (proxy, "GetConnectionCredentials",
async_authorised_cb,
data,
NULL,
@@ -263,7 +327,7 @@ caller_async_authorised (const McpDBusAcl *self,
static void
-dbus_acl_iface_init (McpDBusAclIface *iface,
+aegis_acl_iface_init (McpDBusAclIface *iface,
gpointer unused G_GNUC_UNUSED)
{
mcp_dbus_acl_iface_set_name (iface, PLUGIN_NAME);
@@ -273,18 +337,80 @@ dbus_acl_iface_init (McpDBusAclIface *iface,
mcp_dbus_acl_iface_implement_authorised_async (iface, caller_async_authorised);
}
-GObject *
-mcp_plugin_ref_nth_object (guint n)
+static gchar *restricted_cms[] = { "ring", "mmscm", NULL };
+
+static inline gboolean
+cm_is_restricted (const gchar *cm_name)
{
- DEBUG ("Initializing mcp-dbus-caller-id plugin (n=%u)", n);
+ guint i;
- switch (n)
+ for (i = 0; restricted_cms[i] != NULL; i++)
{
- case 0:
- return g_object_new (dbus_aegis_acl_get_type (), NULL);
+ if (!tp_strdiff (restricted_cms[i], cm_name))
+ return TRUE;
+ }
+
+ return FALSE;
+}
- default:
- return NULL;
+static void
+handler_is_suitable_async (McpDispatchOperationPolicy *self,
+ TpClient *recipient,
+ const gchar *unique_name,
+ McpDispatchOperation *dispatch_op,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ const gchar *manager = mcp_dispatch_operation_get_cm_name (dispatch_op);
+ GSimpleAsyncResult *simple = g_simple_async_result_new ((GObject *) self,
+ callback, user_data, handler_is_suitable_async);
+ gboolean ok = TRUE;
+
+ if (cm_is_restricted (manager))
+ {
+ TpDBusDaemon *dbus = tp_dbus_daemon_dup (NULL);
+
+ /* if MC started successfully, we ought to have one */
+ g_assert (dbus != NULL);
+
+ if (!tp_str_empty (unique_name))
+ {
+ ok = check_peer_creds_sync (tp_proxy_get_dbus_connection (dbus),
+ unique_name);
+ }
+ else
+ {
+ g_assert (recipient != NULL);
+
+ ok = check_peer_creds_sync (tp_proxy_get_dbus_connection (dbus),
+ tp_proxy_get_bus_name (recipient));
+ }
+
+ if (!ok)
+ {
+ g_simple_async_result_set_error (simple, TP_ERRORS,
+ TP_ERROR_PERMISSION_DENIED, "insufficient Aegis credentials");
+ }
+
+ g_object_unref (dbus);
}
+
+ DEBUG ("sync Aegis CDO policy check [%s]", ok ? "Allowed" : "Forbidden");
+
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+}
+
+static void
+aegis_cdo_policy_iface_init (McpDispatchOperationPolicyIface *iface,
+ gpointer unused G_GNUC_UNUSED)
+{
+ iface->handler_is_suitable_async = handler_is_suitable_async;
+ /* the default finish function accepts our GSimpleAsyncResult */
}
+GObject *
+aegis_acl_new (void)
+{
+ return g_object_new (aegis_acl_get_type (), NULL);
+}
diff --git a/plugins/mcp-dbus-aegis-acl.h b/plugins/mcp-dbus-aegis-acl.h
new file mode 100644
index 00000000..96baaf46
--- /dev/null
+++ b/plugins/mcp-dbus-aegis-acl.h
@@ -0,0 +1,34 @@
+/*
+ * A pseudo-plugin that checks the caller's Aegis permission tokens
+ *
+ * Copyright © 2010-2011 Nokia Corporation
+ * Copyright © 2010-2011 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AEGIS_ACL_H
+#define AEGIS_ACL_H
+
+#include <mission-control-plugins/mission-control-plugins.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+GObject *aegis_acl_new (void);
+
+G_END_DECLS
+
+#endif
diff --git a/src/Makefile.am b/src/Makefile.am
index 860374c0..dbc10d3d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -117,6 +117,10 @@ libmcd_convenience_la_LIBADD += $(LIBACCOUNTS_SSO_LIBS)
INCLUDES += $(LIBACCOUNTS_SSO_CFLAGS)
endif
+if ENABLE_AEGIS
+libmcd_convenience_la_LIBADD += $(top_builddir)/plugins/libmcp-aegis.la
+endif
+
noinst_LTLIBRARIES = libmcd-convenience.la
if ENABLE_MCD_PLUGINS
diff --git a/src/mcd-dispatch-operation.c b/src/mcd-dispatch-operation.c
index bcc31f5f..8f79d5b5 100644
--- a/src/mcd-dispatch-operation.c
+++ b/src/mcd-dispatch-operation.c
@@ -243,6 +243,13 @@ struct _McdDispatchOperationPrivate
* A reference is held for each pending approver. */
gsize ado_pending;
+ /* The number of plugins whose decision we're waiting for,
+ * regarding whether a handler is in fact suitable. */
+ gsize handler_suitable_pending;
+
+ /* If non-NULL, a plugin has decided the selected handler is unsuitable. */
+ GError *handler_unsuitable;
+
/* If TRUE, we're dispatching a channel request and it was cancelled */
gboolean cancelled;
@@ -250,9 +257,9 @@ struct _McdDispatchOperationPrivate
* after observers */
gboolean observe_only;
- /* If TRUE, we're in the middle of calling HandleChannels. This is a
- * client lock. */
- gboolean calling_handle_channels;
+ /* If non-NULL, we're in the middle of asking plugins whether we may call
+ * HandleChannels, or doing so. This is a client lock. */
+ McdClientProxy *trying_handler;
/* If TRUE, we've tried all the BypassApproval handlers, which happens
* before we run approvers. */
@@ -457,9 +464,9 @@ _mcd_dispatch_operation_check_client_locks (McdDispatchOperation *self)
/* if we've called one Handler, we may not continue until it responds
* with an error */
- if (self->priv->calling_handle_channels)
+ if (self->priv->trying_handler != NULL)
{
- DEBUG ("waiting for HandleChannels to return");
+ DEBUG ("waiting for handler_is_suitable or HandleChannels to return");
return;
}
@@ -917,27 +924,97 @@ dispatch_operation_handle_with (TpSvcChannelDispatchOperation *cdo,
context);
}
+typedef struct {
+ McdDispatchOperation *self;
+ DBusGMethodInvocation *context;
+ gsize handler_suitable_pending;
+} ClaimAttempt;
+
+static void
+claim_attempt_resolve (ClaimAttempt *claim_attempt)
+{
+ if (claim_attempt->context != NULL)
+ {
+ g_queue_push_tail (claim_attempt->self->priv->approvals,
+ approval_new_claim (claim_attempt->context));
+ _mcd_dispatch_operation_check_client_locks (claim_attempt->self);
+ }
+
+ g_object_unref (claim_attempt->self);
+ g_slice_free (ClaimAttempt, claim_attempt);
+}
+
+static void
+claim_attempt_suitability_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ ClaimAttempt *claim_attempt = user_data;
+ GError *error = NULL;
+
+ if (!mcp_dispatch_operation_policy_handler_is_suitable_finish (
+ MCP_DISPATCH_OPERATION_POLICY (source), res, &error))
+ {
+ if (claim_attempt->context != NULL)
+ dbus_g_method_return_error (claim_attempt->context, error);
+
+ claim_attempt->context = NULL;
+ g_error_free (error);
+ }
+
+ if (--claim_attempt->handler_suitable_pending == 0)
+ {
+ DEBUG ("all plugins have finished, resolving claim attempt");
+ claim_attempt_resolve (claim_attempt);
+ }
+}
+
static void
dispatch_operation_claim (TpSvcChannelDispatchOperation *cdo,
DBusGMethodInvocation *context)
{
McdDispatchOperation *self = MCD_DISPATCH_OPERATION (cdo);
- McdDispatchOperationPrivate *priv = self->priv;
+ ClaimAttempt *claim_attempt;
+ gchar *sender = dbus_g_method_get_sender (context);
+ McpDispatchOperation *plugin_api = MCP_DISPATCH_OPERATION (
+ self->priv->plugin_api);
+ const GList *p;
if (self->priv->result != NULL)
{
- gchar *sender = dbus_g_method_get_sender (context);
- DEBUG ("Giving error to %s: %s", sender, priv->result->message);
- dbus_g_method_return_error (context, priv->result);
+ DEBUG ("Giving error to %s: %s", sender, self->priv->result->message);
+ dbus_g_method_return_error (context, self->priv->result);
+ goto finally;
+ }
- g_free (sender);
+ claim_attempt = g_slice_new0 (ClaimAttempt);
+ claim_attempt->self = g_object_ref (self);
+ claim_attempt->context = context;
+ claim_attempt->handler_suitable_pending = 0;
- return;
+ for (p = mcp_list_objects (); p != NULL; p = g_list_next (p))
+ {
+ if (MCP_IS_DISPATCH_OPERATION_POLICY (p->data))
+ {
+ McpDispatchOperationPolicy *plugin = p->data;
+
+ DEBUG ("%s: checking policy for %s",
+ G_OBJECT_TYPE_NAME (plugin), sender);
+
+ claim_attempt->handler_suitable_pending++;
+ mcp_dispatch_operation_policy_handler_is_suitable_async (plugin,
+ NULL, sender, plugin_api,
+ claim_attempt_suitability_cb,
+ claim_attempt);
+ }
}
- g_queue_push_tail (priv->approvals, approval_new_claim (context));
- _mcd_dispatch_operation_check_client_locks (self);
+ if (claim_attempt->handler_suitable_pending == 0)
+ claim_attempt_resolve (claim_attempt);
+
+finally:
+ g_free (sender);
}
static void
@@ -1881,7 +1958,7 @@ _mcd_dispatch_operation_handle_channels_cb (TpClient *client,
tp_proxy_get_bus_name (client));
}
- self->priv->calling_handle_channels = FALSE;
+ tp_clear_object (&self->priv->trying_handler);
_mcd_dispatch_operation_check_client_locks (self);
}
@@ -2215,14 +2292,29 @@ _mcd_dispatch_operation_run_clients (McdDispatchOperation *self)
* Invoke the handler for the given channels.
*/
static void
-mcd_dispatch_operation_handle_channels (McdDispatchOperation *self,
- McdClientProxy *handler)
+mcd_dispatch_operation_handle_channels (McdDispatchOperation *self)
{
GHashTable *handler_info;
GHashTable *request_properties;
- g_assert (!self->priv->calling_handle_channels);
- self->priv->calling_handle_channels = TRUE;
+ g_assert (self->priv->trying_handler != NULL);
+
+ if (self->priv->handler_unsuitable != NULL)
+ {
+ GError *tmp = self->priv->handler_unsuitable;
+
+ /* move the error out of the way first, in case the callback
+ * tries a different handler which will also want to check
+ * handler_unsuitable */
+ self->priv->handler_unsuitable = NULL;
+
+ _mcd_dispatch_operation_handle_channels_cb (
+ (TpClient *) self->priv->trying_handler,
+ tmp, self, NULL);
+ g_error_free (tmp);
+
+ return;
+ }
handler_info = tp_asv_new (NULL, NULL);
collect_satisfied_requests (self->priv->channels, NULL,
@@ -2231,7 +2323,7 @@ mcd_dispatch_operation_handle_channels (McdDispatchOperation *self,
MC_HASH_TYPE_OBJECT_IMMUTABLE_PROPERTIES_MAP, request_properties);
request_properties = NULL;
- _mcd_client_proxy_handle_channels (handler,
+ _mcd_client_proxy_handle_channels (self->priv->trying_handler,
-1, self->priv->channels, self->priv->handle_with_time,
handler_info, _mcd_dispatch_operation_handle_channels_cb,
g_object_ref (self), g_object_unref, NULL);
@@ -2239,6 +2331,76 @@ mcd_dispatch_operation_handle_channels (McdDispatchOperation *self,
g_hash_table_unref (handler_info);
}
+static void
+mcd_dispatch_operation_handler_decision_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ McdDispatchOperation *self = user_data;
+ GError *error = NULL;
+
+ if (!mcp_dispatch_operation_policy_handler_is_suitable_finish (
+ MCP_DISPATCH_OPERATION_POLICY (source), res, &error))
+ {
+ /* ignore any errors after the first */
+ if (self->priv->handler_unsuitable == NULL)
+ g_propagate_error (&self->priv->handler_unsuitable, error);
+ else
+ g_error_free (error);
+ }
+
+ if (--self->priv->handler_suitable_pending == 0)
+ {
+ mcd_dispatch_operation_handle_channels (self);
+ }
+
+ g_object_unref (self);
+}
+
+static void
+mcd_dispatch_operation_try_handler (McdDispatchOperation *self,
+ McdClientProxy *handler)
+{
+ TpClient *handler_client = (TpClient *) handler;
+ const GList *p;
+ McpDispatchOperation *plugin_api = MCP_DISPATCH_OPERATION (
+ self->priv->plugin_api);
+
+ g_assert (self->priv->trying_handler == NULL);
+ self->priv->trying_handler = g_object_ref (handler);
+
+ self->priv->handler_suitable_pending = 0;
+
+ DEBUG ("%s: channel ACL verification [%u channels]",
+ self->priv->unique_name,
+ g_list_length (self->priv->channels));
+
+ for (p = mcp_list_objects (); p != NULL; p = g_list_next (p))
+ {
+ if (MCP_IS_DISPATCH_OPERATION_POLICY (p->data))
+ {
+ McpDispatchOperationPolicy *plugin = p->data;
+
+ DEBUG ("%s: checking policy for %s",
+ G_OBJECT_TYPE_NAME (plugin),
+ tp_proxy_get_object_path (handler));
+
+ self->priv->handler_suitable_pending++;
+ mcp_dispatch_operation_policy_handler_is_suitable_async (plugin,
+ handler_client,
+ _mcd_client_proxy_get_unique_name (handler),
+ plugin_api,
+ mcd_dispatch_operation_handler_decision_cb,
+ g_object_ref (self));
+ }
+ }
+
+ if (self->priv->handler_suitable_pending == 0)
+ {
+ mcd_dispatch_operation_handle_channels (self);
+ }
+}
+
static gboolean
_mcd_dispatch_operation_try_next_handler (McdDispatchOperation *self)
{
@@ -2269,7 +2431,7 @@ _mcd_dispatch_operation_try_next_handler (McdDispatchOperation *self)
if (handler != NULL &&
(approval->type == APPROVAL_TYPE_HANDLE_WITH || !failed))
{
- mcd_dispatch_operation_handle_channels (self, handler);
+ mcd_dispatch_operation_try_handler (self, handler);
return TRUE;
}
@@ -2278,11 +2440,14 @@ _mcd_dispatch_operation_try_next_handler (McdDispatchOperation *self)
* can legitimately try more handlers. */
if (approval->type == APPROVAL_TYPE_HANDLE_WITH)
{
- GError gone = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ GError gone = { TP_ERRORS,
+ TP_ERROR_NOT_IMPLEMENTED,
"The requested Handler does not exist" };
g_queue_pop_head (self->priv->approvals);
+
dbus_g_method_return_error (approval->context, &gone);
+
approval->context = NULL;
approval_free (approval);
return TRUE;
@@ -2304,7 +2469,7 @@ _mcd_dispatch_operation_try_next_handler (McdDispatchOperation *self)
if (handler != NULL && !failed &&
(is_approved || _mcd_client_proxy_get_bypass_approval (handler)))
{
- mcd_dispatch_operation_handle_channels (self, handler);
+ mcd_dispatch_operation_try_handler (self, handler);
return TRUE;
}
}
diff --git a/src/mcd-manager.c b/src/mcd-manager.c
index d7911e9f..9df13ce0 100644
--- a/src/mcd-manager.c
+++ b/src/mcd-manager.c
@@ -220,10 +220,8 @@ _mcd_manager_constructor (GType type, guint n_params,
{
GObjectClass *object_class = (GObjectClass *)mcd_manager_parent_class;
McdManager *manager;
- McdManagerPrivate *priv;
manager = MCD_MANAGER (object_class->constructor (type, n_params, params));
- priv = manager->priv;
g_return_val_if_fail (manager != NULL, NULL);
diff --git a/src/plugin-account.c b/src/plugin-account.c
index 1680914d..cfb83ab7 100644
--- a/src/plugin-account.c
+++ b/src/plugin-account.c
@@ -303,6 +303,9 @@ sort_and_cache_plugins ()
const GList *p;
static gboolean plugins_cached = FALSE;
+ if (plugins_cached)
+ return;
+
/* not guaranteed to have been called, but idempotent: */
_mcd_plugin_loader_init ();
diff --git a/src/plugin-loader.c b/src/plugin-loader.c
index 35e3625f..7062c3a2 100644
--- a/src/plugin-loader.c
+++ b/src/plugin-loader.c
@@ -25,6 +25,12 @@
#include <mission-control-plugins/mission-control-plugins.h>
+#include "mcd-debug.h"
+
+#if ENABLE_AEGIS
+#include "plugins/mcp-dbus-aegis-acl.h"
+#endif
+
static gsize ready = 0;
void
@@ -32,12 +38,25 @@ _mcd_plugin_loader_init (void)
{
if (g_once_init_enter (&ready))
{
+#if ENABLE_AEGIS
+ GObject *pseudo_plugin;
+#endif
const gchar *dir = g_getenv ("MC_FILTER_PLUGIN_DIR");
if (dir == NULL)
dir = MCD_PLUGIN_LOADER_DIR;
mcp_read_dir (dir);
+
+#if ENABLE_AEGIS
+ /* The last object added by mcp_add_object() will be treated as highest
+ * priority, at least for the interfaces used here */
+ DEBUG ("Initialising built-in Aegis ACL plugin");
+ pseudo_plugin = G_OBJECT (aegis_acl_new ());
+ mcp_add_object (pseudo_plugin);
+ g_object_unref (pseudo_plugin);
+#endif
+
g_once_init_leave (&ready, 1);
}
}
diff --git a/tests/twisted/dispatcher/dispatch-delayed-by-mini-plugin.py b/tests/twisted/dispatcher/dispatch-delayed-by-mini-plugin.py
index 50bc8033..eeab38cb 100644
--- a/tests/twisted/dispatcher/dispatch-delayed-by-mini-plugin.py
+++ b/tests/twisted/dispatcher/dispatch-delayed-by-mini-plugin.py
@@ -174,10 +174,24 @@ def test(q, bus, mc):
q.dbus_return(e.message, signature='')
q.dbus_return(k.message, signature='')
+ empathy_cdo = bus.get_object(cs.CD, cdo_path)
+ empathy_cdo_iface = dbus.Interface(empathy_cdo, cs.CDO)
+ call_async(q, empathy_cdo_iface, 'Claim')
+
+ check_handler = q.expect('dbus-method-call', path='/com/example/Policy',
+ interface='com.example.Policy', method='CheckHandler')
+ q.dbus_raise(check_handler.message, 'com.example.Errors.No',
+ "That handler doesn't have enough options")
+ q.expect('dbus-error', method='Claim', name=cs.PERMISSION_DENIED)
+
kopete_cdo = bus.get_object(cs.CD, cdo_path)
kopete_cdo_iface = dbus.Interface(kopete_cdo, cs.CDO)
call_async(q, kopete_cdo_iface, 'Claim')
+ check_handler = q.expect('dbus-method-call', path='/com/example/Policy',
+ interface='com.example.Policy', method='CheckHandler')
+ q.dbus_return(check_handler.message, signature='')
+
q.expect_many(
EventPattern('dbus-signal', path=cdo_path, signal='Finished'),
EventPattern('dbus-signal', path=cs.CD_PATH,
@@ -187,6 +201,67 @@ def test(q, bus, mc):
sync_dbus(bus, q, mc)
+ # Try again; this time we'll reject a selected handler
+ e, chan, cdo_path = signal_channel_expect_query(q, bus, account, conn,
+ empathy, kopete)
+
+ # The request is fine, continue...
+ q.dbus_return(e.message, signature='')
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+ q.dbus_return(e.message, signature='')
+ q.dbus_return(k.message, signature='')
+
+ kopete_cdo = bus.get_object(cs.CD, cdo_path)
+ kopete_cdo_iface = dbus.Interface(kopete_cdo, cs.CDO)
+ call_async(q, kopete_cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Kopete')
+
+ check_handler = q.expect('dbus-method-call', path='/com/example/Policy',
+ interface='com.example.Policy', method='CheckHandler')
+ q.dbus_raise(check_handler.message, 'com.example.Errors.No',
+ 'That handler is not good enough')
+ q.expect('dbus-error', method='HandleWith', name=cs.PERMISSION_DENIED)
+
+ # well, let's try *something*... Kopete has been marked as failed,
+ # so this will try Empathy
+ call_async(q, kopete_cdo_iface, 'HandleWith', '')
+
+ check_handler = q.expect('dbus-method-call', path='/com/example/Policy',
+ interface='com.example.Policy', method='CheckHandler')
+ q.dbus_raise(check_handler.message, 'com.example.Errors.No',
+ 'That handler is no good either')
+
+ # Oops... we ran out of handlers
+ _, _, _, e = q.expect_many(
+ EventPattern('dbus-error', method='HandleWith',
+ name=cs.PERMISSION_DENIED),
+ EventPattern('dbus-signal', path=cdo_path,
+ interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished',
+ args=[cdo_path]),
+ EventPattern('dbus-method-call',
+ path=chan.object_path,
+ interface=cs.CHANNEL_IFACE_DESTROYABLE,
+ method='Destroy',
+ handled=False),
+ )
+ q.dbus_return(e.message, signature='')
+ chan.close()
+
+ sync_dbus(bus, q, mc)
+
# From now on we no longer want to forbid HandleChannels, but we do want
# to forbid AddDispatchOperation
q.unforbid_events(forbidden)
@@ -231,6 +306,14 @@ def test(q, bus, mc):
q.dbus_return(policy_request.message, signature='')
+ # Now we want to pass the channel to the selected handler.
+ # What does the policy service think about that?
+ check_handler = q.expect('dbus-method-call', path='/com/example/Policy',
+ interface='com.example.Policy', method='CheckHandler')
+
+ # Yeah, we're OK with that.
+ q.dbus_return(check_handler.message, signature='')
+
e = q.expect('dbus-method-call',
path=kopete.object_path,
interface=cs.HANDLER, method='HandleChannels',
diff --git a/tests/twisted/mcp-plugin.c b/tests/twisted/mcp-plugin.c
index 53aa1731..67e3a8fb 100644
--- a/tests/twisted/mcp-plugin.c
+++ b/tests/twisted/mcp-plugin.c
@@ -30,6 +30,35 @@
#define DEBUG g_debug
+/* ------ TestNoOpPlugin -------------------------------------- */
+/* doesn't implement anything, to check that NULL pointers are OK */
+
+typedef struct {
+ GObject parent;
+} TestNoOpPlugin;
+
+typedef struct {
+ GObjectClass parent_class;
+} TestNoOpPluginClass;
+
+GType test_no_op_plugin_get_type (void) G_GNUC_CONST;
+
+G_DEFINE_TYPE_WITH_CODE (TestNoOpPlugin, test_no_op_plugin,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (MCP_TYPE_DBUS_ACL, NULL);
+ G_IMPLEMENT_INTERFACE (MCP_TYPE_REQUEST_POLICY, NULL);
+ G_IMPLEMENT_INTERFACE (MCP_TYPE_DISPATCH_OPERATION_POLICY, NULL))
+
+static void
+test_no_op_plugin_init (TestNoOpPlugin *self)
+{
+}
+
+static void
+test_no_op_plugin_class_init (TestNoOpPluginClass *cls)
+{
+}
+
/* ------ TestPermissionPlugin -------------------------------------- */
typedef struct {
@@ -65,6 +94,7 @@ test_permission_plugin_class_init (TestPermissionPluginClass *cls)
typedef struct {
McpDispatchOperation *dispatch_operation;
McpDispatchOperationDelay *delay;
+ GSimpleAsyncResult *result;
} PermissionContext;
static void
@@ -72,7 +102,15 @@ permission_context_free (gpointer p)
{
PermissionContext *ctx = p;
- mcp_dispatch_operation_end_delay (ctx->dispatch_operation, ctx->delay);
+ if (ctx->delay != NULL)
+ mcp_dispatch_operation_end_delay (ctx->dispatch_operation, ctx->delay);
+
+ if (ctx->result != NULL)
+ {
+ g_simple_async_result_complete_in_idle (ctx->result);
+ g_object_unref (ctx->result);
+ }
+
g_object_unref (ctx->dispatch_operation);
g_slice_free (PermissionContext, ctx);
}
@@ -87,9 +125,18 @@ permission_cb (DBusPendingCall *pc,
if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR)
{
DEBUG ("Permission denied");
- mcp_dispatch_operation_leave_channels (ctx->dispatch_operation,
- TRUE, TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED,
- "Computer says no");
+
+ if (ctx->result != NULL)
+ {
+ g_simple_async_result_set_error (ctx->result, TP_ERRORS,
+ TP_ERROR_PERMISSION_DENIED, "No, sorry");
+ }
+ else
+ {
+ mcp_dispatch_operation_leave_channels (ctx->dispatch_operation,
+ TRUE, TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED,
+ "Computer says no");
+ }
}
else
{
@@ -180,11 +227,106 @@ finally:
}
static void
+handler_is_suitable_async (McpDispatchOperationPolicy *self,
+ TpClient *recipient,
+ const gchar *unique_name,
+ McpDispatchOperation *dispatch_operation,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple = g_simple_async_result_new ((GObject *) self,
+ callback, user_data, handler_is_suitable_async);
+ GHashTable *properties = mcp_dispatch_operation_ref_nth_channel_properties (
+ dispatch_operation, 0);
+ PermissionContext *ctx = NULL;
+
+ DEBUG ("enter");
+
+ if (properties == NULL)
+ {
+ DEBUG ("no channels!?");
+ goto finally;
+ }
+
+ /* currently this example just checks the first channel */
+
+ if (!tp_strdiff (tp_asv_get_string (properties,
+ TP_IFACE_CHANNEL ".TargetID"),
+ "policy@example.net"))
+ {
+ TpDBusDaemon *dbus_daemon = tp_dbus_daemon_dup (NULL);
+ DBusGConnection *gconn = tp_proxy_get_dbus_connection (dbus_daemon);
+ DBusConnection *libdbus = dbus_g_connection_get_connection (gconn);
+ DBusPendingCall *pc = NULL;
+ DBusMessage *message;
+
+ ctx = g_slice_new0 (PermissionContext);
+ ctx->dispatch_operation = g_object_ref (dispatch_operation);
+ ctx->result = simple;
+ /* take ownership */
+ simple = NULL;
+
+ /* in a real policy-mechanism you'd give some details, like the
+ * channel's properties or object path, and the name of the handler */
+ message = dbus_message_new_method_call ("com.example.Policy",
+ "/com/example/Policy", "com.example.Policy", "CheckHandler");
+
+ if (!dbus_connection_send_with_reply (libdbus, message,
+ &pc, -1))
+ {
+ g_error ("out of memory");
+ }
+
+ dbus_message_unref (message);
+
+ if (pc == NULL)
+ {
+ DEBUG ("got disconnected from D-Bus...");
+
+ goto finally;
+ }
+
+ /* pc is unreffed by permission_cb */
+
+ DEBUG ("Waiting for permission");
+
+ if (dbus_pending_call_get_completed (pc))
+ {
+ permission_cb (pc, ctx);
+ goto finally;
+ }
+
+ if (!dbus_pending_call_set_notify (pc, permission_cb, ctx,
+ permission_context_free))
+ {
+ g_error ("Out of memory");
+ }
+
+ /* ctx will be freed later */
+ ctx = NULL;
+ }
+
+finally:
+ if (ctx != NULL)
+ permission_context_free (ctx);
+
+ g_hash_table_unref (properties);
+
+ if (simple != NULL)
+ {
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ }
+}
+
+static void
cdo_policy_iface_init (McpDispatchOperationPolicyIface *iface,
gpointer unused G_GNUC_UNUSED)
{
- mcp_dispatch_operation_policy_iface_implement_check (iface,
- test_permission_plugin_check_cdo);
+ iface->check = test_permission_plugin_check_cdo;
+
+ iface->handler_is_suitable_async = handler_is_suitable_async;
+ /* the default finish function accepts our GSimpleAsyncResult */
}
typedef struct {
@@ -378,8 +520,7 @@ static void
rej_cdo_policy_iface_init (McpDispatchOperationPolicyIface *iface,
gpointer unused G_GNUC_UNUSED)
{
- mcp_dispatch_operation_policy_iface_implement_check (iface,
- test_rejection_plugin_check_cdo);
+ iface->check = test_rejection_plugin_check_cdo;
}
static void
@@ -429,13 +570,21 @@ mcp_plugin_ref_nth_object (guint n)
switch (n)
{
case 0:
- return g_object_new (test_permission_plugin_get_type (),
+ return g_object_new (test_no_op_plugin_get_type (),
NULL);
case 1:
+ return g_object_new (test_permission_plugin_get_type (),
+ NULL);
+
+ case 2:
return g_object_new (test_rejection_plugin_get_type (),
NULL);
+ case 3:
+ return g_object_new (test_no_op_plugin_get_type (),
+ NULL);
+
default:
return NULL;
}