summaryrefslogtreecommitdiff
path: root/plugins/authenticator/shotwell/GoogleAuthenticator.vala
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/authenticator/shotwell/GoogleAuthenticator.vala')
-rw-r--r--plugins/authenticator/shotwell/GoogleAuthenticator.vala219
1 files changed, 109 insertions, 110 deletions
diff --git a/plugins/authenticator/shotwell/GoogleAuthenticator.vala b/plugins/authenticator/shotwell/GoogleAuthenticator.vala
index a607cd0..3276e67 100644
--- a/plugins/authenticator/shotwell/GoogleAuthenticator.vala
+++ b/plugins/authenticator/shotwell/GoogleAuthenticator.vala
@@ -7,6 +7,9 @@ namespace Publishing.Authenticator.Shotwell.Google {
private const string OAUTH_CLIENT_SECRET = "pwpzZ7W1TCcD5uIfYCu8sM7x";
private const string OAUTH_CALLBACK_URI = REVERSE_CLIENT_ID + ":/auth-callback";
+ private const string SCHEMA_KEY_PROFILE_ID = "shotwell-profile-id";
+ private const string SCHEMA_KEY_ACCOUNTNAME = "accountname";
+
private class WebAuthenticationPane : Common.WebAuthenticationPane {
public static bool cache_dirty = false;
private string? auth_code = null;
@@ -27,10 +30,15 @@ namespace Publishing.Authenticator.Shotwell.Google {
return;
}
- var uri = new Soup.URI(get_view().get_uri());
- if (uri.scheme == REVERSE_CLIENT_ID && this.auth_code == null) {
- var form_data = Soup.Form.decode (uri.query);
- this.auth_code = form_data.lookup("code");
+ try {
+ var uri = GLib.Uri.parse(get_view().get_uri(), UriFlags.NONE);
+ if (uri.get_scheme() == REVERSE_CLIENT_ID && this.auth_code == null) {
+ var form_data = Soup.Form.decode (uri.get_query());
+ this.auth_code = form_data.lookup("code");
+ }
+ } catch (Error err) {
+ debug ("Failed to parse auth code from URI %s: %s", get_view().get_uri(),
+ err.message);
}
if (this.auth_code != null) {
@@ -39,10 +47,14 @@ namespace Publishing.Authenticator.Shotwell.Google {
}
private void on_shotwell_auth_request_cb(WebKit.URISchemeRequest request) {
- var uri = new Soup.URI(request.get_uri());
- debug("URI: %s", request.get_uri());
- var form_data = Soup.Form.decode (uri.query);
- this.auth_code = form_data.lookup("code");
+ try {
+ var uri = GLib.Uri.parse(request.get_uri(), GLib.UriFlags.NONE);
+ debug("URI: %s", request.get_uri());
+ var form_data = Soup.Form.decode (uri.get_query());
+ this.auth_code = form_data.lookup("code");
+ } catch (Error err) {
+ debug("Failed to parse request URI: %s", err.message);
+ }
var response = "";
var mins = new MemoryInputStream.from_data(response.data, null);
@@ -77,7 +89,7 @@ namespace Publishing.Authenticator.Shotwell.Google {
}
private class GetAccessTokensTransaction : Publishing.RESTSupport.Transaction {
- private const string ENDPOINT_URL = "https://accounts.google.com/o/oauth2/token";
+ private const string ENDPOINT_URL = "https://oauth2.googleapis.com/token";
public GetAccessTokensTransaction(Session session, string auth_code) {
base.with_endpoint_url(session, ENDPOINT_URL);
@@ -91,7 +103,7 @@ namespace Publishing.Authenticator.Shotwell.Google {
}
private class RefreshAccessTokenTransaction : Publishing.RESTSupport.Transaction {
- private const string ENDPOINT_URL = "https://accounts.google.com/o/oauth2/token";
+ private const string ENDPOINT_URL = "https://oauth2.googleapis.com/token";
public RefreshAccessTokenTransaction(Session session) {
base.with_endpoint_url(session, ENDPOINT_URL);
@@ -112,12 +124,18 @@ namespace Publishing.Authenticator.Shotwell.Google {
}
internal class Google : Spit.Publishing.Authenticator, Object {
+ private const string PASSWORD_SCHEME = "org.gnome.Shotwell.Google";
+
private string scope = null;
+
+ // Prepare for multiple user accounts
+ private string accountname = "default";
private Spit.Publishing.PluginHost host = null;
private GLib.HashTable<string, Variant> params = null;
private WebAuthenticationPane web_auth_pane = null;
private Session session = null;
private string welcome_message = null;
+ private Secret.Schema? schema = null;
public Google(string scope,
string welcome_message,
@@ -127,13 +145,24 @@ namespace Publishing.Authenticator.Shotwell.Google {
this.scope = scope;
this.session = new Session();
this.welcome_message = welcome_message;
+ this.schema = new Secret.Schema(PASSWORD_SCHEME, Secret.SchemaFlags.NONE,
+ SCHEMA_KEY_PROFILE_ID, Secret.SchemaAttributeType.STRING,
+ SCHEMA_KEY_ACCOUNTNAME, Secret.SchemaAttributeType.STRING,
+ "scope", Secret.SchemaAttributeType.STRING);
}
public void authenticate() {
- var refresh_token = host.get_config_string("refresh_token", null);
+ string? refresh_token = null;
+ try {
+ refresh_token = Secret.password_lookup_sync(this.schema, null,
+ SCHEMA_KEY_PROFILE_ID, host.get_current_profile_id(),
+ SCHEMA_KEY_ACCOUNTNAME, this.accountname, "scope", this.scope);
+ } catch (Error err) {
+ critical("Failed to lookup refresh_token from password store: %s", err.message);
+ }
if (refresh_token != null && refresh_token != "") {
on_refresh_token_available(refresh_token);
- do_exchange_refresh_token_for_access_token();
+ do_exchange_refresh_token_for_access_token.begin();
return;
}
@@ -157,22 +186,32 @@ namespace Publishing.Authenticator.Shotwell.Google {
public void logout() {
session.deauthenticate();
- host.set_config_string("refresh_token", "");
+ try {
+ Secret.password_clear_sync(this.schema, null,
+ SCHEMA_KEY_PROFILE_ID, host.get_current_profile_id(),
+ SCHEMA_KEY_ACCOUNTNAME, this.accountname, "scope", this.scope);
+ } catch (Error err) {
+ critical("Failed to remove password for scope %s: %s", this.scope, err.message);
+ }
}
public void refresh() {
// TODO: Needs to re-auth
}
+ public void set_accountname(string accountname) {
+ this.accountname = accountname;
+ }
+
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(OAUTH_CALLBACK_URI, null) + "&" +
- "scope=" + Soup.URI.encode(this.scope, null) + "+" +
- Soup.URI.encode("https://www.googleapis.com/auth/userinfo.profile", null) + "&" +
+ "redirect_uri=" + GLib.Uri.escape_string(OAUTH_CALLBACK_URI, null) + "&" +
+ "scope=" + GLib.Uri.escape_string(this.scope, null) + "+" +
+ GLib.Uri.escape_string("https://www.googleapis.com/auth/userinfo.profile", null) + "&" +
"state=connect&" +
"access_type=offline&" +
"approval_prompt=force";
@@ -189,48 +228,31 @@ namespace Publishing.Authenticator.Shotwell.Google {
debug("EVENT: user authorized scope %s with auth_code %s", scope, auth_code);
- do_get_access_tokens(auth_code);
+ do_get_access_tokens.begin(auth_code);
}
private void on_web_auth_pane_error() {
host.post_error(web_auth_pane.load_error);
}
- private void do_get_access_tokens(string auth_code) {
+ private async 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) {
+ yield tokens_txn.execute_async();
+ debug("EVENT: network transaction to exchange authorization code for access tokens " +
+ "completed successfully.");
+ do_extract_tokens(tokens_txn.get_response());
+ } catch (Error err) {
+ debug("EVENT: network transaction to exchange authorization code for access tokens " +
+ "failed; response = '%s'", tokens_txn.get_response());
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) {
@@ -297,45 +319,28 @@ namespace Publishing.Authenticator.Shotwell.Google {
session.access_token = token;
this.params.insert("AccessToken", new Variant.string(token));
- do_fetch_username();
+ do_fetch_username.begin();
}
- private void do_fetch_username() {
+ private async 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();
+ yield txn.execute_async();
+ debug("EVENT: username fetch transaction completed successfully.");
+ do_extract_username(txn.get_response());
} catch (Error err) {
+ debug("EVENT: username fetch transaction caused a network error");
+
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");
@@ -368,61 +373,57 @@ namespace Publishing.Authenticator.Shotwell.Google {
// by the time we get a username, the session should be authenticated, or else something
// really tragic has happened
assert(session.is_authenticated());
- host.set_config_string("refresh_token", session.refresh_token);
+ try {
+ Secret.password_store_sync(this.schema, Secret.COLLECTION_DEFAULT,
+ "Shotwell publishing (Google account scope %s@%s)".printf(this.accountname, this.scope),
+ session.refresh_token, null,
+ SCHEMA_KEY_PROFILE_ID, host.get_current_profile_id(),
+ SCHEMA_KEY_ACCOUNTNAME, this.accountname, "scope", this.scope);
+ } catch (Error err) {
+ critical("Failed to look up password for scope %s: %s", this.scope, err.message);
+ }
this.authenticated();
web_auth_pane.clear();
}
-
- private void do_exchange_refresh_token_for_access_token() {
+ private async 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);
+ yield txn.execute_async();
+ debug("EVENT: refresh access token transaction completed successfully.");
- 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;
- if (txn.get_status_code() == Soup.Status.BAD_REQUEST ||
- txn.get_status_code() == Soup.Status.UNAUTHORIZED) {
- // Refresh token invalid, starting over
- host.set_config_string("refresh_token", "");
- Idle.add (() => { this.authenticate(); return false; });
+ if (session.is_authenticated()) // ignore these events if the session is already auth'd
+ return;
+
+ do_extract_tokens(txn.get_response());
+ } catch (Error err) {
+ 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;
+
+ if (txn.get_status_code() == Soup.Status.BAD_REQUEST ||
+ txn.get_status_code() == Soup.Status.UNAUTHORIZED) {
+ // Refresh token invalid, starting over
+ try {
+ Secret.password_clear_sync(this.schema, null,
+ SCHEMA_KEY_PROFILE_ID, host.get_current_profile_id(),
+ SCHEMA_KEY_ACCOUNTNAME, this.accountname, "scope", this.scope);
+ } catch (Error err) {
+ critical("Failed to remove password for accountname@scope %s@%s: %s", this.accountname, this.scope, err.message);
+ }
+
+ Idle.add (() => { this.authenticate(); return false; });
+ }
+
+ web_auth_pane.clear();
+ host.post_error(err);
}
-
- web_auth_pane.clear();
- host.post_error(err);
}
private void do_show_service_welcome_pane() {
@@ -436,7 +437,5 @@ namespace Publishing.Authenticator.Shotwell.Google {
this.do_hosted_web_authentication();
}
-
-
}
}