From de11f360b009caea7acb547f0bf6968392dda304 Mon Sep 17 00:00:00 2001 From: Guillaume Desmottes Date: Wed, 25 May 2011 12:01:47 +0200 Subject: [PATCH] TpChannel: add high-level API for Password (#37360) --- docs/reference/telepathy-glib-sections.txt | 6 + telepathy-glib/channel-internal.h | 2 + telepathy-glib/channel.c | 219 ++++++++++++++++++++++++++++ telepathy-glib/channel.h | 16 ++ tests/dbus/channel.c | 107 ++++++++++++++ 5 files changed, 350 insertions(+), 0 deletions(-) diff --git a/docs/reference/telepathy-glib-sections.txt b/docs/reference/telepathy-glib-sections.txt index 0e72f6a..e3a4735 100644 --- a/docs/reference/telepathy-glib-sections.txt +++ b/docs/reference/telepathy-glib-sections.txt @@ -3081,6 +3081,7 @@ tp_channel_close_finish tp_channel_get_feature_quark_chat_states tp_channel_get_feature_quark_core tp_channel_get_feature_quark_group +tp_channel_get_feature_quark_password TP_CHANNEL_FEATURE_CHAT_STATES tp_channel_get_chat_state @@ -3094,6 +3095,11 @@ tp_channel_group_get_members tp_channel_group_get_remote_pending tp_channel_group_get_local_pending_info TP_ERRORS_REMOVED_FROM_GROUP + +TP_CHANNEL_FEATURE_PASSWORD +tp_channel_password_needed +tp_channel_provide_password_async +tp_channel_provide_password_finish tp_errors_removed_from_group_quark tp_channel_get_type diff --git a/telepathy-glib/channel-internal.h b/telepathy-glib/channel-internal.h index 4e4bcf6..87fc1f9 100644 --- a/telepathy-glib/channel-internal.h +++ b/telepathy-glib/channel-internal.h @@ -82,6 +82,8 @@ struct _TpChannelPrivate { unsigned exists:1; /* GetGroupFlags has returned */ unsigned have_group_flags:1; + + TpChannelPasswordFlags password_flags; }; /* channel.c internals */ diff --git a/telepathy-glib/channel.c b/telepathy-glib/channel.c index 724b17a..276bc34 100644 --- a/telepathy-glib/channel.c +++ b/telepathy-glib/channel.c @@ -100,6 +100,7 @@ enum PROP_REQUESTED, PROP_INITIATOR_HANDLE, PROP_INITIATOR_IDENTIFIER, + PROP_PASSWORD_NEEDED, N_PROPS }; @@ -450,6 +451,9 @@ tp_channel_get_property (GObject *object, case PROP_INITIATOR_IDENTIFIER: g_value_set_string (value, tp_channel_get_initiator_identifier (self)); break; + case PROP_PASSWORD_NEEDED: + g_value_set_boolean (value, tp_channel_password_needed (self)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -1337,10 +1341,78 @@ tp_channel_finalize (GObject *object) ((GObjectClass *) tp_channel_parent_class)->finalize (object); } +static void +got_password_flags_cb (TpChannel *self, + guint password_flags, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + GSimpleAsyncResult *result = user_data; + + if (error != NULL) + { + DEBUG ("Failed to get password flags: %s", error->message); + } + else + { + self->priv->password_flags = password_flags; + + if (tp_channel_password_needed (self)) + { + /* password-needed is FALSE by default */ + g_object_notify (G_OBJECT (self), "password-needed"); + } + } + + g_simple_async_result_complete (result); +} + +static void +password_flags_changed_cb (TpChannel *self, + guint added, + guint removed, + gpointer user_data, + GObject *weak_object) +{ + gboolean was_needed, needed; + + was_needed = tp_channel_password_needed (self); + + self->priv->password_flags |= added; + self->priv->password_flags ^= removed; + + needed = tp_channel_password_needed (self); + + if (was_needed != needed) + g_object_notify (G_OBJECT (self), "password-needed"); +} + +static void +tp_channel_prepare_password_async (TpProxy *proxy, + const TpProxyFeature *feature, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TpChannel *self = (TpChannel *) proxy; + GSimpleAsyncResult *result; + + result = g_simple_async_result_new ((GObject *) proxy, callback, user_data, + tp_channel_prepare_password_async); + + tp_cli_channel_interface_password_connect_to_password_flags_changed (self, + password_flags_changed_cb, self, NULL, G_OBJECT (self), NULL); + + tp_cli_channel_interface_password_call_get_password_flags (self, -1, + got_password_flags_cb, result, g_object_unref, G_OBJECT (self)); +} + + enum { FEAT_CORE, FEAT_GROUP, FEAT_CHAT_STATES, + FEAT_PASSWORD, N_FEAT }; @@ -1349,6 +1421,7 @@ tp_channel_list_features (TpProxyClass *cls G_GNUC_UNUSED) { static TpProxyFeature features[N_FEAT + 1] = { { 0 } }; static GQuark need_chat_states[2] = {0, 0}; + static GQuark need_password[2] = {0, 0}; if (G_LIKELY (features[0].name != 0)) return features; @@ -1364,6 +1437,12 @@ tp_channel_list_features (TpProxyClass *cls G_GNUC_UNUSED) need_chat_states[0] = TP_IFACE_QUARK_CHANNEL_INTERFACE_CHAT_STATE; features[FEAT_CHAT_STATES].interfaces_needed = need_chat_states; + features[FEAT_PASSWORD].name = TP_CHANNEL_FEATURE_PASSWORD; + features[FEAT_PASSWORD].prepare_async = + tp_channel_prepare_password_async; + need_password[0] = TP_IFACE_QUARK_CHANNEL_INTERFACE_PASSWORD; + features[FEAT_PASSWORD].interfaces_needed = need_password; + /* assert that the terminator at the end is there */ g_assert (features[N_FEAT].name == 0); @@ -1602,6 +1681,28 @@ tp_channel_class_init (TpChannelClass *klass) param_spec); /** + * TpChannel:password-needed: + * + * If %TRUE, tp_channel_provide_password_async() has to be called + * to be able to join the channel. + * + * This is not guaranteed to be meaningful until tp_proxy_prepare_async() has + * finished preparing %TP_CHANNEL_FEATURE_PASSWORD; until then, it may return + * %FALSE even if the channel is actually protected by a password. + * Preparing %TP_CHANNEL_FEATURE_PASSWORD also ensures that the + * notify::password-needed signal will be fired when this property changes. + * + * Since: 0.15.UNRELEASED + */ + param_spec = g_param_spec_boolean ("password-needed", + "Password needed", + "Password neede to join the channel", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_PASSWORD_NEEDED, + param_spec); + + /** * TpChannel::group-flags-changed: * @self: a channel * @added: #TpChannelGroupFlags which are newly set @@ -2382,3 +2483,121 @@ tp_channel_close_finish (TpChannel *self, { _tp_implement_finish_void (self, tp_channel_close_async) } + +/** + * TP_CHANNEL_FEATURE_PASSWORD: + * + * Expands to a call to a function that returns a quark representing the + * password feature on a #TpChannel. + * + * When this feature is prepared, tp_channel_password_needed() and the + * #TpChannel:password-needed property become useful. + * + * One can ask for a feature to be prepared using the + * tp_proxy_prepare_async() function, and waiting for it to callback. + * + * Since: 0.15.UNRELEASED + */ + +GQuark +tp_channel_get_feature_quark_password (void) +{ + return g_quark_from_static_string ("tp-channel-feature-password"); +} + +/** + * tp_channel_password_needed: + * @self: a #TpChannel + * + * Return the #TpChannel:password-needed property + * + * Returns: the value of #TpChannel:password-needed + * + * Since: 0.15.UNRELEASED + */ +gboolean +tp_channel_password_needed (TpChannel *self) +{ + return self->priv->password_flags & TP_CHANNEL_PASSWORD_FLAG_PROVIDE; +} + +static void +provide_password_cb (TpChannel *self, + gboolean correct, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + GSimpleAsyncResult *result = user_data; + + if (error != NULL) + { + g_simple_async_result_set_from_error (result, error); + } + else if (!correct) + { + DEBUG ("Wrong password provided for %s", tp_proxy_get_object_path (self)); + + g_simple_async_result_set_error (result, TP_ERRORS, + TP_ERROR_AUTHENTICATION_FAILED, "Password was not correct"); + } + + g_simple_async_result_complete (result); +} + +/** + * tp_channel_provide_password_async: + * @self: a #TpChannel + * @password: the password + * @callback: a callback to call when @password has been provided + * @user_data: data to pass to @callback + * + * Provide @password so that @self can be joined. + * This function must be called with the correct password in order for + * channel joining to proceed if the TpChannel:password-needed property + * is set. + * + * Once the password has been provided, @callback will be + * called. You can then call tp_channel_provide_password_finish() + * to get the result of the operation. + * + * Since: 0.15.UNRELEASED + */ +void +tp_channel_provide_password_async (TpChannel *self, + const gchar *password, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + + g_return_if_fail (TP_IS_CHANNEL (self)); + + result = g_simple_async_result_new (G_OBJECT (self), callback, + user_data, tp_channel_provide_password_async); + + tp_cli_channel_interface_password_call_provide_password (self, -1, password, + provide_password_cb, result, g_object_unref, G_OBJECT (self)); +} + +/** + * tp_channel_provide_password_finish: + * @self: a #TpChannel + * @result: a #GAsyncResult + * @error: a #GError to fill + * + * Finishes to provide a password. If the password was rejected, the operation + * fails with #TP_ERROR_AUTHENTICATION_FAILED. + * + * Returns: %TRUE if the password has been provided and accepted, + * %FALSE otherwise. + * + * Since: 0.15.UNRELEASED + */ +gboolean +tp_channel_provide_password_finish (TpChannel *self, + GAsyncResult *result, + GError **error) +{ + _tp_implement_finish_void (self, tp_channel_provide_password_async); +} diff --git a/telepathy-glib/channel.h b/telepathy-glib/channel.h index 440ae05..631430f 100644 --- a/telepathy-glib/channel.h +++ b/telepathy-glib/channel.h @@ -152,6 +152,22 @@ gboolean tp_channel_close_finish (TpChannel *self, GAsyncResult *result, GError **error); +/* Channel.Interface.Password */ +#define TP_CHANNEL_FEATURE_PASSWORD \ + tp_channel_get_feature_quark_password () +GQuark tp_channel_get_feature_quark_password (void) G_GNUC_CONST; + +gboolean tp_channel_password_needed (TpChannel *self); + +void tp_channel_provide_password_async (TpChannel *self, + const gchar *password, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean tp_channel_provide_password_finish (TpChannel *self, + GAsyncResult *result, + GError **error); + G_END_DECLS #include diff --git a/tests/dbus/channel.c b/tests/dbus/channel.c index fab026a..dae8511 100644 --- a/tests/dbus/channel.c +++ b/tests/dbus/channel.c @@ -412,6 +412,108 @@ test_close_room (Test *test, check_not_removed (test->chan_room_service); } +static void +property_changed_cb (GObject *object, + GParamSpec *spec, + Test *test) +{ + test->wait--; + if (test->wait <= 0) + g_main_loop_quit (test->mainloop); +} + +static void +test_password_feature (Test *test, + gconstpointer data G_GNUC_UNUSED) +{ + GQuark features[] = { TP_CHANNEL_FEATURE_PASSWORD, 0 }; + gboolean pass_needed; + + /* Channel needs a password */ + tp_tests_text_channel_set_password (test->chan_room_service, "test"); + + /* Feature is not yet prepared */ + g_assert (!tp_channel_password_needed (test->channel_room)); + g_object_get (test->channel_room, "password-needed", &pass_needed, NULL); + g_assert (!pass_needed); + + g_signal_connect (test->channel_room, "notify::password-needed", + G_CALLBACK (property_changed_cb), test); + + tp_proxy_prepare_async (test->channel_room, features, channel_prepared_cb, + test); + + test->wait = 2; + g_main_loop_run (test->mainloop); + g_assert_no_error (test->error); + + g_assert (tp_channel_password_needed (test->channel_room)); + g_object_get (test->channel_room, "password-needed", &pass_needed, NULL); + g_assert (pass_needed); + + /* Channel does not need a password any more */ + tp_tests_text_channel_set_password (test->chan_room_service, NULL); + + test->wait = 1; + g_main_loop_run (test->mainloop); + g_assert_no_error (test->error); + + g_assert (!tp_channel_password_needed (test->channel_room)); + g_object_get (test->channel_room, "password-needed", &pass_needed, NULL); + g_assert (!pass_needed); + + /* Channel does not re-need a password */ + tp_tests_text_channel_set_password (test->chan_room_service, "test"); + + test->wait = 1; + g_main_loop_run (test->mainloop); + g_assert_no_error (test->error); + + g_assert (tp_channel_password_needed (test->channel_room)); + g_object_get (test->channel_room, "password-needed", &pass_needed, NULL); + g_assert (pass_needed); +} + +static void +provide_password_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + Test *test = user_data; + + g_clear_error (&test->error); + + tp_channel_provide_password_finish (TP_CHANNEL (source), result, + &test->error); + + test->wait--; + if (test->wait <= 0) + g_main_loop_quit (test->mainloop); +} + +static void +test_password_provide (Test *test, + gconstpointer data G_GNUC_UNUSED) +{ + tp_tests_text_channel_set_password (test->chan_room_service, "test"); + + /* Try a wrong password */ + tp_channel_provide_password_async (test->channel_room, "badger", + provide_password_cb, test); + + test->wait = 1; + g_main_loop_run (test->mainloop); + g_assert_error (test->error, TP_ERRORS, TP_ERROR_AUTHENTICATION_FAILED); + + /* Try the right password */ + tp_channel_provide_password_async (test->channel_room, "test", + provide_password_cb, test); + + test->wait = 1; + g_main_loop_run (test->mainloop); + g_assert_no_error (test->error); +} + int main (int argc, char **argv) @@ -442,5 +544,10 @@ main (int argc, g_test_add ("/channel/close/room", Test, NULL, setup, test_close_room, teardown); + g_test_add ("/channel/password/feature", Test, NULL, setup, + test_password_feature, teardown); + g_test_add ("/channel/password/provide", Test, NULL, setup, + test_password_provide, teardown); + return g_test_run (); } -- 1.7.4.1