[gnome-maps/wip/mlundblad/osm-oauth2] Migrate to OAuth2 for OSM editing




commit ca2535f071e333d99b30ecfd01b1c22e546e264d
Author: Marcus Lundblad <ml dfupdate se>
Date:   Sun Aug 21 00:57:33 2022 +0200

    Migrate to OAuth2 for OSM editing
    
    librest has dropped OAuth 1 support.
    
    And also, there are flaws of the OAuth 1
    protocol and it has been recommended to
    migrate to OAuth 2 for some time now.
    
    Switch from using OAuth 1.1a to OAuth 2.0
    for authenticating for OSM editing.
    
    Since there's a new token, users will have to
    re-authorize with their OSM account before editing.
    The gsettings key for identifying a "logged in"
    account has been changed to "osm-username-oauth2".

 data/org.gnome.Maps.gschema.xml |   2 +-
 data/ui/osm-account-dialog.ui   |  17 +------
 lib/maps-osm-oauth-proxy-call.c |   6 +--
 lib/maps-osm-oauth-proxy-call.h |  10 ++--
 org.gnome.Maps.json             |   6 +--
 src/osmAccountDialog.js         |  21 ++------
 src/osmConnection.js            | 110 +++++++++++++++++-----------------------
 src/osmEdit.js                  |  16 ++----
 8 files changed, 69 insertions(+), 119 deletions(-)
---
diff --git a/data/org.gnome.Maps.gschema.xml b/data/org.gnome.Maps.gschema.xml
index 2ed4ad61..ea36bc2a 100644
--- a/data/org.gnome.Maps.gschema.xml
+++ b/data/org.gnome.Maps.gschema.xml
@@ -51,7 +51,7 @@
       <summary>Number of recent routes to store</summary>
       <description>Number of recently visited routes to store.</description>
     </key>
-    <key name="osm-username" type="s">
+    <key name="osm-username-oauth2" type="s">
       <default>""</default>
       <summary>OpenStreetMap username or e-mail address</summary>
       <description>Indicates if the user has signed in to edit OpenStreetMap data.</description>
diff --git a/data/ui/osm-account-dialog.ui b/data/ui/osm-account-dialog.ui
index 31d636ee..46ee8787 100644
--- a/data/ui/osm-account-dialog.ui
+++ b/data/ui/osm-account-dialog.ui
@@ -62,19 +62,6 @@ Then fill in the obtained verification code here in the next step.</property>
                         </layout>
                       </object>
                     </child>
-                    <child>
-                      <object class="GtkSpinner" id="signInSpinner">
-                        <property name="height_request">16</property>
-                        <property name="width_request">16</property>
-                        <property name="spinning">True</property>
-                        <property name="halign">end</property>
-                        <property name="hexpand">1</property>
-                        <layout>
-                          <property name="column">0</property>
-                          <property name="row">3</property>
-                        </layout>
-                      </object>
-                    </child>
                     <child>
                       <object class="GtkLinkButton">
                         <property name="focusable">1</property>
@@ -83,7 +70,7 @@ Then fill in the obtained verification code here in the next step.</property>
                         <property name="halign">end</property>
                         <property name="hexpand">1</property>
                         <layout>
-                          <property name="column">1</property>
+                          <property name="column">0</property>
                           <property name="row">3</property>
                         </layout>
                       </object>
@@ -96,7 +83,7 @@ Then fill in the obtained verification code here in the next step.</property>
                           <class name="suggested-action"/>
                         </style>
                         <layout>
-                          <property name="column">2</property>
+                          <property name="column">1</property>
                           <property name="row">3</property>
                         </layout>
                       </object>
diff --git a/lib/maps-osm-oauth-proxy-call.c b/lib/maps-osm-oauth-proxy-call.c
index 5e9e7544..47359c60 100644
--- a/lib/maps-osm-oauth-proxy-call.c
+++ b/lib/maps-osm-oauth-proxy-call.c
@@ -33,7 +33,7 @@ struct _MapsOSMOAuthProxyCallPrivate
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE(MapsOSMOAuthProxyCall, maps_osm_oauth_proxy_call,
-                           OAUTH_TYPE_PROXY_CALL);
+                           REST_TYPE_OAUTH2_PROXY_CALL);
 
 static gboolean
 maps_osm_oauth_proxy_call_serialize_params (RestProxyCall *call,
@@ -85,9 +85,9 @@ maps_osm_oauth_proxy_call_init (MapsOSMOAuthProxyCall *call)
 }
 
 MapsOSMOAuthProxyCall *
