From: Andrea Azzarone <azzaronea@gmail.com>
Date: Mon, 3 Sep 2018 10:45:50 +0200
Subject: Implement an Ubuntu Single Sign-On provider. This talks with the
 on-system snapd in order to authenticate the user. At the moment the
 credentials can be used to install and remove snaps from gnome-software.

https://bugzilla.gnome.org/show_bug.cgi?id=793755
---
 configure.ac                                       |  17 +
 data/icons/scalable/Makefile.am                    |   1 +
 data/icons/scalable/goa-account-ubuntusso.svg      | 109 +++
 data/icons/symbolic/Makefile.am                    |   1 +
 .../symbolic/goa-account-ubuntusso-symbolic.svg    |  31 +
 po/POTFILES.in                                     |   1 +
 src/goabackend/Makefile.am                         |   3 +
 src/goabackend/goaprovider.c                       |   4 +
 src/goabackend/goaubuntussoprovider.c              | 943 +++++++++++++++++++++
 src/goabackend/goaubuntussoprovider.h              |  39 +
 10 files changed, 1149 insertions(+)
 create mode 100644 data/icons/scalable/goa-account-ubuntusso.svg
 create mode 100644 data/icons/symbolic/goa-account-ubuntusso-symbolic.svg
 create mode 100644 src/goabackend/goaubuntussoprovider.c
 create mode 100644 src/goabackend/goaubuntussoprovider.h

Index: gnome-online-accounts-3.35.90/configure.ac
===================================================================
--- gnome-online-accounts-3.35.90.orig/configure.ac
+++ gnome-online-accounts-3.35.90/configure.ac
@@ -140,6 +140,12 @@ if test "$enable_backend" != "no"; then
   PKG_CHECK_MODULES(LIBXML, [libxml-2.0])
   AC_SUBST(LIBXML_CFLAGS)
   AC_SUBST(LIBXML_LIBS)
