[shotwell/wip/pluggable-auth: 15/20] wip: Extract Google authenticator
- From: Jens Georg <jensgeorg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [shotwell/wip/pluggable-auth: 15/20] wip: Extract Google authenticator
- Date: Thu, 9 Feb 2017 22:05:53 +0000 (UTC)
commit 3b40c81afe5e71eecf2be9826b20db80d7793cb4
Author: Jens Georg <mail jensge org>
Date: Wed Feb 8 23:15:04 2017 +0100
wip: Extract Google authenticator
Signed-off-by: Jens Georg <mail jensge org>
authenticator.am | 3 +-
configure.ac | 4 +-
.../shotwell/GoogleAuthenticator.vala | 370 ++++++++++++++++++++
.../shotwell/ShotwellAuthenticatorFactory.vala | 11 +-
plugins/common/RESTSupport.vala | 363 ++------------------
plugins/shotwell-publishing/PicasaPublishing.vala | 4 +
plugins/shotwell-publishing/YouTubePublishing.vala | 4 +
7 files changed, 413 insertions(+), 346 deletions(-)
---
diff --git a/authenticator.am b/authenticator.am
index 442ad5b..ba0dda2 100644
--- a/authenticator.am
+++ b/authenticator.am
@@ -48,5 +48,6 @@ AUTHENTICATOR_RESOURCE_SOURCEDIR := $(abs_top_srcdir)/plugins/authenticator/shot
plugins_authenticator_libshotwell_authenticator_la_SOURCES += \
plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala \
plugins/authenticator/shotwell/FacebookPublishingAuthenticator.vala \
- plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala
+ plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala \
+ plugins/authenticator/shotwell/GoogleAuthenticator.vala
endif
diff --git a/configure.ac b/configure.ac
index c67fb7a..6ef70c1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -141,8 +141,8 @@ AS_IF([test "x$with_authenticator" = "xshotwell"],
[
PKG_CHECK_MODULES(AUTHENTICATOR, [gobject-2.0 glib-2.0 libsoup-2.4
webkit2gtk-4.0 gee-0.8 gtk+-3.0
- libxml-2.0])
- AC_SUBST(AUTHENTICATOR_PACKAGES, ["--pkg webkit2gtk-4.0 --pkg gtk+-3.0 --pkg libsoup-2.4 --pkg
gee-0.8 --pkg libxml-2.0"])
+ libxml-2.0 json-glib-1.0])
+ AC_SUBST(AUTHENTICATOR_PACKAGES, ["--pkg webkit2gtk-4.0 --pkg gtk+-3.0 --pkg libsoup-2.4 --pkg
gee-0.8 --pkg libxml-2.0 --pkg json-glib-1.0"])
])
dnl ***********************************************************************
diff --git a/plugins/authenticator/shotwell/GoogleAuthenticator.vala
b/plugins/authenticator/shotwell/GoogleAuthenticator.vala
new file mode 100644
index 0000000..e92455e
--- /dev/null
+++ b/plugins/authenticator/shotwell/GoogleAuthenticator.vala
@@ -0,0 +1,370 @@
+using Shotwell;
+using Shotwell.Plugins;
+
+namespace Publishing.Authenticator.Shotwell.Google {
+ private const string OAUTH_CLIENT_ID =
"1073902228337-gm4uf5etk25s0hnnm0g7uv2tm2bm1j0b.apps.googleusercontent.com";
+ private const string OAUTH_CLIENT_SECRET = "_kA4RZz72xqed4DqfO7xMmMN";
+
+ private class WebAuthenticationPane : Common.WebAuthenticationPane {
+ public static bool cache_dirty = false;
+
+ public signal void authorized(string auth_code);
+
+ public WebAuthenticationPane(string auth_sequence_start_url) {
+ Object (login_uri : auth_sequence_start_url);
+ }
+
+ public static bool is_cache_dirty() {
+ return cache_dirty;
+ }
+
+ public override void on_page_load() {
+ string page_title = get_view ().get_title();
+ if (page_title.index_of("state=connect") > 0) {
+ int auth_code_field_start = page_title.index_of("code=");
+ if (auth_code_field_start < 0)
+ return;
+
+ string auth_code =
+ page_title.substring(auth_code_field_start + 5); // 5 = "code=".length
+
+ cache_dirty = true;
+
+ authorized(auth_code);
+ }
+ }
+ }
+
+ private class Session : Publishing.RESTSupport.Session {
+ public string access_token = null;
+ public string refresh_token = null;
+
+ public override bool is_authenticated() {
+ return (access_token != null);
+ }
+ }
+
+ private class GetAccessTokensTransaction : Publishing.RESTSupport.Transaction {
+ private const string ENDPOINT_URL = "https://accounts.google.com/o/oauth2/token";
+
+ public GetAccessTokensTransaction(Session session, string auth_code) {
+ base.with_endpoint_url(session, ENDPOINT_URL);
+
+ add_argument("code", auth_code);
+ add_argument("client_id", OAUTH_CLIENT_ID);
+ add_argument("client_secret", OAUTH_CLIENT_SECRET);
+ add_argument("redirect_uri", "urn:ietf:wg:oauth:2.0:oob");
+ add_argument("grant_type", "authorization_code");
+ }
+ }
+
+ private class RefreshAccessTokenTransaction : Publishing.RESTSupport.Transaction {
+ private const string ENDPOINT_URL = "https://accounts.google.com/o/oauth2/token";
+
+ public RefreshAccessTokenTransaction(Session session) {
+ base.with_endpoint_url(session, ENDPOINT_URL);
+
+ add_argument("client_id", OAUTH_CLIENT_ID);
+ add_argument("client_secret", OAUTH_CLIENT_SECRET);
+ add_argument("refresh_token", session.refresh_token);
+ add_argument("grant_type", "refresh_token");
+ }
+ }
+
+ private class UsernameFetchTransaction : Publishing.RESTSupport.Transaction {
+ private const string ENDPOINT_URL = "https://www.googleapis.com/oauth2/v1/userinfo";
+ public UsernameFetchTransaction(Session session) {
+ base.with_endpoint_url(session, ENDPOINT_URL, Publishing.RESTSupport.HttpMethod.GET);
+ add_header("Authorization", "Bearer " + session.access_token);
+ }
+ }
+
+ internal class Google : Spit.Publishing.Authenticator, Object {
+ private string scope = null;
+ private Spit.Publishing.PluginHost host = null;
+ private GLib.HashTable<string, Variant> params = null;
+ private WebAuthenticationPane web_auth_pane = null;
+ private Session session = null;
+
+ public Google(string scope, Spit.Publishing.PluginHost host) {
+ this.host = host;
+ this.params = new GLib.HashTable<string, Variant>(str_hash, str_equal);
+ this.scope = scope;
+ this.session = new Session();
+ }
+
+ public void authenticate() {
+ var refresh_token = host.get_config_string("refresh_token", null);
+ if (refresh_token != null && refresh_token != "") {
+ session.refresh_token = refresh_token;
+ do_exchange_refresh_token_for_access_token();
+ }
+
+ // FIXME: Find a way for a proper logout
+ if (WebAuthenticationPane.is_cache_dirty()) {
+ host.set_service_locked(false);
+
+ host.install_static_message_pane(_("You have already logged in and out of a Google service
during this Shotwell session.\n\nTo continue publishing to Google services, quit and restart Shotwell, then
try publishing again."));
+ } else {
+ this.do_hosted_web_authentication();
+ }
+ }
+
+ public bool can_logout() {
+ return true;
+ }
+
+ public GLib.HashTable<string, Variant> get_authentication_parameter() {
+ return this.params;
+ }
+
+ public void invalidate_persistent_session() {
+ }
+
+ public void logout() {
+ }
+
+ private void do_hosted_web_authentication() {
+ debug("ACTION: running OAuth authentication flow in hosted web pane.");
+
+ string user_authorization_url = "https://accounts.google.com/o/oauth2/auth?" +
+ "response_type=code&" +
+ "client_id=" + OAUTH_CLIENT_ID + "&" +
+ "redirect_uri=" + Soup.URI.encode("urn:ietf:wg:oauth:2.0:oob", null) + "&" +
+ "scope=" + Soup.URI.encode(this.scope, null) + "+" +
+ Soup.URI.encode("https://www.googleapis.com/auth/userinfo.profile", null) + "&" +
+ "state=connect&" +
+ "access_type=offline&" +
+ "approval_prompt=force";
+
+ web_auth_pane = new WebAuthenticationPane(user_authorization_url);
+ web_auth_pane.authorized.connect(on_web_auth_pane_authorized);
+
+ host.install_dialog_pane(web_auth_pane);
+
+ }
+
+ private void on_web_auth_pane_authorized(string auth_code) {
+ web_auth_pane.authorized.disconnect(on_web_auth_pane_authorized);
+
+ debug("EVENT: user authorized scope %s with auth_code %s", scope, auth_code);
+
+ do_get_access_tokens(auth_code);
+ }
+
+ private void do_get_access_tokens(string auth_code) {
+ debug("ACTION: exchanging authorization code for access & refresh tokens");
+
+ host.install_login_wait_pane();
+
+ GetAccessTokensTransaction tokens_txn = new GetAccessTokensTransaction(session, auth_code);
+ tokens_txn.completed.connect(on_get_access_tokens_complete);
+ tokens_txn.network_error.connect(on_get_access_tokens_error);
+
+ try {
+ tokens_txn.execute();
+ } catch (Spit.Publishing.PublishingError err) {
+ host.post_error(err);
+ }
+ }
+
+ private void on_get_access_tokens_complete(Publishing.RESTSupport.Transaction txn) {
+ txn.completed.disconnect(on_get_access_tokens_complete);
+ txn.network_error.disconnect(on_get_access_tokens_error);
+
+ debug("EVENT: network transaction to exchange authorization code for access tokens " +
+ "completed successfully.");
+
+ do_extract_tokens(txn.get_response());
+ }
+
+ private void on_get_access_tokens_error(Publishing.RESTSupport.Transaction txn,
+ Spit.Publishing.PublishingError err) {
+ txn.completed.disconnect(on_get_access_tokens_complete);
+ txn.network_error.disconnect(on_get_access_tokens_error);
+
+ debug("EVENT: network transaction to exchange authorization code for access tokens " +
+ "failed; response = '%s'", txn.get_response());
+
+ host.post_error(err);
+ }
+
+ private void do_extract_tokens(string response_body) {
+ debug("ACTION: extracting OAuth tokens from body of server response");
+
+ Json.Parser parser = new Json.Parser();
+
+ try {
+ parser.load_from_data(response_body);
+ } catch (Error err) {
+ host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
+ "Couldn't parse JSON response: " + err.message));
+ return;
+ }
+
+ Json.Object response_obj = parser.get_root().get_object();
+
+ if ((!response_obj.has_member("access_token")) && (!response_obj.has_member("refresh_token"))) {
+ host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
+ "neither access_token nor refresh_token not present in server response"));
+ return;
+ }
+
+ if (response_obj.has_member("refresh_token")) {
+ string refresh_token = response_obj.get_string_member("refresh_token");
+
+ if (refresh_token != "")
+ on_refresh_token_available(refresh_token);
+ }
+
+ if (response_obj.has_member("access_token")) {
+ string access_token = response_obj.get_string_member("access_token");
+
+ if (access_token != "")
+ on_access_token_available(access_token);
+ }
+ }
+ private void on_refresh_token_available(string token) {
+ debug("EVENT: an OAuth refresh token has become available; token = '%s'.", token);
+ this.params.insert("RefreshToken", new Variant.string(token));
+
+ session.refresh_token = token;
+ }
+
+ private void on_access_token_available(string token) {
+ debug("EVENT: an OAuth access token has become available; token = '%s'.", token);
+
+ session.access_token = token;
+ this.params.insert("AccessToken", new Variant.string(token));
+
+ do_fetch_username();
+ }
+
+ private void do_fetch_username() {
+ debug("ACTION: running network transaction to fetch username.");
+
+ host.install_login_wait_pane();
+ host.set_service_locked(true);
+
+ UsernameFetchTransaction txn = new UsernameFetchTransaction(session);
+ txn.completed.connect(on_fetch_username_transaction_completed);
+ txn.network_error.connect(on_fetch_username_transaction_error);
+
+ try {
+ txn.execute();
+ } catch (Error err) {
+ host.post_error(err);
+ }
+ }
+
+ private void on_fetch_username_transaction_completed(Publishing.RESTSupport.Transaction txn) {
+ txn.completed.disconnect(on_fetch_username_transaction_completed);
+ txn.network_error.disconnect(on_fetch_username_transaction_error);
+
+ debug("EVENT: username fetch transaction completed successfully.");
+
+ do_extract_username(txn.get_response());
+ }
+
+ private void on_fetch_username_transaction_error(Publishing.RESTSupport.Transaction txn,
+ Spit.Publishing.PublishingError err) {
+ txn.completed.disconnect(on_fetch_username_transaction_completed);
+ txn.network_error.disconnect(on_fetch_username_transaction_error);
+
+ debug("EVENT: username fetch transaction caused a network error");
+
+ host.post_error(err);
+ }
+
+ private void do_extract_username(string response_body) {
+ debug("ACTION: extracting username from body of server response");
+
+ Json.Parser parser = new Json.Parser();
+
+ try {
+ parser.load_from_data(response_body);
+ } catch (Error err) {
+ host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
+ "Couldn't parse JSON response: " + err.message));
+ return;
+ }
+
+ Json.Object response_obj = parser.get_root().get_object();
+
+ if (response_obj.has_member("name")) {
+ string username = response_obj.get_string_member("name");
+
+ if (username != "")
+ this.params.insert("UserName", new Variant.string(username));
+ }
+
+ if (response_obj.has_member("access_token")) {
+ string access_token = response_obj.get_string_member("access_token");
+
+ if (access_token != "")
+ this.params.insert("AccessToken", new Variant.string(access_token));
+ }
+
+ // by the time we get a username, the session should be authenticated, or else something
+ // really tragic has happened
+ assert(session.is_authenticated());
+
+ this.authenticated();
+ }
+
+
+ private void do_exchange_refresh_token_for_access_token() {
+ debug("ACTION: exchanging OAuth refresh token for OAuth access token.");
+
+ host.install_login_wait_pane();
+
+ RefreshAccessTokenTransaction txn = new RefreshAccessTokenTransaction(session);
+
+ txn.completed.connect(on_refresh_access_token_transaction_completed);
+ txn.network_error.connect(on_refresh_access_token_transaction_error);
+
+ try {
+ txn.execute();
+ } catch (Spit.Publishing.PublishingError err) {
+ host.post_error(err);
+ }
+ }
+
+ private void on_refresh_access_token_transaction_completed(Publishing.RESTSupport.Transaction
+ txn) {
+ txn.completed.disconnect(on_refresh_access_token_transaction_completed);
+ txn.network_error.disconnect(on_refresh_access_token_transaction_error);
+
+ debug("EVENT: refresh access token transaction completed successfully.");
+
+ if (session.is_authenticated()) // ignore these events if the session is already auth'd
+ return;
+
+ do_extract_tokens(txn.get_response());
+ }
+
+ private void on_refresh_access_token_transaction_error(Publishing.RESTSupport.Transaction txn,
+ Spit.Publishing.PublishingError err) {
+ txn.completed.disconnect(on_refresh_access_token_transaction_completed);
+ txn.network_error.disconnect(on_refresh_access_token_transaction_error);
+
+ debug("EVENT: refresh access token transaction caused a network error.");
+
+ if (session.is_authenticated()) // ignore these events if the session is already auth'd
+ return;
+
+ // 400 errors indicate that the OAuth client ID and secret have become invalid. In most
+ // cases, this can be fixed by logging the user out
+#if 0
+ if (txn.get_status_code() == 400) {
+ do_logout();
+ return;
+ }
+#endif
+
+ host.post_error(err);
+ }
+
+ }
+
+}
diff --git a/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
b/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
index d804440..b059b78 100644
--- a/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
+++ b/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
@@ -12,8 +12,10 @@ namespace Publishing.Authenticator {
public GLib.List<string> get_available_authenticators() {
var list = new GLib.List<string>();
- list.append ("flickr");
- list.append ("facebook");
+ list.append("flickr");
+ list.append("facebook");
+ list.append("picasa");
+ list.append("youtube");
return list;
}
@@ -25,6 +27,11 @@ namespace Publishing.Authenticator {
return new Shotwell.Flickr.Flickr(host);
case "facebook":
return new Shotwell.Facebook.Facebook(host);
+ case "picasa":
+ return new Shotwell.Google.Google("https://picasaweb.google.com/data/", host);
+
+ case "youtube":
+ return new Shotwell.Google.Google("https://gdata.youtube.com/", host);
default:
return null;
}
diff --git a/plugins/common/RESTSupport.vala b/plugins/common/RESTSupport.vala
index 09c28af..ceee967 100644
--- a/plugins/common/RESTSupport.vala
+++ b/plugins/common/RESTSupport.vala
@@ -791,63 +791,6 @@ public abstract class GooglePublisher : Object, Spit.Publishing.Publisher {
}
}
- private class WebAuthenticationPane : Shotwell.Plugins.Common.WebAuthenticationPane {
- public static bool cache_dirty = false;
-
- public signal void authorized(string auth_code);
-
- public WebAuthenticationPane(string auth_sequence_start_url) {
- Object (login_uri : auth_sequence_start_url);
- }
-
- public static bool is_cache_dirty() {
- return cache_dirty;
- }
-
- public override void on_page_load() {
- string page_title = get_view ().get_title();
- if (page_title.index_of("state=connect") > 0) {
- int auth_code_field_start = page_title.index_of("code=");
- if (auth_code_field_start < 0)
- return;
-
- string auth_code =
- page_title.substring(auth_code_field_start + 5); // 5 = "code=".length
-
- cache_dirty = true;
-
- authorized(auth_code);
- }
- }
- }
-
- private class GetAccessTokensTransaction : Publishing.RESTSupport.Transaction {
- private const string ENDPOINT_URL = "https://accounts.google.com/o/oauth2/token";
-
- public GetAccessTokensTransaction(Session session, string auth_code) {
- base.with_endpoint_url(session, ENDPOINT_URL);
-
- add_argument("code", auth_code);
- add_argument("client_id", OAUTH_CLIENT_ID);
- add_argument("client_secret", OAUTH_CLIENT_SECRET);
- add_argument("redirect_uri", "urn:ietf:wg:oauth:2.0:oob");
- add_argument("grant_type", "authorization_code");
- }
- }
-
- private class RefreshAccessTokenTransaction : Publishing.RESTSupport.Transaction {
- private const string ENDPOINT_URL = "https://accounts.google.com/o/oauth2/token";
-
- public RefreshAccessTokenTransaction(Session session) {
- base.with_endpoint_url(session, ENDPOINT_URL);
-
- add_argument("client_id", OAUTH_CLIENT_ID);
- add_argument("client_secret", OAUTH_CLIENT_SECRET);
- add_argument("refresh_token", ((GoogleSession) session).get_refresh_token());
- add_argument("grant_type", "refresh_token");
- }
- }
-
public class AuthenticatedTransaction : Publishing.RESTSupport.Transaction {
private AuthenticatedTransaction.with_endpoint_url(GoogleSession session,
string endpoint_url, Publishing.RESTSupport.HttpMethod method) {
@@ -863,19 +806,11 @@ public abstract class GooglePublisher : Object, Spit.Publishing.Publisher {
}
}
- private class UsernameFetchTransaction : AuthenticatedTransaction {
- private const string ENDPOINT_URL = "https://www.googleapis.com/oauth2/v1/userinfo";
-
- public UsernameFetchTransaction(GoogleSession session) {
- base(session, ENDPOINT_URL, Publishing.RESTSupport.HttpMethod.GET);
- }
- }
-
private string scope;
private GoogleSessionImpl session;
- private WebAuthenticationPane? web_auth_pane;
private weak Spit.Publishing.PluginHost host;
private weak Spit.Publishing.Service service;
+ private Spit.Publishing.Authenticator authenticator;
protected GooglePublisher(Spit.Publishing.Service service, Spit.Publishing.PluginHost host,
string scope) {
@@ -883,272 +818,11 @@ public abstract class GooglePublisher : Object, Spit.Publishing.Publisher {
this.session = new GoogleSessionImpl();
this.service = service;
this.host = host;
- this.web_auth_pane = null;
- }
-
- private void on_web_auth_pane_authorized(string auth_code) {
- web_auth_pane.authorized.disconnect(on_web_auth_pane_authorized);
-
- debug("EVENT: user authorized scope %s with auth_code %s", scope, auth_code);
-
- if (!is_running())
- return;
-
- do_get_access_tokens(auth_code);
- }
-
- private void on_get_access_tokens_complete(Publishing.RESTSupport.Transaction txn) {
- txn.completed.disconnect(on_get_access_tokens_complete);
- txn.network_error.disconnect(on_get_access_tokens_error);
-
- debug("EVENT: network transaction to exchange authorization code for access tokens " +
- "completed successfully.");
-
- if (!is_running())
- return;
-
- do_extract_tokens(txn.get_response());
+ this.authenticator = this.get_authenticator();
+ this.authenticator.authenticated.connect(on_authenticator_authenticated);
}
- private void on_get_access_tokens_error(Publishing.RESTSupport.Transaction txn,
- Spit.Publishing.PublishingError err) {
- txn.completed.disconnect(on_get_access_tokens_complete);
- txn.network_error.disconnect(on_get_access_tokens_error);
-
- debug("EVENT: network transaction to exchange authorization code for access tokens " +
- "failed; response = '%s'", txn.get_response());
-
- if (!is_running())
- return;
-
- host.post_error(err);
- }
-
- private void on_refresh_access_token_transaction_completed(Publishing.RESTSupport.Transaction
- txn) {
- txn.completed.disconnect(on_refresh_access_token_transaction_completed);
- txn.network_error.disconnect(on_refresh_access_token_transaction_error);
-
- debug("EVENT: refresh access token transaction completed successfully.");
-
- if (!is_running())
- return;
-
- if (session.is_authenticated()) // ignore these events if the session is already auth'd
- return;
-
- do_extract_tokens(txn.get_response());
- }
-
- private void on_refresh_access_token_transaction_error(Publishing.RESTSupport.Transaction txn,
- Spit.Publishing.PublishingError err) {
- txn.completed.disconnect(on_refresh_access_token_transaction_completed);
- txn.network_error.disconnect(on_refresh_access_token_transaction_error);
-
- debug("EVENT: refresh access token transaction caused a network error.");
-
- if (!is_running())
- return;
-
- if (session.is_authenticated()) // ignore these events if the session is already auth'd
- return;
-
- // 400 errors indicate that the OAuth client ID and secret have become invalid. In most
- // cases, this can be fixed by logging the user out
- if (txn.get_status_code() == 400) {
- do_logout();
- return;
- }
-
- host.post_error(err);
- }
-
- private void on_refresh_token_available(string token) {
- debug("EVENT: an OAuth refresh token has become available; token = '%s'.", token);
-
- if (!is_running())
- return;
-
- session.refresh_token = token;
- }
-
- private void on_access_token_available(string token) {
- debug("EVENT: an OAuth access token has become available; token = '%s'.", token);
-
- if (!is_running())
- return;
-
- session.access_token = token;
-
- do_fetch_username();
- }
-
- private void on_fetch_username_transaction_completed(Publishing.RESTSupport.Transaction txn) {
- txn.completed.disconnect(on_fetch_username_transaction_completed);
- txn.network_error.disconnect(on_fetch_username_transaction_error);
-
- debug("EVENT: username fetch transaction completed successfully.");
-
- if (!is_running())
- return;
-
- do_extract_username(txn.get_response());
- }
-
- private void on_fetch_username_transaction_error(Publishing.RESTSupport.Transaction txn,
- Spit.Publishing.PublishingError err) {
- txn.completed.disconnect(on_fetch_username_transaction_completed);
- txn.network_error.disconnect(on_fetch_username_transaction_error);
-
- debug("EVENT: username fetch transaction caused a network error");
-
- if (!is_running())
- return;
-
- host.post_error(err);
- }
-
- private void do_get_access_tokens(string auth_code) {
- debug("ACTION: exchanging authorization code for access & refresh tokens");
-
- host.install_login_wait_pane();
-
- GetAccessTokensTransaction tokens_txn = new GetAccessTokensTransaction(session, auth_code);
- tokens_txn.completed.connect(on_get_access_tokens_complete);
- tokens_txn.network_error.connect(on_get_access_tokens_error);
-
- try {
- tokens_txn.execute();
- } catch (Spit.Publishing.PublishingError err) {
- host.post_error(err);
- }
- }
-
- private void do_hosted_web_authentication() {
- debug("ACTION: running OAuth authentication flow in hosted web pane.");
-
- string user_authorization_url = "https://accounts.google.com/o/oauth2/auth?" +
- "response_type=code&" +
- "client_id=" + OAUTH_CLIENT_ID + "&" +
- "redirect_uri=" + Soup.URI.encode("urn:ietf:wg:oauth:2.0:oob", null) + "&" +
- "scope=" + Soup.URI.encode(scope, null) + "+" +
- Soup.URI.encode("https://www.googleapis.com/auth/userinfo.profile", null) + "&" +
- "state=connect&" +
- "access_type=offline&" +
- "approval_prompt=force";
-
- web_auth_pane = new WebAuthenticationPane(user_authorization_url);
- web_auth_pane.authorized.connect(on_web_auth_pane_authorized);
-
- host.install_dialog_pane(web_auth_pane);
-
- }
-
- private void do_exchange_refresh_token_for_access_token() {
- debug("ACTION: exchanging OAuth refresh token for OAuth access token.");
-
- host.install_login_wait_pane();
-
- RefreshAccessTokenTransaction txn = new RefreshAccessTokenTransaction(session);
-
- txn.completed.connect(on_refresh_access_token_transaction_completed);
- txn.network_error.connect(on_refresh_access_token_transaction_error);
-
- try {
- txn.execute();
- } catch (Spit.Publishing.PublishingError err) {
- host.post_error(err);
- }
- }
-
- private void do_extract_tokens(string response_body) {
- debug("ACTION: extracting OAuth tokens from body of server response");
-
- Json.Parser parser = new Json.Parser();
-
- try {
- parser.load_from_data(response_body);
- } catch (Error err) {
- host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
- "Couldn't parse JSON response: " + err.message));
- return;
- }
-
- Json.Object response_obj = parser.get_root().get_object();
-
- if ((!response_obj.has_member("access_token")) && (!response_obj.has_member("refresh_token"))) {
- host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
- "neither access_token nor refresh_token not present in server response"));
- return;
- }
-
- if (response_obj.has_member("refresh_token")) {
- string refresh_token = response_obj.get_string_member("refresh_token");
-
- if (refresh_token != "")
- on_refresh_token_available(refresh_token);
- }
-
- if (response_obj.has_member("access_token")) {
- string access_token = response_obj.get_string_member("access_token");
-
- if (access_token != "")
- on_access_token_available(access_token);
- }
- }
-
- private void do_fetch_username() {
- debug("ACTION: running network transaction to fetch username.");
-
- host.install_login_wait_pane();
- host.set_service_locked(true);
-
- UsernameFetchTransaction txn = new UsernameFetchTransaction(session);
- txn.completed.connect(on_fetch_username_transaction_completed);
- txn.network_error.connect(on_fetch_username_transaction_error);
-
- try {
- txn.execute();
- } catch (Error err) {
- host.post_error(err);
- }
- }
-
- private void do_extract_username(string response_body) {
- debug("ACTION: extracting username from body of server response");
-
- Json.Parser parser = new Json.Parser();
-
- try {
- parser.load_from_data(response_body);
- } catch (Error err) {
- host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
- "Couldn't parse JSON response: " + err.message));
- return;
- }
-
- Json.Object response_obj = parser.get_root().get_object();
-
- if (response_obj.has_member("name")) {
- string username = response_obj.get_string_member("name");
-
- if (username != "")
- session.user_name = username;
- }
-
- if (response_obj.has_member("access_token")) {
- string access_token = response_obj.get_string_member("access_token");
-
- if (access_token != "")
- session.access_token = access_token;
- }
-
- // by the time we get a username, the session should be authenticated, or else something
- // really tragic has happened
- assert(session.is_authenticated());
-
- on_login_flow_complete();
- }
+ protected abstract Spit.Publishing.Authenticator get_authenticator();
protected unowned Spit.Publishing.PluginHost get_host() {
return host;
@@ -1159,17 +833,7 @@ public abstract class GooglePublisher : Object, Spit.Publishing.Publisher {
}
protected void start_oauth_flow(string? refresh_token = null) {
- if (refresh_token != null && refresh_token != "") {
- session.refresh_token = refresh_token;
- do_exchange_refresh_token_for_access_token();
- } else {
- if (WebAuthenticationPane.is_cache_dirty()) {
- host.install_static_message_pane(_("You have already logged in and out of a Google service
during this Shotwell session.\n\nTo continue publishing to Google services, quit and restart Shotwell, then
try publishing again."));
- return;
- }
-
- do_hosted_web_authentication();
- }
+ authenticator.authenticate();
}
protected abstract void on_login_flow_complete();
@@ -1185,6 +849,23 @@ public abstract class GooglePublisher : Object, Spit.Publishing.Publisher {
public Spit.Publishing.Service get_service() {
return service;
}
+
+ private void on_authenticator_authenticated() {
+ var params = this.authenticator.get_authentication_parameter();
+ Variant refresh_token = null;
+ Variant access_token = null;
+ Variant user_name = null;
+
+ params.lookup_extended("RefreshToken", null, out refresh_token);
+ params.lookup_extended("AccessToken", null, out access_token);
+ params.lookup_extended("UserName", null, out user_name);
+
+ this.session.refresh_token = refresh_token.get_string();
+ this.session.access_token = access_token.get_string();
+ this.session.user_name = user_name.get_string();
+
+ this.on_login_flow_complete();
+ }
}
}
diff --git a/plugins/shotwell-publishing/PicasaPublishing.vala
b/plugins/shotwell-publishing/PicasaPublishing.vala
index 976ba42..cff9beb 100644
--- a/plugins/shotwell-publishing/PicasaPublishing.vala
+++ b/plugins/shotwell-publishing/PicasaPublishing.vala
@@ -459,6 +459,10 @@ public class PicasaPublisher : Publishing.RESTSupport.GooglePublisher {
running = false;
}
+
+ protected override Spit.Publishing.Authenticator get_authenticator() {
+ return Publishing.Authenticator.Factory.get_instance().create("picasa", get_host());
+ }
}
internal class Album {
diff --git a/plugins/shotwell-publishing/YouTubePublishing.vala
b/plugins/shotwell-publishing/YouTubePublishing.vala
index bf2399a..ee7c158 100644
--- a/plugins/shotwell-publishing/YouTubePublishing.vala
+++ b/plugins/shotwell-publishing/YouTubePublishing.vala
@@ -415,6 +415,10 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
do_show_service_welcome_pane();
}
+
+ protected override Spit.Publishing.Authenticator get_authenticator() {
+ return Publishing.Authenticator.Factory.get_instance().create("picasa", get_host());
+ }
}
internal class PublishingOptionsPane : Spit.Publishing.DialogPane, GLib.Object {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]