[gnome-maps/wip/mlundblad/osm-oauth-external: 2/2] WIP: Use external browser to authorize OAuth token
- From: Marcus Lundblad <mlundblad src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-maps/wip/mlundblad/osm-oauth-external: 2/2] WIP: Use external browser to authorize OAuth token
- Date: Sun, 24 Apr 2022 21:28:19 +0000 (UTC)
commit 4c58c6634c6f6d64288f3e90265344ab1f072fda
Author: Marcus Lundblad <ml dfupdate se>
Date: Sat Apr 23 23:05:34 2022 +0200
WIP: Use external browser to authorize OAuth token
data/ui/osm-account-dialog.ui | 69 -----------------
src/osmAccountDialog.js | 52 ++-----------
src/osmConnection.js | 172 +++++++++++-------------------------------
src/osmEdit.js | 35 ++++-----
4 files changed, 65 insertions(+), 263 deletions(-)
---
diff --git a/data/ui/osm-account-dialog.ui b/data/ui/osm-account-dialog.ui
index 8cddfeca..44c8b5d0 100644
--- a/data/ui/osm-account-dialog.ui
+++ b/data/ui/osm-account-dialog.ui
@@ -50,59 +50,6 @@ OpenStreetMap account.</property>
<property name="visible">True</property>
<property name="column-spacing">10</property>
<property name="row-spacing">10</property>
- <child>
- <object class="GtkLabel">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Email</property>
- <property name="halign">GTK_ALIGN_END</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkEntry" id="emailEntry">
- <property name="visible">True</property>
- <property name="hexpand">True</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">0</property>
- <property name="width">2</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Password</property>
- <property name="halign">GTK_ALIGN_END</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkEntry" id="passwordEntry">
- <property name="visible">True</property>
- <property name="hexpand">True</property>
- <property name="input-purpose">GTK_INPUT_PURPOSE_PASSWORD</property>
- <property name="visibility">False</property>
- <property name="caps-lock-warning">True</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">1</property>
- <property name="width">2</property>
- </packing>
- </child>
<child>
<object class="GtkSpinner" id="signInSpinner">
<property name="visible">False</property>
@@ -137,7 +84,6 @@ OpenStreetMap account.</property>
<property name="visible">True</property>
<property name="halign">GTK_ALIGN_END</property>
<property name="label" translatable="yes">Sign In</property>
- <property name="sensitive">False</property>
<style>
<class name="suggested-action"/>
</style>
@@ -153,21 +99,6 @@ OpenStreetMap account.</property>
<property name="top_attach">2</property>
</packing>
</child>
-
- <child>
- <object class="GtkLabel" id="resetPasswordLabel">
- <property name="visible">False</property>
- <property name="can_focus">True</property>
- <property name="label" translatable="yes"
- comments="The label should contain the link to the OSM reset password page with a
translated title">Sorry, that didn’t work. Please try again, or visit
-<a href="https://www.openstreetmap.org/user/forgot-password">OpenStreetMap</a> to reset your
password.</property>
- <property name="use-markup">True</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">4</property>
- </packing>
- </child>
<child>
<object class="GtkLabel" id="verificationFailedLabel">
<property name="visible">False</property>
diff --git a/src/osmAccountDialog.js b/src/osmAccountDialog.js
index d522e456..f0cdd5b3 100644
--- a/src/osmAccountDialog.js
+++ b/src/osmAccountDialog.js
@@ -34,12 +34,9 @@ var Response = {
var OSMAccountDialog = GObject.registerClass({
Template: 'resource:///org/gnome/Maps/ui/osm-account-dialog.ui',
InternalChildren: ['stack',
- 'emailEntry',
- 'passwordEntry',
'signInButton',
'signInSpinner',
'signUpLinkButton',
- 'resetPasswordLabel',
'verifyGrid',
'verificationEntry',
'verifyButton',
@@ -57,12 +54,6 @@ var OSMAccountDialog = GObject.registerClass({
super._init(params);
- this._emailEntry.connect('changed',
- this._onCredentialsChanged.bind(this));
- this._passwordEntry.connect('changed',
- this._onCredentialsChanged.bind(this));
- this._passwordEntry.connect('activate',
- this._onPasswordActivated.bind(this));
this._signInButton.connect('clicked',
this._onSignInButtonClicked.bind(this));
this._verifyButton.connect('clicked',
@@ -93,59 +84,28 @@ var OSMAccountDialog = GObject.registerClass({
this._verifyGrid.attach(this._verifyView, 0, 0, 1, 1);
}
- _onCredentialsChanged() {
- let email = this._emailEntry.text;
- let password = this._passwordEntry.text;
-
- // make sign in button sensitive if credential have been entered
- this._signInButton.sensitive =
- email && email.length > 0 && password && password.length > 0;
- }
-
_onSignInButtonClicked() {
this._performSignIn();
}
- _onPasswordActivated() {
- /* if username and password was entered, proceed with sign-in */
- let email = this._emailEntry.text;
- let password = this._passwordEntry.text;
-
- if (email && email.length > 0 && password && password.length > 0)
- this._performSignIn();
- }
-
_performSignIn() {
- /* turn on signing in spinner and desensisize credential entries */
+ // turn on signing in spinner
this._signInSpinner.visible = true;
this._signInButton.sensitive = false;
- this._emailEntry.sensitive = false;
- this._passwordEntry.sensitive = false;
this._signUpLinkButton.visible = false;
- Application.osmEdit.performOAuthSignIn(this._emailEntry.text,
- this._passwordEntry.text,
- this._onOAuthSignInPerformed.bind(this));
+ Application.osmEdit.performOAuthSignIn(this._onOAuthTokenAuthorized.bind(this));
}
- _onOAuthSignInPerformed(success, verificationPage) {
+ _onOAuthTokenAuthorized(success) {
if (success) {
- /* switch to the verification view and show the verification
- page */
- this._verifyView.load_html(verificationPage,
- 'https://www.openstreetmap.org/');
+ // switch to the verification view
this._stack.visible_child_name = 'verify';
} else {
- /* clear password entry */
- this._passwordEntry.text = '';
- /* show the password reset link */
- this._resetPasswordLabel.visible = true;
+ // TODO: show error
}
this._signInSpinner.visible = false;
- /* re-sensisize credential entries */
- this._emailEntry.sensitive = true;
- this._passwordEntry.sensitive = true;
}
_onVerifyButtonClicked() {
@@ -192,7 +152,6 @@ var OSMAccountDialog = GObject.registerClass({
} else {
/* switch to the logged in view and reset the state in case
the user signs out and start over again */
- this._resetPasswordLabel.visible = false;
this._verificationFailedLabel = false;
this._signUpLinkButton.visible = true;
this._stack.visible_child_name = 'logged-in';
@@ -202,7 +161,6 @@ var OSMAccountDialog = GObject.registerClass({
Utils.showDialog(errorMessage, Gtk.MessageType.ERROR, this);
/* switch back to the sign-in view, and show a label indicating
that verification failed */
- this._resetPasswordLabel.visible = false;
this._signUpLinkButton.visible = false;
this._verificationFailedLabel.visible = true;
this._signInButton.sensitive = true;
diff --git a/src/osmConnection.js b/src/osmConnection.js
index 6bb7026c..f20e8600 100644
--- a/src/osmConnection.js
+++ b/src/osmConnection.js
@@ -23,6 +23,8 @@
const _ = imports.gettext.gettext;
const Maps = imports.gi.GnomeMaps;
+
+const Gio = imports.gi.Gio;
const Rest = imports.gi.Rest;
const Secret = imports.gi.Secret;
const Soup = imports.gi.Soup;
@@ -238,102 +240,19 @@ var OSMConnection = class OSMConnection {
callback(true);
}
- authorizeOAuthToken(username, password, callback) {
- /* get login session ID */
- let loginUrl = LOGIN_URL + '?cookie_test=true';
- let uri = new Soup.URI(loginUrl);
- let msg = new Soup.Message({method: 'GET', uri: uri});
+ authorizeOAuthToken(callback) {
+ let auth = '/authorize?oauth_token=';
+ let authorizeUrl = OAUTH_ENDPOINT_URL + auth + this._oauthToken;
- this._session.queue_message(msg, (obj, message) => {
- this._onLoginFormReceived(message, username, password, callback);
- });
- }
+ Utils.debug('Trying to open: ' + authorizeUrl);
- _onLoginFormReceived(message, username, password, callback) {
- if (message.status_code !== Soup.Status.OK) {
+ try {
+ Gio.AppInfo.launch_default_for_uri(authorizeUrl, null);
+ callback(true);
+ } catch (e) {
+ Utils.debug('error: ' + e.message);
callback(false);
- return;
}
-
- let osmSessionID =
- this._extractOSMSessionID(message.response_headers);
- let osmSessionToken =
- this._extractToken(message.response_body.data);
-
- if (osmSessionID === null || osmSessionToken === null) {
- callback(false, null);
- return;
- }
-
- this._login(username, password, osmSessionID, osmSessionToken, callback);
- }
-
- _login(username, password, sessionId, token, callback) {
- /* post login form */
- let msg = Soup.form_request_new_from_hash('POST', LOGIN_URL,
- {username: username,
- password: password,
- referer: '/',
- commit: 'Login',
- authenticity_token: token});
- let requestHeaders = msg.request_headers;
-
- requestHeaders.append('Content-Type',
- 'application/x-www-form-urlencoded');
- requestHeaders.append('Cookie', '_osm_session=' + sessionId);
- msg.flags |= Soup.MessageFlags.NO_REDIRECT;
-
- this._session.queue_message(msg, (obj, message) => {
- if (message.status_code === Soup.Status.MOVED_TEMPORARILY)
- this._fetchAuthorizeForm(username, sessionId, callback);
- else
- callback(false, null);
- });
-
- }
-
- _fetchAuthorizeForm(username, sessionId, callback) {
- let auth = '/authorize?oauth_token=';
- let authorizeUrl = OAUTH_ENDPOINT_URL + auth + this._oauthToken;
- let uri = new Soup.URI(authorizeUrl);
- let msg = new Soup.Message({uri: uri, method: 'GET'});
-
- msg.request_headers.append('Cookie',
- '_osm_session=' + sessionId +
- '; _osm_username=' + username);
- this._session.queue_message(msg, (obj, message) => {
- if (message.status_code === Soup.Status.OK) {
- let token = this._extractToken(message.response_body.data);
- this._postAuthorizeForm(username, sessionId, token, callback);
- } else {
- callback(false, null);
- }
- });
- }
-
- _postAuthorizeForm(username, sessionId, token, callback) {
- let authorizeUrl = OAUTH_ENDPOINT_URL + '/authorize';
- let msg = Soup.form_request_new_from_hash('POST', authorizeUrl, {
- oauth_token: this._oauthToken,
- oauth_callback: '',
- authenticity_token: token,
- allow_write_api: '1',
- commit: 'Save changes'
- });
- let requestHeaders = msg.request_headers;
-
- requestHeaders.append('Content-Type',
- 'application/x-www-form-urlencoded');
- requestHeaders.append('Cookie',
- '_osm_session=' + sessionId +
- '; _osm_username=' + username);
-
- this._session.queue_message(msg, (obj, message) => {
- if (msg.status_code === Soup.Status.OK) {
- callback(true, message.response_body.data);
- } else
- callback(false, null);
- });
}
requestOAuthAccessToken(code, callback) {
@@ -342,6 +261,37 @@ var OSMConnection = class OSMConnection {
}, this._oauthProxy);
}
+ fetchLoggedInUser(callback) {
+ let call = this._callProxy.new_call();
+ call.set_method('GET');
+ call.set_function('/user/details');
+
+ call.invoke_async(null, (call, res, userdata) =>
+ { this._onFetchedLoggedInUser(call, callback); });
+ }
+
+ _onFetchedLoggedInUser(call, callback) {
+ switch (call.get_status_code()) {
+ case Soup.Status.OK:
+ try {
+ callback(Maps.osm_parse_user_details(call.get_payload()));
+ } catch (e) {
+ Utils.debug('Error parsing user details: ' + e.message);
+ callback(null);
+ }
+ break;
+ default:
+ /* Not ok, most likely 403 (forbidden), meaning the user
+ * didn't give permission to read user details.
+ * Just consider the user name unknown in this case
+ */
+ Utils.debug('Got status code ' + call.get_status_code() +
+ ' getting user details');
+ callback(null);
+ break;
+ }
+ }
+
_onAccessOAuthToken(error, callback) {
if (error) {
callback(false);
@@ -389,44 +339,6 @@ var OSMConnection = class OSMConnection {
_onPasswordCleared(source, result) {
Secret.password_clear_finish(result);
}
-
- /* extract the session ID from the login form response headers */
- _extractOSMSessionID(responseHeaders) {
- let cookie = responseHeaders.get('Set-Cookie');
-
- if (cookie === null)
- return null;
-
- let cookieParts = cookie.split(';');
- for (let cookiePart of cookieParts) {
- let kvPair = cookiePart.trim().split('=');
- let kv = kvPair;
-
- if (kv.length !== 2) {
- continue;
- } else if (kv[0] === '_osm_session') {
- return kv[1];
- }
- }
-
- return null;
- }
-
- /* extract the authenticity token from the hidden input field of the login
- form */
- _extractToken(messageBody) {
- let regex = /.*authenticity_token.*value=\"([^\"]+)\".*/;
- let lines = messageBody.split('\n');
-
- for (let line of lines) {
- let match = line.match(regex);
-
- if (match && match.length === 2)
- return match[1];
- }
-
- return null;
- }
};
/*
diff --git a/src/osmEdit.js b/src/osmEdit.js
index fa1a7e5d..bd3d6256 100644
--- a/src/osmEdit.js
+++ b/src/osmEdit.js
@@ -142,21 +142,16 @@ var OSMEdit = class OSMEdit {
this._osmConnection.closeChangeset(changesetId, callback);
}
- performOAuthSignIn(username, password, callback) {
+ performOAuthSignIn(callback) {
this._osmConnection.requestOAuthToken((success) => {
- if (success)
- this._onOAuthTokenRequested(username, password, callback);
- else
- callback(false, null);
+ if (success) {
+ this._osmConnection.authorizeOAuthToken(callback);
+ } else {
+ callback(false);
+ }
});
}
- _onOAuthTokenRequested(username, password, callback) {
- /* keep track of authorizing username */
- this._username = username;
- this._osmConnection.authorizeOAuthToken(username, password, callback);
- }
-
requestOAuthAccessToken(code, callback) {
this._osmConnection.requestOAuthAccessToken(code, (success, token) => {
this._onOAuthAccessTokenRequested(success, callback);
@@ -165,14 +160,20 @@ var OSMEdit = class OSMEdit {
_onOAuthAccessTokenRequested(success, callback) {
if (success) {
- this._isSignedIn = true;
- Application.settings.set('osm-username', this._username);
+ this._osmConnection.fetchLoggedInUser((username) => {
+ this._isSignedIn = true;
+ /* if we couldn't retrieve the logged-in username,
+ * e.g. if the user de-selected the permission when
+ * authorizing the OAuth token, use a dummy placeholder
+ * username to signify that we are signed in
+ */
+ this._username = username ?? '_unknown_';
+ Application.settings.set('osm-username', this._username);
+ callback(true);
+ });
} else {
- /* clear out username if verification was unsuccessful */
- this._username = null;
+ callback(false);
}
-
- callback(success);
}
signOut() {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]