From 34fc4547647bceff56a0e7a853e14a2ea454c69c Mon Sep 17 00:00:00 2001 From: "Zeeshan Ali (Khattak)" Date: Mon, 15 Feb 2016 19:45:38 +0000 Subject: [PATCH] location: Add AppAuthorizer class This class will be responsible for authorizing applications that try to access location information. Since this is mainly targetted for xdg-app applications, we make use of xdg-app's D-Bus API to store per-application authorization. https://bugzilla.gnome.org/show_bug.cgi?id=762119 --- js/ui/status/location.js | 159 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/js/ui/status/location.js b/js/ui/status/location.js index 2dad0b836..ff3943201 100644 --- a/js/ui/status/location.js +++ b/js/ui/status/location.js @@ -17,6 +17,9 @@ const LOCATION_SCHEMA = 'org.gnome.system.location'; const MAX_ACCURACY_LEVEL = 'max-accuracy-level'; const ENABLED = 'enabled'; +const APP_PERMISSIONS_TABLE = 'desktop'; +const APP_PERMISSIONS_ID = 'geolocation'; + const GeoclueAccuracyLevel = { NONE: 0, COUNTRY: 1, @@ -26,6 +29,15 @@ const GeoclueAccuracyLevel = { EXACT: 8 }; +function accuracyLevelToString(accuracyLevel) { + for (let key in GeoclueAccuracyLevel) { + if (GeoclueAccuracyLevel[key] == accuracyLevel) + return key; + } + + return 'NONE'; +} + var GeoclueIface = ' \ \ \ @@ -50,6 +62,24 @@ var AgentIface = ' \ \ '; +var XdgAppIface = ' \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +'; + const Indicator = new Lang.Class({ Name: 'LocationIndicator', Extends: PanelMenu.SystemIndicator, @@ -222,6 +252,135 @@ function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } +const AppAuthorizer = new Lang.Class({ + Name: 'LocationAppAuthorizer', + + _init: function(desktopId, + reqAccuracyLevel, + permStoreProxy, + maxAccuracyLevel) { + this.desktopId = desktopId; + this.reqAccuracyLevel = reqAccuracyLevel; + this._permStoreProxy = permStoreProxy; + this._maxAccuracyLevel = maxAccuracyLevel; + + this._accuracyLevel = GeoclueAccuracyLevel.NONE; + this._timesAllowed = 0; + }, + + authorize: function(onAuthDone) { + this._onAuthDone = onAuthDone; + + let appSystem = Shell.AppSystem.get_default(); + this._app = appSystem.lookup_app(this.desktopId + ".desktop"); + if (this._app == null || this._permStoreProxy == null) { + this._completeAuth(); + + return; + } + + this._permStoreProxy.LookupRemote(APP_PERMISSIONS_TABLE, + APP_PERMISSIONS_ID, + Lang.bind(this, + this._onPermLookupDone)); + }, + + _onPermLookupDone: function(result, error) { + if (error != null) { + if (error.domain == Gio.DBusError) { + // Likely no xdg-app installed, just authorize the app + this._accuracyLevel = this.reqAccuracyLevel; + this._permStoreProxy = null; + this._completeAuth(); + } else { + // Currently xdg-app throws an error if we lookup for + // unknown ID (which would be the case first time this code + // runs) so we continue with user authorization as normal + // and ID is added to the store if user says "yes". + log(error.message); + this._permissions = {}; + this._userAuthorizeApp(); + } + + return; + } + + [this._permissions] = result; + let permission = this._permissions[this.desktopId]; + + let [levelStr, timeStr] = permission || ['NONE', '0']; + this._accuracyLevel = GeoclueAccuracyLevel[levelStr] || + GeoclueAccuracyLevel.NONE; + this._timesAllowed = Number(timeStr) || 0; + + if (this._timesAllowed < 3) + this._userAuthorizeApp(); + else + this._completeAuth(); + }, + + _userAuthorizeApp: function() { + let name = this._app.get_name(); + let appInfo = this._app.get_app_info(); + let reason = appInfo.get_string("X-Geoclue-Reason"); + + this._showAppAuthDialog(name, reason); + }, + + _showAppAuthDialog: function(name, reason) { + this._dialog = new GeolocationDialog(name, + reason, + this.reqAccuracyLevel); + + let responseId = this._dialog.connect('response', Lang.bind(this, + function(dialog, level) { + this._dialog.disconnect(responseId); + this._accuracyLevel = level; + this._completeAuth(); + })); + + this._dialog.open(); + }, + + _completeAuth: function() { + if (this._accuracyLevel != GeoclueAccuracyLevel.NONE) { + this._accuracyLevel = clamp(this._accuracyLevel, + 0, + this._maxAccuracyLevel); + this._timesAllowed++; + } + this._saveToPermissionStore(); + + this._onAuthDone(this._accuracyLevel); + }, + + _saveToPermissionStore: function() { + if (this._permStoreProxy == null) + return; + + if (this._accuracyLevel != GeoclueAccuracyLevel.NONE) { + let levelStr = accuracyLevelToString(this._accuracyLevel); + let dateStr = Math.round(Date.now() / 1000).toString(); + this._permissions[this.desktopId] = [levelStr, + this._timesAllowed.toString(), + dateStr]; + } else { + delete this._permissions[this.desktopId]; + } + let data = GLib.Variant.new('av', {}); + + this._permStoreProxy.SetRemote(APP_PERMISSIONS_TABLE, + true, + APP_PERMISSIONS_ID, + this._permissions, + data, + function (result, error) { + if (error != null) + log(error.message); + }); + }, +}); + const GeolocationDialog = new Lang.Class({ Name: 'GeolocationDialog', Extends: ModalDialog.ModalDialog,