-maps_osm_oauth_proxy_call_new (OAuthProxy *proxy, const char *payload)
+maps_osm_oauth_proxy_call_new (RestOAuth2Proxy *proxy, const char *payload)
 {
-  g_return_val_if_fail (OAUTH_IS_PROXY (proxy), NULL);
+  g_return_val_if_fail (REST_IS_OAUTH2_PROXY (proxy), NULL);
   g_return_val_if_fail (payload != NULL, NULL);
 
   MapsOSMOAuthProxyCall *call =
diff --git a/lib/maps-osm-oauth-proxy-call.h b/lib/maps-osm-oauth-proxy-call.h
index e9c240d8..03c6a363 100644
--- a/lib/maps-osm-oauth-proxy-call.h
+++ b/lib/maps-osm-oauth-proxy-call.h
@@ -22,8 +22,8 @@
 
 #include <glib-object.h>
 
-#include <rest/oauth-proxy-call.h>
-#include <rest/oauth-proxy.h>
+#include <rest/rest-oauth2-proxy-call.h>
+#include <rest/rest-oauth2-proxy.h>
 
 G_BEGIN_DECLS
 
@@ -40,17 +40,17 @@ typedef struct _MapsOSMOAuthProxyCallClass MapsOSMOAuthProxyCallClass;
 
 struct _MapsOSMOAuthProxyCall
 {
-  OAuthProxyCall parent;
+  RestOAuth2ProxyCall parent;
   MapsOSMOAuthProxyCallPrivate *priv;
 };
 
 struct _MapsOSMOAuthProxyCallClass
 {
-  OAuthProxyCallClass parent_class;
+  RestOAuth2ProxyCallClass parent_class;
 };
 
 GType maps_osm_oauth_proxy_call_get_type(void);
-MapsOSMOAuthProxyCall *maps_osm_oauth_proxy_call_new (OAuthProxy *proxy,
+MapsOSMOAuthProxyCall *maps_osm_oauth_proxy_call_new (RestOAuth2Proxy *proxy,
                                                       const char *content);
 
 G_END_DECLS
diff --git a/org.gnome.Maps.json b/org.gnome.Maps.json
index 35a27a54..9558cf4e 100644
--- a/org.gnome.Maps.json
+++ b/org.gnome.Maps.json
@@ -90,9 +90,9 @@
             ],
             "sources" : [
                 {
-                    "type" : "archive",
-                    "url" : "https://gitlab.gnome.org/GNOME/librest/-/archive/1.0.0/librest-1.0.0.tar.gz";,
-                    "sha256" : "eeba5ddbf91a29decec01c3ccce64b922bd9bf52d631e307e185227295aea51d"
+                    "type" : "git",
+                    "url" : "https://gitlab.gnome.org/GNOME/librest.git";,
+                    "branch": "master"
                 }
             ]
         },
