From e0c8a5249b6edd36ead695ce382e631bec6345d8 Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Tue, 17 May 2011 14:39:26 -0400 Subject: Move backend code into own dir and strip the word Backend from identifiers Signed-off-by: David Zeuthen --- configure.ac | 1 + doc/Makefile.am | 16 +- doc/goa-docs.xml | 22 +- doc/goa-sections.txt | 256 ++-- doc/goa.types | 23 +- src/Makefile.am | 2 +- src/daemon/Makefile.am | 2 +- src/daemon/goadaemon.c | 30 +- src/goa/Makefile.am | 71 -- src/goa/goabackend.h | 45 - src/goa/goabackendenums.h | 36 - src/goa/goabackendenumtypes.c.template | 40 - src/goa/goabackendenumtypes.h.template | 24 - src/goa/goabackendfacebookprovider.c | 307 ----- src/goa/goabackendfacebookprovider.h | 42 - src/goa/goabackendgoogleprovider.c | 352 ------ src/goa/goabackendgoogleprovider.h | 42 - src/goa/goabackendimapauth.c | 84 -- src/goa/goabackendimapauth.h | 88 -- src/goa/goabackendimapauthoauth.c | 568 --------- src/goa/goabackendimapauthoauth.h | 46 - src/goa/goabackendimapclient.c | 694 ----------- src/goa/goabackendimapclient.h | 65 - src/goa/goabackendimapmail.c | 1058 ---------------- src/goa/goabackendimapmail.h | 45 - src/goa/goabackendoauth2provider.c | 1473 ----------------------- src/goa/goabackendoauth2provider.h | 133 --- src/goa/goabackendoauthprovider.c | 1593 ------------------------- src/goa/goabackendoauthprovider.h | 137 --- src/goa/goabackendprovider.c | 672 ----------- src/goa/goabackendprovider.h | 164 --- src/goa/goabackendtwitterprovider.c | 332 ------ src/goa/goabackendtwitterprovider.h | 42 - src/goa/goabackendtypes.h | 71 -- src/goa/goabackendyahooprovider.c | 369 ------ src/goa/goabackendyahooprovider.h | 42 - src/goabackend/goabackend.h | 45 + src/goabackend/goabackendenums.h | 36 + src/goabackend/goabackendenumtypes.c.template | 40 + src/goabackend/goabackendenumtypes.h.template | 24 + src/goabackend/goabackendtypes.h | 71 ++ src/goabackend/goafacebookprovider.c | 307 +++++ src/goabackend/goafacebookprovider.h | 42 + src/goabackend/goagoogleprovider.c | 351 ++++++ src/goabackend/goagoogleprovider.h | 42 + src/goabackend/goaimapauth.c | 84 ++ src/goabackend/goaimapauth.h | 88 ++ src/goabackend/goaimapauthoauth.c | 568 +++++++++ src/goabackend/goaimapauthoauth.h | 46 + src/goabackend/goaimapclient.c | 694 +++++++++++ src/goabackend/goaimapclient.h | 63 + src/goabackend/goaimapmail.c | 1058 ++++++++++++++++ src/goabackend/goaimapmail.h | 45 + src/goabackend/goaoauth2provider.c | 1473 +++++++++++++++++++++++ src/goabackend/goaoauth2provider.h | 130 ++ src/goabackend/goaoauthprovider.c | 1593 +++++++++++++++++++++++++ src/goabackend/goaoauthprovider.h | 134 +++ src/goabackend/goaprovider.c | 672 +++++++++++ src/goabackend/goaprovider.h | 159 +++ src/goabackend/goatwitterprovider.c | 332 ++++++ src/goabackend/goatwitterprovider.h | 42 + src/goabackend/goayahooprovider.c | 369 ++++++ src/goabackend/goayahooprovider.h | 42 + src/panel/Makefile.am | 2 +- src/panel/goapanel.c | 44 +- 65 files changed, 8750 insertions(+), 8833 deletions(-) delete mode 100644 src/goa/goabackend.h delete mode 100644 src/goa/goabackendenums.h delete mode 100644 src/goa/goabackendenumtypes.c.template delete mode 100644 src/goa/goabackendenumtypes.h.template delete mode 100644 src/goa/goabackendfacebookprovider.c delete mode 100644 src/goa/goabackendfacebookprovider.h delete mode 100644 src/goa/goabackendgoogleprovider.c delete mode 100644 src/goa/goabackendgoogleprovider.h delete mode 100644 src/goa/goabackendimapauth.c delete mode 100644 src/goa/goabackendimapauth.h delete mode 100644 src/goa/goabackendimapauthoauth.c delete mode 100644 src/goa/goabackendimapauthoauth.h delete mode 100644 src/goa/goabackendimapclient.c delete mode 100644 src/goa/goabackendimapclient.h delete mode 100644 src/goa/goabackendimapmail.c delete mode 100644 src/goa/goabackendimapmail.h delete mode 100644 src/goa/goabackendoauth2provider.c delete mode 100644 src/goa/goabackendoauth2provider.h delete mode 100644 src/goa/goabackendoauthprovider.c delete mode 100644 src/goa/goabackendoauthprovider.h delete mode 100644 src/goa/goabackendprovider.c delete mode 100644 src/goa/goabackendprovider.h delete mode 100644 src/goa/goabackendtwitterprovider.c delete mode 100644 src/goa/goabackendtwitterprovider.h delete mode 100644 src/goa/goabackendtypes.h delete mode 100644 src/goa/goabackendyahooprovider.c delete mode 100644 src/goa/goabackendyahooprovider.h create mode 100644 src/goabackend/goabackend.h create mode 100644 src/goabackend/goabackendenums.h create mode 100644 src/goabackend/goabackendenumtypes.c.template create mode 100644 src/goabackend/goabackendenumtypes.h.template create mode 100644 src/goabackend/goabackendtypes.h create mode 100644 src/goabackend/goafacebookprovider.c create mode 100644 src/goabackend/goafacebookprovider.h create mode 100644 src/goabackend/goagoogleprovider.c create mode 100644 src/goabackend/goagoogleprovider.h create mode 100644 src/goabackend/goaimapauth.c create mode 100644 src/goabackend/goaimapauth.h create mode 100644 src/goabackend/goaimapauthoauth.c create mode 100644 src/goabackend/goaimapauthoauth.h create mode 100644 src/goabackend/goaimapclient.c create mode 100644 src/goabackend/goaimapclient.h create mode 100644 src/goabackend/goaimapmail.c create mode 100644 src/goabackend/goaimapmail.h create mode 100644 src/goabackend/goaoauth2provider.c create mode 100644 src/goabackend/goaoauth2provider.h create mode 100644 src/goabackend/goaoauthprovider.c create mode 100644 src/goabackend/goaoauthprovider.h create mode 100644 src/goabackend/goaprovider.c create mode 100644 src/goabackend/goaprovider.h create mode 100644 src/goabackend/goatwitterprovider.c create mode 100644 src/goabackend/goatwitterprovider.h create mode 100644 src/goabackend/goayahooprovider.c create mode 100644 src/goabackend/goayahooprovider.h diff --git a/configure.ac b/configure.ac index e79ae2c..48f14dd 100644 --- a/configure.ac +++ b/configure.ac @@ -82,6 +82,7 @@ Makefile data/Makefile src/Makefile src/goa/Makefile +src/goabackend/Makefile src/daemon/Makefile src/panel/Makefile src/examples/Makefile diff --git a/doc/Makefile.am b/doc/Makefile.am index 693d5ac..c867b7e 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -13,10 +13,10 @@ DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.xml SCAN_OPTIONS= # The directory containing the source code. Relative to $(srcdir) -DOC_SOURCE_DIR=$(top_srcdir)/src/goa +DOC_SOURCE_DIR=$(top_srcdir)/src -HFILE_GLOB=$(top_srcdir)/src/goa/*.h -CFILE_GLOB=$(top_srcdir)/src/goa/*.c +HFILE_GLOB=$(top_srcdir)/src/goa/*.h $(top_srcdir)/src/goabackend/*.h +CFILE_GLOB=$(top_srcdir)/src/goa/*.c $(top_srcdir)/src/goabackend/*.c # Headers to ignore IGNORE_HFILES= \ @@ -33,11 +33,11 @@ INCLUDES = \ $(GIO_CFLAGS) \ $(NULL) -GTKDOC_LIBS = \ - $(GLIB_LIBS) \ - $(GIO_LIBS) \ - $(top_builddir)/src/goa/libgoa.la \ - $(top_builddir)/src/goa/libgoa-backend.la \ +GTKDOC_LIBS = \ + $(GLIB_LIBS) \ + $(GIO_LIBS) \ + $(top_builddir)/src/goa/libgoa.la \ + $(top_builddir)/src/goabackend/libgoa-backend.la \ $(NULL) # Extra options to supply to gtkdoc-mkdb diff --git a/doc/goa-docs.xml b/doc/goa-docs.xml index 05910b8..05eee4b 100644 --- a/doc/goa-docs.xml +++ b/doc/goa-docs.xml @@ -131,17 +131,17 @@ Backend Library API Reference - - - - - - - - - - - + + + + + + + + + + + diff --git a/doc/goa-sections.txt b/doc/goa-sections.txt index f99c7ab..a3f4b83 100644 --- a/doc/goa-sections.txt +++ b/doc/goa-sections.txt @@ -484,122 +484,122 @@ goa_error_get_type
-goabackendprovider -GoaBackendProvider -GoaBackendProviderClass -goa_backend_provider_get_provider_type -goa_backend_provider_get_name -goa_backend_provider_build_object -goa_backend_provider_add_account -goa_backend_provider_refresh_account -goa_backend_provider_store_credentials_sync -goa_backend_provider_lookup_credentials_sync -goa_backend_provider_ensure_credentials -goa_backend_provider_ensure_credentials_finish -goa_backend_provider_ensure_credentials_sync -GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME -goa_backend_provider_get_all -goa_backend_provider_get_for_provider_type +goaprovider +GoaProvider +GoaProviderClass +goa_provider_get_provider_type +goa_provider_get_name +goa_provider_build_object +goa_provider_add_account +goa_provider_refresh_account +goa_provider_store_credentials_sync +goa_provider_lookup_credentials_sync +goa_provider_ensure_credentials +goa_provider_ensure_credentials_finish +goa_provider_ensure_credentials_sync +GOA_PROVIDER_EXTENSION_POINT_NAME +goa_provider_get_all +goa_provider_get_for_provider_type -GOA_BACKEND_PROVIDER -GOA_BACKEND_PROVIDER_CLASS -GOA_BACKEND_PROVIDER_GET_CLASS -GOA_IS_BACKEND_PROVIDER -GOA_IS_BACKEND_PROVIDER_CLASS -GOA_TYPE_BACKEND_PROVIDER -GoaBackendProviderPrivate -goa_backend_provider_get_type +GOA_PROVIDER +GOA_PROVIDER_CLASS +GOA_PROVIDER_GET_CLASS +GOA_IS_PROVIDER +GOA_IS_PROVIDER_CLASS +GOA_TYPE_PROVIDER +GoaProviderPrivate +goa_provider_get_type
-goabackendoauth2provider -GoaBackendOAuth2Provider -GoaBackendOAuth2ProviderClass -goa_backend_oauth2_provider_get_authorization_uri -goa_backend_oauth2_provider_get_token_uri -goa_backend_oauth2_provider_get_redirect_uri -goa_backend_oauth2_provider_get_scope -goa_backend_oauth2_provider_get_client_id -goa_backend_oauth2_provider_get_client_secret -goa_backend_oauth2_provider_build_authorization_uri -goa_backend_oauth2_provider_get_use_external_browser -goa_backend_oauth2_provider_get_identity_sync -goa_backend_oauth2_provider_get_access_token_sync +goaoauth2provider +GoaOAuth2Provider +GoaOAuth2ProviderClass +goa_oauth2_provider_get_authorization_uri +goa_oauth2_provider_get_token_uri +goa_oauth2_provider_get_redirect_uri +goa_oauth2_provider_get_scope +goa_oauth2_provider_get_client_id +goa_oauth2_provider_get_client_secret +goa_oauth2_provider_build_authorization_uri +goa_oauth2_provider_get_use_external_browser +goa_oauth2_provider_get_identity_sync +goa_oauth2_provider_get_access_token_sync -GOA_BACKEND_OAUTH2_PROVIDER -GOA_BACKEND_OAUTH2_PROVIDER_CLASS -GOA_BACKEND_OAUTH2_PROVIDER_GET_CLASS -GOA_IS_BACKEND_OAUTH2_PROVIDER -GOA_IS_BACKEND_OAUTH2_PROVIDER_CLASS -GOA_TYPE_BACKEND_OAUTH2_PROVIDER -GoaBackendOAuth2ProviderPrivate -goa_backend_oauth2_provider_get_type +GOA_OAUTH2_PROVIDER +GOA_OAUTH2_PROVIDER_CLASS +GOA_OAUTH2_PROVIDER_GET_CLASS +GOA_IS_OAUTH2_PROVIDER +GOA_IS_OAUTH2_PROVIDER_CLASS +GOA_TYPE_OAUTH2_PROVIDER +GoaOAuth2ProviderPrivate +goa_oauth2_provider_get_type
-goabackendoauthprovider -GoaBackendOAuthProvider -GoaBackendOAuthProviderClass -goa_backend_oauth_provider_get_request_uri -goa_backend_oauth_provider_get_request_uri_params -goa_backend_oauth_provider_get_authorization_uri -goa_backend_oauth_provider_get_token_uri -goa_backend_oauth_provider_get_callback_uri -goa_backend_oauth_provider_get_consumer_key -goa_backend_oauth_provider_get_consumer_secret -goa_backend_oauth_provider_build_authorization_uri -goa_backend_oauth_provider_get_use_external_browser -goa_backend_oauth_provider_get_identity_sync -goa_backend_oauth_provider_get_access_token_sync +goaoauthprovider +GoaOAuthProvider +GoaOAuthProviderClass +goa_oauth_provider_get_request_uri +goa_oauth_provider_get_request_uri_params +goa_oauth_provider_get_authorization_uri +goa_oauth_provider_get_token_uri +goa_oauth_provider_get_callback_uri +goa_oauth_provider_get_consumer_key +goa_oauth_provider_get_consumer_secret +goa_oauth_provider_build_authorization_uri +goa_oauth_provider_get_use_external_browser +goa_oauth_provider_get_identity_sync +goa_oauth_provider_get_access_token_sync -GOA_BACKEND_OAUTH_PROVIDER -GOA_BACKEND_OAUTH_PROVIDER_CLASS -GOA_BACKEND_OAUTH_PROVIDER_GET_CLASS -GOA_IS_BACKEND_OAUTH_PROVIDER -GOA_IS_BACKEND_OAUTH_PROVIDER_CLASS -GOA_TYPE_BACKEND_OAUTH_PROVIDER -GoaBackendOAuthProviderPrivate -goa_backend_oauth_provider_get_type +GOA_OAUTH_PROVIDER +GOA_OAUTH_PROVIDER_CLASS +GOA_OAUTH_PROVIDER_GET_CLASS +GOA_IS_OAUTH_PROVIDER +GOA_IS_OAUTH_PROVIDER_CLASS +GOA_TYPE_OAUTH_PROVIDER +GoaOAuthProviderPrivate +goa_oauth_provider_get_type
-goabackendgoogleprovider -GoaBackendGoogleProvider +goagoogleprovider +GoaGoogleProvider -GOA_BACKEND_GOOGLE_PROVIDER -GOA_IS_BACKEND_GOOGLE_PROVIDER -GOA_TYPE_BACKEND_GOOGLE_PROVIDER -goa_backend_google_provider_get_type +GOA_GOOGLE_PROVIDER +GOA_IS_GOOGLE_PROVIDER +GOA_TYPE_GOOGLE_PROVIDER +goa_google_provider_get_type
-goabackendfacebookprovider -GoaBackendFacebookProvider +goafacebookprovider +GoaFacebookProvider -GOA_BACKEND_FACEBOOK_PROVIDER -GOA_IS_BACKEND_FACEBOOK_PROVIDER -GOA_TYPE_BACKEND_FACEBOOK_PROVIDER -goa_backend_facebook_provider_get_type +GOA_FACEBOOK_PROVIDER +GOA_IS_FACEBOOK_PROVIDER +GOA_TYPE_FACEBOOK_PROVIDER +goa_facebook_provider_get_type
-goabackendyahooprovider -GoaBackendYahooProvider +goayahooprovider +GoaYahooProvider -GOA_BACKEND_YAHOO_PROVIDER -GOA_IS_BACKEND_YAHOO_PROVIDER -GOA_TYPE_BACKEND_YAHOO_PROVIDER -goa_backend_yahoo_provider_get_type +GOA_YAHOO_PROVIDER +GOA_IS_YAHOO_PROVIDER +GOA_TYPE_YAHOO_PROVIDER +goa_yahoo_provider_get_type
-goabackendtwitterprovider -GoaBackendTwitterProvider +goatwitterprovider +GoaTwitterProvider -GOA_BACKEND_TWITTER_PROVIDER -GOA_IS_BACKEND_TWITTER_PROVIDER -GOA_TYPE_BACKEND_TWITTER_PROVIDER -goa_backend_twitter_provider_get_type +GOA_TWITTER_PROVIDER +GOA_IS_TWITTER_PROVIDER +GOA_TYPE_TWITTER_PROVIDER +goa_twitter_provider_get_type
@@ -699,54 +699,54 @@ goa_mail_monitor_skeleton_get_type
-goabackendimapclient -GoaBackendImapClient -goa_backend_imap_client_new -goa_backend_imap_client_connect_sync -goa_backend_imap_client_run_command_sync -goa_backend_imap_client_idle_sync -goa_backend_imap_client_disconnect_sync +goaimapclient +GoaImapClient +goa_imap_client_new +goa_imap_client_connect_sync +goa_imap_client_run_command_sync +goa_imap_client_idle_sync +goa_imap_client_disconnect_sync -GOA_BACKEND_IMAP_CLIENT -GOA_IS_BACKEND_IMAP_CLIENT -GOA_TYPE_BACKEND_IMAP_CLIENT -goa_backend_imap_client_get_type +GOA_IMAP_CLIENT +GOA_IS_IMAP_CLIENT +GOA_TYPE_IMAP_CLIENT +goa_imap_client_get_type
-goabackendimapmail -GoaBackendImapMail -goa_backend_imap_mail_new +goaimapmail +GoaImapMail +goa_imap_mail_new -GOA_BACKEND_IMAP_MAIL -GOA_IS_BACKEND_IMAP_MAIL -GOA_TYPE_BACKEND_IMAP_MAIL -goa_backend_imap_mail_get_type +GOA_IMAP_MAIL +GOA_IS_IMAP_MAIL +GOA_TYPE_IMAP_MAIL +goa_imap_mail_get_type
-goabackendimapauth -GoaBackendImapAuth -GoaBackendImapAuthClass -goa_backend_imap_auth_run_sync +goaimapauth +GoaImapAuth +GoaImapAuthClass +goa_imap_auth_run_sync -GoaBackendImapAuthPrivate -GOA_BACKEND_IMAP_AUTH -GOA_IS_BACKEND_IMAP_AUTH -GOA_TYPE_BACKEND_IMAP_AUTH -GOA_BACKEND_IMAP_AUTH_CLASS -GOA_IS_BACKEND_IMAP_AUTH_CLASS -GOA_BACKEND_IMAP_AUTH_GET_CLASS -goa_backend_imap_auth_get_type +GoaImapAuthPrivate +GOA_IMAP_AUTH +GOA_IS_IMAP_AUTH +GOA_TYPE_IMAP_AUTH +GOA_IMAP_AUTH_CLASS +GOA_IS_IMAP_AUTH_CLASS +GOA_IMAP_AUTH_GET_CLASS +goa_imap_auth_get_type
-goabackendimapauthoauth -GoaBackendImapAuthOAuth -goa_backend_imap_auth_oauth_new +goaimapauthoauth +GoaImapAuthOAuth +goa_imap_auth_oauth_new -GOA_BACKEND_IMAP_AUTH_OAUTH -GOA_IS_BACKEND_IMAP_AUTH_OAUTH -GOA_TYPE_BACKEND_IMAP_AUTH_OAUTH -goa_backend_imap_auth_oauth_get_type +GOA_IMAP_AUTH_OAUTH +GOA_IS_IMAP_AUTH_OAUTH +GOA_TYPE_IMAP_AUTH_OAUTH +goa_imap_auth_oauth_get_type
diff --git a/doc/goa.types b/doc/goa.types index f87f4b0..5a73646 100644 --- a/doc/goa.types +++ b/doc/goa.types @@ -1,11 +1,4 @@ goa_client_get_type -goa_backend_provider_get_type -goa_backend_oauth_provider_get_type -goa_backend_oauth2_provider_get_type -goa_backend_google_provider_get_type -goa_backend_facebook_provider_get_type -goa_backend_yahoo_provider_get_type -goa_backend_twitter_provider_get_type goa_account_get_type goa_account_proxy_get_type goa_account_skeleton_get_type @@ -36,7 +29,15 @@ goa_mail_skeleton_get_type goa_mail_monitor_get_type goa_mail_monitor_proxy_get_type goa_mail_monitor_skeleton_get_type -goa_backend_imap_auth_get_type -goa_backend_imap_auth_oauth_get_type -goa_backend_imap_client_get_type -goa_backend_imap_mail_get_type + +goa_provider_get_type +goa_oauth_provider_get_type +goa_oauth2_provider_get_type +goa_google_provider_get_type +goa_facebook_provider_get_type +goa_yahoo_provider_get_type +goa_twitter_provider_get_type +goa_imap_auth_get_type +goa_imap_auth_oauth_get_type +goa_imap_client_get_type +goa_imap_mail_get_type diff --git a/src/Makefile.am b/src/Makefile.am index 6027402..1bbe999 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,4 @@ NULL = -SUBDIRS = goa daemon panel examples +SUBDIRS = goa goabackend daemon panel examples diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am index 73476f2..32bf722 100644 --- a/src/daemon/Makefile.am +++ b/src/daemon/Makefile.am @@ -35,7 +35,7 @@ goa_daemon_CFLAGS = \ goa_daemon_LDADD = \ $(GLIB_LIBS) \ $(top_builddir)/src/goa/libgoa.la \ - $(top_builddir)/src/goa/libgoa-backend.la \ + $(top_builddir)/src/goabackend/libgoa-backend.la \ $(GTK_LIBS) \ $(LIBNOTIFY_LIBS) \ $(REST_LIBS) \ diff --git a/src/daemon/goadaemon.c b/src/daemon/goadaemon.c index ce57311..721bccd 100644 --- a/src/daemon/goadaemon.c +++ b/src/daemon/goadaemon.c @@ -36,7 +36,7 @@ #include #include "goadaemon.h" -#include "goa/goabackend.h" +#include "goabackend/goabackend.h" struct _GoaDaemon { @@ -368,7 +368,7 @@ update_account_object (GoaDaemon *daemon, gboolean just_added) { GoaAccount *account; - GoaBackendProvider *provider; + GoaProvider *provider; gboolean ret; gchar *name; gchar *type; @@ -402,7 +402,7 @@ update_account_object (GoaDaemon *daemon, goa_account_set_provider_type (account, type); goa_account_set_name (account, name); - provider = goa_backend_provider_get_for_provider_type (type); + provider = goa_provider_get_for_provider_type (type); if (provider == NULL) { /* TODO: syslog */ @@ -411,7 +411,7 @@ update_account_object (GoaDaemon *daemon, } error = NULL; - if (!goa_backend_provider_build_object (provider, object, key_file, group, &error)) + if (!goa_provider_build_object (provider, object, key_file, group, &error)) { /* TODO: syslog */ g_warning ("Error parsing account: %s (%s, %d)", @@ -1174,16 +1174,16 @@ is_authorization_error (GError *error) } static void -ensure_credentials_cb (GoaBackendProvider *provider, - GAsyncResult *res, - gpointer user_data) +ensure_credentials_cb (GoaProvider *provider, + GAsyncResult *res, + gpointer user_data) { EnsureCredentialsData *data = user_data; gint expires_in; GError *error; error= NULL; - if (!goa_backend_provider_ensure_credentials_finish (provider, &expires_in, res, &error)) + if (!goa_provider_ensure_credentials_finish (provider, &expires_in, res, &error)) { /* Set AttentionNeeded only if the error is an authorization error */ if (is_authorization_error (error)) @@ -1230,11 +1230,11 @@ on_account_handle_ensure_credentials (GoaAccount *account, gpointer user_data) { GoaDaemon *daemon = GOA_DAEMON (user_data); - GoaBackendProvider *provider; + GoaProvider *provider; GoaObject *object; object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (account))); - provider = goa_backend_provider_get_for_provider_type (goa_account_get_provider_type (account)); + provider = goa_provider_get_for_provider_type (goa_account_get_provider_type (account)); if (provider == NULL) { /* TODO: syslog */ @@ -1247,11 +1247,11 @@ on_account_handle_ensure_credentials (GoaAccount *account, goto out; } - goa_backend_provider_ensure_credentials (provider, - object, - NULL, /* GCancellable */ - (GAsyncReadyCallback) ensure_credentials_cb, - ensure_credentials_data_new (daemon, object, invocation)); + goa_provider_ensure_credentials (provider, + object, + NULL, /* GCancellable */ + (GAsyncReadyCallback) ensure_credentials_cb, + ensure_credentials_data_new (daemon, object, invocation)); out: return TRUE; /* invocation was handled */ diff --git a/src/goa/Makefile.am b/src/goa/Makefile.am index fab70e2..2458cae 100644 --- a/src/goa/Makefile.am +++ b/src/goa/Makefile.am @@ -42,19 +42,8 @@ goaenumtypes.c: goaenums.h goaenumtypes.c.template cd $(srcdir) && glib-mkenums --template goaenumtypes.c.template goaenums.h ) > \ goaenumtypes.c.tmp && mv goaenumtypes.c.tmp goaenumtypes.c -goabackendenumtypes.h: goabackendenums.h goabackendenumtypes.h.template - ( top_builddir=`cd $(top_builddir) && pwd`; \ - cd $(srcdir) && glib-mkenums --template goabackendenumtypes.h.template goabackendenums.h ) > \ - goabackendenumtypes.h.tmp && mv goabackendenumtypes.h.tmp goabackendenumtypes.h - -goabackendenumtypes.c: goabackendenums.h goabackendenumtypes.c.template - ( top_builddir=`cd $(top_builddir) && pwd`; \ - cd $(srcdir) && glib-mkenums --template goabackendenumtypes.c.template goabackendenums.h ) > \ - goabackendenumtypes.c.tmp && mv goabackendenumtypes.c.tmp goabackendenumtypes.c - enum_built_sources = \ goaenumtypes.h goaenumtypes.c \ - goabackendenumtypes.h goabackendenumtypes.c \ $(NULL) # ---------------------------------------------------------------------------------------------------- @@ -125,65 +114,5 @@ endif # HAVE_INTROSPECTION # ---------------------------------------------------------------------------------------------------- -lib_LTLIBRARIES += libgoa-backend.la - -libgoa_backend_ladir = $(includedir)/goa/goa - -libgoa_backend_la_HEADERS = \ - goabackend.h \ - goabackendtypes.h \ - goabackendprovider.h \ - goabackendoauthprovider.h \ - goabackendoauth2provider.h \ - goabackendgoogleprovider.h \ - goabackendfacebookprovider.h \ - goabackendyahooprovider.h \ - goabackendtwitterprovider.h \ - goabackendimapauth.h \ - goabackendimapauthoauth.h \ - goabackendimapclient.h \ - goabackendenums.h \ - goabackendenumtypes.h \ - $(NULL) - -libgoa_backend_la_SOURCES = \ - goabackend.h \ - goabackendtypes.h \ - goabackendprovider.h goabackendprovider.c \ - goabackendoauthprovider.h goabackendoauthprovider.c \ - goabackendoauth2provider.h goabackendoauth2provider.c \ - goabackendgoogleprovider.h goabackendgoogleprovider.c \ - goabackendfacebookprovider.h goabackendfacebookprovider.c \ - goabackendyahooprovider.h goabackendyahooprovider.c \ - goabackendtwitterprovider.h goabackendtwitterprovider.c \ - goabackendimapauth.h goabackendimapauth.c \ - goabackendimapauthoauth.h goabackendimapauthoauth.c \ - goabackendimapclient.h goabackendimapclient.c \ - goabackendimapmail.h goabackendimapmail.c \ - goabackendenumtypes.h goabackendenumtypes.c \ - $(NULL) - -libgoa_backend_la_CFLAGS = \ - -DGOA_BACKEND_COMPILATION \ - -DGOA_API_IS_SUBJECT_TO_CHANGE \ - $(WEBKIT_GTK_CFLAGS) \ - $(JSON_GLIB_CFLAGS) \ - $(GLIB_CFLAGS) \ - $(GTK_CFLAGS) \ - $(GNOME_KEYRING_CFLAGS) \ - $(REST_CFLAGS) \ - $(NULL) - -libgoa_backend_la_LIBADD = \ - $(WEBKIT_GTK_LIBS) \ - $(JSON_GLIB_LIBS) \ - $(GLIB_LIBS) \ - $(GTK_LIBS) \ - $(GNOME_KEYRING_LIBS) \ - $(REST_LIBS) \ - $(NULL) - -# ---------------------------------------------------------------------------------------------------- - clean-local : rm -f *~ goa-generated-doc-*.xml $(dbus_built_sources) $(enum_built_sources) diff --git a/src/goa/goabackend.h b/src/goa/goabackend.h deleted file mode 100644 index df265ce..0000000 --- a/src/goa/goabackend.h +++ /dev/null @@ -1,45 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#ifndef __GOA_BACKEND_H__ -#define __GOA_BACKEND_H__ - -#if !defined(GOA_BACKEND_API_IS_SUBJECT_TO_CHANGE) && !defined(GOA_BACKEND_COMPILATION) -#error libgoa-backend is unstable API. You must define GOA_API_IS_SUBJECT_TO_CHANGE before including goa/goabackend.h -#endif - -#define __GOA_BACKEND_INSIDE_GOA_BACKEND_H__ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#undef __GOA_BACKEND_INSIDE_GOA_BACKEND_H__ - -#endif /* __GOA_BACKEND_H__ */ diff --git a/src/goa/goabackendenums.h b/src/goa/goabackendenums.h deleted file mode 100644 index c13587e..0000000 --- a/src/goa/goabackendenums.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __GOA_BACKEND_ENUMS_H__ -#define __GOA_BACKEND_ENUMS_H__ - -#include - -G_BEGIN_DECLS - -G_END_DECLS - -#endif /* __GOA_BACKEND_ENUMS_H__ */ diff --git a/src/goa/goabackendenumtypes.c.template b/src/goa/goabackendenumtypes.c.template deleted file mode 100644 index 7a76459..0000000 --- a/src/goa/goabackendenumtypes.c.template +++ /dev/null @@ -1,40 +0,0 @@ -/*** BEGIN file-header ***/ -#include "goabackendenums.h" -#include "goabackendenumtypes.h" - -/*** END file-header ***/ - -/*** BEGIN file-production ***/ -/* enumerations from "@filename@" */ -/*** END file-production ***/ - -/*** BEGIN value-header ***/ -GType -@enum_name@_get_type (void) -{ - static volatile gsize g_define_type_id__volatile = 0; - - if (g_once_init_enter (&g_define_type_id__volatile)) - { - static const G@Type@Value values[] = { -/*** END value-header ***/ - -/*** BEGIN value-production ***/ - { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, -/*** END value-production ***/ - -/*** BEGIN value-tail ***/ - { 0, NULL, NULL } - }; - GType g_define_type_id = - g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); - g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); - } - - return g_define_type_id__volatile; -} - -/*** END value-tail ***/ - -/*** BEGIN file-tail ***/ -/*** END file-tail ***/ diff --git a/src/goa/goabackendenumtypes.h.template b/src/goa/goabackendenumtypes.h.template deleted file mode 100644 index 7321076..0000000 --- a/src/goa/goabackendenumtypes.h.template +++ /dev/null @@ -1,24 +0,0 @@ -/*** BEGIN file-header ***/ -#ifndef __GOA_BACKEND_ENUM_TYPES_H__ -#define __GOA_BACKEND_ENUM_TYPES_H__ - -#include - -G_BEGIN_DECLS -/*** END file-header ***/ - -/*** BEGIN file-production ***/ - -/* enumerations from "@filename@" */ -/*** END file-production ***/ - -/*** BEGIN value-header ***/ -GType @enum_name@_get_type (void) G_GNUC_CONST; -#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) -/*** END value-header ***/ - -/*** BEGIN file-tail ***/ -G_END_DECLS - -#endif /* __GOA_BACKEND_ENUM_TYPES_H__ */ -/*** END file-tail ***/ diff --git a/src/goa/goabackendfacebookprovider.c b/src/goa/goabackendfacebookprovider.c deleted file mode 100644 index 92a1c22..0000000 --- a/src/goa/goabackendfacebookprovider.c +++ /dev/null @@ -1,307 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#include "config.h" -#include - -#include -#include - -#include "goabackendprovider.h" -#include "goabackendoauth2provider.h" -#include "goabackendfacebookprovider.h" - -/** - * GoaBackendFacebookProvider: - * - * The #GoaBackendFacebookProvider structure contains only private data and should - * only be accessed using the provided API. - */ -struct _GoaBackendFacebookProvider -{ - /*< private >*/ - GoaBackendOAuth2Provider parent_instance; -}; - -typedef struct _GoaBackendFacebookProviderClass GoaBackendFacebookProviderClass; - -struct _GoaBackendFacebookProviderClass -{ - GoaBackendOAuth2ProviderClass parent_class; -}; - -/** - * SECTION:goabackendfacebookprovider - * @title: GoaBackendFacebookProvider - * @short_description: A provider for Facebook - * - * #GoaBackendFacebookProvider is used for handling Facebook accounts. - */ - -G_DEFINE_TYPE_WITH_CODE (GoaBackendFacebookProvider, goa_backend_facebook_provider, GOA_TYPE_BACKEND_OAUTH2_PROVIDER, - g_io_extension_point_implement (GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME, - g_define_type_id, - "facebook", - 0)); - -/* ---------------------------------------------------------------------------------------------------- */ - -static const gchar * -get_provider_type (GoaBackendProvider *_provider) -{ - return "facebook"; -} - -static const gchar * -get_name (GoaBackendProvider *_provider) -{ - return _("Facebook Account"); -} - -static const gchar * -get_authorization_uri (GoaBackendOAuth2Provider *provider) -{ - return "https://www.facebook.com/dialog/oauth"; -} - - -static const gchar * -get_token_uri (GoaBackendOAuth2Provider *provider) -{ - return "https://graph.facebook.com/oauth/access_token"; -} - - -static const gchar * -get_redirect_uri (GoaBackendOAuth2Provider *provider) -{ - return "https://www.gnome.org/goa-1.0/oauth2?callback=1"; -} - -static const gchar * -get_scope (GoaBackendOAuth2Provider *provider) -{ - /* see https://developers.facebook.com/docs/authentication/permissions/ */ - return - "user_events," - "read_mailbox," - "offline_access"; -} - -static const gchar * -get_client_id (GoaBackendOAuth2Provider *provider) -{ - return "103995033022129"; -} - -static const gchar * -get_client_secret (GoaBackendOAuth2Provider *provider) -{ - return "c3a9f8d49188a6dd8b596df48a90b94a"; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gchar * -get_identity_sync (GoaBackendOAuth2Provider *provider, - const gchar *access_token, - gchar **out_name, - GCancellable *cancellable, - GError **error) -{ - RestProxy *proxy; - RestProxyCall *call; - JsonParser *parser; - JsonObject *json_object; - gchar *ret; - gchar *id; - gchar *name; - - ret = NULL; - proxy = NULL; - call = NULL; - parser = NULL; - id = NULL; - name = NULL; - - /* TODO: cancellable */ - - proxy = rest_proxy_new ("https://graph.facebook.com/me", FALSE); - call = rest_proxy_new_call (proxy); - rest_proxy_call_set_method (call, "GET"); - rest_proxy_call_add_param (call, "access_token", access_token); - - if (!rest_proxy_call_sync (call, error)) - goto out; - if (rest_proxy_call_get_status_code (call) != 200) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Expected status 200 when requesting guid, instead got status %d (%s)"), - rest_proxy_call_get_status_code (call), - rest_proxy_call_get_status_message (call)); - goto out; - } - - parser = json_parser_new (); - if (!json_parser_load_from_data (parser, - rest_proxy_call_get_payload (call), - rest_proxy_call_get_payload_length (call), - error)) - { - g_prefix_error (error, _("Error parsing response as JSON: ")); - goto out; - } - - json_object = json_node_get_object (json_parser_get_root (parser)); - id = g_strdup (json_object_get_string_member (json_object, "username")); - if (id == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Didn't find username member in JSON data")); - goto out; - } - name = g_strdup (json_object_get_string_member (json_object, "name")); - if (name == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Didn't find name member in JSON data")); - goto out; - } - - ret = id; - id = NULL; - if (out_name != NULL) - { - *out_name = name; - name = NULL; - } - - out: - g_free (id); - g_free (name); - if (call != NULL) - g_object_unref (call); - if (proxy != NULL) - g_object_unref (proxy); - return ret; -} - - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean -goa_backend_facebook_provider_build_object (GoaBackendProvider *provider, - GoaObjectSkeleton *object, - GKeyFile *key_file, - const gchar *group, - GError **error) -{ - GoaAccount *account; - GoaFacebookAccount *facebook_account; - gboolean ret; - gchar *user_name; - - user_name = NULL; - account = NULL; - facebook_account = NULL; - ret = FALSE; - - /* Chain up */ - if (!GOA_BACKEND_PROVIDER_CLASS (goa_backend_facebook_provider_parent_class)->build_object (provider, - object, - key_file, - group, - error)) - goto out; - - account = goa_object_get_account (GOA_OBJECT (object)); - facebook_account = goa_object_get_facebook_account (GOA_OBJECT (object)); - if (facebook_account == NULL) - { - facebook_account = goa_facebook_account_skeleton_new (); - goa_object_skeleton_set_facebook_account (object, facebook_account); - } - - user_name = g_key_file_get_string (key_file, group, "Identity", NULL); - if (user_name == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - "Invalid identity %s for id %s", - user_name, - goa_account_get_id (account)); - goto out; - } - - goa_facebook_account_set_user_name (facebook_account, user_name); - - ret = TRUE; - - out: - if (facebook_account != NULL) - g_object_unref (facebook_account); - if (account != NULL) - g_object_unref (account); - return ret; -} - -static gboolean -get_use_external_browser (GoaBackendOAuth2Provider *provider) -{ - return FALSE; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -goa_backend_facebook_provider_init (GoaBackendFacebookProvider *client) -{ -} - -static void -goa_backend_facebook_provider_class_init (GoaBackendFacebookProviderClass *klass) -{ - GoaBackendProviderClass *provider_class; - GoaBackendOAuth2ProviderClass *oauth2_class; - - provider_class = GOA_BACKEND_PROVIDER_CLASS (klass); - provider_class->get_provider_type = get_provider_type; - provider_class->get_name = get_name; - provider_class->build_object = goa_backend_facebook_provider_build_object; - - oauth2_class = GOA_BACKEND_OAUTH2_PROVIDER_CLASS (klass); - oauth2_class->get_authorization_uri = get_authorization_uri; - oauth2_class->get_token_uri = get_token_uri; - oauth2_class->get_redirect_uri = get_redirect_uri; - oauth2_class->get_scope = get_scope; - oauth2_class->get_client_id = get_client_id; - oauth2_class->get_client_secret = get_client_secret; - oauth2_class->get_identity_sync = get_identity_sync; - oauth2_class->get_use_external_browser = get_use_external_browser; -} diff --git a/src/goa/goabackendfacebookprovider.h b/src/goa/goabackendfacebookprovider.h deleted file mode 100644 index 256ed98..0000000 --- a/src/goa/goabackendfacebookprovider.h +++ /dev/null @@ -1,42 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __GOA_BACKEND_FACEBOOK_PROVIDER_H__ -#define __GOA_BACKEND_FACEBOOK_PROVIDER_H__ - -#include - -G_BEGIN_DECLS - -#define GOA_TYPE_BACKEND_FACEBOOK_PROVIDER (goa_backend_facebook_provider_get_type ()) -#define GOA_BACKEND_FACEBOOK_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_FACEBOOK_PROVIDER, GoaBackendFacebookProvider)) -#define GOA_IS_BACKEND_FACEBOOK_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_FACEBOOK_PROVIDER)) - -GType goa_backend_facebook_provider_get_type (void) G_GNUC_CONST; - -G_END_DECLS - -#endif /* __GOA_BACKEND_FACEBOOK_PROVIDER_H__ */ diff --git a/src/goa/goabackendgoogleprovider.c b/src/goa/goabackendgoogleprovider.c deleted file mode 100644 index 0bbd8fb..0000000 --- a/src/goa/goabackendgoogleprovider.c +++ /dev/null @@ -1,352 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#include "config.h" -#include - -#include -#include - -#include "goabackendprovider.h" -#include "goabackendoauthprovider.h" -#include "goabackendgoogleprovider.h" - -#include "goabackendimapmail.h" -#include "goabackendimapauthoauth.h" - -/** - * GoaBackendGoogleProvider: - * - * The #GoaBackendGoogleProvider structure contains only private data and should - * only be accessed using the provided API. - */ -struct _GoaBackendGoogleProvider -{ - /*< private >*/ - GoaBackendOAuthProvider parent_instance; -}; - -typedef struct _GoaBackendGoogleProviderClass GoaBackendGoogleProviderClass; - -struct _GoaBackendGoogleProviderClass -{ - GoaBackendOAuthProviderClass parent_class; -}; - -/** - * SECTION:goabackendgoogleprovider - * @title: GoaBackendGoogleProvider - * @short_description: A provider for Google - * - * #GoaBackendGoogleProvider is used for handling Google accounts. - */ - -G_DEFINE_TYPE_WITH_CODE (GoaBackendGoogleProvider, goa_backend_google_provider, GOA_TYPE_BACKEND_OAUTH_PROVIDER, - g_io_extension_point_implement (GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME, - g_define_type_id, - "google", - 0)); - -/* ---------------------------------------------------------------------------------------------------- */ - -static const gchar * -get_provider_type (GoaBackendProvider *_provider) -{ - return "google"; -} - -static const gchar * -get_name (GoaBackendProvider *_provider) -{ - return _("Google Account"); -} - -static const gchar * -get_consumer_key (GoaBackendOAuthProvider *provider) -{ - return "anonymous"; -} - -static const gchar * -get_consumer_secret (GoaBackendOAuthProvider *provider) -{ - return "anonymous"; -} - -static const gchar * -get_request_uri (GoaBackendOAuthProvider *provider) -{ - return "https://www.google.com/accounts/OAuthGetRequestToken"; -} - -static gchar ** -get_request_uri_params (GoaBackendOAuthProvider *provider) -{ - GPtrArray *p; - p = g_ptr_array_new (); - g_ptr_array_add (p, g_strdup ("xoauth_displayname")); - g_ptr_array_add (p, g_strdup ("GNOME")); - - g_ptr_array_add (p, g_strdup ("scope")); - g_ptr_array_add (p, g_strdup ( - /* Display email address: cf. https://sites.google.com/site/oauthgoog/Home/emaildisplayscope */ - "https://www.googleapis.com/auth/userinfo#email " - /* IMAP, SMTP access: http://code.google.com/apis/gmail/oauth/protocol.html */ - "https://mail.google.com/ " - /* Calendar data API: http://code.google.com/apis/calendar/data/2.0/developers_guide.html */ - "https://www.google.com/calendar/feeds")); - g_ptr_array_add (p, NULL); - return (gchar **) g_ptr_array_free (p, FALSE); -} - - -static const gchar * -get_authorization_uri (GoaBackendOAuthProvider *provider) -{ - return "https://www.google.com/accounts/OAuthAuthorizeToken"; -} - -static const gchar * -get_token_uri (GoaBackendOAuthProvider *provider) -{ - return "https://www.google.com/accounts/OAuthGetAccessToken"; -} - -static const gchar * -get_callback_uri (GoaBackendOAuthProvider *provider) -{ - return "https://www.gnome.org/goa-1.0/oauth"; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gchar * -get_identity_sync (GoaBackendOAuthProvider *provider, - const gchar *access_token, - const gchar *access_token_secret, - gchar **out_name, - GCancellable *cancellable, - GError **error) -{ - RestProxy *proxy; - RestProxyCall *call; - JsonParser *parser; - JsonObject *json_object; - JsonObject *json_data_object; - gchar *ret; - gchar *email; - - ret = NULL; - proxy = NULL; - call = NULL; - parser = NULL; - email = NULL; - - /* TODO: cancellable */ - - proxy = oauth_proxy_new_with_token (goa_backend_oauth_provider_get_consumer_key (provider), - goa_backend_oauth_provider_get_consumer_secret (provider), - access_token, - access_token_secret, - "https://www.googleapis.com/userinfo/email", - FALSE); - call = rest_proxy_new_call (proxy); - rest_proxy_call_set_method (call, "GET"); - rest_proxy_call_add_param (call, "alt", "json"); - - if (!rest_proxy_call_sync (call, error)) - goto out; - if (rest_proxy_call_get_status_code (call) != 200) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Expected status 200 when requesting guid, instead got status %d (%s)"), - rest_proxy_call_get_status_code (call), - rest_proxy_call_get_status_message (call)); - goto out; - } - - parser = json_parser_new (); - if (!json_parser_load_from_data (parser, - rest_proxy_call_get_payload (call), - rest_proxy_call_get_payload_length (call), - error)) - { - g_prefix_error (error, _("Error parsing response as JSON: ")); - goto out; - } - - json_object = json_node_get_object (json_parser_get_root (parser)); - json_data_object = json_object_get_object_member (json_object, "data"); - if (json_data_object == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Didn't find data member in JSON data")); - goto out; - } - - email = g_strdup (json_object_get_string_member (json_data_object, "email")); - if (email == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Didn't find email member in JSON data")); - goto out; - } - - - ret = email; - email = NULL; - if (out_name != NULL) - *out_name = g_strdup (ret); /* for now: use email as name */ - - out: - g_free (email); - if (call != NULL) - g_object_unref (call); - if (proxy != NULL) - g_object_unref (proxy); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean -goa_backend_google_provider_build_object (GoaBackendProvider *provider, - GoaObjectSkeleton *object, - GKeyFile *key_file, - const gchar *group, - GError **error) -{ - GoaAccount *account; - GoaGoogleAccount *google_account; - GoaMail *mail; - gboolean ret; - gchar *email_address; - - email_address = NULL; - account = NULL; - google_account = NULL; - mail = NULL; - ret = FALSE; - - /* Chain up */ - if (!GOA_BACKEND_PROVIDER_CLASS (goa_backend_google_provider_parent_class)->build_object (provider, - object, - key_file, - group, - error)) - goto out; - - account = goa_object_get_account (GOA_OBJECT (object)); - google_account = goa_object_get_google_account (GOA_OBJECT (object)); - if (google_account == NULL) - { - google_account = goa_google_account_skeleton_new (); - goa_object_skeleton_set_google_account (object, google_account); - } - - email_address = g_key_file_get_string (key_file, group, "Identity", NULL); - if (email_address == NULL /* || !is_valid_email_address () */) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - "Invalid identity %s for id %s", - email_address, - goa_account_get_id (account)); - goto out; - } - - goa_google_account_set_email_address (google_account, email_address); - - mail = goa_object_get_mail (GOA_OBJECT (object)); - if (mail == NULL) - { - GoaBackendImapAuth *auth; - gchar *request_uri; - request_uri = g_strdup_printf ("https://mail.google.com/mail/b/%s/imap/", email_address); - auth = goa_backend_imap_auth_oauth_new (GOA_BACKEND_OAUTH_PROVIDER (provider), - GOA_OBJECT (object), - request_uri); - mail = goa_backend_imap_mail_new ("imap.gmail.com", TRUE, auth); - goa_object_skeleton_set_mail (object, mail); - g_object_unref (auth); - g_free (request_uri); - } - - ret = TRUE; - - out: - g_free (email_address); - if (mail != NULL) - g_object_unref (mail); - if (google_account != NULL) - g_object_unref (google_account); - if (account != NULL) - g_object_unref (account); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean -get_use_external_browser (GoaBackendOAuthProvider *provider) -{ - return FALSE; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -goa_backend_google_provider_init (GoaBackendGoogleProvider *client) -{ -} - -static void -goa_backend_google_provider_class_init (GoaBackendGoogleProviderClass *klass) -{ - GoaBackendProviderClass *provider_class; - GoaBackendOAuthProviderClass *oauth_class; - - provider_class = GOA_BACKEND_PROVIDER_CLASS (klass); - provider_class->get_provider_type = get_provider_type; - provider_class->get_name = get_name; - provider_class->build_object = goa_backend_google_provider_build_object; - - oauth_class = GOA_BACKEND_OAUTH_PROVIDER_CLASS (klass); - oauth_class->get_identity_sync = get_identity_sync; - oauth_class->get_consumer_key = get_consumer_key; - oauth_class->get_consumer_secret = get_consumer_secret; - oauth_class->get_request_uri = get_request_uri; - oauth_class->get_request_uri_params = get_request_uri_params; - oauth_class->get_authorization_uri = get_authorization_uri; - oauth_class->get_token_uri = get_token_uri; - oauth_class->get_callback_uri = get_callback_uri; - oauth_class->get_use_external_browser = get_use_external_browser; -} - -/* ---------------------------------------------------------------------------------------------------- */ diff --git a/src/goa/goabackendgoogleprovider.h b/src/goa/goabackendgoogleprovider.h deleted file mode 100644 index 74d1265..0000000 --- a/src/goa/goabackendgoogleprovider.h +++ /dev/null @@ -1,42 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __GOA_BACKEND_GOOGLE_PROVIDER_H__ -#define __GOA_BACKEND_GOOGLE_PROVIDER_H__ - -#include - -G_BEGIN_DECLS - -#define GOA_TYPE_BACKEND_GOOGLE_PROVIDER (goa_backend_google_provider_get_type ()) -#define GOA_BACKEND_GOOGLE_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_GOOGLE_PROVIDER, GoaBackendGoogleProvider)) -#define GOA_IS_BACKEND_GOOGLE_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_GOOGLE_PROVIDER)) - -GType goa_backend_google_provider_get_type (void) G_GNUC_CONST; - -G_END_DECLS - -#endif /* __GOA_BACKEND_GOOGLE_PROVIDER_H__ */ diff --git a/src/goa/goabackendimapauth.c b/src/goa/goabackendimapauth.c deleted file mode 100644 index 2f8f02b..0000000 --- a/src/goa/goabackendimapauth.c +++ /dev/null @@ -1,84 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#include "config.h" -#include -#include - -#include "goabackendimapauth.h" - -/** - * SECTION:goabackendimapauth - * @title: GoaBackendImapAuth - * @short_description: Helper type for authenticating IMAP connections - * - * #GoaBackendImapAuth is an abstract type used for authenticating - * IMAP connections. See #GoaBackendImapAuthOAuth for a concrete - * implementation. - */ - -G_DEFINE_ABSTRACT_TYPE (GoaBackendImapAuth, goa_backend_imap_auth, G_TYPE_OBJECT); - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -goa_backend_imap_auth_init (GoaBackendImapAuth *client) -{ -} - -static void -goa_backend_imap_auth_class_init (GoaBackendImapAuthClass *klass) -{ -} - -/* ---------------------------------------------------------------------------------------------------- */ - -/** - * goa_backend_imap_auth_run_sync: - * @auth: A #GoaBackendImapAuth. - * @input: A valid #GDataInputStream. - * @output: A valid #GDataOutputStream. - * @cancellable: (allow-none): A #GCancellable or %NULL. - * @error: Return location for error or %NULL. - * - * Authenticates the IMAP connection represented by @input and - * @output. This method blocks the calling thread until authentication - * is done. - * - * Returns: %TRUE if authentication succeeded, %FALSE if @error is - * set. - */ -gboolean -goa_backend_imap_auth_run_sync (GoaBackendImapAuth *auth, - GDataInputStream *input, - GDataOutputStream *output, - GCancellable *cancellable, - GError **error) -{ - g_return_val_if_fail (GOA_IS_BACKEND_IMAP_AUTH (auth), FALSE); - g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (input), FALSE); - g_return_val_if_fail (G_IS_DATA_OUTPUT_STREAM (output), FALSE); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); - return GOA_BACKEND_IMAP_AUTH_GET_CLASS (auth)->run_sync (auth, input, output, cancellable, error); -} - -/* ---------------------------------------------------------------------------------------------------- */ diff --git a/src/goa/goabackendimapauth.h b/src/goa/goabackendimapauth.h deleted file mode 100644 index 37ac521..0000000 --- a/src/goa/goabackendimapauth.h +++ /dev/null @@ -1,88 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __GOA_BACKEND_IMAP_AUTH_H__ -#define __GOA_BACKEND_IMAP_AUTH_H__ - -#include - -G_BEGIN_DECLS - -#define GOA_TYPE_BACKEND_IMAP_AUTH (goa_backend_imap_auth_get_type ()) -#define GOA_BACKEND_IMAP_AUTH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_IMAP_AUTH, GoaBackendImapAuth)) -#define GOA_BACKEND_IMAP_AUTH_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOA_TYPE_BACKEND_IMAP_AUTH, GoaBackendImapAuthClass)) -#define GOA_BACKEND_IMAP_AUTH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOA_TYPE_BACKEND_IMAP_AUTH, GoaBackendImapAuthClass)) -#define GOA_IS_BACKEND_IMAP_AUTH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_IMAP_AUTH)) -#define GOA_IS_BACKEND_IMAP_AUTH_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOA_TYPE_BACKEND_IMAP_AUTH)) - -struct _GoaBackendImapAuthClass; -struct _GoaBackendImapAuthPrivate; -typedef struct _GoaBackendImapAuthClass GoaBackendImapAuthClass; -typedef struct _GoaBackendImapAuthPrivate GoaBackendImapAuthPrivate; - -/** - * GoaBackendImapAuth: - * - * The #GoaBackendImapAuth structure contains only private data and - * should only be accessed using the provided API. - */ -struct _GoaBackendImapAuth -{ - /*< private >*/ - GObject parent_instance; - GoaBackendImapAuthPrivate *priv; -}; - -/** - * GoaBackendImapAuthClass: - * @parent_class: The parent class - * @run_sync: Virtual function for the goa_backend_imap_auth_run_sync() method. - * - * Class structure for #GoaBackendImapAuth. - */ -struct _GoaBackendImapAuthClass -{ - GObjectClass parent_class; - gboolean (*run_sync) (GoaBackendImapAuth *auth, - GDataInputStream *input, - GDataOutputStream *output, - GCancellable *cancellable, - GError **error); - /*< private >*/ - /* Padding for future expansion */ - gpointer goa_reserved[8]; -}; - -GType goa_backend_imap_auth_get_type (void) G_GNUC_CONST; -gboolean goa_backend_imap_auth_run_sync (GoaBackendImapAuth *auth, - GDataInputStream *input, - GDataOutputStream *output, - GCancellable *cancellable, - GError **error); - -G_END_DECLS - -#endif /* __GOA_BACKEND_IMAP_AUTH_H__ */ diff --git a/src/goa/goabackendimapauthoauth.c b/src/goa/goabackendimapauthoauth.c deleted file mode 100644 index 4c5bed7..0000000 --- a/src/goa/goabackendimapauthoauth.c +++ /dev/null @@ -1,568 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#include "config.h" -#include -#include - -#include "goabackendimapauth.h" -#include "goabackendimapauthoauth.h" -#include "goabackendoauthprovider.h" - -/** - * SECTION:goabackendimapauthoauth - * @title: GoaBackendImapAuthOAuth - * @short_description: XOAUTH authentication method for IMAP - * - * #GoaBackendImapAuthOAuth implements the XOAUTH - * authentication method for IMAP. - */ - -/** - * GoaBackendImapAuthOAuth: - * - * The #GoaBackendImapAuthOAuth structure contains only private data - * and should only be accessed using the provided API. - */ -struct _GoaBackendImapAuthOAuth -{ - GoaBackendImapAuth parent_instance; - - GoaBackendOAuthProvider *provider; - GoaObject *object; - gchar *request_uri; -}; - -typedef struct -{ - GoaBackendImapAuthClass parent_class; - -} GoaBackendImapAuthOAuthClass; - -enum -{ - PROP_0, - PROP_PROVIDER, - PROP_OBJECT, - PROP_REQUEST_URI -}; - -static gboolean goa_backend_imap_auth_oauth_run_sync (GoaBackendImapAuth *_auth, - GDataInputStream *input, - GDataOutputStream *output, - GCancellable *cancellable, - GError **error); - -G_DEFINE_TYPE (GoaBackendImapAuthOAuth, goa_backend_imap_auth_oauth, GOA_TYPE_BACKEND_IMAP_AUTH); - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -goa_backend_imap_auth_oauth_finalize (GObject *object) -{ - GoaBackendImapAuthOAuth *auth = GOA_BACKEND_IMAP_AUTH_OAUTH (object); - - g_object_unref (auth->provider); - g_object_unref (auth->object); - - G_OBJECT_CLASS (goa_backend_imap_auth_oauth_parent_class)->finalize (object); -} - -static void -goa_backend_imap_auth_oauth_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - GoaBackendImapAuthOAuth *auth = GOA_BACKEND_IMAP_AUTH_OAUTH (object); - - switch (prop_id) - { - case PROP_PROVIDER: - g_value_set_object (value, auth->provider); - break; - - case PROP_OBJECT: - g_value_set_object (value, auth->object); - break; - - case PROP_REQUEST_URI: - g_value_set_string (value, auth->request_uri); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -goa_backend_imap_auth_oauth_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - GoaBackendImapAuthOAuth *auth = GOA_BACKEND_IMAP_AUTH_OAUTH (object); - - switch (prop_id) - { - case PROP_PROVIDER: - auth->provider = g_value_dup_object (value); - break; - - case PROP_OBJECT: - auth->object = g_value_dup_object (value); - break; - - case PROP_REQUEST_URI: - auth->request_uri = g_value_dup_string (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -/* ---------------------------------------------------------------------------------------------------- */ - - -static void -goa_backend_imap_auth_oauth_init (GoaBackendImapAuthOAuth *client) -{ -} - -static void -goa_backend_imap_auth_oauth_class_init (GoaBackendImapAuthOAuthClass *klass) -{ - GObjectClass *gobject_class; - GoaBackendImapAuthClass *auth_class; - - gobject_class = G_OBJECT_CLASS (klass); - gobject_class->finalize = goa_backend_imap_auth_oauth_finalize; - gobject_class->get_property = goa_backend_imap_auth_oauth_get_property; - gobject_class->set_property = goa_backend_imap_auth_oauth_set_property; - - auth_class = GOA_BACKEND_IMAP_AUTH_CLASS (klass); - auth_class->run_sync = goa_backend_imap_auth_oauth_run_sync; - - /** - * GoaBackendImapAuthOAuth:provider: - * - * The #GoaBackendOAuthProvider object to use when calculating the XOAUTH mechanism parameter. - */ - g_object_class_install_property (gobject_class, - PROP_PROVIDER, - g_param_spec_object ("provider", - "provider", - "provider", - GOA_TYPE_BACKEND_OAUTH_PROVIDER, - G_PARAM_READABLE | - G_PARAM_WRITABLE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS)); - - /** - * GoaBackendImapAuthOAuth:object: - * - * The #GoaObject object to use when calculating the XOAUTH mechanism parameter. - */ - g_object_class_install_property (gobject_class, - PROP_OBJECT, - g_param_spec_object ("object", - "object", - "object", - GOA_TYPE_OBJECT, - G_PARAM_READABLE | - G_PARAM_WRITABLE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS)); - - /** - * GoaBackendImapAuthOAuth:request-uri: - * - * The request URI to use when calculating the XOAUTH mechanism parameter. - */ - g_object_class_install_property (gobject_class, - PROP_REQUEST_URI, - g_param_spec_string ("request-uri", - "request-uri", - "request-uri", - NULL, - G_PARAM_READABLE | - G_PARAM_WRITABLE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS)); -} - -/* ---------------------------------------------------------------------------------------------------- */ - -/** - * goa_backend_imap_auth_oauth_new: - * @provider: A #GoaBackendOAuthProvider. - * @object: An account object. - * @request_uri: The request URI to use. - * - * Creates a new #GoaBackendImapAuth to be used for XOAUTH authentication. - * - * Returns: (type GoaBackendImapAuthOAuth): A #GoaBackendImapAuthOAuth. Free with g_object_unref(). - */ -GoaBackendImapAuth * -goa_backend_imap_auth_oauth_new (GoaBackendOAuthProvider *provider, - GoaObject *object, - const gchar *request_uri) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL); - g_return_val_if_fail (GOA_IS_OBJECT (object), NULL); - return GOA_BACKEND_IMAP_AUTH (g_object_new (GOA_TYPE_BACKEND_IMAP_AUTH_OAUTH, - "provider", provider, - "object", object, - "request-uri", request_uri, - NULL)); -} - -/* ---------------------------------------------------------------------------------------------------- */ - - -#include - -#define OAUTH_ENCODE_STRING(x_) (x_ ? soup_uri_encode( (x_), "!$&'()*+,;=@") : g_strdup ("")) - -#define SHA1_BLOCK_SIZE 64 -#define SHA1_LENGTH 20 - -/* - * hmac_sha1: - * @key: The key - * @message: The message - * - * Given the key and message, compute the HMAC-SHA1 hash and return the base-64 - * encoding of it. This is very geared towards OAuth, and as such both key and - * message must be NULL-terminated strings, and the result is base-64 encoded. - */ -static char * -hmac_sha1 (const char *key, const char *message) -{ - GChecksum *checksum; - char *real_key; - guchar ipad[SHA1_BLOCK_SIZE]; - guchar opad[SHA1_BLOCK_SIZE]; - guchar inner[SHA1_LENGTH]; - guchar digest[SHA1_LENGTH]; - gsize key_length, inner_length, digest_length; - int i; - - g_return_val_if_fail (key, NULL); - g_return_val_if_fail (message, NULL); - - checksum = g_checksum_new (G_CHECKSUM_SHA1); - - /* If the key is longer than the block size, hash it first */ - if (strlen (key) > SHA1_BLOCK_SIZE) { - guchar new_key[SHA1_LENGTH]; - - key_length = sizeof (new_key); - - g_checksum_update (checksum, (guchar*)key, strlen (key)); - g_checksum_get_digest (checksum, new_key, &key_length); - g_checksum_reset (checksum); - - real_key = g_memdup (new_key, key_length); - } else { - real_key = g_strdup (key); - key_length = strlen (key); - } - - /* Sanity check the length */ - g_assert (key_length <= SHA1_BLOCK_SIZE); - - /* Protect against use of the provided key by NULLing it */ - key = NULL; - - /* Stage 1 */ - memset (ipad, 0, sizeof (ipad)); - memset (opad, 0, sizeof (opad)); - - memcpy (ipad, real_key, key_length); - memcpy (opad, real_key, key_length); - - /* Stage 2 and 5 */ - for (i = 0; i < sizeof (ipad); i++) { - ipad[i] ^= 0x36; - opad[i] ^= 0x5C; - } - - /* Stage 3 and 4 */ - g_checksum_update (checksum, ipad, sizeof (ipad)); - g_checksum_update (checksum, (guchar*)message, strlen (message)); - inner_length = sizeof (inner); - g_checksum_get_digest (checksum, inner, &inner_length); - g_checksum_reset (checksum); - - /* Stage 6 and 7 */ - g_checksum_update (checksum, opad, sizeof (opad)); - g_checksum_update (checksum, inner, inner_length); - - digest_length = sizeof (digest); - g_checksum_get_digest (checksum, digest, &digest_length); - - g_checksum_free (checksum); - g_free (real_key); - - return g_base64_encode (digest, digest_length); -} - -static char * -sign_plaintext (const gchar *consumer_secret, - const gchar *token_secret) -{ - char *cs; - char *ts; - char *rv; - - cs = OAUTH_ENCODE_STRING (consumer_secret); - ts = OAUTH_ENCODE_STRING (token_secret); - rv = g_strconcat (cs, "&", ts, NULL); - - g_free (cs); - g_free (ts); - - return rv; -} - -static char * -sign_hmac (const gchar *consumer_secret, - const gchar *token_secret, - const gchar *http_method, - const gchar *request_uri, - const gchar *encoded_params) -{ - GString *text; - - text = g_string_new (NULL); - g_string_append (text, http_method); - g_string_append_c (text, '&'); - g_string_append_uri_escaped (text, request_uri, NULL, FALSE); - g_string_append_c (text, '&'); - g_string_append_uri_escaped (text, encoded_params, NULL, FALSE); - - /* PLAINTEXT signature value is the HMAC-SHA1 key value */ - gchar *key; - key = sign_plaintext (consumer_secret, token_secret); - - gchar *signature; - signature = hmac_sha1 (key, text->str); - - g_free (key); - g_string_free (text, TRUE); - - return signature; -} - -static GHashTable * -calculate_xoauth_params (const gchar *request_uri, - const gchar *consumer_key, - const gchar *consumer_secret, - const gchar *access_token, - const gchar *access_token_secret) -{ - gchar *signature; - GHashTable *params; - gchar *nonce; - gchar *timestamp; - GList *keys; - GList *l; - GString *normalized; - - nonce = g_strdup_printf ("%u", g_random_int ()); - timestamp = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) time (NULL)); - - params = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); - g_hash_table_insert (params, "oauth_consumer_key", g_strdup (consumer_key)); - g_hash_table_insert (params, "oauth_nonce", nonce); /* takes ownership */ - g_hash_table_insert (params, "oauth_timestamp", timestamp); /* takes ownership */ - g_hash_table_insert (params, "oauth_version", g_strdup ("1.0")); - g_hash_table_insert (params, "oauth_signature_method", g_strdup ("HMAC-SHA1")); - g_hash_table_insert (params, "oauth_token", g_strdup (access_token)); - - normalized = g_string_new (NULL); - keys = g_hash_table_get_keys (params); - keys = g_list_sort (keys, (GCompareFunc) g_strcmp0); /* TODO: locale specific? */ - for (l = keys; l != NULL; l = l->next) - { - const gchar *key = l->data; - const gchar *value; - gchar *k; - gchar *v; - - value = g_hash_table_lookup (params, key); - if (normalized->len > 0) - g_string_append_c (normalized, '&'); - - k = OAUTH_ENCODE_STRING (key); - v = OAUTH_ENCODE_STRING (value); - - g_string_append_printf (normalized, "%s=%s", k, v); - - g_free (k); - g_free (v); - } - g_list_free (keys); - - signature = sign_hmac (consumer_secret, - access_token_secret, - "GET", - request_uri, - normalized->str); - g_hash_table_insert (params, "oauth_signature", signature); /* takes ownership */ - - g_string_free (normalized, TRUE); - return params; -} - -static gchar * -calculate_xoauth_param (const gchar *request_uri, - const gchar *consumer_key, - const gchar *consumer_secret, - const gchar *access_token, - const gchar *access_token_secret, - GError **error) -{ - gchar *ret; - GString *str; - GHashTable *params; - GList *keys; - GList *l; - - params = calculate_xoauth_params (request_uri, - consumer_key, - consumer_secret, - access_token, - access_token_secret); - str = g_string_new ("GET "); - g_string_append (str, request_uri); - g_string_append_c (str, ' '); - keys = g_hash_table_get_keys (params); - keys = g_list_sort (keys, (GCompareFunc) g_strcmp0); /* TODO: locale specific? */ - for (l = keys; l != NULL; l = l->next) - { - const gchar *key = l->data; - const gchar *value; - gchar *k; - gchar *v; - - value = g_hash_table_lookup (params, key); - if (l != keys) - g_string_append_c (str, ','); - - k = OAUTH_ENCODE_STRING (key); - v = OAUTH_ENCODE_STRING (value); - g_string_append_printf (str, "%s=\"%s\"", k, v); - g_free (k); - g_free (v); - } - g_list_free (keys); - - ret = g_base64_encode ((const guchar *) str->str, str->len); - g_string_free (str, TRUE); - g_hash_table_unref (params); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean -goa_backend_imap_auth_oauth_run_sync (GoaBackendImapAuth *_auth, - GDataInputStream *input, - GDataOutputStream *output, - GCancellable *cancellable, - GError **error) -{ - GoaBackendImapAuthOAuth *auth = GOA_BACKEND_IMAP_AUTH_OAUTH (_auth); - gchar *access_token; - gchar *access_token_secret; - gchar *xoauth_param; - gchar *request; - gchar *response; - gboolean ret; - - access_token = NULL; - access_token_secret = NULL; - xoauth_param = NULL; - request = NULL; - response = NULL; - ret = FALSE; - - access_token = goa_backend_oauth_provider_get_access_token_sync (auth->provider, - auth->object, - FALSE, /* force_refresh */ - &access_token_secret, - NULL, /* out_access_token_expires_in */ - NULL, /* GCancellable */ - error); /* GError */ - if (access_token == NULL) - goto out; - - xoauth_param = calculate_xoauth_param (auth->request_uri, - goa_backend_oauth_provider_get_consumer_key (auth->provider), - goa_backend_oauth_provider_get_consumer_secret (auth->provider), - access_token, - access_token_secret, - error); - if (xoauth_param == NULL) - goto out; - - request = g_strdup_printf ("A001 AUTHENTICATE XOAUTH %s\r\n", xoauth_param); - if (!g_data_output_stream_put_string (output, request, cancellable, error)) - goto out; - - again: - response = g_data_input_stream_read_line (input, NULL, cancellable, error); - if (response == NULL) - goto out; - /* ignore untagged responses */ - if (g_str_has_prefix (response, "*")) - { - g_free (response); - goto again; - } - if (!g_str_has_prefix (response, "A001 OK")) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - "Unexpected response `%s' while doing XOAUTH authentication", - response); - goto out; - } - - ret = TRUE; - - out: - g_free (response); - g_free (request); - g_free (xoauth_param); - g_free (access_token); - g_free (access_token_secret); - return ret; -} diff --git a/src/goa/goabackendimapauthoauth.h b/src/goa/goabackendimapauthoauth.h deleted file mode 100644 index 3899117..0000000 --- a/src/goa/goabackendimapauthoauth.h +++ /dev/null @@ -1,46 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __GOA_BACKEND_IMAP_AUTH_OAUTH_H__ -#define __GOA_BACKEND_IMAP_AUTH_OAUTH_H__ - -#include - -G_BEGIN_DECLS - -#define GOA_TYPE_BACKEND_IMAP_AUTH_OAUTH (goa_backend_imap_auth_oauth_get_type ()) -#define GOA_BACKEND_IMAP_AUTH_OAUTH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_IMAP_AUTH_OAUTH, GoaBackendImapAuthOAuth)) -#define GOA_IS_BACKEND_IMAP_AUTH_OAUTH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_IMAP_AUTH_OAUTH)) - - -GType goa_backend_imap_auth_oauth_get_type (void) G_GNUC_CONST; -GoaBackendImapAuth *goa_backend_imap_auth_oauth_new (GoaBackendOAuthProvider *provider, - GoaObject *object, - const gchar *request_uri); - -G_END_DECLS - -#endif /* __GOA_BACKEND_IMAP_AUTH_OAUTH_H__ */ diff --git a/src/goa/goabackendimapclient.c b/src/goa/goabackendimapclient.c deleted file mode 100644 index 0c30e4c..0000000 --- a/src/goa/goabackendimapclient.c +++ /dev/null @@ -1,694 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#include "config.h" -#include -#include - -#include "goabackendimapauth.h" -#include "goabackendimapclient.h" - -/* The timeout used for non-IDLE commands */ -#define COMMAND_TIMEOUT_SEC 30 - -/** - * GoaBackendImapClient: - * - * The #GoaBackendImapClient structure contains only private data and should - * only be accessed using the provided API. - */ -struct _GoaBackendImapClient -{ - /*< private >*/ - GObject parent_instance; - - /* The remaining data members are related to the running session - */ - GSocketClient *sc; - GSocketConnection *c; - GDataInputStream *dis; - GDataOutputStream *dos; - - /* counter used used for generating command tags */ - guint tag; - - GMutex *lock; -}; - -typedef struct _GoaBackendImapClientClass GoaBackendImapClientClass; - -struct _GoaBackendImapClientClass -{ - GObjectClass parent_class; - void (*untagged_response) (GoaBackendImapClient *client, - const gchar *response); -}; - -/** - * SECTION:goabackendimapclient - * @title: GoaBackendImapClient - * @short_description: A simple IMAP client - * - * #GoaBackendImapClient provides a way to talk to - * IMAP - * servers. - */ - -enum -{ - UNTAGGED_RESPONSE_SIGNAL, - LAST_SIGNAL -}; - -static guint signals[LAST_SIGNAL] = {0}; - -G_DEFINE_TYPE (GoaBackendImapClient, goa_backend_imap_client, G_TYPE_OBJECT); - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -goa_backend_imap_client_finalize (GObject *object) -{ - GoaBackendImapClient *client = GOA_BACKEND_IMAP_CLIENT (object); - - g_clear_object (&client->sc); - g_clear_object (&client->c); - g_clear_object (&client->dis); - g_clear_object (&client->dos); - g_mutex_free (client->lock); - - G_OBJECT_CLASS (goa_backend_imap_client_parent_class)->finalize (object); -} - -static void -goa_backend_imap_client_init (GoaBackendImapClient *client) -{ - client->lock = g_mutex_new (); -} - -static void -goa_backend_imap_client_class_init (GoaBackendImapClientClass *klass) -{ - GObjectClass *gobject_class; - - gobject_class = G_OBJECT_CLASS (klass); - gobject_class->finalize = goa_backend_imap_client_finalize; - - /** - * GoaBackendImapClient::untagged-response: - * @client: The #GoaBackendImapClient emitting the signal. - * @untagged_response: The untagged response. - * - * Signal emitted every an untagged - * response has been received. - * - * This signal is emitted in the same thread that calls the - * goa_backend_imap_client_run_command_sync() method. - */ - signals[UNTAGGED_RESPONSE_SIGNAL] = - g_signal_new ("untagged-response", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GoaBackendImapClientClass, untagged_response), - NULL, - NULL, - g_cclosure_marshal_VOID__STRING, - G_TYPE_NONE, - 1, - G_TYPE_STRING); -} - -/* ---------------------------------------------------------------------------------------------------- */ - -/** - * goa_backend_imap_client_new: - * - * Creates a new #GoaBackendImapClient instance. - * - * Typical usage includes connecting to the - * #GoaBackendImapClient::untagged-response signals and then invoking - * goa_backend_imap_client_connect_sync(). - * - * You can then use goa_backend_imap_client_connect_sync(), - * goa_backend_imap_client_idle_sync() and the - * #GoaBackendImapClient::untagged-response handler to interact with - * the IMAP server. If the connection fails then the appropriate error - * e.g. #G_IO_ERROR_TIMED_OUT or %G_IO_ERROR_NETWORK_UNREACHABLE is - * returned. - * - * You can only make a single successful connection with each - * #GoaBackendImapClient instance - just create a new instance if the - * connection breaks and you need to reconnect. - * - * Returns: (transfer full): A #GoaBackendImapClient that should be freed with g_object_unref(). - */ -GoaBackendImapClient * -goa_backend_imap_client_new (void) -{ - return GOA_BACKEND_IMAP_CLIENT (g_object_new (GOA_TYPE_BACKEND_IMAP_CLIENT, NULL)); -} - - -/** - * goa_backend_imap_client_connect_sync: - * @client: A #GoaBackendImapClient. - * @host_and_port: The name and optionally port to connect to. - * @use_tls: Whether TLS should be used. - * @auth: Object used for authenticating the connection. - * @cancellable: (allow-none): A #GCancellable or %NULL. - * @error: Return location for error or %NULL. - * - * Connects to the IMAP server represented by @host_and_port using - * @auth to authenticate the connection. The calling thread is blocked - * while the operation is pending. - * - * Returns: %TRUE if the connection was established and authentication - * worked, %FALSE if @error is set. - */ -gboolean -goa_backend_imap_client_connect_sync (GoaBackendImapClient *client, - const gchar *host_and_port, - gboolean use_tls, - GoaBackendImapAuth *auth, - GCancellable *cancellable, - GError **error) -{ - gboolean ret; - - g_return_val_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client), FALSE); - g_return_val_if_fail (host_and_port != NULL, FALSE); - g_return_val_if_fail (GOA_IS_BACKEND_IMAP_AUTH (auth), FALSE); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - ret = FALSE; - - g_mutex_lock (client->lock); - - if (client->sc != NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - "Already connected"); - goto out; - } - - client->sc = g_socket_client_new (); - if (use_tls) - g_socket_client_set_tls (client->sc, TRUE); - - /* TODO: TLS validation etc etc */ - - client->c = g_socket_client_connect_to_host (client->sc, - host_and_port, - use_tls ? 993 : 143, - cancellable, - error); - if (client->c == NULL) - goto out; - - /* fail quickly */ - g_socket_set_timeout (g_socket_connection_get_socket (client->c), COMMAND_TIMEOUT_SEC); - - client->dis = g_data_input_stream_new (g_io_stream_get_input_stream (G_IO_STREAM (client->c))); - client->dos = g_data_output_stream_new (g_io_stream_get_output_stream (G_IO_STREAM (client->c))); - g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (client->dis), FALSE); - g_filter_output_stream_set_close_base_stream (G_FILTER_OUTPUT_STREAM (client->dos), FALSE); - g_data_input_stream_set_newline_type (client->dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF); - - /* Authenticate via the passed in auth helper */ - if (!goa_backend_imap_auth_run_sync (auth, - client->dis, - client->dos, - cancellable, - error)) - goto out; - - - ret = TRUE; - - out: - if (!ret) - { - g_clear_object (&client->sc); - g_clear_object (&client->c); - g_clear_object (&client->dis); - g_clear_object (&client->dos); - } - g_mutex_unlock (client->lock); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -/** - * goa_backend_imap_client_run_command_sync: - * @client: A #GoaBackendImapClient. - * @command: The command to run. - * @cancellable: A #GCancellable or %NULL. - * @error: Return location for error. - * - * Submits @command to the remote IMAP server and blocks the calling - * thread until a response is received. Do not include a command tag - * in @command - this will automatically be appended. - * - * If the received response starts with BAD (a - * protocol-level - * error), then @error will be set to %GOA_ERROR_FAILED and - * %NULL is returned. Otherwise the full response string (excluding - * the command tag) is returned. - * - * If @command is IDLE (see - * RFC 2177) - * and @cancellable is cancelled, the continuation string - * DONE is written out automatically. - * While this method can be used for to submit - * the IDLE IMAP command, the - * goa_backend_imap_client_idle_sync() method should be used instead. - * - * The timeout on the underlying socket will be set to 30 seconds - * except for the the IDLE command which never - * times out. - * - * Note that #GoaBackendImapClient::untagged-response signals are - * emitted in the same thread that - * you call this method from - not the - * thread-default main loop - * of the thread that @client was constructed in, as one - * would except. - * - * Returns: The response or %NULL if error is set. - */ -gchar * -goa_backend_imap_client_run_command_sync (GoaBackendImapClient *client, - const gchar *command, - GCancellable *cancellable, - GError **error) -{ - gchar *s; - gchar *tag; - gchar *ret; - GString *response; - gsize len; - gboolean is_idle_command; - gboolean idle_has_sent_done; - GError *local_error; - - g_return_val_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client), NULL); - g_return_val_if_fail (command != NULL, NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); - g_return_val_if_fail (error == NULL || *error == NULL, NULL); - - s = NULL; - tag = NULL; - response = NULL; - ret = NULL; - is_idle_command = FALSE; - idle_has_sent_done = FALSE; - - g_mutex_lock (client->lock); - - if (client->sc == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - "Not yet connected"); - goto out; - } - - if (g_strcmp0 (command, "IDLE") == 0) - is_idle_command = TRUE; - - /* select a timeout */ - if (is_idle_command) - { - /* never time out */ - g_socket_set_timeout (g_socket_connection_get_socket (client->c), 0); - } - else - { - /* fail quickly */ - g_socket_set_timeout (g_socket_connection_get_socket (client->c), COMMAND_TIMEOUT_SEC); - } - - tag = g_strdup_printf ("T%05d ", client->tag++); - s = g_strconcat (tag, command, "\r\n", NULL); - if (!g_data_output_stream_put_string (client->dos, s, cancellable, error)) - { - g_prefix_error (error, "Error putting string: "); - g_free (s); - goto out; - } - g_free (s); - - response = g_string_new (NULL); - again: - local_error = NULL; - s = g_data_input_stream_read_line (client->dis, NULL, cancellable, &local_error); - if (s == NULL) - { - if (local_error != NULL) - { - g_prefix_error (&local_error, "Error reading line: "); - /* if doing an IDLE that was cancelled, write the continuation string - * anyway, ignoring the cancellable - */ - if ((local_error->domain == G_IO_ERROR && local_error->code == G_IO_ERROR_CANCELLED) && is_idle_command) - { - if (!g_data_output_stream_put_string (client->dos, "DONE\r\n", NULL, error)) - { - /* if this fails, ignore the cancelled error */ - g_error_free (local_error); - g_prefix_error (error, "Error putting IDLE continuation string: "); - goto out; - } - /* TODO: this way we're ignoring the response to the IDLE command we just - * fired off.. it's not a problem per se, but it's annoying to see in - * debug output... we could sit around and wait for the response but there's - * really no point in doing so - */ - } - g_propagate_error (error, local_error); - } - else - { - g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, "No content to read"); - } - goto out; - } - len = strlen (s); - /* So far so good */ - g_string_append_len (response, s, len); - - /* Could be it's a literal string */ - if (len >= 3 && s[len-1] == '}') - { - gint n; - n = len - 2; - while (g_ascii_isdigit (s[n]) && n >= 0) - n--; - if (s[n] == '{') - { - gsize num_read; - gsize lit_len; - gchar *lit; - lit_len = atoi (s + n + 1); - /* Don't blindly allocate any big number of bytes */ - if (lit_len > 10*1024*1024) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - "Refusing to read an additional %" G_GSIZE_FORMAT " bytes for literal string", - lit_len); - g_free (s); - goto out; - } - lit = g_malloc0 (lit_len + 1); - if (!g_input_stream_read_all (G_INPUT_STREAM (client->dis), - lit, - lit_len, - &num_read, - cancellable, - error)) - { - g_free (lit); - g_prefix_error (error, - "Requested %" G_GSIZE_FORMAT " bytes for literal string " - "but only read %" G_GSSIZE_FORMAT ": ", - lit_len, num_read); - g_free (s); - goto out; - } - /* include the original CRLF, then the literal string */ - g_string_append (response, "\r\n"); - g_string_append_len (response, lit, lit_len); - g_free (lit); - g_free (s); - /* then keep reading */ - goto again; - } - } - - if (g_str_has_prefix (response->str, tag)) - { - gint tag_len; - tag_len = strlen (tag); - if (g_str_has_prefix (response->str + tag_len, "BAD")) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - "BAD response to `%s': %s", - command, - response->str + tag_len + 4); - goto out; - } - ret = g_strdup (response->str + tag_len); - /* TODO: return additional response? */ - goto out; - } - else if (g_str_has_prefix (response->str, "*")) - { - /* untagged */ - g_signal_emit (client, signals[UNTAGGED_RESPONSE_SIGNAL], 0, response->str + 2); - } - else - { - /* TODO: not yet interesting to handle other unhandled responses.. - * Typically these are command continuation requests, see - * - * http://tools.ietf.org/html/rfc3501#section-7.5 - */ - /* g_debug ("unhandled response `%s'", response->str); */ - } - - /* If idling, when we receive real data, put the DONE continuation - * string so the IDLE command will terminate - */ - if (is_idle_command && !g_str_has_prefix (response->str, "+") && !idle_has_sent_done) - { - idle_has_sent_done = TRUE; - if (!g_data_output_stream_put_string (client->dos, "DONE\r\n", cancellable, error)) - { - g_prefix_error (error, "Error putting IDLE continuation string: "); - goto out; - } - } - - /* reset */ - g_string_set_size (response, 0); - goto again; - - out: - if (response != NULL) - g_string_free (response, TRUE); - g_free (tag); - g_mutex_unlock (client->lock); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -typedef struct -{ - GoaBackendImapClient *client; - GCancellable *local_cancellable; - gboolean timed_out; -} IdleData; - -static gboolean -timeout_while_idling_cb (gpointer user_data) -{ - IdleData *data = user_data; - data->timed_out = TRUE; - g_cancellable_cancel (data->local_cancellable); - return FALSE; -} - -static gboolean -cancel_in_idle_cb (gpointer user_data) -{ - GCancellable *cancellable = G_CANCELLABLE (user_data); - g_cancellable_cancel (cancellable); - return FALSE; -} - -static void -cancelled_while_idling_cb (GCancellable *cancellable, - gpointer user_data) -{ - IdleData *data = user_data; - - /* cancel in idle because right now calling g_cancellable_cancel() - * in a ::cancelled handler may deadlock, see - * - * https://bugzilla.gnome.org/show_bug.cgi?id=650252 - */ - g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, - cancel_in_idle_cb, - g_object_ref (data->local_cancellable), - g_object_unref); -} - -/** - * goa_backend_imap_client_idle_sync: - * @client: A #GoaBackendImapClient. - * @max_idle_seconds: Max number of seconds to idle for. This should be no longer than 29 minutes. - * @cancellable: (allow-none): A #GCancellable or %NULL. - * @error: Return location for error or %NULL. - * - * Method used to sit and wait until something happens to the selected - * mailbox. When a change has been detected this method returns %TRUE. - * - * Otherwise the method simply blocks for @max_idle_seconds (in which - * case %TRUE is also returned) or until @cancellable is cancelled - - * in which case %FALSE is returned and @error is set to - * %G_IO_ERROR_CANCELLED. - * - * If the IMAP server supports the IDLE command then - * it is used. Otherwise: TODO: handle servers not using IMAP IDLE. - * - * Note that #GoaBackendImapClient::untagged-response signals are - * emitted in the same thread that - * you call this method from - not the - * thread-default main loop - * of the thread that @client was constructed in, as one - * would except. - * - * Returns: %TRUE if the request completed, %FALSE if @error is set. - */ -gboolean -goa_backend_imap_client_idle_sync (GoaBackendImapClient *client, - guint max_idle_seconds, - GCancellable *cancellable, - GError **error) -{ - GError *local_error; - gboolean ret; - gchar *response; - guint timeout_id; - IdleData data; - gulong cancelled_id; - - g_return_val_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client), FALSE); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - ret = FALSE; - cancelled_id = 0; - timeout_id = 0; - - data.client = g_object_ref (client); - data.local_cancellable = g_cancellable_new (); - data.timed_out = FALSE; - if (cancellable != NULL) - { - cancelled_id = g_cancellable_connect (cancellable, - G_CALLBACK (cancelled_while_idling_cb), - &data, - NULL); - } - - /* We use the default GMainContext for the wake-up. This might not - * be ideal but the only alternative is to create our own thread. - */ - timeout_id = g_timeout_add_seconds (max_idle_seconds, - timeout_while_idling_cb, - &data); - - /* OK, sit around and wait until the mailbox changes (e.g. new mail - * arriving)... For we use the IMAP IDLE command if it's available, - * see http://tools.ietf.org/html/rfc2177 - * - * (TODO: actually handle IDLE not being available) - */ - local_error = NULL; - response = goa_backend_imap_client_run_command_sync (client, - "IDLE", - data.local_cancellable, - &local_error); - if (response == NULL) - { - if ((local_error->domain == G_IO_ERROR && local_error->code == G_IO_ERROR_CANCELLED) - && data.timed_out) - { - g_error_free (local_error); - } - else - { - g_propagate_error (error, local_error); - goto out; - } - } - else - { - g_free (response); - } - - ret = TRUE; - - out: - if (timeout_id > 0) - g_source_remove (timeout_id); - if (cancelled_id != 0) - g_cancellable_disconnect (cancellable, cancelled_id); - g_object_unref (data.local_cancellable); - g_object_unref (data.client); - return ret; -} - -/** - * goa_backend_imap_client_disconnect_sync: - * @client: A #GoaBackendImapClient. - * @cancellable: (allow-none): A #GCancellable or %NULL. - * @error: Return location for error or %NULL. - * - * Closes the connection used by @client, if any. The calling thread - * is blocked while the operation is pending. - * - * Returns: %TRUE if the connection was closed, %FALSE if @error is set. - */ -gboolean -goa_backend_imap_client_disconnect_sync (GoaBackendImapClient *client, - GCancellable *cancellable, - GError **error) -{ - gboolean ret; - - g_return_val_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client), FALSE); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - g_mutex_lock (client->lock); - if (client->c == NULL) - ret = TRUE; - else - ret = g_io_stream_close (G_IO_STREAM (client->c), cancellable, error); - g_mutex_unlock (client->lock); - return ret; -} - diff --git a/src/goa/goabackendimapclient.h b/src/goa/goabackendimapclient.h deleted file mode 100644 index 8582c41..0000000 --- a/src/goa/goabackendimapclient.h +++ /dev/null @@ -1,65 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __GOA_BACKEND_IMAP_CLIENT_H__ -#define __GOA_BACKEND_IMAP_CLIENT_H__ - -#include - -G_BEGIN_DECLS - -#define GOA_TYPE_BACKEND_IMAP_CLIENT (goa_backend_imap_client_get_type ()) -#define GOA_BACKEND_IMAP_CLIENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_IMAP_CLIENT, GoaBackendImapClient)) -#define GOA_IS_BACKEND_IMAP_CLIENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_IMAP_CLIENT)) - -GType goa_backend_imap_client_get_type (void) G_GNUC_CONST; -GoaBackendImapClient *goa_backend_imap_client_new (void); -gboolean goa_backend_imap_client_connect_sync (GoaBackendImapClient *client, - const gchar *host_and_port, - gboolean use_tls, - GoaBackendImapAuth *auth, - GCancellable *cancellable, - GError **error); -gchar *goa_backend_imap_client_run_command_sync (GoaBackendImapClient *client, - const gchar *command, - GCancellable *cancellable, - GError **error); - -gboolean goa_backend_imap_client_idle_sync (GoaBackendImapClient *client, - guint max_idle_seconds, - GCancellable *cancellable, - GError **error); - -gboolean goa_backend_imap_client_disconnect_sync (GoaBackendImapClient *client, - GCancellable *cancellable, - GError **error); - -// TODO: gint goa_backend_imap_client_get_socket_fd (GoaBackendImapClient *client); - - -G_END_DECLS - -#endif /* __GOA_BACKEND_IMAP_CLIENT_H__ */ diff --git a/src/goa/goabackendimapmail.c b/src/goa/goabackendimapmail.c deleted file mode 100644 index f52617b..0000000 --- a/src/goa/goabackendimapmail.c +++ /dev/null @@ -1,1058 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#include "config.h" -#include -#include - -#include -#include -#include - -#include "goabackendimapauth.h" -#include "goabackendimapclient.h" -#include "goabackendimapmail.h" - -/** - * GoaBackendImapMail: - * - * The #GoaBackendImapMail structure contains only private data and should - * only be accessed using the provided API. - */ -struct _GoaBackendImapMail -{ - /*< private >*/ - GoaMailSkeleton parent_instance; - - gchar *host_and_port; - gboolean use_tls; - GoaBackendImapAuth *auth; -}; - -typedef struct _GoaBackendImapMailClass GoaBackendImapMailClass; - -struct _GoaBackendImapMailClass -{ - GoaMailSkeletonClass parent_class; -}; - -enum -{ - PROP_0, - PROP_HOST_AND_PORT, - PROP_USE_TLS, - PROP_AUTH -}; - -/** - * SECTION:goabackendimapmail - * @title: GoaBackendImapMail - * @short_description: Implementation of the #GoaMail interface for IMAP servers - * - * #GoaBackendImapMail is an implementation of the #GoaMail D-Bus - * interface that uses a #GoaBackendImapClient instance to speak to a - * remote IMAP server. - */ - -static void goa_backend_imap_mail__goa_mail_iface_init (GoaMailIface *iface); - -G_DEFINE_TYPE_WITH_CODE (GoaBackendImapMail, goa_backend_imap_mail, GOA_TYPE_MAIL_SKELETON, - G_IMPLEMENT_INTERFACE (GOA_TYPE_MAIL, goa_backend_imap_mail__goa_mail_iface_init)); - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -goa_backend_imap_mail_finalize (GObject *object) -{ - GoaBackendImapMail *mail = GOA_BACKEND_IMAP_MAIL (object); - - g_free (mail->host_and_port); - g_object_unref (mail->auth); - - G_OBJECT_CLASS (goa_backend_imap_mail_parent_class)->finalize (object); -} - -static void -goa_backend_imap_mail_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - GoaBackendImapMail *mail = GOA_BACKEND_IMAP_MAIL (object); - - switch (prop_id) - { - case PROP_HOST_AND_PORT: - g_value_set_string (value, mail->host_and_port); - break; - - case PROP_USE_TLS: - g_value_set_boolean (value, mail->use_tls); - break; - - case PROP_AUTH: - g_value_set_object (value, mail->auth); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -goa_backend_imap_mail_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - GoaBackendImapMail *mail = GOA_BACKEND_IMAP_MAIL (object); - - switch (prop_id) - { - case PROP_HOST_AND_PORT: - mail->host_and_port = g_value_dup_string (value); - break; - - case PROP_USE_TLS: - mail->use_tls = g_value_get_boolean (value); - break; - - case PROP_AUTH: - mail->auth = g_value_dup_object (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -goa_backend_imap_mail_init (GoaBackendImapMail *mail) -{ - /* Ensure D-Bus method invocations run in their own thread */ - g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (mail), - G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); -} - -static void -goa_backend_imap_mail_class_init (GoaBackendImapMailClass *klass) -{ - GObjectClass *gobject_class; - - gobject_class = G_OBJECT_CLASS (klass); - gobject_class->finalize = goa_backend_imap_mail_finalize; - gobject_class->set_property = goa_backend_imap_mail_set_property; - gobject_class->get_property = goa_backend_imap_mail_get_property; - - g_object_class_install_property (gobject_class, - PROP_HOST_AND_PORT, - g_param_spec_string ("host-and-port", - "host-and-port", - "host-and-port", - NULL, - G_PARAM_READABLE | - G_PARAM_WRITABLE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (gobject_class, - PROP_USE_TLS, - g_param_spec_boolean ("use-tls", - "use-tls", - "use-tls", - TRUE, - G_PARAM_READABLE | - G_PARAM_WRITABLE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (gobject_class, - PROP_AUTH, - g_param_spec_object ("auth", - "auth", - "auth", - GOA_TYPE_BACKEND_IMAP_AUTH, - G_PARAM_READABLE | - G_PARAM_WRITABLE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS)); -} - -/* ---------------------------------------------------------------------------------------------------- */ - -/** - * goa_backend_imap_mail_new: - * @host_and_port: The name and optionally port to connect to. - * @use_tls: Whether TLS should be used. - * @auth: Object used for authenticating the connection. - * - * Creates a new #GoaMail object. - * - * Returns: (type GoaBackendImapMail): A new #GoaMail instance. - */ -GoaMail * -goa_backend_imap_mail_new (const gchar *host_and_port, - gboolean use_tls, - GoaBackendImapAuth *auth) -{ - g_return_val_if_fail (host_and_port != NULL, NULL); - return GOA_MAIL (g_object_new (GOA_TYPE_BACKEND_IMAP_MAIL, - "host-and-port", host_and_port, - "use-tls", use_tls, - "auth", auth, - NULL)); -} - -/* ---------------------------------------------------------------------------------------------------- */ - -typedef struct -{ - volatile gint ref_count; - - GoaBackendImapMail *mail; - GoaMailMonitor *monitor; - - /* Used so we can nuke the monitor if the creator vanishes */ - guint name_watcher_id; - - /* Use to communicate with the thread running the IMAP client */ - GCancellable *imap_cancellable; - gboolean imap_request_close; - GMutex *imap_counter_lock; - GCond *imap_counter_cond; - gint imap_num_refreshes; - gint imap_num_connections_failed; -} MonitorData; - -static MonitorData * -monitor_data_ref (MonitorData *data) -{ - g_atomic_int_inc (&data->ref_count); - return data; -} - -static void -monitor_data_unref (MonitorData *data) -{ - if (g_atomic_int_dec_and_test (&data->ref_count)) - { - g_clear_object (&data->mail); - g_clear_object (&data->monitor); - if (data->name_watcher_id) - g_bus_unwatch_name (data->name_watcher_id); - g_clear_object (&data->imap_cancellable); - if (data->imap_counter_lock != NULL) - g_mutex_free (data->imap_counter_lock); - if (data->imap_counter_cond != NULL) - g_cond_free (data->imap_counter_cond); - g_slice_free (MonitorData, data); - } -} - -static void -nuke_monitor (MonitorData *data) -{ - /* unexport the D-Bus object */ - g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (data->monitor)); - /* nuke the running IMAP client */ - data->imap_request_close = TRUE; - g_mutex_lock (data->imap_counter_lock); - g_cancellable_cancel (data->imap_cancellable); - g_mutex_unlock (data->imap_counter_lock); - monitor_data_unref (data); -} - -static void -on_monitor_owner_vanished (GDBusConnection *connection, - const gchar *name, - gpointer user_data) -{ - MonitorData *data = user_data; - /* yippee ki yay motherfucker */ - nuke_monitor (data); -} - -/* ---------------------------------------------------------------------------------------------------- */ - -typedef struct -{ - MonitorData *monitor_data; - gint num_exists; - gint last_num_exists; - - gint uidvalidity; -} ImapClientData; - - -static gboolean -parse_int (const gchar *s, - gint *out_result) -{ - gboolean ret; - gchar *endp; - gint result; - - g_return_val_if_fail (s != NULL, FALSE); - - ret = FALSE; - result = strtol (s, &endp, 0); - if (result == 0 && endp == s) - goto out; - - if (out_result != NULL) - *out_result = result; - - ret = TRUE; - - out: - return ret; -} - -static gboolean -fetch_check (const gchar *data, - guint *pos, - const gchar *key) -{ - gsize key_len; - gboolean ret; - - g_return_val_if_fail (data != NULL, FALSE); - g_return_val_if_fail (pos != NULL, FALSE); - g_return_val_if_fail (key != NULL, FALSE); - - ret = FALSE; - - key_len = strlen (key); - if (strncmp (data + *pos, key, key_len) == 0 && data[*pos + key_len] == ' ') - { - ret = TRUE; - *pos += key_len + 1; - goto out; - } - out: - return ret; -} - -static gchar * -fetch_string (const gchar *data, - guint *pos) -{ - gchar *ret; - guint start_pos; - - g_return_val_if_fail (data != NULL, FALSE); - g_return_val_if_fail (pos != NULL, FALSE); - - ret = NULL; - - start_pos = *pos; - - while (data[*pos] != ' ' && data[*pos] != ')' && data[*pos] != '\0') - *pos += 1; - - ret = g_strndup (data + start_pos, *pos - start_pos); - - return ret; -} - -static gboolean -fetch_int (const gchar *data, - guint *pos, - gint *out_value) -{ - gchar *str_value; - gboolean ret; - - g_return_val_if_fail (data != NULL, FALSE); - g_return_val_if_fail (pos != NULL, FALSE); - g_return_val_if_fail (out_value != NULL, FALSE); - - str_value = NULL; - ret = FALSE; - - str_value = fetch_string (data, pos); - if (str_value == NULL) - goto out; - - if (!parse_int (str_value, out_value)) - goto out; - - ret = TRUE; - - out: - g_free (str_value); - return ret; -} - -static gchar * -fetch_literal_string (const gchar *data, - guint *pos, - guint *out_len) -{ - gchar *ret; - guint start_pos; - guint len; - - g_return_val_if_fail (data != NULL, FALSE); - g_return_val_if_fail (pos != NULL, FALSE); - - ret = NULL; - - start_pos = *pos; - - if (data[*pos] != '{') - goto out; - *pos += 1; - while (g_ascii_isdigit (data[*pos])) - *pos += 1; - if (strncmp (data + *pos, "}\r\n", 3) != 0) - goto out; - *pos += 3; - - if (!parse_int (data + start_pos + 1, (gint*) &len)) - goto out; - - ret = g_strndup (data + *pos, len); - *pos += len; - - if (out_len != NULL) - *out_len = len; - - out: - return ret; -} - -/* TODO: try a little harder to make this a conformant RFC822 parser */ -static GHashTable * -parse_rfc822_headers (const gchar *rfc822_headers) -{ - GHashTable *ret; - gchar **lines; - guint n; - - ret = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); - lines = g_strsplit (rfc822_headers, "\r\n", -1); - for (n = 0; lines[n] != NULL; n++) - { - const gchar *line = lines[n]; - const gchar *s; - - if (line[0] == '\0') - continue; - - s = strstr (line, ": "); - if (s != NULL) - { - gchar *key; - gchar *value; - key = g_strndup (line, s - line); - value = g_strdup (s + 2); - g_hash_table_insert (ret, key, value); - } - else - { - g_warning ("%s: ignoring mysterious line `%s' whilst parsing `%s'", - G_STRFUNC, line, rfc822_headers); - } - } - g_strfreev (lines); - - return ret; -} - -/* Simple FETCH response parser only handling a subset of FETCH - * responses, see - * - * http://tools.ietf.org/html/rfc3501#section-7.4.2 - * - * for more details. - */ -static void -imap_client_handle_fetch_response (ImapClientData *data, - guint message_seqnum, - const gchar *params) -{ - guint n; - gboolean parsed; - gboolean has_uid; - gint uid; - gchar *rfc822_headers; - guint rfc822_headers_len; - gchar *excerpt; - guint excerpt_len; - GHashTable *headers; - const gchar *from_header; - const gchar *subject_header; - gchar *uid_str; - gchar *uri; - /* GVariantBuilder extras_builder; */ - - g_return_if_fail (message_seqnum >= 1); - g_return_if_fail (params != NULL); - - uid = 0; - has_uid = FALSE; - excerpt = NULL; - rfc822_headers = NULL; - headers = NULL; - uid_str = NULL; - uri = NULL; - parsed = FALSE; - - if (params[0] != '(') - goto out; - n = 1; - while (params[n] != ')' && params[n] != '\0') - { - if (fetch_check (params, &n, "UID")) - { - if (!fetch_int (params, &n, &uid)) - goto out; - has_uid = TRUE; - } - else if (fetch_check (params, &n, "BODY[HEADER.FIELDS (Date From To Cc Subject)]")) - { - rfc822_headers = fetch_literal_string (params, &n, &rfc822_headers_len); - if (rfc822_headers == NULL) - goto out; - } - else if (fetch_check (params, &n, "BODY[TEXT]<0>")) - { - excerpt = fetch_literal_string (params, &n, &excerpt_len); - if (excerpt == NULL) - goto out; - } - else - { - /* Don't know how to handle unknown params so fail completely */ - goto out; - } - /* advance to next value in FETCH response list, if any */ - while (params[n] == ' ') - n++; - } - - if (!has_uid || rfc822_headers == NULL || excerpt == NULL) - goto out; - - /* OK, message is valid */ - parsed = TRUE; - - uid_str = g_strdup_printf ("%" G_GUINT64_FORMAT, - ((guint64) data->uidvalidity << 32) | ((guint64) uid)); - headers = parse_rfc822_headers (rfc822_headers); - from_header = g_hash_table_lookup (headers, "From"); - subject_header = g_hash_table_lookup (headers, "Subject"); - if (from_header == NULL) - from_header = ""; - if (subject_header == NULL) - subject_header = ""; - - /* TODO: set this */ - uri = g_strdup (""); - - /* extras is currently unused (and not currently in the D-Bus signature) */ - /* g_variant_builder_init (&extras_builder, G_VARIANT_TYPE_VARDICT); */ - - /* Emit D-Bus message */ - goa_mail_monitor_emit_message_received (data->monitor_data->monitor, - uid_str, - from_header, - subject_header, - excerpt, - uri); - /* g_variant_builder_end (&extras_builder)); */ - out: - if (!parsed) - { - /* Use g_warning() since we want bug-reports to improve the FETCH parser */ - g_warning ("Was unable to parse FETCH response for message with sequence number %d and parameters `%s'. " - "Please report this to %s", - message_seqnum, - params, - PACKAGE_BUGREPORT); - } - g_free (uri); - g_free (uid_str); - if (headers != NULL) - g_hash_table_unref (headers); - g_free (rfc822_headers); - g_free (excerpt); -} - -static void -imap_client_on_untagged_response (GoaBackendImapClient *client, - const gchar *response, - gpointer user_data) -{ - ImapClientData *data = user_data; - gchar s[64+1]; - gint i; - gint n; - - if (sscanf (response, "%d %64s", &i, s) == 2 && g_strcmp0 (s, "EXISTS") == 0) - { - data->num_exists = i; - } - else if (sscanf (response, "%d %64s", &i, s) == 2 && g_strcmp0 (s, "EXPUNGE") == 0) - { - /* See http://tools.ietf.org/html/rfc3501#section-7.4.1 */ - data->num_exists -= 1; - data->last_num_exists -= 1; - } - else if (sscanf (response, "OK [UIDVALIDITY %d]", &i) == 1) - { - data->uidvalidity = i; - } - else if (sscanf (response, "%d %64s%n", &i, s, &n) == 2 && g_strcmp0 (s, "FETCH") == 0) - { - const gchar *params; - params = response + n; - while (g_ascii_isspace (*params)) - params++; - imap_client_handle_fetch_response (data, i, params); - } - else - { - /* g_debug ("unhandled untagged response `%s'", response); */ - } -} - -static void -imap_client_sync_single (ImapClientData *data) -{ - GError *error; - gchar *response; - GoaBackendImapClient *client; - - /* Get ourselves an IMAP client and connect to the server */ - data->num_exists = -1; - client = goa_backend_imap_client_new (); - error = NULL; - if (!goa_backend_imap_client_connect_sync (client, - data->monitor_data->mail->host_and_port, - data->monitor_data->mail->use_tls, - data->monitor_data->mail->auth, - NULL, /* GCancellable */ - &error)) - goto out; - - /* Houston, we have a connection */ - goa_mail_monitor_set_connected (data->monitor_data->monitor, TRUE); - - g_signal_connect (client, - "untagged-response", - G_CALLBACK (imap_client_on_untagged_response), - data); - - /* First, select the INBOX - this is guaranteed to emit the EXISTS untagged response */ - error = NULL; - response = goa_backend_imap_client_run_command_sync (client, - "SELECT INBOX", - NULL, /* GCancellable */ - &error); - if (response == NULL) - goto out; - g_free (response); - - if (data->num_exists == -1) - { - g_set_error (&error, - GOA_ERROR, - GOA_ERROR_FAILED, - "Expected EXISTS untagged response for SELECT but received none"); - goto out; - } - data->last_num_exists = data->num_exists; - - /* This is the main loop where we idle, then refresh, then idle, - * then refresh again and around and around she goes... - */ - while (TRUE) - { - /* If the connection is closed/severed, this is the way we find out since - * the IDLE command submitted above disables timeouts - */ - response = goa_backend_imap_client_run_command_sync (client, - "NOOP", - NULL, /* GCancellable */ - &error); - if (response == NULL) - goto out; - g_free (response); - - /* Fetch newly received messages, if any - the D-Bus signal will - * get emitted from imap_client_handle_fetch_response() that - * will be called while the command is pending - */ - if (data->num_exists > data->last_num_exists) - { - GString *request_str; - guint num_new_messages; - guint n; - - num_new_messages = data->num_exists - data->last_num_exists; - request_str = g_string_new ("FETCH "); - for (n = 0; n < num_new_messages; n++) - { - if (n > 0) - g_string_append_c (request_str, ','); - g_string_append_printf (request_str, "%d", data->last_num_exists + 1 + n); - } - - g_string_append (request_str, - " (" - "UID " - "BODY.PEEK[HEADER.FIELDS (Date From To Cc Subject)] " - "BODY.PEEK[TEXT]<0.1000>" - ")"); - error = NULL; - response = goa_backend_imap_client_run_command_sync (client, - request_str->str, - NULL, /* GCancellable */ - &error); - g_string_free (request_str, TRUE); - if (response == NULL) - goto out; - g_free (response); - } - data->last_num_exists = data->num_exists; - - /* Wake up waiters */ - g_mutex_lock (data->monitor_data->imap_counter_lock); - data->monitor_data->imap_num_refreshes += 1; - g_cond_broadcast (data->monitor_data->imap_counter_cond); - g_mutex_unlock (data->monitor_data->imap_counter_lock); - - /* Never idle for more than 25 minutes cf. the recommendation - * in RFC 2177: http://tools.ietf.org/html/rfc2177 - */ - error = NULL; - if (!goa_backend_imap_client_idle_sync (client, - 25 * 60, - data->monitor_data->imap_cancellable, - &error)) - { - if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED) - { - g_cancellable_reset (data->monitor_data->imap_cancellable); - g_error_free (error); - error = NULL; - } - else - { - goto out; - } - } - - /* Check if asked to close */ - if (data->monitor_data->imap_request_close) - goto out; - - } /* Main loop */ - - out: - /* We no longer have a connection */ - goa_mail_monitor_set_connected (data->monitor_data->monitor, FALSE); - - /* Wake up waiters */ - g_mutex_lock (data->monitor_data->imap_counter_lock); - data->monitor_data->imap_num_connections_failed += 1; - g_cond_broadcast (data->monitor_data->imap_counter_cond); - g_mutex_unlock (data->monitor_data->imap_counter_lock); - - if (error != NULL) - { - /* g_debug ("leaving serve loop: error: %s (%s, %d)", error->message, g_quark_to_string (error->domain), error->code); */ - g_error_free (error); - } - else - { - /* g_debug ("leaving serve loop without error"); */ - } - g_signal_handlers_disconnect_by_func (client, - G_CALLBACK (imap_client_on_untagged_response), - data); - error = NULL; - if (!goa_backend_imap_client_disconnect_sync (client, - NULL, /* GCancellable */ - &error)) - { - g_warning ("%s:Error closing connection: %s (%s, %d)", - G_STRFUNC, - error->message, g_quark_to_string (error->domain), error->code); - g_error_free (error); - } - g_object_unref (client); -} - -static void -imap_client_sync (MonitorData *data) -{ - ImapClientData *imap_data; - - imap_data = g_slice_new0 (ImapClientData); - imap_data->monitor_data = monitor_data_ref (data); - - while (TRUE) - { - GPollFD poll_fd; - - /* tries connecting - blocks until the connection is closed */ - imap_client_sync_single (imap_data); - - if (data->imap_request_close) - goto out; - - /* Wait to get woken up */ - if (g_cancellable_make_pollfd (data->imap_cancellable, &poll_fd)) - { - gint poll_ret; - do - { - poll_ret = g_poll (&poll_fd, 1, -1); - } - while (poll_ret == -1 && errno == EINTR); - g_cancellable_release_fd (data->imap_cancellable); - g_cancellable_reset (data->imap_cancellable); - } - - if (data->imap_request_close) - goto out; - } - - out: - - /* Wake up waiters (if any) */ - g_mutex_lock (data->imap_counter_lock); - data->imap_num_refreshes = -1; - data->imap_num_connections_failed = -1; - g_cond_broadcast (data->imap_counter_cond); - g_mutex_unlock (data->imap_counter_lock); - - monitor_data_unref (imap_data->monitor_data); - g_slice_free (ImapClientData, imap_data); -} - - -/* ---------------------------------------------------------------------------------------------------- */ - -/* runs in thread dedicated to the method invocation */ -static gboolean -monitor_on_handle_refresh (GoaMailMonitor *monitor, - GDBusMethodInvocation *invocation, - gpointer user_data) -{ - MonitorData *data = user_data; - gint orig_imap_num_connections_failed; - gint orig_imap_num_refreshes; - gboolean refreshed, connection_failed, closed; - gint num_attempts; - - monitor_data_ref (data); - - num_attempts = 0; - again: - g_mutex_lock (data->imap_counter_lock); - orig_imap_num_refreshes = data->imap_num_refreshes; - orig_imap_num_connections_failed = data->imap_num_connections_failed; - /* Wake up the IMAP client thread - this will cause either a connection - * failure or a refresh - */ - g_cancellable_cancel (data->imap_cancellable); - g_cond_wait (data->imap_counter_cond, data->imap_counter_lock); - num_attempts += 1; - - closed = (orig_imap_num_refreshes == -1); - refreshed = (orig_imap_num_refreshes != data->imap_num_refreshes); - connection_failed = (orig_imap_num_connections_failed != data->imap_num_connections_failed); - g_mutex_unlock (data->imap_counter_lock); - - g_warn_if_fail (refreshed || connection_failed || closed); - - if (refreshed) - { - goa_mail_monitor_complete_refresh (monitor, invocation); - goto out; - } - - if (closed) - { - g_dbus_method_invocation_return_error (invocation, - GOA_ERROR, - GOA_ERROR_FAILED, - "The monitor was closed"); - goto out; - } - - /* Try at least three times to cope with broken connections */ - if (connection_failed && num_attempts < 3) - goto again; - - if (connection_failed) - { - g_dbus_method_invocation_return_error (invocation, - GOA_ERROR, - GOA_ERROR_FAILED, - "Failed to reconnect (tried %d times)", - num_attempts); - goto out; - } - - /* should never end up here but if we do, make sure - * the bug reporters can give us something useful - */ - g_warning ("Unexpected state while trying to refresh: refreshed=%d connection_failed=%d closed=%d num_attempts=%d", - refreshed, connection_failed, closed, num_attempts); - g_dbus_method_invocation_return_error (invocation, - GOA_ERROR, - GOA_ERROR_FAILED, - "Failed with unexpected state: " - "refreshed=%d connection_failed=%d closed=%d num_attempts=%d", - refreshed, connection_failed, closed, num_attempts); - - out: - monitor_data_unref (data); - return TRUE; /* invocation was handled */ -} - -/* runs in thread dedicated to the method invocation */ -static gboolean -monitor_on_handle_close (GoaMailMonitor *monitor, - GDBusMethodInvocation *invocation, - gpointer user_data) -{ - MonitorData *data = user_data; - /* yippee ki yay motherfucker */ - nuke_monitor (data); - goa_mail_monitor_complete_close (monitor, invocation); - return TRUE; /* invocation was handled */ -} - -/* runs in thread dedicated to the method invocation */ -static gboolean -monitor_on_handle_simulate_message_received (GoaMailMonitor *monitor, - GDBusMethodInvocation *invocation, - const gchar *uid, - const gchar *from, - const gchar *subject, - const gchar *excerpt, - const gchar *uri, - gpointer user_data) -{ - goa_mail_monitor_emit_message_received (monitor, uid, from, subject, excerpt, uri); - goa_mail_monitor_complete_simulate_message_received (monitor, invocation); - return TRUE; /* invocation was handled */ -} - - -/* runs in thread dedicated to the method invocation */ -static gboolean -handle_create_monitor (GoaMail *_mail, - GDBusMethodInvocation *invocation) -{ - GoaBackendImapMail *mail = GOA_BACKEND_IMAP_MAIL (_mail); - gchar *monitor_object_path; - GError *error; - MonitorData *data; - static gint _g_monitor_count = 0; - - monitor_object_path = NULL; - - data = g_slice_new0 (MonitorData); - data->ref_count = 1; - data->mail = g_object_ref (mail); - data->monitor = goa_mail_monitor_skeleton_new (); - /* Be optimistic that the connection works - if this is not so, - * imap_client_sync() will clear the flag - */ - goa_mail_monitor_set_connected (data->monitor, TRUE); - g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (data->monitor), - G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); - g_signal_connect (data->monitor, - "handle-refresh", - G_CALLBACK (monitor_on_handle_refresh), - data); - g_signal_connect (data->monitor, - "handle-close", - G_CALLBACK (monitor_on_handle_close), - data); - g_signal_connect (data->monitor, - "handle-simulate-message-received", - G_CALLBACK (monitor_on_handle_simulate_message_received), - data); - - monitor_object_path = g_strdup_printf ("/org/gnome/OnlineAccounts/mail_monitors/%d", _g_monitor_count++); - error = NULL; - if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (data->monitor), - g_dbus_method_invocation_get_connection (invocation), - monitor_object_path, - &error)) - { - g_prefix_error (&error, "Error exporting mail monitor: "); - g_dbus_method_invocation_return_gerror (invocation, error); - monitor_data_unref (data); - goto out; - } - - data->name_watcher_id = g_bus_watch_name_on_connection (g_dbus_method_invocation_get_connection (invocation), - g_dbus_method_invocation_get_sender (invocation), - G_BUS_NAME_WATCHER_FLAGS_NONE, - NULL, /* name_appeared_handler */ - on_monitor_owner_vanished, - data, - NULL); - - /* OK, we're in business - finish the invocation - * - * TODO: set up things so only caller can access the created object? - */ - goa_mail_complete_create_monitor (GOA_MAIL (mail), invocation, monitor_object_path); - - /* Create the IMAP client - this blocks our thread until the owner - * vanishes or the Close() method is called ... - * - * The data->imap_cancellable member can be used to wake up the loop - * to check for data->imap_request_close member or just to - * refresh... - */ - data->imap_cancellable = g_cancellable_new (); - data->imap_counter_lock = g_mutex_new (); - data->imap_counter_cond = g_cond_new (); - imap_client_sync (data); - - out: - g_free (monitor_object_path); - return TRUE; /* invocation was handled */ -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -goa_backend_imap_mail__goa_mail_iface_init (GoaMailIface *iface) -{ - iface->handle_create_monitor = handle_create_monitor; -} - -/* ---------------------------------------------------------------------------------------------------- */ diff --git a/src/goa/goabackendimapmail.h b/src/goa/goabackendimapmail.h deleted file mode 100644 index 2d87aaf..0000000 --- a/src/goa/goabackendimapmail.h +++ /dev/null @@ -1,45 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __GOA_BACKEND_IMAP_MAIL_H__ -#define __GOA_BACKEND_IMAP_MAIL_H__ - -#include - -G_BEGIN_DECLS - -#define GOA_TYPE_BACKEND_IMAP_MAIL (goa_backend_imap_mail_get_type ()) -#define GOA_BACKEND_IMAP_MAIL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_IMAP_MAIL, GoaBackendImapMail)) -#define GOA_IS_BACKEND_IMAP_MAIL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_IMAP_MAIL)) - -GType goa_backend_imap_mail_get_type (void) G_GNUC_CONST; -GoaMail *goa_backend_imap_mail_new (const gchar *host_and_port, - gboolean use_tls, - GoaBackendImapAuth *auth); - -G_END_DECLS - -#endif /* __GOA_BACKEND_IMAP_MAIL_H__ */ diff --git a/src/goa/goabackendoauth2provider.c b/src/goa/goabackendoauth2provider.c deleted file mode 100644 index 4713d53..0000000 --- a/src/goa/goabackendoauth2provider.c +++ /dev/null @@ -1,1473 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#include "config.h" -#include -#include - -#include -#include -#include - -#include "goabackendprovider.h" -#include "goabackendoauth2provider.h" - -/** - * SECTION:goabackendoauth2provider - * @title: GoaBackendOAuth2Provider - * @short_description: Abstract base class for OAuth 2.0 providers - * - * #GoaBackendOAuth2Provider is an abstract base class for OAuth - * 2.0 based providers. - * - * Subclasses must implement - * #GoaBackendOAuth2ProviderClass.get_authorization_uri, - * #GoaBackendOAuth2ProviderClass.get_token_uri, - * #GoaBackendOAuth2ProviderClass.get_redirect_uri, - * #GoaBackendOAuth2ProviderClass.get_scope, - * #GoaBackendOAuth2ProviderClass.get_client_id, - * #GoaBackendOAuth2ProviderClass.get_client_secret and - * #GoaBackendOAuth2ProviderClass.get_identity_sync methods. - * - * Additionally, the - * #GoaBackendProviderClass.get_provider_type, - * #GoaBackendProviderClass.get_name, - * #GoaBackendProviderClass.build_object (this should chain up to its - * parent class) methods must be implemented. - * - * Note that the #GoaBackendProviderClass.add_account, - * #GoaBackendProviderClass.refresh_account and - * #GoaBackendProviderClass.ensure_credentials_sync methods do not - * need to be implemented - this type implements these methods.. - */ - -G_LOCK_DEFINE_STATIC (provider_lock); - -G_DEFINE_ABSTRACT_TYPE (GoaBackendOAuth2Provider, goa_backend_oauth2_provider, GOA_TYPE_BACKEND_PROVIDER); - -static gboolean -is_authorization_error (GError *error) -{ - gboolean ret; - - g_return_val_if_fail (error != NULL, FALSE); - - ret = FALSE; - if (error->domain == REST_PROXY_ERROR || error->domain == SOUP_HTTP_ERROR) - { - if (SOUP_STATUS_IS_CLIENT_ERROR (error->code)) - ret = TRUE; - } - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean -goa_backend_oauth2_provider_get_use_external_browser_default (GoaBackendOAuth2Provider *provider) -{ - return FALSE; -} - -/** - * goa_backend_oauth2_provider_get_use_external_browser: - * @provider: A #GoaBackendOAuth2Provider. - * - * Returns whether an external browser (the users default browser) - * should be used for user interaction. - * - * If an external browser is used, then the dialogs presented in - * goa_backend_provider_add_account() and - * goa_backend_provider_refresh_account() will show a simple "Paste - * authorization code here" instructions along with an entry and - * button. - * - * This is a virtual method where the default implementation returns - * %FALSE. - * - * Returns: %TRUE if the user interaction should happen in an external - * browser, %FALSE to use an embedded browser widget. - */ -gboolean -goa_backend_oauth2_provider_get_use_external_browser (GoaBackendOAuth2Provider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH2_PROVIDER (provider), FALSE); - return GOA_BACKEND_OAUTH2_PROVIDER_GET_CLASS (provider)->get_use_external_browser (provider); -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gchar * -goa_backend_oauth2_provider_build_authorization_uri_default (GoaBackendOAuth2Provider *provider, - const gchar *authorization_uri, - const gchar *escaped_redirect_uri, - const gchar *escaped_client_id, - const gchar *escaped_scope) -{ - return g_strdup_printf ("%s" - "?response_type=code" - "&redirect_uri=%s" - "&client_id=%s" - "&scope=%s", - authorization_uri, - escaped_redirect_uri, - escaped_client_id, - escaped_scope); -} - -/** - * goa_backend_oauth2_provider_build_authorization_uri: - * @provider: A #GoaBackendOAuth2Provider. - * @authorization_uri: An authorization URI. - * @escaped_redirect_uri: An escaped redirect URI - * @escaped_client_id: An escaped client id - * @escaped_scope: The escaped scope. - * - * Builds the URI that can be opened in a web browser (or embedded web - * browser widget) to start authenticating an user. - * - * The default implementation just returns the expected URI - * (e.g. http://example.com/dialog/oauth2?response_type=code&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb&client_id=foo&scope=email%20stuff) - * - override (and chain up) if you e.g. need to to pass additional - * parameters. - * - * The @authorization_uri, @escaped_redirect_uri, @escaped_client_id - * and @escaped_scope parameters originate from the result of the - * the goa_backend_oauth2_provider_get_authorization_uri(), goa_backend_oauth2_provider_get_redirect_uri(), goa_backend_oauth2_provider_get_client_id() - * and goa_backend_oauth2_provider_get_scope() methods with the latter - * three escaped using g_uri_escape_string(). - * - * Returns: (transfer full): An authorization URI that must be freed with g_free(). - */ -gchar * -goa_backend_oauth2_provider_build_authorization_uri (GoaBackendOAuth2Provider *provider, - const gchar *authorization_uri, - const gchar *escaped_redirect_uri, - const gchar *escaped_client_id, - const gchar *escaped_scope) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH2_PROVIDER (provider), NULL); - g_return_val_if_fail (authorization_uri != NULL, NULL); - g_return_val_if_fail (escaped_redirect_uri != NULL, NULL); - g_return_val_if_fail (escaped_client_id != NULL, NULL); - g_return_val_if_fail (escaped_scope != NULL, NULL); - return GOA_BACKEND_OAUTH2_PROVIDER_GET_CLASS (provider)->build_authorization_uri (provider, - authorization_uri, - escaped_redirect_uri, - escaped_client_id, - escaped_scope); -} - -/** - * goa_backend_oauth2_provider_get_authorization_uri: - * @provider: A #GoaBackendOAuth2Provider. - * - * Gets the authorization - * endpoint used for authenticating the user and obtaining - * authorization. - * - * You should not include any parameters in the returned URI. If you - * need to include additional parameters than the standard ones, - * override #GoaBackendOAuth2ProviderClass.build_authorization_uri - - * see goa_backend_oauth2_provider_build_authorization_uri() for more - * details. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider - do not free. - */ -const gchar * -goa_backend_oauth2_provider_get_authorization_uri (GoaBackendOAuth2Provider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH2_PROVIDER (provider), NULL); - return GOA_BACKEND_OAUTH2_PROVIDER_GET_CLASS (provider)->get_authorization_uri (provider); -} - -/** - * goa_backend_oauth2_provider_get_token_uri: - * @provider: A #GoaBackendOAuth2Provider. - * - * Gets the token - * endpoint used for obtaining an access token. - * - * You should not include any parameters in the returned URI. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider - do not free. - */ -const gchar * -goa_backend_oauth2_provider_get_token_uri (GoaBackendOAuth2Provider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH2_PROVIDER (provider), NULL); - return GOA_BACKEND_OAUTH2_PROVIDER_GET_CLASS (provider)->get_token_uri (provider); -} - -/** - * goa_backend_oauth2_provider_get_redirect_uri: - * @provider: A #GoaBackendOAuth2Provider. - * - * Gets the redirect_uri - * used when requesting authorization. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider - do not free. - */ -const gchar * -goa_backend_oauth2_provider_get_redirect_uri (GoaBackendOAuth2Provider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH2_PROVIDER (provider), NULL); - return GOA_BACKEND_OAUTH2_PROVIDER_GET_CLASS (provider)->get_redirect_uri (provider); -} - -/** - * goa_backend_oauth2_provider_get_scope: - * @provider: A #GoaBackendOAuth2Provider. - * - * Gets the scope - * used when requesting authorization. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider - do not free. - */ -const gchar * -goa_backend_oauth2_provider_get_scope (GoaBackendOAuth2Provider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH2_PROVIDER (provider), NULL); - return GOA_BACKEND_OAUTH2_PROVIDER_GET_CLASS (provider)->get_scope (provider); -} - -/** - * goa_backend_oauth2_provider_get_client_id: - * @provider: A #GoaBackendOAuth2Provider. - * - * Gets the client_id - * identifying the client. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider - do not free. - */ -const gchar * -goa_backend_oauth2_provider_get_client_id (GoaBackendOAuth2Provider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH2_PROVIDER (provider), NULL); - return GOA_BACKEND_OAUTH2_PROVIDER_GET_CLASS (provider)->get_client_id (provider); -} - -/** - * goa_backend_oauth2_provider_get_client_secret: - * @provider: A #GoaBackendOAuth2Provider. - * - * Gets the client_secret - * associated with the client. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider - do not free. - */ -const gchar * -goa_backend_oauth2_provider_get_client_secret (GoaBackendOAuth2Provider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH2_PROVIDER (provider), NULL); - return GOA_BACKEND_OAUTH2_PROVIDER_GET_CLASS (provider)->get_client_secret (provider); -} - -/** - * goa_backend_oauth2_provider_get_identity_sync: - * @provider: A #GoaBackendOAuth2Provider. - * @access_token: A valid OAuth 2.0 access token. - * @out_name: (out): Return location for name or %NULL. - * @cancellable: (allow-none): A #GCancellable or %NULL. - * @error: Return location for @error or %NULL. - * - * Method that returns the identity corresponding to - * @access_token. - * - * The identity is needed because all authentication happens out of - * band. The only requirement is that the returned identity is unique - * - for example, for #GoaBackendGoogleProvider the returned identity - * is the email address, for #GoaBackendFacebookProvider it's the user - * name. In addition to the identity, an implementation also returns a - * name that is more suitable for presentation - * (the identity could be a GUID for example) and doesn't have to be - * unique. - * - * The calling thread is blocked while the identity is obtained. - * - * Returns: The identity or %NULL if error is set. The returned string - * must be freed with g_free(). - */ -gchar * -goa_backend_oauth2_provider_get_identity_sync (GoaBackendOAuth2Provider *provider, - const gchar *access_token, - gchar **out_name, - GCancellable *cancellable, - GError **error) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH2_PROVIDER (provider), NULL); - g_return_val_if_fail (access_token != NULL, NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); - g_return_val_if_fail (error == NULL || *error == NULL, NULL); - return GOA_BACKEND_OAUTH2_PROVIDER_GET_CLASS (provider)->get_identity_sync (provider, access_token, out_name, cancellable, error); -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gchar * -get_tokens_sync (GoaBackendOAuth2Provider *provider, - const gchar *authorization_code, - const gchar *refresh_token, - gchar **out_refresh_token, - gint *out_access_token_expires_in, - GCancellable *cancellable, - GError **error) -{ - RestProxy *proxy; - RestProxyCall *call; - gchar *ret; - guint status_code; - gchar *ret_access_token; - gint ret_access_token_expires_in; - gchar *ret_refresh_token; - const gchar *payload; - gsize payload_length; - - ret = NULL; - ret_access_token = NULL; - ret_access_token_expires_in = 0; - ret_refresh_token = NULL; - - proxy = rest_proxy_new (goa_backend_oauth2_provider_get_token_uri (provider), FALSE); - call = rest_proxy_new_call (proxy); - rest_proxy_call_set_method (call, "POST"); - rest_proxy_call_add_header (call, "Content-Type", "application/x-www-form-urlencoded"); - if (refresh_token != NULL) - { - /* Swell, we have a refresh code - just use that */ - rest_proxy_call_add_param (call, "client_id", goa_backend_oauth2_provider_get_client_id (provider)); - rest_proxy_call_add_param (call, "client_secret", goa_backend_oauth2_provider_get_client_secret (provider)); - rest_proxy_call_add_param (call, "grant_type", "refresh_token"); - rest_proxy_call_add_param (call, "refresh_token", refresh_token); - } - else - { - /* No refresh code.. request an access token using the authorization code instead */ - rest_proxy_call_add_param (call, "client_id", goa_backend_oauth2_provider_get_client_id (provider)); - rest_proxy_call_add_param (call, "client_secret", goa_backend_oauth2_provider_get_client_secret (provider)); - rest_proxy_call_add_param (call, "grant_type", "authorization_code"); - rest_proxy_call_add_param (call, "code", authorization_code); - rest_proxy_call_add_param (call, "redirect_uri", goa_backend_oauth2_provider_get_redirect_uri (provider)); - } - - /* TODO: cancellable support? */ - if (!rest_proxy_call_sync (call, error)) - goto out; - - status_code = rest_proxy_call_get_status_code (call); - if (status_code != 200) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Expected status 200 when requesting access token, instead got status %d (%s)"), - status_code, - rest_proxy_call_get_status_message (call)); - goto out; - } - - payload = rest_proxy_call_get_payload (call); - payload_length = rest_proxy_call_get_payload_length (call); - /* some older OAuth2 implementations does not return json - handle that too */ - if (g_str_has_prefix (payload, "access_token=")) - { - GHashTable *hash; - const gchar *expires_in_str; - hash = soup_form_decode (payload); - ret_access_token = g_strdup (g_hash_table_lookup (hash, "access_token")); - if (ret_access_token == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Didn't find access_token in non-JSON data")); - g_hash_table_unref (hash); - goto out; - } - /* refresh_token is optional */ - ret_refresh_token = g_hash_table_lookup (hash, "refresh_token"); - /* expires_in is optional */ - expires_in_str = g_hash_table_lookup (hash, "expires_in"); - /* sometimes "expires_in" appears as "expires" */ - if (expires_in_str == NULL) - expires_in_str = g_hash_table_lookup (hash, "expires"); - if (expires_in_str != NULL) - ret_access_token_expires_in = atoi (expires_in_str); - g_hash_table_unref (hash); - } - else - { - GError *local_error; - JsonParser *parser; - JsonObject *object; - - local_error = NULL; - parser = json_parser_new (); - if (!json_parser_load_from_data (parser, payload, payload_length, &local_error)) - { - g_prefix_error (error, _("Error parsing response as JSON: ")); - g_object_unref (parser); - goto out; - } - object = json_node_get_object (json_parser_get_root (parser)); - ret_access_token = g_strdup (json_object_get_string_member (object, "access_token")); - if (ret_access_token == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Didn't find access_token in JSON data")); - goto out; - } - /* refresh_token is optional... */ - if (json_object_has_member (object, "refresh_token")) - ret_refresh_token = g_strdup (json_object_get_string_member (object, "refresh_token")); - if (json_object_has_member (object, "expires_in")) - ret_access_token_expires_in = json_object_get_int_member (object, "expires_in"); - g_object_unref (parser); - } - - ret = ret_access_token; - ret_access_token = NULL; - if (out_access_token_expires_in != NULL) - *out_access_token_expires_in = ret_access_token_expires_in; - if (out_refresh_token != NULL) - { - *out_refresh_token = ret_refresh_token; - ret_refresh_token = NULL; - } - - out: - g_free (ret_access_token); - g_free (ret_refresh_token); - if (call != NULL) - g_object_unref (call); - if (proxy != NULL) - g_object_unref (proxy); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ -typedef struct -{ - GoaBackendOAuth2Provider *provider; - GtkDialog *dialog; - GError *error; - GMainLoop *loop; - - gchar *authorization_code; - gchar *access_token; - gint access_token_expires_in; - gchar *refresh_token; - - gchar *identity; - gchar *name; -} IdentifyData; - -static gboolean -on_web_view_navigation_policy_decision_requested (WebKitWebView *webView, - WebKitWebFrame *frame, - WebKitNetworkRequest *request, - WebKitWebNavigationAction *navigation_action, - WebKitWebPolicyDecision *policy_decision, - gpointer user_data) -{ - IdentifyData *data = user_data; - const gchar *redirect_uri; - const gchar *requested_uri; - - /* TODO: use oauth2_proxy_extract_access_token() */ - - requested_uri = webkit_network_request_get_uri (request); - //g_debug ("requested_uri is %s", requested_uri); - redirect_uri = goa_backend_oauth2_provider_get_redirect_uri (data->provider); - if (g_str_has_prefix (requested_uri, redirect_uri)) - { - SoupMessage *message; - SoupURI *uri; - GHashTable *key_value_pairs; - - message = webkit_network_request_get_message (request); - uri = soup_message_get_uri (message); - key_value_pairs = soup_form_decode (uri->query); - - data->authorization_code = g_strdup (g_hash_table_lookup (key_value_pairs, "code")); - if (data->authorization_code != NULL) - { - gtk_dialog_response (data->dialog, GTK_RESPONSE_OK); - } - else - { - g_set_error (&data->error, - GOA_ERROR, - GOA_ERROR_NOT_AUTHORIZED, - _("Authorization response was \"%s\""), - (const gchar *) g_hash_table_lookup (key_value_pairs, "error")); - gtk_dialog_response (data->dialog, GTK_RESPONSE_CLOSE); - } - g_hash_table_unref (key_value_pairs); - webkit_web_policy_decision_ignore (policy_decision); - return TRUE; /* ignore the request */ - } - else - { - return FALSE; /* make default behavior apply */ - } -} - -static void -on_entry_changed (GtkEditable *editable, - gpointer user_data) -{ - IdentifyData *data = user_data; - gboolean sensitive; - - g_free (data->authorization_code); - data->authorization_code = g_strdup (gtk_entry_get_text (GTK_ENTRY (editable))); - sensitive = data->authorization_code != NULL && (strlen (data->authorization_code) > 0); - gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, sensitive); -} - -static gboolean -get_tokens_and_identity (GoaBackendOAuth2Provider *provider, - GtkDialog *dialog, - GtkBox *vbox, - gchar **out_authorization_code, - gchar **out_access_token, - gint *out_access_token_expires_in, - gchar **out_refresh_token, - gchar **out_identity, - gchar **out_name, - GError **error) -{ - gboolean ret; - gchar *url; - IdentifyData data; - gchar *escaped_redirect_uri; - gchar *escaped_client_id; - gchar *escaped_scope; - - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH2_PROVIDER (provider), FALSE); - g_return_val_if_fail (GTK_IS_DIALOG (dialog), FALSE); - g_return_val_if_fail (GTK_IS_BOX (vbox), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - ret = FALSE; - escaped_redirect_uri = NULL; - escaped_client_id = NULL; - escaped_scope = NULL; - - /* TODO: check with NM whether we're online, if not - return error */ - - memset (&data, '\0', sizeof (IdentifyData)); - data.provider = provider; - data.loop = g_main_loop_new (NULL, FALSE); - - /* TODO: use oauth2_proxy_build_login_url_full() */ - escaped_redirect_uri = g_uri_escape_string (goa_backend_oauth2_provider_get_redirect_uri (provider), NULL, TRUE); - escaped_client_id = g_uri_escape_string (goa_backend_oauth2_provider_get_client_id (provider), NULL, TRUE); - escaped_scope = g_uri_escape_string (goa_backend_oauth2_provider_get_scope (provider), NULL, TRUE); - url = goa_backend_oauth2_provider_build_authorization_uri (provider, - goa_backend_oauth2_provider_get_authorization_uri (provider), - escaped_redirect_uri, - escaped_client_id, - escaped_scope); - //g_debug ("url = %s", url); - - if (goa_backend_oauth2_provider_get_use_external_browser (provider)) - { - GtkWidget *label; - GtkWidget *entry; - gchar *escaped_url; - gchar *markup; - - escaped_url = g_markup_escape_text (url, -1); - markup = g_strdup_printf (_("Paste authorization code obtained from the authorization page:"), - escaped_url); - g_free (escaped_url); - - label = gtk_label_new (NULL); - gtk_label_set_markup (GTK_LABEL (label), markup); - g_free (markup); - gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0); - entry = gtk_entry_new (); - gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); - gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, TRUE, 0); - gtk_widget_grab_focus (entry); - gtk_widget_show_all (GTK_WIDGET (vbox)); - - gtk_dialog_add_button (dialog, GTK_STOCK_OK, GTK_RESPONSE_OK); - gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK); - gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_OK, FALSE); - g_signal_connect (entry, "changed", G_CALLBACK (on_entry_changed), &data); - - if (!gtk_show_uri (NULL, - url, - GDK_CURRENT_TIME, - &data.error)) - { - goto out; - } - } - else - { - GtkWidget *scrolled_window; - GtkWidget *web_view; - SoupSession *webkit_soup_session; - SoupCookieJar *cookie_jar; - - /* Ensure we use an empty non-persistent cookie to avoid login - * credentials being reused... - */ - webkit_soup_session = webkit_get_default_session (); - soup_session_remove_feature_by_type (webkit_soup_session, SOUP_TYPE_COOKIE_JAR); - cookie_jar = soup_cookie_jar_new (); - soup_session_add_feature (webkit_soup_session, SOUP_SESSION_FEATURE (cookie_jar)); - g_object_unref (cookie_jar); - - /* TODO: we might need to add some more web browser UI to make this - * work... - */ - web_view = webkit_web_view_new (); - webkit_web_view_load_uri (WEBKIT_WEB_VIEW (web_view), url); - g_signal_connect (web_view, - "navigation-policy-decision-requested", - G_CALLBACK (on_web_view_navigation_policy_decision_requested), - &data); - - scrolled_window = gtk_scrolled_window_new (NULL, NULL); - gtk_widget_set_size_request (scrolled_window, 500, 400); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), - GTK_POLICY_AUTOMATIC, - GTK_POLICY_AUTOMATIC); - gtk_container_add (GTK_CONTAINER (scrolled_window), web_view); - gtk_container_add (GTK_CONTAINER (vbox), scrolled_window); - gtk_widget_show_all (scrolled_window); - } - data.dialog = dialog; - gtk_dialog_run (GTK_DIALOG (dialog)); - if (data.authorization_code == NULL) - { - if (data.error == NULL) - { - g_set_error (&data.error, - GOA_ERROR, - GOA_ERROR_DIALOG_DISMISSED, - _("Dialog was dismissed")); - } - goto out; - } - g_assert (data.error == NULL); - - gtk_widget_hide (GTK_WIDGET (dialog)); - - /* OK, we now have the authorization code... now we need to get the - * email address (to e.g. check if the account already exists on - * @client).. for that we need to get a (short-lived) access token - * and a refresh_token - */ - - /* TODO: run in worker thread */ - data.access_token = get_tokens_sync (provider, - data.authorization_code, - NULL, /* refresh_token */ - &data.refresh_token, - &data.access_token_expires_in, - NULL, /* GCancellable */ - error); - if (data.access_token == NULL) - { - g_prefix_error (&data.error, _("Error getting an Access Token: ")); - goto out; - } - - /* TODO: run in worker thread */ - data.identity = goa_backend_oauth2_provider_get_identity_sync (provider, - data.access_token, - &data.name, - NULL, /* TODO: GCancellable */ - error); - if (data.identity == NULL) - { - g_prefix_error (&data.error, _("Error getting identity: ")); - goto out; - } - - ret = TRUE; - - out: - if (ret) - { - g_warn_if_fail (data.error == NULL); - if (out_authorization_code != NULL) - *out_authorization_code = g_strdup (data.authorization_code); - if (out_access_token != NULL) - *out_access_token = g_strdup (data.access_token); - if (out_access_token_expires_in != NULL) - *out_access_token_expires_in = data.access_token_expires_in; - if (out_refresh_token != NULL) - *out_refresh_token = g_strdup (data.refresh_token); - if (out_identity != NULL) - *out_identity = g_strdup (data.identity); - if (out_name != NULL) - *out_name = g_strdup (data.name); - } - else - { - g_warn_if_fail (data.error != NULL); - g_propagate_error (error, data.error); - } - - g_free (data.identity); - g_free (data.name); - g_free (url); - - g_free (data.authorization_code); - if (data.loop != NULL) - g_main_loop_unref (data.loop); - g_free (data.access_token); - g_free (data.refresh_token); - g_free (escaped_redirect_uri); - g_free (escaped_client_id); - g_free (escaped_scope); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -typedef struct -{ - GError *error; - GMainLoop *loop; - gchar *account_object_path; -} AddData; - -static void -add_account_cb (GoaManager *manager, - GAsyncResult *res, - gpointer user_data) -{ - AddData *data = user_data; - goa_manager_call_add_account_finish (manager, - &data->account_object_path, - res, - &data->error); - g_main_loop_quit (data->loop); -} - -static gint64 -duration_to_abs_usec (gint duration_sec) -{ - gint64 ret; - GTimeVal now; - - g_get_current_time (&now); - ret = ((gint64) now.tv_sec) * 1000L * 1000L + ((gint64) now.tv_usec); - ret += ((gint64) duration_sec) * 1000L * 1000L; - return ret; -} - -static gint -abs_usec_to_duration (gint64 abs_usec) -{ - gint64 ret; - GTimeVal now; - - g_get_current_time (&now); - ret = abs_usec - (((gint64) now.tv_sec) * 1000L * 1000L + ((gint64) now.tv_usec)); - ret /= 1000L * 1000L; - return ret; -} - -static GoaObject * -goa_backend_oauth2_provider_add_account (GoaBackendProvider *_provider, - GoaClient *client, - GtkDialog *dialog, - GtkBox *vbox, - GError **error) -{ - GoaBackendOAuth2Provider *provider = GOA_BACKEND_OAUTH2_PROVIDER (_provider); - GoaObject *ret; - gchar *authorization_code; - gchar *access_token; - gint access_token_expires_in; - gchar *refresh_token; - gchar *identity; - gchar *name; - GList *accounts; - GList *l; - AddData data; - GVariantBuilder builder; - - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH2_PROVIDER (provider), NULL); - g_return_val_if_fail (GOA_IS_CLIENT (client), NULL); - g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); - g_return_val_if_fail (GTK_IS_BOX (vbox), NULL); - g_return_val_if_fail (error == NULL || *error == NULL, NULL); - - ret = NULL; - authorization_code = NULL; - access_token = NULL; - refresh_token = NULL; - identity = NULL; - name = NULL; - accounts = NULL; - - memset (&data, '\0', sizeof (AddData)); - data.loop = g_main_loop_new (NULL, FALSE); - - if (!get_tokens_and_identity (provider, - dialog, - vbox, - &authorization_code, - &access_token, - &access_token_expires_in, - &refresh_token, - &identity, - &name, - &data.error)) - goto out; - - /* OK, got the identity... see if there's already an account - * of this type with the given identity - */ - accounts = goa_client_get_accounts (client); - for (l = accounts; l != NULL; l = l->next) - { - GoaObject *object = GOA_OBJECT (l->data); - GoaAccount *account; - GoaOAuth2Based *oauth2_based; - const gchar *identity_from_object; - - account = goa_object_peek_account (object); - oauth2_based = goa_object_peek_oauth2_based (object); - if (oauth2_based == NULL) - continue; - - if (g_strcmp0 (goa_account_get_provider_type (account), - goa_backend_provider_get_provider_type (GOA_BACKEND_PROVIDER (provider))) != 0) - continue; - - identity_from_object = goa_oauth2_based_get_identity (oauth2_based); - if (g_strcmp0 (identity_from_object, identity) == 0) - { - g_set_error (&data.error, - GOA_ERROR, - GOA_ERROR_ACCOUNT_EXISTS, - _("There is already an account for the identity %s"), - identity); - goto out; - } - } - - /* we want the GoaClient to update before this method returns (so it - * can create a proxy for the new object) so run the mainloop while - * waiting for this to complete - */ - goa_manager_call_add_account (goa_client_get_manager (client), - goa_backend_provider_get_provider_type (GOA_BACKEND_PROVIDER (provider)), - name, /* Name */ - g_variant_new_parsed ("{'Identity': %s}", - identity), - NULL, /* GCancellable* */ - (GAsyncReadyCallback) add_account_cb, - &data); - g_main_loop_run (data.loop); - if (data.error != NULL) - goto out; - - g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); - g_variant_builder_add (&builder, "{sv}", "authorization_code", g_variant_new_string (authorization_code)); - g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token)); - if (access_token_expires_in > 0) - g_variant_builder_add (&builder, "{sv}", "access_token_expires_at", - g_variant_new_int64 (duration_to_abs_usec (access_token_expires_in))); - if (refresh_token != NULL) - g_variant_builder_add (&builder, "{sv}", "refresh_token", g_variant_new_string (refresh_token)); - /* TODO: run in worker thread */ - if (!goa_backend_provider_store_credentials_sync (GOA_BACKEND_PROVIDER (provider), - identity, - g_variant_builder_end (&builder), - NULL, /* GCancellable */ - &data.error)) - goto out; - - ret = GOA_OBJECT (g_dbus_object_manager_get_object (goa_client_get_object_manager (client), - data.account_object_path)); - - out: - if (data.error != NULL) - { - g_propagate_error (error, data.error); - g_assert (ret == NULL); - } - else - { - g_assert (ret != NULL); - } - - g_list_foreach (accounts, (GFunc) g_object_unref, NULL); - g_list_free (accounts); - g_free (identity); - g_free (name); - g_free (refresh_token); - g_free (access_token); - g_free (authorization_code); - g_free (data.account_object_path); - if (data.loop != NULL) - g_main_loop_unref (data.loop); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean -goa_backend_oauth2_provider_refresh_account (GoaBackendProvider *_provider, - GoaClient *client, - GoaObject *object, - GtkWindow *parent, - GError **error) -{ - GoaBackendOAuth2Provider *provider = GOA_BACKEND_OAUTH2_PROVIDER (_provider); - GtkWidget *dialog; - gchar *authorization_code; - gchar *access_token; - gint access_token_expires_in; - gchar *refresh_token; - gchar *identity; - const gchar *existing_identity; - GVariantBuilder builder; - gboolean ret; - - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH2_PROVIDER (provider), FALSE); - g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE); - g_return_val_if_fail (GOA_IS_OBJECT (object), FALSE); - g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - authorization_code = NULL; - access_token = NULL; - refresh_token = NULL; - identity = NULL; - - dialog = gtk_dialog_new_with_buttons (NULL, - parent, - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, - NULL); - gtk_container_set_border_width (GTK_CONTAINER (dialog), 12); - gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); - gtk_widget_show_all (dialog); - - if (!get_tokens_and_identity (provider, - GTK_DIALOG (dialog), - GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), - &authorization_code, - &access_token, - &access_token_expires_in, - &refresh_token, - &identity, - NULL, /* out_name */ - error)) - goto out; - - existing_identity = goa_oauth2_based_get_identity (goa_object_peek_oauth2_based (object)); - if (g_strcmp0 (identity, existing_identity) != 0) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Was asked to login as %s, but logged in as %s"), - existing_identity, - identity); - goto out; - } - - g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); - g_variant_builder_add (&builder, "{sv}", "authorization_code", g_variant_new_string (authorization_code)); - g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token)); - if (access_token_expires_in > 0) - g_variant_builder_add (&builder, "{sv}", "access_token_expires_at", - g_variant_new_int64 (duration_to_abs_usec (access_token_expires_in))); - if (refresh_token != NULL) - g_variant_builder_add (&builder, "{sv}", "refresh_token", g_variant_new_string (refresh_token)); - if (!goa_backend_provider_store_credentials_sync (GOA_BACKEND_PROVIDER (provider), - identity, - g_variant_builder_end (&builder), - NULL, /* GCancellable */ - error)) - goto out; - - goa_account_call_ensure_credentials (goa_object_peek_account (object), - NULL, /* GCancellable */ - NULL, NULL); /* callback, user_data */ - - ret = TRUE; - - out: - gtk_widget_destroy (dialog); - - g_free (identity); - g_free (access_token); - g_free (authorization_code); - g_free (refresh_token); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -free_mutex (GMutex *mutex) -{ - g_mutex_free (mutex); -} - -/** - * goa_backend_oauth2_provider_get_access_token_sync: - * @provider: A #GoaBackendOAuth2Provider. - * @object: A #GoaObject. - * @force_refresh: If set to %TRUE, forces a refresh of the access token, if possible. - * @out_access_token_expires_in: (out): Return location for how many seconds the returned token is valid for (0 if unknown) or %NULL. - * @cancellable: (allow-none): A #GCancellable or %NULL. - * @error: Return location for error or %NULL. - * - * Synchronously gets an access token for @object. The calling thread - * is blocked while the operation is pending. - * - * The resulting token is typically read from the local cache so most - * of the time only a local roundtrip to the storage for the token - * cache (e.g. gnome-keyring-daemon) is - * needed. However, the operation may involve refreshing the token - * with the service provider so a full network round-trip may be - * needed. - * - * Note that multiple calls are serialized to avoid multiple - * outstanding requests to the service provider. - * - * This operation may fail if e.g. unable to refresh the credentials - * or if network connectivity is not available. Note that even if a - * token is returned, the returned token isn't guaranteed to work - - * use goa_backend_provider_ensure_credentials_sync() if you need - * stronger guarantees. - * - * Returns: The access token or %NULL if error is set. The returned - * string must be freed with g_free(). - */ -gchar * -goa_backend_oauth2_provider_get_access_token_sync (GoaBackendOAuth2Provider *provider, - GoaObject *object, - gboolean force_refresh, - gint *out_access_token_expires_in, - GCancellable *cancellable, - GError **error) -{ - const gchar *identity; - GVariant *credentials; - GVariantIter iter; - const gchar *key; - GVariant *value; - gchar *authorization_code; - gchar *access_token; - gint access_token_expires_in; - gchar *refresh_token; - gchar *old_refresh_token; - gboolean success; - GVariantBuilder builder; - gchar *ret; - GMutex *lock; - - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH2_PROVIDER (provider), NULL); - g_return_val_if_fail (GOA_IS_OBJECT (object), NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); - g_return_val_if_fail (error == NULL || *error == NULL, NULL); - - ret = NULL; - credentials = NULL; - authorization_code = NULL; - access_token = NULL; - refresh_token = NULL; - old_refresh_token = NULL; - access_token_expires_in = 0; - success = FALSE; - - /* provider_lock is too coarse, use a per-object lock instead */ - G_LOCK (provider_lock); - lock = g_object_get_data (G_OBJECT (object), "-goa-backend-oauth2-provider-get-access-token-lock"); - if (lock == NULL) - { - lock = g_mutex_new (); - g_object_set_data_full (G_OBJECT (object), - "-goa-backend-oauth2-provider-get-access-token-lock", - lock, - (GDestroyNotify) free_mutex); - } - G_UNLOCK (provider_lock); - - g_mutex_lock (lock); - - /* First, get the credentials from the keyring */ - identity = goa_oauth2_based_get_identity (goa_object_peek_oauth2_based (object)); - credentials = goa_backend_provider_lookup_credentials_sync (GOA_BACKEND_PROVIDER (provider), - identity, - cancellable, - error); - if (credentials == NULL) - { - if (error != NULL) - { - g_prefix_error (error, _("Credentials not found in keyring (%s, %d): "), - g_quark_to_string ((*error)->domain), (*error)->code); - (*error)->domain = GOA_ERROR; - (*error)->code = GOA_ERROR_NOT_AUTHORIZED; - } - goto out; - } - - g_variant_iter_init (&iter, credentials); - while (g_variant_iter_next (&iter, "{&sv}", &key, &value)) - { - if (g_strcmp0 (key, "access_token") == 0) - access_token = g_variant_dup_string (value, NULL); - else if (g_strcmp0 (key, "access_token_expires_at") == 0) - access_token_expires_in = abs_usec_to_duration (g_variant_get_int64 (value)); - else if (g_strcmp0 (key, "refresh_token") == 0) - refresh_token = g_variant_dup_string (value, NULL); - else if (g_strcmp0 (key, "authorization_code") == 0) - authorization_code = g_variant_dup_string (value, NULL); - g_variant_unref (value); - } - - if (access_token == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_NOT_AUTHORIZED, - _("Credentials does not contain access_token")); - goto out; - } - - /* if we can't refresh the token, just return it no matter what */ - if (refresh_token == NULL) - { - g_debug ("Returning locally cached credentials that cannot be refreshed"); - success = TRUE; - goto out; - } - - /* If access_token is still "fresh enough" (e.g. more than ten - * minutes of life left in it), just return it unless we've been - * asked to forcibly refresh it - */ - if (!force_refresh && access_token_expires_in > 10*60) - { - g_debug ("Returning locally cached credentials (expires in %d seconds)", access_token_expires_in); - success = TRUE; - goto out; - } - - g_debug ("Refreshing locally cached credentials (expires in %d seconds, force_refresh=%d)", access_token_expires_in, force_refresh); - - /* Otherwise, refresh it */ - old_refresh_token = refresh_token; refresh_token = NULL; - g_free (access_token); access_token = NULL; - access_token = get_tokens_sync (provider, - authorization_code, - old_refresh_token, - &refresh_token, - &access_token_expires_in, - cancellable, - error); - if (access_token == NULL) - { - if (error != NULL) - { - g_prefix_error (error, _("Failed to refresh access token (%s, %d): "), - g_quark_to_string ((*error)->domain), (*error)->code); - (*error)->code = is_authorization_error (*error) ? GOA_ERROR_NOT_AUTHORIZED : GOA_ERROR_FAILED; - (*error)->domain = GOA_ERROR; - } - goto out; - } - - /* It's not a sure thing we get a new refresh_token, so use our old - * old if we didn't get a new one - */ - if (refresh_token == NULL) - { - refresh_token = old_refresh_token; - old_refresh_token = NULL; - } - - /* Good. Now update the keyring with the refreshed credentials */ - g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); - g_variant_builder_add (&builder, "{sv}", "authorization_code", g_variant_new_string (authorization_code)); - g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token)); - if (access_token_expires_in > 0) - g_variant_builder_add (&builder, "{sv}", "access_token_expires_at", - g_variant_new_int64 (duration_to_abs_usec (access_token_expires_in))); - if (refresh_token != NULL) - g_variant_builder_add (&builder, "{sv}", "refresh_token", g_variant_new_string (refresh_token)); - - identity = goa_oauth2_based_get_identity (goa_object_peek_oauth2_based (object)); - if (!goa_backend_provider_store_credentials_sync (GOA_BACKEND_PROVIDER (provider), - identity, - g_variant_builder_end (&builder), - cancellable, - error)) - { - if (error != NULL) - { - g_prefix_error (error, _("Error storing credentials in keyring (%s, %d): "), - g_quark_to_string ((*error)->domain), (*error)->code); - (*error)->domain = GOA_ERROR; - (*error)->code = GOA_ERROR_NOT_AUTHORIZED; - } - goto out; - } - - success = TRUE; - - out: - if (success) - { - ret = access_token; access_token = NULL; - g_assert (ret != NULL); - if (out_access_token_expires_in != NULL) - *out_access_token_expires_in = access_token_expires_in; - } - g_free (authorization_code); - g_free (access_token); - g_free (refresh_token); - g_free (old_refresh_token); - if (credentials != NULL) - g_variant_unref (credentials); - - g_mutex_unlock (lock); - - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean on_handle_get_access_token (GoaOAuth2Based *object, - GDBusMethodInvocation *invocation, - gpointer user_data); - -static gboolean -goa_backend_oauth2_provider_build_object (GoaBackendProvider *provider, - GoaObjectSkeleton *object, - GKeyFile *key_file, - const gchar *group, - GError **error) -{ - GoaOAuth2Based *oauth2_based; - gchar *identity; - - identity = NULL; - - oauth2_based = goa_object_get_oauth2_based (GOA_OBJECT (object)); - if (oauth2_based != NULL) - goto out; - - oauth2_based = goa_oauth2_based_skeleton_new (); - /* Ensure D-Bus method invocations run in their own thread */ - g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (oauth2_based), - G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); - goa_object_skeleton_set_oauth2_based (object, oauth2_based); - g_signal_connect (oauth2_based, - "handle-get-access-token", - G_CALLBACK (on_handle_get_access_token), - NULL); - - identity = g_key_file_get_string (key_file, group, "Identity", NULL); - if (identity == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - "No Identity key"); - goto out; - } - goa_oauth2_based_set_identity (oauth2_based, identity); - - out: - g_object_unref (oauth2_based); - g_free (identity); - return TRUE; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean -goa_backend_oauth2_provider_ensure_credentials_sync (GoaBackendProvider *_provider, - GoaObject *object, - gint *out_expires_in, - GCancellable *cancellable, - GError **error) -{ - GoaBackendOAuth2Provider *provider = GOA_BACKEND_OAUTH2_PROVIDER (_provider); - gboolean ret; - gchar *access_token; - gint access_token_expires_in; - gchar *identity; - gboolean force_refresh; - - ret = FALSE; - access_token = NULL; - identity = NULL; - force_refresh = FALSE; - - again: - access_token = goa_backend_oauth2_provider_get_access_token_sync (provider, - object, - force_refresh, - &access_token_expires_in, - cancellable, - error); - if (access_token == NULL) - goto out; - - identity = goa_backend_oauth2_provider_get_identity_sync (provider, - access_token, - NULL, /* out_name */ - cancellable, - error); - if (identity == NULL) - { - /* OK, try again, with forcing the locally cached credentials to be refreshed */ - if (!force_refresh) - { - force_refresh = TRUE; - g_free (access_token); access_token = NULL; - goto again; - } - else - { - goto out; - } - } - - /* TODO: maybe check with the identity we have */ - ret = TRUE; - if (out_expires_in != NULL) - *out_expires_in = access_token_expires_in; - - out: - g_free (identity); - g_free (access_token); - return ret; -} - - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -goa_backend_oauth2_provider_init (GoaBackendOAuth2Provider *client) -{ -} - -static void -goa_backend_oauth2_provider_class_init (GoaBackendOAuth2ProviderClass *klass) -{ - GoaBackendProviderClass *provider_class; - - provider_class = GOA_BACKEND_PROVIDER_CLASS (klass); - provider_class->add_account = goa_backend_oauth2_provider_add_account; - provider_class->refresh_account = goa_backend_oauth2_provider_refresh_account; - provider_class->build_object = goa_backend_oauth2_provider_build_object; - provider_class->ensure_credentials_sync = goa_backend_oauth2_provider_ensure_credentials_sync; - - klass->build_authorization_uri = goa_backend_oauth2_provider_build_authorization_uri_default; - klass->get_use_external_browser = goa_backend_oauth2_provider_get_use_external_browser_default; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -/* runs in a thread dedicated to handling @invocation */ -static gboolean -on_handle_get_access_token (GoaOAuth2Based *interface, - GDBusMethodInvocation *invocation, - gpointer user_data) -{ - GoaObject *object; - GoaAccount *account; - GoaBackendProvider *provider; - GError *error; - gchar *access_token; - gint access_token_expires_in; - - /* TODO: maybe log what app is requesting access */ - - access_token = NULL; - - object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (interface))); - account = goa_object_peek_account (object); - provider = goa_backend_provider_get_for_provider_type (goa_account_get_provider_type (account)); - - error = NULL; - access_token = goa_backend_oauth2_provider_get_access_token_sync (GOA_BACKEND_OAUTH2_PROVIDER (provider), - object, - FALSE, /* force_refresh */ - &access_token_expires_in, - NULL, /* GCancellable* */ - &error); - if (access_token == NULL) - { - g_dbus_method_invocation_return_gerror (invocation, error); - g_error_free (error); - } - else - { - goa_oauth2_based_complete_get_access_token (interface, - invocation, - access_token, - access_token_expires_in); - } - g_free (access_token); - g_object_unref (provider); - return TRUE; /* invocation was handled */ -} diff --git a/src/goa/goabackendoauth2provider.h b/src/goa/goabackendoauth2provider.h deleted file mode 100644 index f1f0426..0000000 --- a/src/goa/goabackendoauth2provider.h +++ /dev/null @@ -1,133 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __GOA_BACKEND_OAUTH2_PROVIDER_H__ -#define __GOA_BACKEND_OAUTH2_PROVIDER_H__ - -#include - -G_BEGIN_DECLS - -#define GOA_TYPE_BACKEND_OAUTH2_PROVIDER (goa_backend_oauth2_provider_get_type ()) -#define GOA_BACKEND_OAUTH2_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_OAUTH2_PROVIDER, GoaBackendOAuth2Provider)) -#define GOA_BACKEND_OAUTH2_PROVIDER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOA_TYPE_BACKEND_OAUTH2_PROVIDER, GoaBackendOAuth2ProviderClass)) -#define GOA_BACKEND_OAUTH2_PROVIDER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOA_TYPE_BACKEND_OAUTH2_PROVIDER, GoaBackendOAuth2ProviderClass)) -#define GOA_IS_BACKEND_OAUTH2_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_OAUTH2_PROVIDER)) - -#define GOA_IS_BACKEND_OAUTH2_PROVIDER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOA_TYPE_BACKEND_OAUTH2_PROVIDER)) - -typedef struct _GoaBackendOAuth2ProviderClass GoaBackendOAuth2ProviderClass; -typedef struct _GoaBackendOAuth2ProviderPrivate GoaBackendOAuth2ProviderPrivate; - -/** - * GoaBackendOAuth2Provider: - * - * The #GoaBackendOAuth2Provider structure contains only private data and should - * only be accessed using the provided API. - */ -struct _GoaBackendOAuth2Provider -{ - /*< private >*/ - GoaBackendProvider parent_instance; - GoaBackendOAuth2ProviderPrivate *priv; -}; - -/** - * GoaBackendOAuth2ProviderClass: - * @parent_class: The parent class. - * @get_authorization_uri: Virtual function for goa_backend_oauth2_provider_get_authorization_uri(). - * @get_token_uri: Virtual function for goa_backend_oauth2_provider_get_token_uri(). - * @get_redirect_uri: Virtual function for goa_backend_oauth2_provider_get_redirect_uri(). - * @get_scope: Virtual function for goa_backend_oauth2_provider_get_scope(). - * @get_client_id: Virtual function for goa_backend_oauth2_provider_get_client_id(). - * @get_client_secret: Virtual function for goa_backend_oauth2_provider_get_client_secret(). - * @get_identity_sync: Virtual function for goa_backend_oauth2_provider_get_identity_sync(). - * @build_authorization_uri: Virtual function for goa_backend_oauth2_provider_build_authorization_uri(). - * @get_use_external_browser: Virtual function for goa_backend_oauth2_provider_get_use_external_browser(). - * - * Class structure for #GoaBackendOAuth2Provider. - */ -struct _GoaBackendOAuth2ProviderClass -{ - GoaBackendProviderClass parent_class; - - /* pure virtual */ - const gchar *(*get_authorization_uri) (GoaBackendOAuth2Provider *provider); - const gchar *(*get_token_uri) (GoaBackendOAuth2Provider *provider); - const gchar *(*get_redirect_uri) (GoaBackendOAuth2Provider *provider); - const gchar *(*get_scope) (GoaBackendOAuth2Provider *provider); - const gchar *(*get_client_id) (GoaBackendOAuth2Provider *provider); - const gchar *(*get_client_secret) (GoaBackendOAuth2Provider *provider); - gchar *(*get_identity_sync) (GoaBackendOAuth2Provider *provider, - const gchar *access_token, - gchar **out_name, - GCancellable *cancellable, - GError **error); - - /* virtual but with default implementation */ - gchar *(*build_authorization_uri) (GoaBackendOAuth2Provider *provider, - const gchar *authorization_uri, - const gchar *escaped_redirect_uri, - const gchar *escaped_client_id, - const gchar *escaped_scope); - gboolean (*get_use_external_browser) (GoaBackendOAuth2Provider *provider); - - /*< private >*/ - /* Padding for future expansion */ - gpointer goa_reserved[32]; -}; - -GType goa_backend_oauth2_provider_get_type (void) G_GNUC_CONST; -const gchar *goa_backend_oauth2_provider_get_authorization_uri (GoaBackendOAuth2Provider *provider); -const gchar *goa_backend_oauth2_provider_get_token_uri (GoaBackendOAuth2Provider *provider); -const gchar *goa_backend_oauth2_provider_get_redirect_uri (GoaBackendOAuth2Provider *provider); -const gchar *goa_backend_oauth2_provider_get_scope (GoaBackendOAuth2Provider *provider); -const gchar *goa_backend_oauth2_provider_get_client_id (GoaBackendOAuth2Provider *provider); -const gchar *goa_backend_oauth2_provider_get_client_secret (GoaBackendOAuth2Provider *provider); -gchar *goa_backend_oauth2_provider_get_identity_sync (GoaBackendOAuth2Provider *provider, - const gchar *access_token, - gchar **out_name, - GCancellable *cancellable, - GError **error); -gchar *goa_backend_oauth2_provider_get_access_token_sync (GoaBackendOAuth2Provider *provider, - GoaObject *object, - gboolean force_refresh, - gint *out_access_token_expires_in, - GCancellable *cancellable, - GError **error); - -/* ---------------------------------------------------------------------------------------------------- */ - -gchar *goa_backend_oauth2_provider_build_authorization_uri (GoaBackendOAuth2Provider *provider, - const gchar *authorization_uri, - const gchar *escaped_redirect_uri, - const gchar *escaped_client_id, - const gchar *escaped_scope); -gboolean goa_backend_oauth2_provider_get_use_external_browser (GoaBackendOAuth2Provider *provider); - -G_END_DECLS - -#endif /* __GOA_BACKEND_OAUTH2_PROVIDER_H__ */ diff --git a/src/goa/goabackendoauthprovider.c b/src/goa/goabackendoauthprovider.c deleted file mode 100644 index ae1bfb0..0000000 --- a/src/goa/goabackendoauthprovider.c +++ /dev/null @@ -1,1593 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#include "config.h" -#include -#include - -#include -#include -#include - -#include "goabackendprovider.h" -#include "goabackendoauthprovider.h" - -/** - * SECTION:goabackendoauthprovider - * @title: GoaBackendOAuthProvider - * @short_description: Abstract base class for OAuth 1.0a providers - * - * #GoaBackendOAuthProvider is an abstract base class for OAuth 1.0a - * compliant implementations as defined by RFC - * 5849. Additionally, the code works with providers - * implementing OAuth - * Session 1.0 Draft 1 for refreshing access tokens. - * - * Subclasses must implement - * #GoaBackendOAuthProviderClass.get_consumer_key, - * #GoaBackendOAuthProviderClass.get_consumer_secret, - * #GoaBackendOAuthProviderClass.get_request_uri, - * #GoaBackendOAuthProviderClass.get_authorization_uri, - * #GoaBackendOAuthProviderClass.get_token_uri, - * #GoaBackendOAuthProviderClass.get_callback_uri and - * #GoaBackendOAuthProviderClass.get_identity_sync methods. - * - * Additionally, the - * #GoaBackendProviderClass.get_provider_type, - * #GoaBackendProviderClass.get_name, - * #GoaBackendProviderClass.build_object (this should chain up to its - * parent class) methods must be implemented. - * - * Note that the #GoaBackendProviderClass.add_account, - * #GoaBackendProviderClass.refresh_account and - * #GoaBackendProviderClass.ensure_credentials_sync methods do not - * need to be implemented - this type implements these methods. - */ - -G_LOCK_DEFINE_STATIC (provider_lock); - -G_DEFINE_ABSTRACT_TYPE (GoaBackendOAuthProvider, goa_backend_oauth_provider, GOA_TYPE_BACKEND_PROVIDER); - -static gboolean -is_authorization_error (GError *error) -{ - gboolean ret; - - g_return_val_if_fail (error != NULL, FALSE); - - ret = FALSE; - if (error->domain == REST_PROXY_ERROR || error->domain == SOUP_HTTP_ERROR) - { - if (SOUP_STATUS_IS_CLIENT_ERROR (error->code)) - ret = TRUE; - } - return ret; -} - -G_GNUC_UNUSED static void -print_header (const gchar *name, - const gchar *value, - gpointer user_data) -{ - g_print ("header: `%s' -> `%s'\n", name, value); -} - -G_GNUC_UNUSED static void -_print_response (RestProxyCall *call) -{ - GHashTable *headers; - GHashTable *form; - GHashTableIter iter; - const gchar *key; - const gchar *value; - const gchar *payload; - - headers = rest_proxy_call_get_response_headers (call); - if (headers != NULL) - { - g_hash_table_iter_init (&iter, headers); - while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) - g_print ("Header %s: %s\n", key, value); - g_hash_table_unref (headers); - } - - payload = rest_proxy_call_get_payload (call); - if (payload != NULL) - { - form = soup_form_decode (payload); - if (form != NULL) - { - g_hash_table_iter_init (&iter, form); - while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) - g_print ("Form %s: %s\n", key, value); - g_hash_table_unref (form); - } - } -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean -goa_backend_oauth_provider_get_use_external_browser_default (GoaBackendOAuthProvider *provider) -{ - return FALSE; -} - -/** - * goa_backend_oauth_provider_get_use_external_browser: - * @provider: A #GoaBackendOAuthProvider. - * - * Returns whether an external browser (the users default browser) - * should be used for user interaction. - * - * If an external browser is used, then the dialogs presented in - * goa_backend_provider_add_account() and - * goa_backend_provider_refresh_account() will show a simple "Paste - * authorization code here" instructions along with an entry and - * button. - * - * This is a virtual method where the default implementation returns - * %FALSE. - * - * Returns: %TRUE if the user interaction should happen in an external - * browser, %FALSE to use an embedded browser widget. - */ -gboolean -goa_backend_oauth_provider_get_use_external_browser (GoaBackendOAuthProvider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), FALSE); - return GOA_BACKEND_OAUTH_PROVIDER_GET_CLASS (provider)->get_use_external_browser (provider); -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gchar * -goa_backend_oauth_provider_build_authorization_uri_default (GoaBackendOAuthProvider *provider, - const gchar *authorization_uri, - const gchar *escaped_oauth_token) -{ - return g_strdup_printf ("%s" - "?oauth_token=%s", - authorization_uri, - escaped_oauth_token); -} - -/** - * goa_backend_oauth_provider_build_authorization_uri: - * @provider: A #GoaBackendOAuthProvider. - * @authorization_uri: An authorization URI. - * @escaped_oauth_token: An escaped oauth token. - * - * Builds the URI that can be opened in a web browser (or embedded web - * browser widget) to start authenticating an user. - * - * The default implementation just returns the expected URI - * (e.g. http://example.com/dialog/oauth?auth_token=1234567890) - * - override (and chain up) if you e.g. need to to pass additional - * parameters. - * - * The @authorization_uri parameter originate from the result of the - * the goa_backend_oauth_provider_get_authorization_uri() method. The - * @escaped_oauth_token parameter is the temporary credentials identifier - * escaped using g_uri_escape_string(). - * - * Returns: (transfer full): An authorization URI that must be freed with g_free(). - */ -gchar * -goa_backend_oauth_provider_build_authorization_uri (GoaBackendOAuthProvider *provider, - const gchar *authorization_uri, - const gchar *escaped_oauth_token) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL); - g_return_val_if_fail (authorization_uri != NULL, NULL); - g_return_val_if_fail (escaped_oauth_token != NULL, NULL); - return GOA_BACKEND_OAUTH_PROVIDER_GET_CLASS (provider)->build_authorization_uri (provider, - authorization_uri, - escaped_oauth_token); -} - -/** - * goa_backend_oauth_provider_get_consumer_key: - * @provider: A #GoaBackendOAuthProvider. - * - * Gets the consumer key identifying the client. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider - do not free. - */ -const gchar * -goa_backend_oauth_provider_get_consumer_key (GoaBackendOAuthProvider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL); - return GOA_BACKEND_OAUTH_PROVIDER_GET_CLASS (provider)->get_consumer_key (provider); -} - -/** - * goa_backend_oauth_provider_get_consumer_secret: - * @provider: A #GoaBackendOAuthProvider. - * - * Gets the consumer secret identifying the client. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider - do not free. - */ -const gchar * -goa_backend_oauth_provider_get_consumer_secret (GoaBackendOAuthProvider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL); - return GOA_BACKEND_OAUTH_PROVIDER_GET_CLASS (provider)->get_consumer_secret (provider); -} - -/** - * goa_backend_oauth_provider_get_request_uri: - * @provider: A #GoaBackendOAuthProvider. - * - * Gets the request uri. - * - * http://tools.ietf.org/html/rfc5849#section-2.1 - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider - do not free. - */ -const gchar * -goa_backend_oauth_provider_get_request_uri (GoaBackendOAuthProvider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL); - return GOA_BACKEND_OAUTH_PROVIDER_GET_CLASS (provider)->get_request_uri (provider); -} - -/** - * goa_backend_oauth_provider_get_request_uri_params: - * @provider: A #GoaBackendOAuthProvider. - * - * Gets additional parameters for the request URI. - * - * http://tools.ietf.org/html/rfc5849#section-2.1 - * - * This is a virtual method where the default implementation returns - * %NULL. - * - * Returns: (transfer full): %NULL (for no parameters) or a - * %NULL-terminated array of (key, value) pairs that will be added to - * the URI. The caller will free the returned value with g_strfreev(). - */ -gchar ** -goa_backend_oauth_provider_get_request_uri_params (GoaBackendOAuthProvider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL); - return GOA_BACKEND_OAUTH_PROVIDER_GET_CLASS (provider)->get_request_uri_params (provider); -} - -static gchar ** -goa_backend_oauth_provider_get_request_uri_params_default (GoaBackendOAuthProvider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL); - return NULL; -} - -/** - * goa_backend_oauth_provider_get_authorization_uri: - * @provider: A #GoaBackendOAuthProvider. - * - * Gets the authorization uri. - * - * http://tools.ietf.org/html/rfc5849#section-2.2 - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider - do not free. - */ -const gchar * -goa_backend_oauth_provider_get_authorization_uri (GoaBackendOAuthProvider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL); - return GOA_BACKEND_OAUTH_PROVIDER_GET_CLASS (provider)->get_authorization_uri (provider); -} - -/** - * goa_backend_oauth_provider_get_token_uri: - * @provider: A #GoaBackendOAuthProvider. - * - * Gets the token uri. - * - * http://tools.ietf.org/html/rfc5849#section-2.3 - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider - do not free. - */ -const gchar * -goa_backend_oauth_provider_get_token_uri (GoaBackendOAuthProvider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL); - return GOA_BACKEND_OAUTH_PROVIDER_GET_CLASS (provider)->get_token_uri (provider); -} - -/** - * goa_backend_oauth_provider_get_callback_uri: - * @provider: A #GoaBackendOAuthProvider. - * - * Gets the callback uri. - * - * http://tools.ietf.org/html/rfc5849#section-2.1 - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider - do not free. - */ -const gchar * -goa_backend_oauth_provider_get_callback_uri (GoaBackendOAuthProvider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL); - return GOA_BACKEND_OAUTH_PROVIDER_GET_CLASS (provider)->get_callback_uri (provider); -} - -/** - * goa_backend_oauth_provider_get_identity_sync: - * @provider: A #GoaBackendOAuthProvider. - * @access_token: A valid OAuth 1.0 access token. - * @access_token_secret: The valid secret for @access_token. - * @out_name: (out): Return location for name or %NULL. - * @cancellable: (allow-none): A #GCancellable or %NULL. - * @error: Return location for error or %NULL. - * - * Method that returns the identity corresponding to @access_token and - * @access_token_secret. - * - * The identity is needed because all authentication happens out of - * band. The only requirement is that the returned identity is unique - * - for example, for #GoaBackendGoogleProvider the returned identity - * is the email address, for #GoaBackendFacebookProvider it's the user - * name. In addition to the identity, an implementation also returns a - * name in @out_name that is more suitable for - * presentation (the identity could be a GUID for example) and doesn't - * have to be unique. - * - * The calling thread is blocked while the identity is obtained. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: The identity or %NULL if error is set. The returned string - * must be freed with g_free(). - */ -gchar * -goa_backend_oauth_provider_get_identity_sync (GoaBackendOAuthProvider *provider, - const gchar *access_token, - const gchar *access_token_secret, - gchar **out_name, - GCancellable *cancellable, - GError **error) -{ - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL); - g_return_val_if_fail (access_token != NULL, NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); - g_return_val_if_fail (error == NULL || *error == NULL, NULL); - return GOA_BACKEND_OAUTH_PROVIDER_GET_CLASS (provider)->get_identity_sync (provider, access_token, access_token_secret, out_name, cancellable, error); -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gchar * -get_tokens_sync (GoaBackendOAuthProvider *provider, - const gchar *token, - const gchar *token_secret, - const gchar *session_handle, /* may be NULL */ - const gchar *verifier, /* may be NULL */ - gchar **out_access_token_secret, - gint *out_access_token_expires_in, - gchar **out_session_handle, - gint *out_session_handle_expires_in, - GCancellable *cancellable, - GError **error) -{ - RestProxy *proxy; - RestProxyCall *call; - gchar *ret; - guint status_code; - GHashTable *f; - const gchar *expires_in_str; - gchar *ret_access_token; - gchar *ret_access_token_secret; - gint ret_access_token_expires_in; - gchar *ret_session_handle; - gint ret_session_handle_expires_in; - - ret = NULL; - ret_access_token = NULL; - ret_access_token_secret = NULL; - ret_access_token_expires_in = 0; - ret_session_handle = NULL; - ret_session_handle_expires_in = 0; - - proxy = oauth_proxy_new (goa_backend_oauth_provider_get_consumer_key (provider), - goa_backend_oauth_provider_get_consumer_secret (provider), - goa_backend_oauth_provider_get_token_uri (provider), - FALSE); - oauth_proxy_set_token (OAUTH_PROXY (proxy), token); - oauth_proxy_set_token_secret (OAUTH_PROXY (proxy), token_secret); - call = rest_proxy_new_call (proxy); - rest_proxy_call_set_method (call, "POST"); - if (verifier != NULL) - rest_proxy_call_add_param (call, "oauth_verifier", verifier); - if (session_handle != NULL) - rest_proxy_call_add_param (call, "oauth_session_handle", session_handle); - /* TODO: cancellable support? */ - if (!rest_proxy_call_sync (call, error)) - goto out; - - status_code = rest_proxy_call_get_status_code (call); - if (status_code != 200) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Expected status 200 when requesting access token, instead got status %d (%s)"), - status_code, - rest_proxy_call_get_status_message (call)); - goto out; - } - - f = soup_form_decode (rest_proxy_call_get_payload (call)); - ret_access_token = g_strdup (g_hash_table_lookup (f, "oauth_token")); - ret_access_token_secret = g_strdup (g_hash_table_lookup (f, "oauth_token_secret")); - ret_session_handle = g_strdup (g_hash_table_lookup (f, "oauth_session_handle")); - expires_in_str = g_hash_table_lookup (f, "oauth_expires_in"); - if (expires_in_str != NULL) - ret_access_token_expires_in = atoi (expires_in_str); - expires_in_str = g_hash_table_lookup (f, "oauth_authorization_expires_in"); - if (expires_in_str != NULL) - ret_session_handle_expires_in = atoi (expires_in_str); - g_hash_table_unref (f); - - if (ret_access_token == NULL || ret_access_token_secret == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Missing access_token or access_token_secret headers in response")); - goto out; - } - - ret = ret_access_token; ret_access_token = NULL; - if (out_access_token_secret != NULL) - { - *out_access_token_secret = ret_access_token_secret; - ret_access_token_secret = NULL; - } - if (out_access_token_expires_in != NULL) - *out_access_token_expires_in = ret_access_token_expires_in; - if (out_session_handle != NULL) - { - *out_session_handle = ret_session_handle; - ret_session_handle = NULL; - } - if (out_session_handle_expires_in != NULL) - *out_session_handle_expires_in = ret_session_handle_expires_in; - - out: - g_free (ret_access_token); - g_free (ret_access_token_secret); - g_free (ret_session_handle); - if (call != NULL) - g_object_unref (call); - if (proxy != NULL) - g_object_unref (proxy); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -typedef struct -{ - GoaBackendOAuthProvider *provider; - GtkDialog *dialog; - GError *error; - GMainLoop *loop; - - gchar *oauth_verifier; - - gchar *identity; - gchar *name; - - gchar *request_token; - gchar *request_token_secret; - gchar *access_token; - gchar *access_token_secret; - gint access_token_expires_in; - gchar *session_handle; - gint session_handle_expires_in; -} IdentifyData; - -static gboolean -on_web_view_navigation_policy_decision_requested (WebKitWebView *webView, - WebKitWebFrame *frame, - WebKitNetworkRequest *request, - WebKitWebNavigationAction *navigation_action, - WebKitWebPolicyDecision *policy_decision, - gpointer user_data) -{ - IdentifyData *data = user_data; - const gchar *redirect_uri; - const gchar *requested_uri; - - /* TODO: use oauth_proxy_extract_access_token() */ - - requested_uri = webkit_network_request_get_uri (request); - redirect_uri = goa_backend_oauth_provider_get_callback_uri (data->provider); - if (g_str_has_prefix (requested_uri, redirect_uri)) - { - SoupMessage *message; - SoupURI *uri; - GHashTable *key_value_pairs; - - message = webkit_network_request_get_message (request); - uri = soup_message_get_uri (message); - key_value_pairs = soup_form_decode (uri->query); - - /* TODO: error handling? */ - data->oauth_verifier = g_strdup (g_hash_table_lookup (key_value_pairs, "oauth_verifier")); - if (data->oauth_verifier != NULL) - { - gtk_dialog_response (data->dialog, GTK_RESPONSE_OK); - } - g_hash_table_unref (key_value_pairs); - webkit_web_policy_decision_ignore (policy_decision); - return TRUE; /* ignore the request */ - } - else - { - return FALSE; /* make default behavior apply */ - } -} - -static void -on_entry_changed (GtkEditable *editable, - gpointer user_data) -{ - IdentifyData *data = user_data; - gboolean sensitive; - - g_free (data->oauth_verifier); - data->oauth_verifier = g_strdup (gtk_entry_get_text (GTK_ENTRY (editable))); - sensitive = data->oauth_verifier != NULL && (strlen (data->oauth_verifier) > 0); - gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, sensitive); -} - -static gboolean -get_tokens_and_identity (GoaBackendOAuthProvider *provider, - GtkDialog *dialog, - GtkBox *vbox, - gchar **out_access_token, - gchar **out_access_token_secret, - gint *out_access_token_expires_in, - gchar **out_session_handle, - gint *out_session_handle_expires_in, - gchar **out_identity, - gchar **out_name, - GError **error) -{ - gboolean ret; - gchar *url; - IdentifyData data; - gchar *escaped_request_token; - RestProxy *proxy; - RestProxyCall *call; - GHashTable *f; - gboolean use_external_browser; - gchar **request_params; - guint n; - - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), FALSE); - g_return_val_if_fail (GTK_IS_DIALOG (dialog), FALSE); - g_return_val_if_fail (GTK_IS_BOX (vbox), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - ret = FALSE; - escaped_request_token = NULL; - proxy = NULL; - call = NULL; - url = NULL; - request_params = NULL; - - use_external_browser = goa_backend_oauth_provider_get_use_external_browser (provider); - - /* TODO: check with NM whether we're online, if not - return error */ - - memset (&data, '\0', sizeof (IdentifyData)); - data.provider = provider; - data.loop = g_main_loop_new (NULL, FALSE); - - /* TODO: run in worker thread */ - proxy = oauth_proxy_new (goa_backend_oauth_provider_get_consumer_key (provider), - goa_backend_oauth_provider_get_consumer_secret (provider), - goa_backend_oauth_provider_get_request_uri (provider), FALSE); - call = rest_proxy_new_call (proxy); - rest_proxy_call_set_method (call, "POST"); - if (use_external_browser) - rest_proxy_call_add_param (call, "oauth_callback", "oob"); - else - rest_proxy_call_add_param (call, "oauth_callback", goa_backend_oauth_provider_get_callback_uri (provider)); - - request_params = goa_backend_oauth_provider_get_request_uri_params (provider); - if (request_params != NULL) - { - g_assert (g_strv_length (request_params) % 2 == 0); - for (n = 0; request_params[n] != NULL; n += 2) - rest_proxy_call_add_param (call, request_params[n], request_params[n+1]); - } - if (!rest_proxy_call_sync (call, &data.error)) - { - g_prefix_error (&data.error, _("Error getting a Request Token: ")); - goto out; - } - if (rest_proxy_call_get_status_code (call) != 200) - { - g_set_error (&data.error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Expected 200 for getting a Request Token, got %d (%s)"), - rest_proxy_call_get_status_code (call), - rest_proxy_call_get_status_message (call)); - goto out; - } - f = soup_form_decode (rest_proxy_call_get_payload (call)); - data.request_token = g_strdup (g_hash_table_lookup (f, "oauth_token")); - data.request_token_secret = g_strdup (g_hash_table_lookup (f, "oauth_token_secret")); - g_hash_table_unref (f); - if (data.request_token == NULL || data.request_token_secret == NULL) - { - g_set_error (&data.error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Missing request_token or request_token_secret headers in response")); - goto out; - } - - escaped_request_token = g_uri_escape_string (data.request_token, NULL, TRUE); - url = goa_backend_oauth_provider_build_authorization_uri (provider, - goa_backend_oauth_provider_get_authorization_uri (provider), - escaped_request_token); - if (use_external_browser) - { - GtkWidget *label; - GtkWidget *entry; - gchar *escaped_url; - gchar *markup; - - escaped_url = g_markup_escape_text (url, -1); - markup = g_strdup_printf (_("Paste token obtained from the authorization page:"), - escaped_url); - g_free (escaped_url); - - label = gtk_label_new (NULL); - gtk_label_set_markup (GTK_LABEL (label), markup); - g_free (markup); - gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0); - entry = gtk_entry_new (); - gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); - gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, TRUE, 0); - gtk_widget_grab_focus (entry); - gtk_widget_show_all (GTK_WIDGET (vbox)); - - gtk_dialog_add_button (dialog, GTK_STOCK_OK, GTK_RESPONSE_OK); - gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK); - gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_OK, FALSE); - g_signal_connect (entry, "changed", G_CALLBACK (on_entry_changed), &data); - - if (!gtk_show_uri (NULL, - url, - GDK_CURRENT_TIME, - &data.error)) - { - goto out; - } - } - else - { - GtkWidget *scrolled_window; - GtkWidget *web_view; - SoupSession *webkit_soup_session; - SoupCookieJar *cookie_jar; - - /* Ensure we use an empty non-persistent cookie to avoid login - * credentials being reused... - */ - webkit_soup_session = webkit_get_default_session (); - soup_session_remove_feature_by_type (webkit_soup_session, SOUP_TYPE_COOKIE_JAR); - cookie_jar = soup_cookie_jar_new (); - soup_session_add_feature (webkit_soup_session, SOUP_SESSION_FEATURE (cookie_jar)); - g_object_unref (cookie_jar); - - /* TODO: we might need to add some more web browser UI to make this - * work... - */ - web_view = webkit_web_view_new (); - webkit_web_view_load_uri (WEBKIT_WEB_VIEW (web_view), url); - g_signal_connect (web_view, - "navigation-policy-decision-requested", - G_CALLBACK (on_web_view_navigation_policy_decision_requested), - &data); - - scrolled_window = gtk_scrolled_window_new (NULL, NULL); - gtk_widget_set_size_request (scrolled_window, 500, 400); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), - GTK_POLICY_AUTOMATIC, - GTK_POLICY_AUTOMATIC); - gtk_container_add (GTK_CONTAINER (scrolled_window), web_view); - gtk_container_add (GTK_CONTAINER (vbox), scrolled_window); - - gtk_widget_show_all (scrolled_window); - } - data.dialog = dialog; - gtk_dialog_run (GTK_DIALOG (dialog)); - if (data.oauth_verifier == NULL) - { - if (data.error == NULL) - { - g_set_error (&data.error, - GOA_ERROR, - GOA_ERROR_DIALOG_DISMISSED, - _("Dialog was dismissed")); - } - goto out; - } - g_assert (data.error == NULL); - - /* OK, we are done interacting with the user ... but before we can - * make up our mind, there are two more RPC calls to make and these - * call may take some time. So hide the dialog immediately. - */ - gtk_widget_hide (GTK_WIDGET (dialog)); - - g_print ("woot verifier is %s\n", data.oauth_verifier); - - /* OK, we now have the request token... we can exchange that for a - * (short-lived) access token and session_handle (used to refresh the - * access token).. - */ - - /* TODO: run in worker thread */ - data.access_token = get_tokens_sync (provider, - data.request_token, - data.request_token_secret, - NULL, /* session_handle */ - data.oauth_verifier, - &data.access_token_secret, - &data.access_token_expires_in, - &data.session_handle, - &data.session_handle_expires_in, - NULL, /* GCancellable */ - &data.error); - if (data.access_token == NULL) - { - g_prefix_error (&data.error, _("Error getting an Access Token: ")); - goto out; - } - - /* TODO: run in worker thread */ - data.identity = goa_backend_oauth_provider_get_identity_sync (provider, - data.access_token, - data.access_token_secret, - &data.name, - NULL, /* TODO: GCancellable */ - &data.error); - if (data.identity == NULL) - { - g_prefix_error (&data.error, _("Error getting identity: ")); - goto out; - } - - ret = TRUE; - - out: - if (call != NULL) - g_object_unref (call); - - if (ret) - { - g_warn_if_fail (data.error == NULL); - if (out_access_token != NULL) - *out_access_token = g_strdup (data.access_token); - if (out_access_token_secret != NULL) - *out_access_token_secret = g_strdup (data.access_token_secret); - if (out_access_token_expires_in != NULL) - *out_access_token_expires_in = data.access_token_expires_in; - if (out_session_handle != NULL) - *out_session_handle = g_strdup (data.session_handle); - if (out_session_handle_expires_in != NULL) - *out_session_handle_expires_in = data.session_handle_expires_in; - if (out_identity != NULL) - *out_identity = g_strdup (data.identity); - if (out_name != NULL) - *out_name = g_strdup (data.name); - } - else - { - g_warn_if_fail (data.error != NULL); - g_propagate_error (error, data.error); - } - - g_free (data.name); - g_free (data.identity); - g_free (url); - - g_free (data.oauth_verifier); - if (data.loop != NULL) - g_main_loop_unref (data.loop); - g_free (data.access_token); - g_free (data.access_token_secret); - g_free (escaped_request_token); - - g_free (data.request_token); - g_free (data.request_token_secret); - - g_strfreev (request_params); - if (proxy != NULL) - g_object_unref (proxy); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -typedef struct -{ - GError *error; - GMainLoop *loop; - gchar *account_object_path; -} AddData; - -static void -add_account_cb (GoaManager *manager, - GAsyncResult *res, - gpointer user_data) -{ - AddData *data = user_data; - goa_manager_call_add_account_finish (manager, - &data->account_object_path, - res, - &data->error); - g_main_loop_quit (data->loop); -} - -static gint64 -duration_to_abs_usec (gint duration_sec) -{ - gint64 ret; - GTimeVal now; - - g_get_current_time (&now); - ret = ((gint64) now.tv_sec) * 1000L * 1000L + ((gint64) now.tv_usec); - ret += ((gint64) duration_sec) * 1000L * 1000L; - return ret; -} - -static gint -abs_usec_to_duration (gint64 abs_usec) -{ - gint64 ret; - GTimeVal now; - - g_get_current_time (&now); - ret = abs_usec - (((gint64) now.tv_sec) * 1000L * 1000L + ((gint64) now.tv_usec)); - ret /= 1000L * 1000L; - return ret; -} - -static GoaObject * -goa_backend_oauth_provider_add_account (GoaBackendProvider *_provider, - GoaClient *client, - GtkDialog *dialog, - GtkBox *vbox, - GError **error) -{ - GoaBackendOAuthProvider *provider = GOA_BACKEND_OAUTH_PROVIDER (_provider); - GoaObject *ret; - gchar *access_token; - gchar *access_token_secret; - gint access_token_expires_in; - gchar *session_handle; - gint session_handle_expires_in; - gchar *identity; - gchar *name; - GList *accounts; - GList *l; - AddData data; - GVariantBuilder builder; - - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL); - g_return_val_if_fail (GOA_IS_CLIENT (client), NULL); - g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); - g_return_val_if_fail (GTK_IS_BOX (vbox), NULL); - g_return_val_if_fail (error == NULL || *error == NULL, NULL); - - ret = NULL; - access_token = NULL; - access_token_secret = NULL; - session_handle = NULL; - identity = NULL; - name = NULL; - accounts = NULL; - - memset (&data, '\0', sizeof (AddData)); - data.loop = g_main_loop_new (NULL, FALSE); - - if (!get_tokens_and_identity (provider, - dialog, - vbox, - &access_token, - &access_token_secret, - &access_token_expires_in, - &session_handle, - &session_handle_expires_in, - &identity, - &name, - &data.error)) - goto out; - - /* OK, got the identity... see if there's already an account - * of this type with the given identity - */ - accounts = goa_client_get_accounts (client); - for (l = accounts; l != NULL; l = l->next) - { - GoaObject *object = GOA_OBJECT (l->data); - GoaAccount *account; - GoaOAuthBased *oauth_based; - const gchar *identity_from_object; - - account = goa_object_peek_account (object); - oauth_based = goa_object_peek_oauth_based (object); - if (oauth_based == NULL) - continue; - - if (g_strcmp0 (goa_account_get_provider_type (account), - goa_backend_provider_get_provider_type (GOA_BACKEND_PROVIDER (provider))) != 0) - continue; - - identity_from_object = goa_oauth_based_get_identity (oauth_based); - if (g_strcmp0 (identity_from_object, identity) == 0) - { - g_set_error (&data.error, - GOA_ERROR, - GOA_ERROR_ACCOUNT_EXISTS, - _("There is already an account for the identity %s"), - identity); - goto out; - } - } - - /* we want the GoaClient to update before this method returns (so it - * can create a proxy for the new object) so run the mainloop while - * waiting for this to complete - */ - goa_manager_call_add_account (goa_client_get_manager (client), - goa_backend_provider_get_provider_type (GOA_BACKEND_PROVIDER (provider)), - name, /* Name */ - g_variant_new_parsed ("{'Identity': %s}", - identity), - NULL, /* GCancellable* */ - (GAsyncReadyCallback) add_account_cb, - &data); - g_main_loop_run (data.loop); - if (data.error != NULL) - goto out; - - g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); - g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token)); - g_variant_builder_add (&builder, "{sv}", "access_token_secret", g_variant_new_string (access_token_secret)); - if (access_token_expires_in > 0) - g_variant_builder_add (&builder, "{sv}", "access_token_expires_at", - g_variant_new_int64 (duration_to_abs_usec (access_token_expires_in))); - if (session_handle != NULL) - g_variant_builder_add (&builder, "{sv}", "session_handle", g_variant_new_string (session_handle)); - if (session_handle_expires_in > 0) - g_variant_builder_add (&builder, "{sv}", "session_handle_expires_at", - g_variant_new_int64 (duration_to_abs_usec (session_handle_expires_in))); - /* TODO: run in worker thread */ - if (!goa_backend_provider_store_credentials_sync (GOA_BACKEND_PROVIDER (provider), - identity, - g_variant_builder_end (&builder), - NULL, /* GCancellable */ - &data.error)) - goto out; - - ret = GOA_OBJECT (g_dbus_object_manager_get_object (goa_client_get_object_manager (client), - data.account_object_path)); - - out: - if (data.error != NULL) - { - g_propagate_error (error, data.error); - g_assert (ret == NULL); - } - else - { - g_assert (ret != NULL); - } - - g_list_foreach (accounts, (GFunc) g_object_unref, NULL); - g_list_free (accounts); - g_free (identity); - g_free (name); - g_free (access_token); - g_free (access_token_secret); - g_free (session_handle); - g_free (data.account_object_path); - if (data.loop != NULL) - g_main_loop_unref (data.loop); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean -goa_backend_oauth_provider_refresh_account (GoaBackendProvider *_provider, - GoaClient *client, - GoaObject *object, - GtkWindow *parent, - GError **error) -{ - GoaBackendOAuthProvider *provider = GOA_BACKEND_OAUTH_PROVIDER (_provider); - GtkWidget *dialog; - gchar *access_token; - gchar *access_token_secret; - gint access_token_expires_in; - gchar *session_handle; - gint session_handle_expires_in; - gchar *identity; - const gchar *existing_identity; - GVariantBuilder builder; - gboolean ret; - - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), FALSE); - g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE); - g_return_val_if_fail (GOA_IS_OBJECT (object), FALSE); - g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - access_token = NULL; - access_token_secret = NULL; - session_handle = NULL; - identity = NULL; - - dialog = gtk_dialog_new_with_buttons (NULL, - parent, - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, - NULL); - gtk_container_set_border_width (GTK_CONTAINER (dialog), 12); - gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); - gtk_widget_show_all (dialog); - - if (!get_tokens_and_identity (provider, - GTK_DIALOG (dialog), - GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), - &access_token, - &access_token_secret, - &access_token_expires_in, - &session_handle, - &session_handle_expires_in, - &identity, - NULL, /* out_name */ - error)) - goto out; - - existing_identity = goa_oauth_based_get_identity (goa_object_peek_oauth_based (object)); - if (g_strcmp0 (identity, existing_identity) != 0) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Was asked to login as %s, but logged in as %s"), - existing_identity, - identity); - goto out; - } - - g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); - g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token)); - g_variant_builder_add (&builder, "{sv}", "access_token_secret", g_variant_new_string (access_token_secret)); - if (access_token_expires_in > 0) - g_variant_builder_add (&builder, "{sv}", "access_token_expires_at", - g_variant_new_int64 (duration_to_abs_usec (access_token_expires_in))); - if (session_handle != NULL) - g_variant_builder_add (&builder, "{sv}", "session_handle", g_variant_new_string (session_handle)); - if (session_handle_expires_in > 0) - g_variant_builder_add (&builder, "{sv}", "session_handle_expires_at", - g_variant_new_int64 (duration_to_abs_usec (session_handle_expires_in))); - /* TODO: run in worker thread */ - if (!goa_backend_provider_store_credentials_sync (GOA_BACKEND_PROVIDER (provider), - identity, - g_variant_builder_end (&builder), - NULL, /* GCancellable */ - error)) - goto out; - - goa_account_call_ensure_credentials (goa_object_peek_account (object), - NULL, /* GCancellable */ - NULL, NULL); /* callback, user_data */ - - ret = TRUE; - - out: - gtk_widget_destroy (dialog); - - g_free (identity); - g_free (access_token); - g_free (access_token_secret); - g_free (session_handle); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -free_mutex (GMutex *mutex) -{ - g_mutex_free (mutex); -} - -/** - * goa_backend_oauth_provider_get_access_token_sync: - * @provider: A #GoaBackendOAuthProvider. - * @object: A #GoaObject. - * @force_refresh: If set to %TRUE, forces a refresh of the access token, if possible. - * @out_access_token_secret: (out): The secret for the return access token. - * @out_access_token_expires_in: (out): Return location for how many seconds the returned token is valid for (0 if unknown) or %NULL. - * @cancellable: (allow-none): A #GCancellable or %NULL. - * @error: Return location for error or %NULL. - * - * Synchronously gets an access token for @object. The calling thread - * is blocked while the operation is pending. - * - * The resulting token is typically read from the local cache so most - * of the time only a local roundtrip to the storage for the token - * cache (e.g. gnome-keyring-daemon) is - * needed. However, the operation may involve refreshing the token - * with the service provider so a full network round-trip may be - * needed. - * - * Note that multiple calls are serialized to avoid multiple - * outstanding requests to the service provider. - * - * This operation may fail if e.g. unable to refresh the credentials - * or if network connectivity is not available. Note that even if a - * token is returned, the returned token isn't guaranteed to work - - * use goa_backend_provider_ensure_credentials_sync() if you need - * stronger guarantees. - * - * Returns: The access token or %NULL if error is set. The returned - * string must be freed with g_free(). - */ -gchar * -goa_backend_oauth_provider_get_access_token_sync (GoaBackendOAuthProvider *provider, - GoaObject *object, - gboolean force_refresh, - gchar **out_access_token_secret, - gint *out_access_token_expires_in, - GCancellable *cancellable, - GError **error) -{ - const gchar *identity; - GVariant *credentials; - GVariantIter iter; - const gchar *key; - GVariant *value; - gchar *access_token; - gchar *access_token_secret; - gchar *session_handle; - gchar *access_token_for_refresh; - gchar *access_token_secret_for_refresh; - gchar *session_handle_for_refresh; - gint access_token_expires_in; - gint session_handle_expires_in; - gboolean success; - GVariantBuilder builder; - gchar *ret; - GMutex *lock; - - g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL); - g_return_val_if_fail (GOA_IS_OBJECT (object), NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); - g_return_val_if_fail (error == NULL || *error == NULL, NULL); - - ret = NULL; - credentials = NULL; - access_token = NULL; - access_token_secret = NULL; - access_token_expires_in = 0; - session_handle = NULL; - session_handle_expires_in = 0; - access_token_for_refresh = NULL; - access_token_secret_for_refresh = NULL; - session_handle_for_refresh = NULL; - success = FALSE; - - /* provider_lock is too coarse, use a per-object lock instead */ - G_LOCK (provider_lock); - lock = g_object_get_data (G_OBJECT (object), "-goa-backend-oauth-provider-get-access-token-lock"); - if (lock == NULL) - { - lock = g_mutex_new (); - g_object_set_data_full (G_OBJECT (object), - "-goa-backend-oauth-provider-get-access-token-lock", - lock, - (GDestroyNotify) free_mutex); - } - G_UNLOCK (provider_lock); - - g_mutex_lock (lock); - - /* First, get the credentials from the keyring */ - identity = goa_oauth_based_get_identity (goa_object_peek_oauth_based (object)); - credentials = goa_backend_provider_lookup_credentials_sync (GOA_BACKEND_PROVIDER (provider), - identity, - cancellable, - error); - if (credentials == NULL) - { - if (error != NULL) - { - g_prefix_error (error, _("Credentials not found in keyring (%s, %d): "), - g_quark_to_string ((*error)->domain), (*error)->code); - (*error)->domain = GOA_ERROR; - (*error)->code = GOA_ERROR_NOT_AUTHORIZED; - } - goto out; - } - - g_variant_iter_init (&iter, credentials); - while (g_variant_iter_next (&iter, "{&sv}", &key, &value)) - { - if (g_strcmp0 (key, "access_token") == 0) - access_token = g_variant_dup_string (value, NULL); - else if (g_strcmp0 (key, "access_token_secret") == 0) - access_token_secret = g_variant_dup_string (value, NULL); - else if (g_strcmp0 (key, "access_token_expires_at") == 0) - access_token_expires_in = abs_usec_to_duration (g_variant_get_int64 (value)); - else if (g_strcmp0 (key, "session_handle") == 0) - session_handle = g_variant_dup_string (value, NULL); - else if (g_strcmp0 (key, "session_handle_expires_at") == 0) - session_handle_expires_in = abs_usec_to_duration (g_variant_get_int64 (value)); - g_variant_unref (value); - } - - if (access_token == NULL || access_token_secret == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_NOT_AUTHORIZED, - _("Credentials does not contain access_token or access_token_secret")); - goto out; - } - - /* if we can't refresh the token, just return it no matter what */ - if (session_handle == NULL) - { - g_debug ("Returning locally cached credentials that cannot be refreshed"); - success = TRUE; - goto out; - } - - /* If access_token is still "fresh enough" (e.g. more than ten - * minutes of life left in it), just return it unless we've been - * asked to forcibly refresh it - */ - if (!force_refresh && access_token_expires_in > 10*60) - { - g_debug ("Returning locally cached credentials (expires in %d seconds)", access_token_expires_in); - success = TRUE; - goto out; - } - - g_debug ("Refreshing locally cached credentials (expires in %d seconds, force_refresh=%d)", access_token_expires_in, force_refresh); - - /* Otherwise, refresh it */ - access_token_for_refresh = access_token; access_token = NULL; - access_token_secret_for_refresh = access_token_secret; access_token_secret = NULL; - session_handle_for_refresh = session_handle; session_handle = NULL; - access_token = get_tokens_sync (provider, - access_token_for_refresh, - access_token_secret_for_refresh, - session_handle_for_refresh, - NULL, /* verifier */ - &access_token_secret, - &access_token_expires_in, - &session_handle, - &session_handle_expires_in, - cancellable, - error); - if (access_token == NULL) - { - if (error != NULL) - { - g_prefix_error (error, _("Failed to refresh access token (%s, %d): "), - g_quark_to_string ((*error)->domain), (*error)->code); - (*error)->code = is_authorization_error (*error) ? GOA_ERROR_NOT_AUTHORIZED : GOA_ERROR_FAILED; - (*error)->domain = GOA_ERROR; - } - goto out; - } - - /* Good. Now update the keyring with the refreshed credentials */ - g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); - g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token)); - g_variant_builder_add (&builder, "{sv}", "access_token_secret", g_variant_new_string (access_token_secret)); - if (access_token_expires_in > 0) - g_variant_builder_add (&builder, "{sv}", "access_token_expires_at", - g_variant_new_int64 (duration_to_abs_usec (access_token_expires_in))); - if (session_handle != NULL) - g_variant_builder_add (&builder, "{sv}", "session_handle", g_variant_new_string (session_handle)); - if (session_handle_expires_in > 0) - g_variant_builder_add (&builder, "{sv}", "session_handle_expires_at", - g_variant_new_int64 (duration_to_abs_usec (session_handle_expires_in))); - - identity = goa_oauth_based_get_identity (goa_object_peek_oauth_based (object)); - /* TODO: run in worker thread */ - if (!goa_backend_provider_store_credentials_sync (GOA_BACKEND_PROVIDER (provider), - identity, - g_variant_builder_end (&builder), - cancellable, - error)) - { - if (error != NULL) - { - g_prefix_error (error, _("Error storing credentials in keyring (%s, %d): "), - g_quark_to_string ((*error)->domain), (*error)->code); - (*error)->domain = GOA_ERROR; - (*error)->code = GOA_ERROR_NOT_AUTHORIZED; - } - goto out; - } - - success = TRUE; - - out: - if (success) - { - ret = access_token; access_token = NULL; - g_assert (ret != NULL); - if (out_access_token_secret != NULL) - { - *out_access_token_secret = access_token_secret; access_token_secret = NULL; - } - if (out_access_token_expires_in != NULL) - *out_access_token_expires_in = access_token_expires_in; - } - g_free (access_token); - g_free (access_token_secret); - g_free (session_handle); - g_free (access_token_for_refresh); - g_free (access_token_secret_for_refresh); - g_free (session_handle_for_refresh); - if (credentials != NULL) - g_variant_unref (credentials); - - g_mutex_unlock (lock); - - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean on_handle_get_access_token (GoaOAuthBased *object, - GDBusMethodInvocation *invocation, - gpointer user_data); - -static gboolean -goa_backend_oauth_provider_build_object (GoaBackendProvider *provider, - GoaObjectSkeleton *object, - GKeyFile *key_file, - const gchar *group, - GError **error) -{ - GoaOAuthBased *oauth_based; - gchar *identity; - - identity = NULL; - - oauth_based = goa_object_get_oauth_based (GOA_OBJECT (object)); - if (oauth_based != NULL) - goto out; - - oauth_based = goa_oauth_based_skeleton_new (); - /* Ensure D-Bus method invocations run in their own thread */ - g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (oauth_based), - G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); - goa_object_skeleton_set_oauth_based (object, oauth_based); - g_signal_connect (oauth_based, - "handle-get-access-token", - G_CALLBACK (on_handle_get_access_token), - NULL); - - identity = g_key_file_get_string (key_file, group, "Identity", NULL); - if (identity == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - "No Identity key"); - goto out; - } - goa_oauth_based_set_identity (oauth_based, identity); - - out: - g_object_unref (oauth_based); - g_free (identity); - return TRUE; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean -goa_backend_oauth_provider_ensure_credentials_sync (GoaBackendProvider *_provider, - GoaObject *object, - gint *out_expires_in, - GCancellable *cancellable, - GError **error) -{ - GoaBackendOAuthProvider *provider = GOA_BACKEND_OAUTH_PROVIDER (_provider); - gboolean ret; - gchar *access_token; - gchar *access_token_secret; - gint access_token_expires_in; - gchar *identity; - gboolean force_refresh; - - ret = FALSE; - access_token = NULL; - access_token_secret = NULL; - identity = NULL; - force_refresh = FALSE; - - again: - access_token = goa_backend_oauth_provider_get_access_token_sync (provider, - object, - force_refresh, - &access_token_secret, - &access_token_expires_in, - cancellable, - error); - if (access_token == NULL) - goto out; - - identity = goa_backend_oauth_provider_get_identity_sync (provider, - access_token, - access_token_secret, - NULL, /* out_name */ - cancellable, - error); - if (identity == NULL) - { - /* OK, try again, with forcing the locally cached credentials to be refreshed */ - if (!force_refresh) - { - force_refresh = TRUE; - g_free (access_token); access_token = NULL; - g_free (access_token_secret); access_token_secret = NULL; - goto again; - } - else - { - goto out; - } - } - - /* TODO: maybe check with the identity we have */ - ret = TRUE; - if (out_expires_in != NULL) - *out_expires_in = access_token_expires_in; - - out: - g_free (identity); - g_free (access_token); - g_free (access_token_secret); - return ret; -} - - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -goa_backend_oauth_provider_init (GoaBackendOAuthProvider *client) -{ -} - -static void -goa_backend_oauth_provider_class_init (GoaBackendOAuthProviderClass *klass) -{ - GoaBackendProviderClass *provider_class; - - provider_class = GOA_BACKEND_PROVIDER_CLASS (klass); - provider_class->add_account = goa_backend_oauth_provider_add_account; - provider_class->refresh_account = goa_backend_oauth_provider_refresh_account; - provider_class->build_object = goa_backend_oauth_provider_build_object; - provider_class->ensure_credentials_sync = goa_backend_oauth_provider_ensure_credentials_sync; - - klass->build_authorization_uri = goa_backend_oauth_provider_build_authorization_uri_default; - klass->get_use_external_browser = goa_backend_oauth_provider_get_use_external_browser_default; - klass->get_request_uri_params = goa_backend_oauth_provider_get_request_uri_params_default; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -/* runs in a thread dedicated to handling @invocation */ -static gboolean -on_handle_get_access_token (GoaOAuthBased *interface, - GDBusMethodInvocation *invocation, - gpointer user_data) -{ - GoaObject *object; - GoaAccount *account; - GoaBackendProvider *provider; - GError *error; - gchar *access_token; - gchar *access_token_secret; - gint access_token_expires_in; - - /* TODO: maybe log what app is requesting access */ - - access_token = NULL; - access_token_secret = NULL; - - object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (interface))); - account = goa_object_peek_account (object); - provider = goa_backend_provider_get_for_provider_type (goa_account_get_provider_type (account)); - - error = NULL; - access_token = goa_backend_oauth_provider_get_access_token_sync (GOA_BACKEND_OAUTH_PROVIDER (provider), - object, - FALSE, /* force_refresh */ - &access_token_secret, - &access_token_expires_in, - NULL, /* GCancellable* */ - &error); - if (access_token == NULL) - { - g_dbus_method_invocation_return_gerror (invocation, error); - g_error_free (error); - } - else - { - goa_oauth_based_complete_get_access_token (interface, - invocation, - access_token, - access_token_secret, - access_token_expires_in); - } - g_free (access_token); - g_free (access_token_secret); - g_object_unref (provider); - return TRUE; /* invocation was handled */ -} diff --git a/src/goa/goabackendoauthprovider.h b/src/goa/goabackendoauthprovider.h deleted file mode 100644 index 4b8e457..0000000 --- a/src/goa/goabackendoauthprovider.h +++ /dev/null @@ -1,137 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __GOA_BACKEND_OAUTH_PROVIDER_H__ -#define __GOA_BACKEND_OAUTH_PROVIDER_H__ - -#include -#include - -G_BEGIN_DECLS - -#define GOA_TYPE_BACKEND_OAUTH_PROVIDER (goa_backend_oauth_provider_get_type ()) -#define GOA_BACKEND_OAUTH_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_OAUTH_PROVIDER, GoaBackendOAuthProvider)) -#define GOA_BACKEND_OAUTH_PROVIDER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOA_TYPE_BACKEND_OAUTH_PROVIDER, GoaBackendOAuthProviderClass)) -#define GOA_BACKEND_OAUTH_PROVIDER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOA_TYPE_BACKEND_OAUTH_PROVIDER, GoaBackendOAuthProviderClass)) -#define GOA_IS_BACKEND_OAUTH_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_OAUTH_PROVIDER)) - -#define GOA_IS_BACKEND_OAUTH_PROVIDER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOA_TYPE_BACKEND_OAUTH_PROVIDER)) - -typedef struct _GoaBackendOAuthProviderClass GoaBackendOAuthProviderClass; -typedef struct _GoaBackendOAuthProviderPrivate GoaBackendOAuthProviderPrivate; - -/** - * GoaBackendOAuthProvider: - * - * The #GoaBackendOAuthProvider structure contains only private data and should - * only be accessed using the provided API. - */ -struct _GoaBackendOAuthProvider -{ - /*< private >*/ - GoaBackendProvider parent_instance; - GoaBackendOAuthProviderPrivate *priv; -}; - -/** - * GoaBackendOAuthProviderClass: - * @parent_class: The parent class. - * @get_consumer_key: Virtual function for goa_backend_oauth_provider_get_consumer_key(). - * @get_consumer_secret: Virtual function for goa_backend_oauth_provider_get_consumer_secret(). - * @get_request_uri: Virtual function for goa_backend_oauth_provider_get_request_uri(). - * @get_authorization_uri: Virtual function for goa_backend_oauth_provider_get_authorization_uri(). - * @get_token_uri: Virtual function for goa_backend_oauth_provider_get_token_uri(). - * @get_callback_uri: Virtual function for goa_backend_oauth_provider_get_callback_uri(). - * @get_identity_sync: Virtual function for goa_backend_oauth_provider_get_identity_sync(). - * @build_authorization_uri: Virtual function for goa_backend_oauth_provider_build_authorization_uri(). - * @get_use_external_browser: Virtual function for goa_backend_oauth_provider_get_use_external_browser(). - * @get_request_uri_params: Virtual function for goa_backend_oauth_provider_get_request_uri_params(). - * - * Class structure for #GoaBackendOAuthProvider. - */ -struct _GoaBackendOAuthProviderClass -{ - GoaBackendProviderClass parent_class; - - /* pure virtual */ - const gchar *(*get_consumer_key) (GoaBackendOAuthProvider *provider); - const gchar *(*get_consumer_secret) (GoaBackendOAuthProvider *provider); - const gchar *(*get_request_uri) (GoaBackendOAuthProvider *provider); - const gchar *(*get_authorization_uri) (GoaBackendOAuthProvider *provider); - const gchar *(*get_token_uri) (GoaBackendOAuthProvider *provider); - const gchar *(*get_callback_uri) (GoaBackendOAuthProvider *provider); - - gchar *(*get_identity_sync) (GoaBackendOAuthProvider *provider, - const gchar *access_token, - const gchar *access_token_secret, - gchar **out_name, - GCancellable *cancellable, - GError **error); - - /* virtual but with default implementation */ - gchar *(*build_authorization_uri) (GoaBackendOAuthProvider *provider, - const gchar *authorization_uri, - const gchar *escaped_oauth_token); - gboolean (*get_use_external_browser) (GoaBackendOAuthProvider *provider); - gchar **(*get_request_uri_params) (GoaBackendOAuthProvider *provider); - - /*< private >*/ - /* Padding for future expansion */ - gpointer goa_reserved[32]; -}; - -GType goa_backend_oauth_provider_get_type (void) G_GNUC_CONST; -const gchar *goa_backend_oauth_provider_get_consumer_key (GoaBackendOAuthProvider *provider); -const gchar *goa_backend_oauth_provider_get_consumer_secret (GoaBackendOAuthProvider *provider); -const gchar *goa_backend_oauth_provider_get_request_uri (GoaBackendOAuthProvider *provider); -gchar **goa_backend_oauth_provider_get_request_uri_params (GoaBackendOAuthProvider *provider); -const gchar *goa_backend_oauth_provider_get_authorization_uri (GoaBackendOAuthProvider *provider); -const gchar *goa_backend_oauth_provider_get_token_uri (GoaBackendOAuthProvider *provider); -const gchar *goa_backend_oauth_provider_get_callback_uri (GoaBackendOAuthProvider *provider); -gchar *goa_backend_oauth_provider_get_identity_sync (GoaBackendOAuthProvider *provider, - const gchar *access_token, - const gchar *access_token_secret, - gchar **out_name, - GCancellable *cancellable, - GError **error); -gchar *goa_backend_oauth_provider_get_access_token_sync (GoaBackendOAuthProvider *provider, - GoaObject *object, - gboolean force_refresh, - gchar **out_access_token_secret, - gint *out_access_token_expires_in, - GCancellable *cancellable, - GError **error); - -/* ---------------------------------------------------------------------------------------------------- */ - -gchar *goa_backend_oauth_provider_build_authorization_uri (GoaBackendOAuthProvider *provider, - const gchar *authorization_uri, - const gchar *escaped_oauth_token); -gboolean goa_backend_oauth_provider_get_use_external_browser (GoaBackendOAuthProvider *provider); - -G_END_DECLS - -#endif /* __GOA_BACKEND_OAUTH_PROVIDER_H__ */ diff --git a/src/goa/goabackendprovider.c b/src/goa/goabackendprovider.c deleted file mode 100644 index 0499002..0000000 --- a/src/goa/goabackendprovider.c +++ /dev/null @@ -1,672 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#include "config.h" -#include -#include - -#include "goabackendprovider.h" -#include "goabackendgoogleprovider.h" -#include "goabackendfacebookprovider.h" -#include "goabackendyahooprovider.h" -#include "goabackendtwitterprovider.h" - -/** - * SECTION:goabackendprovider - * @title: GoaBackendProvider - * @short_description: Abstract base class for providers - * - * #GoaBackendProvider is the base type for all providers. - */ - -static gboolean goa_backend_provider_ensure_credentials_sync_real (GoaBackendProvider *provider, - GoaObject *object, - gint *out_expires_in, - GCancellable *cancellable, - GError **error); - -G_DEFINE_ABSTRACT_TYPE (GoaBackendProvider, goa_backend_provider, G_TYPE_OBJECT); - -static void -goa_backend_provider_init (GoaBackendProvider *client) -{ -} - -static void -goa_backend_provider_class_init (GoaBackendProviderClass *klass) -{ - klass->ensure_credentials_sync = goa_backend_provider_ensure_credentials_sync_real; -} - -/** - * goa_backend_provider_get_provider_type: - * @provider: A #GoaBackendProvider. - * - * Gets the type of @provider. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider, do not free. - */ -const gchar * -goa_backend_provider_get_provider_type (GoaBackendProvider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_PROVIDER (provider), NULL); - return GOA_BACKEND_PROVIDER_GET_CLASS (provider)->get_provider_type (provider); -} - -/** - * goa_backend_provider_get_name: - * @provider: A #GoaBackendProvider. - * - * Gets a localized name for @provider that is suitable for display in - * an user interface. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: (transfer none): A string owned by @provider, do not free. - */ -const gchar * -goa_backend_provider_get_name (GoaBackendProvider *provider) -{ - g_return_val_if_fail (GOA_IS_BACKEND_PROVIDER (provider), NULL); - return GOA_BACKEND_PROVIDER_GET_CLASS (provider)->get_name (provider); -} - -/* ---------------------------------------------------------------------------------------------------- */ - -/** - * goa_backend_provider_add_account: - * @provider: A #GoaBackendProvider. - * @client: A #GoaClient. - * @dialog: A #GtkDialog. - * @vbox: A vertically oriented #GtkBox to put content in. - * @error: Return location for error or %NULL. - * - * This method brings up the user interface necessary to create a new - * account on @client of the type for @provider, interacts with the - * user to get all information needed and creates the account. - * - * The passed in @dialog widget is guaranteed to be visible with @vbox - * being empty and the only visible widget in @dialog's content - * area. The dialog has exactly one action widget, a cancel button - * with response id GTK_RESPONSE_CANCEL. Implementations are free to - * add additional action widgets, as needed. - * - * If an account was successfully created, a #GoaObject for the - * created account is returned. If @dialog is dismissed, %NULL is - * returned and @error is set to %GOA_ERROR_DIALOG_DISMISSED. If an - * account couldn't be created then @error is set. - * - * The caller will always show an error dialog if @error is set unless - * the error is %GOA_ERROR_DIALOG_DISMISSED. - * - * Implementations should run the default main loop while - * interacting with the user and may do so using e.g. gtk_dialog_run() - * on @dialog. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: The #GoaObject for the created account (must be relased - * with g_object_unref()) or %NULL if @error is set. - */ -GoaObject * -goa_backend_provider_add_account (GoaBackendProvider *provider, - GoaClient *client, - GtkDialog *dialog, - GtkBox *vbox, - GError **error) -{ - GoaObject *ret; - - g_return_val_if_fail (GOA_IS_BACKEND_PROVIDER (provider), NULL); - g_return_val_if_fail (GOA_IS_CLIENT (client), NULL); - g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); - g_return_val_if_fail (error == NULL || *error == NULL, NULL); - - ret = GOA_BACKEND_PROVIDER_GET_CLASS (provider)->add_account (provider, client, dialog, vbox, error); - - g_warn_if_fail ((ret == NULL && (error == NULL || *error != NULL)) || GOA_IS_OBJECT (ret)); - - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -/** - * goa_backend_provider_refresh_account: - * @provider: A #GoaBackendProvider. - * @client: A #GoaClient. - * @object: A #GoaObject with a #GoaAccount interface. - * @parent: (allow-none): Transient parent of dialogs or %NULL. - * @error: Return location for error or %NULL. - * - * This method brings up the user interface necessary for refreshing - * the credentials for the account specified by @object. This - * typically involves having the user log in to the account again. - * - * Implementations should use @parent (unless %NULL) as the transient - * parent of any created windows/dialogs. - * - * Implementations should run the default main loop while - * interacting with the user. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: %TRUE if the account has been refreshed, %FALSE if @error - * is set. - */ -gboolean -goa_backend_provider_refresh_account (GoaBackendProvider *provider, - GoaClient *client, - GoaObject *object, - GtkWindow *parent, - GError **error) -{ - g_return_val_if_fail (GOA_IS_BACKEND_PROVIDER (provider), FALSE); - g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE); - g_return_val_if_fail (GOA_IS_OBJECT (object) && goa_object_peek_account (object) != NULL, FALSE); - g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - return GOA_BACKEND_PROVIDER_GET_CLASS (provider)->refresh_account (provider, client, object, parent, error); -} - -/* ---------------------------------------------------------------------------------------------------- */ - -/** - * goa_backend_provider_build_object: - * @provider: A #GoaBackendProvider. - * @object: The #GoaObjectSkeleton that is being built. - * @key_file: The #GKeyFile with configuation data. - * @group: The group in @key_file to get data from. - * @error: Return location for error or %NULL. - * - * This method is called when construction account #GoaObject - * from configuration data - it basically provides a way to add - * provider-specific information. - * - * The passed in @object will have a #GoaAccount interface - * set. Implementations should validate and use data from @key_file to - * add more interfaces to @object. - * - * Note that this may be called on already exported objects - for - * example on configuration files reload. - * - * This is a pure virtual method - a subclass must provide an - * implementation. - * - * Returns: %TRUE if data was valid, %FALSE if @error is set. - */ -gboolean -goa_backend_provider_build_object (GoaBackendProvider *provider, - GoaObjectSkeleton *object, - GKeyFile *key_file, - const gchar *group, - GError **error) -{ - g_return_val_if_fail (GOA_IS_BACKEND_PROVIDER (provider), FALSE); - g_return_val_if_fail (GOA_IS_OBJECT_SKELETON (object) && goa_object_peek_account (GOA_OBJECT (object)) != NULL, FALSE); - g_return_val_if_fail (key_file != NULL, FALSE); - g_return_val_if_fail (group != NULL, FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - return GOA_BACKEND_PROVIDER_GET_CLASS (provider)->build_object (provider, object, key_file, group, error); -} - -/* ---------------------------------------------------------------------------------------------------- */ - -typedef struct -{ - GoaObject *object; - gint expires_in; -} EnsureCredentialsData; - -static EnsureCredentialsData * -ensure_credentials_data_new (GoaObject *object) -{ - EnsureCredentialsData *data; - data = g_new0 (EnsureCredentialsData, 1); - data->object = g_object_ref (object); - return data; -} - -static void -ensure_credentials_data_free (EnsureCredentialsData *data) -{ - g_object_unref (data->object); - g_free (data); -} - -static void -ensure_credentials_in_thread_func (GSimpleAsyncResult *simple, - GObject *object, - GCancellable *cancellable) -{ - EnsureCredentialsData *data; - GError *error; - - data = g_simple_async_result_get_op_res_gpointer (simple); - - error = NULL; - if (!goa_backend_provider_ensure_credentials_sync (GOA_BACKEND_PROVIDER (object), - data->object, - &data->expires_in, - cancellable, - &error)) - g_simple_async_result_take_error (simple, error); -} - - -/** - * goa_backend_provider_ensure_credentials: - * @provider: A #GoaBackendProvider. - * @object: A #GoaObject with a #GoaAccount interface. - * @cancellable: (allow-none): A #GCancellable or %NULL. - * @callback: The function to call when the request is satisfied. - * @user_data: Pointer to pass to @callback. - * - * Ensures that credentials for @object are still valid. - * - * When the result is ready, @callback will be called in the the thread-default main - * loop this function was called from. You can then call - * goa_backend_provider_ensure_credentials_finish() to get the result - * of the operation. - * - * This is a virtual method where the default implemention simply returns - * the %GOA_ERROR_NOT_SUPPORTED error. A subclass may provide - * another implementation. - */ -void -goa_backend_provider_ensure_credentials (GoaBackendProvider *provider, - GoaObject *object, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - GSimpleAsyncResult *simple; - - g_return_if_fail (GOA_IS_BACKEND_PROVIDER (provider)); - g_return_if_fail (GOA_IS_OBJECT (object)); - g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); - - simple = g_simple_async_result_new (G_OBJECT (provider), - callback, - user_data, - goa_backend_provider_ensure_credentials); - g_simple_async_result_set_op_res_gpointer (simple, - ensure_credentials_data_new (object), - (GDestroyNotify) ensure_credentials_data_free); - g_simple_async_result_run_in_thread (simple, - ensure_credentials_in_thread_func, - G_PRIORITY_DEFAULT, - cancellable); - g_object_unref (simple); -} - -/** - * goa_backend_provider_ensure_credentials_finish: - * @provider: A #GoaBackendProvider. - * @out_expires_in: (out): Return location for how long the expired credentials are good for (0 if unknown) or %NULL. - * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to goa_backend_provider_ensure_credentials(). - * @error: Return location for error or %NULL. - * - * Finishes an operation started with goa_backend_provider_ensure_credentials(). - * - * Returns: %TRUE if the credentials for the passed #GoaObject are valid, %FALSE if @error is set. - */ -gboolean -goa_backend_provider_ensure_credentials_finish (GoaBackendProvider *provider, - gint *out_expires_in, - GAsyncResult *res, - GError **error) -{ - GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); - gboolean ret; - EnsureCredentialsData *data; - - ret = FALSE; - - g_return_val_if_fail (GOA_IS_BACKEND_PROVIDER (provider), FALSE); - g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == goa_backend_provider_ensure_credentials); - - if (g_simple_async_result_propagate_error (simple, error)) - goto out; - - ret = TRUE; - data = g_simple_async_result_get_op_res_gpointer (simple); - if (out_expires_in != NULL) - *out_expires_in = data->expires_in; - - out: - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -/** - * goa_backend_provider_ensure_credentials_sync: - * @provider: A #GoaBackendProvider. - * @object: A #GoaObject with a #GoaAccount interface. - * @out_expires_in: (out): Return location for how long the expired credentials are good for (0 if unknown) or %NULL. - * @cancellable: (allow-none): A #GCancellable or %NULL. - * @error: Return location for error or %NULL. - * - * Like goa_backend_provider_ensure_credentials() but blocks the - * calling thread until an answer is received. - * - * Returns: %TRUE if the credentials for the passed #GoaObject are valid, %FALSE if @error is set. - */ -gboolean -goa_backend_provider_ensure_credentials_sync (GoaBackendProvider *provider, - GoaObject *object, - gint *out_expires_in, - GCancellable *cancellable, - GError **error) -{ - g_return_val_if_fail (GOA_IS_BACKEND_PROVIDER (provider), FALSE); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - return GOA_BACKEND_PROVIDER_GET_CLASS (provider)->ensure_credentials_sync (provider, object, out_expires_in, cancellable, error); -} - -static gboolean -goa_backend_provider_ensure_credentials_sync_real (GoaBackendProvider *provider, - GoaObject *object, - gint *out_expires_in, - GCancellable *cancellable, - GError **error) -{ - g_set_error (error, - GOA_ERROR, - GOA_ERROR_NOT_SUPPORTED, - _("ensure_credentials_sync not been implemented on type %s"), - g_type_name (G_TYPE_FROM_INSTANCE (provider))); - return FALSE; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -ensure_ep_and_builtins (void) -{ - static gsize once_init_value = 0; - - if (g_once_init_enter (&once_init_value)) - { - GIOExtensionPoint *extension_point; - static volatile GType type = 0; - - extension_point = g_io_extension_point_register (GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME); - g_io_extension_point_set_required_type (extension_point, GOA_TYPE_BACKEND_PROVIDER); - - type = GOA_TYPE_BACKEND_GOOGLE_PROVIDER; - type = GOA_TYPE_BACKEND_FACEBOOK_PROVIDER; - type = GOA_TYPE_BACKEND_YAHOO_PROVIDER; - type = GOA_TYPE_BACKEND_TWITTER_PROVIDER; - type = type; /* for -Wunused-but-set-variable */ - - g_once_init_leave (&once_init_value, 1); - } -} - - -/* ---------------------------------------------------------------------------------------------------- */ - -/** - * goa_backend_provider_get_for_provider_type: - * @provider_type: A provider type. - * - * Looks up the %GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME extension - * point and returns a newly created #GoaBackendProvider for - * @provider_type, if any. - * - * Returns: (transfer full): A #GoaBackendProvider (that must be freed - * with g_object_unref()) or %NULL if not found. - */ -GoaBackendProvider * -goa_backend_provider_get_for_provider_type (const gchar *provider_type) -{ - GIOExtension *extension; - GIOExtensionPoint *extension_point; - GoaBackendProvider *ret; - - ensure_ep_and_builtins (); - - ret = NULL; - - extension_point = g_io_extension_point_lookup (GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME); - extension = g_io_extension_point_get_extension_by_name (extension_point, provider_type); - if (extension != NULL) - ret = GOA_BACKEND_PROVIDER (g_object_new (g_io_extension_get_type (extension), NULL)); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -/** - * goa_backend_provider_get_all: - * - * Looks up the %GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME extension - * point and returns a newly created #GoaBackendProvider for each - * provider type encountered. - * - * Returns: (transfer full) (element-type GoaBackendProvider): A list - * of element providers that should be freed with g_list_free() - * after each element has been freed with g_object_unref(). - */ -GList * -goa_backend_provider_get_all (void) -{ - GList *ret; - GList *extensions; - GList *l; - GIOExtensionPoint *extension_point; - - ensure_ep_and_builtins (); - - ret = NULL; - extension_point = g_io_extension_point_lookup (GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME); - extensions = g_io_extension_point_get_extensions (extension_point); - /* TODO: what if there are two extensions with the same name? */ - for (l = extensions; l != NULL; l = l->next) - { - GIOExtension *extension = l->data; - ret = g_list_prepend (ret, g_object_new (g_io_extension_get_type (extension), NULL)); - } - ret = g_list_reverse (ret); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static const GnomeKeyringPasswordSchema keyring_password_schema = -{ - GNOME_KEYRING_ITEM_GENERIC_SECRET, - { - { "goa-identity", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, - { NULL, 0 } - } -}; - -/** - * goa_backend_provider_store_credentials_sync: - * @provider: A #GoaBackendProvider. - * @identity: The identity to store credentials for. - * @credentials: The credentials to store. - * @cancellable: (allow-none): A #GCancellable or %NULL. - * @error: Return location for error or %NULL. - * - * Stores @credentials for @identity in the key-ring. If @credentials - * is floating, it is consumed. - * - * The calling thread is blocked while waiting for a reply. - * - * This is a convenience method (not virtual) that subclasses can use. - * - * Returns: %TRUE if the credentials was successfully stored, %FALSE - * if @error is set. - */ -gboolean -goa_backend_provider_store_credentials_sync (GoaBackendProvider *provider, - const gchar *identity, - GVariant *credentials, - GCancellable *cancellable, - GError **error) -{ - gboolean ret; - gchar *credentials_str; - gchar *password_description; - gchar *password_key; - GnomeKeyringResult result; - - g_return_val_if_fail (GOA_IS_BACKEND_PROVIDER (provider), FALSE); - g_return_val_if_fail (identity != NULL, FALSE); - g_return_val_if_fail (credentials != NULL, FALSE); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - /* TODO: use GCancellable */ - ret = FALSE; - - credentials_str = g_variant_print (credentials, TRUE); - g_variant_ref_sink (credentials); - g_variant_unref (credentials); - - password_key = g_strdup_printf ("%s:%s", - goa_backend_provider_get_provider_type (GOA_BACKEND_PROVIDER (provider)), - identity); - password_description = g_strdup_printf (_("GOA %s credentials for identity %s"), - goa_backend_provider_get_provider_type (GOA_BACKEND_PROVIDER (provider)), - identity); - result = gnome_keyring_store_password_sync (&keyring_password_schema, - NULL, /* default keyring */ - password_description, - credentials_str, - "goa-identity", password_key, - NULL); - if (result != GNOME_KEYRING_RESULT_OK) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, /* TODO: more specific */ - _("Failed to store credentials in the keyring: %s"), - gnome_keyring_result_to_message (result)); - goto out; - } - - ret = TRUE; - - out: - g_free (credentials_str); - g_free (password_key); - g_free (password_description); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -/** - * goa_backend_provider_lookup_credentials_sync: - * @provider: A #GoaBackendProvider. - * @identity: The identity to look up credentials for. - * @cancellable: (allow-none): A #GCancellable or %NULL. - * @error: Return location for error or %NULL. - * - * Looks up credentials in the keyring for @identity previously stored - * with goa_backend_provider_store_credentials(). - * - * The calling thread is blocked while waiting for a reply. - * - * This is a convenience method (not virtual) that subclasses can use. - * - * Returns: (transfer full): A #GVariant (never floating) - * with credentials or %NULL if @error is set. Free with - * g_variant_unref(). - */ -GVariant * -goa_backend_provider_lookup_credentials_sync (GoaBackendProvider *provider, - const gchar *identity, - GCancellable *cancellable, - GError **error) -{ - gchar *password_key; - GVariant *ret; - GnomeKeyringResult result; - gchar *returned_password; - - g_return_val_if_fail (GOA_IS_BACKEND_PROVIDER (provider), NULL); - g_return_val_if_fail (identity != NULL, NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); - g_return_val_if_fail (error == NULL || *error == NULL, NULL); - - ret = NULL; - password_key = NULL; - returned_password = NULL; - - password_key = g_strdup_printf ("%s:%s", - goa_backend_provider_get_provider_type (GOA_BACKEND_PROVIDER (provider)), - identity); - - result = gnome_keyring_find_password_sync (&keyring_password_schema, - &returned_password, - "goa-identity", password_key, - NULL); - if (result != GNOME_KEYRING_RESULT_OK) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, /* TODO: more specific */ - _("Failed to retrieve credentials from the keyring: %s"), - gnome_keyring_result_to_message (result)); - goto out; - } - - ret = g_variant_parse (NULL, /* GVariantType */ - returned_password, - NULL, /* limit */ - NULL, /* endptr */ - error); - if (ret == NULL) - { - g_prefix_error (error, _("Error parsing result obtained from the keyring: ")); - goto out; - } - - if (g_variant_is_floating (ret)) - g_variant_ref_sink (ret); - - out: - gnome_keyring_free_password (returned_password); - g_free (password_key); - return ret; -} - diff --git a/src/goa/goabackendprovider.h b/src/goa/goabackendprovider.h deleted file mode 100644 index e1a4fa2..0000000 --- a/src/goa/goabackendprovider.h +++ /dev/null @@ -1,164 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __GOA_BACKEND_PROVIDER_H__ -#define __GOA_BACKEND_PROVIDER_H__ - -#include - -G_BEGIN_DECLS - -#define GOA_TYPE_BACKEND_PROVIDER (goa_backend_provider_get_type ()) -#define GOA_BACKEND_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_PROVIDER, GoaBackendProvider)) -#define GOA_BACKEND_PROVIDER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOA_TYPE_BACKEND_PROVIDER, GoaBackendProviderClass)) -#define GOA_BACKEND_PROVIDER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOA_TYPE_BACKEND_PROVIDER, GoaBackendProviderClass)) -#define GOA_IS_BACKEND_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_PROVIDER)) -#define GOA_IS_BACKEND_PROVIDER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOA_TYPE_BACKEND_PROVIDER)) - -typedef struct _GoaBackendProviderClass GoaBackendProviderClass; -typedef struct _GoaBackendProviderPrivate GoaBackendProviderPrivate; - -/** - * GoaBackendProvider: - * - * The #GoaBackendProvider structure contains only private data and should - * only be accessed using the provided API. - */ -struct _GoaBackendProvider -{ - /*< private >*/ - GObject parent_instance; - GoaBackendProviderPrivate *priv; -}; - -/** - * GoaBackendProviderClass: - * @parent_class: The parent class. - * @get_provider_type: Virtual function for goa_backend_provider_get_provider_type(). - * @get_name: Virtual function for goa_backend_provider_get_name(). - * @add_account: Virtual function for goa_backend_provider_add_account(). - * @refresh_account: Virtual function for goa_backend_provider_refresh_account(). - * @build_object: Virtual function for goa_backend_provider_build_object(). - * @ensure_credentials_sync: Virtual function for goa_backend_provider_ensure_credentials_sync(). - * - * Class structure for #GoaBackendProvider. - */ -struct _GoaBackendProviderClass -{ - GObjectClass parent_class; - - /* pure virtual */ - const gchar *(*get_provider_type) (GoaBackendProvider *provider); - const gchar *(*get_name) (GoaBackendProvider *provider); - GoaObject *(*add_account) (GoaBackendProvider *provider, - GoaClient *client, - GtkDialog *dialog, - GtkBox *vbox, - GError **error); - gboolean (*refresh_account) (GoaBackendProvider *provider, - GoaClient *client, - GoaObject *object, - GtkWindow *parent, - GError **error); - gboolean (*build_object) (GoaBackendProvider *provider, - GoaObjectSkeleton *object, - GKeyFile *key_file, - const gchar *group, - GError **error); - - /* virtual but with default implementation */ - gboolean (*ensure_credentials_sync) (GoaBackendProvider *provider, - GoaObject *object, - gint *out_expires_in, - GCancellable *cancellable, - GError **error); - - /*< private >*/ - /* Padding for future expansion */ - gpointer goa_reserved[32]; -}; - -GType goa_backend_provider_get_type (void) G_GNUC_CONST; -const gchar *goa_backend_provider_get_provider_type (GoaBackendProvider *provider); -const gchar *goa_backend_provider_get_name (GoaBackendProvider *provider); -GoaObject *goa_backend_provider_add_account (GoaBackendProvider *provider, - GoaClient *client, - GtkDialog *dialog, - GtkBox *vbox, - GError **error); -gboolean goa_backend_provider_refresh_account (GoaBackendProvider *provider, - GoaClient *client, - GoaObject *object, - GtkWindow *parent, - GError **error); -gboolean goa_backend_provider_build_object (GoaBackendProvider *provider, - GoaObjectSkeleton *object, - GKeyFile *key_file, - const gchar *group, - GError **error); - -gboolean goa_backend_provider_store_credentials_sync (GoaBackendProvider *provider, - const gchar *identity, - GVariant *credentials, - GCancellable *cancellable, - GError **error); - -GVariant *goa_backend_provider_lookup_credentials_sync (GoaBackendProvider *provider, - const gchar *identity, - GCancellable *cancellable, - GError **error); - -void goa_backend_provider_ensure_credentials (GoaBackendProvider *provider, - GoaObject *object, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); - -gboolean goa_backend_provider_ensure_credentials_finish (GoaBackendProvider *provider, - gint *out_expires_in, - GAsyncResult *res, - GError **error); - -gboolean goa_backend_provider_ensure_credentials_sync (GoaBackendProvider *provider, - GoaObject *object, - gint *out_expires_in, - GCancellable *cancellable, - GError **error); - - -/** - * GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME: - * - * Extension point for #GoaBackendProvider implementations. - */ -#define GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME "goa-backend-provider" - -GList *goa_backend_provider_get_all (void); -GoaBackendProvider *goa_backend_provider_get_for_provider_type (const gchar *provider_type); - -G_END_DECLS - -#endif /* __GOA_BACKEND_PROVIDER_H__ */ diff --git a/src/goa/goabackendtwitterprovider.c b/src/goa/goabackendtwitterprovider.c deleted file mode 100644 index d036dd9..0000000 --- a/src/goa/goabackendtwitterprovider.c +++ /dev/null @@ -1,332 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#include "config.h" -#include - -#include -#include - -#include "goabackendprovider.h" -#include "goabackendoauthprovider.h" -#include "goabackendtwitterprovider.h" - -/** - * GoaBackendTwitterProvider: - * - * The #GoaBackendTwitterProvider structure contains only private data and should - * only be accessed using the provided API. - */ -struct _GoaBackendTwitterProvider -{ - /*< private >*/ - GoaBackendOAuthProvider parent_instance; -}; - -typedef struct _GoaBackendTwitterProviderClass GoaBackendTwitterProviderClass; - -struct _GoaBackendTwitterProviderClass -{ - GoaBackendOAuthProviderClass parent_class; -}; - -/** - * SECTION:goabackendtwitterprovider - * @title: GoaBackendTwitterProvider - * @short_description: A provider for Twitter - * - * #GoaBackendTwitterProvider is used for handling Twitter accounts. - */ - -G_DEFINE_TYPE_WITH_CODE (GoaBackendTwitterProvider, goa_backend_twitter_provider, GOA_TYPE_BACKEND_OAUTH_PROVIDER, - g_io_extension_point_implement (GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME, - g_define_type_id, - "twitter", - 0)); - -/* ---------------------------------------------------------------------------------------------------- */ - -static const gchar * -get_provider_type (GoaBackendProvider *_provider) -{ - return "twitter"; -} - -static const gchar * -get_name (GoaBackendProvider *_provider) -{ - return _("Twitter Account"); -} - -static const gchar * -get_consumer_key (GoaBackendOAuthProvider *provider) -{ - return "tlVEAXvkgqr0VUFyqVQ"; -} - -static const gchar * -get_consumer_secret (GoaBackendOAuthProvider *provider) -{ - return "RN2FBARWy7scDmWFwfhIA6Qwf6kPYxZ0PIpVWzgpdU"; -} - -static const gchar * -get_request_uri (GoaBackendOAuthProvider *provider) -{ - return "https://api.twitter.com/oauth/request_token"; -} - -static gchar ** -get_request_uri_params (GoaBackendOAuthProvider *provider) -{ - return NULL; - GPtrArray *p; - p = g_ptr_array_new (); - g_ptr_array_add (p, g_strdup ("xoauth_displayname")); - g_ptr_array_add (p, g_strdup ("GNOME")); - - g_ptr_array_add (p, g_strdup ("scope")); - g_ptr_array_add (p, g_strdup ( - /* Display email address: cf. https://sites.twitter.com/site/oauthgoog/Home/emaildisplayscope */ - "https://www.twitterapis.com/auth/userinfo#email " - /* IMAP, SMTP access: http://code.twitter.com/apis/gmail/oauth/protocol.html */ - "https://mail.twitter.com/ " - /* Calendar data API: http://code.twitter.com/apis/calendar/data/2.0/developers_guide.html */ - "https://www.twitter.com/calendar/feeds")); - g_ptr_array_add (p, NULL); - return (gchar **) g_ptr_array_free (p, FALSE); -} - - -static const gchar * -get_authorization_uri (GoaBackendOAuthProvider *provider) -{ - return "https://api.twitter.com/oauth/authorize"; -} - -static const gchar * -get_token_uri (GoaBackendOAuthProvider *provider) -{ - return "https://api.twitter.com/oauth/access_token"; -} - -static const gchar * -get_callback_uri (GoaBackendOAuthProvider *provider) -{ - return "https://www.gnome.org/goa-1.0/oauth"; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gchar * -get_identity_sync (GoaBackendOAuthProvider *provider, - const gchar *access_token, - const gchar *access_token_secret, - gchar **out_name, - GCancellable *cancellable, - GError **error) -{ - RestProxy *proxy; - RestProxyCall *call; - JsonParser *parser; - JsonObject *json_object; - gchar *ret; - gchar *id; - gchar *name; - - ret = NULL; - proxy = NULL; - call = NULL; - parser = NULL; - id = NULL; - name = NULL; - - /* TODO: cancellable */ - - proxy = oauth_proxy_new_with_token (goa_backend_oauth_provider_get_consumer_key (provider), - goa_backend_oauth_provider_get_consumer_secret (provider), - access_token, - access_token_secret, - "https://api.twitter.com/1/account/verify_credentials.json", - FALSE); - call = rest_proxy_new_call (proxy); - rest_proxy_call_set_method (call, "GET"); - - if (!rest_proxy_call_sync (call, error)) - goto out; - if (rest_proxy_call_get_status_code (call) != 200) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Expected status 200 when requesting guid, instead got status %d (%s)"), - rest_proxy_call_get_status_code (call), - rest_proxy_call_get_status_message (call)); - goto out; - } - - parser = json_parser_new (); - if (!json_parser_load_from_data (parser, - rest_proxy_call_get_payload (call), - rest_proxy_call_get_payload_length (call), - error)) - { - g_prefix_error (error, _("Error parsing response as JSON: ")); - goto out; - } - - json_object = json_node_get_object (json_parser_get_root (parser)); - id = g_strdup (json_object_get_string_member (json_object, "id_str")); - if (id == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Didn't find id_str member in JSON data")); - goto out; - } - name = g_strdup (json_object_get_string_member (json_object, "screen_name")); - if (name == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Didn't find screen_name member in JSON data")); - goto out; - } - - ret = id; - id = NULL; - if (out_name != NULL) - { - *out_name = name; - name = NULL; - } - - out: - g_free (id); - g_free (name); - if (call != NULL) - g_object_unref (call); - if (proxy != NULL) - g_object_unref (proxy); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean -goa_backend_twitter_provider_build_object (GoaBackendProvider *provider, - GoaObjectSkeleton *object, - GKeyFile *key_file, - const gchar *group, - GError **error) -{ - GoaAccount *account; - GoaTwitterAccount *twitter_account; - gboolean ret; - gchar *id; - - id = NULL; - account = NULL; - twitter_account = NULL; - ret = FALSE; - - /* Chain up */ - if (!GOA_BACKEND_PROVIDER_CLASS (goa_backend_twitter_provider_parent_class)->build_object (provider, - object, - key_file, - group, - error)) - goto out; - - account = goa_object_get_account (GOA_OBJECT (object)); - twitter_account = goa_object_get_twitter_account (GOA_OBJECT (object)); - if (twitter_account == NULL) - { - twitter_account = goa_twitter_account_skeleton_new (); - goa_object_skeleton_set_twitter_account (object, twitter_account); - } - - id = g_key_file_get_string (key_file, group, "Identity", NULL); - if (id == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - "Invalid identity %s for id %s", - id, - goa_account_get_id (account)); - goto out; - } - - goa_twitter_account_set_id (twitter_account, id); - - ret = TRUE; - - out: - g_free (id); - if (twitter_account != NULL) - g_object_unref (twitter_account); - if (account != NULL) - g_object_unref (account); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean -get_use_external_browser (GoaBackendOAuthProvider *provider) -{ - /* For some reason this only works in a browser - bad callback URL? TODO: investigate */ - return TRUE; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -goa_backend_twitter_provider_init (GoaBackendTwitterProvider *client) -{ -} - -static void -goa_backend_twitter_provider_class_init (GoaBackendTwitterProviderClass *klass) -{ - GoaBackendProviderClass *provider_class; - GoaBackendOAuthProviderClass *oauth_class; - - provider_class = GOA_BACKEND_PROVIDER_CLASS (klass); - provider_class->get_provider_type = get_provider_type; - provider_class->get_name = get_name; - provider_class->build_object = goa_backend_twitter_provider_build_object; - - oauth_class = GOA_BACKEND_OAUTH_PROVIDER_CLASS (klass); - oauth_class->get_identity_sync = get_identity_sync; - oauth_class->get_consumer_key = get_consumer_key; - oauth_class->get_consumer_secret = get_consumer_secret; - oauth_class->get_request_uri = get_request_uri; - oauth_class->get_request_uri_params = get_request_uri_params; - oauth_class->get_authorization_uri = get_authorization_uri; - oauth_class->get_token_uri = get_token_uri; - oauth_class->get_callback_uri = get_callback_uri; - oauth_class->get_use_external_browser = get_use_external_browser; -} diff --git a/src/goa/goabackendtwitterprovider.h b/src/goa/goabackendtwitterprovider.h deleted file mode 100644 index 89a37ff..0000000 --- a/src/goa/goabackendtwitterprovider.h +++ /dev/null @@ -1,42 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __GOA_BACKEND_TWITTER_PROVIDER_H__ -#define __GOA_BACKEND_TWITTER_PROVIDER_H__ - -#include - -G_BEGIN_DECLS - -#define GOA_TYPE_BACKEND_TWITTER_PROVIDER (goa_backend_twitter_provider_get_type ()) -#define GOA_BACKEND_TWITTER_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_TWITTER_PROVIDER, GoaBackendTwitterProvider)) -#define GOA_IS_BACKEND_TWITTER_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_TWITTER_PROVIDER)) - -GType goa_backend_twitter_provider_get_type (void) G_GNUC_CONST; - -G_END_DECLS - -#endif /* __GOA_BACKEND_TWITTER_PROVIDER_H__ */ diff --git a/src/goa/goabackendtypes.h b/src/goa/goabackendtypes.h deleted file mode 100644 index ee98a63..0000000 --- a/src/goa/goabackendtypes.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __GOA_BACKEND_TYPES_H__ -#define __GOA_BACKEND_TYPES_H__ - -#include -#include -#include - -G_BEGIN_DECLS - -struct _GoaBackendProvider; -typedef struct _GoaBackendProvider GoaBackendProvider; - -struct _GoaBackendOAuthProvider; -typedef struct _GoaBackendOAuthProvider GoaBackendOAuthProvider; - -struct _GoaBackendOAuth2Provider; -typedef struct _GoaBackendOAuth2Provider GoaBackendOAuth2Provider; - -struct _GoaBackendGoogleProvider; -typedef struct _GoaBackendGoogleProvider GoaBackendGoogleProvider; - -struct _GoaBackendFacebookProvider; -typedef struct _GoaBackendFacebookProvider GoaBackendFacebookProvider; - -struct _GoaBackendYahooProvider; -typedef struct _GoaBackendYahooProvider GoaBackendYahooProvider; - -struct _GoaBackendTwitterProvider; -typedef struct _GoaBackendTwitterProvider GoaBackendTwitterProvider; - -struct _GoaBackendImapAuth; -typedef struct _GoaBackendImapAuth GoaBackendImapAuth; - -struct _GoaBackendImapAuthOAuth; -typedef struct _GoaBackendImapAuthOAuth GoaBackendImapAuthOAuth; - -struct _GoaBackendImapClient; -typedef struct _GoaBackendImapClient GoaBackendImapClient; - -struct _GoaBackendImapMail; -typedef struct _GoaBackendImapMail GoaBackendImapMail; - -G_END_DECLS - -#endif /* __GOA_BACKEND_TYPES_H__ */ diff --git a/src/goa/goabackendyahooprovider.c b/src/goa/goabackendyahooprovider.c deleted file mode 100644 index c9ba5e8..0000000 --- a/src/goa/goabackendyahooprovider.c +++ /dev/null @@ -1,369 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#include "config.h" -#include - -#include -#include - -#include "goabackendprovider.h" -#include "goabackendoauthprovider.h" -#include "goabackendyahooprovider.h" - -/** - * GoaBackendYahooProvider: - * - * The #GoaBackendYahooProvider structure contains only private data and should - * only be accessed using the provided API. - */ -struct _GoaBackendYahooProvider -{ - /*< private >*/ - GoaBackendOAuthProvider parent_instance; -}; - -typedef struct _GoaBackendYahooProviderClass GoaBackendYahooProviderClass; - -struct _GoaBackendYahooProviderClass -{ - GoaBackendOAuthProviderClass parent_class; -}; - -/** - * SECTION:goabackendyahooprovider - * @title: GoaBackendYahooProvider - * @short_description: A provider for Yahoo - * - * #GoaBackendYahooProvider is used for handling Yahoo accounts. - */ - -G_DEFINE_TYPE_WITH_CODE (GoaBackendYahooProvider, goa_backend_yahoo_provider, GOA_TYPE_BACKEND_OAUTH_PROVIDER, - g_io_extension_point_implement (GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME, - g_define_type_id, - "yahoo", - 0)); - -/* ---------------------------------------------------------------------------------------------------- */ - -static const gchar * -get_provider_type (GoaBackendProvider *_provider) -{ - return "yahoo"; -} - -static const gchar * -get_name (GoaBackendProvider *_provider) -{ - return _("Yahoo Account"); -} - -static const gchar * -get_consumer_key (GoaBackendOAuthProvider *provider) -{ - return "dj0yJmk9VnBYMGpGRVFBUVl3JmQ9WVdrOWNWZDZiVTUwTldNbWNHbzlPVFF5TURrNE5UWXkmcz1jb25zdW1lcnNlY3JldCZ4PTQ0"; -} - -static const gchar * -get_consumer_secret (GoaBackendOAuthProvider *provider) -{ - return "33dd9ebe9f5724deabe657eff1de7c3f151cf7eb"; -} - -static const gchar * -get_request_uri (GoaBackendOAuthProvider *provider) -{ - return "https://api.login.yahoo.com/oauth/v2/get_request_token"; -} - -static const gchar * -get_authorization_uri (GoaBackendOAuthProvider *provider) -{ - return "https://api.login.yahoo.com/oauth/v2/request_auth"; -} - -static const gchar * -get_token_uri (GoaBackendOAuthProvider *provider) -{ - return "https://api.login.yahoo.com/oauth/v2/get_token"; -} - -static const gchar * -get_callback_uri (GoaBackendOAuthProvider *provider) -{ - return "https://www.gnome.org/goa-1.0/oauth"; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gchar * -get_identity_sync (GoaBackendOAuthProvider *provider, - const gchar *access_token, - const gchar *access_token_secret, - gchar **out_name, - GCancellable *cancellable, - GError **error) -{ - RestProxy *proxy; - RestProxyCall *call; - JsonParser *parser; - JsonObject *json_object; - JsonObject *json_data_object; - gchar *ret; - gchar *guid; - gchar *name; - - ret = NULL; - proxy = NULL; - call = NULL; - parser = NULL; - guid = NULL; - name = NULL; - - /* TODO: cancellable */ - - proxy = oauth_proxy_new_with_token (goa_backend_oauth_provider_get_consumer_key (provider), - goa_backend_oauth_provider_get_consumer_secret (provider), - access_token, - access_token_secret, - "http://social.yahooapis.com/v1/me/guid", - FALSE); - call = rest_proxy_new_call (proxy); - rest_proxy_call_set_method (call, "GET"); - rest_proxy_call_add_param (call, "format", "json"); - - if (!rest_proxy_call_sync (call, error)) - goto out; - if (rest_proxy_call_get_status_code (call) != 200) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Expected status 200 when requesting guid, instead got status %d (%s)"), - rest_proxy_call_get_status_code (call), - rest_proxy_call_get_status_message (call)); - goto out; - } - - parser = json_parser_new (); - if (!json_parser_load_from_data (parser, - rest_proxy_call_get_payload (call), - rest_proxy_call_get_payload_length (call), - error)) - { - g_prefix_error (error, _("Error parsing response as JSON: ")); - goto out; - } - - json_object = json_node_get_object (json_parser_get_root (parser)); - json_data_object = json_object_get_object_member (json_object, "guid"); - if (json_data_object == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Didn't find guid member in JSON data")); - goto out; - } - - guid = g_strdup (json_object_get_string_member (json_data_object, "value")); - if (guid == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Didn't find value member in JSON data")); - goto out; - } - - /* OK, got the GUID, now get the name via http://developer.yahoo.com/social/rest_api_guide/usercard-resource.html */ - g_object_unref (proxy); - g_object_unref (call); - proxy = oauth_proxy_new_with_token (goa_backend_oauth_provider_get_consumer_key (provider), - goa_backend_oauth_provider_get_consumer_secret (provider), - access_token, - access_token_secret, - "http://social.yahooapis.com/v1/user/%s/profile/usercard", - TRUE); - rest_proxy_bind (proxy, guid); - call = rest_proxy_new_call (proxy); - rest_proxy_call_set_method (call, "GET"); - rest_proxy_call_add_param (call, "format", "json"); - - if (!rest_proxy_call_sync (call, error)) - goto out; - - if (rest_proxy_call_get_status_code (call) != 200) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Expected status 200 when requesting name, instead got status %d (%s)"), - rest_proxy_call_get_status_code (call), - rest_proxy_call_get_status_message (call)); - goto out; - } - - g_object_unref (parser); - parser = json_parser_new (); - if (!json_parser_load_from_data (parser, - rest_proxy_call_get_payload (call), - rest_proxy_call_get_payload_length (call), - error)) - { - g_prefix_error (error, _("Error parsing usercard response as JSON: ")); - goto out; - } - - json_object = json_node_get_object (json_parser_get_root (parser)); - json_data_object = json_object_get_object_member (json_object, "profile"); - if (json_data_object == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Didn't find profile member in JSON data")); - goto out; - } - - name = g_strdup (json_object_get_string_member (json_data_object, "nickname")); - if (name == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - _("Didn't find nickname member in JSON data")); - goto out; - } - - ret = guid; - guid = NULL; - if (out_name != NULL) - { - *out_name = name; - name = NULL; - } - - out: - g_free (name); - g_free (guid); - if (call != NULL) - g_object_unref (call); - if (proxy != NULL) - g_object_unref (proxy); - return ret; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static gboolean -goa_backend_yahoo_provider_build_object (GoaBackendProvider *provider, - GoaObjectSkeleton *object, - GKeyFile *key_file, - const gchar *group, - GError **error) -{ - GoaAccount *account; - GoaYahooAccount *yahoo_account; - gboolean ret; - gchar *guid; - - guid = NULL; - account = NULL; - yahoo_account = NULL; - ret = FALSE; - - /* Chain up */ - if (!GOA_BACKEND_PROVIDER_CLASS (goa_backend_yahoo_provider_parent_class)->build_object (provider, - object, - key_file, - group, - error)) - goto out; - - account = goa_object_get_account (GOA_OBJECT (object)); - yahoo_account = goa_object_get_yahoo_account (GOA_OBJECT (object)); - if (yahoo_account == NULL) - { - yahoo_account = goa_yahoo_account_skeleton_new (); - goa_object_skeleton_set_yahoo_account (object, yahoo_account); - } - - guid = g_key_file_get_string (key_file, group, "Identity", NULL); - if (guid == NULL) - { - g_set_error (error, - GOA_ERROR, - GOA_ERROR_FAILED, - "Invalid identity %s for id %s", - guid, - goa_account_get_id (account)); - goto out; - } - - goa_yahoo_account_set_guid (yahoo_account, guid); - - ret = TRUE; - - out: - g_free (guid); - if (yahoo_account != NULL) - g_object_unref (yahoo_account); - if (account != NULL) - g_object_unref (account); - return ret; -} - -static gboolean -get_use_external_browser (GoaBackendOAuthProvider *provider) -{ - return FALSE; -} - -/* ---------------------------------------------------------------------------------------------------- */ - -static void -goa_backend_yahoo_provider_init (GoaBackendYahooProvider *client) -{ -} - -static void -goa_backend_yahoo_provider_class_init (GoaBackendYahooProviderClass *klass) -{ - GoaBackendProviderClass *provider_class; - GoaBackendOAuthProviderClass *oauth_class; - - provider_class = GOA_BACKEND_PROVIDER_CLASS (klass); - provider_class->get_provider_type = get_provider_type; - provider_class->get_name = get_name; - provider_class->build_object = goa_backend_yahoo_provider_build_object; - - oauth_class = GOA_BACKEND_OAUTH_PROVIDER_CLASS (klass); - oauth_class->get_identity_sync = get_identity_sync; - oauth_class->get_consumer_key = get_consumer_key; - oauth_class->get_consumer_secret = get_consumer_secret; - oauth_class->get_request_uri = get_request_uri; - oauth_class->get_authorization_uri = get_authorization_uri; - oauth_class->get_token_uri = get_token_uri; - oauth_class->get_callback_uri = get_callback_uri; - oauth_class->get_use_external_browser = get_use_external_browser; -} diff --git a/src/goa/goabackendyahooprovider.h b/src/goa/goabackendyahooprovider.h deleted file mode 100644 index cd6c106..0000000 --- a/src/goa/goabackendyahooprovider.h +++ /dev/null @@ -1,42 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * Copyright (C) 2011 Red Hat, Inc. - * - * 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 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: David Zeuthen - */ - -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __GOA_BACKEND_YAHOO_PROVIDER_H__ -#define __GOA_BACKEND_YAHOO_PROVIDER_H__ - -#include - -G_BEGIN_DECLS - -#define GOA_TYPE_BACKEND_YAHOO_PROVIDER (goa_backend_yahoo_provider_get_type ()) -#define GOA_BACKEND_YAHOO_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_YAHOO_PROVIDER, GoaBackendYahooProvider)) -#define GOA_IS_BACKEND_YAHOO_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_YAHOO_PROVIDER)) - -GType goa_backend_yahoo_provider_get_type (void) G_GNUC_CONST; - -G_END_DECLS - -#endif /* __GOA_BACKEND_YAHOO_PROVIDER_H__ */ diff --git a/src/goabackend/goabackend.h b/src/goabackend/goabackend.h new file mode 100644 index 0000000..670d513 --- /dev/null +++ b/src/goabackend/goabackend.h @@ -0,0 +1,45 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __GOA_BACKEND_H__ +#define __GOA_BACKEND_H__ + +#if !defined(GOA_BACKEND_API_IS_SUBJECT_TO_CHANGE) && !defined(GOA_BACKEND_COMPILATION) +#error libgoa-backend is unstable API. You must define GOA_API_IS_SUBJECT_TO_CHANGE before including goabackend/goabackend.h +#endif + +#define __GOA_BACKEND_INSIDE_GOA_BACKEND_H__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#undef __GOA_BACKEND_INSIDE_GOA_BACKEND_H__ + +#endif /* __GOA_BACKEND_H__ */ diff --git a/src/goabackend/goabackendenums.h b/src/goabackend/goabackendenums.h new file mode 100644 index 0000000..8cd0899 --- /dev/null +++ b/src/goabackend/goabackendenums.h @@ -0,0 +1,36 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GOA_BACKEND_ENUMS_H__ +#define __GOA_BACKEND_ENUMS_H__ + +#include + +G_BEGIN_DECLS + +G_END_DECLS + +#endif /* __GOA_BACKEND_ENUMS_H__ */ diff --git a/src/goabackend/goabackendenumtypes.c.template b/src/goabackend/goabackendenumtypes.c.template new file mode 100644 index 0000000..7a76459 --- /dev/null +++ b/src/goabackend/goabackendenumtypes.c.template @@ -0,0 +1,40 @@ +/*** BEGIN file-header ***/ +#include "goabackendenums.h" +#include "goabackendenumtypes.h" + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) + { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} + +/*** END value-tail ***/ + +/*** BEGIN file-tail ***/ +/*** END file-tail ***/ diff --git a/src/goabackend/goabackendenumtypes.h.template b/src/goabackend/goabackendenumtypes.h.template new file mode 100644 index 0000000..7321076 --- /dev/null +++ b/src/goabackend/goabackendenumtypes.h.template @@ -0,0 +1,24 @@ +/*** BEGIN file-header ***/ +#ifndef __GOA_BACKEND_ENUM_TYPES_H__ +#define __GOA_BACKEND_ENUM_TYPES_H__ + +#include + +G_BEGIN_DECLS +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) +/*** END value-header ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* __GOA_BACKEND_ENUM_TYPES_H__ */ +/*** END file-tail ***/ diff --git a/src/goabackend/goabackendtypes.h b/src/goabackend/goabackendtypes.h new file mode 100644 index 0000000..2ffb784 --- /dev/null +++ b/src/goabackend/goabackendtypes.h @@ -0,0 +1,71 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GOA_BACKEND_TYPES_H__ +#define __GOA_BACKEND_TYPES_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +struct _GoaProvider; +typedef struct _GoaProvider GoaProvider; + +struct _GoaOAuthProvider; +typedef struct _GoaOAuthProvider GoaOAuthProvider; + +struct _GoaOAuth2Provider; +typedef struct _GoaOAuth2Provider GoaOAuth2Provider; + +struct _GoaGoogleProvider; +typedef struct _GoaGoogleProvider GoaGoogleProvider; + +struct _GoaFacebookProvider; +typedef struct _GoaFacebookProvider GoaFacebookProvider; + +struct _GoaYahooProvider; +typedef struct _GoaYahooProvider GoaYahooProvider; + +struct _GoaTwitterProvider; +typedef struct _GoaTwitterProvider GoaTwitterProvider; + +struct _GoaImapAuth; +typedef struct _GoaImapAuth GoaImapAuth; + +struct _GoaImapAuthOAuth; +typedef struct _GoaImapAuthOAuth GoaImapAuthOAuth; + +struct _GoaImapClient; +typedef struct _GoaImapClient GoaImapClient; + +struct _GoaImapMail; +typedef struct _GoaImapMail GoaImapMail; + +G_END_DECLS + +#endif /* __GOA_BACKEND_TYPES_H__ */ diff --git a/src/goabackend/goafacebookprovider.c b/src/goabackend/goafacebookprovider.c new file mode 100644 index 0000000..bf39d16 --- /dev/null +++ b/src/goabackend/goafacebookprovider.c @@ -0,0 +1,307 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" +#include + +#include +#include + +#include "goaprovider.h" +#include "goaoauth2provider.h" +#include "goafacebookprovider.h" + +/** + * GoaFacebookProvider: + * + * The #GoaFacebookProvider structure contains only private data and should + * only be accessed using the provided API. + */ +struct _GoaFacebookProvider +{ + /*< private >*/ + GoaOAuth2Provider parent_instance; +}; + +typedef struct _GoaFacebookProviderClass GoaFacebookProviderClass; + +struct _GoaFacebookProviderClass +{ + GoaOAuth2ProviderClass parent_class; +}; + +/** + * SECTION:goafacebookprovider + * @title: GoaFacebookProvider + * @short_description: A provider for Facebook + * + * #GoaFacebookProvider is used for handling Facebook accounts. + */ + +G_DEFINE_TYPE_WITH_CODE (GoaFacebookProvider, goa_facebook_provider, GOA_TYPE_OAUTH2_PROVIDER, + g_io_extension_point_implement (GOA_PROVIDER_EXTENSION_POINT_NAME, + g_define_type_id, + "facebook", + 0)); + +/* ---------------------------------------------------------------------------------------------------- */ + +static const gchar * +get_provider_type (GoaProvider *_provider) +{ + return "facebook"; +} + +static const gchar * +get_name (GoaProvider *_provider) +{ + return _("Facebook Account"); +} + +static const gchar * +get_authorization_uri (GoaOAuth2Provider *provider) +{ + return "https://www.facebook.com/dialog/oauth"; +} + + +static const gchar * +get_token_uri (GoaOAuth2Provider *provider) +{ + return "https://graph.facebook.com/oauth/access_token"; +} + + +static const gchar * +get_redirect_uri (GoaOAuth2Provider *provider) +{ + return "https://www.gnome.org/goa-1.0/oauth2?callback=1"; +} + +static const gchar * +get_scope (GoaOAuth2Provider *provider) +{ + /* see https://developers.facebook.com/docs/authentication/permissions/ */ + return + "user_events," + "read_mailbox," + "offline_access"; +} + +static const gchar * +get_client_id (GoaOAuth2Provider *provider) +{ + return "103995033022129"; +} + +static const gchar * +get_client_secret (GoaOAuth2Provider *provider) +{ + return "c3a9f8d49188a6dd8b596df48a90b94a"; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +get_identity_sync (GoaOAuth2Provider *provider, + const gchar *access_token, + gchar **out_name, + GCancellable *cancellable, + GError **error) +{ + RestProxy *proxy; + RestProxyCall *call; + JsonParser *parser; + JsonObject *json_object; + gchar *ret; + gchar *id; + gchar *name; + + ret = NULL; + proxy = NULL; + call = NULL; + parser = NULL; + id = NULL; + name = NULL; + + /* TODO: cancellable */ + + proxy = rest_proxy_new ("https://graph.facebook.com/me", FALSE); + call = rest_proxy_new_call (proxy); + rest_proxy_call_set_method (call, "GET"); + rest_proxy_call_add_param (call, "access_token", access_token); + + if (!rest_proxy_call_sync (call, error)) + goto out; + if (rest_proxy_call_get_status_code (call) != 200) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Expected status 200 when requesting guid, instead got status %d (%s)"), + rest_proxy_call_get_status_code (call), + rest_proxy_call_get_status_message (call)); + goto out; + } + + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, + rest_proxy_call_get_payload (call), + rest_proxy_call_get_payload_length (call), + error)) + { + g_prefix_error (error, _("Error parsing response as JSON: ")); + goto out; + } + + json_object = json_node_get_object (json_parser_get_root (parser)); + id = g_strdup (json_object_get_string_member (json_object, "username")); + if (id == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Didn't find username member in JSON data")); + goto out; + } + name = g_strdup (json_object_get_string_member (json_object, "name")); + if (name == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Didn't find name member in JSON data")); + goto out; + } + + ret = id; + id = NULL; + if (out_name != NULL) + { + *out_name = name; + name = NULL; + } + + out: + g_free (id); + g_free (name); + if (call != NULL) + g_object_unref (call); + if (proxy != NULL) + g_object_unref (proxy); + return ret; +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +build_object (GoaProvider *provider, + GoaObjectSkeleton *object, + GKeyFile *key_file, + const gchar *group, + GError **error) +{ + GoaAccount *account; + GoaFacebookAccount *facebook_account; + gboolean ret; + gchar *user_name; + + user_name = NULL; + account = NULL; + facebook_account = NULL; + ret = FALSE; + + /* Chain up */ + if (!GOA_PROVIDER_CLASS (goa_facebook_provider_parent_class)->build_object (provider, + object, + key_file, + group, + error)) + goto out; + + account = goa_object_get_account (GOA_OBJECT (object)); + facebook_account = goa_object_get_facebook_account (GOA_OBJECT (object)); + if (facebook_account == NULL) + { + facebook_account = goa_facebook_account_skeleton_new (); + goa_object_skeleton_set_facebook_account (object, facebook_account); + } + + user_name = g_key_file_get_string (key_file, group, "Identity", NULL); + if (user_name == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + "Invalid identity %s for id %s", + user_name, + goa_account_get_id (account)); + goto out; + } + + goa_facebook_account_set_user_name (facebook_account, user_name); + + ret = TRUE; + + out: + if (facebook_account != NULL) + g_object_unref (facebook_account); + if (account != NULL) + g_object_unref (account); + return ret; +} + +static gboolean +get_use_external_browser (GoaOAuth2Provider *provider) +{ + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +goa_facebook_provider_init (GoaFacebookProvider *client) +{ +} + +static void +goa_facebook_provider_class_init (GoaFacebookProviderClass *klass) +{ + GoaProviderClass *provider_class; + GoaOAuth2ProviderClass *oauth2_class; + + provider_class = GOA_PROVIDER_CLASS (klass); + provider_class->get_provider_type = get_provider_type; + provider_class->get_name = get_name; + provider_class->build_object = build_object; + + oauth2_class = GOA_OAUTH2_PROVIDER_CLASS (klass); + oauth2_class->get_authorization_uri = get_authorization_uri; + oauth2_class->get_token_uri = get_token_uri; + oauth2_class->get_redirect_uri = get_redirect_uri; + oauth2_class->get_scope = get_scope; + oauth2_class->get_client_id = get_client_id; + oauth2_class->get_client_secret = get_client_secret; + oauth2_class->get_identity_sync = get_identity_sync; + oauth2_class->get_use_external_browser = get_use_external_browser; +} diff --git a/src/goabackend/goafacebookprovider.h b/src/goabackend/goafacebookprovider.h new file mode 100644 index 0000000..be9655f --- /dev/null +++ b/src/goabackend/goafacebookprovider.h @@ -0,0 +1,42 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GOA_FACEBOOK_PROVIDER_H__ +#define __GOA_FACEBOOK_PROVIDER_H__ + +#include + +G_BEGIN_DECLS + +#define GOA_TYPE_FACEBOOK_PROVIDER (goa_facebook_provider_get_type ()) +#define GOA_FACEBOOK_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_FACEBOOK_PROVIDER, GoaFacebookProvider)) +#define GOA_IS_FACEBOOK_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_FACEBOOK_PROVIDER)) + +GType goa_facebook_provider_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __GOA_FACEBOOK_PROVIDER_H__ */ diff --git a/src/goabackend/goagoogleprovider.c b/src/goabackend/goagoogleprovider.c new file mode 100644 index 0000000..8653eb8 --- /dev/null +++ b/src/goabackend/goagoogleprovider.c @@ -0,0 +1,351 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" +#include + +#include +#include + +#include "goaprovider.h" +#include "goaoauthprovider.h" +#include "goagoogleprovider.h" + +#include "goaimapmail.h" +#include "goaimapauthoauth.h" + +/** + * GoaGoogleProvider: + * + * The #GoaGoogleProvider structure contains only private data and should + * only be accessed using the provided API. + */ +struct _GoaGoogleProvider +{ + /*< private >*/ + GoaOAuthProvider parent_instance; +}; + +typedef struct _GoaGoogleProviderClass GoaGoogleProviderClass; + +struct _GoaGoogleProviderClass +{ + GoaOAuthProviderClass parent_class; +}; + +/** + * SECTION:goagoogleprovider + * @title: GoaGoogleProvider + * @short_description: A provider for Google + * + * #GoaGoogleProvider is used for handling Google accounts. + */ + +G_DEFINE_TYPE_WITH_CODE (GoaGoogleProvider, goa_google_provider, GOA_TYPE_OAUTH_PROVIDER, + g_io_extension_point_implement (GOA_PROVIDER_EXTENSION_POINT_NAME, + g_define_type_id, + "google", + 0)); + +/* ---------------------------------------------------------------------------------------------------- */ + +static const gchar * +get_provider_type (GoaProvider *_provider) +{ + return "google"; +} + +static const gchar * +get_name (GoaProvider *_provider) +{ + return _("Google Account"); +} + +static const gchar * +get_consumer_key (GoaOAuthProvider *provider) +{ + return "anonymous"; +} + +static const gchar * +get_consumer_secret (GoaOAuthProvider *provider) +{ + return "anonymous"; +} + +static const gchar * +get_request_uri (GoaOAuthProvider *provider) +{ + return "https://www.google.com/accounts/OAuthGetRequestToken"; +} + +static gchar ** +get_request_uri_params (GoaOAuthProvider *provider) +{ + GPtrArray *p; + p = g_ptr_array_new (); + g_ptr_array_add (p, g_strdup ("xoauth_displayname")); + g_ptr_array_add (p, g_strdup ("GNOME")); + + g_ptr_array_add (p, g_strdup ("scope")); + g_ptr_array_add (p, g_strdup ( + /* Display email address: cf. https://sites.google.com/site/oauthgoog/Home/emaildisplayscope */ + "https://www.googleapis.com/auth/userinfo#email " + /* IMAP, SMTP access: http://code.google.com/apis/gmail/oauth/protocol.html */ + "https://mail.google.com/ " + /* Calendar data API: http://code.google.com/apis/calendar/data/2.0/developers_guide.html */ + "https://www.google.com/calendar/feeds")); + g_ptr_array_add (p, NULL); + return (gchar **) g_ptr_array_free (p, FALSE); +} + +static const gchar * +get_authorization_uri (GoaOAuthProvider *provider) +{ + return "https://www.google.com/accounts/OAuthAuthorizeToken"; +} + +static const gchar * +get_token_uri (GoaOAuthProvider *provider) +{ + return "https://www.google.com/accounts/OAuthGetAccessToken"; +} + +static const gchar * +get_callback_uri (GoaOAuthProvider *provider) +{ + return "https://www.gnome.org/goa-1.0/oauth"; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +get_identity_sync (GoaOAuthProvider *provider, + const gchar *access_token, + const gchar *access_token_secret, + gchar **out_name, + GCancellable *cancellable, + GError **error) +{ + RestProxy *proxy; + RestProxyCall *call; + JsonParser *parser; + JsonObject *json_object; + JsonObject *json_data_object; + gchar *ret; + gchar *email; + + ret = NULL; + proxy = NULL; + call = NULL; + parser = NULL; + email = NULL; + + /* TODO: cancellable */ + + proxy = oauth_proxy_new_with_token (goa_oauth_provider_get_consumer_key (provider), + goa_oauth_provider_get_consumer_secret (provider), + access_token, + access_token_secret, + "https://www.googleapis.com/userinfo/email", + FALSE); + call = rest_proxy_new_call (proxy); + rest_proxy_call_set_method (call, "GET"); + rest_proxy_call_add_param (call, "alt", "json"); + + if (!rest_proxy_call_sync (call, error)) + goto out; + if (rest_proxy_call_get_status_code (call) != 200) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Expected status 200 when requesting guid, instead got status %d (%s)"), + rest_proxy_call_get_status_code (call), + rest_proxy_call_get_status_message (call)); + goto out; + } + + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, + rest_proxy_call_get_payload (call), + rest_proxy_call_get_payload_length (call), + error)) + { + g_prefix_error (error, _("Error parsing response as JSON: ")); + goto out; + } + + json_object = json_node_get_object (json_parser_get_root (parser)); + json_data_object = json_object_get_object_member (json_object, "data"); + if (json_data_object == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Didn't find data member in JSON data")); + goto out; + } + + email = g_strdup (json_object_get_string_member (json_data_object, "email")); + if (email == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Didn't find email member in JSON data")); + goto out; + } + + + ret = email; + email = NULL; + if (out_name != NULL) + *out_name = g_strdup (ret); /* for now: use email as name */ + + out: + g_free (email); + if (call != NULL) + g_object_unref (call); + if (proxy != NULL) + g_object_unref (proxy); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +build_object (GoaProvider *provider, + GoaObjectSkeleton *object, + GKeyFile *key_file, + const gchar *group, + GError **error) +{ + GoaAccount *account; + GoaGoogleAccount *google_account; + GoaMail *mail; + gboolean ret; + gchar *email_address; + + email_address = NULL; + account = NULL; + google_account = NULL; + mail = NULL; + ret = FALSE; + + /* Chain up */ + if (!GOA_PROVIDER_CLASS (goa_google_provider_parent_class)->build_object (provider, + object, + key_file, + group, + error)) + goto out; + + account = goa_object_get_account (GOA_OBJECT (object)); + google_account = goa_object_get_google_account (GOA_OBJECT (object)); + if (google_account == NULL) + { + google_account = goa_google_account_skeleton_new (); + goa_object_skeleton_set_google_account (object, google_account); + } + + email_address = g_key_file_get_string (key_file, group, "Identity", NULL); + if (email_address == NULL /* || !is_valid_email_address () */) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + "Invalid identity %s for id %s", + email_address, + goa_account_get_id (account)); + goto out; + } + + goa_google_account_set_email_address (google_account, email_address); + + mail = goa_object_get_mail (GOA_OBJECT (object)); + if (mail == NULL) + { + GoaImapAuth *auth; + gchar *request_uri; + request_uri = g_strdup_printf ("https://mail.google.com/mail/b/%s/imap/", email_address); + auth = goa_imap_auth_oauth_new (GOA_OAUTH_PROVIDER (provider), + GOA_OBJECT (object), + request_uri); + mail = goa_imap_mail_new ("imap.gmail.com", TRUE, auth); + goa_object_skeleton_set_mail (object, mail); + g_object_unref (auth); + g_free (request_uri); + } + + ret = TRUE; + + out: + g_free (email_address); + if (mail != NULL) + g_object_unref (mail); + if (google_account != NULL) + g_object_unref (google_account); + if (account != NULL) + g_object_unref (account); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +get_use_external_browser (GoaOAuthProvider *provider) +{ + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +goa_google_provider_init (GoaGoogleProvider *client) +{ +} + +static void +goa_google_provider_class_init (GoaGoogleProviderClass *klass) +{ + GoaProviderClass *provider_class; + GoaOAuthProviderClass *oauth_class; + + provider_class = GOA_PROVIDER_CLASS (klass); + provider_class->get_provider_type = get_provider_type; + provider_class->get_name = get_name; + provider_class->build_object = build_object; + + oauth_class = GOA_OAUTH_PROVIDER_CLASS (klass); + oauth_class->get_identity_sync = get_identity_sync; + oauth_class->get_consumer_key = get_consumer_key; + oauth_class->get_consumer_secret = get_consumer_secret; + oauth_class->get_request_uri = get_request_uri; + oauth_class->get_request_uri_params = get_request_uri_params; + oauth_class->get_authorization_uri = get_authorization_uri; + oauth_class->get_token_uri = get_token_uri; + oauth_class->get_callback_uri = get_callback_uri; + oauth_class->get_use_external_browser = get_use_external_browser; +} + +/* ---------------------------------------------------------------------------------------------------- */ diff --git a/src/goabackend/goagoogleprovider.h b/src/goabackend/goagoogleprovider.h new file mode 100644 index 0000000..b7b07d4 --- /dev/null +++ b/src/goabackend/goagoogleprovider.h @@ -0,0 +1,42 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GOA_GOOGLE_PROVIDER_H__ +#define __GOA_GOOGLE_PROVIDER_H__ + +#include + +G_BEGIN_DECLS + +#define GOA_TYPE_GOOGLE_PROVIDER (goa_google_provider_get_type ()) +#define GOA_GOOGLE_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_GOOGLE_PROVIDER, GoaGoogleProvider)) +#define GOA_IS_GOOGLE_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_GOOGLE_PROVIDER)) + +GType goa_google_provider_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __GOA_GOOGLE_PROVIDER_H__ */ diff --git a/src/goabackend/goaimapauth.c b/src/goabackend/goaimapauth.c new file mode 100644 index 0000000..f8d0bf3 --- /dev/null +++ b/src/goabackend/goaimapauth.c @@ -0,0 +1,84 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" +#include +#include + +#include "goaimapauth.h" + +/** + * SECTION:goaimapauth + * @title: GoaImapAuth + * @short_description: Helper type for authenticating IMAP connections + * + * #GoaImapAuth is an abstract type used for authenticating + * IMAP connections. See #GoaImapAuthOAuth for a concrete + * implementation. + */ + +G_DEFINE_ABSTRACT_TYPE (GoaImapAuth, goa_imap_auth, G_TYPE_OBJECT); + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +goa_imap_auth_init (GoaImapAuth *client) +{ +} + +static void +goa_imap_auth_class_init (GoaImapAuthClass *klass) +{ +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * goa_imap_auth_run_sync: + * @auth: A #GoaImapAuth. + * @input: A valid #GDataInputStream. + * @output: A valid #GDataOutputStream. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Authenticates the IMAP connection represented by @input and + * @output. This method blocks the calling thread until authentication + * is done. + * + * Returns: %TRUE if authentication succeeded, %FALSE if @error is + * set. + */ +gboolean +goa_imap_auth_run_sync (GoaImapAuth *auth, + GDataInputStream *input, + GDataOutputStream *output, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (GOA_IS_IMAP_AUTH (auth), FALSE); + g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (input), FALSE); + g_return_val_if_fail (G_IS_DATA_OUTPUT_STREAM (output), FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + return GOA_IMAP_AUTH_GET_CLASS (auth)->run_sync (auth, input, output, cancellable, error); +} + +/* ---------------------------------------------------------------------------------------------------- */ diff --git a/src/goabackend/goaimapauth.h b/src/goabackend/goaimapauth.h new file mode 100644 index 0000000..b60e08c --- /dev/null +++ b/src/goabackend/goaimapauth.h @@ -0,0 +1,88 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GOA_IMAP_AUTH_H__ +#define __GOA_IMAP_AUTH_H__ + +#include + +G_BEGIN_DECLS + +#define GOA_TYPE_IMAP_AUTH (goa_imap_auth_get_type ()) +#define GOA_IMAP_AUTH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_IMAP_AUTH, GoaImapAuth)) +#define GOA_IMAP_AUTH_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOA_TYPE_IMAP_AUTH, GoaImapAuthClass)) +#define GOA_IMAP_AUTH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOA_TYPE_IMAP_AUTH, GoaImapAuthClass)) +#define GOA_IS_IMAP_AUTH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_IMAP_AUTH)) +#define GOA_IS_IMAP_AUTH_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOA_TYPE_IMAP_AUTH)) + +struct _GoaImapAuthClass; +struct _GoaImapAuthPrivate; +typedef struct _GoaImapAuthClass GoaImapAuthClass; +typedef struct _GoaImapAuthPrivate GoaImapAuthPrivate; + +/** + * GoaImapAuth: + * + * The #GoaImapAuth structure contains only private data and + * should only be accessed using the provided API. + */ +struct _GoaImapAuth +{ + /*< private >*/ + GObject parent_instance; + GoaImapAuthPrivate *priv; +}; + +/** + * GoaImapAuthClass: + * @parent_class: The parent class + * @run_sync: Virtual function for the goa_imap_auth_run_sync() method. + * + * Class structure for #GoaImapAuth. + */ +struct _GoaImapAuthClass +{ + GObjectClass parent_class; + gboolean (*run_sync) (GoaImapAuth *auth, + GDataInputStream *input, + GDataOutputStream *output, + GCancellable *cancellable, + GError **error); + /*< private >*/ + /* Padding for future expansion */ + gpointer goa_reserved[8]; +}; + +GType goa_imap_auth_get_type (void) G_GNUC_CONST; +gboolean goa_imap_auth_run_sync (GoaImapAuth *auth, + GDataInputStream *input, + GDataOutputStream *output, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* __GOA_IMAP_AUTH_H__ */ diff --git a/src/goabackend/goaimapauthoauth.c b/src/goabackend/goaimapauthoauth.c new file mode 100644 index 0000000..5b100f5 --- /dev/null +++ b/src/goabackend/goaimapauthoauth.c @@ -0,0 +1,568 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" +#include +#include + +#include "goaimapauth.h" +#include "goaimapauthoauth.h" +#include "goaoauthprovider.h" + +/** + * SECTION:goaimapauthoauth + * @title: GoaImapAuthOAuth + * @short_description: XOAUTH authentication method for IMAP + * + * #GoaImapAuthOAuth implements the XOAUTH + * authentication method for IMAP. + */ + +/** + * GoaImapAuthOAuth: + * + * The #GoaImapAuthOAuth structure contains only private data + * and should only be accessed using the provided API. + */ +struct _GoaImapAuthOAuth +{ + GoaImapAuth parent_instance; + + GoaOAuthProvider *provider; + GoaObject *object; + gchar *request_uri; +}; + +typedef struct +{ + GoaImapAuthClass parent_class; + +} GoaImapAuthOAuthClass; + +enum +{ + PROP_0, + PROP_PROVIDER, + PROP_OBJECT, + PROP_REQUEST_URI +}; + +static gboolean goa_imap_auth_oauth_run_sync (GoaImapAuth *_auth, + GDataInputStream *input, + GDataOutputStream *output, + GCancellable *cancellable, + GError **error); + +G_DEFINE_TYPE (GoaImapAuthOAuth, goa_imap_auth_oauth, GOA_TYPE_IMAP_AUTH); + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +goa_imap_auth_oauth_finalize (GObject *object) +{ + GoaImapAuthOAuth *auth = GOA_IMAP_AUTH_OAUTH (object); + + g_object_unref (auth->provider); + g_object_unref (auth->object); + + G_OBJECT_CLASS (goa_imap_auth_oauth_parent_class)->finalize (object); +} + +static void +goa_imap_auth_oauth_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GoaImapAuthOAuth *auth = GOA_IMAP_AUTH_OAUTH (object); + + switch (prop_id) + { + case PROP_PROVIDER: + g_value_set_object (value, auth->provider); + break; + + case PROP_OBJECT: + g_value_set_object (value, auth->object); + break; + + case PROP_REQUEST_URI: + g_value_set_string (value, auth->request_uri); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +goa_imap_auth_oauth_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GoaImapAuthOAuth *auth = GOA_IMAP_AUTH_OAUTH (object); + + switch (prop_id) + { + case PROP_PROVIDER: + auth->provider = g_value_dup_object (value); + break; + + case PROP_OBJECT: + auth->object = g_value_dup_object (value); + break; + + case PROP_REQUEST_URI: + auth->request_uri = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + + +static void +goa_imap_auth_oauth_init (GoaImapAuthOAuth *client) +{ +} + +static void +goa_imap_auth_oauth_class_init (GoaImapAuthOAuthClass *klass) +{ + GObjectClass *gobject_class; + GoaImapAuthClass *auth_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = goa_imap_auth_oauth_finalize; + gobject_class->get_property = goa_imap_auth_oauth_get_property; + gobject_class->set_property = goa_imap_auth_oauth_set_property; + + auth_class = GOA_IMAP_AUTH_CLASS (klass); + auth_class->run_sync = goa_imap_auth_oauth_run_sync; + + /** + * GoaImapAuthOAuth:provider: + * + * The #GoaOAuthProvider object to use when calculating the XOAUTH mechanism parameter. + */ + g_object_class_install_property (gobject_class, + PROP_PROVIDER, + g_param_spec_object ("provider", + "provider", + "provider", + GOA_TYPE_OAUTH_PROVIDER, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * GoaImapAuthOAuth:object: + * + * The #GoaObject object to use when calculating the XOAUTH mechanism parameter. + */ + g_object_class_install_property (gobject_class, + PROP_OBJECT, + g_param_spec_object ("object", + "object", + "object", + GOA_TYPE_OBJECT, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * GoaImapAuthOAuth:request-uri: + * + * The request URI to use when calculating the XOAUTH mechanism parameter. + */ + g_object_class_install_property (gobject_class, + PROP_REQUEST_URI, + g_param_spec_string ("request-uri", + "request-uri", + "request-uri", + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * goa_imap_auth_oauth_new: + * @provider: A #GoaOAuthProvider. + * @object: An account object. + * @request_uri: The request URI to use. + * + * Creates a new #GoaImapAuth to be used for XOAUTH authentication. + * + * Returns: (type GoaImapAuthOAuth): A #GoaImapAuthOAuth. Free with g_object_unref(). + */ +GoaImapAuth * +goa_imap_auth_oauth_new (GoaOAuthProvider *provider, + GoaObject *object, + const gchar *request_uri) +{ + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL); + g_return_val_if_fail (GOA_IS_OBJECT (object), NULL); + return GOA_IMAP_AUTH (g_object_new (GOA_TYPE_IMAP_AUTH_OAUTH, + "provider", provider, + "object", object, + "request-uri", request_uri, + NULL)); +} + +/* ---------------------------------------------------------------------------------------------------- */ + + +#include + +#define OAUTH_ENCODE_STRING(x_) (x_ ? soup_uri_encode( (x_), "!$&'()*+,;=@") : g_strdup ("")) + +#define SHA1_BLOCK_SIZE 64 +#define SHA1_LENGTH 20 + +/* + * hmac_sha1: + * @key: The key + * @message: The message + * + * Given the key and message, compute the HMAC-SHA1 hash and return the base-64 + * encoding of it. This is very geared towards OAuth, and as such both key and + * message must be NULL-terminated strings, and the result is base-64 encoded. + */ +static char * +hmac_sha1 (const char *key, const char *message) +{ + GChecksum *checksum; + char *real_key; + guchar ipad[SHA1_BLOCK_SIZE]; + guchar opad[SHA1_BLOCK_SIZE]; + guchar inner[SHA1_LENGTH]; + guchar digest[SHA1_LENGTH]; + gsize key_length, inner_length, digest_length; + int i; + + g_return_val_if_fail (key, NULL); + g_return_val_if_fail (message, NULL); + + checksum = g_checksum_new (G_CHECKSUM_SHA1); + + /* If the key is longer than the block size, hash it first */ + if (strlen (key) > SHA1_BLOCK_SIZE) { + guchar new_key[SHA1_LENGTH]; + + key_length = sizeof (new_key); + + g_checksum_update (checksum, (guchar*)key, strlen (key)); + g_checksum_get_digest (checksum, new_key, &key_length); + g_checksum_reset (checksum); + + real_key = g_memdup (new_key, key_length); + } else { + real_key = g_strdup (key); + key_length = strlen (key); + } + + /* Sanity check the length */ + g_assert (key_length <= SHA1_BLOCK_SIZE); + + /* Protect against use of the provided key by NULLing it */ + key = NULL; + + /* Stage 1 */ + memset (ipad, 0, sizeof (ipad)); + memset (opad, 0, sizeof (opad)); + + memcpy (ipad, real_key, key_length); + memcpy (opad, real_key, key_length); + + /* Stage 2 and 5 */ + for (i = 0; i < sizeof (ipad); i++) { + ipad[i] ^= 0x36; + opad[i] ^= 0x5C; + } + + /* Stage 3 and 4 */ + g_checksum_update (checksum, ipad, sizeof (ipad)); + g_checksum_update (checksum, (guchar*)message, strlen (message)); + inner_length = sizeof (inner); + g_checksum_get_digest (checksum, inner, &inner_length); + g_checksum_reset (checksum); + + /* Stage 6 and 7 */ + g_checksum_update (checksum, opad, sizeof (opad)); + g_checksum_update (checksum, inner, inner_length); + + digest_length = sizeof (digest); + g_checksum_get_digest (checksum, digest, &digest_length); + + g_checksum_free (checksum); + g_free (real_key); + + return g_base64_encode (digest, digest_length); +} + +static char * +sign_plaintext (const gchar *consumer_secret, + const gchar *token_secret) +{ + char *cs; + char *ts; + char *rv; + + cs = OAUTH_ENCODE_STRING (consumer_secret); + ts = OAUTH_ENCODE_STRING (token_secret); + rv = g_strconcat (cs, "&", ts, NULL); + + g_free (cs); + g_free (ts); + + return rv; +} + +static char * +sign_hmac (const gchar *consumer_secret, + const gchar *token_secret, + const gchar *http_method, + const gchar *request_uri, + const gchar *encoded_params) +{ + GString *text; + + text = g_string_new (NULL); + g_string_append (text, http_method); + g_string_append_c (text, '&'); + g_string_append_uri_escaped (text, request_uri, NULL, FALSE); + g_string_append_c (text, '&'); + g_string_append_uri_escaped (text, encoded_params, NULL, FALSE); + + /* PLAINTEXT signature value is the HMAC-SHA1 key value */ + gchar *key; + key = sign_plaintext (consumer_secret, token_secret); + + gchar *signature; + signature = hmac_sha1 (key, text->str); + + g_free (key); + g_string_free (text, TRUE); + + return signature; +} + +static GHashTable * +calculate_xoauth_params (const gchar *request_uri, + const gchar *consumer_key, + const gchar *consumer_secret, + const gchar *access_token, + const gchar *access_token_secret) +{ + gchar *signature; + GHashTable *params; + gchar *nonce; + gchar *timestamp; + GList *keys; + GList *l; + GString *normalized; + + nonce = g_strdup_printf ("%u", g_random_int ()); + timestamp = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) time (NULL)); + + params = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); + g_hash_table_insert (params, "oauth_consumer_key", g_strdup (consumer_key)); + g_hash_table_insert (params, "oauth_nonce", nonce); /* takes ownership */ + g_hash_table_insert (params, "oauth_timestamp", timestamp); /* takes ownership */ + g_hash_table_insert (params, "oauth_version", g_strdup ("1.0")); + g_hash_table_insert (params, "oauth_signature_method", g_strdup ("HMAC-SHA1")); + g_hash_table_insert (params, "oauth_token", g_strdup (access_token)); + + normalized = g_string_new (NULL); + keys = g_hash_table_get_keys (params); + keys = g_list_sort (keys, (GCompareFunc) g_strcmp0); /* TODO: locale specific? */ + for (l = keys; l != NULL; l = l->next) + { + const gchar *key = l->data; + const gchar *value; + gchar *k; + gchar *v; + + value = g_hash_table_lookup (params, key); + if (normalized->len > 0) + g_string_append_c (normalized, '&'); + + k = OAUTH_ENCODE_STRING (key); + v = OAUTH_ENCODE_STRING (value); + + g_string_append_printf (normalized, "%s=%s", k, v); + + g_free (k); + g_free (v); + } + g_list_free (keys); + + signature = sign_hmac (consumer_secret, + access_token_secret, + "GET", + request_uri, + normalized->str); + g_hash_table_insert (params, "oauth_signature", signature); /* takes ownership */ + + g_string_free (normalized, TRUE); + return params; +} + +static gchar * +calculate_xoauth_param (const gchar *request_uri, + const gchar *consumer_key, + const gchar *consumer_secret, + const gchar *access_token, + const gchar *access_token_secret, + GError **error) +{ + gchar *ret; + GString *str; + GHashTable *params; + GList *keys; + GList *l; + + params = calculate_xoauth_params (request_uri, + consumer_key, + consumer_secret, + access_token, + access_token_secret); + str = g_string_new ("GET "); + g_string_append (str, request_uri); + g_string_append_c (str, ' '); + keys = g_hash_table_get_keys (params); + keys = g_list_sort (keys, (GCompareFunc) g_strcmp0); /* TODO: locale specific? */ + for (l = keys; l != NULL; l = l->next) + { + const gchar *key = l->data; + const gchar *value; + gchar *k; + gchar *v; + + value = g_hash_table_lookup (params, key); + if (l != keys) + g_string_append_c (str, ','); + + k = OAUTH_ENCODE_STRING (key); + v = OAUTH_ENCODE_STRING (value); + g_string_append_printf (str, "%s=\"%s\"", k, v); + g_free (k); + g_free (v); + } + g_list_free (keys); + + ret = g_base64_encode ((const guchar *) str->str, str->len); + g_string_free (str, TRUE); + g_hash_table_unref (params); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +goa_imap_auth_oauth_run_sync (GoaImapAuth *_auth, + GDataInputStream *input, + GDataOutputStream *output, + GCancellable *cancellable, + GError **error) +{ + GoaImapAuthOAuth *auth = GOA_IMAP_AUTH_OAUTH (_auth); + gchar *access_token; + gchar *access_token_secret; + gchar *xoauth_param; + gchar *request; + gchar *response; + gboolean ret; + + access_token = NULL; + access_token_secret = NULL; + xoauth_param = NULL; + request = NULL; + response = NULL; + ret = FALSE; + + access_token = goa_oauth_provider_get_access_token_sync (auth->provider, + auth->object, + FALSE, /* force_refresh */ + &access_token_secret, + NULL, /* out_access_token_expires_in */ + NULL, /* GCancellable */ + error); /* GError */ + if (access_token == NULL) + goto out; + + xoauth_param = calculate_xoauth_param (auth->request_uri, + goa_oauth_provider_get_consumer_key (auth->provider), + goa_oauth_provider_get_consumer_secret (auth->provider), + access_token, + access_token_secret, + error); + if (xoauth_param == NULL) + goto out; + + request = g_strdup_printf ("A001 AUTHENTICATE XOAUTH %s\r\n", xoauth_param); + if (!g_data_output_stream_put_string (output, request, cancellable, error)) + goto out; + + again: + response = g_data_input_stream_read_line (input, NULL, cancellable, error); + if (response == NULL) + goto out; + /* ignore untagged responses */ + if (g_str_has_prefix (response, "*")) + { + g_free (response); + goto again; + } + if (!g_str_has_prefix (response, "A001 OK")) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + "Unexpected response `%s' while doing XOAUTH authentication", + response); + goto out; + } + + ret = TRUE; + + out: + g_free (response); + g_free (request); + g_free (xoauth_param); + g_free (access_token); + g_free (access_token_secret); + return ret; +} diff --git a/src/goabackend/goaimapauthoauth.h b/src/goabackend/goaimapauthoauth.h new file mode 100644 index 0000000..746c442 --- /dev/null +++ b/src/goabackend/goaimapauthoauth.h @@ -0,0 +1,46 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GOA_IMAP_AUTH_OAUTH_H__ +#define __GOA_IMAP_AUTH_OAUTH_H__ + +#include + +G_BEGIN_DECLS + +#define GOA_TYPE_IMAP_AUTH_OAUTH (goa_imap_auth_oauth_get_type ()) +#define GOA_IMAP_AUTH_OAUTH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_IMAP_AUTH_OAUTH, GoaImapAuthOAuth)) +#define GOA_IS_IMAP_AUTH_OAUTH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_IMAP_AUTH_OAUTH)) + + +GType goa_imap_auth_oauth_get_type (void) G_GNUC_CONST; +GoaImapAuth *goa_imap_auth_oauth_new (GoaOAuthProvider *provider, + GoaObject *object, + const gchar *request_uri); + +G_END_DECLS + +#endif /* __GOA_IMAP_AUTH_OAUTH_H__ */ diff --git a/src/goabackend/goaimapclient.c b/src/goabackend/goaimapclient.c new file mode 100644 index 0000000..73c3c36 --- /dev/null +++ b/src/goabackend/goaimapclient.c @@ -0,0 +1,694 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" +#include +#include + +#include "goaimapauth.h" +#include "goaimapclient.h" + +/* The timeout used for non-IDLE commands */ +#define COMMAND_TIMEOUT_SEC 30 + +/** + * GoaImapClient: + * + * The #GoaImapClient structure contains only private data and should + * only be accessed using the provided API. + */ +struct _GoaImapClient +{ + /*< private >*/ + GObject parent_instance; + + /* The remaining data members are related to the running session + */ + GSocketClient *sc; + GSocketConnection *c; + GDataInputStream *dis; + GDataOutputStream *dos; + + /* counter used used for generating command tags */ + guint tag; + + GMutex *lock; +}; + +typedef struct _GoaImapClientClass GoaImapClientClass; + +struct _GoaImapClientClass +{ + GObjectClass parent_class; + void (*untagged_response) (GoaImapClient *client, + const gchar *response); +}; + +/** + * SECTION:goaimapclient + * @title: GoaImapClient + * @short_description: A simple IMAP client + * + * #GoaImapClient provides a way to talk to + * IMAP + * servers. + */ + +enum +{ + UNTAGGED_RESPONSE_SIGNAL, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +G_DEFINE_TYPE (GoaImapClient, goa_imap_client, G_TYPE_OBJECT); + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +goa_imap_client_finalize (GObject *object) +{ + GoaImapClient *client = GOA_IMAP_CLIENT (object); + + g_clear_object (&client->sc); + g_clear_object (&client->c); + g_clear_object (&client->dis); + g_clear_object (&client->dos); + g_mutex_free (client->lock); + + G_OBJECT_CLASS (goa_imap_client_parent_class)->finalize (object); +} + +static void +goa_imap_client_init (GoaImapClient *client) +{ + client->lock = g_mutex_new (); +} + +static void +goa_imap_client_class_init (GoaImapClientClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = goa_imap_client_finalize; + + /** + * GoaImapClient::untagged-response: + * @client: The #GoaImapClient emitting the signal. + * @untagged_response: The untagged response. + * + * Signal emitted every an untagged + * response has been received. + * + * This signal is emitted in the same thread that calls the + * goa_imap_client_run_command_sync() method. + */ + signals[UNTAGGED_RESPONSE_SIGNAL] = + g_signal_new ("untagged-response", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GoaImapClientClass, untagged_response), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * goa_imap_client_new: + * + * Creates a new #GoaImapClient instance. + * + * Typical usage includes connecting to the + * #GoaImapClient::untagged-response signals and then invoking + * goa_imap_client_connect_sync(). + * + * You can then use goa_imap_client_connect_sync(), + * goa_imap_client_idle_sync() and the + * #GoaImapClient::untagged-response handler to interact with + * the IMAP server. If the connection fails then the appropriate error + * e.g. #G_IO_ERROR_TIMED_OUT or %G_IO_ERROR_NETWORK_UNREACHABLE is + * returned. + * + * You can only make a single successful connection with each + * #GoaImapClient instance - just create a new instance if the + * connection breaks and you need to reconnect. + * + * Returns: (transfer full): A #GoaImapClient that should be freed with g_object_unref(). + */ +GoaImapClient * +goa_imap_client_new (void) +{ + return GOA_IMAP_CLIENT (g_object_new (GOA_TYPE_IMAP_CLIENT, NULL)); +} + + +/** + * goa_imap_client_connect_sync: + * @client: A #GoaImapClient. + * @host_and_port: The name and optionally port to connect to. + * @use_tls: Whether TLS should be used. + * @auth: Object used for authenticating the connection. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Connects to the IMAP server represented by @host_and_port using + * @auth to authenticate the connection. The calling thread is blocked + * while the operation is pending. + * + * Returns: %TRUE if the connection was established and authentication + * worked, %FALSE if @error is set. + */ +gboolean +goa_imap_client_connect_sync (GoaImapClient *client, + const gchar *host_and_port, + gboolean use_tls, + GoaImapAuth *auth, + GCancellable *cancellable, + GError **error) +{ + gboolean ret; + + g_return_val_if_fail (GOA_IS_IMAP_CLIENT (client), FALSE); + g_return_val_if_fail (host_and_port != NULL, FALSE); + g_return_val_if_fail (GOA_IS_IMAP_AUTH (auth), FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ret = FALSE; + + g_mutex_lock (client->lock); + + if (client->sc != NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + "Already connected"); + goto out; + } + + client->sc = g_socket_client_new (); + if (use_tls) + g_socket_client_set_tls (client->sc, TRUE); + + /* TODO: TLS validation etc etc */ + + client->c = g_socket_client_connect_to_host (client->sc, + host_and_port, + use_tls ? 993 : 143, + cancellable, + error); + if (client->c == NULL) + goto out; + + /* fail quickly */ + g_socket_set_timeout (g_socket_connection_get_socket (client->c), COMMAND_TIMEOUT_SEC); + + client->dis = g_data_input_stream_new (g_io_stream_get_input_stream (G_IO_STREAM (client->c))); + client->dos = g_data_output_stream_new (g_io_stream_get_output_stream (G_IO_STREAM (client->c))); + g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (client->dis), FALSE); + g_filter_output_stream_set_close_base_stream (G_FILTER_OUTPUT_STREAM (client->dos), FALSE); + g_data_input_stream_set_newline_type (client->dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF); + + /* Authenticate via the passed in auth helper */ + if (!goa_imap_auth_run_sync (auth, + client->dis, + client->dos, + cancellable, + error)) + goto out; + + + ret = TRUE; + + out: + if (!ret) + { + g_clear_object (&client->sc); + g_clear_object (&client->c); + g_clear_object (&client->dis); + g_clear_object (&client->dos); + } + g_mutex_unlock (client->lock); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * goa_imap_client_run_command_sync: + * @client: A #GoaImapClient. + * @command: The command to run. + * @cancellable: A #GCancellable or %NULL. + * @error: Return location for error. + * + * Submits @command to the remote IMAP server and blocks the calling + * thread until a response is received. Do not include a command tag + * in @command - this will automatically be appended. + * + * If the received response starts with BAD (a + * protocol-level + * error), then @error will be set to %GOA_ERROR_FAILED and + * %NULL is returned. Otherwise the full response string (excluding + * the command tag) is returned. + * + * If @command is IDLE (see + * RFC 2177) + * and @cancellable is cancelled, the continuation string + * DONE is written out automatically. + * While this method can be used for to submit + * the IDLE IMAP command, the + * goa_imap_client_idle_sync() method should be used instead. + * + * The timeout on the underlying socket will be set to 30 seconds + * except for the the IDLE command which never + * times out. + * + * Note that #GoaImapClient::untagged-response signals are + * emitted in the same thread that + * you call this method from - not the + * thread-default main loop + * of the thread that @client was constructed in, as one + * would except. + * + * Returns: The response or %NULL if error is set. + */ +gchar * +goa_imap_client_run_command_sync (GoaImapClient *client, + const gchar *command, + GCancellable *cancellable, + GError **error) +{ + gchar *s; + gchar *tag; + gchar *ret; + GString *response; + gsize len; + gboolean is_idle_command; + gboolean idle_has_sent_done; + GError *local_error; + + g_return_val_if_fail (GOA_IS_IMAP_CLIENT (client), NULL); + g_return_val_if_fail (command != NULL, NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + s = NULL; + tag = NULL; + response = NULL; + ret = NULL; + is_idle_command = FALSE; + idle_has_sent_done = FALSE; + + g_mutex_lock (client->lock); + + if (client->sc == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + "Not yet connected"); + goto out; + } + + if (g_strcmp0 (command, "IDLE") == 0) + is_idle_command = TRUE; + + /* select a timeout */ + if (is_idle_command) + { + /* never time out */ + g_socket_set_timeout (g_socket_connection_get_socket (client->c), 0); + } + else + { + /* fail quickly */ + g_socket_set_timeout (g_socket_connection_get_socket (client->c), COMMAND_TIMEOUT_SEC); + } + + tag = g_strdup_printf ("T%05d ", client->tag++); + s = g_strconcat (tag, command, "\r\n", NULL); + if (!g_data_output_stream_put_string (client->dos, s, cancellable, error)) + { + g_prefix_error (error, "Error putting string: "); + g_free (s); + goto out; + } + g_free (s); + + response = g_string_new (NULL); + again: + local_error = NULL; + s = g_data_input_stream_read_line (client->dis, NULL, cancellable, &local_error); + if (s == NULL) + { + if (local_error != NULL) + { + g_prefix_error (&local_error, "Error reading line: "); + /* if doing an IDLE that was cancelled, write the continuation string + * anyway, ignoring the cancellable + */ + if ((local_error->domain == G_IO_ERROR && local_error->code == G_IO_ERROR_CANCELLED) && is_idle_command) + { + if (!g_data_output_stream_put_string (client->dos, "DONE\r\n", NULL, error)) + { + /* if this fails, ignore the cancelled error */ + g_error_free (local_error); + g_prefix_error (error, "Error putting IDLE continuation string: "); + goto out; + } + /* TODO: this way we're ignoring the response to the IDLE command we just + * fired off.. it's not a problem per se, but it's annoying to see in + * debug output... we could sit around and wait for the response but there's + * really no point in doing so + */ + } + g_propagate_error (error, local_error); + } + else + { + g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, "No content to read"); + } + goto out; + } + len = strlen (s); + /* So far so good */ + g_string_append_len (response, s, len); + + /* Could be it's a literal string */ + if (len >= 3 && s[len-1] == '}') + { + gint n; + n = len - 2; + while (g_ascii_isdigit (s[n]) && n >= 0) + n--; + if (s[n] == '{') + { + gsize num_read; + gsize lit_len; + gchar *lit; + lit_len = atoi (s + n + 1); + /* Don't blindly allocate any big number of bytes */ + if (lit_len > 10*1024*1024) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + "Refusing to read an additional %" G_GSIZE_FORMAT " bytes for literal string", + lit_len); + g_free (s); + goto out; + } + lit = g_malloc0 (lit_len + 1); + if (!g_input_stream_read_all (G_INPUT_STREAM (client->dis), + lit, + lit_len, + &num_read, + cancellable, + error)) + { + g_free (lit); + g_prefix_error (error, + "Requested %" G_GSIZE_FORMAT " bytes for literal string " + "but only read %" G_GSSIZE_FORMAT ": ", + lit_len, num_read); + g_free (s); + goto out; + } + /* include the original CRLF, then the literal string */ + g_string_append (response, "\r\n"); + g_string_append_len (response, lit, lit_len); + g_free (lit); + g_free (s); + /* then keep reading */ + goto again; + } + } + + if (g_str_has_prefix (response->str, tag)) + { + gint tag_len; + tag_len = strlen (tag); + if (g_str_has_prefix (response->str + tag_len, "BAD")) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + "BAD response to `%s': %s", + command, + response->str + tag_len + 4); + goto out; + } + ret = g_strdup (response->str + tag_len); + /* TODO: return additional response? */ + goto out; + } + else if (g_str_has_prefix (response->str, "*")) + { + /* untagged */ + g_signal_emit (client, signals[UNTAGGED_RESPONSE_SIGNAL], 0, response->str + 2); + } + else + { + /* TODO: not yet interesting to handle other unhandled responses.. + * Typically these are command continuation requests, see + * + * http://tools.ietf.org/html/rfc3501#section-7.5 + */ + /* g_debug ("unhandled response `%s'", response->str); */ + } + + /* If idling, when we receive real data, put the DONE continuation + * string so the IDLE command will terminate + */ + if (is_idle_command && !g_str_has_prefix (response->str, "+") && !idle_has_sent_done) + { + idle_has_sent_done = TRUE; + if (!g_data_output_stream_put_string (client->dos, "DONE\r\n", cancellable, error)) + { + g_prefix_error (error, "Error putting IDLE continuation string: "); + goto out; + } + } + + /* reset */ + g_string_set_size (response, 0); + goto again; + + out: + if (response != NULL) + g_string_free (response, TRUE); + g_free (tag); + g_mutex_unlock (client->lock); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GoaImapClient *client; + GCancellable *local_cancellable; + gboolean timed_out; +} IdleData; + +static gboolean +timeout_while_idling_cb (gpointer user_data) +{ + IdleData *data = user_data; + data->timed_out = TRUE; + g_cancellable_cancel (data->local_cancellable); + return FALSE; +} + +static gboolean +cancel_in_idle_cb (gpointer user_data) +{ + GCancellable *cancellable = G_CANCELLABLE (user_data); + g_cancellable_cancel (cancellable); + return FALSE; +} + +static void +cancelled_while_idling_cb (GCancellable *cancellable, + gpointer user_data) +{ + IdleData *data = user_data; + + /* cancel in idle because right now calling g_cancellable_cancel() + * in a ::cancelled handler may deadlock, see + * + * https://bugzilla.gnome.org/show_bug.cgi?id=650252 + */ + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, + cancel_in_idle_cb, + g_object_ref (data->local_cancellable), + g_object_unref); +} + +/** + * goa_imap_client_idle_sync: + * @client: A #GoaImapClient. + * @max_idle_seconds: Max number of seconds to idle for. This should be no longer than 29 minutes. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Method used to sit and wait until something happens to the selected + * mailbox. When a change has been detected this method returns %TRUE. + * + * Otherwise the method simply blocks for @max_idle_seconds (in which + * case %TRUE is also returned) or until @cancellable is cancelled - + * in which case %FALSE is returned and @error is set to + * %G_IO_ERROR_CANCELLED. + * + * If the IMAP server supports the IDLE command then + * it is used. Otherwise: TODO: handle servers not using IMAP IDLE. + * + * Note that #GoaImapClient::untagged-response signals are + * emitted in the same thread that + * you call this method from - not the + * thread-default main loop + * of the thread that @client was constructed in, as one + * would except. + * + * Returns: %TRUE if the request completed, %FALSE if @error is set. + */ +gboolean +goa_imap_client_idle_sync (GoaImapClient *client, + guint max_idle_seconds, + GCancellable *cancellable, + GError **error) +{ + GError *local_error; + gboolean ret; + gchar *response; + guint timeout_id; + IdleData data; + gulong cancelled_id; + + g_return_val_if_fail (GOA_IS_IMAP_CLIENT (client), FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ret = FALSE; + cancelled_id = 0; + timeout_id = 0; + + data.client = g_object_ref (client); + data.local_cancellable = g_cancellable_new (); + data.timed_out = FALSE; + if (cancellable != NULL) + { + cancelled_id = g_cancellable_connect (cancellable, + G_CALLBACK (cancelled_while_idling_cb), + &data, + NULL); + } + + /* We use the default GMainContext for the wake-up. This might not + * be ideal but the only alternative is to create our own thread. + */ + timeout_id = g_timeout_add_seconds (max_idle_seconds, + timeout_while_idling_cb, + &data); + + /* OK, sit around and wait until the mailbox changes (e.g. new mail + * arriving)... For we use the IMAP IDLE command if it's available, + * see http://tools.ietf.org/html/rfc2177 + * + * (TODO: actually handle IDLE not being available) + */ + local_error = NULL; + response = goa_imap_client_run_command_sync (client, + "IDLE", + data.local_cancellable, + &local_error); + if (response == NULL) + { + if ((local_error->domain == G_IO_ERROR && local_error->code == G_IO_ERROR_CANCELLED) + && data.timed_out) + { + g_error_free (local_error); + } + else + { + g_propagate_error (error, local_error); + goto out; + } + } + else + { + g_free (response); + } + + ret = TRUE; + + out: + if (timeout_id > 0) + g_source_remove (timeout_id); + if (cancelled_id != 0) + g_cancellable_disconnect (cancellable, cancelled_id); + g_object_unref (data.local_cancellable); + g_object_unref (data.client); + return ret; +} + +/** + * goa_imap_client_disconnect_sync: + * @client: A #GoaImapClient. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Closes the connection used by @client, if any. The calling thread + * is blocked while the operation is pending. + * + * Returns: %TRUE if the connection was closed, %FALSE if @error is set. + */ +gboolean +goa_imap_client_disconnect_sync (GoaImapClient *client, + GCancellable *cancellable, + GError **error) +{ + gboolean ret; + + g_return_val_if_fail (GOA_IS_IMAP_CLIENT (client), FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + g_mutex_lock (client->lock); + if (client->c == NULL) + ret = TRUE; + else + ret = g_io_stream_close (G_IO_STREAM (client->c), cancellable, error); + g_mutex_unlock (client->lock); + return ret; +} + diff --git a/src/goabackend/goaimapclient.h b/src/goabackend/goaimapclient.h new file mode 100644 index 0000000..0a9f2ea --- /dev/null +++ b/src/goabackend/goaimapclient.h @@ -0,0 +1,63 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GOA_IMAP_CLIENT_H__ +#define __GOA_IMAP_CLIENT_H__ + +#include + +G_BEGIN_DECLS + +#define GOA_TYPE_IMAP_CLIENT (goa_imap_client_get_type ()) +#define GOA_IMAP_CLIENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_IMAP_CLIENT, GoaImapClient)) +#define GOA_IS_IMAP_CLIENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_IMAP_CLIENT)) + +GType goa_imap_client_get_type (void) G_GNUC_CONST; +GoaImapClient *goa_imap_client_new (void); +gboolean goa_imap_client_connect_sync (GoaImapClient *client, + const gchar *host_and_port, + gboolean use_tls, + GoaImapAuth *auth, + GCancellable *cancellable, + GError **error); +gchar *goa_imap_client_run_command_sync (GoaImapClient *client, + const gchar *command, + GCancellable *cancellable, + GError **error); +gboolean goa_imap_client_idle_sync (GoaImapClient *client, + guint max_idle_seconds, + GCancellable *cancellable, + GError **error); +gboolean goa_imap_client_disconnect_sync (GoaImapClient *client, + GCancellable *cancellable, + GError **error); + +// TODO: gint goa_imap_client_get_socket_fd (GoaImapClient *client); + + +G_END_DECLS + +#endif /* __GOA_IMAP_CLIENT_H__ */ diff --git a/src/goabackend/goaimapmail.c b/src/goabackend/goaimapmail.c new file mode 100644 index 0000000..c3f7c8d --- /dev/null +++ b/src/goabackend/goaimapmail.c @@ -0,0 +1,1058 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" +#include +#include + +#include +#include +#include + +#include "goaimapauth.h" +#include "goaimapclient.h" +#include "goaimapmail.h" + +/** + * GoaImapMail: + * + * The #GoaImapMail structure contains only private data and should + * only be accessed using the provided API. + */ +struct _GoaImapMail +{ + /*< private >*/ + GoaMailSkeleton parent_instance; + + gchar *host_and_port; + gboolean use_tls; + GoaImapAuth *auth; +}; + +typedef struct _GoaImapMailClass GoaImapMailClass; + +struct _GoaImapMailClass +{ + GoaMailSkeletonClass parent_class; +}; + +enum +{ + PROP_0, + PROP_HOST_AND_PORT, + PROP_USE_TLS, + PROP_AUTH +}; + +/** + * SECTION:goaimapmail + * @title: GoaImapMail + * @short_description: Implementation of the #GoaMail interface for IMAP servers + * + * #GoaImapMail is an implementation of the #GoaMail D-Bus + * interface that uses a #GoaImapClient instance to speak to a + * remote IMAP server. + */ + +static void goa_imap_mail__goa_mail_iface_init (GoaMailIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GoaImapMail, goa_imap_mail, GOA_TYPE_MAIL_SKELETON, + G_IMPLEMENT_INTERFACE (GOA_TYPE_MAIL, goa_imap_mail__goa_mail_iface_init)); + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +goa_imap_mail_finalize (GObject *object) +{ + GoaImapMail *mail = GOA_IMAP_MAIL (object); + + g_free (mail->host_and_port); + g_object_unref (mail->auth); + + G_OBJECT_CLASS (goa_imap_mail_parent_class)->finalize (object); +} + +static void +goa_imap_mail_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GoaImapMail *mail = GOA_IMAP_MAIL (object); + + switch (prop_id) + { + case PROP_HOST_AND_PORT: + g_value_set_string (value, mail->host_and_port); + break; + + case PROP_USE_TLS: + g_value_set_boolean (value, mail->use_tls); + break; + + case PROP_AUTH: + g_value_set_object (value, mail->auth); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +goa_imap_mail_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GoaImapMail *mail = GOA_IMAP_MAIL (object); + + switch (prop_id) + { + case PROP_HOST_AND_PORT: + mail->host_and_port = g_value_dup_string (value); + break; + + case PROP_USE_TLS: + mail->use_tls = g_value_get_boolean (value); + break; + + case PROP_AUTH: + mail->auth = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +goa_imap_mail_init (GoaImapMail *mail) +{ + /* Ensure D-Bus method invocations run in their own thread */ + g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (mail), + G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); +} + +static void +goa_imap_mail_class_init (GoaImapMailClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = goa_imap_mail_finalize; + gobject_class->set_property = goa_imap_mail_set_property; + gobject_class->get_property = goa_imap_mail_get_property; + + g_object_class_install_property (gobject_class, + PROP_HOST_AND_PORT, + g_param_spec_string ("host-and-port", + "host-and-port", + "host-and-port", + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_USE_TLS, + g_param_spec_boolean ("use-tls", + "use-tls", + "use-tls", + TRUE, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_AUTH, + g_param_spec_object ("auth", + "auth", + "auth", + GOA_TYPE_IMAP_AUTH, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * goa_imap_mail_new: + * @host_and_port: The name and optionally port to connect to. + * @use_tls: Whether TLS should be used. + * @auth: Object used for authenticating the connection. + * + * Creates a new #GoaMail object. + * + * Returns: (type GoaImapMail): A new #GoaMail instance. + */ +GoaMail * +goa_imap_mail_new (const gchar *host_and_port, + gboolean use_tls, + GoaImapAuth *auth) +{ + g_return_val_if_fail (host_and_port != NULL, NULL); + return GOA_MAIL (g_object_new (GOA_TYPE_IMAP_MAIL, + "host-and-port", host_and_port, + "use-tls", use_tls, + "auth", auth, + NULL)); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + volatile gint ref_count; + + GoaImapMail *mail; + GoaMailMonitor *monitor; + + /* Used so we can nuke the monitor if the creator vanishes */ + guint name_watcher_id; + + /* Use to communicate with the thread running the IMAP client */ + GCancellable *imap_cancellable; + gboolean imap_request_close; + GMutex *imap_counter_lock; + GCond *imap_counter_cond; + gint imap_num_refreshes; + gint imap_num_connections_failed; +} MonitorData; + +static MonitorData * +monitor_data_ref (MonitorData *data) +{ + g_atomic_int_inc (&data->ref_count); + return data; +} + +static void +monitor_data_unref (MonitorData *data) +{ + if (g_atomic_int_dec_and_test (&data->ref_count)) + { + g_clear_object (&data->mail); + g_clear_object (&data->monitor); + if (data->name_watcher_id) + g_bus_unwatch_name (data->name_watcher_id); + g_clear_object (&data->imap_cancellable); + if (data->imap_counter_lock != NULL) + g_mutex_free (data->imap_counter_lock); + if (data->imap_counter_cond != NULL) + g_cond_free (data->imap_counter_cond); + g_slice_free (MonitorData, data); + } +} + +static void +nuke_monitor (MonitorData *data) +{ + /* unexport the D-Bus object */ + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (data->monitor)); + /* nuke the running IMAP client */ + data->imap_request_close = TRUE; + g_mutex_lock (data->imap_counter_lock); + g_cancellable_cancel (data->imap_cancellable); + g_mutex_unlock (data->imap_counter_lock); + monitor_data_unref (data); +} + +static void +on_monitor_owner_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + MonitorData *data = user_data; + /* yippee ki yay motherfucker */ + nuke_monitor (data); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + MonitorData *monitor_data; + gint num_exists; + gint last_num_exists; + + gint uidvalidity; +} ImapClientData; + + +static gboolean +parse_int (const gchar *s, + gint *out_result) +{ + gboolean ret; + gchar *endp; + gint result; + + g_return_val_if_fail (s != NULL, FALSE); + + ret = FALSE; + result = strtol (s, &endp, 0); + if (result == 0 && endp == s) + goto out; + + if (out_result != NULL) + *out_result = result; + + ret = TRUE; + + out: + return ret; +} + +static gboolean +fetch_check (const gchar *data, + guint *pos, + const gchar *key) +{ + gsize key_len; + gboolean ret; + + g_return_val_if_fail (data != NULL, FALSE); + g_return_val_if_fail (pos != NULL, FALSE); + g_return_val_if_fail (key != NULL, FALSE); + + ret = FALSE; + + key_len = strlen (key); + if (strncmp (data + *pos, key, key_len) == 0 && data[*pos + key_len] == ' ') + { + ret = TRUE; + *pos += key_len + 1; + goto out; + } + out: + return ret; +} + +static gchar * +fetch_string (const gchar *data, + guint *pos) +{ + gchar *ret; + guint start_pos; + + g_return_val_if_fail (data != NULL, FALSE); + g_return_val_if_fail (pos != NULL, FALSE); + + ret = NULL; + + start_pos = *pos; + + while (data[*pos] != ' ' && data[*pos] != ')' && data[*pos] != '\0') + *pos += 1; + + ret = g_strndup (data + start_pos, *pos - start_pos); + + return ret; +} + +static gboolean +fetch_int (const gchar *data, + guint *pos, + gint *out_value) +{ + gchar *str_value; + gboolean ret; + + g_return_val_if_fail (data != NULL, FALSE); + g_return_val_if_fail (pos != NULL, FALSE); + g_return_val_if_fail (out_value != NULL, FALSE); + + str_value = NULL; + ret = FALSE; + + str_value = fetch_string (data, pos); + if (str_value == NULL) + goto out; + + if (!parse_int (str_value, out_value)) + goto out; + + ret = TRUE; + + out: + g_free (str_value); + return ret; +} + +static gchar * +fetch_literal_string (const gchar *data, + guint *pos, + guint *out_len) +{ + gchar *ret; + guint start_pos; + guint len; + + g_return_val_if_fail (data != NULL, FALSE); + g_return_val_if_fail (pos != NULL, FALSE); + + ret = NULL; + + start_pos = *pos; + + if (data[*pos] != '{') + goto out; + *pos += 1; + while (g_ascii_isdigit (data[*pos])) + *pos += 1; + if (strncmp (data + *pos, "}\r\n", 3) != 0) + goto out; + *pos += 3; + + if (!parse_int (data + start_pos + 1, (gint*) &len)) + goto out; + + ret = g_strndup (data + *pos, len); + *pos += len; + + if (out_len != NULL) + *out_len = len; + + out: + return ret; +} + +/* TODO: try a little harder to make this a conformant RFC822 parser */ +static GHashTable * +parse_rfc822_headers (const gchar *rfc822_headers) +{ + GHashTable *ret; + gchar **lines; + guint n; + + ret = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); + lines = g_strsplit (rfc822_headers, "\r\n", -1); + for (n = 0; lines[n] != NULL; n++) + { + const gchar *line = lines[n]; + const gchar *s; + + if (line[0] == '\0') + continue; + + s = strstr (line, ": "); + if (s != NULL) + { + gchar *key; + gchar *value; + key = g_strndup (line, s - line); + value = g_strdup (s + 2); + g_hash_table_insert (ret, key, value); + } + else + { + g_warning ("%s: ignoring mysterious line `%s' whilst parsing `%s'", + G_STRFUNC, line, rfc822_headers); + } + } + g_strfreev (lines); + + return ret; +} + +/* Simple FETCH response parser only handling a subset of FETCH + * responses, see + * + * http://tools.ietf.org/html/rfc3501#section-7.4.2 + * + * for more details. + */ +static void +imap_client_handle_fetch_response (ImapClientData *data, + guint message_seqnum, + const gchar *params) +{ + guint n; + gboolean parsed; + gboolean has_uid; + gint uid; + gchar *rfc822_headers; + guint rfc822_headers_len; + gchar *excerpt; + guint excerpt_len; + GHashTable *headers; + const gchar *from_header; + const gchar *subject_header; + gchar *uid_str; + gchar *uri; + /* GVariantBuilder extras_builder; */ + + g_return_if_fail (message_seqnum >= 1); + g_return_if_fail (params != NULL); + + uid = 0; + has_uid = FALSE; + excerpt = NULL; + rfc822_headers = NULL; + headers = NULL; + uid_str = NULL; + uri = NULL; + parsed = FALSE; + + if (params[0] != '(') + goto out; + n = 1; + while (params[n] != ')' && params[n] != '\0') + { + if (fetch_check (params, &n, "UID")) + { + if (!fetch_int (params, &n, &uid)) + goto out; + has_uid = TRUE; + } + else if (fetch_check (params, &n, "BODY[HEADER.FIELDS (Date From To Cc Subject)]")) + { + rfc822_headers = fetch_literal_string (params, &n, &rfc822_headers_len); + if (rfc822_headers == NULL) + goto out; + } + else if (fetch_check (params, &n, "BODY[TEXT]<0>")) + { + excerpt = fetch_literal_string (params, &n, &excerpt_len); + if (excerpt == NULL) + goto out; + } + else + { + /* Don't know how to handle unknown params so fail completely */ + goto out; + } + /* advance to next value in FETCH response list, if any */ + while (params[n] == ' ') + n++; + } + + if (!has_uid || rfc822_headers == NULL || excerpt == NULL) + goto out; + + /* OK, message is valid */ + parsed = TRUE; + + uid_str = g_strdup_printf ("%" G_GUINT64_FORMAT, + ((guint64) data->uidvalidity << 32) | ((guint64) uid)); + headers = parse_rfc822_headers (rfc822_headers); + from_header = g_hash_table_lookup (headers, "From"); + subject_header = g_hash_table_lookup (headers, "Subject"); + if (from_header == NULL) + from_header = ""; + if (subject_header == NULL) + subject_header = ""; + + /* TODO: set this */ + uri = g_strdup (""); + + /* extras is currently unused (and not currently in the D-Bus signature) */ + /* g_variant_builder_init (&extras_builder, G_VARIANT_TYPE_VARDICT); */ + + /* Emit D-Bus message */ + goa_mail_monitor_emit_message_received (data->monitor_data->monitor, + uid_str, + from_header, + subject_header, + excerpt, + uri); + /* g_variant_builder_end (&extras_builder)); */ + out: + if (!parsed) + { + /* Use g_warning() since we want bug-reports to improve the FETCH parser */ + g_warning ("Was unable to parse FETCH response for message with sequence number %d and parameters `%s'. " + "Please report this to %s", + message_seqnum, + params, + PACKAGE_BUGREPORT); + } + g_free (uri); + g_free (uid_str); + if (headers != NULL) + g_hash_table_unref (headers); + g_free (rfc822_headers); + g_free (excerpt); +} + +static void +imap_client_on_untagged_response (GoaImapClient *client, + const gchar *response, + gpointer user_data) +{ + ImapClientData *data = user_data; + gchar s[64+1]; + gint i; + gint n; + + if (sscanf (response, "%d %64s", &i, s) == 2 && g_strcmp0 (s, "EXISTS") == 0) + { + data->num_exists = i; + } + else if (sscanf (response, "%d %64s", &i, s) == 2 && g_strcmp0 (s, "EXPUNGE") == 0) + { + /* See http://tools.ietf.org/html/rfc3501#section-7.4.1 */ + data->num_exists -= 1; + data->last_num_exists -= 1; + } + else if (sscanf (response, "OK [UIDVALIDITY %d]", &i) == 1) + { + data->uidvalidity = i; + } + else if (sscanf (response, "%d %64s%n", &i, s, &n) == 2 && g_strcmp0 (s, "FETCH") == 0) + { + const gchar *params; + params = response + n; + while (g_ascii_isspace (*params)) + params++; + imap_client_handle_fetch_response (data, i, params); + } + else + { + /* g_debug ("unhandled untagged response `%s'", response); */ + } +} + +static void +imap_client_sync_single (ImapClientData *data) +{ + GError *error; + gchar *response; + GoaImapClient *client; + + /* Get ourselves an IMAP client and connect to the server */ + data->num_exists = -1; + client = goa_imap_client_new (); + error = NULL; + if (!goa_imap_client_connect_sync (client, + data->monitor_data->mail->host_and_port, + data->monitor_data->mail->use_tls, + data->monitor_data->mail->auth, + NULL, /* GCancellable */ + &error)) + goto out; + + /* Houston, we have a connection */ + goa_mail_monitor_set_connected (data->monitor_data->monitor, TRUE); + + g_signal_connect (client, + "untagged-response", + G_CALLBACK (imap_client_on_untagged_response), + data); + + /* First, select the INBOX - this is guaranteed to emit the EXISTS untagged response */ + error = NULL; + response = goa_imap_client_run_command_sync (client, + "SELECT INBOX", + NULL, /* GCancellable */ + &error); + if (response == NULL) + goto out; + g_free (response); + + if (data->num_exists == -1) + { + g_set_error (&error, + GOA_ERROR, + GOA_ERROR_FAILED, + "Expected EXISTS untagged response for SELECT but received none"); + goto out; + } + data->last_num_exists = data->num_exists; + + /* This is the main loop where we idle, then refresh, then idle, + * then refresh again and around and around she goes... + */ + while (TRUE) + { + /* If the connection is closed/severed, this is the way we find out since + * the IDLE command submitted above disables timeouts + */ + response = goa_imap_client_run_command_sync (client, + "NOOP", + NULL, /* GCancellable */ + &error); + if (response == NULL) + goto out; + g_free (response); + + /* Fetch newly received messages, if any - the D-Bus signal will + * get emitted from imap_client_handle_fetch_response() that + * will be called while the command is pending + */ + if (data->num_exists > data->last_num_exists) + { + GString *request_str; + guint num_new_messages; + guint n; + + num_new_messages = data->num_exists - data->last_num_exists; + request_str = g_string_new ("FETCH "); + for (n = 0; n < num_new_messages; n++) + { + if (n > 0) + g_string_append_c (request_str, ','); + g_string_append_printf (request_str, "%d", data->last_num_exists + 1 + n); + } + + g_string_append (request_str, + " (" + "UID " + "BODY.PEEK[HEADER.FIELDS (Date From To Cc Subject)] " + "BODY.PEEK[TEXT]<0.1000>" + ")"); + error = NULL; + response = goa_imap_client_run_command_sync (client, + request_str->str, + NULL, /* GCancellable */ + &error); + g_string_free (request_str, TRUE); + if (response == NULL) + goto out; + g_free (response); + } + data->last_num_exists = data->num_exists; + + /* Wake up waiters */ + g_mutex_lock (data->monitor_data->imap_counter_lock); + data->monitor_data->imap_num_refreshes += 1; + g_cond_broadcast (data->monitor_data->imap_counter_cond); + g_mutex_unlock (data->monitor_data->imap_counter_lock); + + /* Never idle for more than 25 minutes cf. the recommendation + * in RFC 2177: http://tools.ietf.org/html/rfc2177 + */ + error = NULL; + if (!goa_imap_client_idle_sync (client, + 25 * 60, + data->monitor_data->imap_cancellable, + &error)) + { + if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED) + { + g_cancellable_reset (data->monitor_data->imap_cancellable); + g_error_free (error); + error = NULL; + } + else + { + goto out; + } + } + + /* Check if asked to close */ + if (data->monitor_data->imap_request_close) + goto out; + + } /* Main loop */ + + out: + /* We no longer have a connection */ + goa_mail_monitor_set_connected (data->monitor_data->monitor, FALSE); + + /* Wake up waiters */ + g_mutex_lock (data->monitor_data->imap_counter_lock); + data->monitor_data->imap_num_connections_failed += 1; + g_cond_broadcast (data->monitor_data->imap_counter_cond); + g_mutex_unlock (data->monitor_data->imap_counter_lock); + + if (error != NULL) + { + /* g_debug ("leaving serve loop: error: %s (%s, %d)", error->message, g_quark_to_string (error->domain), error->code); */ + g_error_free (error); + } + else + { + /* g_debug ("leaving serve loop without error"); */ + } + g_signal_handlers_disconnect_by_func (client, + G_CALLBACK (imap_client_on_untagged_response), + data); + error = NULL; + if (!goa_imap_client_disconnect_sync (client, + NULL, /* GCancellable */ + &error)) + { + g_warning ("%s:Error closing connection: %s (%s, %d)", + G_STRFUNC, + error->message, g_quark_to_string (error->domain), error->code); + g_error_free (error); + } + g_object_unref (client); +} + +static void +imap_client_sync (MonitorData *data) +{ + ImapClientData *imap_data; + + imap_data = g_slice_new0 (ImapClientData); + imap_data->monitor_data = monitor_data_ref (data); + + while (TRUE) + { + GPollFD poll_fd; + + /* tries connecting - blocks until the connection is closed */ + imap_client_sync_single (imap_data); + + if (data->imap_request_close) + goto out; + + /* Wait to get woken up */ + if (g_cancellable_make_pollfd (data->imap_cancellable, &poll_fd)) + { + gint poll_ret; + do + { + poll_ret = g_poll (&poll_fd, 1, -1); + } + while (poll_ret == -1 && errno == EINTR); + g_cancellable_release_fd (data->imap_cancellable); + g_cancellable_reset (data->imap_cancellable); + } + + if (data->imap_request_close) + goto out; + } + + out: + + /* Wake up waiters (if any) */ + g_mutex_lock (data->imap_counter_lock); + data->imap_num_refreshes = -1; + data->imap_num_connections_failed = -1; + g_cond_broadcast (data->imap_counter_cond); + g_mutex_unlock (data->imap_counter_lock); + + monitor_data_unref (imap_data->monitor_data); + g_slice_free (ImapClientData, imap_data); +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +/* runs in thread dedicated to the method invocation */ +static gboolean +monitor_on_handle_refresh (GoaMailMonitor *monitor, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + MonitorData *data = user_data; + gint orig_imap_num_connections_failed; + gint orig_imap_num_refreshes; + gboolean refreshed, connection_failed, closed; + gint num_attempts; + + monitor_data_ref (data); + + num_attempts = 0; + again: + g_mutex_lock (data->imap_counter_lock); + orig_imap_num_refreshes = data->imap_num_refreshes; + orig_imap_num_connections_failed = data->imap_num_connections_failed; + /* Wake up the IMAP client thread - this will cause either a connection + * failure or a refresh + */ + g_cancellable_cancel (data->imap_cancellable); + g_cond_wait (data->imap_counter_cond, data->imap_counter_lock); + num_attempts += 1; + + closed = (orig_imap_num_refreshes == -1); + refreshed = (orig_imap_num_refreshes != data->imap_num_refreshes); + connection_failed = (orig_imap_num_connections_failed != data->imap_num_connections_failed); + g_mutex_unlock (data->imap_counter_lock); + + g_warn_if_fail (refreshed || connection_failed || closed); + + if (refreshed) + { + goa_mail_monitor_complete_refresh (monitor, invocation); + goto out; + } + + if (closed) + { + g_dbus_method_invocation_return_error (invocation, + GOA_ERROR, + GOA_ERROR_FAILED, + "The monitor was closed"); + goto out; + } + + /* Try at least three times to cope with broken connections */ + if (connection_failed && num_attempts < 3) + goto again; + + if (connection_failed) + { + g_dbus_method_invocation_return_error (invocation, + GOA_ERROR, + GOA_ERROR_FAILED, + "Failed to reconnect (tried %d times)", + num_attempts); + goto out; + } + + /* should never end up here but if we do, make sure + * the bug reporters can give us something useful + */ + g_warning ("Unexpected state while trying to refresh: refreshed=%d connection_failed=%d closed=%d num_attempts=%d", + refreshed, connection_failed, closed, num_attempts); + g_dbus_method_invocation_return_error (invocation, + GOA_ERROR, + GOA_ERROR_FAILED, + "Failed with unexpected state: " + "refreshed=%d connection_failed=%d closed=%d num_attempts=%d", + refreshed, connection_failed, closed, num_attempts); + + out: + monitor_data_unref (data); + return TRUE; /* invocation was handled */ +} + +/* runs in thread dedicated to the method invocation */ +static gboolean +monitor_on_handle_close (GoaMailMonitor *monitor, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + MonitorData *data = user_data; + /* yippee ki yay motherfucker */ + nuke_monitor (data); + goa_mail_monitor_complete_close (monitor, invocation); + return TRUE; /* invocation was handled */ +} + +/* runs in thread dedicated to the method invocation */ +static gboolean +monitor_on_handle_simulate_message_received (GoaMailMonitor *monitor, + GDBusMethodInvocation *invocation, + const gchar *uid, + const gchar *from, + const gchar *subject, + const gchar *excerpt, + const gchar *uri, + gpointer user_data) +{ + goa_mail_monitor_emit_message_received (monitor, uid, from, subject, excerpt, uri); + goa_mail_monitor_complete_simulate_message_received (monitor, invocation); + return TRUE; /* invocation was handled */ +} + + +/* runs in thread dedicated to the method invocation */ +static gboolean +handle_create_monitor (GoaMail *_mail, + GDBusMethodInvocation *invocation) +{ + GoaImapMail *mail = GOA_IMAP_MAIL (_mail); + gchar *monitor_object_path; + GError *error; + MonitorData *data; + static gint _g_monitor_count = 0; + + monitor_object_path = NULL; + + data = g_slice_new0 (MonitorData); + data->ref_count = 1; + data->mail = g_object_ref (mail); + data->monitor = goa_mail_monitor_skeleton_new (); + /* Be optimistic that the connection works - if this is not so, + * imap_client_sync() will clear the flag + */ + goa_mail_monitor_set_connected (data->monitor, TRUE); + g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (data->monitor), + G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); + g_signal_connect (data->monitor, + "handle-refresh", + G_CALLBACK (monitor_on_handle_refresh), + data); + g_signal_connect (data->monitor, + "handle-close", + G_CALLBACK (monitor_on_handle_close), + data); + g_signal_connect (data->monitor, + "handle-simulate-message-received", + G_CALLBACK (monitor_on_handle_simulate_message_received), + data); + + monitor_object_path = g_strdup_printf ("/org/gnome/OnlineAccounts/mail_monitors/%d", _g_monitor_count++); + error = NULL; + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (data->monitor), + g_dbus_method_invocation_get_connection (invocation), + monitor_object_path, + &error)) + { + g_prefix_error (&error, "Error exporting mail monitor: "); + g_dbus_method_invocation_return_gerror (invocation, error); + monitor_data_unref (data); + goto out; + } + + data->name_watcher_id = g_bus_watch_name_on_connection (g_dbus_method_invocation_get_connection (invocation), + g_dbus_method_invocation_get_sender (invocation), + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, /* name_appeared_handler */ + on_monitor_owner_vanished, + data, + NULL); + + /* OK, we're in business - finish the invocation + * + * TODO: set up things so only caller can access the created object? + */ + goa_mail_complete_create_monitor (GOA_MAIL (mail), invocation, monitor_object_path); + + /* Create the IMAP client - this blocks our thread until the owner + * vanishes or the Close() method is called ... + * + * The data->imap_cancellable member can be used to wake up the loop + * to check for data->imap_request_close member or just to + * refresh... + */ + data->imap_cancellable = g_cancellable_new (); + data->imap_counter_lock = g_mutex_new (); + data->imap_counter_cond = g_cond_new (); + imap_client_sync (data); + + out: + g_free (monitor_object_path); + return TRUE; /* invocation was handled */ +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +goa_imap_mail__goa_mail_iface_init (GoaMailIface *iface) +{ + iface->handle_create_monitor = handle_create_monitor; +} + +/* ---------------------------------------------------------------------------------------------------- */ diff --git a/src/goabackend/goaimapmail.h b/src/goabackend/goaimapmail.h new file mode 100644 index 0000000..6f30fee --- /dev/null +++ b/src/goabackend/goaimapmail.h @@ -0,0 +1,45 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GOA_IMAP_MAIL_H__ +#define __GOA_IMAP_MAIL_H__ + +#include + +G_BEGIN_DECLS + +#define GOA_TYPE_IMAP_MAIL (goa_imap_mail_get_type ()) +#define GOA_IMAP_MAIL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_IMAP_MAIL, GoaImapMail)) +#define GOA_IS_IMAP_MAIL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_IMAP_MAIL)) + +GType goa_imap_mail_get_type (void) G_GNUC_CONST; +GoaMail *goa_imap_mail_new (const gchar *host_and_port, + gboolean use_tls, + GoaImapAuth *auth); + +G_END_DECLS + +#endif /* __GOA_IMAP_MAIL_H__ */ diff --git a/src/goabackend/goaoauth2provider.c b/src/goabackend/goaoauth2provider.c new file mode 100644 index 0000000..74cc6c1 --- /dev/null +++ b/src/goabackend/goaoauth2provider.c @@ -0,0 +1,1473 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" +#include +#include + +#include +#include +#include + +#include "goaprovider.h" +#include "goaoauth2provider.h" + +/** + * SECTION:goaoauth2provider + * @title: GoaOAuth2Provider + * @short_description: Abstract base class for OAuth 2.0 providers + * + * #GoaOAuth2Provider is an abstract base class for OAuth + * 2.0 based providers. + * + * Subclasses must implement + * #GoaOAuth2ProviderClass.get_authorization_uri, + * #GoaOAuth2ProviderClass.get_token_uri, + * #GoaOAuth2ProviderClass.get_redirect_uri, + * #GoaOAuth2ProviderClass.get_scope, + * #GoaOAuth2ProviderClass.get_client_id, + * #GoaOAuth2ProviderClass.get_client_secret and + * #GoaOAuth2ProviderClass.get_identity_sync methods. + * + * Additionally, the + * #GoaProviderClass.get_provider_type, + * #GoaProviderClass.get_name, + * #GoaProviderClass.build_object (this should chain up to its + * parent class) methods must be implemented. + * + * Note that the #GoaProviderClass.add_account, + * #GoaProviderClass.refresh_account and + * #GoaProviderClass.ensure_credentials_sync methods do not + * need to be implemented - this type implements these methods.. + */ + +G_LOCK_DEFINE_STATIC (provider_lock); + +G_DEFINE_ABSTRACT_TYPE (GoaOAuth2Provider, goa_oauth2_provider, GOA_TYPE_PROVIDER); + +static gboolean +is_authorization_error (GError *error) +{ + gboolean ret; + + g_return_val_if_fail (error != NULL, FALSE); + + ret = FALSE; + if (error->domain == REST_PROXY_ERROR || error->domain == SOUP_HTTP_ERROR) + { + if (SOUP_STATUS_IS_CLIENT_ERROR (error->code)) + ret = TRUE; + } + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +goa_oauth2_provider_get_use_external_browser_default (GoaOAuth2Provider *provider) +{ + return FALSE; +} + +/** + * goa_oauth2_provider_get_use_external_browser: + * @provider: A #GoaOAuth2Provider. + * + * Returns whether an external browser (the users default browser) + * should be used for user interaction. + * + * If an external browser is used, then the dialogs presented in + * goa_provider_add_account() and + * goa_provider_refresh_account() will show a simple "Paste + * authorization code here" instructions along with an entry and + * button. + * + * This is a virtual method where the default implementation returns + * %FALSE. + * + * Returns: %TRUE if the user interaction should happen in an external + * browser, %FALSE to use an embedded browser widget. + */ +gboolean +goa_oauth2_provider_get_use_external_browser (GoaOAuth2Provider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (provider), FALSE); + return GOA_OAUTH2_PROVIDER_GET_CLASS (provider)->get_use_external_browser (provider); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +goa_oauth2_provider_build_authorization_uri_default (GoaOAuth2Provider *provider, + const gchar *authorization_uri, + const gchar *escaped_redirect_uri, + const gchar *escaped_client_id, + const gchar *escaped_scope) +{ + return g_strdup_printf ("%s" + "?response_type=code" + "&redirect_uri=%s" + "&client_id=%s" + "&scope=%s", + authorization_uri, + escaped_redirect_uri, + escaped_client_id, + escaped_scope); +} + +/** + * goa_oauth2_provider_build_authorization_uri: + * @provider: A #GoaOAuth2Provider. + * @authorization_uri: An authorization URI. + * @escaped_redirect_uri: An escaped redirect URI + * @escaped_client_id: An escaped client id + * @escaped_scope: The escaped scope. + * + * Builds the URI that can be opened in a web browser (or embedded web + * browser widget) to start authenticating an user. + * + * The default implementation just returns the expected URI + * (e.g. http://example.com/dialog/oauth2?response_type=code&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb&client_id=foo&scope=email%20stuff) + * - override (and chain up) if you e.g. need to to pass additional + * parameters. + * + * The @authorization_uri, @escaped_redirect_uri, @escaped_client_id + * and @escaped_scope parameters originate from the result of the + * the goa_oauth2_provider_get_authorization_uri(), goa_oauth2_provider_get_redirect_uri(), goa_oauth2_provider_get_client_id() + * and goa_oauth2_provider_get_scope() methods with the latter + * three escaped using g_uri_escape_string(). + * + * Returns: (transfer full): An authorization URI that must be freed with g_free(). + */ +gchar * +goa_oauth2_provider_build_authorization_uri (GoaOAuth2Provider *provider, + const gchar *authorization_uri, + const gchar *escaped_redirect_uri, + const gchar *escaped_client_id, + const gchar *escaped_scope) +{ + g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (provider), NULL); + g_return_val_if_fail (authorization_uri != NULL, NULL); + g_return_val_if_fail (escaped_redirect_uri != NULL, NULL); + g_return_val_if_fail (escaped_client_id != NULL, NULL); + g_return_val_if_fail (escaped_scope != NULL, NULL); + return GOA_OAUTH2_PROVIDER_GET_CLASS (provider)->build_authorization_uri (provider, + authorization_uri, + escaped_redirect_uri, + escaped_client_id, + escaped_scope); +} + +/** + * goa_oauth2_provider_get_authorization_uri: + * @provider: A #GoaOAuth2Provider. + * + * Gets the authorization + * endpoint used for authenticating the user and obtaining + * authorization. + * + * You should not include any parameters in the returned URI. If you + * need to include additional parameters than the standard ones, + * override #GoaOAuth2ProviderClass.build_authorization_uri - + * see goa_oauth2_provider_build_authorization_uri() for more + * details. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider - do not free. + */ +const gchar * +goa_oauth2_provider_get_authorization_uri (GoaOAuth2Provider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (provider), NULL); + return GOA_OAUTH2_PROVIDER_GET_CLASS (provider)->get_authorization_uri (provider); +} + +/** + * goa_oauth2_provider_get_token_uri: + * @provider: A #GoaOAuth2Provider. + * + * Gets the token + * endpoint used for obtaining an access token. + * + * You should not include any parameters in the returned URI. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider - do not free. + */ +const gchar * +goa_oauth2_provider_get_token_uri (GoaOAuth2Provider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (provider), NULL); + return GOA_OAUTH2_PROVIDER_GET_CLASS (provider)->get_token_uri (provider); +} + +/** + * goa_oauth2_provider_get_redirect_uri: + * @provider: A #GoaOAuth2Provider. + * + * Gets the redirect_uri + * used when requesting authorization. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider - do not free. + */ +const gchar * +goa_oauth2_provider_get_redirect_uri (GoaOAuth2Provider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (provider), NULL); + return GOA_OAUTH2_PROVIDER_GET_CLASS (provider)->get_redirect_uri (provider); +} + +/** + * goa_oauth2_provider_get_scope: + * @provider: A #GoaOAuth2Provider. + * + * Gets the scope + * used when requesting authorization. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider - do not free. + */ +const gchar * +goa_oauth2_provider_get_scope (GoaOAuth2Provider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (provider), NULL); + return GOA_OAUTH2_PROVIDER_GET_CLASS (provider)->get_scope (provider); +} + +/** + * goa_oauth2_provider_get_client_id: + * @provider: A #GoaOAuth2Provider. + * + * Gets the client_id + * identifying the client. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider - do not free. + */ +const gchar * +goa_oauth2_provider_get_client_id (GoaOAuth2Provider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (provider), NULL); + return GOA_OAUTH2_PROVIDER_GET_CLASS (provider)->get_client_id (provider); +} + +/** + * goa_oauth2_provider_get_client_secret: + * @provider: A #GoaOAuth2Provider. + * + * Gets the client_secret + * associated with the client. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider - do not free. + */ +const gchar * +goa_oauth2_provider_get_client_secret (GoaOAuth2Provider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (provider), NULL); + return GOA_OAUTH2_PROVIDER_GET_CLASS (provider)->get_client_secret (provider); +} + +/** + * goa_oauth2_provider_get_identity_sync: + * @provider: A #GoaOAuth2Provider. + * @access_token: A valid OAuth 2.0 access token. + * @out_name: (out): Return location for name or %NULL. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for @error or %NULL. + * + * Method that returns the identity corresponding to + * @access_token. + * + * The identity is needed because all authentication happens out of + * band. The only requirement is that the returned identity is unique + * - for example, for #GoaGoogleProvider the returned identity + * is the email address, for #GoaFacebookProvider it's the user + * name. In addition to the identity, an implementation also returns a + * name that is more suitable for presentation + * (the identity could be a GUID for example) and doesn't have to be + * unique. + * + * The calling thread is blocked while the identity is obtained. + * + * Returns: The identity or %NULL if error is set. The returned string + * must be freed with g_free(). + */ +gchar * +goa_oauth2_provider_get_identity_sync (GoaOAuth2Provider *provider, + const gchar *access_token, + gchar **out_name, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (provider), NULL); + g_return_val_if_fail (access_token != NULL, NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + return GOA_OAUTH2_PROVIDER_GET_CLASS (provider)->get_identity_sync (provider, access_token, out_name, cancellable, error); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +get_tokens_sync (GoaOAuth2Provider *provider, + const gchar *authorization_code, + const gchar *refresh_token, + gchar **out_refresh_token, + gint *out_access_token_expires_in, + GCancellable *cancellable, + GError **error) +{ + RestProxy *proxy; + RestProxyCall *call; + gchar *ret; + guint status_code; + gchar *ret_access_token; + gint ret_access_token_expires_in; + gchar *ret_refresh_token; + const gchar *payload; + gsize payload_length; + + ret = NULL; + ret_access_token = NULL; + ret_access_token_expires_in = 0; + ret_refresh_token = NULL; + + proxy = rest_proxy_new (goa_oauth2_provider_get_token_uri (provider), FALSE); + call = rest_proxy_new_call (proxy); + rest_proxy_call_set_method (call, "POST"); + rest_proxy_call_add_header (call, "Content-Type", "application/x-www-form-urlencoded"); + if (refresh_token != NULL) + { + /* Swell, we have a refresh code - just use that */ + rest_proxy_call_add_param (call, "client_id", goa_oauth2_provider_get_client_id (provider)); + rest_proxy_call_add_param (call, "client_secret", goa_oauth2_provider_get_client_secret (provider)); + rest_proxy_call_add_param (call, "grant_type", "refresh_token"); + rest_proxy_call_add_param (call, "refresh_token", refresh_token); + } + else + { + /* No refresh code.. request an access token using the authorization code instead */ + rest_proxy_call_add_param (call, "client_id", goa_oauth2_provider_get_client_id (provider)); + rest_proxy_call_add_param (call, "client_secret", goa_oauth2_provider_get_client_secret (provider)); + rest_proxy_call_add_param (call, "grant_type", "authorization_code"); + rest_proxy_call_add_param (call, "code", authorization_code); + rest_proxy_call_add_param (call, "redirect_uri", goa_oauth2_provider_get_redirect_uri (provider)); + } + + /* TODO: cancellable support? */ + if (!rest_proxy_call_sync (call, error)) + goto out; + + status_code = rest_proxy_call_get_status_code (call); + if (status_code != 200) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Expected status 200 when requesting access token, instead got status %d (%s)"), + status_code, + rest_proxy_call_get_status_message (call)); + goto out; + } + + payload = rest_proxy_call_get_payload (call); + payload_length = rest_proxy_call_get_payload_length (call); + /* some older OAuth2 implementations does not return json - handle that too */ + if (g_str_has_prefix (payload, "access_token=")) + { + GHashTable *hash; + const gchar *expires_in_str; + hash = soup_form_decode (payload); + ret_access_token = g_strdup (g_hash_table_lookup (hash, "access_token")); + if (ret_access_token == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Didn't find access_token in non-JSON data")); + g_hash_table_unref (hash); + goto out; + } + /* refresh_token is optional */ + ret_refresh_token = g_hash_table_lookup (hash, "refresh_token"); + /* expires_in is optional */ + expires_in_str = g_hash_table_lookup (hash, "expires_in"); + /* sometimes "expires_in" appears as "expires" */ + if (expires_in_str == NULL) + expires_in_str = g_hash_table_lookup (hash, "expires"); + if (expires_in_str != NULL) + ret_access_token_expires_in = atoi (expires_in_str); + g_hash_table_unref (hash); + } + else + { + GError *local_error; + JsonParser *parser; + JsonObject *object; + + local_error = NULL; + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, payload, payload_length, &local_error)) + { + g_prefix_error (error, _("Error parsing response as JSON: ")); + g_object_unref (parser); + goto out; + } + object = json_node_get_object (json_parser_get_root (parser)); + ret_access_token = g_strdup (json_object_get_string_member (object, "access_token")); + if (ret_access_token == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Didn't find access_token in JSON data")); + goto out; + } + /* refresh_token is optional... */ + if (json_object_has_member (object, "refresh_token")) + ret_refresh_token = g_strdup (json_object_get_string_member (object, "refresh_token")); + if (json_object_has_member (object, "expires_in")) + ret_access_token_expires_in = json_object_get_int_member (object, "expires_in"); + g_object_unref (parser); + } + + ret = ret_access_token; + ret_access_token = NULL; + if (out_access_token_expires_in != NULL) + *out_access_token_expires_in = ret_access_token_expires_in; + if (out_refresh_token != NULL) + { + *out_refresh_token = ret_refresh_token; + ret_refresh_token = NULL; + } + + out: + g_free (ret_access_token); + g_free (ret_refresh_token); + if (call != NULL) + g_object_unref (call); + if (proxy != NULL) + g_object_unref (proxy); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ +typedef struct +{ + GoaOAuth2Provider *provider; + GtkDialog *dialog; + GError *error; + GMainLoop *loop; + + gchar *authorization_code; + gchar *access_token; + gint access_token_expires_in; + gchar *refresh_token; + + gchar *identity; + gchar *name; +} IdentifyData; + +static gboolean +on_web_view_navigation_policy_decision_requested (WebKitWebView *webView, + WebKitWebFrame *frame, + WebKitNetworkRequest *request, + WebKitWebNavigationAction *navigation_action, + WebKitWebPolicyDecision *policy_decision, + gpointer user_data) +{ + IdentifyData *data = user_data; + const gchar *redirect_uri; + const gchar *requested_uri; + + /* TODO: use oauth2_proxy_extract_access_token() */ + + requested_uri = webkit_network_request_get_uri (request); + //g_debug ("requested_uri is %s", requested_uri); + redirect_uri = goa_oauth2_provider_get_redirect_uri (data->provider); + if (g_str_has_prefix (requested_uri, redirect_uri)) + { + SoupMessage *message; + SoupURI *uri; + GHashTable *key_value_pairs; + + message = webkit_network_request_get_message (request); + uri = soup_message_get_uri (message); + key_value_pairs = soup_form_decode (uri->query); + + data->authorization_code = g_strdup (g_hash_table_lookup (key_value_pairs, "code")); + if (data->authorization_code != NULL) + { + gtk_dialog_response (data->dialog, GTK_RESPONSE_OK); + } + else + { + g_set_error (&data->error, + GOA_ERROR, + GOA_ERROR_NOT_AUTHORIZED, + _("Authorization response was \"%s\""), + (const gchar *) g_hash_table_lookup (key_value_pairs, "error")); + gtk_dialog_response (data->dialog, GTK_RESPONSE_CLOSE); + } + g_hash_table_unref (key_value_pairs); + webkit_web_policy_decision_ignore (policy_decision); + return TRUE; /* ignore the request */ + } + else + { + return FALSE; /* make default behavior apply */ + } +} + +static void +on_entry_changed (GtkEditable *editable, + gpointer user_data) +{ + IdentifyData *data = user_data; + gboolean sensitive; + + g_free (data->authorization_code); + data->authorization_code = g_strdup (gtk_entry_get_text (GTK_ENTRY (editable))); + sensitive = data->authorization_code != NULL && (strlen (data->authorization_code) > 0); + gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, sensitive); +} + +static gboolean +get_tokens_and_identity (GoaOAuth2Provider *provider, + GtkDialog *dialog, + GtkBox *vbox, + gchar **out_authorization_code, + gchar **out_access_token, + gint *out_access_token_expires_in, + gchar **out_refresh_token, + gchar **out_identity, + gchar **out_name, + GError **error) +{ + gboolean ret; + gchar *url; + IdentifyData data; + gchar *escaped_redirect_uri; + gchar *escaped_client_id; + gchar *escaped_scope; + + g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (provider), FALSE); + g_return_val_if_fail (GTK_IS_DIALOG (dialog), FALSE); + g_return_val_if_fail (GTK_IS_BOX (vbox), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ret = FALSE; + escaped_redirect_uri = NULL; + escaped_client_id = NULL; + escaped_scope = NULL; + + /* TODO: check with NM whether we're online, if not - return error */ + + memset (&data, '\0', sizeof (IdentifyData)); + data.provider = provider; + data.loop = g_main_loop_new (NULL, FALSE); + + /* TODO: use oauth2_proxy_build_login_url_full() */ + escaped_redirect_uri = g_uri_escape_string (goa_oauth2_provider_get_redirect_uri (provider), NULL, TRUE); + escaped_client_id = g_uri_escape_string (goa_oauth2_provider_get_client_id (provider), NULL, TRUE); + escaped_scope = g_uri_escape_string (goa_oauth2_provider_get_scope (provider), NULL, TRUE); + url = goa_oauth2_provider_build_authorization_uri (provider, + goa_oauth2_provider_get_authorization_uri (provider), + escaped_redirect_uri, + escaped_client_id, + escaped_scope); + //g_debug ("url = %s", url); + + if (goa_oauth2_provider_get_use_external_browser (provider)) + { + GtkWidget *label; + GtkWidget *entry; + gchar *escaped_url; + gchar *markup; + + escaped_url = g_markup_escape_text (url, -1); + markup = g_strdup_printf (_("Paste authorization code obtained from the authorization page:"), + escaped_url); + g_free (escaped_url); + + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), markup); + g_free (markup); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0); + entry = gtk_entry_new (); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, TRUE, 0); + gtk_widget_grab_focus (entry); + gtk_widget_show_all (GTK_WIDGET (vbox)); + + gtk_dialog_add_button (dialog, GTK_STOCK_OK, GTK_RESPONSE_OK); + gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK); + gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_OK, FALSE); + g_signal_connect (entry, "changed", G_CALLBACK (on_entry_changed), &data); + + if (!gtk_show_uri (NULL, + url, + GDK_CURRENT_TIME, + &data.error)) + { + goto out; + } + } + else + { + GtkWidget *scrolled_window; + GtkWidget *web_view; + SoupSession *webkit_soup_session; + SoupCookieJar *cookie_jar; + + /* Ensure we use an empty non-persistent cookie to avoid login + * credentials being reused... + */ + webkit_soup_session = webkit_get_default_session (); + soup_session_remove_feature_by_type (webkit_soup_session, SOUP_TYPE_COOKIE_JAR); + cookie_jar = soup_cookie_jar_new (); + soup_session_add_feature (webkit_soup_session, SOUP_SESSION_FEATURE (cookie_jar)); + g_object_unref (cookie_jar); + + /* TODO: we might need to add some more web browser UI to make this + * work... + */ + web_view = webkit_web_view_new (); + webkit_web_view_load_uri (WEBKIT_WEB_VIEW (web_view), url); + g_signal_connect (web_view, + "navigation-policy-decision-requested", + G_CALLBACK (on_web_view_navigation_policy_decision_requested), + &data); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_size_request (scrolled_window, 500, 400); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (scrolled_window), web_view); + gtk_container_add (GTK_CONTAINER (vbox), scrolled_window); + gtk_widget_show_all (scrolled_window); + } + data.dialog = dialog; + gtk_dialog_run (GTK_DIALOG (dialog)); + if (data.authorization_code == NULL) + { + if (data.error == NULL) + { + g_set_error (&data.error, + GOA_ERROR, + GOA_ERROR_DIALOG_DISMISSED, + _("Dialog was dismissed")); + } + goto out; + } + g_assert (data.error == NULL); + + gtk_widget_hide (GTK_WIDGET (dialog)); + + /* OK, we now have the authorization code... now we need to get the + * email address (to e.g. check if the account already exists on + * @client).. for that we need to get a (short-lived) access token + * and a refresh_token + */ + + /* TODO: run in worker thread */ + data.access_token = get_tokens_sync (provider, + data.authorization_code, + NULL, /* refresh_token */ + &data.refresh_token, + &data.access_token_expires_in, + NULL, /* GCancellable */ + error); + if (data.access_token == NULL) + { + g_prefix_error (&data.error, _("Error getting an Access Token: ")); + goto out; + } + + /* TODO: run in worker thread */ + data.identity = goa_oauth2_provider_get_identity_sync (provider, + data.access_token, + &data.name, + NULL, /* TODO: GCancellable */ + error); + if (data.identity == NULL) + { + g_prefix_error (&data.error, _("Error getting identity: ")); + goto out; + } + + ret = TRUE; + + out: + if (ret) + { + g_warn_if_fail (data.error == NULL); + if (out_authorization_code != NULL) + *out_authorization_code = g_strdup (data.authorization_code); + if (out_access_token != NULL) + *out_access_token = g_strdup (data.access_token); + if (out_access_token_expires_in != NULL) + *out_access_token_expires_in = data.access_token_expires_in; + if (out_refresh_token != NULL) + *out_refresh_token = g_strdup (data.refresh_token); + if (out_identity != NULL) + *out_identity = g_strdup (data.identity); + if (out_name != NULL) + *out_name = g_strdup (data.name); + } + else + { + g_warn_if_fail (data.error != NULL); + g_propagate_error (error, data.error); + } + + g_free (data.identity); + g_free (data.name); + g_free (url); + + g_free (data.authorization_code); + if (data.loop != NULL) + g_main_loop_unref (data.loop); + g_free (data.access_token); + g_free (data.refresh_token); + g_free (escaped_redirect_uri); + g_free (escaped_client_id); + g_free (escaped_scope); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GError *error; + GMainLoop *loop; + gchar *account_object_path; +} AddData; + +static void +add_account_cb (GoaManager *manager, + GAsyncResult *res, + gpointer user_data) +{ + AddData *data = user_data; + goa_manager_call_add_account_finish (manager, + &data->account_object_path, + res, + &data->error); + g_main_loop_quit (data->loop); +} + +static gint64 +duration_to_abs_usec (gint duration_sec) +{ + gint64 ret; + GTimeVal now; + + g_get_current_time (&now); + ret = ((gint64) now.tv_sec) * 1000L * 1000L + ((gint64) now.tv_usec); + ret += ((gint64) duration_sec) * 1000L * 1000L; + return ret; +} + +static gint +abs_usec_to_duration (gint64 abs_usec) +{ + gint64 ret; + GTimeVal now; + + g_get_current_time (&now); + ret = abs_usec - (((gint64) now.tv_sec) * 1000L * 1000L + ((gint64) now.tv_usec)); + ret /= 1000L * 1000L; + return ret; +} + +static GoaObject * +goa_oauth2_provider_add_account (GoaProvider *_provider, + GoaClient *client, + GtkDialog *dialog, + GtkBox *vbox, + GError **error) +{ + GoaOAuth2Provider *provider = GOA_OAUTH2_PROVIDER (_provider); + GoaObject *ret; + gchar *authorization_code; + gchar *access_token; + gint access_token_expires_in; + gchar *refresh_token; + gchar *identity; + gchar *name; + GList *accounts; + GList *l; + AddData data; + GVariantBuilder builder; + + g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (provider), NULL); + g_return_val_if_fail (GOA_IS_CLIENT (client), NULL); + g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); + g_return_val_if_fail (GTK_IS_BOX (vbox), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + ret = NULL; + authorization_code = NULL; + access_token = NULL; + refresh_token = NULL; + identity = NULL; + name = NULL; + accounts = NULL; + + memset (&data, '\0', sizeof (AddData)); + data.loop = g_main_loop_new (NULL, FALSE); + + if (!get_tokens_and_identity (provider, + dialog, + vbox, + &authorization_code, + &access_token, + &access_token_expires_in, + &refresh_token, + &identity, + &name, + &data.error)) + goto out; + + /* OK, got the identity... see if there's already an account + * of this type with the given identity + */ + accounts = goa_client_get_accounts (client); + for (l = accounts; l != NULL; l = l->next) + { + GoaObject *object = GOA_OBJECT (l->data); + GoaAccount *account; + GoaOAuth2Based *oauth2_based; + const gchar *identity_from_object; + + account = goa_object_peek_account (object); + oauth2_based = goa_object_peek_oauth2_based (object); + if (oauth2_based == NULL) + continue; + + if (g_strcmp0 (goa_account_get_provider_type (account), + goa_provider_get_provider_type (GOA_PROVIDER (provider))) != 0) + continue; + + identity_from_object = goa_oauth2_based_get_identity (oauth2_based); + if (g_strcmp0 (identity_from_object, identity) == 0) + { + g_set_error (&data.error, + GOA_ERROR, + GOA_ERROR_ACCOUNT_EXISTS, + _("There is already an account for the identity %s"), + identity); + goto out; + } + } + + /* we want the GoaClient to update before this method returns (so it + * can create a proxy for the new object) so run the mainloop while + * waiting for this to complete + */ + goa_manager_call_add_account (goa_client_get_manager (client), + goa_provider_get_provider_type (GOA_PROVIDER (provider)), + name, /* Name */ + g_variant_new_parsed ("{'Identity': %s}", + identity), + NULL, /* GCancellable* */ + (GAsyncReadyCallback) add_account_cb, + &data); + g_main_loop_run (data.loop); + if (data.error != NULL) + goto out; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&builder, "{sv}", "authorization_code", g_variant_new_string (authorization_code)); + g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token)); + if (access_token_expires_in > 0) + g_variant_builder_add (&builder, "{sv}", "access_token_expires_at", + g_variant_new_int64 (duration_to_abs_usec (access_token_expires_in))); + if (refresh_token != NULL) + g_variant_builder_add (&builder, "{sv}", "refresh_token", g_variant_new_string (refresh_token)); + /* TODO: run in worker thread */ + if (!goa_provider_store_credentials_sync (GOA_PROVIDER (provider), + identity, + g_variant_builder_end (&builder), + NULL, /* GCancellable */ + &data.error)) + goto out; + + ret = GOA_OBJECT (g_dbus_object_manager_get_object (goa_client_get_object_manager (client), + data.account_object_path)); + + out: + if (data.error != NULL) + { + g_propagate_error (error, data.error); + g_assert (ret == NULL); + } + else + { + g_assert (ret != NULL); + } + + g_list_foreach (accounts, (GFunc) g_object_unref, NULL); + g_list_free (accounts); + g_free (identity); + g_free (name); + g_free (refresh_token); + g_free (access_token); + g_free (authorization_code); + g_free (data.account_object_path); + if (data.loop != NULL) + g_main_loop_unref (data.loop); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +goa_oauth2_provider_refresh_account (GoaProvider *_provider, + GoaClient *client, + GoaObject *object, + GtkWindow *parent, + GError **error) +{ + GoaOAuth2Provider *provider = GOA_OAUTH2_PROVIDER (_provider); + GtkWidget *dialog; + gchar *authorization_code; + gchar *access_token; + gint access_token_expires_in; + gchar *refresh_token; + gchar *identity; + const gchar *existing_identity; + GVariantBuilder builder; + gboolean ret; + + g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (provider), FALSE); + g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE); + g_return_val_if_fail (GOA_IS_OBJECT (object), FALSE); + g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + authorization_code = NULL; + access_token = NULL; + refresh_token = NULL; + identity = NULL; + + dialog = gtk_dialog_new_with_buttons (NULL, + parent, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 12); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + gtk_widget_show_all (dialog); + + if (!get_tokens_and_identity (provider, + GTK_DIALOG (dialog), + GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + &authorization_code, + &access_token, + &access_token_expires_in, + &refresh_token, + &identity, + NULL, /* out_name */ + error)) + goto out; + + existing_identity = goa_oauth2_based_get_identity (goa_object_peek_oauth2_based (object)); + if (g_strcmp0 (identity, existing_identity) != 0) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Was asked to login as %s, but logged in as %s"), + existing_identity, + identity); + goto out; + } + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&builder, "{sv}", "authorization_code", g_variant_new_string (authorization_code)); + g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token)); + if (access_token_expires_in > 0) + g_variant_builder_add (&builder, "{sv}", "access_token_expires_at", + g_variant_new_int64 (duration_to_abs_usec (access_token_expires_in))); + if (refresh_token != NULL) + g_variant_builder_add (&builder, "{sv}", "refresh_token", g_variant_new_string (refresh_token)); + if (!goa_provider_store_credentials_sync (GOA_PROVIDER (provider), + identity, + g_variant_builder_end (&builder), + NULL, /* GCancellable */ + error)) + goto out; + + goa_account_call_ensure_credentials (goa_object_peek_account (object), + NULL, /* GCancellable */ + NULL, NULL); /* callback, user_data */ + + ret = TRUE; + + out: + gtk_widget_destroy (dialog); + + g_free (identity); + g_free (access_token); + g_free (authorization_code); + g_free (refresh_token); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +free_mutex (GMutex *mutex) +{ + g_mutex_free (mutex); +} + +/** + * goa_oauth2_provider_get_access_token_sync: + * @provider: A #GoaOAuth2Provider. + * @object: A #GoaObject. + * @force_refresh: If set to %TRUE, forces a refresh of the access token, if possible. + * @out_access_token_expires_in: (out): Return location for how many seconds the returned token is valid for (0 if unknown) or %NULL. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously gets an access token for @object. The calling thread + * is blocked while the operation is pending. + * + * The resulting token is typically read from the local cache so most + * of the time only a local roundtrip to the storage for the token + * cache (e.g. gnome-keyring-daemon) is + * needed. However, the operation may involve refreshing the token + * with the service provider so a full network round-trip may be + * needed. + * + * Note that multiple calls are serialized to avoid multiple + * outstanding requests to the service provider. + * + * This operation may fail if e.g. unable to refresh the credentials + * or if network connectivity is not available. Note that even if a + * token is returned, the returned token isn't guaranteed to work - + * use goa_provider_ensure_credentials_sync() if you need + * stronger guarantees. + * + * Returns: The access token or %NULL if error is set. The returned + * string must be freed with g_free(). + */ +gchar * +goa_oauth2_provider_get_access_token_sync (GoaOAuth2Provider *provider, + GoaObject *object, + gboolean force_refresh, + gint *out_access_token_expires_in, + GCancellable *cancellable, + GError **error) +{ + const gchar *identity; + GVariant *credentials; + GVariantIter iter; + const gchar *key; + GVariant *value; + gchar *authorization_code; + gchar *access_token; + gint access_token_expires_in; + gchar *refresh_token; + gchar *old_refresh_token; + gboolean success; + GVariantBuilder builder; + gchar *ret; + GMutex *lock; + + g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (provider), NULL); + g_return_val_if_fail (GOA_IS_OBJECT (object), NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + ret = NULL; + credentials = NULL; + authorization_code = NULL; + access_token = NULL; + refresh_token = NULL; + old_refresh_token = NULL; + access_token_expires_in = 0; + success = FALSE; + + /* provider_lock is too coarse, use a per-object lock instead */ + G_LOCK (provider_lock); + lock = g_object_get_data (G_OBJECT (object), "-goa-oauth2-provider-get-access-token-lock"); + if (lock == NULL) + { + lock = g_mutex_new (); + g_object_set_data_full (G_OBJECT (object), + "-goa-oauth2-provider-get-access-token-lock", + lock, + (GDestroyNotify) free_mutex); + } + G_UNLOCK (provider_lock); + + g_mutex_lock (lock); + + /* First, get the credentials from the keyring */ + identity = goa_oauth2_based_get_identity (goa_object_peek_oauth2_based (object)); + credentials = goa_provider_lookup_credentials_sync (GOA_PROVIDER (provider), + identity, + cancellable, + error); + if (credentials == NULL) + { + if (error != NULL) + { + g_prefix_error (error, _("Credentials not found in keyring (%s, %d): "), + g_quark_to_string ((*error)->domain), (*error)->code); + (*error)->domain = GOA_ERROR; + (*error)->code = GOA_ERROR_NOT_AUTHORIZED; + } + goto out; + } + + g_variant_iter_init (&iter, credentials); + while (g_variant_iter_next (&iter, "{&sv}", &key, &value)) + { + if (g_strcmp0 (key, "access_token") == 0) + access_token = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "access_token_expires_at") == 0) + access_token_expires_in = abs_usec_to_duration (g_variant_get_int64 (value)); + else if (g_strcmp0 (key, "refresh_token") == 0) + refresh_token = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "authorization_code") == 0) + authorization_code = g_variant_dup_string (value, NULL); + g_variant_unref (value); + } + + if (access_token == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_NOT_AUTHORIZED, + _("Credentials does not contain access_token")); + goto out; + } + + /* if we can't refresh the token, just return it no matter what */ + if (refresh_token == NULL) + { + g_debug ("Returning locally cached credentials that cannot be refreshed"); + success = TRUE; + goto out; + } + + /* If access_token is still "fresh enough" (e.g. more than ten + * minutes of life left in it), just return it unless we've been + * asked to forcibly refresh it + */ + if (!force_refresh && access_token_expires_in > 10*60) + { + g_debug ("Returning locally cached credentials (expires in %d seconds)", access_token_expires_in); + success = TRUE; + goto out; + } + + g_debug ("Refreshing locally cached credentials (expires in %d seconds, force_refresh=%d)", access_token_expires_in, force_refresh); + + /* Otherwise, refresh it */ + old_refresh_token = refresh_token; refresh_token = NULL; + g_free (access_token); access_token = NULL; + access_token = get_tokens_sync (provider, + authorization_code, + old_refresh_token, + &refresh_token, + &access_token_expires_in, + cancellable, + error); + if (access_token == NULL) + { + if (error != NULL) + { + g_prefix_error (error, _("Failed to refresh access token (%s, %d): "), + g_quark_to_string ((*error)->domain), (*error)->code); + (*error)->code = is_authorization_error (*error) ? GOA_ERROR_NOT_AUTHORIZED : GOA_ERROR_FAILED; + (*error)->domain = GOA_ERROR; + } + goto out; + } + + /* It's not a sure thing we get a new refresh_token, so use our old + * old if we didn't get a new one + */ + if (refresh_token == NULL) + { + refresh_token = old_refresh_token; + old_refresh_token = NULL; + } + + /* Good. Now update the keyring with the refreshed credentials */ + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&builder, "{sv}", "authorization_code", g_variant_new_string (authorization_code)); + g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token)); + if (access_token_expires_in > 0) + g_variant_builder_add (&builder, "{sv}", "access_token_expires_at", + g_variant_new_int64 (duration_to_abs_usec (access_token_expires_in))); + if (refresh_token != NULL) + g_variant_builder_add (&builder, "{sv}", "refresh_token", g_variant_new_string (refresh_token)); + + identity = goa_oauth2_based_get_identity (goa_object_peek_oauth2_based (object)); + if (!goa_provider_store_credentials_sync (GOA_PROVIDER (provider), + identity, + g_variant_builder_end (&builder), + cancellable, + error)) + { + if (error != NULL) + { + g_prefix_error (error, _("Error storing credentials in keyring (%s, %d): "), + g_quark_to_string ((*error)->domain), (*error)->code); + (*error)->domain = GOA_ERROR; + (*error)->code = GOA_ERROR_NOT_AUTHORIZED; + } + goto out; + } + + success = TRUE; + + out: + if (success) + { + ret = access_token; access_token = NULL; + g_assert (ret != NULL); + if (out_access_token_expires_in != NULL) + *out_access_token_expires_in = access_token_expires_in; + } + g_free (authorization_code); + g_free (access_token); + g_free (refresh_token); + g_free (old_refresh_token); + if (credentials != NULL) + g_variant_unref (credentials); + + g_mutex_unlock (lock); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean on_handle_get_access_token (GoaOAuth2Based *object, + GDBusMethodInvocation *invocation, + gpointer user_data); + +static gboolean +goa_oauth2_provider_build_object (GoaProvider *provider, + GoaObjectSkeleton *object, + GKeyFile *key_file, + const gchar *group, + GError **error) +{ + GoaOAuth2Based *oauth2_based; + gchar *identity; + + identity = NULL; + + oauth2_based = goa_object_get_oauth2_based (GOA_OBJECT (object)); + if (oauth2_based != NULL) + goto out; + + oauth2_based = goa_oauth2_based_skeleton_new (); + /* Ensure D-Bus method invocations run in their own thread */ + g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (oauth2_based), + G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); + goa_object_skeleton_set_oauth2_based (object, oauth2_based); + g_signal_connect (oauth2_based, + "handle-get-access-token", + G_CALLBACK (on_handle_get_access_token), + NULL); + + identity = g_key_file_get_string (key_file, group, "Identity", NULL); + if (identity == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + "No Identity key"); + goto out; + } + goa_oauth2_based_set_identity (oauth2_based, identity); + + out: + g_object_unref (oauth2_based); + g_free (identity); + return TRUE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +goa_oauth2_provider_ensure_credentials_sync (GoaProvider *_provider, + GoaObject *object, + gint *out_expires_in, + GCancellable *cancellable, + GError **error) +{ + GoaOAuth2Provider *provider = GOA_OAUTH2_PROVIDER (_provider); + gboolean ret; + gchar *access_token; + gint access_token_expires_in; + gchar *identity; + gboolean force_refresh; + + ret = FALSE; + access_token = NULL; + identity = NULL; + force_refresh = FALSE; + + again: + access_token = goa_oauth2_provider_get_access_token_sync (provider, + object, + force_refresh, + &access_token_expires_in, + cancellable, + error); + if (access_token == NULL) + goto out; + + identity = goa_oauth2_provider_get_identity_sync (provider, + access_token, + NULL, /* out_name */ + cancellable, + error); + if (identity == NULL) + { + /* OK, try again, with forcing the locally cached credentials to be refreshed */ + if (!force_refresh) + { + force_refresh = TRUE; + g_free (access_token); access_token = NULL; + goto again; + } + else + { + goto out; + } + } + + /* TODO: maybe check with the identity we have */ + ret = TRUE; + if (out_expires_in != NULL) + *out_expires_in = access_token_expires_in; + + out: + g_free (identity); + g_free (access_token); + return ret; +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +goa_oauth2_provider_init (GoaOAuth2Provider *client) +{ +} + +static void +goa_oauth2_provider_class_init (GoaOAuth2ProviderClass *klass) +{ + GoaProviderClass *provider_class; + + provider_class = GOA_PROVIDER_CLASS (klass); + provider_class->add_account = goa_oauth2_provider_add_account; + provider_class->refresh_account = goa_oauth2_provider_refresh_account; + provider_class->build_object = goa_oauth2_provider_build_object; + provider_class->ensure_credentials_sync = goa_oauth2_provider_ensure_credentials_sync; + + klass->build_authorization_uri = goa_oauth2_provider_build_authorization_uri_default; + klass->get_use_external_browser = goa_oauth2_provider_get_use_external_browser_default; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* runs in a thread dedicated to handling @invocation */ +static gboolean +on_handle_get_access_token (GoaOAuth2Based *interface, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GoaObject *object; + GoaAccount *account; + GoaProvider *provider; + GError *error; + gchar *access_token; + gint access_token_expires_in; + + /* TODO: maybe log what app is requesting access */ + + access_token = NULL; + + object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (interface))); + account = goa_object_peek_account (object); + provider = goa_provider_get_for_provider_type (goa_account_get_provider_type (account)); + + error = NULL; + access_token = goa_oauth2_provider_get_access_token_sync (GOA_OAUTH2_PROVIDER (provider), + object, + FALSE, /* force_refresh */ + &access_token_expires_in, + NULL, /* GCancellable* */ + &error); + if (access_token == NULL) + { + g_dbus_method_invocation_return_gerror (invocation, error); + g_error_free (error); + } + else + { + goa_oauth2_based_complete_get_access_token (interface, + invocation, + access_token, + access_token_expires_in); + } + g_free (access_token); + g_object_unref (provider); + return TRUE; /* invocation was handled */ +} diff --git a/src/goabackend/goaoauth2provider.h b/src/goabackend/goaoauth2provider.h new file mode 100644 index 0000000..7c489f2 --- /dev/null +++ b/src/goabackend/goaoauth2provider.h @@ -0,0 +1,130 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GOA_OAUTH2_PROVIDER_H__ +#define __GOA_OAUTH2_PROVIDER_H__ + +#include + +G_BEGIN_DECLS + +#define GOA_TYPE_OAUTH2_PROVIDER (goa_oauth2_provider_get_type ()) +#define GOA_OAUTH2_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_OAUTH2_PROVIDER, GoaOAuth2Provider)) +#define GOA_OAUTH2_PROVIDER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOA_TYPE_OAUTH2_PROVIDER, GoaOAuth2ProviderClass)) +#define GOA_OAUTH2_PROVIDER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOA_TYPE_OAUTH2_PROVIDER, GoaOAuth2ProviderClass)) +#define GOA_IS_OAUTH2_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_OAUTH2_PROVIDER)) + +#define GOA_IS_OAUTH2_PROVIDER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOA_TYPE_OAUTH2_PROVIDER)) + +typedef struct _GoaOAuth2ProviderClass GoaOAuth2ProviderClass; +typedef struct _GoaOAuth2ProviderPrivate GoaOAuth2ProviderPrivate; + +/** + * GoaOAuth2Provider: + * + * The #GoaOAuth2Provider structure contains only private data and should + * only be accessed using the provided API. + */ +struct _GoaOAuth2Provider +{ + /*< private >*/ + GoaProvider parent_instance; + GoaOAuth2ProviderPrivate *priv; +}; + +/** + * GoaOAuth2ProviderClass: + * @parent_class: The parent class. + * @get_authorization_uri: Virtual function for goa_oauth2_provider_get_authorization_uri(). + * @get_token_uri: Virtual function for goa_oauth2_provider_get_token_uri(). + * @get_redirect_uri: Virtual function for goa_oauth2_provider_get_redirect_uri(). + * @get_scope: Virtual function for goa_oauth2_provider_get_scope(). + * @get_client_id: Virtual function for goa_oauth2_provider_get_client_id(). + * @get_client_secret: Virtual function for goa_oauth2_provider_get_client_secret(). + * @get_identity_sync: Virtual function for goa_oauth2_provider_get_identity_sync(). + * @build_authorization_uri: Virtual function for goa_oauth2_provider_build_authorization_uri(). + * @get_use_external_browser: Virtual function for goa_oauth2_provider_get_use_external_browser(). + * + * Class structure for #GoaOAuth2Provider. + */ +struct _GoaOAuth2ProviderClass +{ + GoaProviderClass parent_class; + + /* pure virtual */ + const gchar *(*get_authorization_uri) (GoaOAuth2Provider *provider); + const gchar *(*get_token_uri) (GoaOAuth2Provider *provider); + const gchar *(*get_redirect_uri) (GoaOAuth2Provider *provider); + const gchar *(*get_scope) (GoaOAuth2Provider *provider); + const gchar *(*get_client_id) (GoaOAuth2Provider *provider); + const gchar *(*get_client_secret) (GoaOAuth2Provider *provider); + gchar *(*get_identity_sync) (GoaOAuth2Provider *provider, + const gchar *access_token, + gchar **out_name, + GCancellable *cancellable, + GError **error); + + /* virtual but with default implementation */ + gchar *(*build_authorization_uri) (GoaOAuth2Provider *provider, + const gchar *authorization_uri, + const gchar *escaped_redirect_uri, + const gchar *escaped_client_id, + const gchar *escaped_scope); + gboolean (*get_use_external_browser) (GoaOAuth2Provider *provider); + + /*< private >*/ + /* Padding for future expansion */ + gpointer goa_reserved[32]; +}; + +GType goa_oauth2_provider_get_type (void) G_GNUC_CONST; +const gchar *goa_oauth2_provider_get_authorization_uri (GoaOAuth2Provider *provider); +const gchar *goa_oauth2_provider_get_token_uri (GoaOAuth2Provider *provider); +const gchar *goa_oauth2_provider_get_redirect_uri (GoaOAuth2Provider *provider); +const gchar *goa_oauth2_provider_get_scope (GoaOAuth2Provider *provider); +const gchar *goa_oauth2_provider_get_client_id (GoaOAuth2Provider *provider); +const gchar *goa_oauth2_provider_get_client_secret (GoaOAuth2Provider *provider); +gchar *goa_oauth2_provider_get_identity_sync (GoaOAuth2Provider *provider, + const gchar *access_token, + gchar **out_name, + GCancellable *cancellable, + GError **error); +gchar *goa_oauth2_provider_get_access_token_sync (GoaOAuth2Provider *provider, + GoaObject *object, + gboolean force_refresh, + gint *out_access_token_expires_in, + GCancellable *cancellable, + GError **error); +gchar *goa_oauth2_provider_build_authorization_uri (GoaOAuth2Provider *provider, + const gchar *authorization_uri, + const gchar *escaped_redirect_uri, + const gchar *escaped_client_id, + const gchar *escaped_scope); +gboolean goa_oauth2_provider_get_use_external_browser (GoaOAuth2Provider *provider); + +G_END_DECLS + +#endif /* __GOA_OAUTH2_PROVIDER_H__ */ diff --git a/src/goabackend/goaoauthprovider.c b/src/goabackend/goaoauthprovider.c new file mode 100644 index 0000000..543bd15 --- /dev/null +++ b/src/goabackend/goaoauthprovider.c @@ -0,0 +1,1593 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" +#include +#include + +#include +#include +#include + +#include "goaprovider.h" +#include "goaoauthprovider.h" + +/** + * SECTION:goaoauthprovider + * @title: GoaOAuthProvider + * @short_description: Abstract base class for OAuth 1.0a providers + * + * #GoaOAuthProvider is an abstract base class for OAuth 1.0a + * compliant implementations as defined by RFC + * 5849. Additionally, the code works with providers + * implementing OAuth + * Session 1.0 Draft 1 for refreshing access tokens. + * + * Subclasses must implement + * #GoaOAuthProviderClass.get_consumer_key, + * #GoaOAuthProviderClass.get_consumer_secret, + * #GoaOAuthProviderClass.get_request_uri, + * #GoaOAuthProviderClass.get_authorization_uri, + * #GoaOAuthProviderClass.get_token_uri, + * #GoaOAuthProviderClass.get_callback_uri and + * #GoaOAuthProviderClass.get_identity_sync methods. + * + * Additionally, the + * #GoaProviderClass.get_provider_type, + * #GoaProviderClass.get_name, + * #GoaProviderClass.build_object (this should chain up to its + * parent class) methods must be implemented. + * + * Note that the #GoaProviderClass.add_account, + * #GoaProviderClass.refresh_account and + * #GoaProviderClass.ensure_credentials_sync methods do not + * need to be implemented - this type implements these methods. + */ + +G_LOCK_DEFINE_STATIC (provider_lock); + +G_DEFINE_ABSTRACT_TYPE (GoaOAuthProvider, goa_oauth_provider, GOA_TYPE_PROVIDER); + +static gboolean +is_authorization_error (GError *error) +{ + gboolean ret; + + g_return_val_if_fail (error != NULL, FALSE); + + ret = FALSE; + if (error->domain == REST_PROXY_ERROR || error->domain == SOUP_HTTP_ERROR) + { + if (SOUP_STATUS_IS_CLIENT_ERROR (error->code)) + ret = TRUE; + } + return ret; +} + +G_GNUC_UNUSED static void +print_header (const gchar *name, + const gchar *value, + gpointer user_data) +{ + g_print ("header: `%s' -> `%s'\n", name, value); +} + +G_GNUC_UNUSED static void +_print_response (RestProxyCall *call) +{ + GHashTable *headers; + GHashTable *form; + GHashTableIter iter; + const gchar *key; + const gchar *value; + const gchar *payload; + + headers = rest_proxy_call_get_response_headers (call); + if (headers != NULL) + { + g_hash_table_iter_init (&iter, headers); + while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) + g_print ("Header %s: %s\n", key, value); + g_hash_table_unref (headers); + } + + payload = rest_proxy_call_get_payload (call); + if (payload != NULL) + { + form = soup_form_decode (payload); + if (form != NULL) + { + g_hash_table_iter_init (&iter, form); + while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) + g_print ("Form %s: %s\n", key, value); + g_hash_table_unref (form); + } + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +goa_oauth_provider_get_use_external_browser_default (GoaOAuthProvider *provider) +{ + return FALSE; +} + +/** + * goa_oauth_provider_get_use_external_browser: + * @provider: A #GoaOAuthProvider. + * + * Returns whether an external browser (the users default browser) + * should be used for user interaction. + * + * If an external browser is used, then the dialogs presented in + * goa_provider_add_account() and + * goa_provider_refresh_account() will show a simple "Paste + * authorization code here" instructions along with an entry and + * button. + * + * This is a virtual method where the default implementation returns + * %FALSE. + * + * Returns: %TRUE if the user interaction should happen in an external + * browser, %FALSE to use an embedded browser widget. + */ +gboolean +goa_oauth_provider_get_use_external_browser (GoaOAuthProvider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), FALSE); + return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_use_external_browser (provider); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +goa_oauth_provider_build_authorization_uri_default (GoaOAuthProvider *provider, + const gchar *authorization_uri, + const gchar *escaped_oauth_token) +{ + return g_strdup_printf ("%s" + "?oauth_token=%s", + authorization_uri, + escaped_oauth_token); +} + +/** + * goa_oauth_provider_build_authorization_uri: + * @provider: A #GoaOAuthProvider. + * @authorization_uri: An authorization URI. + * @escaped_oauth_token: An escaped oauth token. + * + * Builds the URI that can be opened in a web browser (or embedded web + * browser widget) to start authenticating an user. + * + * The default implementation just returns the expected URI + * (e.g. http://example.com/dialog/oauth?auth_token=1234567890) + * - override (and chain up) if you e.g. need to to pass additional + * parameters. + * + * The @authorization_uri parameter originate from the result of the + * the goa_oauth_provider_get_authorization_uri() method. The + * @escaped_oauth_token parameter is the temporary credentials identifier + * escaped using g_uri_escape_string(). + * + * Returns: (transfer full): An authorization URI that must be freed with g_free(). + */ +gchar * +goa_oauth_provider_build_authorization_uri (GoaOAuthProvider *provider, + const gchar *authorization_uri, + const gchar *escaped_oauth_token) +{ + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL); + g_return_val_if_fail (authorization_uri != NULL, NULL); + g_return_val_if_fail (escaped_oauth_token != NULL, NULL); + return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->build_authorization_uri (provider, + authorization_uri, + escaped_oauth_token); +} + +/** + * goa_oauth_provider_get_consumer_key: + * @provider: A #GoaOAuthProvider. + * + * Gets the consumer key identifying the client. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider - do not free. + */ +const gchar * +goa_oauth_provider_get_consumer_key (GoaOAuthProvider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL); + return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_consumer_key (provider); +} + +/** + * goa_oauth_provider_get_consumer_secret: + * @provider: A #GoaOAuthProvider. + * + * Gets the consumer secret identifying the client. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider - do not free. + */ +const gchar * +goa_oauth_provider_get_consumer_secret (GoaOAuthProvider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL); + return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_consumer_secret (provider); +} + +/** + * goa_oauth_provider_get_request_uri: + * @provider: A #GoaOAuthProvider. + * + * Gets the request uri. + * + * http://tools.ietf.org/html/rfc5849#section-2.1 + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider - do not free. + */ +const gchar * +goa_oauth_provider_get_request_uri (GoaOAuthProvider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL); + return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_request_uri (provider); +} + +/** + * goa_oauth_provider_get_request_uri_params: + * @provider: A #GoaOAuthProvider. + * + * Gets additional parameters for the request URI. + * + * http://tools.ietf.org/html/rfc5849#section-2.1 + * + * This is a virtual method where the default implementation returns + * %NULL. + * + * Returns: (transfer full): %NULL (for no parameters) or a + * %NULL-terminated array of (key, value) pairs that will be added to + * the URI. The caller will free the returned value with g_strfreev(). + */ +gchar ** +goa_oauth_provider_get_request_uri_params (GoaOAuthProvider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL); + return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_request_uri_params (provider); +} + +static gchar ** +goa_oauth_provider_get_request_uri_params_default (GoaOAuthProvider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL); + return NULL; +} + +/** + * goa_oauth_provider_get_authorization_uri: + * @provider: A #GoaOAuthProvider. + * + * Gets the authorization uri. + * + * http://tools.ietf.org/html/rfc5849#section-2.2 + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider - do not free. + */ +const gchar * +goa_oauth_provider_get_authorization_uri (GoaOAuthProvider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL); + return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_authorization_uri (provider); +} + +/** + * goa_oauth_provider_get_token_uri: + * @provider: A #GoaOAuthProvider. + * + * Gets the token uri. + * + * http://tools.ietf.org/html/rfc5849#section-2.3 + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider - do not free. + */ +const gchar * +goa_oauth_provider_get_token_uri (GoaOAuthProvider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL); + return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_token_uri (provider); +} + +/** + * goa_oauth_provider_get_callback_uri: + * @provider: A #GoaOAuthProvider. + * + * Gets the callback uri. + * + * http://tools.ietf.org/html/rfc5849#section-2.1 + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider - do not free. + */ +const gchar * +goa_oauth_provider_get_callback_uri (GoaOAuthProvider *provider) +{ + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL); + return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_callback_uri (provider); +} + +/** + * goa_oauth_provider_get_identity_sync: + * @provider: A #GoaOAuthProvider. + * @access_token: A valid OAuth 1.0 access token. + * @access_token_secret: The valid secret for @access_token. + * @out_name: (out): Return location for name or %NULL. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Method that returns the identity corresponding to @access_token and + * @access_token_secret. + * + * The identity is needed because all authentication happens out of + * band. The only requirement is that the returned identity is unique + * - for example, for #GoaGoogleProvider the returned identity + * is the email address, for #GoaFacebookProvider it's the user + * name. In addition to the identity, an implementation also returns a + * name in @out_name that is more suitable for + * presentation (the identity could be a GUID for example) and doesn't + * have to be unique. + * + * The calling thread is blocked while the identity is obtained. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: The identity or %NULL if error is set. The returned string + * must be freed with g_free(). + */ +gchar * +goa_oauth_provider_get_identity_sync (GoaOAuthProvider *provider, + const gchar *access_token, + const gchar *access_token_secret, + gchar **out_name, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL); + g_return_val_if_fail (access_token != NULL, NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_identity_sync (provider, access_token, access_token_secret, out_name, cancellable, error); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +get_tokens_sync (GoaOAuthProvider *provider, + const gchar *token, + const gchar *token_secret, + const gchar *session_handle, /* may be NULL */ + const gchar *verifier, /* may be NULL */ + gchar **out_access_token_secret, + gint *out_access_token_expires_in, + gchar **out_session_handle, + gint *out_session_handle_expires_in, + GCancellable *cancellable, + GError **error) +{ + RestProxy *proxy; + RestProxyCall *call; + gchar *ret; + guint status_code; + GHashTable *f; + const gchar *expires_in_str; + gchar *ret_access_token; + gchar *ret_access_token_secret; + gint ret_access_token_expires_in; + gchar *ret_session_handle; + gint ret_session_handle_expires_in; + + ret = NULL; + ret_access_token = NULL; + ret_access_token_secret = NULL; + ret_access_token_expires_in = 0; + ret_session_handle = NULL; + ret_session_handle_expires_in = 0; + + proxy = oauth_proxy_new (goa_oauth_provider_get_consumer_key (provider), + goa_oauth_provider_get_consumer_secret (provider), + goa_oauth_provider_get_token_uri (provider), + FALSE); + oauth_proxy_set_token (OAUTH_PROXY (proxy), token); + oauth_proxy_set_token_secret (OAUTH_PROXY (proxy), token_secret); + call = rest_proxy_new_call (proxy); + rest_proxy_call_set_method (call, "POST"); + if (verifier != NULL) + rest_proxy_call_add_param (call, "oauth_verifier", verifier); + if (session_handle != NULL) + rest_proxy_call_add_param (call, "oauth_session_handle", session_handle); + /* TODO: cancellable support? */ + if (!rest_proxy_call_sync (call, error)) + goto out; + + status_code = rest_proxy_call_get_status_code (call); + if (status_code != 200) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Expected status 200 when requesting access token, instead got status %d (%s)"), + status_code, + rest_proxy_call_get_status_message (call)); + goto out; + } + + f = soup_form_decode (rest_proxy_call_get_payload (call)); + ret_access_token = g_strdup (g_hash_table_lookup (f, "oauth_token")); + ret_access_token_secret = g_strdup (g_hash_table_lookup (f, "oauth_token_secret")); + ret_session_handle = g_strdup (g_hash_table_lookup (f, "oauth_session_handle")); + expires_in_str = g_hash_table_lookup (f, "oauth_expires_in"); + if (expires_in_str != NULL) + ret_access_token_expires_in = atoi (expires_in_str); + expires_in_str = g_hash_table_lookup (f, "oauth_authorization_expires_in"); + if (expires_in_str != NULL) + ret_session_handle_expires_in = atoi (expires_in_str); + g_hash_table_unref (f); + + if (ret_access_token == NULL || ret_access_token_secret == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Missing access_token or access_token_secret headers in response")); + goto out; + } + + ret = ret_access_token; ret_access_token = NULL; + if (out_access_token_secret != NULL) + { + *out_access_token_secret = ret_access_token_secret; + ret_access_token_secret = NULL; + } + if (out_access_token_expires_in != NULL) + *out_access_token_expires_in = ret_access_token_expires_in; + if (out_session_handle != NULL) + { + *out_session_handle = ret_session_handle; + ret_session_handle = NULL; + } + if (out_session_handle_expires_in != NULL) + *out_session_handle_expires_in = ret_session_handle_expires_in; + + out: + g_free (ret_access_token); + g_free (ret_access_token_secret); + g_free (ret_session_handle); + if (call != NULL) + g_object_unref (call); + if (proxy != NULL) + g_object_unref (proxy); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GoaOAuthProvider *provider; + GtkDialog *dialog; + GError *error; + GMainLoop *loop; + + gchar *oauth_verifier; + + gchar *identity; + gchar *name; + + gchar *request_token; + gchar *request_token_secret; + gchar *access_token; + gchar *access_token_secret; + gint access_token_expires_in; + gchar *session_handle; + gint session_handle_expires_in; +} IdentifyData; + +static gboolean +on_web_view_navigation_policy_decision_requested (WebKitWebView *webView, + WebKitWebFrame *frame, + WebKitNetworkRequest *request, + WebKitWebNavigationAction *navigation_action, + WebKitWebPolicyDecision *policy_decision, + gpointer user_data) +{ + IdentifyData *data = user_data; + const gchar *redirect_uri; + const gchar *requested_uri; + + /* TODO: use oauth_proxy_extract_access_token() */ + + requested_uri = webkit_network_request_get_uri (request); + redirect_uri = goa_oauth_provider_get_callback_uri (data->provider); + if (g_str_has_prefix (requested_uri, redirect_uri)) + { + SoupMessage *message; + SoupURI *uri; + GHashTable *key_value_pairs; + + message = webkit_network_request_get_message (request); + uri = soup_message_get_uri (message); + key_value_pairs = soup_form_decode (uri->query); + + /* TODO: error handling? */ + data->oauth_verifier = g_strdup (g_hash_table_lookup (key_value_pairs, "oauth_verifier")); + if (data->oauth_verifier != NULL) + { + gtk_dialog_response (data->dialog, GTK_RESPONSE_OK); + } + g_hash_table_unref (key_value_pairs); + webkit_web_policy_decision_ignore (policy_decision); + return TRUE; /* ignore the request */ + } + else + { + return FALSE; /* make default behavior apply */ + } +} + +static void +on_entry_changed (GtkEditable *editable, + gpointer user_data) +{ + IdentifyData *data = user_data; + gboolean sensitive; + + g_free (data->oauth_verifier); + data->oauth_verifier = g_strdup (gtk_entry_get_text (GTK_ENTRY (editable))); + sensitive = data->oauth_verifier != NULL && (strlen (data->oauth_verifier) > 0); + gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, sensitive); +} + +static gboolean +get_tokens_and_identity (GoaOAuthProvider *provider, + GtkDialog *dialog, + GtkBox *vbox, + gchar **out_access_token, + gchar **out_access_token_secret, + gint *out_access_token_expires_in, + gchar **out_session_handle, + gint *out_session_handle_expires_in, + gchar **out_identity, + gchar **out_name, + GError **error) +{ + gboolean ret; + gchar *url; + IdentifyData data; + gchar *escaped_request_token; + RestProxy *proxy; + RestProxyCall *call; + GHashTable *f; + gboolean use_external_browser; + gchar **request_params; + guint n; + + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), FALSE); + g_return_val_if_fail (GTK_IS_DIALOG (dialog), FALSE); + g_return_val_if_fail (GTK_IS_BOX (vbox), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ret = FALSE; + escaped_request_token = NULL; + proxy = NULL; + call = NULL; + url = NULL; + request_params = NULL; + + use_external_browser = goa_oauth_provider_get_use_external_browser (provider); + + /* TODO: check with NM whether we're online, if not - return error */ + + memset (&data, '\0', sizeof (IdentifyData)); + data.provider = provider; + data.loop = g_main_loop_new (NULL, FALSE); + + /* TODO: run in worker thread */ + proxy = oauth_proxy_new (goa_oauth_provider_get_consumer_key (provider), + goa_oauth_provider_get_consumer_secret (provider), + goa_oauth_provider_get_request_uri (provider), FALSE); + call = rest_proxy_new_call (proxy); + rest_proxy_call_set_method (call, "POST"); + if (use_external_browser) + rest_proxy_call_add_param (call, "oauth_callback", "oob"); + else + rest_proxy_call_add_param (call, "oauth_callback", goa_oauth_provider_get_callback_uri (provider)); + + request_params = goa_oauth_provider_get_request_uri_params (provider); + if (request_params != NULL) + { + g_assert (g_strv_length (request_params) % 2 == 0); + for (n = 0; request_params[n] != NULL; n += 2) + rest_proxy_call_add_param (call, request_params[n], request_params[n+1]); + } + if (!rest_proxy_call_sync (call, &data.error)) + { + g_prefix_error (&data.error, _("Error getting a Request Token: ")); + goto out; + } + if (rest_proxy_call_get_status_code (call) != 200) + { + g_set_error (&data.error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Expected 200 for getting a Request Token, got %d (%s)"), + rest_proxy_call_get_status_code (call), + rest_proxy_call_get_status_message (call)); + goto out; + } + f = soup_form_decode (rest_proxy_call_get_payload (call)); + data.request_token = g_strdup (g_hash_table_lookup (f, "oauth_token")); + data.request_token_secret = g_strdup (g_hash_table_lookup (f, "oauth_token_secret")); + g_hash_table_unref (f); + if (data.request_token == NULL || data.request_token_secret == NULL) + { + g_set_error (&data.error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Missing request_token or request_token_secret headers in response")); + goto out; + } + + escaped_request_token = g_uri_escape_string (data.request_token, NULL, TRUE); + url = goa_oauth_provider_build_authorization_uri (provider, + goa_oauth_provider_get_authorization_uri (provider), + escaped_request_token); + if (use_external_browser) + { + GtkWidget *label; + GtkWidget *entry; + gchar *escaped_url; + gchar *markup; + + escaped_url = g_markup_escape_text (url, -1); + markup = g_strdup_printf (_("Paste token obtained from the authorization page:"), + escaped_url); + g_free (escaped_url); + + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), markup); + g_free (markup); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0); + entry = gtk_entry_new (); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, TRUE, 0); + gtk_widget_grab_focus (entry); + gtk_widget_show_all (GTK_WIDGET (vbox)); + + gtk_dialog_add_button (dialog, GTK_STOCK_OK, GTK_RESPONSE_OK); + gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK); + gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_OK, FALSE); + g_signal_connect (entry, "changed", G_CALLBACK (on_entry_changed), &data); + + if (!gtk_show_uri (NULL, + url, + GDK_CURRENT_TIME, + &data.error)) + { + goto out; + } + } + else + { + GtkWidget *scrolled_window; + GtkWidget *web_view; + SoupSession *webkit_soup_session; + SoupCookieJar *cookie_jar; + + /* Ensure we use an empty non-persistent cookie to avoid login + * credentials being reused... + */ + webkit_soup_session = webkit_get_default_session (); + soup_session_remove_feature_by_type (webkit_soup_session, SOUP_TYPE_COOKIE_JAR); + cookie_jar = soup_cookie_jar_new (); + soup_session_add_feature (webkit_soup_session, SOUP_SESSION_FEATURE (cookie_jar)); + g_object_unref (cookie_jar); + + /* TODO: we might need to add some more web browser UI to make this + * work... + */ + web_view = webkit_web_view_new (); + webkit_web_view_load_uri (WEBKIT_WEB_VIEW (web_view), url); + g_signal_connect (web_view, + "navigation-policy-decision-requested", + G_CALLBACK (on_web_view_navigation_policy_decision_requested), + &data); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_size_request (scrolled_window, 500, 400); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (scrolled_window), web_view); + gtk_container_add (GTK_CONTAINER (vbox), scrolled_window); + + gtk_widget_show_all (scrolled_window); + } + data.dialog = dialog; + gtk_dialog_run (GTK_DIALOG (dialog)); + if (data.oauth_verifier == NULL) + { + if (data.error == NULL) + { + g_set_error (&data.error, + GOA_ERROR, + GOA_ERROR_DIALOG_DISMISSED, + _("Dialog was dismissed")); + } + goto out; + } + g_assert (data.error == NULL); + + /* OK, we are done interacting with the user ... but before we can + * make up our mind, there are two more RPC calls to make and these + * call may take some time. So hide the dialog immediately. + */ + gtk_widget_hide (GTK_WIDGET (dialog)); + + g_print ("woot verifier is %s\n", data.oauth_verifier); + + /* OK, we now have the request token... we can exchange that for a + * (short-lived) access token and session_handle (used to refresh the + * access token).. + */ + + /* TODO: run in worker thread */ + data.access_token = get_tokens_sync (provider, + data.request_token, + data.request_token_secret, + NULL, /* session_handle */ + data.oauth_verifier, + &data.access_token_secret, + &data.access_token_expires_in, + &data.session_handle, + &data.session_handle_expires_in, + NULL, /* GCancellable */ + &data.error); + if (data.access_token == NULL) + { + g_prefix_error (&data.error, _("Error getting an Access Token: ")); + goto out; + } + + /* TODO: run in worker thread */ + data.identity = goa_oauth_provider_get_identity_sync (provider, + data.access_token, + data.access_token_secret, + &data.name, + NULL, /* TODO: GCancellable */ + &data.error); + if (data.identity == NULL) + { + g_prefix_error (&data.error, _("Error getting identity: ")); + goto out; + } + + ret = TRUE; + + out: + if (call != NULL) + g_object_unref (call); + + if (ret) + { + g_warn_if_fail (data.error == NULL); + if (out_access_token != NULL) + *out_access_token = g_strdup (data.access_token); + if (out_access_token_secret != NULL) + *out_access_token_secret = g_strdup (data.access_token_secret); + if (out_access_token_expires_in != NULL) + *out_access_token_expires_in = data.access_token_expires_in; + if (out_session_handle != NULL) + *out_session_handle = g_strdup (data.session_handle); + if (out_session_handle_expires_in != NULL) + *out_session_handle_expires_in = data.session_handle_expires_in; + if (out_identity != NULL) + *out_identity = g_strdup (data.identity); + if (out_name != NULL) + *out_name = g_strdup (data.name); + } + else + { + g_warn_if_fail (data.error != NULL); + g_propagate_error (error, data.error); + } + + g_free (data.name); + g_free (data.identity); + g_free (url); + + g_free (data.oauth_verifier); + if (data.loop != NULL) + g_main_loop_unref (data.loop); + g_free (data.access_token); + g_free (data.access_token_secret); + g_free (escaped_request_token); + + g_free (data.request_token); + g_free (data.request_token_secret); + + g_strfreev (request_params); + if (proxy != NULL) + g_object_unref (proxy); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GError *error; + GMainLoop *loop; + gchar *account_object_path; +} AddData; + +static void +add_account_cb (GoaManager *manager, + GAsyncResult *res, + gpointer user_data) +{ + AddData *data = user_data; + goa_manager_call_add_account_finish (manager, + &data->account_object_path, + res, + &data->error); + g_main_loop_quit (data->loop); +} + +static gint64 +duration_to_abs_usec (gint duration_sec) +{ + gint64 ret; + GTimeVal now; + + g_get_current_time (&now); + ret = ((gint64) now.tv_sec) * 1000L * 1000L + ((gint64) now.tv_usec); + ret += ((gint64) duration_sec) * 1000L * 1000L; + return ret; +} + +static gint +abs_usec_to_duration (gint64 abs_usec) +{ + gint64 ret; + GTimeVal now; + + g_get_current_time (&now); + ret = abs_usec - (((gint64) now.tv_sec) * 1000L * 1000L + ((gint64) now.tv_usec)); + ret /= 1000L * 1000L; + return ret; +} + +static GoaObject * +goa_oauth_provider_add_account (GoaProvider *_provider, + GoaClient *client, + GtkDialog *dialog, + GtkBox *vbox, + GError **error) +{ + GoaOAuthProvider *provider = GOA_OAUTH_PROVIDER (_provider); + GoaObject *ret; + gchar *access_token; + gchar *access_token_secret; + gint access_token_expires_in; + gchar *session_handle; + gint session_handle_expires_in; + gchar *identity; + gchar *name; + GList *accounts; + GList *l; + AddData data; + GVariantBuilder builder; + + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL); + g_return_val_if_fail (GOA_IS_CLIENT (client), NULL); + g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); + g_return_val_if_fail (GTK_IS_BOX (vbox), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + ret = NULL; + access_token = NULL; + access_token_secret = NULL; + session_handle = NULL; + identity = NULL; + name = NULL; + accounts = NULL; + + memset (&data, '\0', sizeof (AddData)); + data.loop = g_main_loop_new (NULL, FALSE); + + if (!get_tokens_and_identity (provider, + dialog, + vbox, + &access_token, + &access_token_secret, + &access_token_expires_in, + &session_handle, + &session_handle_expires_in, + &identity, + &name, + &data.error)) + goto out; + + /* OK, got the identity... see if there's already an account + * of this type with the given identity + */ + accounts = goa_client_get_accounts (client); + for (l = accounts; l != NULL; l = l->next) + { + GoaObject *object = GOA_OBJECT (l->data); + GoaAccount *account; + GoaOAuthBased *oauth_based; + const gchar *identity_from_object; + + account = goa_object_peek_account (object); + oauth_based = goa_object_peek_oauth_based (object); + if (oauth_based == NULL) + continue; + + if (g_strcmp0 (goa_account_get_provider_type (account), + goa_provider_get_provider_type (GOA_PROVIDER (provider))) != 0) + continue; + + identity_from_object = goa_oauth_based_get_identity (oauth_based); + if (g_strcmp0 (identity_from_object, identity) == 0) + { + g_set_error (&data.error, + GOA_ERROR, + GOA_ERROR_ACCOUNT_EXISTS, + _("There is already an account for the identity %s"), + identity); + goto out; + } + } + + /* we want the GoaClient to update before this method returns (so it + * can create a proxy for the new object) so run the mainloop while + * waiting for this to complete + */ + goa_manager_call_add_account (goa_client_get_manager (client), + goa_provider_get_provider_type (GOA_PROVIDER (provider)), + name, /* Name */ + g_variant_new_parsed ("{'Identity': %s}", + identity), + NULL, /* GCancellable* */ + (GAsyncReadyCallback) add_account_cb, + &data); + g_main_loop_run (data.loop); + if (data.error != NULL) + goto out; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token)); + g_variant_builder_add (&builder, "{sv}", "access_token_secret", g_variant_new_string (access_token_secret)); + if (access_token_expires_in > 0) + g_variant_builder_add (&builder, "{sv}", "access_token_expires_at", + g_variant_new_int64 (duration_to_abs_usec (access_token_expires_in))); + if (session_handle != NULL) + g_variant_builder_add (&builder, "{sv}", "session_handle", g_variant_new_string (session_handle)); + if (session_handle_expires_in > 0) + g_variant_builder_add (&builder, "{sv}", "session_handle_expires_at", + g_variant_new_int64 (duration_to_abs_usec (session_handle_expires_in))); + /* TODO: run in worker thread */ + if (!goa_provider_store_credentials_sync (GOA_PROVIDER (provider), + identity, + g_variant_builder_end (&builder), + NULL, /* GCancellable */ + &data.error)) + goto out; + + ret = GOA_OBJECT (g_dbus_object_manager_get_object (goa_client_get_object_manager (client), + data.account_object_path)); + + out: + if (data.error != NULL) + { + g_propagate_error (error, data.error); + g_assert (ret == NULL); + } + else + { + g_assert (ret != NULL); + } + + g_list_foreach (accounts, (GFunc) g_object_unref, NULL); + g_list_free (accounts); + g_free (identity); + g_free (name); + g_free (access_token); + g_free (access_token_secret); + g_free (session_handle); + g_free (data.account_object_path); + if (data.loop != NULL) + g_main_loop_unref (data.loop); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +goa_oauth_provider_refresh_account (GoaProvider *_provider, + GoaClient *client, + GoaObject *object, + GtkWindow *parent, + GError **error) +{ + GoaOAuthProvider *provider = GOA_OAUTH_PROVIDER (_provider); + GtkWidget *dialog; + gchar *access_token; + gchar *access_token_secret; + gint access_token_expires_in; + gchar *session_handle; + gint session_handle_expires_in; + gchar *identity; + const gchar *existing_identity; + GVariantBuilder builder; + gboolean ret; + + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), FALSE); + g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE); + g_return_val_if_fail (GOA_IS_OBJECT (object), FALSE); + g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + access_token = NULL; + access_token_secret = NULL; + session_handle = NULL; + identity = NULL; + + dialog = gtk_dialog_new_with_buttons (NULL, + parent, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 12); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + gtk_widget_show_all (dialog); + + if (!get_tokens_and_identity (provider, + GTK_DIALOG (dialog), + GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + &access_token, + &access_token_secret, + &access_token_expires_in, + &session_handle, + &session_handle_expires_in, + &identity, + NULL, /* out_name */ + error)) + goto out; + + existing_identity = goa_oauth_based_get_identity (goa_object_peek_oauth_based (object)); + if (g_strcmp0 (identity, existing_identity) != 0) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Was asked to login as %s, but logged in as %s"), + existing_identity, + identity); + goto out; + } + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token)); + g_variant_builder_add (&builder, "{sv}", "access_token_secret", g_variant_new_string (access_token_secret)); + if (access_token_expires_in > 0) + g_variant_builder_add (&builder, "{sv}", "access_token_expires_at", + g_variant_new_int64 (duration_to_abs_usec (access_token_expires_in))); + if (session_handle != NULL) + g_variant_builder_add (&builder, "{sv}", "session_handle", g_variant_new_string (session_handle)); + if (session_handle_expires_in > 0) + g_variant_builder_add (&builder, "{sv}", "session_handle_expires_at", + g_variant_new_int64 (duration_to_abs_usec (session_handle_expires_in))); + /* TODO: run in worker thread */ + if (!goa_provider_store_credentials_sync (GOA_PROVIDER (provider), + identity, + g_variant_builder_end (&builder), + NULL, /* GCancellable */ + error)) + goto out; + + goa_account_call_ensure_credentials (goa_object_peek_account (object), + NULL, /* GCancellable */ + NULL, NULL); /* callback, user_data */ + + ret = TRUE; + + out: + gtk_widget_destroy (dialog); + + g_free (identity); + g_free (access_token); + g_free (access_token_secret); + g_free (session_handle); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +free_mutex (GMutex *mutex) +{ + g_mutex_free (mutex); +} + +/** + * goa_oauth_provider_get_access_token_sync: + * @provider: A #GoaOAuthProvider. + * @object: A #GoaObject. + * @force_refresh: If set to %TRUE, forces a refresh of the access token, if possible. + * @out_access_token_secret: (out): The secret for the return access token. + * @out_access_token_expires_in: (out): Return location for how many seconds the returned token is valid for (0 if unknown) or %NULL. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously gets an access token for @object. The calling thread + * is blocked while the operation is pending. + * + * The resulting token is typically read from the local cache so most + * of the time only a local roundtrip to the storage for the token + * cache (e.g. gnome-keyring-daemon) is + * needed. However, the operation may involve refreshing the token + * with the service provider so a full network round-trip may be + * needed. + * + * Note that multiple calls are serialized to avoid multiple + * outstanding requests to the service provider. + * + * This operation may fail if e.g. unable to refresh the credentials + * or if network connectivity is not available. Note that even if a + * token is returned, the returned token isn't guaranteed to work - + * use goa_provider_ensure_credentials_sync() if you need + * stronger guarantees. + * + * Returns: The access token or %NULL if error is set. The returned + * string must be freed with g_free(). + */ +gchar * +goa_oauth_provider_get_access_token_sync (GoaOAuthProvider *provider, + GoaObject *object, + gboolean force_refresh, + gchar **out_access_token_secret, + gint *out_access_token_expires_in, + GCancellable *cancellable, + GError **error) +{ + const gchar *identity; + GVariant *credentials; + GVariantIter iter; + const gchar *key; + GVariant *value; + gchar *access_token; + gchar *access_token_secret; + gchar *session_handle; + gchar *access_token_for_refresh; + gchar *access_token_secret_for_refresh; + gchar *session_handle_for_refresh; + gint access_token_expires_in; + gint session_handle_expires_in; + gboolean success; + GVariantBuilder builder; + gchar *ret; + GMutex *lock; + + g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL); + g_return_val_if_fail (GOA_IS_OBJECT (object), NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + ret = NULL; + credentials = NULL; + access_token = NULL; + access_token_secret = NULL; + access_token_expires_in = 0; + session_handle = NULL; + session_handle_expires_in = 0; + access_token_for_refresh = NULL; + access_token_secret_for_refresh = NULL; + session_handle_for_refresh = NULL; + success = FALSE; + + /* provider_lock is too coarse, use a per-object lock instead */ + G_LOCK (provider_lock); + lock = g_object_get_data (G_OBJECT (object), "-goa-oauth-provider-get-access-token-lock"); + if (lock == NULL) + { + lock = g_mutex_new (); + g_object_set_data_full (G_OBJECT (object), + "-goa-oauth-provider-get-access-token-lock", + lock, + (GDestroyNotify) free_mutex); + } + G_UNLOCK (provider_lock); + + g_mutex_lock (lock); + + /* First, get the credentials from the keyring */ + identity = goa_oauth_based_get_identity (goa_object_peek_oauth_based (object)); + credentials = goa_provider_lookup_credentials_sync (GOA_PROVIDER (provider), + identity, + cancellable, + error); + if (credentials == NULL) + { + if (error != NULL) + { + g_prefix_error (error, _("Credentials not found in keyring (%s, %d): "), + g_quark_to_string ((*error)->domain), (*error)->code); + (*error)->domain = GOA_ERROR; + (*error)->code = GOA_ERROR_NOT_AUTHORIZED; + } + goto out; + } + + g_variant_iter_init (&iter, credentials); + while (g_variant_iter_next (&iter, "{&sv}", &key, &value)) + { + if (g_strcmp0 (key, "access_token") == 0) + access_token = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "access_token_secret") == 0) + access_token_secret = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "access_token_expires_at") == 0) + access_token_expires_in = abs_usec_to_duration (g_variant_get_int64 (value)); + else if (g_strcmp0 (key, "session_handle") == 0) + session_handle = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "session_handle_expires_at") == 0) + session_handle_expires_in = abs_usec_to_duration (g_variant_get_int64 (value)); + g_variant_unref (value); + } + + if (access_token == NULL || access_token_secret == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_NOT_AUTHORIZED, + _("Credentials does not contain access_token or access_token_secret")); + goto out; + } + + /* if we can't refresh the token, just return it no matter what */ + if (session_handle == NULL) + { + g_debug ("Returning locally cached credentials that cannot be refreshed"); + success = TRUE; + goto out; + } + + /* If access_token is still "fresh enough" (e.g. more than ten + * minutes of life left in it), just return it unless we've been + * asked to forcibly refresh it + */ + if (!force_refresh && access_token_expires_in > 10*60) + { + g_debug ("Returning locally cached credentials (expires in %d seconds)", access_token_expires_in); + success = TRUE; + goto out; + } + + g_debug ("Refreshing locally cached credentials (expires in %d seconds, force_refresh=%d)", access_token_expires_in, force_refresh); + + /* Otherwise, refresh it */ + access_token_for_refresh = access_token; access_token = NULL; + access_token_secret_for_refresh = access_token_secret; access_token_secret = NULL; + session_handle_for_refresh = session_handle; session_handle = NULL; + access_token = get_tokens_sync (provider, + access_token_for_refresh, + access_token_secret_for_refresh, + session_handle_for_refresh, + NULL, /* verifier */ + &access_token_secret, + &access_token_expires_in, + &session_handle, + &session_handle_expires_in, + cancellable, + error); + if (access_token == NULL) + { + if (error != NULL) + { + g_prefix_error (error, _("Failed to refresh access token (%s, %d): "), + g_quark_to_string ((*error)->domain), (*error)->code); + (*error)->code = is_authorization_error (*error) ? GOA_ERROR_NOT_AUTHORIZED : GOA_ERROR_FAILED; + (*error)->domain = GOA_ERROR; + } + goto out; + } + + /* Good. Now update the keyring with the refreshed credentials */ + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token)); + g_variant_builder_add (&builder, "{sv}", "access_token_secret", g_variant_new_string (access_token_secret)); + if (access_token_expires_in > 0) + g_variant_builder_add (&builder, "{sv}", "access_token_expires_at", + g_variant_new_int64 (duration_to_abs_usec (access_token_expires_in))); + if (session_handle != NULL) + g_variant_builder_add (&builder, "{sv}", "session_handle", g_variant_new_string (session_handle)); + if (session_handle_expires_in > 0) + g_variant_builder_add (&builder, "{sv}", "session_handle_expires_at", + g_variant_new_int64 (duration_to_abs_usec (session_handle_expires_in))); + + identity = goa_oauth_based_get_identity (goa_object_peek_oauth_based (object)); + /* TODO: run in worker thread */ + if (!goa_provider_store_credentials_sync (GOA_PROVIDER (provider), + identity, + g_variant_builder_end (&builder), + cancellable, + error)) + { + if (error != NULL) + { + g_prefix_error (error, _("Error storing credentials in keyring (%s, %d): "), + g_quark_to_string ((*error)->domain), (*error)->code); + (*error)->domain = GOA_ERROR; + (*error)->code = GOA_ERROR_NOT_AUTHORIZED; + } + goto out; + } + + success = TRUE; + + out: + if (success) + { + ret = access_token; access_token = NULL; + g_assert (ret != NULL); + if (out_access_token_secret != NULL) + { + *out_access_token_secret = access_token_secret; access_token_secret = NULL; + } + if (out_access_token_expires_in != NULL) + *out_access_token_expires_in = access_token_expires_in; + } + g_free (access_token); + g_free (access_token_secret); + g_free (session_handle); + g_free (access_token_for_refresh); + g_free (access_token_secret_for_refresh); + g_free (session_handle_for_refresh); + if (credentials != NULL) + g_variant_unref (credentials); + + g_mutex_unlock (lock); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean on_handle_get_access_token (GoaOAuthBased *object, + GDBusMethodInvocation *invocation, + gpointer user_data); + +static gboolean +goa_oauth_provider_build_object (GoaProvider *provider, + GoaObjectSkeleton *object, + GKeyFile *key_file, + const gchar *group, + GError **error) +{ + GoaOAuthBased *oauth_based; + gchar *identity; + + identity = NULL; + + oauth_based = goa_object_get_oauth_based (GOA_OBJECT (object)); + if (oauth_based != NULL) + goto out; + + oauth_based = goa_oauth_based_skeleton_new (); + /* Ensure D-Bus method invocations run in their own thread */ + g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (oauth_based), + G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); + goa_object_skeleton_set_oauth_based (object, oauth_based); + g_signal_connect (oauth_based, + "handle-get-access-token", + G_CALLBACK (on_handle_get_access_token), + NULL); + + identity = g_key_file_get_string (key_file, group, "Identity", NULL); + if (identity == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + "No Identity key"); + goto out; + } + goa_oauth_based_set_identity (oauth_based, identity); + + out: + g_object_unref (oauth_based); + g_free (identity); + return TRUE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +goa_oauth_provider_ensure_credentials_sync (GoaProvider *_provider, + GoaObject *object, + gint *out_expires_in, + GCancellable *cancellable, + GError **error) +{ + GoaOAuthProvider *provider = GOA_OAUTH_PROVIDER (_provider); + gboolean ret; + gchar *access_token; + gchar *access_token_secret; + gint access_token_expires_in; + gchar *identity; + gboolean force_refresh; + + ret = FALSE; + access_token = NULL; + access_token_secret = NULL; + identity = NULL; + force_refresh = FALSE; + + again: + access_token = goa_oauth_provider_get_access_token_sync (provider, + object, + force_refresh, + &access_token_secret, + &access_token_expires_in, + cancellable, + error); + if (access_token == NULL) + goto out; + + identity = goa_oauth_provider_get_identity_sync (provider, + access_token, + access_token_secret, + NULL, /* out_name */ + cancellable, + error); + if (identity == NULL) + { + /* OK, try again, with forcing the locally cached credentials to be refreshed */ + if (!force_refresh) + { + force_refresh = TRUE; + g_free (access_token); access_token = NULL; + g_free (access_token_secret); access_token_secret = NULL; + goto again; + } + else + { + goto out; + } + } + + /* TODO: maybe check with the identity we have */ + ret = TRUE; + if (out_expires_in != NULL) + *out_expires_in = access_token_expires_in; + + out: + g_free (identity); + g_free (access_token); + g_free (access_token_secret); + return ret; +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +goa_oauth_provider_init (GoaOAuthProvider *client) +{ +} + +static void +goa_oauth_provider_class_init (GoaOAuthProviderClass *klass) +{ + GoaProviderClass *provider_class; + + provider_class = GOA_PROVIDER_CLASS (klass); + provider_class->add_account = goa_oauth_provider_add_account; + provider_class->refresh_account = goa_oauth_provider_refresh_account; + provider_class->build_object = goa_oauth_provider_build_object; + provider_class->ensure_credentials_sync = goa_oauth_provider_ensure_credentials_sync; + + klass->build_authorization_uri = goa_oauth_provider_build_authorization_uri_default; + klass->get_use_external_browser = goa_oauth_provider_get_use_external_browser_default; + klass->get_request_uri_params = goa_oauth_provider_get_request_uri_params_default; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* runs in a thread dedicated to handling @invocation */ +static gboolean +on_handle_get_access_token (GoaOAuthBased *interface, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GoaObject *object; + GoaAccount *account; + GoaProvider *provider; + GError *error; + gchar *access_token; + gchar *access_token_secret; + gint access_token_expires_in; + + /* TODO: maybe log what app is requesting access */ + + access_token = NULL; + access_token_secret = NULL; + + object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (interface))); + account = goa_object_peek_account (object); + provider = goa_provider_get_for_provider_type (goa_account_get_provider_type (account)); + + error = NULL; + access_token = goa_oauth_provider_get_access_token_sync (GOA_OAUTH_PROVIDER (provider), + object, + FALSE, /* force_refresh */ + &access_token_secret, + &access_token_expires_in, + NULL, /* GCancellable* */ + &error); + if (access_token == NULL) + { + g_dbus_method_invocation_return_gerror (invocation, error); + g_error_free (error); + } + else + { + goa_oauth_based_complete_get_access_token (interface, + invocation, + access_token, + access_token_secret, + access_token_expires_in); + } + g_free (access_token); + g_free (access_token_secret); + g_object_unref (provider); + return TRUE; /* invocation was handled */ +} diff --git a/src/goabackend/goaoauthprovider.h b/src/goabackend/goaoauthprovider.h new file mode 100644 index 0000000..1806cce --- /dev/null +++ b/src/goabackend/goaoauthprovider.h @@ -0,0 +1,134 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GOA_OAUTH_PROVIDER_H__ +#define __GOA_OAUTH_PROVIDER_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GOA_TYPE_OAUTH_PROVIDER (goa_oauth_provider_get_type ()) +#define GOA_OAUTH_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_OAUTH_PROVIDER, GoaOAuthProvider)) +#define GOA_OAUTH_PROVIDER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOA_TYPE_OAUTH_PROVIDER, GoaOAuthProviderClass)) +#define GOA_OAUTH_PROVIDER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOA_TYPE_OAUTH_PROVIDER, GoaOAuthProviderClass)) +#define GOA_IS_OAUTH_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_OAUTH_PROVIDER)) + +#define GOA_IS_OAUTH_PROVIDER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOA_TYPE_OAUTH_PROVIDER)) + +typedef struct _GoaOAuthProviderClass GoaOAuthProviderClass; +typedef struct _GoaOAuthProviderPrivate GoaOAuthProviderPrivate; + +/** + * GoaOAuthProvider: + * + * The #GoaOAuthProvider structure contains only private data and should + * only be accessed using the provided API. + */ +struct _GoaOAuthProvider +{ + /*< private >*/ + GoaProvider parent_instance; + GoaOAuthProviderPrivate *priv; +}; + +/** + * GoaOAuthProviderClass: + * @parent_class: The parent class. + * @get_consumer_key: Virtual function for goa_oauth_provider_get_consumer_key(). + * @get_consumer_secret: Virtual function for goa_oauth_provider_get_consumer_secret(). + * @get_request_uri: Virtual function for goa_oauth_provider_get_request_uri(). + * @get_authorization_uri: Virtual function for goa_oauth_provider_get_authorization_uri(). + * @get_token_uri: Virtual function for goa_oauth_provider_get_token_uri(). + * @get_callback_uri: Virtual function for goa_oauth_provider_get_callback_uri(). + * @get_identity_sync: Virtual function for goa_oauth_provider_get_identity_sync(). + * @build_authorization_uri: Virtual function for goa_oauth_provider_build_authorization_uri(). + * @get_use_external_browser: Virtual function for goa_oauth_provider_get_use_external_browser(). + * @get_request_uri_params: Virtual function for goa_oauth_provider_get_request_uri_params(). + * + * Class structure for #GoaOAuthProvider. + */ +struct _GoaOAuthProviderClass +{ + GoaProviderClass parent_class; + + /* pure virtual */ + const gchar *(*get_consumer_key) (GoaOAuthProvider *provider); + const gchar *(*get_consumer_secret) (GoaOAuthProvider *provider); + const gchar *(*get_request_uri) (GoaOAuthProvider *provider); + const gchar *(*get_authorization_uri) (GoaOAuthProvider *provider); + const gchar *(*get_token_uri) (GoaOAuthProvider *provider); + const gchar *(*get_callback_uri) (GoaOAuthProvider *provider); + + gchar *(*get_identity_sync) (GoaOAuthProvider *provider, + const gchar *access_token, + const gchar *access_token_secret, + gchar **out_name, + GCancellable *cancellable, + GError **error); + + /* virtual but with default implementation */ + gchar *(*build_authorization_uri) (GoaOAuthProvider *provider, + const gchar *authorization_uri, + const gchar *escaped_oauth_token); + gboolean (*get_use_external_browser) (GoaOAuthProvider *provider); + gchar **(*get_request_uri_params) (GoaOAuthProvider *provider); + + /*< private >*/ + /* Padding for future expansion */ + gpointer goa_reserved[32]; +}; + +GType goa_oauth_provider_get_type (void) G_GNUC_CONST; +const gchar *goa_oauth_provider_get_consumer_key (GoaOAuthProvider *provider); +const gchar *goa_oauth_provider_get_consumer_secret (GoaOAuthProvider *provider); +const gchar *goa_oauth_provider_get_request_uri (GoaOAuthProvider *provider); +gchar **goa_oauth_provider_get_request_uri_params (GoaOAuthProvider *provider); +const gchar *goa_oauth_provider_get_authorization_uri (GoaOAuthProvider *provider); +const gchar *goa_oauth_provider_get_token_uri (GoaOAuthProvider *provider); +const gchar *goa_oauth_provider_get_callback_uri (GoaOAuthProvider *provider); +gchar *goa_oauth_provider_get_identity_sync (GoaOAuthProvider *provider, + const gchar *access_token, + const gchar *access_token_secret, + gchar **out_name, + GCancellable *cancellable, + GError **error); +gchar *goa_oauth_provider_get_access_token_sync (GoaOAuthProvider *provider, + GoaObject *object, + gboolean force_refresh, + gchar **out_access_token_secret, + gint *out_access_token_expires_in, + GCancellable *cancellable, + GError **error); +gchar *goa_oauth_provider_build_authorization_uri (GoaOAuthProvider *provider, + const gchar *authorization_uri, + const gchar *escaped_oauth_token); +gboolean goa_oauth_provider_get_use_external_browser (GoaOAuthProvider *provider); + +G_END_DECLS + +#endif /* __GOA_OAUTH_PROVIDER_H__ */ diff --git a/src/goabackend/goaprovider.c b/src/goabackend/goaprovider.c new file mode 100644 index 0000000..7ec7741 --- /dev/null +++ b/src/goabackend/goaprovider.c @@ -0,0 +1,672 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" +#include +#include + +#include "goaprovider.h" +#include "goagoogleprovider.h" +#include "goafacebookprovider.h" +#include "goayahooprovider.h" +#include "goatwitterprovider.h" + +/** + * SECTION:goaprovider + * @title: GoaProvider + * @short_description: Abstract base class for providers + * + * #GoaProvider is the base type for all providers. + */ + +static gboolean goa_provider_ensure_credentials_sync_real (GoaProvider *provider, + GoaObject *object, + gint *out_expires_in, + GCancellable *cancellable, + GError **error); + +G_DEFINE_ABSTRACT_TYPE (GoaProvider, goa_provider, G_TYPE_OBJECT); + +static void +goa_provider_init (GoaProvider *client) +{ +} + +static void +goa_provider_class_init (GoaProviderClass *klass) +{ + klass->ensure_credentials_sync = goa_provider_ensure_credentials_sync_real; +} + +/** + * goa_provider_get_provider_type: + * @provider: A #GoaProvider. + * + * Gets the type of @provider. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider, do not free. + */ +const gchar * +goa_provider_get_provider_type (GoaProvider *provider) +{ + g_return_val_if_fail (GOA_IS_PROVIDER (provider), NULL); + return GOA_PROVIDER_GET_CLASS (provider)->get_provider_type (provider); +} + +/** + * goa_provider_get_name: + * @provider: A #GoaProvider. + * + * Gets a localized name for @provider that is suitable for display in + * an user interface. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: (transfer none): A string owned by @provider, do not free. + */ +const gchar * +goa_provider_get_name (GoaProvider *provider) +{ + g_return_val_if_fail (GOA_IS_PROVIDER (provider), NULL); + return GOA_PROVIDER_GET_CLASS (provider)->get_name (provider); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * goa_provider_add_account: + * @provider: A #GoaProvider. + * @client: A #GoaClient. + * @dialog: A #GtkDialog. + * @vbox: A vertically oriented #GtkBox to put content in. + * @error: Return location for error or %NULL. + * + * This method brings up the user interface necessary to create a new + * account on @client of the type for @provider, interacts with the + * user to get all information needed and creates the account. + * + * The passed in @dialog widget is guaranteed to be visible with @vbox + * being empty and the only visible widget in @dialog's content + * area. The dialog has exactly one action widget, a cancel button + * with response id GTK_RESPONSE_CANCEL. Implementations are free to + * add additional action widgets, as needed. + * + * If an account was successfully created, a #GoaObject for the + * created account is returned. If @dialog is dismissed, %NULL is + * returned and @error is set to %GOA_ERROR_DIALOG_DISMISSED. If an + * account couldn't be created then @error is set. + * + * The caller will always show an error dialog if @error is set unless + * the error is %GOA_ERROR_DIALOG_DISMISSED. + * + * Implementations should run the default main loop while + * interacting with the user and may do so using e.g. gtk_dialog_run() + * on @dialog. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: The #GoaObject for the created account (must be relased + * with g_object_unref()) or %NULL if @error is set. + */ +GoaObject * +goa_provider_add_account (GoaProvider *provider, + GoaClient *client, + GtkDialog *dialog, + GtkBox *vbox, + GError **error) +{ + GoaObject *ret; + + g_return_val_if_fail (GOA_IS_PROVIDER (provider), NULL); + g_return_val_if_fail (GOA_IS_CLIENT (client), NULL); + g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + ret = GOA_PROVIDER_GET_CLASS (provider)->add_account (provider, client, dialog, vbox, error); + + g_warn_if_fail ((ret == NULL && (error == NULL || *error != NULL)) || GOA_IS_OBJECT (ret)); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * goa_provider_refresh_account: + * @provider: A #GoaProvider. + * @client: A #GoaClient. + * @object: A #GoaObject with a #GoaAccount interface. + * @parent: (allow-none): Transient parent of dialogs or %NULL. + * @error: Return location for error or %NULL. + * + * This method brings up the user interface necessary for refreshing + * the credentials for the account specified by @object. This + * typically involves having the user log in to the account again. + * + * Implementations should use @parent (unless %NULL) as the transient + * parent of any created windows/dialogs. + * + * Implementations should run the default main loop while + * interacting with the user. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: %TRUE if the account has been refreshed, %FALSE if @error + * is set. + */ +gboolean +goa_provider_refresh_account (GoaProvider *provider, + GoaClient *client, + GoaObject *object, + GtkWindow *parent, + GError **error) +{ + g_return_val_if_fail (GOA_IS_PROVIDER (provider), FALSE); + g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE); + g_return_val_if_fail (GOA_IS_OBJECT (object) && goa_object_peek_account (object) != NULL, FALSE); + g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + return GOA_PROVIDER_GET_CLASS (provider)->refresh_account (provider, client, object, parent, error); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * goa_provider_build_object: + * @provider: A #GoaProvider. + * @object: The #GoaObjectSkeleton that is being built. + * @key_file: The #GKeyFile with configuation data. + * @group: The group in @key_file to get data from. + * @error: Return location for error or %NULL. + * + * This method is called when construction account #GoaObject + * from configuration data - it basically provides a way to add + * provider-specific information. + * + * The passed in @object will have a #GoaAccount interface + * set. Implementations should validate and use data from @key_file to + * add more interfaces to @object. + * + * Note that this may be called on already exported objects - for + * example on configuration files reload. + * + * This is a pure virtual method - a subclass must provide an + * implementation. + * + * Returns: %TRUE if data was valid, %FALSE if @error is set. + */ +gboolean +goa_provider_build_object (GoaProvider *provider, + GoaObjectSkeleton *object, + GKeyFile *key_file, + const gchar *group, + GError **error) +{ + g_return_val_if_fail (GOA_IS_PROVIDER (provider), FALSE); + g_return_val_if_fail (GOA_IS_OBJECT_SKELETON (object) && goa_object_peek_account (GOA_OBJECT (object)) != NULL, FALSE); + g_return_val_if_fail (key_file != NULL, FALSE); + g_return_val_if_fail (group != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + return GOA_PROVIDER_GET_CLASS (provider)->build_object (provider, object, key_file, group, error); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GoaObject *object; + gint expires_in; +} EnsureCredentialsData; + +static EnsureCredentialsData * +ensure_credentials_data_new (GoaObject *object) +{ + EnsureCredentialsData *data; + data = g_new0 (EnsureCredentialsData, 1); + data->object = g_object_ref (object); + return data; +} + +static void +ensure_credentials_data_free (EnsureCredentialsData *data) +{ + g_object_unref (data->object); + g_free (data); +} + +static void +ensure_credentials_in_thread_func (GSimpleAsyncResult *simple, + GObject *object, + GCancellable *cancellable) +{ + EnsureCredentialsData *data; + GError *error; + + data = g_simple_async_result_get_op_res_gpointer (simple); + + error = NULL; + if (!goa_provider_ensure_credentials_sync (GOA_PROVIDER (object), + data->object, + &data->expires_in, + cancellable, + &error)) + g_simple_async_result_take_error (simple, error); +} + + +/** + * goa_provider_ensure_credentials: + * @provider: A #GoaProvider. + * @object: A #GoaObject with a #GoaAccount interface. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @callback: The function to call when the request is satisfied. + * @user_data: Pointer to pass to @callback. + * + * Ensures that credentials for @object are still valid. + * + * When the result is ready, @callback will be called in the the thread-default main + * loop this function was called from. You can then call + * goa_provider_ensure_credentials_finish() to get the result + * of the operation. + * + * This is a virtual method where the default implemention simply returns + * the %GOA_ERROR_NOT_SUPPORTED error. A subclass may provide + * another implementation. + */ +void +goa_provider_ensure_credentials (GoaProvider *provider, + GoaObject *object, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + + g_return_if_fail (GOA_IS_PROVIDER (provider)); + g_return_if_fail (GOA_IS_OBJECT (object)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + simple = g_simple_async_result_new (G_OBJECT (provider), + callback, + user_data, + goa_provider_ensure_credentials); + g_simple_async_result_set_op_res_gpointer (simple, + ensure_credentials_data_new (object), + (GDestroyNotify) ensure_credentials_data_free); + g_simple_async_result_run_in_thread (simple, + ensure_credentials_in_thread_func, + G_PRIORITY_DEFAULT, + cancellable); + g_object_unref (simple); +} + +/** + * goa_provider_ensure_credentials_finish: + * @provider: A #GoaProvider. + * @out_expires_in: (out): Return location for how long the expired credentials are good for (0 if unknown) or %NULL. + * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to goa_provider_ensure_credentials(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with goa_provider_ensure_credentials(). + * + * Returns: %TRUE if the credentials for the passed #GoaObject are valid, %FALSE if @error is set. + */ +gboolean +goa_provider_ensure_credentials_finish (GoaProvider *provider, + gint *out_expires_in, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + gboolean ret; + EnsureCredentialsData *data; + + ret = FALSE; + + g_return_val_if_fail (GOA_IS_PROVIDER (provider), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == goa_provider_ensure_credentials); + + if (g_simple_async_result_propagate_error (simple, error)) + goto out; + + ret = TRUE; + data = g_simple_async_result_get_op_res_gpointer (simple); + if (out_expires_in != NULL) + *out_expires_in = data->expires_in; + + out: + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * goa_provider_ensure_credentials_sync: + * @provider: A #GoaProvider. + * @object: A #GoaObject with a #GoaAccount interface. + * @out_expires_in: (out): Return location for how long the expired credentials are good for (0 if unknown) or %NULL. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Like goa_provider_ensure_credentials() but blocks the + * calling thread until an answer is received. + * + * Returns: %TRUE if the credentials for the passed #GoaObject are valid, %FALSE if @error is set. + */ +gboolean +goa_provider_ensure_credentials_sync (GoaProvider *provider, + GoaObject *object, + gint *out_expires_in, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (GOA_IS_PROVIDER (provider), FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + return GOA_PROVIDER_GET_CLASS (provider)->ensure_credentials_sync (provider, object, out_expires_in, cancellable, error); +} + +static gboolean +goa_provider_ensure_credentials_sync_real (GoaProvider *provider, + GoaObject *object, + gint *out_expires_in, + GCancellable *cancellable, + GError **error) +{ + g_set_error (error, + GOA_ERROR, + GOA_ERROR_NOT_SUPPORTED, + _("ensure_credentials_sync not been implemented on type %s"), + g_type_name (G_TYPE_FROM_INSTANCE (provider))); + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +ensure_ep_and_builtins (void) +{ + static gsize once_init_value = 0; + + if (g_once_init_enter (&once_init_value)) + { + GIOExtensionPoint *extension_point; + static volatile GType type = 0; + + extension_point = g_io_extension_point_register (GOA_PROVIDER_EXTENSION_POINT_NAME); + g_io_extension_point_set_required_type (extension_point, GOA_TYPE_PROVIDER); + + type = GOA_TYPE_GOOGLE_PROVIDER; + type = GOA_TYPE_FACEBOOK_PROVIDER; + type = GOA_TYPE_YAHOO_PROVIDER; + type = GOA_TYPE_TWITTER_PROVIDER; + type = type; /* for -Wunused-but-set-variable */ + + g_once_init_leave (&once_init_value, 1); + } +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * goa_provider_get_for_provider_type: + * @provider_type: A provider type. + * + * Looks up the %GOA_PROVIDER_EXTENSION_POINT_NAME extension + * point and returns a newly created #GoaProvider for + * @provider_type, if any. + * + * Returns: (transfer full): A #GoaProvider (that must be freed + * with g_object_unref()) or %NULL if not found. + */ +GoaProvider * +goa_provider_get_for_provider_type (const gchar *provider_type) +{ + GIOExtension *extension; + GIOExtensionPoint *extension_point; + GoaProvider *ret; + + ensure_ep_and_builtins (); + + ret = NULL; + + extension_point = g_io_extension_point_lookup (GOA_PROVIDER_EXTENSION_POINT_NAME); + extension = g_io_extension_point_get_extension_by_name (extension_point, provider_type); + if (extension != NULL) + ret = GOA_PROVIDER (g_object_new (g_io_extension_get_type (extension), NULL)); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * goa_provider_get_all: + * + * Looks up the %GOA_PROVIDER_EXTENSION_POINT_NAME extension + * point and returns a newly created #GoaProvider for each + * provider type encountered. + * + * Returns: (transfer full) (element-type GoaProvider): A list + * of element providers that should be freed with g_list_free() + * after each element has been freed with g_object_unref(). + */ +GList * +goa_provider_get_all (void) +{ + GList *ret; + GList *extensions; + GList *l; + GIOExtensionPoint *extension_point; + + ensure_ep_and_builtins (); + + ret = NULL; + extension_point = g_io_extension_point_lookup (GOA_PROVIDER_EXTENSION_POINT_NAME); + extensions = g_io_extension_point_get_extensions (extension_point); + /* TODO: what if there are two extensions with the same name? */ + for (l = extensions; l != NULL; l = l->next) + { + GIOExtension *extension = l->data; + ret = g_list_prepend (ret, g_object_new (g_io_extension_get_type (extension), NULL)); + } + ret = g_list_reverse (ret); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static const GnomeKeyringPasswordSchema keyring_password_schema = +{ + GNOME_KEYRING_ITEM_GENERIC_SECRET, + { + { "goa-identity", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, + { NULL, 0 } + } +}; + +/** + * goa_provider_store_credentials_sync: + * @provider: A #GoaProvider. + * @identity: The identity to store credentials for. + * @credentials: The credentials to store. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Stores @credentials for @identity in the key-ring. If @credentials + * is floating, it is consumed. + * + * The calling thread is blocked while waiting for a reply. + * + * This is a convenience method (not virtual) that subclasses can use. + * + * Returns: %TRUE if the credentials was successfully stored, %FALSE + * if @error is set. + */ +gboolean +goa_provider_store_credentials_sync (GoaProvider *provider, + const gchar *identity, + GVariant *credentials, + GCancellable *cancellable, + GError **error) +{ + gboolean ret; + gchar *credentials_str; + gchar *password_description; + gchar *password_key; + GnomeKeyringResult result; + + g_return_val_if_fail (GOA_IS_PROVIDER (provider), FALSE); + g_return_val_if_fail (identity != NULL, FALSE); + g_return_val_if_fail (credentials != NULL, FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* TODO: use GCancellable */ + ret = FALSE; + + credentials_str = g_variant_print (credentials, TRUE); + g_variant_ref_sink (credentials); + g_variant_unref (credentials); + + password_key = g_strdup_printf ("%s:%s", + goa_provider_get_provider_type (GOA_PROVIDER (provider)), + identity); + password_description = g_strdup_printf (_("GOA %s credentials for identity %s"), + goa_provider_get_provider_type (GOA_PROVIDER (provider)), + identity); + result = gnome_keyring_store_password_sync (&keyring_password_schema, + NULL, /* default keyring */ + password_description, + credentials_str, + "goa-identity", password_key, + NULL); + if (result != GNOME_KEYRING_RESULT_OK) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, /* TODO: more specific */ + _("Failed to store credentials in the keyring: %s"), + gnome_keyring_result_to_message (result)); + goto out; + } + + ret = TRUE; + + out: + g_free (credentials_str); + g_free (password_key); + g_free (password_description); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * goa_provider_lookup_credentials_sync: + * @provider: A #GoaProvider. + * @identity: The identity to look up credentials for. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Looks up credentials in the keyring for @identity previously stored + * with goa_provider_store_credentials(). + * + * The calling thread is blocked while waiting for a reply. + * + * This is a convenience method (not virtual) that subclasses can use. + * + * Returns: (transfer full): A #GVariant (never floating) + * with credentials or %NULL if @error is set. Free with + * g_variant_unref(). + */ +GVariant * +goa_provider_lookup_credentials_sync (GoaProvider *provider, + const gchar *identity, + GCancellable *cancellable, + GError **error) +{ + gchar *password_key; + GVariant *ret; + GnomeKeyringResult result; + gchar *returned_password; + + g_return_val_if_fail (GOA_IS_PROVIDER (provider), NULL); + g_return_val_if_fail (identity != NULL, NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + ret = NULL; + password_key = NULL; + returned_password = NULL; + + password_key = g_strdup_printf ("%s:%s", + goa_provider_get_provider_type (GOA_PROVIDER (provider)), + identity); + + result = gnome_keyring_find_password_sync (&keyring_password_schema, + &returned_password, + "goa-identity", password_key, + NULL); + if (result != GNOME_KEYRING_RESULT_OK) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, /* TODO: more specific */ + _("Failed to retrieve credentials from the keyring: %s"), + gnome_keyring_result_to_message (result)); + goto out; + } + + ret = g_variant_parse (NULL, /* GVariantType */ + returned_password, + NULL, /* limit */ + NULL, /* endptr */ + error); + if (ret == NULL) + { + g_prefix_error (error, _("Error parsing result obtained from the keyring: ")); + goto out; + } + + if (g_variant_is_floating (ret)) + g_variant_ref_sink (ret); + + out: + gnome_keyring_free_password (returned_password); + g_free (password_key); + return ret; +} + diff --git a/src/goabackend/goaprovider.h b/src/goabackend/goaprovider.h new file mode 100644 index 0000000..131e86b --- /dev/null +++ b/src/goabackend/goaprovider.h @@ -0,0 +1,159 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GOA_PROVIDER_H__ +#define __GOA_PROVIDER_H__ + +#include + +G_BEGIN_DECLS + +#define GOA_TYPE_PROVIDER (goa_provider_get_type ()) +#define GOA_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_PROVIDER, GoaProvider)) +#define GOA_PROVIDER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOA_TYPE_PROVIDER, GoaProviderClass)) +#define GOA_PROVIDER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOA_TYPE_PROVIDER, GoaProviderClass)) +#define GOA_IS_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_PROVIDER)) +#define GOA_IS_PROVIDER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOA_TYPE_PROVIDER)) + +typedef struct _GoaProviderClass GoaProviderClass; +typedef struct _GoaProviderPrivate GoaProviderPrivate; + +/** + * GoaProvider: + * + * The #GoaProvider structure contains only private data and should + * only be accessed using the provided API. + */ +struct _GoaProvider +{ + /*< private >*/ + GObject parent_instance; + GoaProviderPrivate *priv; +}; + +/** + * GoaProviderClass: + * @parent_class: The parent class. + * @get_provider_type: Virtual function for goa_provider_get_provider_type(). + * @get_name: Virtual function for goa_provider_get_name(). + * @add_account: Virtual function for goa_provider_add_account(). + * @refresh_account: Virtual function for goa_provider_refresh_account(). + * @build_object: Virtual function for goa_provider_build_object(). + * @ensure_credentials_sync: Virtual function for goa_provider_ensure_credentials_sync(). + * + * Class structure for #GoaProvider. + */ +struct _GoaProviderClass +{ + GObjectClass parent_class; + + /* pure virtual */ + const gchar *(*get_provider_type) (GoaProvider *provider); + const gchar *(*get_name) (GoaProvider *provider); + GoaObject *(*add_account) (GoaProvider *provider, + GoaClient *client, + GtkDialog *dialog, + GtkBox *vbox, + GError **error); + gboolean (*refresh_account) (GoaProvider *provider, + GoaClient *client, + GoaObject *object, + GtkWindow *parent, + GError **error); + gboolean (*build_object) (GoaProvider *provider, + GoaObjectSkeleton *object, + GKeyFile *key_file, + const gchar *group, + GError **error); + + /* virtual but with default implementation */ + gboolean (*ensure_credentials_sync) (GoaProvider *provider, + GoaObject *object, + gint *out_expires_in, + GCancellable *cancellable, + GError **error); + + /*< private >*/ + /* Padding for future expansion */ + gpointer goa_reserved[32]; +}; + +GType goa_provider_get_type (void) G_GNUC_CONST; +const gchar *goa_provider_get_provider_type (GoaProvider *provider); +const gchar *goa_provider_get_name (GoaProvider *provider); +GoaObject *goa_provider_add_account (GoaProvider *provider, + GoaClient *client, + GtkDialog *dialog, + GtkBox *vbox, + GError **error); +gboolean goa_provider_refresh_account (GoaProvider *provider, + GoaClient *client, + GoaObject *object, + GtkWindow *parent, + GError **error); +gboolean goa_provider_build_object (GoaProvider *provider, + GoaObjectSkeleton *object, + GKeyFile *key_file, + const gchar *group, + GError **error); +gboolean goa_provider_store_credentials_sync (GoaProvider *provider, + const gchar *identity, + GVariant *credentials, + GCancellable *cancellable, + GError **error); +GVariant *goa_provider_lookup_credentials_sync (GoaProvider *provider, + const gchar *identity, + GCancellable *cancellable, + GError **error); +void goa_provider_ensure_credentials (GoaProvider *provider, + GoaObject *object, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean goa_provider_ensure_credentials_finish (GoaProvider *provider, + gint *out_expires_in, + GAsyncResult *res, + GError **error); +gboolean goa_provider_ensure_credentials_sync (GoaProvider *provider, + GoaObject *object, + gint *out_expires_in, + GCancellable *cancellable, + GError **error); + + +/** + * GOA_PROVIDER_EXTENSION_POINT_NAME: + * + * Extension point for #GoaProvider implementations. + */ +#define GOA_PROVIDER_EXTENSION_POINT_NAME "goa-backend-provider" + +GList *goa_provider_get_all (void); +GoaProvider *goa_provider_get_for_provider_type (const gchar *provider_type); + +G_END_DECLS + +#endif /* __GOA_PROVIDER_H__ */ diff --git a/src/goabackend/goatwitterprovider.c b/src/goabackend/goatwitterprovider.c new file mode 100644 index 0000000..48c16aa --- /dev/null +++ b/src/goabackend/goatwitterprovider.c @@ -0,0 +1,332 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" +#include + +#include +#include + +#include "goaprovider.h" +#include "goaoauthprovider.h" +#include "goatwitterprovider.h" + +/** + * GoaTwitterProvider: + * + * The #GoaTwitterProvider structure contains only private data and should + * only be accessed using the provided API. + */ +struct _GoaTwitterProvider +{ + /*< private >*/ + GoaOAuthProvider parent_instance; +}; + +typedef struct _GoaTwitterProviderClass GoaTwitterProviderClass; + +struct _GoaTwitterProviderClass +{ + GoaOAuthProviderClass parent_class; +}; + +/** + * SECTION:goatwitterprovider + * @title: GoaTwitterProvider + * @short_description: A provider for Twitter + * + * #GoaTwitterProvider is used for handling Twitter accounts. + */ + +G_DEFINE_TYPE_WITH_CODE (GoaTwitterProvider, goa_twitter_provider, GOA_TYPE_OAUTH_PROVIDER, + g_io_extension_point_implement (GOA_PROVIDER_EXTENSION_POINT_NAME, + g_define_type_id, + "twitter", + 0)); + +/* ---------------------------------------------------------------------------------------------------- */ + +static const gchar * +get_provider_type (GoaProvider *_provider) +{ + return "twitter"; +} + +static const gchar * +get_name (GoaProvider *_provider) +{ + return _("Twitter Account"); +} + +static const gchar * +get_consumer_key (GoaOAuthProvider *provider) +{ + return "tlVEAXvkgqr0VUFyqVQ"; +} + +static const gchar * +get_consumer_secret (GoaOAuthProvider *provider) +{ + return "RN2FBARWy7scDmWFwfhIA6Qwf6kPYxZ0PIpVWzgpdU"; +} + +static const gchar * +get_request_uri (GoaOAuthProvider *provider) +{ + return "https://api.twitter.com/oauth/request_token"; +} + +static gchar ** +get_request_uri_params (GoaOAuthProvider *provider) +{ + return NULL; + GPtrArray *p; + p = g_ptr_array_new (); + g_ptr_array_add (p, g_strdup ("xoauth_displayname")); + g_ptr_array_add (p, g_strdup ("GNOME")); + + g_ptr_array_add (p, g_strdup ("scope")); + g_ptr_array_add (p, g_strdup ( + /* Display email address: cf. https://sites.twitter.com/site/oauthgoog/Home/emaildisplayscope */ + "https://www.twitterapis.com/auth/userinfo#email " + /* IMAP, SMTP access: http://code.twitter.com/apis/gmail/oauth/protocol.html */ + "https://mail.twitter.com/ " + /* Calendar data API: http://code.twitter.com/apis/calendar/data/2.0/developers_guide.html */ + "https://www.twitter.com/calendar/feeds")); + g_ptr_array_add (p, NULL); + return (gchar **) g_ptr_array_free (p, FALSE); +} + + +static const gchar * +get_authorization_uri (GoaOAuthProvider *provider) +{ + return "https://api.twitter.com/oauth/authorize"; +} + +static const gchar * +get_token_uri (GoaOAuthProvider *provider) +{ + return "https://api.twitter.com/oauth/access_token"; +} + +static const gchar * +get_callback_uri (GoaOAuthProvider *provider) +{ + return "https://www.gnome.org/goa-1.0/oauth"; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +get_identity_sync (GoaOAuthProvider *provider, + const gchar *access_token, + const gchar *access_token_secret, + gchar **out_name, + GCancellable *cancellable, + GError **error) +{ + RestProxy *proxy; + RestProxyCall *call; + JsonParser *parser; + JsonObject *json_object; + gchar *ret; + gchar *id; + gchar *name; + + ret = NULL; + proxy = NULL; + call = NULL; + parser = NULL; + id = NULL; + name = NULL; + + /* TODO: cancellable */ + + proxy = oauth_proxy_new_with_token (goa_oauth_provider_get_consumer_key (provider), + goa_oauth_provider_get_consumer_secret (provider), + access_token, + access_token_secret, + "https://api.twitter.com/1/account/verify_credentials.json", + FALSE); + call = rest_proxy_new_call (proxy); + rest_proxy_call_set_method (call, "GET"); + + if (!rest_proxy_call_sync (call, error)) + goto out; + if (rest_proxy_call_get_status_code (call) != 200) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Expected status 200 when requesting guid, instead got status %d (%s)"), + rest_proxy_call_get_status_code (call), + rest_proxy_call_get_status_message (call)); + goto out; + } + + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, + rest_proxy_call_get_payload (call), + rest_proxy_call_get_payload_length (call), + error)) + { + g_prefix_error (error, _("Error parsing response as JSON: ")); + goto out; + } + + json_object = json_node_get_object (json_parser_get_root (parser)); + id = g_strdup (json_object_get_string_member (json_object, "id_str")); + if (id == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Didn't find id_str member in JSON data")); + goto out; + } + name = g_strdup (json_object_get_string_member (json_object, "screen_name")); + if (name == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Didn't find screen_name member in JSON data")); + goto out; + } + + ret = id; + id = NULL; + if (out_name != NULL) + { + *out_name = name; + name = NULL; + } + + out: + g_free (id); + g_free (name); + if (call != NULL) + g_object_unref (call); + if (proxy != NULL) + g_object_unref (proxy); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +build_object (GoaProvider *provider, + GoaObjectSkeleton *object, + GKeyFile *key_file, + const gchar *group, + GError **error) +{ + GoaAccount *account; + GoaTwitterAccount *twitter_account; + gboolean ret; + gchar *id; + + id = NULL; + account = NULL; + twitter_account = NULL; + ret = FALSE; + + /* Chain up */ + if (!GOA_PROVIDER_CLASS (goa_twitter_provider_parent_class)->build_object (provider, + object, + key_file, + group, + error)) + goto out; + + account = goa_object_get_account (GOA_OBJECT (object)); + twitter_account = goa_object_get_twitter_account (GOA_OBJECT (object)); + if (twitter_account == NULL) + { + twitter_account = goa_twitter_account_skeleton_new (); + goa_object_skeleton_set_twitter_account (object, twitter_account); + } + + id = g_key_file_get_string (key_file, group, "Identity", NULL); + if (id == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + "Invalid identity %s for id %s", + id, + goa_account_get_id (account)); + goto out; + } + + goa_twitter_account_set_id (twitter_account, id); + + ret = TRUE; + + out: + g_free (id); + if (twitter_account != NULL) + g_object_unref (twitter_account); + if (account != NULL) + g_object_unref (account); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +get_use_external_browser (GoaOAuthProvider *provider) +{ + /* For some reason this only works in a browser - bad callback URL? TODO: investigate */ + return TRUE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +goa_twitter_provider_init (GoaTwitterProvider *client) +{ +} + +static void +goa_twitter_provider_class_init (GoaTwitterProviderClass *klass) +{ + GoaProviderClass *provider_class; + GoaOAuthProviderClass *oauth_class; + + provider_class = GOA_PROVIDER_CLASS (klass); + provider_class->get_provider_type = get_provider_type; + provider_class->get_name = get_name; + provider_class->build_object = build_object; + + oauth_class = GOA_OAUTH_PROVIDER_CLASS (klass); + oauth_class->get_identity_sync = get_identity_sync; + oauth_class->get_consumer_key = get_consumer_key; + oauth_class->get_consumer_secret = get_consumer_secret; + oauth_class->get_request_uri = get_request_uri; + oauth_class->get_request_uri_params = get_request_uri_params; + oauth_class->get_authorization_uri = get_authorization_uri; + oauth_class->get_token_uri = get_token_uri; + oauth_class->get_callback_uri = get_callback_uri; + oauth_class->get_use_external_browser = get_use_external_browser; +} diff --git a/src/goabackend/goatwitterprovider.h b/src/goabackend/goatwitterprovider.h new file mode 100644 index 0000000..8164388 --- /dev/null +++ b/src/goabackend/goatwitterprovider.h @@ -0,0 +1,42 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GOA_TWITTER_PROVIDER_H__ +#define __GOA_TWITTER_PROVIDER_H__ + +#include + +G_BEGIN_DECLS + +#define GOA_TYPE_TWITTER_PROVIDER (goa_twitter_provider_get_type ()) +#define GOA_TWITTER_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_TWITTER_PROVIDER, GoaTwitterProvider)) +#define GOA_IS_TWITTER_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_TWITTER_PROVIDER)) + +GType goa_twitter_provider_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __GOA_TWITTER_PROVIDER_H__ */ diff --git a/src/goabackend/goayahooprovider.c b/src/goabackend/goayahooprovider.c new file mode 100644 index 0000000..604fc89 --- /dev/null +++ b/src/goabackend/goayahooprovider.c @@ -0,0 +1,369 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" +#include + +#include +#include + +#include "goaprovider.h" +#include "goaoauthprovider.h" +#include "goayahooprovider.h" + +/** + * GoaYahooProvider: + * + * The #GoaYahooProvider structure contains only private data and should + * only be accessed using the provided API. + */ +struct _GoaYahooProvider +{ + /*< private >*/ + GoaOAuthProvider parent_instance; +}; + +typedef struct _GoaYahooProviderClass GoaYahooProviderClass; + +struct _GoaYahooProviderClass +{ + GoaOAuthProviderClass parent_class; +}; + +/** + * SECTION:goayahooprovider + * @title: GoaYahooProvider + * @short_description: A provider for Yahoo + * + * #GoaYahooProvider is used for handling Yahoo accounts. + */ + +G_DEFINE_TYPE_WITH_CODE (GoaYahooProvider, goa_yahoo_provider, GOA_TYPE_OAUTH_PROVIDER, + g_io_extension_point_implement (GOA_PROVIDER_EXTENSION_POINT_NAME, + g_define_type_id, + "yahoo", + 0)); + +/* ---------------------------------------------------------------------------------------------------- */ + +static const gchar * +get_provider_type (GoaProvider *_provider) +{ + return "yahoo"; +} + +static const gchar * +get_name (GoaProvider *_provider) +{ + return _("Yahoo Account"); +} + +static const gchar * +get_consumer_key (GoaOAuthProvider *provider) +{ + return "dj0yJmk9VnBYMGpGRVFBUVl3JmQ9WVdrOWNWZDZiVTUwTldNbWNHbzlPVFF5TURrNE5UWXkmcz1jb25zdW1lcnNlY3JldCZ4PTQ0"; +} + +static const gchar * +get_consumer_secret (GoaOAuthProvider *provider) +{ + return "33dd9ebe9f5724deabe657eff1de7c3f151cf7eb"; +} + +static const gchar * +get_request_uri (GoaOAuthProvider *provider) +{ + return "https://api.login.yahoo.com/oauth/v2/get_request_token"; +} + +static const gchar * +get_authorization_uri (GoaOAuthProvider *provider) +{ + return "https://api.login.yahoo.com/oauth/v2/request_auth"; +} + +static const gchar * +get_token_uri (GoaOAuthProvider *provider) +{ + return "https://api.login.yahoo.com/oauth/v2/get_token"; +} + +static const gchar * +get_callback_uri (GoaOAuthProvider *provider) +{ + return "https://www.gnome.org/goa-1.0/oauth"; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +get_identity_sync (GoaOAuthProvider *provider, + const gchar *access_token, + const gchar *access_token_secret, + gchar **out_name, + GCancellable *cancellable, + GError **error) +{ + RestProxy *proxy; + RestProxyCall *call; + JsonParser *parser; + JsonObject *json_object; + JsonObject *json_data_object; + gchar *ret; + gchar *guid; + gchar *name; + + ret = NULL; + proxy = NULL; + call = NULL; + parser = NULL; + guid = NULL; + name = NULL; + + /* TODO: cancellable */ + + proxy = oauth_proxy_new_with_token (goa_oauth_provider_get_consumer_key (provider), + goa_oauth_provider_get_consumer_secret (provider), + access_token, + access_token_secret, + "http://social.yahooapis.com/v1/me/guid", + FALSE); + call = rest_proxy_new_call (proxy); + rest_proxy_call_set_method (call, "GET"); + rest_proxy_call_add_param (call, "format", "json"); + + if (!rest_proxy_call_sync (call, error)) + goto out; + if (rest_proxy_call_get_status_code (call) != 200) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Expected status 200 when requesting guid, instead got status %d (%s)"), + rest_proxy_call_get_status_code (call), + rest_proxy_call_get_status_message (call)); + goto out; + } + + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, + rest_proxy_call_get_payload (call), + rest_proxy_call_get_payload_length (call), + error)) + { + g_prefix_error (error, _("Error parsing response as JSON: ")); + goto out; + } + + json_object = json_node_get_object (json_parser_get_root (parser)); + json_data_object = json_object_get_object_member (json_object, "guid"); + if (json_data_object == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Didn't find guid member in JSON data")); + goto out; + } + + guid = g_strdup (json_object_get_string_member (json_data_object, "value")); + if (guid == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Didn't find value member in JSON data")); + goto out; + } + + /* OK, got the GUID, now get the name via http://developer.yahoo.com/social/rest_api_guide/usercard-resource.html */ + g_object_unref (proxy); + g_object_unref (call); + proxy = oauth_proxy_new_with_token (goa_oauth_provider_get_consumer_key (provider), + goa_oauth_provider_get_consumer_secret (provider), + access_token, + access_token_secret, + "http://social.yahooapis.com/v1/user/%s/profile/usercard", + TRUE); + rest_proxy_bind (proxy, guid); + call = rest_proxy_new_call (proxy); + rest_proxy_call_set_method (call, "GET"); + rest_proxy_call_add_param (call, "format", "json"); + + if (!rest_proxy_call_sync (call, error)) + goto out; + + if (rest_proxy_call_get_status_code (call) != 200) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Expected status 200 when requesting name, instead got status %d (%s)"), + rest_proxy_call_get_status_code (call), + rest_proxy_call_get_status_message (call)); + goto out; + } + + g_object_unref (parser); + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, + rest_proxy_call_get_payload (call), + rest_proxy_call_get_payload_length (call), + error)) + { + g_prefix_error (error, _("Error parsing usercard response as JSON: ")); + goto out; + } + + json_object = json_node_get_object (json_parser_get_root (parser)); + json_data_object = json_object_get_object_member (json_object, "profile"); + if (json_data_object == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Didn't find profile member in JSON data")); + goto out; + } + + name = g_strdup (json_object_get_string_member (json_data_object, "nickname")); + if (name == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + _("Didn't find nickname member in JSON data")); + goto out; + } + + ret = guid; + guid = NULL; + if (out_name != NULL) + { + *out_name = name; + name = NULL; + } + + out: + g_free (name); + g_free (guid); + if (call != NULL) + g_object_unref (call); + if (proxy != NULL) + g_object_unref (proxy); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +build_object (GoaProvider *provider, + GoaObjectSkeleton *object, + GKeyFile *key_file, + const gchar *group, + GError **error) +{ + GoaAccount *account; + GoaYahooAccount *yahoo_account; + gboolean ret; + gchar *guid; + + guid = NULL; + account = NULL; + yahoo_account = NULL; + ret = FALSE; + + /* Chain up */ + if (!GOA_PROVIDER_CLASS (goa_yahoo_provider_parent_class)->build_object (provider, + object, + key_file, + group, + error)) + goto out; + + account = goa_object_get_account (GOA_OBJECT (object)); + yahoo_account = goa_object_get_yahoo_account (GOA_OBJECT (object)); + if (yahoo_account == NULL) + { + yahoo_account = goa_yahoo_account_skeleton_new (); + goa_object_skeleton_set_yahoo_account (object, yahoo_account); + } + + guid = g_key_file_get_string (key_file, group, "Identity", NULL); + if (guid == NULL) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + "Invalid identity %s for id %s", + guid, + goa_account_get_id (account)); + goto out; + } + + goa_yahoo_account_set_guid (yahoo_account, guid); + + ret = TRUE; + + out: + g_free (guid); + if (yahoo_account != NULL) + g_object_unref (yahoo_account); + if (account != NULL) + g_object_unref (account); + return ret; +} + +static gboolean +get_use_external_browser (GoaOAuthProvider *provider) +{ + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +goa_yahoo_provider_init (GoaYahooProvider *client) +{ +} + +static void +goa_yahoo_provider_class_init (GoaYahooProviderClass *klass) +{ + GoaProviderClass *provider_class; + GoaOAuthProviderClass *oauth_class; + + provider_class = GOA_PROVIDER_CLASS (klass); + provider_class->get_provider_type = get_provider_type; + provider_class->get_name = get_name; + provider_class->build_object = build_object; + + oauth_class = GOA_OAUTH_PROVIDER_CLASS (klass); + oauth_class->get_identity_sync = get_identity_sync; + oauth_class->get_consumer_key = get_consumer_key; + oauth_class->get_consumer_secret = get_consumer_secret; + oauth_class->get_request_uri = get_request_uri; + oauth_class->get_authorization_uri = get_authorization_uri; + oauth_class->get_token_uri = get_token_uri; + oauth_class->get_callback_uri = get_callback_uri; + oauth_class->get_use_external_browser = get_use_external_browser; +} diff --git a/src/goabackend/goayahooprovider.h b/src/goabackend/goayahooprovider.h new file mode 100644 index 0000000..2315703 --- /dev/null +++ b/src/goabackend/goayahooprovider.h @@ -0,0 +1,42 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GOA_YAHOO_PROVIDER_H__ +#define __GOA_YAHOO_PROVIDER_H__ + +#include + +G_BEGIN_DECLS + +#define GOA_TYPE_YAHOO_PROVIDER (goa_yahoo_provider_get_type ()) +#define GOA_YAHOO_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_YAHOO_PROVIDER, GoaYahooProvider)) +#define GOA_IS_YAHOO_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_YAHOO_PROVIDER)) + +GType goa_yahoo_provider_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __GOA_YAHOO_PROVIDER_H__ */ diff --git a/src/panel/Makefile.am b/src/panel/Makefile.am index e29aec1..e51878a 100644 --- a/src/panel/Makefile.am +++ b/src/panel/Makefile.am @@ -40,7 +40,7 @@ libgoa_panel_la_LIBADD = \ $(GTK_LIBS) \ $(GNOME_CONTROL_CENTER_LIBS) \ $(top_builddir)/src/goa/libgoa.la \ - $(top_builddir)/src/goa/libgoa-backend.la \ + $(top_builddir)/src/goabackend/libgoa-backend.la \ $(NULL) clean-local : diff --git a/src/panel/goapanel.c b/src/panel/goapanel.c index 2aea59e..9cd1a29 100644 --- a/src/panel/goapanel.c +++ b/src/panel/goapanel.c @@ -26,7 +26,7 @@ #include #include -#include +#include #include "goapanel.h" #include "goapanelaccountsmodel.h" @@ -296,7 +296,7 @@ on_info_bar_response (GtkInfoBar *info_bar, NULL, &iter)) { - GoaBackendProvider *provider; + GoaProvider *provider; const gchar *provider_type; GoaAccount *account; GoaObject *object; @@ -310,16 +310,16 @@ on_info_bar_response (GtkInfoBar *info_bar, account = goa_object_peek_account (object); provider_type = goa_account_get_provider_type (account); - provider = goa_backend_provider_get_for_provider_type (provider_type); + provider = goa_provider_get_for_provider_type (provider_type); parent = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel)))); error = NULL; - if (!goa_backend_provider_refresh_account (provider, - panel->client, - object, - parent, - &error)) + if (!goa_provider_refresh_account (provider, + panel->client, + object, + parent, + &error)) { if (!(error->domain == GOA_ERROR && error->code == GOA_ERROR_DIALOG_DISMISSED)) { @@ -381,7 +381,7 @@ show_page_account (GoaPanel *panel, GtkWidget *label; GtkWidget *editable_label; guint row; - GoaBackendProvider *provider; + GoaProvider *provider; GoaAccount *account; GoaGoogleAccount *gaccount; GoaFacebookAccount *fbaccount; @@ -403,7 +403,7 @@ show_page_account (GoaPanel *panel, gaccount = goa_object_peek_google_account (object); fbaccount = goa_object_peek_facebook_account (object); provider_type = goa_account_get_provider_type (account); - provider = goa_backend_provider_get_for_provider_type (provider_type); + provider = goa_provider_get_for_provider_type (provider_type); /* And in with the new */ if (goa_account_get_attention_needed (account)) @@ -436,7 +436,7 @@ show_page_account (GoaPanel *panel, if (provider != NULL) { - s = g_strdup (goa_backend_provider_get_name (provider)); + s = g_strdup (goa_provider_get_name (provider)); } else { @@ -527,7 +527,7 @@ on_toolbar_add_button_clicked (GtkToolButton *button, GtkWidget *combo_box; gint response; GList *providers; - GoaBackendProvider *provider; + GoaProvider *provider; GList *children; GList *l; GoaObject *object; @@ -552,13 +552,13 @@ on_toolbar_add_button_clicked (GtkToolButton *button, label = gtk_label_new (_("Account Type:")); combo_box = gtk_combo_box_text_new (); - providers = goa_backend_provider_get_all (); + providers = goa_provider_get_all (); for (l = providers; l != NULL; l = l->next) { - GoaBackendProvider *provider = GOA_BACKEND_PROVIDER (l->data); + GoaProvider *provider = GOA_PROVIDER (l->data); gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo_box), - goa_backend_provider_get_provider_type (provider), - goa_backend_provider_get_name (provider)); + goa_provider_get_provider_type (provider), + goa_provider_get_name (provider)); } gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0); @@ -589,7 +589,7 @@ on_toolbar_add_button_clicked (GtkToolButton *button, } gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (add_account_button)), add_account_button); - provider = goa_backend_provider_get_for_provider_type (gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box))); + provider = goa_provider_get_for_provider_type (gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box))); g_assert (provider != NULL); /* Prepare GtkDialog for the provider */ @@ -603,11 +603,11 @@ on_toolbar_add_button_clicked (GtkToolButton *button, g_list_free (children); error = NULL; - object = goa_backend_provider_add_account (provider, - panel->client, - GTK_DIALOG (dialog), - GTK_BOX (vbox), - &error); + object = goa_provider_add_account (provider, + panel->client, + GTK_DIALOG (dialog), + GTK_BOX (vbox), + &error); if (object != NULL) { GtkTreeIter iter; -- cgit v1.2.3