[gnome-shell] Add a helper to handle captive portal logins
- From: Giovanni Campagna <gcampagna src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell] Add a helper to handle captive portal logins
- Date: Thu, 26 Jun 2014 17:55:06 +0000 (UTC)
commit 8c67a70db0a02b67d572a46373cea30a67b8d679
Author: Giovanni Campagna <gcampagna src gnome org>
Date: Mon Feb 17 17:19:18 2014 +0100
Add a helper to handle captive portal logins
Add a small DBus-activated GtkApplication that embeds a WebKitWebView
and implements some minimal logic to see if the login succeeds.
It will try to connect to a custom NM-provided url (the portal login
page), if one exists, or to www.gnome.org in the normal case of
a portal doing redirect.
https://bugzilla.gnome.org/show_bug.cgi?id=704416
data/Makefile.am | 22 +++-
data/org.gnome.Shell.PortalHelper.desktop.in | 9 +
data/org.gnome.Shell.PortalHelper.service.in | 3 +
js/js-resources.gresource.xml | 2 +
js/portalHelper/main.js | 247 ++++++++++++++++++++++++++
src/Makefile.am | 15 ++
src/gnome-shell-portal-helper.c | 52 ++++++
7 files changed, 349 insertions(+), 1 deletions(-)
---
diff --git a/data/Makefile.am b/data/Makefile.am
index c22dd1f..1befb87 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -1,6 +1,24 @@
+CLEANFILES =
+
desktopdir=$(datadir)/applications
desktop_DATA = gnome-shell.desktop gnome-shell-wayland.desktop gnome-shell-extension-prefs.desktop
+if HAVE_NETWORKMANAGER
+desktop_DATA += org.gnome.Shell.PortalHelper.desktop
+
+servicedir = $(datadir)/dbus-1/services
+service_DATA = org.gnome.Shell.PortalHelper.service
+
+CLEANFILES += \
+ org.gnome.Shell.PortalHelper.service \
+ org.gnome.Shell.PortalHelper.desktop
+
+endif
+
+%.service: %.service.in
+ $(AM_V_GEN) sed -e "s|@libexecdir[ ]|$(libexecdir)|" \
+ $< > $@ || rm $@
+
# We substitute in bindir so it works as an autostart
# file when built in a non-system prefix
%.desktop.in:%.desktop.in.in
@@ -88,9 +106,11 @@ EXTRA_DIST = \
$(menu_DATA) \
$(convert_DATA) \
$(keys_in_files) \
+ org.gnome.Shell.PortalHelper.desktop.in \
+ org.gnome.Shell.PortalHelper.service.in \
org.gnome.shell.gschema.xml.in.in
-CLEANFILES = \
+CLEANFILES += \
gnome-shell.desktop.in \
gnome-shell-wayland.desktop.in \
gnome-shell-extension-prefs.in \
diff --git a/data/org.gnome.Shell.PortalHelper.desktop.in b/data/org.gnome.Shell.PortalHelper.desktop.in
new file mode 100644
index 0000000..c82760f
--- /dev/null
+++ b/data/org.gnome.Shell.PortalHelper.desktop.in
@@ -0,0 +1,9 @@
+[Desktop Entry]
+_Name=Captive Portal
+Type=Application
+Exec=gapplication launch org.gnome.Shell.PortalHelper
+DBusActivatable=true
+NoDisplay=true
+Icon=network-workgroup
+StartupNotify=true
+OnlyShowIn=GNOME;
\ No newline at end of file
diff --git a/data/org.gnome.Shell.PortalHelper.service.in b/data/org.gnome.Shell.PortalHelper.service.in
new file mode 100644
index 0000000..5465a32
--- /dev/null
+++ b/data/org.gnome.Shell.PortalHelper.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.gnome.Shell.PortalHelper
+Exec= libexecdir@/gnome-shell-portal-helper
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index 47bdd00..32df2dd 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -26,6 +26,8 @@
<file>perf/core.js</file>
+ <file>portalHelper/main.js</file>
+
<file>ui/altTab.js</file>
<file>ui/animation.js</file>
<file>ui/appDisplay.js</file>
diff --git a/js/portalHelper/main.js b/js/portalHelper/main.js
new file mode 100644
index 0000000..bb6a2a5
--- /dev/null
+++ b/js/portalHelper/main.js
@@ -0,0 +1,247 @@
+const Format = imports.format;
+const Gettext = imports.gettext;
+const GLib = imports.gi.GLib;
+const GObject = imports.gi.GObject;
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+const Pango = imports.gi.Pango;
+const Soup = imports.gi.Soup;
+const WebKit = imports.gi.WebKit2;
+
+const _ = Gettext.gettext;
+
+const Config = imports.misc.config;
+
+const PortalHelperResult = {
+ CANCELLED: 0,
+ COMPLETED: 1,
+ RECHECK: 2
+};
+
+const INACTIVITY_TIMEOUT = 30000; //ms
+const CONNECTIVITY_RECHECK_RATELIMIT_TIMEOUT = 30 * GLib.USEC_PER_SEC;
+
+const HelperDBusInterface = '<node> \
+<interface name="org.gnome.Shell.PortalHelper"> \
+<method name="Authenticate"> \
+ <arg type="o" direction="in" name="connection" /> \
+ <arg type="s" direction="in" name="url" /> \
+ <arg type="u" direction="in" name="timestamp" /> \
+</method> \
+<method name="Close"> \
+ <arg type="o" direction="in" name="connection" /> \
+</method> \
+<method name="Refresh"> \
+ <arg type="o" direction="in" name="connection" /> \
+</method> \
+<signal name="Done"> \
+ <arg type="o" name="connection" /> \
+ <arg type="u" name="result" /> \
+</signal> \
+</interface> \
+</node>';
+
+const PortalWindow = new Lang.Class({
+ Name: 'PortalWindow',
+ Extends: Gtk.ApplicationWindow,
+
+ _init: function(application, url, timestamp, doneCallback) {
+ this.parent({ application: application });
+
+ if (url) {
+ this._uri = new Soup.URI(uri);
+ } else {
+ url = 'http://www.gnome.org';
+ this._uri = null;
+ this._everSeenRedirect = false;
+ }
+ this._originalUrl = url;
+ this._doneCallback = doneCallback;
+ this._lastRecheck = 0;
+ this._recheckAtExit = false;
+
+ this._webView = new WebKit.WebView();
+ this._webView.connect('decide-policy', Lang.bind(this, this._onDecidePolicy));
+ this._webView.load_uri(url);
+ this._webView.connect('notify::title', Lang.bind(this, this._syncTitle));
+ this._syncTitle();
+
+ this.add(this._webView);
+ this._webView.show();
+ this.maximize();
+ this.present_with_time(timestamp);
+ },
+
+ _syncTitle: function() {
+ let title = this._webView.title;
+
+ if (title) {
+ this.title = title;
+ } else {
+ // TRANSLATORS: this is the title of the wifi captive portal login
+ // window, until we know the title of the actual login page
+ this.title = _("Web Authentication Redirect");
+ },
+
+ refresh: function() {
+ this._everSeenRedirect = false;
+ this._webView.load_uri(this._originalUrl);
+ },
+
+ vfunc_delete_event: function(event) {
+ if (this._recheckAtExit)
+ this._doneCallback(PortalHelperResult.RECHECK);
+ else
+ this._doneCallback(PortalHelperResult.CANCELLED);
+ return false;
+ },
+
+ _onDecidePolicy: function(view, decision, type) {
+ if (type == WebKit.PolicyDecisionType.NEW_WINDOW_ACTION) {
+ decision.ignore();
+ return true;
+ }
+
+ if (type != WebKit.PolicyDecisionType.NAVIGATION_ACTION)
+ return false;
+
+ let request = decision.get_request();
+ let uri = new Soup.URI(request.get_uri());
+
+ if (this._uri != null) {
+ if (!uri.host_equal(uri, this._uri)) {
+ // We *may* have finished here, but we don't know for
+ // sure. Tell gnome-shell to run another connectivity check
+ // (but ratelimit the checks, we don't want to spam
+ // gnome.org for portals that have 10 or more internal
+ // redirects - and unfortunately they exist)
+ // If we hit the rate limit, we also queue a recheck
+ // when the window is closed, just in case we miss the
+ // final check and don't realize we're connected
+ // This should not be a problem in the cancelled logic,
+ // because if the user doesn't want to start the login,
+ // we should not see any redirect at all, outside this._uri
+
+ let now = GLib.get_monotonic_time();
+ let shouldRecheck = (now - this._lastRecheck) >
+ CONNECTIVITY_RECHECK_RATELIMIT_TIMEOUT;
+
+ if (shouldRecheck) {
+ this._lastRecheck = now;
+ this._recheckAtExit = false;
+ this._doneCallback(PortalHelperResult.RECHECK);
+ } else {
+ this._recheckAtExit = true;
+ }
+ }
+
+ // Update the URI, in case of chained redirects, so we still
+ // think we're doing the login until gnome-shell kills us
+ this._uri = uri;
+ } else {
+ if (uri.get_host() == 'www.gnome.org' && this._everSeenRedirect) {
+ // Yay, we got to gnome!
+ decision.ignore();
+ this._doneCallback(PortalHelperResult.COMPLETED);
+ return true;
+ } else if (uri.get_host() != 'www.gnome.org') {
+ this._everSeenRedirect = true;
+ }
+ }
+
+ decision.use();
+ return true;
+ },
+});
+
+const WebPortalHelper = new Lang.Class({
+ Name: 'WebPortalHelper',
+ Extends: Gtk.Application,
+
+ _init: function() {
+ this.parent({ application_id: 'org.gnome.Shell.PortalHelper',
+ flags: Gio.ApplicationFlags.IS_SERVICE,
+ inactivity_timeout: 30000 });
+
+ this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(HelperDBusInterface, this);
+ this._queue = [];
+ },
+
+ vfunc_dbus_register: function(connection, path) {
+ this._dbusImpl.export(connection, path);
+ this.parent(connection, path);
+ return true;
+ },
+
+ vfunc_dbus_unregister: function(connection, path) {
+ this._dbusImpl.unexport_from_connection(connection);
+ this.parent(connection, path);
+ },
+
+ vfunc_activate: function() {
+ // If launched manually (for example for testing), force a dummy authentication
+ // session with the default url
+ this.Authenticate('/org/gnome/dummy', '', 0);
+ },
+
+ Authenticate: function(connection, url, timestamp) {
+ this._queue.push({ connection: connection, url: url, timestamp: timestamp });
+
+ this._processQueue();
+ },
+
+ Close: function(connection) {
+ for (let i = 0; i < this._queue.length; i++) {
+ let obj = this._queue[i];
+
+ if (obj.connection == connection) {
+ if (obj.window)
+ obj.window.destroy();
+ this._queue.splice(i, 1);
+ break;
+ }
+ }
+
+ this._processQueue();
+ },
+
+ Refresh: function(connection) {
+ for (let i = 0; i < this._queue.length; i++) {
+ let obj = this._queue[i];
+
+ if (obj.connection == connection) {
+ if (obj.window)
+ obj.window.refresh();
+ break;
+ }
+ }
+ },
+
+ _processQueue: function() {
+ if (this._queue.length == 0)
+ return;
+
+ let top = this._queue[0];
+ if (top.window != null)
+ return;
+
+ top.window = new PortalWindow(this, top.uri, top.timestamp, Lang.bind(this, function(result) {
+ this._dbusImpl.emit_signal('Done', new GLib.Variant('(ou)', [top.connection, result]));
+ }));
+ },
+});
+
+function initEnvironment() {
+ String.prototype.format = Format.format;
+}
+
+function main(argv) {
+ initEnvironment();
+
+ Gettext.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
+ Gettext.textdomain(Config.GETTEXT_PACKAGE);
+
+ let app = new WebPortalHelper();
+ return app.run(argv);
+}
diff --git a/src/Makefile.am b/src/Makefile.am
index 124b3de..52ddbfd 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -199,6 +199,21 @@ nodist_gnome_shell_extension_prefs_SOURCES = \
gnome_shell_extension_prefs_CPPFLAGS = $(gnome_shell_cflags)
gnome_shell_extension_prefs_LDADD = libgnome-shell-js.la $(GNOME_SHELL_LIBS)
+if HAVE_NETWORKMANAGER
+
+libexec_PROGRAMS += gnome-shell-portal-helper
+gnome_shell_portal_helper_SOURCES = \
+ gnome-shell-portal-helper.c \
+ $(NULL)
+nodist_gnome_shell_portal_helper_SOURCES = \
+ $(top_builddir)/js/js-resources.c \
+ $(top_builddir)/js/js-resources.h \
+ $(NULL)
+gnome_shell_portal_helper_CPPFLAGS = $(gnome_shell_cflags)
+gnome_shell_portal_helper_LDADD = libgnome-shell-js.la $(GNOME_SHELL_LIBS)
+
+endif
+
########################################
libgnome_shell_js_la_SOURCES = \
diff --git a/src/gnome-shell-portal-helper.c b/src/gnome-shell-portal-helper.c
new file mode 100644
index 0000000..4087f87
--- /dev/null
+++ b/src/gnome-shell-portal-helper.c
@@ -0,0 +1,52 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include <girepository.h>
+#include <gjs/gjs.h>
+#include <glib/gi18n.h>
+
+int
+main (int argc, char *argv[])
+{
+ const char *search_path[] = { "resource:///org/gnome/shell", NULL };
+ GError *error = NULL;
+ GjsContext *context;
+ int status;
+
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ g_irepository_prepend_search_path (GNOME_SHELL_PKGLIBDIR);
+
+ context = g_object_new (GJS_TYPE_CONTEXT,
+ "search-path", search_path,
+ NULL);
+
+ if (!gjs_context_define_string_array(context, "ARGV",
+ argc, (const char**)argv,
+ &error))
+ {
+ g_message("Failed to define ARGV: %s", error->message);
+ g_error_free (error);
+
+ return 1;
+ }
+
+
+ if (!gjs_context_eval (context,
+ "const Main = imports.portalHelper.main; Main.main(ARGV);",
+ -1,
+ "<main>",
+ &status,
+ &error))
+ {
+ g_message ("Execution of main.js threw exception: %s", error->message);
+ g_error_free (error);
+
+ return status;
+ }
+
+ return 0;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]