diff --git a/src/osmAccountDialog.js b/src/osmAccountDialog.js
index 378960b8..035a77d8 100644
--- a/src/osmAccountDialog.js
+++ b/src/osmAccountDialog.js
@@ -76,24 +76,10 @@ export class OSMAccountDialog extends Gtk.Dialog {
     }
 
     _performSignIn() {
-        // turn on signing in spinner
-        this._signInSpinner.visible = true;
-        this._signInButton.sensitive = false;
+        // switch to the verification view
+        this._stack.visible_child_name = 'verify';
 
-        Application.osmEdit.performOAuthSignIn(this._onOAuthTokenAuthorized.bind(this));
-    }
-
-    _onOAuthTokenAuthorized(success) {
-        if (success) {
-            // switch to the verification view
-            this._stack.visible_child_name = 'verify';
-        } else {
-            this._errorLabel.visible = true;
-            this._errorLabel.label = _("Failed to authorize access");
-            this._signInButton.label = _("Try again");
-        }
-
-        this._signInSpinner.visible = false;
+        Application.osmEdit.performOAuthSignIn();
     }
 
     _onVerifyButtonClicked() {
@@ -169,7 +155,6 @@ GObject.registerClass({
     Template: 'resource:///org/gnome/Maps/ui/osm-account-dialog.ui',
     InternalChildren: ['stack',
                        'signInButton',
-                       'signInSpinner',
                        'verificationEntry',
                        'verifyButton',
                        'errorLabel',
diff --git a/src/osmConnection.js b/src/osmConnection.js
index 5a18f350..4ab1f924 100644
--- a/src/osmConnection.js
+++ b/src/osmConnection.js
@@ -38,10 +38,11 @@ const BASE_URL = 'https://api.openstreetmap.org/api';
 const API_VERSION = '0.6';
 
 /* OAuth constants */
-const CONSUMER_KEY = '2lbpDoED0ZspGssTBAJ8zOCtrtmUoX4KnmZUIWIK';
-const CONSUMER_SECRET = 'AO9BhDl9sJ33DjaZgQmYcNIuM3ZSml4xtugai6gE';
-const OAUTH_ENDPOINT_URL = 'https://www.openstreetmap.org/oauth';
-const LOGIN_URL = 'https://www.openstreetmap.org/login';
+const CLIENT_KEY = 'ATOMKAKOXQuAJXpFxkm__nDVRlJLYYmP-0P54UfDnZI';
+const CLIENT_SECRET = '9vda-8M_lt0cLJMLJlbTfJVRDGiS2-pPbeSNMRqBQ0k';
+const AUTH_URL = 'https://www.openstreetmap.org/oauth2/authorize';
+const ACCESS_TOKEN_URL = 'https://www.openstreetmap.org/oauth2/token';
+const REDIRECT_URL = 'urn:ietf:wg:oauth:2.0:oob';
 
 const SECRET_SCHEMA = new Secret.Schema("org.gnome.Maps",
     Secret.SchemaFlags.NONE,
@@ -55,9 +56,10 @@ export class OSMConnection {
         this._session = new Soup.Session({ user_agent : 'gnome-maps/' + pkg.version });
 
         /* OAuth proxy used for making OSM uploads */
-        this._callProxy = Rest.OAuthProxy.new(CONSUMER_KEY, CONSUMER_SECRET,
-                                              BASE_URL + '/' + API_VERSION,
-                                              false);
+        this._callProxy = Rest.OAuth2Proxy.new(AUTH_URL, ACCESS_TOKEN_URL,
+                                               REDIRECT_URL,
+                                               CLIENT_KEY, CLIENT_SECRET,
+                                               BASE_URL + '/' + API_VERSION);
         GnomeMaps.osm_init();
     }
 
@@ -93,7 +95,7 @@ export class OSMConnection {
            OAuth access token enrolled, so, if the currently instantiated
            proxy instance doesn't have a token set, we could safely count on
            it being present in the keyring */
-        if (this._callProxy.get_token() === null) {
+        if (this._callProxy.get_access_token() === null) {
             Secret.password_lookup(SECRET_SCHEMA, {}, null, (s, res) => {
                 this._onPasswordLookedUp(res,
                                          comment,
@@ -108,25 +110,29 @@ export class OSMConnection {
         let password = Secret.password_lookup_finish(result);
 
         if (password) {
-            let token = password.split(':')[0];
-            let secret = password.split(':')[1];
-
-            this._callProxy.token = token;
-            this._callProxy.token_secret = secret;
+            this._callProxy.access_token = password;
             this._doOpenChangeset(comment, callback);
         } else {
             callback(false, null, null);
         }
     }
 
+    _createCallWithPayload(payload, method, func) {
+        let call = GnomeMaps.OSMOAuthProxyCall.new(this._callProxy, payload);
+
+        call.set_method(method);
+        call.set_function(func);
+        call.add_header('Authorization',
+                        'Bearer ' + this._callProxy.access_token);
+
+        return call;
+    }
+
     _doOpenChangeset(comment, callback) {
         let changeset =
             GnomeMaps.OSMChangeset.new(comment, 'gnome-maps ' + pkg.version);
         let xml = changeset.serialize();
-
-        let call = GnomeMaps.OSMOAuthProxyCall.new(this._callProxy, xml);
-        call.set_method('PUT');
-        call.set_function('/changeset/create');
+        let call = this._createCallWithPayload(xml, 'PUT', '/changeset/create');
 
         call.invoke_async(null, (call, res, userdata) =>
                                 { this._onChangesetOpened(call, callback); });
@@ -146,10 +152,10 @@ export class OSMConnection {
         object.changeset = changeset;
 
         let xml = object.serialize();
-        let call = GnomeMaps.OSMOAuthProxyCall.new(this._callProxy, xml);
-
-        call.set_method('PUT');
-        call.set_function(this._getCreateOrUpdateFunction(object, type));
+        let call =
+            this._createCallWithPayload(xml, 'PUT',
+                                        this._getCreateOrUpdateFunction(object,
+                                                                        type));
 
         call.invoke_async(null, (call, res, userdata) =>
                                 { this._onObjectUploaded(call, callback); });
@@ -168,10 +174,9 @@ export class OSMConnection {
         object.changeset = changeset;
 
         let xml = object.serialize();
-        let call = GnomeMaps.OSMOAuthProxyCall.new(this._callProxy, xml);
-
-        call.set_method('DELETE');
-        call.set_function(this._getDeleteFunction(object, type));
+        let call =
+            this._createCallWithPayload(xml, 'DELETE',
+                                        this._getDeleteFunction(object, type));
 
         call.invoke_async(null, (call, res, userdata) =>
                                 { this._onObjectDeleted(call, callback); });
@@ -219,46 +224,28 @@ export class OSMConnection {
         return type + '/' + id;
     }
 
-    requestOAuthToken(callback) {
-        /* OAuth proxy used for enrolling access tokens */
-        this._oauthProxy = Rest.OAuthProxy.new(CONSUMER_KEY, CONSUMER_SECRET,
-                                               OAUTH_ENDPOINT_URL, false);
-        this._oauthProxy.request_token_async('request_token', 'oob', (p, error, w, u) => {
-            this._onRequestOAuthToken(error, callback);
-        }, this._oauthProxy);
-    }
-
-    _onRequestOAuthToken(error, callback) {
-        if (error) {
-            Utils.debug(error);
-            callback(false);
-            return;
-        }
-
-        this._oauthToken = this._oauthProxy.get_token();
-        this._oauthTokenSecret = this._oauthProxy.get_token_secret();
-        callback(true);
-    }
-
-    authorizeOAuthToken(callback) {
-        let auth = '/authorize?oauth_token=';
-        let authorizeUrl = OAUTH_ENDPOINT_URL + auth + this._oauthToken;
+    authorizeOAuthToken() {
+        this._codeChallenge = Rest.PkceCodeChallenge.new_random();
+        let [authorizeUrl, state] =
+            this._callProxy.build_authorization_url(this._codeChallenge.get_challenge(),
+                                                    'read_prefs write_api');
 
         Utils.debug('Trying to open: ' + authorizeUrl);
 
         try {
             Gio.AppInfo.launch_default_for_uri(authorizeUrl, null);
-            callback(true);
         } catch (e) {
             Utils.debug('error: ' + e.message);
-            callback(false);
         }
     }
 
     requestOAuthAccessToken(code, callback) {
-        this._oauthProxy.access_token_async('access_token', code, (p, error, w, data) => {
-            this._onAccessOAuthToken(error, callback);
-        }, this._oauthProxy);
+        this._callProxy.fetch_access_token_async(code,
+                                                 this._codeChallenge.get_verifier(),
+                                                 null,
+                                                 (source, res) => {
+           this._onAccessOAuthToken(res, callback);
+        });
     }
 
     fetchLoggedInUser(callback) {
@@ -292,21 +279,18 @@ export class OSMConnection {
         }
     }
 
-    _onAccessOAuthToken(error, callback) {
-        if (error) {
+    _onAccessOAuthToken(res, callback) {
+        let success = this._callProxy.fetch_access_token_finish(res);
+        if (!success) {
             callback(false);
             return;
         }
 
-        let token = this._oauthProxy.token;
-        let secret = this._oauthProxy.token_secret;
+        let token = this._callProxy.access_token;
 
-        this._callProxy.token = token;
-        this._callProxy.token_secret = secret;
         Secret.password_store(SECRET_SCHEMA, {}, Secret.COLLECTION_DEFAULT,
-                              "OSM OAuth access token and secret",
-                              this._oauthProxy.token + ":" +
-                              this._oauthProxy.token_secret, null,
+                              "OSM OAuth access token",
+                              token, null,
                               (source, result, userData) => {
                                 this._onPasswordStored(result, callback);
                               });
diff --git a/src/osmEdit.js b/src/osmEdit.js
index 16f41741..e4fa02d6 100644
--- a/src/osmEdit.js
+++ b/src/osmEdit.js
@@ -34,7 +34,7 @@ export class OSMEdit {
     constructor() {
         this._osmConnection = new OSMConnection();
         this._osmObject = null; // currently edited object
-        this._username = Application.settings.get('osm-username');
+        this._username = Application.settings.get('osm-username-oauth2');
         this._isSignedIn = this._username !== null && this._username.length > 0;
     }
 
@@ -138,14 +138,8 @@ export class OSMEdit {
         this._osmConnection.closeChangeset(changesetId, callback);
     }
 
-    performOAuthSignIn(callback) {
-        this._osmConnection.requestOAuthToken((success) => {
-            if (success) {
-                this._osmConnection.authorizeOAuthToken(callback);
-            } else {
-                callback(false);
-            }
-        });
+    performOAuthSignIn() {
+        this._osmConnection.authorizeOAuthToken();
     }
 
     requestOAuthAccessToken(code, callback) {
@@ -164,7 +158,7 @@ export class OSMEdit {
                  * username to signify that we are signed in
                  */
                 this._username = username ?? '_unknown_';
-                Application.settings.set('osm-username', this._username);
+                Application.settings.set('osm-username-oauth2', this._username);
                 callback(true);
             });
         } else {
@@ -176,7 +170,7 @@ export class OSMEdit {
         this._username = null;
         this._isSignedIn = false;
 
-        Application.settings.set('osm-username', '');
+        Application.settings.set('osm-username-oauth2', '');
         this._osmConnection.signOut();
     }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]