+
+  if test "$enable_ubuntu_sso" != "no"; then
+    PKG_CHECK_MODULES(SNAPD_GLIB, [snapd-glib >= 1.43])
+    AC_SUBST(SNAPD_GLIB_CFLAGS)
+    AC_SUBST(SNAPD_GLIB_LIBS)
+  fi
 fi
 
 AC_ARG_ENABLE([inspector],
@@ -343,6 +349,16 @@ if test "$enable_windows_live" != "no";
   AC_DEFINE(GOA_WINDOWS_LIVE_ENABLED, 1, [Enable Windows Live data provider])
 fi
 
+# Ubuntu SSO
+AC_DEFINE(GOA_UBUNTU_SSO_NAME, ["ubuntusso"], [ProviderType and extension point name])
+AC_ARG_ENABLE([ubuntu-sso],
+              [AS_HELP_STRING([--enable-ubuntu-sso], [Enable Ubuntu SSO provider])],
+              [],
+              [enable_ubuntu_sso=no])
+if test "$enable_ubuntu_sso" != "no"; then
+  AC_DEFINE(GOA_UBUNTU_SSO_ENABLED, 1, [Enable Ubuntu SSO data provider])
+fi
+
 # Kerberos
 AC_DEFINE(GOA_KERBEROS_NAME, ["kerberos"], [ProviderType and extension point name])
 AC_ARG_ENABLE([kerberos],
@@ -577,6 +593,7 @@ echo "
 	Facebook provider:              ${enable_facebook} (OAuth 2.0, id:${with_facebook_client_id})
 	Windows Live provider:          ${enable_windows_live} (OAuth 2.0, id:${with_windows_live_client_id})
 	Last.fm provider:               ${enable_lastfm} (id:${with_lastfm_client_id} secret:${with_lastfm_client_secret})
+        Ubuntu SSO provider:            ${enable_ubuntu_sso}
 
 	Maintainer mode:                ${USE_MAINTAINER_MODE}
 	Building api docs:              ${enable_gtk_doc}
Index: gnome-online-accounts-3.35.90/data/icons/scalable/Makefile.am
===================================================================
--- gnome-online-accounts-3.35.90.orig/data/icons/scalable/Makefile.am
+++ gnome-online-accounts-3.35.90/data/icons/scalable/Makefile.am
@@ -12,6 +12,7 @@ icon_DATA =				\
 	goa-account-msn.svg		\
 	goa-account-owncloud.svg	\
 	goa-account-foursquare.svg	\
+	goa-account-ubuntusso.svg	\
 	$(NULL)
 
 EXTRA_DIST = \
Index: gnome-online-accounts-3.35.90/data/icons/scalable/goa-account-ubuntusso.svg
===================================================================
--- /dev/null
+++ gnome-online-accounts-3.35.90/data/icons/scalable/goa-account-ubuntusso.svg
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   viewBox="0 0 128 128"
+   style="display:inline;enable-background:new"
+   version="1.0"
+   id="svg11300"
+   height="128"
+   width="128">
+  <title
+     id="title4162">Ubuntu One Icon</title>
+  <defs
+     id="defs3" />
+  <metadata
+     id="metadata4">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:creator>
+          <cc:Agent>
+            <dc:title>Andrea Azzarone</dc:title>
+          </cc:Agent>
+        </dc:creator>
+        <dc:source />
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+        <dc:title>Ubuntu One Icon</dc:title>
+        <dc:subject>
+          <rdf:Bag />
+        </dc:subject>
+        <dc:date />
+        <dc:rights>
+          <cc:Agent>
+            <dc:title />
+          </cc:Agent>
+        </dc:rights>
+        <dc:publisher>
+          <cc:Agent>
+            <dc:title />
+          </cc:Agent>
+        </dc:publisher>
+        <dc:identifier />
+        <dc:relation />
+        <dc:language />
+        <dc:coverage />
+        <dc:description />
+        <dc:contributor>
+          <cc:Agent>
+            <dc:title />
+          </cc:Agent>
+        </dc:contributor>
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <g
+     transform="translate(0,-172)"
+     style="display:inline"
+     id="layer1">
+    <g
+       style="display:inline"
+       id="layer9">
+      <g
+         id="g3900"
+         style="display:inline;enable-background:new"
+         transform="translate(-3e-5,-159.99988)">
+        <g
+           transform="translate(368.00003,159.99976)"
+           id="g1747"
+           style="display:inline;enable-background:new">
+          <rect
+             style="display:inline;opacity:1;vector-effect:none;fill:#E95420;fill-opacity:1;stroke:none;stroke-width:0.24999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;enable-background:new"
+             id="rect1745"
+             width="104"
+             height="104.00024"
+             x="-356"
+             y="184"
+             rx="7.9999986"
+             ry="8" />
+        </g>
+        <path
+           style="fill:#ffffff;fill-opacity:1;stroke-width:0.33180749"
+           d="m 46.310338,361.96552 c -3.251491,0.0493 -6.606439,0.0979 -7.455439,0.10827 l -1.543638,0.0184 -0.002,4.45838 c -0.0052,11.65145 0.279497,41.15819 0.407257,42.20373 0.143526,1.17358 0.180967,1.3707 0.647672,3.4192 1.294356,5.68091 5.519652,11.60519 10.048093,14.08914 3.61014,1.98012 6.489188,2.99214 10.327918,3.63039 3.739544,0.62149 14.27011,0.0721 20.873269,-1.08912 0.614168,-0.10827 1.67829,-0.29197 2.364717,-0.40896 2.88726,-0.49109 4.901867,-0.891 7.159848,-1.42132 0.289024,-0.0677 0.75671,-0.16157 1.039165,-0.20816 l 0.513669,-0.0848 -0.09361,-32.25719 c -0.07189,-24.81606 -0.130059,-32.28066 -0.25092,-32.35977 -0.08639,-0.0563 -4.029547,-0.0882 -8.762273,-0.0707 l -8.60496,0.0322 -0.03319,25.17575 -0.03319,25.17574 -8.177665,-0.0345 -8.177667,-0.0345 -0.06569,-16.62672 -0.06569,-16.62673 -3.493875,-0.0671 -3.494203,-0.0671 1.772227,-3.68738 c 0.97479,-2.02806 2.798253,-5.78717 4.05254,-8.35325 1.253959,-2.56608 2.279985,-4.75337 2.279985,-4.86064 0,-0.21755 -0.31858,-0.21889 -11.232424,-0.0533"
+           id="path4060-7" />
+      </g>
+    </g>
+  </g>
+</svg>
Index: gnome-online-accounts-3.35.90/data/icons/symbolic/Makefile.am
===================================================================
--- gnome-online-accounts-3.35.90.orig/data/icons/symbolic/Makefile.am
+++ gnome-online-accounts-3.35.90/data/icons/symbolic/Makefile.am
@@ -11,6 +11,7 @@ icon_DATA =					\
 	goa-account-msn-symbolic.svg		\
 	goa-account-owncloud-symbolic.svg	\
 	goa-account-foursquare-symbolic.svg	\
+	goa-account-ubuntusso-symbolic.svg	\
 	$(NULL)
 
 EXTRA_DIST = \
Index: gnome-online-accounts-3.35.90/data/icons/symbolic/goa-account-ubuntusso-symbolic.svg
===================================================================
--- /dev/null
+++ gnome-online-accounts-3.35.90/data/icons/symbolic/goa-account-ubuntusso-symbolic.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   width="16"
+   height="16"
+   viewBox="0 0 16 16"
+   version="1.1"
+   id="svg4043">
+  <defs
+     id="defs4037" />
+  <metadata
+     id="metadata4040">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <path
+     id="path4060-7"
+     d="m 5.0932841,2.4075749 c -0.5342753,0.00813 -1.0855491,0.016086 -1.2250546,0.017792 l -0.2536459,0.00301 -3.279e-4,0.7325869 c -8.547e-4,1.9145293 0.045926,6.762984 0.066917,6.9347842 0.023581,0.192838 0.029733,0.225229 0.1064234,0.561832 0.2126841,0.933469 0.9069714,1.906928 1.6510707,2.315083 0.5932069,0.325367 1.0662826,0.491659 1.6970502,0.596534 0.61447,0.102122 2.3448198,0.01184 3.42983,-0.17896 0.100919,-0.01779 0.275772,-0.04798 0.388564,-0.0672 0.474425,-0.0807 0.805458,-0.146406 1.176483,-0.233546 0.04749,-0.01113 0.12434,-0.02655 0.170752,-0.03421 l 0.08441,-0.01394 -0.01538,-5.3004009 c -0.01182,-4.0776962 -0.02137,-5.3042552 -0.04123,-5.3172542 -0.01419,-0.00925 -0.662122,-0.014495 -1.439789,-0.011614 l -1.4139402,0.0053 -0.0055,4.1367996 -0.0055,4.1367984 L 8.1206869,10.68527 6.7769585,10.67957 6.7662066,7.947609 6.7554146,5.215557 6.1813124,5.2045352 5.6071554,5.1935134 5.898362,4.5876142 C 6.0585366,4.2543702 6.358162,3.6366846 6.5642631,3.2150348 6.7703095,2.7933849 6.9389029,2.4339759 6.9389029,2.4163499 c 0,-0.035743 -0.052351,-0.035964 -1.8456763,-0.00875"
+     style="display:inline;fill:#241f31;fill-opacity:1;stroke-width:0.05452156;enable-background:new" />
+</svg>
Index: gnome-online-accounts-3.35.90/po/POTFILES.in
===================================================================
--- gnome-online-accounts-3.35.90.orig/po/POTFILES.in
+++ gnome-online-accounts-3.35.90/po/POTFILES.in
@@ -17,6 +17,7 @@ src/goabackend/goamediaserverprovider.c
 src/goabackend/goaoauth2provider.c
 src/goabackend/goaoauthprovider.c
 src/goabackend/goaowncloudprovider.c
+src/goabackend/goaubuntussoprovider.c
 src/goabackend/goaprovider.c
 src/goabackend/goasmtpauth.c
 src/goabackend/goautils.c
Index: gnome-online-accounts-3.35.90/src/goabackend/Makefile.am
===================================================================
--- gnome-online-accounts-3.35.90.orig/src/goabackend/Makefile.am
+++ gnome-online-accounts-3.35.90/src/goabackend/Makefile.am
@@ -93,6 +93,7 @@ libgoa_backend_1_0_la_SOURCES =						\
 	goafoursquareprovider.h		goafoursquareprovider.c		\
 	goawindowsliveprovider.h	goawindowsliveprovider.c	\
 	goalastfmprovider.h		goalastfmprovider.c		\
+	goaubuntussoprovider.h		goaubuntussoprovider.c		\
 	goaobjectskeletonutils.h	goaobjectskeletonutils.c	\
 	goautils.h			goautils.c			\
 	goawebview.h			goawebview.c			\
@@ -124,6 +125,7 @@ libgoa_backend_1_0_la_CFLAGS =					\
 	$(LIBXML_CFLAGS)					\
 	$(TP_CFLAGS)						\
 	$(TPAW_CFLAGS)						\
+	$(SNAPD_GLIB_CFLAGS)					\
 	$(NULL)
 
 libgoa_backend_1_0_la_LIBADD = 					\
@@ -140,6 +142,7 @@ libgoa_backend_1_0_la_LIBADD = 					\
 	$(LIBXML_LIBS)						\
 	$(TP_LIBS)						\
 	$(TPAW_LIBS)						\
+	$(SNAPD_GLIB_LIBS)					\
 	$(NULL)
 
 libgoa_backend_1_0_la_LDFLAGS = 				\
Index: gnome-online-accounts-3.35.90/src/goabackend/goaprovider.c
===================================================================
--- gnome-online-accounts-3.35.90.orig/src/goabackend/goaprovider.c
+++ gnome-online-accounts-3.35.90/src/goabackend/goaprovider.c
@@ -32,6 +32,7 @@
 #include "goawindowsliveprovider.h"
 #include "goamediaserverprovider.h"
 #include "goalastfmprovider.h"
+#include "goaubuntussoprovider.h"
 
 #ifdef GOA_FEDORA_ENABLED
 #include "goafedoraprovider.h"
@@ -935,6 +936,9 @@ static struct
    * important because it affects the order in which they are
    * returned by goa_provider_get_all.
    */
+#ifdef GOA_UBUNTU_SSO_ENABLED
+  { GOA_UBUNTU_SSO_NAME, goa_ubuntu_sso_provider_get_type },
+#endif
 #ifdef GOA_GOOGLE_ENABLED
   { GOA_GOOGLE_NAME, goa_google_provider_get_type },
 #endif
Index: gnome-online-accounts-3.35.90/src/goabackend/goaubuntussoprovider.c
===================================================================
--- /dev/null
+++ gnome-online-accounts-3.35.90/src/goabackend/goaubuntussoprovider.c
@@ -0,0 +1,943 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright © 2018 Canonical Ltd
+ *
+ * Authors: Andrea Azzarone <andrea.azzarone@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n-lib.h>
+#include <snapd-glib/snapd-glib.h>
+
+#include "goaprovider.h"
+#include "goaubuntussoprovider.h"
+#include "goaobjectskeletonutils.h"
+#include "goautils.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct _GoaUbuntuSSOProvider
+{
+  GoaProvider parent_instance;
+};
+
+G_DEFINE_TYPE_WITH_CODE (GoaUbuntuSSOProvider, goa_ubuntu_sso_provider, GOA_TYPE_PROVIDER,
+                         goa_provider_ensure_extension_points_registered ();
+                         g_io_extension_point_implement (GOA_PROVIDER_EXTENSION_POINT_NAME,
+                         g_define_type_id,
+                         GOA_UBUNTU_SSO_NAME,
+                         0));
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GCancellable *cancellable;
+
+  GtkDialog *dialog;
+  GMainLoop *loop;
+
+  GtkWidget *cluebar;
+  GtkWidget *cluebar_label;
+  GtkWidget *connect_button;
+  GtkWidget *progress_grid;
+
+  GtkWidget *stack;
+
+  GtkWidget *username_entry;
+  GtkWidget *password_entry;
+  GtkWidget *otp_entry;
+  GtkWidget *login_radio;
+  GtkWidget *register_radio;
+  GtkWidget *reset_radio;
+
+  SnapdClient *snapd_client;
+
+  gchar  *macaroon;
+  gchar **discharges;
+  gchar  *account_object_path;
+
+  GError *error;
+} AddAccountData;
+
+static void
+add_account_data_init (AddAccountData *data)
+{
+  memset (data, 0, sizeof (AddAccountData));
+  data->cancellable = g_cancellable_new ();
+  data->loop = g_main_loop_new (NULL, FALSE);
+  data->snapd_client = snapd_client_new ();
+}
+
+static void
+add_account_data_clear (AddAccountData *data)
+{
+  g_clear_object (&data->cancellable);
+  g_clear_pointer (&data->loop, (GDestroyNotify) g_main_loop_unref);
+  g_clear_object (&data->snapd_client);
+  g_free (data->macaroon);
+  g_strfreev (data->discharges);
+  g_free (data->account_object_path);
+  g_clear_error (&data->error);
+}
+
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(AddAccountData, add_account_data_clear)
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const gchar *
+get_provider_type (GoaProvider *provider)
+{
+  return GOA_UBUNTU_SSO_NAME;
+}
+
+static gchar *
+get_provider_name (GoaProvider *provider, GoaObject *object)
+{
+  return g_strdup(_("Ubuntu Single Sign-On"));
+}
+
+static GoaProviderGroup
+get_provider_group (GoaProvider *provider)
+{
+  return GOA_PROVIDER_GROUP_BRANDED;
+}
+
+static GoaProviderFeatures
+get_provider_features (GoaProvider *provider)
+{
+  return GOA_PROVIDER_FEATURE_BRANDED |
+         GOA_PROVIDER_FEATURE_TICKETING;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean on_handle_get_password (GoaPasswordBased      *interface,
+                                        GDBusMethodInvocation *invocation,
+                                        const gchar           *id,
+                                        gpointer               user_data);
+
+static gboolean
+build_object (GoaProvider         *provider,
+              GoaObjectSkeleton   *object,
+              GKeyFile            *key_file,
+              const gchar         *group,
+              GDBusConnection     *connection,
+              gboolean             just_added,
+              GError             **error)
+{
+  GoaPasswordBased *password_based = NULL;
+  gboolean ret = FALSE;
+
+  // Chain up
+  if (!GOA_PROVIDER_CLASS (goa_ubuntu_sso_provider_parent_class)->build_object (provider,
+                                                                                object,
+                                                                                key_file,
+                                                                                group,
+                                                                                connection,
+                                                                                just_added,
+                                                                                error))
+    goto out;
+
+  password_based = goa_object_get_password_based (GOA_OBJECT (object));
+  if (password_based == NULL)
+    {
+      password_based = goa_password_based_skeleton_new ();
+      // Ensure D-Bus method invocations run in their own thread
+      g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (password_based),
+                                           G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+      goa_object_skeleton_set_password_based (object, password_based);
+      g_signal_connect (password_based,
+                        "handle-get-password",
+                        G_CALLBACK (on_handle_get_password),
+                        NULL);
+    }
+
+  ret = TRUE;
+
+ out:
+  g_clear_object (&password_based);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+ensure_credentials_sync (GoaProvider   *provider,
+                         GoaObject     *object,
+                         gint          *out_expires_in,
+                         GCancellable  *cancellable,
+                         GError       **error)
+{
+  g_autofree gchar *macaroon = NULL;
+  g_autofree gchar *discharges_str = NULL;
+  g_autoptr(GVariant) discharges_var = NULL;
+  g_autofree const gchar **discharges = NULL;
+  g_autoptr(SnapdClient) snapd_client = NULL;
+  g_autoptr(SnapdAuthData) auth_data = NULL;
+
+  if (!goa_utils_get_credentials (provider, object, "macaroon", NULL, &macaroon, cancellable, error))
+    goto edit_error_and_return;
+
+  if (!goa_utils_get_credentials (provider, object, "discharges", NULL, &discharges_str, cancellable, error))
+    goto edit_error_and_return;
+
+  if (discharges_str)
+    discharges_var = g_variant_parse (G_VARIANT_TYPE ("as"), discharges_str, NULL, NULL, NULL);
+  if (discharges_var)
+    discharges = g_variant_get_strv (discharges_var, NULL);
+
+  snapd_client = snapd_client_new ();
+  auth_data = snapd_auth_data_new (macaroon, (gchar **) discharges);
+  snapd_client_set_auth_data (snapd_client, auth_data);
+  snapd_client_check_buy_sync (snapd_client, cancellable, error);
+
+  if (error != NULL)
+    {
+      if (g_error_matches (*error, SNAPD_ERROR, SNAPD_ERROR_AUTH_DATA_REQUIRED))
+        goto edit_error_and_return;
+      else
+        g_clear_error (error);
+    }
+
+  if (out_expires_in != NULL)
+    *out_expires_in = 0;
+
+  return TRUE;
+
+edit_error_and_return:
+  if (error != NULL)
+    {
+      (*error)->domain = GOA_ERROR;
+      (*error)->code = GOA_ERROR_NOT_AUTHORIZED;
+    }
+  return FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_entry (GtkWidget     *grid,
+           gint           row,
+           const gchar   *text,
+           GtkWidget    **out_entry)
+{
+  GtkWidget *label;
+  GtkWidget *entry;
+
+  label = gtk_label_new_with_mnemonic (text);
+  gtk_widget_set_halign (label, GTK_ALIGN_END);
+  gtk_widget_set_hexpand (label, FALSE);
+  gtk_grid_attach (GTK_GRID (grid), label, 0, row, 1, 1);
+
+  entry = gtk_entry_new ();
+  gtk_widget_set_hexpand (entry, TRUE);
+  gtk_widget_set_size_request (entry, 250, -1);
+  gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+  gtk_grid_attach (GTK_GRID (grid), entry, 1, row, 2, 1);
+
+  gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+
+  if (out_entry != NULL)
+    *out_entry = entry;
+}
+
+static void
+add_radio (GtkWidget      *grid,
+           gint            row,
+           const gchar    *text,
+           GtkRadioButton *group_source,
+           GtkWidget     **out_radio)
+{
+  GtkWidget *radio;
+
+  radio = gtk_radio_button_new_with_mnemonic_from_widget (group_source, text);
+  gtk_widget_set_hexpand (radio, FALSE);
+  gtk_widget_set_halign (radio, GTK_ALIGN_START);
+  gtk_grid_attach (GTK_GRID (grid), radio, 0, row, 3, 1);
+
+  if (out_radio != NULL)
+    *out_radio = radio;
+}
+
+static void
+update_widgets (AddAccountData *data)
+{
+  gboolean can_add = TRUE;
+  const gchar *visible_page;
+
+  visible_page = gtk_stack_get_visible_child_name (GTK_STACK (data->stack));
+  if (g_strcmp0 (visible_page, "login") == 0)
+    {
+      if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (data->login_radio)))
+        {
+          can_add =  gtk_entry_get_text_length (GTK_ENTRY (data->username_entry)) > 0
+                     && gtk_entry_get_text_length (GTK_ENTRY (data->password_entry)) > 0;
+          gtk_widget_set_sensitive (data->password_entry, TRUE);
+        }
+      else
+        {
+          gtk_widget_set_sensitive (data->password_entry, FALSE);
+        }
+    }
+  else if (g_strcmp0 (visible_page, "otp") == 0)
+    {
+      can_add = gtk_entry_get_text_length (GTK_ENTRY (data->otp_entry)) > 0;
+    }
+
+  gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, can_add);
+}
+
+static void
+on_username_or_password_changed (GtkEditable *editable,
+                                 gpointer user_data)
+{
+  AddAccountData *data = user_data;
+  update_widgets (data);
+}
+
+static void
+on_otp_changed (GtkEditable *editable,
+                gpointer user_data)
+{
+  AddAccountData *data = user_data;
+  update_widgets (data);
+}
+
+static void
+on_radio_button_toggled_cb (GtkToggleButton *togglebutton,
+                            gpointer         user_data)
+{
+  AddAccountData *data = user_data;
+  update_widgets (data);
+}
+
+static void
+show_progress_ui (GtkContainer *container,
+                  gboolean progress)
+{
+  g_autoptr(GList) children = gtk_container_get_children (container);
+  for (GList *l = children; l != NULL; l = l->next)
+    {
+      GtkWidget *widget = GTK_WIDGET (l->data);
+      gdouble opacity;
+
+      opacity = progress ? 1.0 : 0.0;
+      gtk_widget_set_opacity (widget, opacity);
+    }
+}
+
+static char *
+get_item (const char *buffer, const char *name)
+{
+  char *label, *start, *end, *result;
+  char end_char;
+
+  result = NULL;
+  start = NULL;
+  end = NULL;
+  label = g_strconcat (name, "=", NULL);
+  if ((start = strstr (buffer, label)) != NULL)
+    {
+      start += strlen (label);
+      end_char = '\n';
+      if (*start == '"')
+        {
+          start++;
+          end_char = '"';
+        }
+
+      end = strchr (start, end_char);
+    }
+
+    if (start != NULL && end != NULL)
+      {
+        result = g_strndup (start, end - start);
+      }
+
+  g_free (label);
+
+  return result;
+}
+
+static void
+create_account_details_ui (GoaProvider    *provider,
+                           GtkDialog      *dialog,
+                           GtkBox         *vbox,
+                           gboolean        new_account,
+                           const gchar    *existing_identity,
+                           AddAccountData *data)
+{
+  GtkWidget *grid0;
+  GtkWidget *grid1;
+  GtkWidget *grid2;
+  GtkWidget *label;
+  GtkWidget *footer_box;
+  GtkWidget *privacy_button;
+  GObject *revealer;
+  GtkWidget *spinner;
+  gint row;
+  gint width;
+  g_autofree gchar *buffer = NULL;
+  g_autofree gchar* privacy_policy = NULL;
+
+  goa_utils_set_dialog_title (provider, dialog, new_account);
+
+  grid0 = gtk_grid_new ();
+  gtk_container_set_border_width (GTK_CONTAINER (grid0), 12);
+  gtk_widget_set_margin_bottom (grid0, 0);
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (grid0), GTK_ORIENTATION_VERTICAL);
+  gtk_grid_set_row_spacing (GTK_GRID (grid0), 12);
+  gtk_container_add (GTK_CONTAINER (vbox), grid0);
+
+  data->cluebar = gtk_info_bar_new ();
+  gtk_info_bar_set_message_type (GTK_INFO_BAR (data->cluebar), GTK_MESSAGE_ERROR);
+  gtk_widget_set_hexpand (data->cluebar, TRUE);
+  gtk_widget_set_no_show_all (data->cluebar, TRUE);
+  gtk_container_add (GTK_CONTAINER (grid0), data->cluebar);
+
+  // GtkInfoBar fails to show after gtk_widget_hide has been called. Apply
+  // a workaround as suggested in https://bugzilla.gnome.org/show_bug.cgi?id=710888
+  revealer = gtk_widget_get_template_child (GTK_WIDGET (data->cluebar), GTK_TYPE_INFO_BAR, "revealer");
+  if (revealer)
+    {
+      gtk_revealer_set_transition_type (GTK_REVEALER (revealer), GTK_REVEALER_TRANSITION_TYPE_NONE);
+      gtk_revealer_set_transition_duration (GTK_REVEALER (revealer), 0);
+    }
+
+  data->cluebar_label = gtk_label_new ("");
+  gtk_widget_set_hexpand (data->cluebar_label, TRUE);
+  gtk_label_set_line_wrap (GTK_LABEL (data->cluebar_label), TRUE);
+  gtk_label_set_max_width_chars (GTK_LABEL (data->cluebar_label), 50);
+  gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (data->cluebar))),
+                     data->cluebar_label);
+
+  data->stack = gtk_stack_new ();
+  gtk_container_add (GTK_CONTAINER (grid0), data->stack);
+
+  grid1 = gtk_grid_new ();
+  gtk_grid_set_column_spacing (GTK_GRID (grid1), 12);
+  gtk_grid_set_row_spacing (GTK_GRID (grid1), 12);
+  gtk_stack_add_named (GTK_STACK (data->stack), grid1, "login");
+
+  row = 0;
+  add_entry (grid1, row++, _("_Email address:"), &data->username_entry);
+  add_radio (grid1, row++, _("I have an Ubuntu Single Sign-On account"), NULL, &data->login_radio);
+  add_entry (grid1, row++, _("_Password:"), &data->password_entry);
+  add_radio (grid1, row++, _("I want to register for an account now"), GTK_RADIO_BUTTON (data->login_radio), &data->register_radio);
+  add_radio (grid1, row++, _("I've forgotten my password"), GTK_RADIO_BUTTON (data->login_radio), &data->reset_radio);
+  gtk_entry_set_visibility (GTK_ENTRY (data->password_entry), FALSE);
+
+  if (new_account)
+    {
+      gtk_widget_grab_focus (data->username_entry);
+    }
+  else
+    {
+      gtk_widget_set_sensitive (data->username_entry, FALSE);
+      gtk_entry_set_text (GTK_ENTRY (data->username_entry), existing_identity);
+      gtk_widget_set_sensitive (data->register_radio, FALSE);
+      gtk_widget_grab_focus (data->password_entry);
+    }
+
+  g_signal_connect (data->username_entry, "changed", G_CALLBACK (on_username_or_password_changed), data);
+  g_signal_connect (data->password_entry, "changed", G_CALLBACK (on_username_or_password_changed), data);
+  g_signal_connect (data->login_radio, "toggled", G_CALLBACK (on_radio_button_toggled_cb), data);
+  g_signal_connect (data->register_radio, "toggled", G_CALLBACK (on_radio_button_toggled_cb), data);
+  g_signal_connect (data->reset_radio, "toggled", G_CALLBACK (on_radio_button_toggled_cb), data);
+
+  grid2 = gtk_grid_new ();
+  gtk_grid_set_column_spacing (GTK_GRID (grid2), 12);
+  gtk_grid_set_row_spacing (GTK_GRID (grid2), 12);
+  gtk_stack_add_named (GTK_STACK (data->stack), grid2, "otp");
+
+  row = 0;
+  add_entry (grid2, row++, _("Passc_ode:"), &data->otp_entry);
+
+  g_signal_connect (data->otp_entry, "changed", G_CALLBACK (on_otp_changed), data);
+
+  gtk_dialog_add_button (data->dialog, _("_Cancel"), GTK_RESPONSE_CANCEL);
+  data->connect_button = gtk_dialog_add_button (data->dialog, _("C_onnect"), GTK_RESPONSE_OK);
+  gtk_dialog_set_default_response (data->dialog, GTK_RESPONSE_OK);
+  gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, FALSE);
+
+  footer_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
+  gtk_widget_show (footer_box);
+  gtk_container_add (GTK_CONTAINER (grid0), footer_box);
+
+  data->progress_grid = gtk_grid_new ();
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (data->progress_grid), GTK_ORIENTATION_HORIZONTAL);
+  gtk_grid_set_column_spacing (GTK_GRID (data->progress_grid), 3);
+  gtk_box_pack_start (GTK_CONTAINER (footer_box), data->progress_grid, TRUE, TRUE, 0);
+
+  spinner = gtk_spinner_new ();
+  gtk_widget_set_opacity (spinner, 0.0);
+  gtk_widget_set_size_request (spinner, 20, 20);
+  gtk_spinner_start (GTK_SPINNER (spinner));
+  gtk_container_add (GTK_CONTAINER (data->progress_grid), spinner);
+
+  label = gtk_label_new (_("Connecting…"));
+  gtk_widget_set_opacity (label, 0.0);
+  gtk_container_add (GTK_CONTAINER (data->progress_grid), label);
+
+  if (g_file_get_contents ("/etc/os-release", &buffer, NULL, NULL))
+    {
+      privacy_policy = get_item (buffer, "PRIVACY_POLICY_URL");
+
+      if (privacy_policy)
+        {
+          privacy_button = gtk_link_button_new_with_label (privacy_policy,
+                                                           _("Privacy Policy"));
+          gtk_widget_show (privacy_button);
+          gtk_box_pack_end (GTK_BOX (footer_box), privacy_button, FALSE, FALSE, 0);
+        }
+    }
+
+  if (new_account)
+   {
+     gtk_window_get_size (GTK_WINDOW (data->dialog), &width, NULL);
+     gtk_window_set_default_size (GTK_WINDOW (data->dialog), width, -1);
+   }
+  else
+    {
+      GtkWindow *parent;
+
+      /* Keep in sync with GoaPanelAddAccountDialog in
+       * gnome-control-center.
+       */
+      parent = gtk_window_get_transient_for (GTK_WINDOW (data->dialog));
+      if (parent != NULL)
+        {
+          gtk_window_get_size (parent, &width, NULL);
+          gtk_window_set_default_size (GTK_WINDOW (data->dialog), (gint) (0.5 * width), -1);
+        }
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const gchar*
+get_snapd_error_message (GError *error)
+{
+  g_return_val_if_fail (error != NULL, NULL);
+
+  g_debug ("Error logging in snapd: %s (%s, %d)",
+           error->message, g_quark_to_string (error->domain), error->code);
+
+  if (g_error_matches (error, SNAPD_ERROR, SNAPD_ERROR_AUTH_DATA_REQUIRED))
+    return _("Provided email/password is not correct");
+  else if (g_error_matches (error, SNAPD_ERROR, SNAPD_ERROR_TWO_FACTOR_INVALID))
+    return _("The provided 2-factor key is not recognised");
+  else
+    return _("Something went wrong, please try again");
+}
+
+static void
+dialog_response_cb (GtkDialog *dialog,
+                    gint response_id,
+                    gpointer user_data)
+{
+  AddAccountData *data = user_data;
+
+  if (response_id == GTK_RESPONSE_CANCEL || response_id == GTK_RESPONSE_DELETE_EVENT)
+    g_cancellable_cancel (data->cancellable);
+}
+
+static void
+snapd_login_ready_cb (GObject *object,
+                      GAsyncResult *result,
+                      gpointer user_data)
+{
+  AddAccountData *data = user_data;
+  g_autoptr(SnapdUserInformation) user_information = NULL;
+  SnapdAuthData *auth_data;
+
+  user_information = snapd_client_login2_finish (data->snapd_client, result, &data->error);
+  if (user_information != NULL)
+    {
+      auth_data = snapd_user_information_get_auth_data (user_information);
+      data->macaroon = g_strdup (snapd_auth_data_get_macaroon (auth_data));
+      data->discharges = g_strdupv (snapd_auth_data_get_discharges (auth_data));
+    }
+
+  update_widgets (data);
+  show_progress_ui (GTK_CONTAINER (data->progress_grid), FALSE);
+  g_main_loop_quit (data->loop);
+}
+
+static void
+add_account_cb (GoaManager *manager,
+                GAsyncResult *res,
+                gpointer user_data)
+{
+  AddAccountData *data = user_data;
+  goa_manager_call_add_account_finish (manager,
+                                       &data->account_object_path,
+                                       res,
+                                       &data->error);
+  g_main_loop_quit (data->loop);
+}
+
+static void
+add_credentials_key_values (GVariantBuilder *builder,
+                            AddAccountData *data)
+{
+  g_autofree gchar *discharges_str = NULL;
+  g_autoptr(GVariant) discharges_var = NULL;
+
+  g_variant_builder_add (builder, "{sv}", "macaroon", g_variant_new_string (data->macaroon));
+  discharges_var = g_variant_new_strv ((const gchar * const*) data->discharges, -1);
+  discharges_str = g_variant_print (discharges_var, FALSE);
+  g_variant_builder_add (builder, "{sv}", "discharges", g_variant_new_string (discharges_str));
+}
+
+static gboolean
+get_tokens_and_identity (GoaProvider    *provider,
+                         gboolean        add_account,
+                         const gchar    *existing_identity,
+                         GtkDialog      *dialog,
+                         GtkBox         *vbox,
+                         AddAccountData *data)
+{
+  gboolean ret = FALSE;
+  const gchar *password;
+  const gchar *username;
+  const gchar *otp;
+  gint response;
+
+  g_return_val_if_fail (GOA_IS_UBUNTU_SSO_PROVIDER (provider), FALSE);
+  g_return_val_if_fail ((!add_account && existing_identity != NULL && existing_identity[0] != '\0')
+                        || (add_account && existing_identity == NULL), FALSE);
+  g_return_val_if_fail (GTK_IS_DIALOG (dialog), FALSE);
+  g_return_val_if_fail (GTK_IS_BOX (vbox), FALSE);
+  g_return_val_if_fail (data != NULL, FALSE);
+
+  data->dialog = dialog;
+
+  create_account_details_ui (provider, dialog, vbox, add_account, existing_identity, data);
+  gtk_widget_show_all (GTK_WIDGET (vbox));
+  g_signal_connect (dialog, "response", G_CALLBACK (dialog_response_cb), data);
+
+ login_again:
+  response = gtk_dialog_run (dialog);
+  if (response != GTK_RESPONSE_OK)
+    {
+      g_set_error (&data->error,
+                   GOA_ERROR,
+                   GOA_ERROR_DIALOG_DISMISSED,
+                   _("Dialog was dismissed"));
+      goto out;
+    }
+
+  gtk_widget_set_no_show_all (data->cluebar, TRUE);
+  gtk_widget_hide (data->cluebar);
+
+  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (data->register_radio)))
+    {
+      g_app_info_launch_default_for_uri ("https://login.ubuntu.com/+new_account", NULL, NULL);
+      goto login_again;
+    }
+  else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (data->reset_radio)))
+    {
+      g_app_info_launch_default_for_uri ("https://login.ubuntu.com/+forgot_password", NULL, NULL);
+      goto login_again;
+    }
+
+  username = gtk_entry_get_text (GTK_ENTRY (data->username_entry));
+  password = gtk_entry_get_text (GTK_ENTRY (data->password_entry));
+  otp = gtk_entry_get_text (GTK_ENTRY (data->otp_entry));
+  otp = otp && strlen (otp) > 0 ? otp : NULL;
+
+  g_clear_object (&data->cancellable);
+  data->cancellable = g_cancellable_new ();
+
+  snapd_client_login2_async (data->snapd_client,
+                             username, password, otp,
+                             data->cancellable, snapd_login_ready_cb, data);
+
+  gtk_widget_set_sensitive (data->connect_button, FALSE);
+  show_progress_ui (GTK_CONTAINER (data->progress_grid), TRUE);
+  g_main_loop_run (data->loop);
+
+  if (g_cancellable_is_cancelled (data->cancellable))
+    {
+      g_prefix_error (&data->error,
+                      _("Dialog was dismissed (%s, %d): "),
+                      g_quark_to_string (data->error->domain),
+                      data->error->code);
+      data->error->domain = GOA_ERROR;
+      data->error->code = GOA_ERROR_DIALOG_DISMISSED;
+      goto out;
+    }
+  else if (data->error != NULL)
+    {
+      if (data->error->code == SNAPD_ERROR_TWO_FACTOR_REQUIRED)
+        {
+          g_autofree gchar *markup = NULL;
+
+          g_clear_error (&data->error);
+          gtk_button_set_label (GTK_BUTTON (data->connect_button), _("C_onnect"));
+          gtk_stack_set_visible_child_name (GTK_STACK (data->stack), "otp");
+          gtk_widget_set_sensitive (data->connect_button, FALSE);
+
+          markup = g_strdup_printf ("<b>%s</b>", _("Please enter a passcode from your authentication device or app"));
+          gtk_label_set_markup (GTK_LABEL (data->cluebar_label), markup);
+          gtk_info_bar_set_message_type (GTK_INFO_BAR (data->cluebar), GTK_MESSAGE_INFO);
+          gtk_widget_set_no_show_all (data->cluebar, FALSE);
+          gtk_widget_show_all (data->cluebar);
+        }
+      else if (data->error->code == SNAPD_ERROR_AUTH_CANCELLED)
+        {
+          g_clear_error (&data->error);
+        }
+      else
+        {
+          g_autofree gchar *markup = NULL;
+
+          gtk_button_set_label (GTK_BUTTON (data->connect_button), _("_Try Again"));
+          markup = g_strdup_printf ("<b>%s:</b>\n%s",
+                                    _("Error connecting to Ubuntu Single Sign-On server"),
+                                    get_snapd_error_message (data->error));
+          g_clear_error (&data->error);
+          gtk_label_set_markup (GTK_LABEL (data->cluebar_label), markup);
+          gtk_info_bar_set_message_type (GTK_INFO_BAR (data->cluebar), GTK_MESSAGE_ERROR);
+          gtk_widget_set_no_show_all (data->cluebar, FALSE);
+          gtk_widget_show_all (data->cluebar);
+        }
+
+      goto login_again;
+    }
+
+  gtk_widget_hide (GTK_WIDGET (dialog));
+
+  ret = TRUE;
+
+ out:
+  g_signal_handlers_disconnect_by_func (dialog, dialog_response_cb, data);
+
+  return ret;
+}
+
+static GoaObject *
+add_account (GoaProvider *provider,
+             GoaClient   *client,
+             GtkDialog   *dialog,
+             GtkBox      *vbox,
+             GError     **error)
+{
+  g_auto(AddAccountData) data;
+  GVariantBuilder credentials;
+  GVariantBuilder details;
+  GoaObject *ret = NULL;
+  const gchar *username;
+
+  g_return_val_if_fail (GOA_IS_UBUNTU_SSO_PROVIDER (provider), NULL);
+  g_return_val_if_fail (GOA_IS_CLIENT (client), NULL);
+  g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL);
+  g_return_val_if_fail (GTK_IS_BOX (vbox), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  add_account_data_init (&data);
+  if (!get_tokens_and_identity (provider, TRUE, NULL, dialog, vbox, &data))
+    goto out;
+
+  username = gtk_entry_get_text (GTK_ENTRY (data.username_entry));
+
+  /* OK, got the identity... see if there's already an account
+   * of this type with the given identity
+   */
+  if (!goa_utils_check_duplicate (client,
+                                  username,
+                                  username,
+                                  goa_provider_get_provider_type (provider),
+                                  (GoaPeekInterfaceFunc) goa_object_peek_password_based,
+                                  &data.error))
+    goto out;
+
+  g_variant_builder_init (&credentials, G_VARIANT_TYPE_VARDICT);
+  add_credentials_key_values (&credentials, &data);
+  g_variant_builder_init (&details, G_VARIANT_TYPE ("a{ss}"));
+
+  /* OK, everything is dandy, add the account */
+  /* we want the GoaClient to update before this method returns (so it
+   * can create a proxy for the new object) so run the mainloop while
+   * waiting for this to complete
+   */
+  goa_manager_call_add_account (goa_client_get_manager (client),
+                                goa_provider_get_provider_type (provider),
+                                username,
+                                username,
+                                g_variant_builder_end (&credentials),
+                                g_variant_builder_end (&details),
+                                NULL, // GCancellable*
+                                (GAsyncReadyCallback) add_account_cb,
+                                &data);
+  g_main_loop_run (data.loop);
+  if (data.error != NULL)
+    goto out;
+
+  ret = GOA_OBJECT (g_dbus_object_manager_get_object (goa_client_get_object_manager (client),
+                                                      data.account_object_path));
+
+ out:
+  /* We might have an object even when data.error is set.
+   * eg., if we failed to store the credentials in the keyring.
+   */
+  if (data.error != NULL)
+    {
+      g_propagate_error (error, data.error);
+      data.error = NULL;
+    }
+  else
+    g_assert (ret != NULL);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+refresh_account (GoaProvider  *provider,
+                 GoaClient    *client,
+                 GoaObject    *object,
+                 GtkWindow    *parent,
+                 GError      **error)
+{
+  g_auto(AddAccountData) data;
+  GoaAccount *account;
+  GtkWidget *dialog;
+  const gchar *existing_presentation_identity;
+  GVariantBuilder builder;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (GOA_IS_UBUNTU_SSO_PROVIDER (provider), FALSE);
+  g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE);
+  g_return_val_if_fail (GOA_IS_OBJECT (object), FALSE);
+  g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  dialog = gtk_dialog_new_with_buttons (NULL,
+                                        parent,
+                                        GTK_DIALOG_MODAL
+                                        | GTK_DIALOG_DESTROY_WITH_PARENT
+                                        | GTK_DIALOG_USE_HEADER_BAR,
+                                        NULL,
+                                        NULL);
+  gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
+  gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+  gtk_widget_show_all (dialog);
+
+  account = goa_object_peek_account (object);
+
+  existing_presentation_identity = goa_account_get_presentation_identity (account);
+  add_account_data_init (&data);
+  if (!get_tokens_and_identity (provider,
+                                FALSE,
+                                existing_presentation_identity,
+                                GTK_DIALOG (dialog),
+                                GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+                                &data))
+    {
+      goto out;
+    }
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+  add_credentials_key_values (&builder, &data);
+
+  if (!goa_utils_store_credentials_for_object_sync (GOA_PROVIDER (provider),
+                                                    object,
+                                                    g_variant_builder_end (&builder),
+                                                    NULL, /* GCancellable */
+                                                    error))
+    goto out;
+
+  goa_account_call_ensure_credentials (goa_object_peek_account (object),
+                                       NULL, /* GCancellable */
+                                       NULL, NULL); /* callback, user_data */
+
+  ret = TRUE;
+
+ out:
+  if (data.error != NULL)
+    {
+      g_propagate_error (error, data.error);
+      data.error = NULL;
+    }
+
+  gtk_widget_destroy (dialog);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+goa_ubuntu_sso_provider_init (GoaUbuntuSSOProvider *self)
+{
+}
+
+static void
+goa_ubuntu_sso_provider_class_init (GoaUbuntuSSOProviderClass *klass)
+{
+  GoaProviderClass *provider_class = GOA_PROVIDER_CLASS (klass);
+
+  provider_class->get_provider_type       = get_provider_type;
+  provider_class->get_provider_name       = get_provider_name;
+  provider_class->get_provider_group      = get_provider_group;
+  provider_class->get_provider_features   = get_provider_features;
+  provider_class->add_account             = add_account;
+  provider_class->refresh_account         = refresh_account;
+  provider_class->build_object            = build_object;
+  provider_class->ensure_credentials_sync = ensure_credentials_sync;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+on_handle_get_password (GoaPasswordBased      *interface,
+                        GDBusMethodInvocation *invocation,
+                        const gchar           *id,
+                        gpointer               user_data)
+{
+  GoaObject *object;
+  GoaAccount *account;
+  g_autoptr(GoaProvider) provider = NULL;
+  GError *error;
+  const gchar *account_id;
+  const gchar *method_name;
+  const gchar *provider_type;
+  g_autofree gchar *password = NULL;
+
+  object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (interface)));
+  account = goa_object_peek_account (object);
+  account_id = goa_account_get_id (account);
+  provider_type = goa_account_get_provider_type (account);
+  method_name = g_dbus_method_invocation_get_method_name (invocation);
+  g_debug ("Handling %s for account (%s, %s)", method_name, provider_type, account_id);
+
+  provider = goa_provider_get_for_provider_type (provider_type);
+
+  error = NULL;
+  if (!goa_utils_get_credentials (provider, object, id, NULL, &password, NULL, &error))
+    {
+      g_dbus_method_invocation_take_error (invocation, error);
+      return TRUE; // invocation was handled
+    }
+
+  goa_password_based_complete_get_password (interface, invocation, password);
+
+  return TRUE; // invocation was handled
+}
Index: gnome-online-accounts-3.35.90/src/goabackend/goaubuntussoprovider.h
===================================================================
--- /dev/null
+++ gnome-online-accounts-3.35.90/src/goabackend/goaubuntussoprovider.h
@@ -0,0 +1,39 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright © 2018 Canonical Ltd
+ *
+ * Authors: Andrea Azzarone <andrea.azzarone@canonical.com> 
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARCULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only <goabackend/goabackend.h> can be included directly."
+#endif
+
+#ifndef __GOA_UBUNTU_SSO_PROVIDER_H__
+#define __GOA_UBUNTU_SSO_PROVIDER_H__
+
+#include <glib-object.h>
+
+#include "goaprovider-priv.h"
+
+G_BEGIN_DECLS
+
+#define GOA_TYPE_UBUNTU_SSO_PROVIDER (goa_ubuntu_sso_provider_get_type ())
+G_DECLARE_FINAL_TYPE (GoaUbuntuSSOProvider, goa_ubuntu_sso_provider, GOA, UBUNTU_SSO_PROVIDER, GoaProvider);
+
+G_END_DECLS
+
+#endif /* __GOA_UBUNTU_SSO_PROVIDER_H__ */
