[gnome-software/wip/ubuntu-xenial] Backport GsAuth
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/wip/ubuntu-xenial] Backport GsAuth
- Date: Fri, 9 Sep 2016 04:45:46 +0000 (UTC)
commit 9600031ede6a6b8dedc3a41763b50e0d73dfc35c
Author: Richard Hughes <richard hughsie com>
Date: Fri Jun 10 14:37:22 2016 +0100
Backport GsAuth
contrib/gnome-software.spec.in | 1 +
po/POTFILES.in | 2 +
po/ar.po | 48 +++
po/ca.po | 48 +++
po/cs.po | 48 +++
po/de.po | 48 +++
po/en_GB.po | 48 +++
po/eo.po | 48 +++
po/es.po | 49 +++
po/fi.po | 48 +++
po/fr.po | 48 +++
po/fur.po | 48 +++
po/gd.po | 69 ++++-
po/gl.po | 48 +++
po/he.po | 48 +++
po/hu.po | 49 +++
po/lt.po | 48 +++
po/pa.po | 48 +++
po/pl.po | 48 +++
po/pt.po | 48 +++
po/pt_BR.po | 48 +++
po/sk.po | 48 +++
po/sr.po | 48 +++
po/sr latin po | 48 +++
po/sv.po | 48 +++
po/tr.po | 48 +++
po/zh_TW.po | 48 +++
src/Makefile.am | 11 +
src/gnome-software.gresource.xml | 1 +
src/gs-auth-dialog.c | 354 +++++++++++++++++++
src/gs-auth-dialog.h | 45 +++
src/gs-auth-dialog.ui | 359 ++++++++++++++++++++
src/gs-auth.c | 697 ++++++++++++++++++++++++++++++++++++++
src/gs-auth.h | 134 ++++++++
src/gs-page.c | 97 ++++++
src/gs-plugin-loader.c | 178 ++++++++++
src/gs-plugin-loader.h | 12 +
src/gs-plugin.c | 26 ++
src/gs-plugin.h | 30 ++
src/gs-self-test.c | 40 +++
src/gs-shell-details.c | 114 ++++++-
src/gs-utils.c | 22 ++
src/gs-utils.h | 1 +
43 files changed, 3323 insertions(+), 24 deletions(-)
---
diff --git a/contrib/gnome-software.spec.in b/contrib/gnome-software.spec.in
index 331e235..1a6db72 100644
--- a/contrib/gnome-software.spec.in
+++ b/contrib/gnome-software.spec.in
@@ -33,6 +33,7 @@ BuildRequires: libappstream-glib-devel >= 0.5.11
BuildRequires: fwupd-devel
BuildRequires: json-glib-devel
BuildRequires: polkit-devel
+BuildRequires: libsecret-devel
# this is not a library version
%define gs_plugin_version 9
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 02f8681..91c990a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -13,6 +13,8 @@ src/gs-application.c
src/gs-app-row.c
[type: gettext/glade]src/gs-app-row.ui
src/gs-app-tile.c
+src/gs-auth-dialog.c
+[type: gettext/glade]src/gs-auth-dialog.ui
src/gs-category.c
src/gs-dbus-helper.c
src/gs-feature-tile.c
diff --git a/po/ar.po b/po/ar.po
index 29bdce2..d5ac8a8 100644
--- a/po/ar.po
+++ b/po/ar.po
@@ -267,6 +267,54 @@ msgstr "أزل"
msgid "Updates"
msgstr "تحديثات"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "عليك الولوج للمتابعة."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "عليك الولوج إلى %s للمتابعة."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "البريد الإلكتروني"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "لدي حساب بالفعل"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "كلمة المرور"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "أريد تسجيل حساب الآن"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "نسيتُ كلمة السر"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "لِجني تلقائيا المرة القادمة"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "أدخل رمز الاستيثاق بخطوتين"
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "الرمز"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "تابع"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:207
diff --git a/po/ca.po b/po/ca.po
index 22c4d36..227e7b7 100644
--- a/po/ca.po
+++ b/po/ca.po
@@ -394,6 +394,54 @@ msgstr "aplicació web"
msgid "nonfree"
msgstr "no lliure"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Per a continuar heu d'iniciar la sessió."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Per a continuar heu d'iniciar la sessió a %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Adreça electrònica"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Ja tinc un compte"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Contrasenya"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Vull registrar un compte ara"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "He oblidat la contrasenya"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Inicieu la sessió automàticament la propera vegada"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Introduïu el PIN d'un ús per l'autenticació amb dos components."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continua"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/cs.po b/po/cs.po
index 485df1b..0bbdc3b 100644
--- a/po/cs.po
+++ b/po/cs.po
@@ -392,6 +392,54 @@ msgstr "webová aplikace"
msgid "nonfree"
msgstr "nesvobodný"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Pro pokračování se musíte přihlásit."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Pro pokračování se musíte přihlásit ke službě %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "E-mailová adresa"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Již mám účet"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Heslo"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Chci se hned k účtu zaregistrovat"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Zapoměl jsem své heslo"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Příště se přihlásit automaticky"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Zadejte svůj jednorázový PIN pro dvoufázové ověření."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Pokračovat"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/de.po b/po/de.po
index 007e8ba..c529837 100644
--- a/po/de.po
+++ b/po/de.po
@@ -413,6 +413,54 @@ msgstr "Web-App"
msgid "nonfree"
msgstr "Unfrei"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Sie müssen sich anmelden, um fortsetzen zu können."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Sie müssen sich bei %s anmelden, um fortsetzen zu können."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "E-Mail-Adresse"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Ich habe bereits ein Benutzerkonto"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Passwort"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Ich möchte mich für ein neues Benutzerkonto registrieren"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Ich habe mein Passwort vergessen"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Das nächste Mal automatisch anmelden"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Geben Sie Ihre einmalige PIN für die Zwei-Stufen-Legitimierung ein."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Fortfahren"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/en_GB.po b/po/en_GB.po
index f367387..12b4066 100644
--- a/po/en_GB.po
+++ b/po/en_GB.po
@@ -168,6 +168,54 @@ msgstr "Installing"
msgid "Removing"
msgstr "Removing"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "To continue you need to sign in."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "To continue you need to sign in to %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "E-mail address"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "I have an account already"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Password"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "I want to register for an account now"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "I have forgotten my password"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Sign in automatically next time"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Enter your one-time pin for two-factor authentication."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continue"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:121
diff --git a/po/eo.po b/po/eo.po
index a7dff7b..7aca50e 100644
--- a/po/eo.po
+++ b/po/eo.po
@@ -282,6 +282,54 @@ msgstr "Forigi"
msgid "Updates"
msgstr "Ĝisdatigoj"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Ensalutu por daŭrigi."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Ensalutu en %s por daŭrigi."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Retadreso"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Mi jam havas konton"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Pasvorto"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Mi forgesis mian pasvorton"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Daŭrigi"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:210
diff --git a/po/es.po b/po/es.po
index eb6cf5a..05833a6 100644
--- a/po/es.po
+++ b/po/es.po
@@ -3192,3 +3192,52 @@ msgstr "Localización"
#~ msgid "OS Update"
#~ msgstr "Actualización del SO"
+
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Debe iniciar sesión para continuar."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Debe iniciar sesión en %s para continuar."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Dirección de correo-e"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Ya tengo una cuenta"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Contraseña"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Quiero crear una cuenta ahora"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "He olvidado mi contraseña"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Iniciar sesión automáticamente la próxima vez"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr ""
+"Introduzca su pin de un solo uso para la autenticación de dos factores."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continuar"
diff --git a/po/fi.po b/po/fi.po
index 79fda75..2b0594f 100644
--- a/po/fi.po
+++ b/po/fi.po
@@ -387,6 +387,54 @@ msgstr "verkkosovellus"
msgid "nonfree"
msgstr "ei-vapaa"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Jatkaaksesi sinun tulee olla kirjautunut sisään."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Jatkaaksesi kirjaudu sisään palveluun %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Sähköpostiosoite"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Minulla on jo tili"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Salasana"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Haluan rekisteröidä tilin nyt"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Unohdin salasanani"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Kirjaudu sisään automaattisesti ensi kerralla"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Anna kertakäyttöinen PIN-koodi kaksivaiheista tunnistaumista varten."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN-koodi"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Jatka"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/fr.po b/po/fr.po
index 2ca83f4..fb47704 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -397,6 +397,54 @@ msgstr "application Web"
msgid "nonfree"
msgstr "non libre"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Vous devez vous authentifier pour continuer."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Vous devez vous authentifier auprès de %s pour continuer."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Adresse de courriel"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "J'ai déjà un compte"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Mot de passe"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Je souhaite enregistrer un compte maintenant"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "J'ai oublié mon mot de passe"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "M'authentifier automatiquement la prochaine fois"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Saisir votre code personnel d'authentification à usage unique."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "Code personnel"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continuer"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/fur.po b/po/fur.po
index 859dc1e..a644d92 100644
--- a/po/fur.po
+++ b/po/fur.po
@@ -396,6 +396,54 @@ msgstr "Aplicazion web"
msgid "nonfree"
msgstr "no libare"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Par continuâ tu scugnis jentrâ."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Par continuâ tu scugnis jentrâ in %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Direzion e-mail"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "O ai za un account"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Password"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "O desideri regjistrâ un account cumò"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "O ai dismenteât la mê password"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Jentre in automatic la prossime volte"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Inserìs il to pin a ûs singul pe autenticazion a doi fatôrs."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continue"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/gd.po b/po/gd.po
index d85db66..aa5d39a 100644
--- a/po/gd.po
+++ b/po/gd.po
@@ -400,6 +400,69 @@ msgstr "aplacaid-lìn"
msgid "nonfree"
msgstr "chan eil e saor"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:69
+msgid "To continue you need to sign in."
+msgstr "Feumaidh tu clàradh a-steach mus lean thu air adhart."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:73
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Feumaidh tu clàradh a-steach dha %s mus lean thu air adhart."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+#| msgid "_Email address:"
+msgid "Email address"
+msgstr "Seòladh puist-d"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Tha cunntas agam mar-thà"
+
+#: ../src/gs-auth-dialog.ui.h:3
+#| msgid "_Password:"
+msgid "Password"
+msgstr "Facal-faire"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Tha mi airson cunntas a chlàradh an-dràsta"
+
+#: ../src/gs-auth-dialog.ui.h:5
+#| msgid "I've forgotten my password"
+msgid "I have forgotten my password"
+msgstr "Dhìochuimhnich mi am facal-faire agam"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Clàraich a-<steach gu fèin-obrachail an ath-thuras"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "page0"
+msgstr "duilleag0"
+
+#: ../src/gs-auth-dialog.ui.h:8
+#| msgid "Enter your one-time password for two-factor authentication."
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr ""
+"Cuir a-steach am PIN aon turais agad a chum a' chlàraidh a-steach "
+"dhà-cheumnaich"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:10
+msgid "page1"
+msgstr "duilleag1"
+
+#: ../src/gs-auth-dialog.ui.h:11
+#| msgid "_Continue"
+msgid "Continue"
+msgstr "Lean air adhart"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
@@ -2951,12 +3014,6 @@ msgstr "Ionadaileadh"
#~ msgid "_Password:"
#~ msgstr "_Facal-faire:"
-#~ msgid "Sign in automatically next time"
-#~ msgstr "Clàraich a-steach gu fèin-obrachail an ath-thuras"
-
-#~ msgid "I want to register for an account now"
-#~ msgstr "Tha mi airson cunntas a chlàradh an-dràsta"
-
#~ msgid "I've forgotten my password"
#~ msgstr "Dhìochuimhnich mi am facal-faire agam"
diff --git a/po/gl.po b/po/gl.po
index 33a3ebf..4b5dc9b 100644
--- a/po/gl.po
+++ b/po/gl.po
@@ -394,6 +394,54 @@ msgstr "aplicativo web"
msgid "nonfree"
msgstr "non libre"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Para continuar debe iniciar sesión."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Para continuar debe iniciar sesión en %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Enderezo de correo electrónico"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Xa teño unha conta"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Contrasinal"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Quero rexistrar unha conta agora"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Esquecín o meu contrasinal"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Iniciar automaticamente a seguinte vez"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Escriba o seu PIN de uso único para a autenticación de dous factores."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continuar"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/he.po b/po/he.po
index bb38aa9..76cc4c1 100644
--- a/po/he.po
+++ b/po/he.po
@@ -270,6 +270,54 @@ msgstr "הסרה"
msgid "Updates"
msgstr "עדכונים"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "על מנת להמשיך יש להתחבר."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "על מנת להמשיך יש להתחבר אל %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "כתובת דוא״ל"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "כבר יש ברשותי חשבון"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "ססמה"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "ברצוני להירשם עכשיו"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "שכחתי את הססמה שלי"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "התחברות אוטומטית להבא"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "יש להזין את הקוד הסודי החד פעמי עבור שני גורמי אימות."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "קוד סוגי"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "המשך"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:207
diff --git a/po/hu.po b/po/hu.po
index a8c5638..2592211 100644
--- a/po/hu.po
+++ b/po/hu.po
@@ -397,6 +397,55 @@ msgstr "webes app"
msgid "nonfree"
msgstr "nem-szabad"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "A folytatáshoz be kell jelentkeznie."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "A folytatáshoz be kell jelentkeznie ide: %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "E-mail cím"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Már van egy fiókom"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Jelszó"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Szeretnék egy fiókot regisztrálni most"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Elfelejtettem a jelszavamat"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Legközelebb automatikusan jelentkezzen be"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr ""
+"Adja meg az egyszer használatos PIN-kódját a kétlépcsős hitelesítéshez."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN-kód"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Folytatás"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/lt.po b/po/lt.po
index ea17387..9dff5c4 100644
--- a/po/lt.po
+++ b/po/lt.po
@@ -391,6 +391,54 @@ msgstr "internetinė programa"
msgid "nonfree"
msgstr "nelaisva"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Norėdami tęsti, turite prisijungti."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Norėdami tęsti, turite prisijungti prie %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "El. pašto adresas"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Jau turiu paskyrą"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Slaptažodis"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Noriu dabar registruoti paskyrą"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Pamiršau savo slaptažodį"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Automatiškai prisijungti kitą kartą"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Įveskite vienkartinį PIN dviejų žingsnių tapatybės patvirtinimui."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Tęsti"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/pa.po b/po/pa.po
index 76048a5..2b411da 100644
--- a/po/pa.po
+++ b/po/pa.po
@@ -266,6 +266,54 @@ msgstr "ਹਟਾਓ"
msgid "Updates"
msgstr "ਅੱਪਡੇਟ"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:69
+msgid "To continue you need to sign in."
+msgstr "ਜਾਰੀ ਰੱਖਣ ਲਈ ਤੁਹਾਨੂੰ ਸਾਇਨ ਇਨ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।"
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:73
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "ਜਾਰੀ ਰੱਖਣ ਲਈ ਤੁਹਾਨੂੰ %s ਵਿੱਚ ਸਾਇਨ ਇਨ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।"
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "ਈਮੇਲ ਪਤਾ"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "ਮੇਰੇ ਕੋਲ ਪਹਿਲਾਂ ਹੀ ਖਾਤਾ ਹੈ"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "ਪਾਸਵਰਡ"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "ਮੈਂ ਹੁਣੇ ਖਾਤੇ ਲਈ ਰਜਿਸਟਰ ਕਰਨਾ ਚਾਹੁੰਦਾ/ਚਾਹੁੰਦੀ ਹਾਂ"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "ਮੈਂ ਆਪਣਾ ਪਾਸਵਰਡ ਭੁੱਲ ਚੁੱਕਾ/ਚੁੱਕੀ ਹਾਂ"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "ਅਗਲੀ ਵਾਰ ਮੈਨੂੰ ਆਪਣੇ-ਆਪ ਸਾਇਨ ਇਨ ਕਰੋ"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "ਪਿੰਨ"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "ਜਾਰੀ ਰੱਖੋ"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:207
diff --git a/po/pl.po b/po/pl.po
index c2a9e2d..042d5c3 100644
--- a/po/pl.po
+++ b/po/pl.po
@@ -394,6 +394,54 @@ msgstr "WWW"
msgid "nonfree"
msgstr "własnościowe"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Należy się zalogować przed kontynuowaniem."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Należy się zalogować w serwisie %s przed kontynuowaniem."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Adres e-mail"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Już mam konto"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Hasło"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Chcę zarejestrować konto"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Nie pamiętam hasła"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Zapamiętanie logowania"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Proszę wpisać jednorazowy kod PIN do uwierzytelnienia dwuetapowego."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "Kod PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Kontynuuj"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/pt.po b/po/pt.po
index 9eca86c..fd0532d 100644
--- a/po/pt.po
+++ b/po/pt.po
@@ -399,6 +399,54 @@ msgstr "aplicação web"
msgid "nonfree"
msgstr "não grátis"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Para continuar tem de autenticar-se"
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Para continuar tem de autenticar-se em %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Endereço de e-mail"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Eu já tenho uma conta"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Palavra passe"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Pretendo criar agora uma conta"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Esqueci-me da palavra passe"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Autentique-se automaticamente da próxima vez"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Introduza o seu PIN único para a autenticação com dois fatores"
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continuar"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/pt_BR.po b/po/pt_BR.po
index d039838..f3244d1 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -408,6 +408,54 @@ msgstr "aplicativo web"
msgid "nonfree"
msgstr "não livre"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Para continuar, você precisa se autenticar."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Para continuar, você precisa se autenticar em %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Endereço de e-mail"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Eu já tenho uma conta"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Senha"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Eu desejo registrar para ter uma conta agora"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Eu esqueci a minha senha"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Autenticar automaticamente na próxima vez"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Insira seu pin para autenticação de dois fatores."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continuar"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/sk.po b/po/sk.po
index e9a33d6..5980222 100644
--- a/po/sk.po
+++ b/po/sk.po
@@ -412,6 +412,54 @@ msgstr "webová aplikácia"
msgid "nonfree"
msgstr "neslobodný"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Pre pokračovanie sa musíte prihlásiť."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Pre pokračovanie sa musíte prihlásiť do služby %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Emailová adresa"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Už mám účet"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Heslo"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Chcem si teraz zaregistrovať účet"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Zabudol som heslo"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Prihlásiť ma automaticky nabudúce"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Zadajte váš jednorázový pin pre dvojfaktorové overenie totožnosti."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Pokračovať"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/sr.po b/po/sr.po
index 0e10b76..0a38656 100644
--- a/po/sr.po
+++ b/po/sr.po
@@ -399,6 +399,54 @@ msgstr "програми веба"
msgid "nonfree"
msgstr "неслободни"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Да наставите треба да се пријавите."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Да наставите треба да се пријавите на „%s“."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Адреса е-поште"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Већ имам налог"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Лозинка"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Желим сада да се региструјем за налог"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Заборавио сам лозинку"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Сам се пријави следећи пут"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Унесите ваш једновремени пин за потврђивање идентитета са два чиниоца."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "ПИН"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Настави"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/sr latin po b/po/sr latin po
index 4163459..0791272 100644
--- a/po/sr latin po
+++ b/po/sr latin po
@@ -399,6 +399,54 @@ msgstr "programi veba"
msgid "nonfree"
msgstr "neslobodni"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Da nastavite treba da se prijavite."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Da nastavite treba da se prijavite na „%s“."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Adresa e-pošte"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Već imam nalog"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Lozinka"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Želim sada da se registrujem za nalog"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Zaboravio sam lozinku"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Sam se prijavi sledeći put"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Unesite vaš jednovremeni pin za potvrđivanje identiteta sa dva činioca."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Nastavi"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/sv.po b/po/sv.po
index c8ddf99..bb5e478 100644
--- a/po/sv.po
+++ b/po/sv.po
@@ -400,6 +400,54 @@ msgstr "webbapplikation"
msgid "nonfree"
msgstr "icke-fri"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Du måste logga in för att fortsätta."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Du måste logga in på %s för att fortsätta."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "E-postadress"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Jag har redan ett konto"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Lösenord"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Jag vill registrera ett konto nu"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Jag har glömt mitt lösenord"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Logga in automatiskt nästa gång"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Ange din engångs-pin för tvåfaktorsautentisering."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Fortsätt"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/tr.po b/po/tr.po
index 635efae..38dfe7e 100644
--- a/po/tr.po
+++ b/po/tr.po
@@ -391,6 +391,54 @@ msgstr "web uygulaması"
msgid "nonfree"
msgstr "özgür olmayan"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr ""
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr ""
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr ""
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/po/zh_TW.po b/po/zh_TW.po
index dabb2ac..f6220ad 100644
--- a/po/zh_TW.po
+++ b/po/zh_TW.po
@@ -379,6 +379,54 @@ msgstr "網頁應用程式"
msgid "nonfree"
msgstr "非自由"
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "若要繼續,需要您登入。"
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "若要繼續,需要您登入 %s。"
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "電子郵件位址"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "我已有帳號"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "密碼"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "我想要立刻註冊帳號"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "我竟然忘記密碼"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "下次自動登入"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "請輸入雙因素身份認證的一次性 PIN 碼。"
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN 碼"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "繼續"
+
#. TRANSLATORS: this is where all applications that don't
#. * fit in other groups are put
#: ../src/gs-category.c:235
diff --git a/src/Makefile.am b/src/Makefile.am
index 132fa71..b12394c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -9,6 +9,7 @@ AM_CPPFLAGS = \
$(PACKAGEKIT_CFLAGS) \
$(GNOME_DESKTOP_CFLAGS) \
$(POLKIT_CFLAGS) \
+ $(LIBSECRET_CFLAGS) \
-DG_LOG_DOMAIN=\"Gs\" \
-DI_KNOW_THE_PACKAGEKIT_GLIB2_API_IS_SUBJECT_TO_CHANGE \
-DGS_MODULESETDIR=\"$(datadir)/gnome-software/modulesets.d\" \
@@ -33,6 +34,7 @@ UI_FILES = \
gs-app-folder-dialog.ui \
gs-app-row.ui \
gs-app-tile.ui \
+ gs-auth-dialog.ui \
gs-category-tile.ui \
gs-feature-tile.ui \
gs-first-run-dialog.ui \
@@ -86,6 +88,7 @@ noinst_PROGRAMS = \
gnome_software_cmd_SOURCES = \
gs-app.c \
+ gs-auth.c \
gs-review.c \
gs-cmd.c \
gs-utils.c \
@@ -99,6 +102,7 @@ gnome_software_cmd_LDADD = \
$(APPSTREAM_LIBS) \
$(POLKIT_LIBS) \
$(SOUP_LIBS) \
+ $(LIBSECRET_LIBS) \
$(GLIB_LIBS) \
$(GTK_LIBS)
@@ -113,6 +117,10 @@ gnome_software_SOURCES = \
gs-utils.h \
gs-app.c \
gs-app.h \
+ gs-auth.c \
+ gs-auth.h \
+ gs-auth-dialog.c \
+ gs-auth-dialog.h \
gs-category.c \
gs-category.h \
gs-app-addon-row.c \
@@ -226,6 +234,7 @@ gnome_software_LDADD = \
$(GLIB_LIBS) \
$(GTK_LIBS) \
$(SOUP_LIBS) \
+ $(LIBSECRET_LIBS) \
$(PACKAGEKIT_LIBS) \
$(GNOME_DESKTOP_LIBS) \
$(POLKIT_LIBS) \
@@ -283,6 +292,7 @@ check_PROGRAMS = \
gs_self_test_SOURCES = \
gs-app.c \
+ gs-auth.c \
gs-category.c \
gs-os-release.c \
gs-plugin-loader-sync.c \
@@ -296,6 +306,7 @@ gs_self_test_LDADD = \
$(APPSTREAM_LIBS) \
$(POLKIT_LIBS) \
$(SOUP_LIBS) \
+ $(LIBSECRET_LIBS) \
$(GLIB_LIBS) \
$(GTK_LIBS)
diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml
index bc01ff5..dd289a2 100644
--- a/src/gnome-software.gresource.xml
+++ b/src/gnome-software.gresource.xml
@@ -7,6 +7,7 @@
<file preprocess="xml-stripblanks">gs-app-folder-dialog.ui</file>
<file preprocess="xml-stripblanks">gs-app-row.ui</file>
<file preprocess="xml-stripblanks">gs-app-tile.ui</file>
+ <file preprocess="xml-stripblanks">gs-auth-dialog.ui</file>
<file preprocess="xml-stripblanks">gs-category-tile.ui</file>
<file preprocess="xml-stripblanks">gs-feature-tile.ui</file>
<file preprocess="xml-stripblanks">gs-first-run-dialog.ui</file>
diff --git a/src/gs-auth-dialog.c b/src/gs-auth-dialog.c
new file mode 100644
index 0000000..614638e
--- /dev/null
+++ b/src/gs-auth-dialog.c
@@ -0,0 +1,354 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gs-auth.h"
+#include "gs-auth-dialog.h"
+#include "gs-utils.h"
+
+struct _GsAuthDialog
+{
+ GtkDialog parent_instance;
+
+ GCancellable *cancellable;
+ GsPluginLoader *plugin_loader;
+ GsApp *app;
+ GsAuth *auth;
+ GtkWidget *box_dialog;
+ GtkWidget *box_error;
+ GtkWidget *button_cancel;
+ GtkWidget *button_continue;
+ GtkWidget *checkbutton_remember;
+ GtkWidget *entry_password;
+ GtkWidget *entry_pin;
+ GtkWidget *entry_username;
+ GtkWidget *image_vendor;
+ GtkWidget *label_error;
+ GtkWidget *label_title;
+ GtkWidget *radiobutton_already;
+ GtkWidget *radiobutton_lost_pwd;
+ GtkWidget *radiobutton_register;
+ GtkWidget *stack;
+};
+
+G_DEFINE_TYPE (GsAuthDialog, gs_auth_dialog, GTK_TYPE_DIALOG)
+
+static void
+gs_auth_dialog_check_ui (GsAuthDialog *dialog)
+{
+ g_autofree gchar *title = NULL;
+ const gchar *tmp;
+ const gchar *username = gtk_entry_get_text (GTK_ENTRY (dialog->entry_username));
+ const gchar *password = gtk_entry_get_text (GTK_ENTRY (dialog->entry_password));
+
+ /* set the header */
+ tmp = gs_auth_get_provider_name (dialog->auth);
+ if (tmp == NULL) {
+ /* TRANSLATORS: this is when the service name is not known */
+ title = g_strdup (_("To continue you need to sign in."));
+ gtk_label_set_label (GTK_LABEL (dialog->label_title), title);
+ } else {
+ /* TRANSLATORS: the %s is a service name, e.g. "Ubuntu One" */
+ title = g_strdup_printf (_("To continue you need to sign in to %s."), tmp);
+ gtk_label_set_label (GTK_LABEL (dialog->label_title), title);
+ }
+
+ /* set the vendor image */
+ tmp = gs_auth_get_provider_logo (dialog->auth);
+ if (tmp == NULL) {
+ gtk_widget_hide (dialog->image_vendor);
+ } else {
+ gtk_image_set_from_file (GTK_IMAGE (dialog->image_vendor), tmp);
+ gtk_widget_show (dialog->image_vendor);
+ }
+
+ /* need username and password to continue for known account */
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radiobutton_already))) {
+ gtk_widget_set_sensitive (dialog->button_continue,
+ username[0] != '\0' && password[0] != '\0');
+ gtk_widget_set_sensitive (dialog->checkbutton_remember, TRUE);
+ } else {
+ gtk_entry_set_text (GTK_ENTRY (dialog->entry_password), "");
+ gtk_widget_set_sensitive (dialog->button_continue,
+ username[0] != '\0');
+ gtk_widget_set_sensitive (dialog->checkbutton_remember, FALSE);
+ }
+}
+
+static void
+gs_auth_dialog_cancel_button_cb (GtkWidget *widget, GsAuthDialog *dialog)
+{
+ gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
+}
+
+static void
+gs_auth_dialog_authenticate_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+ GsAuthDialog *dialog = GS_AUTH_DIALOG (user_data);
+ g_autoptr(GError) error = NULL;
+
+ gtk_widget_set_sensitive (dialog->box_dialog, TRUE);
+ gtk_widget_set_sensitive (dialog->button_continue, TRUE);
+
+ gtk_widget_set_visible (dialog->box_error, FALSE);
+
+ /* we failed */
+ if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
+ const gchar *url;
+
+ if (g_error_matches (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_PIN_REQUIRED)) {
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "2fa");
+ gtk_widget_grab_focus (dialog->entry_pin);
+ return;
+ }
+
+ /* have we been given a link */
+ url = gs_utils_get_error_value (error);
+ if (url != NULL) {
+ g_autoptr(GError) error_local = NULL;
+ g_debug ("showing link in: %s", error->message);
+ if (!gtk_show_uri (NULL, url, GDK_CURRENT_TIME, &error_local)) {
+ g_warning ("failed to show URI %s: %s",
+ url, error_local->message);
+ }
+ return;
+ }
+
+ g_warning ("failed to authenticate: %s", error->message);
+ gtk_label_set_label (GTK_LABEL (dialog->label_error), error->message);
+ gtk_widget_set_visible (dialog->box_error, TRUE);
+ return;
+ }
+
+ /* we didn't get authenticated */
+ if (!gs_auth_has_flag (dialog->auth, GS_AUTH_FLAG_VALID)) {
+ return;
+ }
+
+ /* success */
+ gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+}
+
+static void
+gs_auth_dialog_continue_cb (GtkWidget *widget, GsAuthDialog *dialog)
+{
+ GsPluginLoaderAction action = GS_AUTH_ACTION_LOGIN;
+
+ gtk_widget_set_sensitive (dialog->box_dialog, FALSE);
+ gtk_widget_set_sensitive (dialog->button_continue, FALSE);
+
+ /* alternate actions */
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radiobutton_lost_pwd)))
+ action = GS_AUTH_ACTION_LOST_PASSWORD;
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radiobutton_register)))
+ action = GS_AUTH_ACTION_REGISTER;
+ gs_plugin_loader_auth_action_async (dialog->plugin_loader,
+ dialog->auth,
+ action,
+ dialog->cancellable,
+ gs_auth_dialog_authenticate_cb,
+ dialog);
+}
+
+static void
+gs_auth_dialog_setup (GsAuthDialog *dialog)
+{
+
+ /* update widgets with known values */
+ if (gs_auth_get_username (dialog->auth) != NULL) {
+ gtk_entry_set_text (GTK_ENTRY (dialog->entry_username),
+ gs_auth_get_username (dialog->auth));
+ }
+ if (gs_auth_get_password (dialog->auth) != NULL) {
+ gtk_entry_set_text (GTK_ENTRY (dialog->entry_password),
+ gs_auth_get_password (dialog->auth));
+ }
+
+ /* refresh UI */
+ gs_auth_dialog_check_ui (dialog);
+}
+
+static void
+gs_auth_dialog_notify_username_cb (GtkEntry *entry,
+ GParamSpec *pspec,
+ GsAuthDialog *dialog)
+{
+ gs_auth_set_username (dialog->auth, gtk_entry_get_text (entry));
+ gs_auth_dialog_check_ui (dialog);
+}
+
+static void
+gs_auth_dialog_notify_password_cb (GtkEntry *entry,
+ GParamSpec *pspec,
+ GsAuthDialog *dialog)
+{
+ gs_auth_set_password (dialog->auth, gtk_entry_get_text (entry));
+ gs_auth_dialog_check_ui (dialog);
+}
+
+static void
+gs_auth_dialog_notify_pin_cb (GtkEntry *entry,
+ GParamSpec *pspec,
+ GsAuthDialog *dialog)
+{
+ gs_auth_set_pin (dialog->auth, gtk_entry_get_text (entry));
+ gs_auth_dialog_check_ui (dialog);
+}
+
+static void
+gs_auth_dialog_toggled_cb (GtkToggleButton *togglebutton, GsAuthDialog *dialog)
+{
+ gs_auth_dialog_check_ui (dialog);
+}
+
+static void
+gs_auth_dialog_remember_cb (GtkToggleButton *togglebutton, GsAuthDialog *dialog)
+{
+ if (gtk_toggle_button_get_active (togglebutton))
+ gs_auth_add_flags (dialog->auth, GS_AUTH_FLAG_REMEMBER);
+ gs_auth_dialog_check_ui (dialog);
+}
+
+static void
+gs_auth_dialog_dispose (GObject *object)
+{
+ GsAuthDialog *dialog = GS_AUTH_DIALOG (object);
+
+ g_clear_object (&dialog->plugin_loader);
+ g_clear_object (&dialog->app);
+ g_clear_object (&dialog->auth);
+
+ if (dialog->cancellable != NULL) {
+ g_cancellable_cancel (dialog->cancellable);
+ g_clear_object (&dialog->cancellable);
+ }
+
+ G_OBJECT_CLASS (gs_auth_dialog_parent_class)->dispose (object);
+}
+
+static void
+gs_auth_dialog_init (GsAuthDialog *dialog)
+{
+ gtk_widget_init_template (GTK_WIDGET (dialog));
+
+ dialog->cancellable = g_cancellable_new ();
+
+ g_signal_connect (dialog->entry_username, "notify::text",
+ G_CALLBACK (gs_auth_dialog_notify_username_cb), dialog);
+ g_signal_connect (dialog->entry_password, "notify::text",
+ G_CALLBACK (gs_auth_dialog_notify_password_cb), dialog);
+ g_signal_connect (dialog->entry_password, "activate",
+ G_CALLBACK (gs_auth_dialog_continue_cb), dialog);
+ g_signal_connect (dialog->entry_pin, "notify::text",
+ G_CALLBACK (gs_auth_dialog_notify_pin_cb), dialog);
+ g_signal_connect (dialog->entry_pin, "activate",
+ G_CALLBACK (gs_auth_dialog_continue_cb), dialog);
+ g_signal_connect (dialog->checkbutton_remember, "toggled",
+ G_CALLBACK (gs_auth_dialog_remember_cb), dialog);
+ g_signal_connect (dialog->radiobutton_already, "toggled",
+ G_CALLBACK (gs_auth_dialog_toggled_cb), dialog);
+ g_signal_connect (dialog->radiobutton_register, "toggled",
+ G_CALLBACK (gs_auth_dialog_toggled_cb), dialog);
+ g_signal_connect (dialog->radiobutton_lost_pwd, "toggled",
+ G_CALLBACK (gs_auth_dialog_toggled_cb), dialog);
+ g_signal_connect (dialog->button_cancel, "clicked",
+ G_CALLBACK (gs_auth_dialog_cancel_button_cb), dialog);
+ g_signal_connect (dialog->button_continue, "clicked",
+ G_CALLBACK (gs_auth_dialog_continue_cb), dialog);
+}
+
+static void
+gs_auth_dialog_class_init (GsAuthDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = gs_auth_dialog_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-auth-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, box_dialog);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, box_error);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, button_cancel);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, button_continue);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, checkbutton_remember);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, entry_password);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, entry_pin);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, entry_username);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, image_vendor);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, label_error);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, label_title);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, radiobutton_already);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, radiobutton_lost_pwd);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, radiobutton_register);
+ gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, stack);
+}
+
+GtkWidget *
+gs_auth_dialog_new (GsPluginLoader *plugin_loader,
+ GsApp *app,
+ const gchar *provider_id,
+ GError **error)
+{
+ GsAuthDialog *dialog;
+ GsAuth *auth;
+
+ /* get the authentication provider */
+ if (provider_id == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "no auth-provider given for %s",
+ gs_app_get_id (app));
+ return NULL;
+ }
+ auth = gs_plugin_loader_get_auth_by_id (plugin_loader, provider_id);
+ if (auth == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "no auth-provider %s for %s",
+ provider_id, gs_app_get_id (app));
+ return NULL;
+ }
+
+ /* create dialog */
+ dialog = g_object_new (GS_TYPE_AUTH_DIALOG,
+ "use-header-bar", TRUE,
+ NULL);
+ dialog->plugin_loader = g_object_ref (plugin_loader);
+ dialog->app = g_object_ref (app);
+ dialog->auth = g_object_ref (auth);
+ gs_auth_dialog_setup (dialog);
+
+ return GTK_WIDGET (dialog);
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-auth-dialog.h b/src/gs-auth-dialog.h
new file mode 100644
index 0000000..5487c2b
--- /dev/null
+++ b/src/gs-auth-dialog.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef GS_AUTH_DIALOG_H
+#define GS_AUTH_DIALOG_H
+
+#include <gtk/gtk.h>
+
+#include "gs-app.h"
+#include "gs-plugin-loader.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_AUTH_DIALOG (gs_auth_dialog_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsAuthDialog, gs_auth_dialog, GS, AUTH_DIALOG, GtkDialog)
+
+GtkWidget *gs_auth_dialog_new (GsPluginLoader *plugin_loader,
+ GsApp *app,
+ const gchar *provider_id,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* GS_AUTH_DIALOG_H */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-auth-dialog.ui b/src/gs-auth-dialog.ui
new file mode 100644
index 0000000..7cee9f5
--- /dev/null
+++ b/src/gs-auth-dialog.ui
@@ -0,0 +1,359 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface>
+ <requires lib="gtk+" version="3.10"/>
+ <template class="GsAuthDialog" parent="GtkDialog">
+ <property name="can_focus">False</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="deletable">False</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="box_dialog">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">21</property>
+ <property name="row_spacing">9</property>
+ <property name="column_spacing">21</property>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="transition_type">crossfade</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="name">intro</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">start</property>
+ <property name="row_spacing">9</property>
+ <property name="column_spacing">9</property>
+ <child>
+ <object class="GtkLabel" id="label_title">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label">To continue you need an %NAME% account.</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</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="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">Email address</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="entry_username">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="margin_bottom">15</property>
+ <property name="input_purpose">email</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton_already">
+ <property name="label" translatable="yes">I have an account already</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">Password</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_password">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="input_purpose">password</property>
+ <property name="visibility">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton_register">
+ <property name="label" translatable="yes">I want to register for an account
now</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton_already</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">5</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton_lost_pwd">
+ <property name="label" translatable="yes">I have forgotten my password</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton_already</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">6</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton_remember">
+ <property name="label" translatable="yes">Sign in automatically next time</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">login</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid">
+ <property name="name">2fa</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">9</property>
+ <property name="column_spacing">9</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Enter your one-time pin for two-factor
authentication.</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</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="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">PIN</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="entry_pin">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="input_purpose">password</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">2fa</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="image_vendor">
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">start</property>
+ <property name="stock">gtk-floppy</property>
+ <property name="icon_size">6</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box_error">
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="valign">start</property>
+ <property name="icon_name">dialog-warning-symbolic</property>
+ <property name="use_fallback">True</property>
+ <property name="icon_size">5</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_error">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label">The supplied credentials were not correct</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="titlebar">
+ <object class="GtkHeaderBar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="title">Authenticate</property>
+ <child>
+ <object class="GtkButton" id="button_cancel">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_continue">
+ <property name="label" translatable="yes">Continue</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkSizeGroup" id="sizegroup_buttons">
+ <widgets>
+ <widget name="button_continue"/>
+ <widget name="button_cancel"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/src/gs-auth.c b/src/gs-auth.c
new file mode 100644
index 0000000..e780a2a
--- /dev/null
+++ b/src/gs-auth.c
@@ -0,0 +1,697 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * SECTION:gs-auth
+ * @title: GsAuth
+ * @include: gnome-software.h
+ * @stability: Unstable
+ * @short_description: User data used for authentication
+ *
+ * This object represents user data used for authentication.
+ * This data is shared between all plugins.
+ */
+
+#include "config.h"
+
+#include <libsecret/secret.h>
+
+#include "gs-auth.h"
+#include "gs-plugin.h"
+
+struct _GsAuth
+{
+ GObject parent_instance;
+
+ GsAuthFlags flags;
+ gchar *provider_id;
+ gchar *provider_name;
+ gchar *provider_logo;
+ gchar *provider_uri;
+ gchar *provider_schema;
+ gchar *username;
+ gchar *password;
+ gchar *pin;
+ GHashTable *metadata; /* utf8: utf8 */
+};
+
+enum {
+ PROP_0,
+ PROP_USERNAME,
+ PROP_PASSWORD,
+ PROP_PIN,
+ PROP_FLAGS,
+ PROP_LAST
+};
+
+G_DEFINE_TYPE (GsAuth, gs_auth, G_TYPE_OBJECT)
+
+/**
+ * gs_auth_get_provider_id:
+ * @auth: a #GsAuth
+ *
+ * Gets the authentication service ID.
+ *
+ * Returns: the string to use for searching, e.g. "UbuntuOne"
+ */
+const gchar *
+gs_auth_get_provider_id (GsAuth *auth)
+{
+ g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+ return auth->provider_id;
+}
+
+/**
+ * gs_auth_get_provider_name:
+ * @auth: a #GsAuth
+ *
+ * Gets the authentication service name.
+ *
+ * Returns: the string to show in the UI
+ */
+const gchar *
+gs_auth_get_provider_name (GsAuth *auth)
+{
+ g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+ return auth->provider_name;
+}
+
+/**
+ * gs_auth_set_provider_name:
+ * @auth: a #GsAuth
+ * @provider_name: a service name, e.g. "GNOME Online Accounts"
+ *
+ * Sets the name to be used for the authentication dialog.
+ */
+void
+gs_auth_set_provider_name (GsAuth *auth, const gchar *provider_name)
+{
+ g_return_if_fail (GS_IS_AUTH (auth));
+ g_free (auth->provider_name);
+ auth->provider_name = g_strdup (provider_name);
+}
+
+/**
+ * gs_auth_get_provider_logo:
+ * @auth: a #GsAuth
+ *
+ * Gets the authentication service image.
+ *
+ * Returns: the filename of an image, or %NULL
+ */
+const gchar *
+gs_auth_get_provider_logo (GsAuth *auth)
+{
+ g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+ return auth->provider_logo;
+}
+
+/**
+ * gs_auth_set_provider_logo:
+ * @auth: a #GsAuth
+ * @provider_logo: an image, e.g. "/usr/share/icons/gnome-online.png"
+ *
+ * Sets the image to be used for the authentication dialog.
+ */
+void
+gs_auth_set_provider_logo (GsAuth *auth, const gchar *provider_logo)
+{
+ g_return_if_fail (GS_IS_AUTH (auth));
+ g_free (auth->provider_logo);
+ auth->provider_logo = g_strdup (provider_logo);
+}
+
+/**
+ * gs_auth_get_provider_uri:
+ * @auth: a #GsAuth
+ *
+ * Gets the authentication service website.
+ *
+ * Returns: the URI, or %NULL
+ */
+const gchar *
+gs_auth_get_provider_uri (GsAuth *auth)
+{
+ g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+ return auth->provider_uri;
+}
+
+/**
+ * gs_auth_set_provider_uri:
+ * @auth: a #GsAuth
+ * @provider_uri: a URI, e.g. "http://www.gnome.org/sso"
+ *
+ * Sets the website to be used for the authentication dialog.
+ */
+void
+gs_auth_set_provider_uri (GsAuth *auth, const gchar *provider_uri)
+{
+ g_return_if_fail (GS_IS_AUTH (auth));
+ g_free (auth->provider_uri);
+ auth->provider_uri = g_strdup (provider_uri);
+}
+
+/**
+ * gs_auth_get_provider_schema:
+ * @auth: a #GsAuth
+ *
+ * Gets the authentication schema ID.
+ *
+ * Returns: the URI, or %NULL
+ */
+const gchar *
+gs_auth_get_provider_schema (GsAuth *auth)
+{
+ g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+ return auth->provider_schema;
+}
+
+/**
+ * gs_auth_set_provider_schema:
+ * @auth: a #GsAuth
+ * @provider_schema: a URI, e.g. "com.distro.provider"
+ *
+ * Sets the schema ID to be used for saving the state to disk.
+ */
+void
+gs_auth_set_provider_schema (GsAuth *auth, const gchar *provider_schema)
+{
+ g_return_if_fail (GS_IS_AUTH (auth));
+ g_free (auth->provider_schema);
+ auth->provider_schema = g_strdup (provider_schema);
+}
+
+/**
+ * gs_auth_get_username:
+ * @auth: a #GsAuth
+ *
+ * Gets the auth username.
+ *
+ * Returns: the username to be used for the authentication
+ */
+const gchar *
+gs_auth_get_username (GsAuth *auth)
+{
+ g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+ return auth->username;
+}
+
+/**
+ * gs_auth_set_username:
+ * @auth: a #GsAuth
+ * @username: a username, e.g. "hughsie"
+ *
+ * Sets the username to be used for the authentication.
+ */
+void
+gs_auth_set_username (GsAuth *auth, const gchar *username)
+{
+ g_return_if_fail (GS_IS_AUTH (auth));
+ g_free (auth->username);
+ auth->username = g_strdup (username);
+}
+
+/**
+ * gs_auth_get_password:
+ * @auth: a #GsAuth
+ *
+ * Gets the password to be used for the authentication.
+ *
+ * Returns: the string, or %NULL
+ **/
+const gchar *
+gs_auth_get_password (GsAuth *auth)
+{
+ g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+ return auth->password;
+}
+
+/**
+ * gs_auth_set_password:
+ * @auth: a #GsAuth
+ * @password: password string, e.g. "p@ssw0rd"
+ *
+ * Sets the password to be used for the authentication.
+ */
+void
+gs_auth_set_password (GsAuth *auth, const gchar *password)
+{
+ g_return_if_fail (GS_IS_AUTH (auth));
+ g_free (auth->password);
+ auth->password = g_strdup (password);
+}
+
+/**
+ * gs_auth_get_flags:
+ * @auth: a #GsAuth
+ *
+ * Gets any flags set on the authentication, for example if we should remember
+ * credentials.
+ *
+ * Returns: a #GsAuthFlags, e.g. %GS_AUTH_FLAG_REMEMBER
+ */
+GsAuthFlags
+gs_auth_get_flags (GsAuth *auth)
+{
+ g_return_val_if_fail (GS_IS_AUTH (auth), 0);
+ return auth->flags;
+}
+
+/**
+ * gs_auth_set_flags:
+ * @auth: a #GsAuth
+ * @flags: a #GsAuthFlags, e.g. %GS_AUTH_FLAG_REMEMBER
+ *
+ * Gets any flags set on the authentication.
+ */
+void
+gs_auth_set_flags (GsAuth *auth, GsAuthFlags flags)
+{
+ g_return_if_fail (GS_IS_AUTH (auth));
+ auth->flags = flags;
+}
+
+/**
+ * gs_auth_add_flags:
+ * @auth: a #GsAuth
+ * @flags: a #GsAuthFlags, e.g. %GS_AUTH_FLAG_REMEMBER
+ *
+ * Adds flags to an existing authentication without replacing the other flags.
+ */
+void
+gs_auth_add_flags (GsAuth *auth, GsAuthFlags flags)
+{
+ g_return_if_fail (GS_IS_AUTH (auth));
+ auth->flags |= flags;
+}
+
+/**
+ * gs_auth_has_flag:
+ * @auth: a #GsAuth
+ * @flags: a #GsAuthFlags, e.g. %GS_AUTH_FLAG_REMEMBER
+ *
+ * Finds ouf if the authentication has a flag.
+ *
+ * Returns: %TRUE if set
+ */
+gboolean
+gs_auth_has_flag (GsAuth *auth, GsAuthFlags flags)
+{
+ g_return_val_if_fail (GS_IS_AUTH (auth), FALSE);
+ return (auth->flags & flags) > 0;
+}
+
+/**
+ * gs_auth_get_pin:
+ * @auth: a #GsAuth
+ *
+ * Gets the PIN code.
+ *
+ * Returns: the 2 factor authentication PIN, or %NULL
+ **/
+const gchar *
+gs_auth_get_pin (GsAuth *auth)
+{
+ g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+ return auth->pin;
+}
+
+/**
+ * gs_auth_set_pin:
+ * @auth: a #GsAuth
+ * @pin: the PIN code, e.g. "12345"
+ *
+ * Sets the 2 factor authentication PIN, which can be left unset.
+ */
+void
+gs_auth_set_pin (GsAuth *auth, const gchar *pin)
+{
+ g_return_if_fail (GS_IS_AUTH (auth));
+ g_free (auth->pin);
+ auth->pin = g_strdup (pin);
+}
+
+/**
+ * gs_auth_get_metadata_item:
+ * @auth: a #GsAuth
+ * @key: a string
+ *
+ * Gets some metadata from a authentication object.
+ * It is left for the the plugin to use this method as required, but a
+ * typical use would be to retrieve some secure auth token.
+ *
+ * Returns: A string value, or %NULL for not found
+ */
+const gchar *
+gs_auth_get_metadata_item (GsAuth *auth, const gchar *key)
+{
+ g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+ g_return_val_if_fail (key != NULL, NULL);
+ return g_hash_table_lookup (auth->metadata, key);
+}
+
+/**
+ * gs_auth_add_metadata:
+ * @auth: a #GsAuth
+ * @key: a string
+ * @value: a string
+ *
+ * Adds metadata to the authentication object.
+ * It is left for the the plugin to use this method as required, but a
+ * typical use would be to store some secure auth token.
+ */
+void
+gs_auth_add_metadata (GsAuth *auth, const gchar *key, const gchar *value)
+{
+ g_return_if_fail (GS_IS_AUTH (auth));
+ g_hash_table_insert (auth->metadata, g_strdup (key), g_strdup (value));
+}
+
+static gboolean
+_g_error_is_set (GError **error)
+{
+ if (error == NULL)
+ return FALSE;
+ return *error != NULL;
+}
+
+/**
+ * gs_auth_store_load:
+ * @auth: a #GsAuth
+ * @flags: some #GsAuthStoreFlags, e.g. %GS_AUTH_STORE_FLAG_USERNAME
+ * @cancellable: a #GCancellable or %NULL
+ * @error: a #GError or %NULL
+ *
+ * Loads authentication tokens from disk in a secure way.
+ * By default only the username and password are loaded, but they are not
+ * overwritten if already set.
+ *
+ * If additional tokens are required to be loaded you must first tell the
+ * GsAuth instance what metadata to load. This can be done using:
+ * `gs_auth_add_metadata("additional-secret-key-name",NULL)`
+ *
+ * This function is expected to be called from gs_plugin_setup().
+ *
+ * Returns: %TRUE if the tokens were loaded correctly.
+ */
+gboolean
+gs_auth_store_load (GsAuth *auth, GsAuthStoreFlags flags,
+ GCancellable *cancellable, GError **error)
+{
+ SecretSchema schema = {
+ auth->provider_schema,
+ SECRET_SCHEMA_NONE,
+ { { "key", SECRET_SCHEMA_ATTRIBUTE_STRING } }
+ };
+
+ /* no schema */
+ if (auth->provider_schema == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "No provider schema set for %s",
+ auth->provider_id);
+ return FALSE;
+ }
+
+ /* username */
+ if ((flags & GS_AUTH_STORE_FLAG_USERNAME) > 0 && auth->username == NULL) {
+ auth->username = secret_password_lookup_sync (&schema,
+ cancellable,
+ error,
+ "key", "username",
+ NULL);
+ if (_g_error_is_set (error))
+ return FALSE;
+ }
+
+ /* password */
+ if ((flags & GS_AUTH_STORE_FLAG_PASSWORD) > 0 && auth->password == NULL) {
+ auth->password = secret_password_lookup_sync (&schema,
+ cancellable,
+ error,
+ "key", "password",
+ NULL);
+ if (_g_error_is_set (error))
+ return FALSE;
+ }
+
+ /* metadata */
+ if (flags & GS_AUTH_STORE_FLAG_METADATA) {
+ GList *l;
+ g_autoptr(GList) keys = NULL;
+ keys = g_hash_table_get_keys (auth->metadata);
+ for (l = keys; l != NULL; l = l->next) {
+ g_autofree gchar *tmp = NULL;
+ const gchar *key = l->data;
+ const gchar *value = g_hash_table_lookup (auth->metadata, key);
+ if (value != NULL)
+ continue;
+ tmp = secret_password_lookup_sync (&schema,
+ cancellable,
+ error,
+ "key", key,
+ NULL);
+ if (_g_error_is_set (error))
+ return FALSE;
+ if (tmp != NULL)
+ gs_auth_add_metadata (auth, key, tmp);
+ }
+ }
+
+ /* success */
+ return TRUE;
+}
+
+/**
+ * gs_auth_store_save:
+ * @auth: a #GsAuth
+ * @flags: some #GsAuthStoreFlags, e.g. %GS_AUTH_STORE_FLAG_USERNAME
+ * @cancellable: a #GCancellable or %NULL
+ * @error: a #GError or %NULL
+ *
+ * Saves the username, password and all added metadata to disk in a secure way.
+ *
+ * This function is expected to be called from gs_plugin_setup().
+ *
+ * Returns: %TRUE if the tokens were all saved correctly.
+ */
+gboolean
+gs_auth_store_save (GsAuth *auth, GsAuthStoreFlags flags,
+ GCancellable *cancellable, GError **error)
+{
+ SecretSchema schema = {
+ auth->provider_schema,
+ SECRET_SCHEMA_NONE,
+ { { "key", SECRET_SCHEMA_ATTRIBUTE_STRING } }
+ };
+
+ /* no schema */
+ if (auth->provider_schema == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "No provider schema set for %s",
+ auth->provider_id);
+ return FALSE;
+ }
+
+ /* username */
+ if ((flags & GS_AUTH_STORE_FLAG_USERNAME) > 0 && auth->username != NULL) {
+ if (!secret_password_store_sync (&schema,
+ NULL, /* collection */
+ auth->provider_schema,
+ auth->username,
+ cancellable, error,
+ "key", "username", NULL))
+ return FALSE;
+ }
+
+ /* password */
+ if ((flags & GS_AUTH_STORE_FLAG_PASSWORD) > 0 && auth->password != NULL) {
+ if (!secret_password_store_sync (&schema,
+ NULL, /* collection */
+ auth->provider_schema,
+ auth->password,
+ cancellable, error,
+ "key", "password", NULL))
+ return FALSE;
+ }
+
+ /* metadata */
+ if (flags & GS_AUTH_STORE_FLAG_METADATA) {
+ GList *l;
+ g_autoptr(GList) keys = NULL;
+ keys = g_hash_table_get_keys (auth->metadata);
+ for (l = keys; l != NULL; l = l->next) {
+ const gchar *key = l->data;
+ const gchar *value = g_hash_table_lookup (auth->metadata, key);
+ if (value == NULL)
+ continue;
+ if (!secret_password_store_sync (&schema,
+ NULL, /* collection */
+ auth->provider_schema,
+ value,
+ cancellable, error,
+ "key", key, NULL))
+ return FALSE;
+ }
+ }
+
+ /* success */
+ return TRUE;
+}
+
+static void
+gs_auth_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ GsAuth *auth = GS_AUTH (object);
+
+ switch (prop_id) {
+ case PROP_USERNAME:
+ g_value_set_string (value, auth->username);
+ break;
+ case PROP_PASSWORD:
+ g_value_set_string (value, auth->password);
+ break;
+ case PROP_FLAGS:
+ g_value_set_uint64 (value, auth->flags);
+ break;
+ case PROP_PIN:
+ g_value_set_string (value, auth->pin);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_auth_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ GsAuth *auth = GS_AUTH (object);
+
+ switch (prop_id) {
+ case PROP_USERNAME:
+ gs_auth_set_username (auth, g_value_get_string (value));
+ break;
+ case PROP_PASSWORD:
+ gs_auth_set_password (auth, g_value_get_string (value));
+ break;
+ case PROP_FLAGS:
+ gs_auth_set_flags (auth, g_value_get_uint64 (value));
+ break;
+ case PROP_PIN:
+ gs_auth_set_pin (auth, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_auth_finalize (GObject *object)
+{
+ GsAuth *auth = GS_AUTH (object);
+
+ g_free (auth->provider_id);
+ g_free (auth->provider_name);
+ g_free (auth->provider_logo);
+ g_free (auth->provider_uri);
+ g_free (auth->provider_schema);
+ g_free (auth->username);
+ g_free (auth->password);
+ g_free (auth->pin);
+ g_hash_table_unref (auth->metadata);
+
+ G_OBJECT_CLASS (gs_auth_parent_class)->finalize (object);
+}
+
+static void
+gs_auth_class_init (GsAuthClass *klass)
+{
+ GParamSpec *pspec;
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = gs_auth_finalize;
+ object_class->get_property = gs_auth_get_property;
+ object_class->set_property = gs_auth_set_property;
+
+ /**
+ * GsAuth:username:
+ */
+ pspec = g_param_spec_string ("username", NULL, NULL,
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ g_object_class_install_property (object_class, PROP_USERNAME, pspec);
+
+ /**
+ * GsAuth:password:
+ */
+ pspec = g_param_spec_string ("password", NULL, NULL,
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ g_object_class_install_property (object_class, PROP_PASSWORD, pspec);
+
+ /**
+ * GsAuth:flags:
+ */
+ pspec = g_param_spec_uint64 ("flags", NULL, NULL,
+ GS_AUTH_FLAG_NONE,
+ GS_AUTH_FLAG_LAST,
+ GS_AUTH_FLAG_NONE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ g_object_class_install_property (object_class, PROP_FLAGS, pspec);
+
+ /**
+ * GsAuth:pin:
+ */
+ pspec = g_param_spec_string ("pin", NULL, NULL,
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ g_object_class_install_property (object_class, PROP_PIN, pspec);
+}
+
+static void
+gs_auth_init (GsAuth *auth)
+{
+ auth->metadata = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_free);
+}
+
+/**
+ * gs_auth_new:
+ * @provider_id: a provider ID used for mapping, e.g. "GnomeSSO"
+ *
+ * Return value: a new #GsAuth object.
+ **/
+GsAuth *
+gs_auth_new (const gchar *provider_id)
+{
+ GsAuth *auth;
+ auth = g_object_new (GS_TYPE_AUTH, NULL);
+ auth->provider_id = g_strdup (provider_id);
+ return GS_AUTH (auth);
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-auth.h b/src/gs-auth.h
new file mode 100644
index 0000000..9f34e92
--- /dev/null
+++ b/src/gs-auth.h
@@ -0,0 +1,134 @@
+ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GS_AUTH_H
+#define __GS_AUTH_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_AUTH (gs_auth_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsAuth, gs_auth, GS, AUTH, GObject)
+
+/**
+ * GsAuthFlags:
+ * @GS_AUTH_FLAG_NONE: No special flags set
+ * @GS_AUTH_FLAG_VALID: Authorisation is valid
+ * @GS_AUTH_FLAG_REMEMBER: Remember this authentication if possible
+ *
+ * The flags for the auth.
+ **/
+typedef enum {
+ GS_AUTH_FLAG_NONE = 0,
+ GS_AUTH_FLAG_VALID = 1 << 0,
+ GS_AUTH_FLAG_REMEMBER = 1 << 1,
+ /*< private >*/
+ GS_AUTH_FLAG_LAST
+} GsAuthFlags;
+
+/**
+ * GsAuthAction:
+ * @GS_AUTH_ACTION_LOGIN: Login action
+ * @GS_AUTH_ACTION_LOGOUT: Logout action
+ * @GS_AUTH_ACTION_REGISTER: Register action
+ * @GS_AUTH_ACTION_LOST_PASSWORD: Lost password action
+ *
+ * The actions that can be performed on an authentication.
+ **/
+typedef enum {
+ GS_AUTH_ACTION_LOGIN,
+ GS_AUTH_ACTION_LOGOUT,
+ GS_AUTH_ACTION_REGISTER,
+ GS_AUTH_ACTION_LOST_PASSWORD,
+ /*< private >*/
+ GS_AUTH_ACTION_LAST
+} GsAuthAction;
+
+/**
+ * GsAuthStoreFlags:
+ * @GS_AUTH_STORE_FLAG_NONE: No special flags set
+ * @GS_AUTH_STORE_FLAG_USERNAME: Load or save the username
+ * @GS_AUTH_STORE_FLAG_PASSWORD: Load or save the password
+ * @GS_AUTH_STORE_FLAG_METADATA: Load or save any metadata
+ *
+ * The flags used when loading or saving the authentication to disk.
+ **/
+typedef enum {
+ GS_AUTH_STORE_FLAG_NONE = 0,
+ GS_AUTH_STORE_FLAG_USERNAME = 1 << 0,
+ GS_AUTH_STORE_FLAG_PASSWORD = 1 << 1,
+ GS_AUTH_STORE_FLAG_METADATA = 1 << 2,
+ /*< private >*/
+ GS_AUTH_STORE_FLAG_LAST
+} GsAuthStoreFlags;
+
+GsAuth *gs_auth_new (const gchar *provider_id);
+const gchar *gs_auth_get_provider_id (GsAuth *auth);
+const gchar *gs_auth_get_provider_name (GsAuth *auth);
+void gs_auth_set_provider_name (GsAuth *auth,
+ const gchar *provider_name);
+const gchar *gs_auth_get_provider_logo (GsAuth *auth);
+void gs_auth_set_provider_logo (GsAuth *auth,
+ const gchar *provider_logo);
+const gchar *gs_auth_get_provider_uri (GsAuth *auth);
+void gs_auth_set_provider_uri (GsAuth *auth,
+ const gchar *provider_uri);
+const gchar *gs_auth_get_provider_schema (GsAuth *auth);
+void gs_auth_set_provider_schema (GsAuth *auth,
+ const gchar *provider_schema);
+const gchar *gs_auth_get_username (GsAuth *auth);
+void gs_auth_set_username (GsAuth *auth,
+ const gchar *username);
+const gchar *gs_auth_get_password (GsAuth *auth);
+void gs_auth_set_password (GsAuth *auth,
+ const gchar *password);
+const gchar *gs_auth_get_pin (GsAuth *auth);
+void gs_auth_set_pin (GsAuth *auth,
+ const gchar *pin);
+GsAuthFlags gs_auth_get_flags (GsAuth *auth);
+void gs_auth_set_flags (GsAuth *auth,
+ GsAuthFlags flags);
+void gs_auth_add_flags (GsAuth *auth,
+ GsAuthFlags flags);
+gboolean gs_auth_has_flag (GsAuth *auth,
+ GsAuthFlags flags);
+const gchar *gs_auth_get_metadata_item (GsAuth *auth,
+ const gchar *key);
+void gs_auth_add_metadata (GsAuth *auth,
+ const gchar *key,
+ const gchar *value);
+gboolean gs_auth_store_load (GsAuth *auth,
+ GsAuthStoreFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+gboolean gs_auth_store_save (GsAuth *auth,
+ GsAuthStoreFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __GS_AUTH_H */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-page.c b/src/gs-page.c
index 30104eb..852b7e3 100644
--- a/src/gs-page.c
+++ b/src/gs-page.c
@@ -28,6 +28,7 @@
#include "gs-page.h"
#include "gs-shell.h"
#include "gs-utils.h"
+#include "gs-auth-dialog.h"
typedef struct
{
@@ -58,6 +59,60 @@ gs_page_helper_free (GsPageHelper *helper)
static void
gs_page_app_installed_cb (GObject *source,
GAsyncResult *res,
+ gpointer user_data);
+
+static void
+gs_page_install_authenticate_cb (GtkDialog *dialog,
+ GtkResponseType response_type,
+ GsPageHelper *helper)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+
+ /* unmap the dialog */
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ if (response_type != GTK_RESPONSE_OK) {
+ gs_page_helper_free (helper);
+ return;
+ }
+ gs_plugin_loader_app_action_async (priv->plugin_loader,
+ helper->app,
+ GS_PLUGIN_LOADER_ACTION_INSTALL,
+ priv->cancellable,
+ gs_page_app_installed_cb,
+ helper);
+}
+
+static void
+gs_page_app_removed_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data);
+
+static void
+gs_page_remove_authenticate_cb (GtkDialog *dialog,
+ GtkResponseType response_type,
+ GsPageHelper *helper)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+
+ /* unmap the dialog */
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ if (response_type != GTK_RESPONSE_OK) {
+ gs_page_helper_free (helper);
+ return;
+ }
+ gs_plugin_loader_app_action_async (priv->plugin_loader,
+ helper->app,
+ GS_PLUGIN_LOADER_ACTION_REMOVE,
+ priv->cancellable,
+ gs_page_app_removed_cb,
+ helper);
+}
+
+static void
+gs_page_app_installed_cb (GObject *source,
+ GAsyncResult *res,
gpointer user_data)
{
GError *last_error;
@@ -72,6 +127,27 @@ gs_page_app_installed_cb (GObject *source,
res,
&error);
if (!ret) {
+ /* try to authenticate then retry */
+ if (g_error_matches (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_AUTH_REQUIRED)) {
+ g_autoptr(GError) error_local = NULL;
+ GtkWidget *dialog;
+ dialog = gs_auth_dialog_new (priv->plugin_loader,
+ helper->app,
+ gs_utils_get_error_value (error),
+ &error_local);
+ if (dialog == NULL) {
+ g_warning ("%s", error_local->message);
+ return;
+ }
+ gs_shell_modal_dialog_present (priv->shell, GTK_DIALOG (dialog));
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gs_page_install_authenticate_cb),
+ g_steal_pointer (&helper));
+ return;
+ }
+
g_warning ("failed to install %s: %s",
gs_app_get_id (helper->app),
error->message);
@@ -124,6 +200,27 @@ gs_page_app_removed_cb (GObject *source,
res,
&error);
if (!ret) {
+ /* try to authenticate then retry */
+ if (g_error_matches (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_AUTH_REQUIRED)) {
+ g_autoptr(GError) error_local = NULL;
+ GtkWidget *dialog;
+ dialog = gs_auth_dialog_new (priv->plugin_loader,
+ helper->app,
+ gs_utils_get_error_value (error),
+ &error_local);
+ if (dialog == NULL) {
+ g_warning ("%s", error_local->message);
+ return;
+ }
+ gs_shell_modal_dialog_present (priv->shell, GTK_DIALOG (dialog));
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gs_page_remove_authenticate_cb),
+ g_steal_pointer (&helper));
+ return;
+ }
+
g_warning ("failed to remove: %s", error->message);
gs_app_notify_failed_modal (helper->app,
gs_shell_get_window (priv->shell),
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index a8a47a1..39549e0 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -39,6 +39,7 @@ typedef struct
GsPluginStatus status_last;
AsProfile *profile;
SoupSession *soup_session;
+ GPtrArray *auth_array;
GMutex pending_apps_mutex;
GPtrArray *pending_apps;
@@ -74,6 +75,7 @@ typedef struct {
GsCategory *category;
GsApp *app;
GsReview *review;
+ GsAuth *auth;
} GsPluginLoaderAsyncState;
static void
@@ -83,6 +85,8 @@ gs_plugin_loader_free_async_state (GsPluginLoaderAsyncState *state)
g_object_unref (state->category);
if (state->app != NULL)
g_object_unref (state->app);
+ if (state->auth != NULL)
+ g_object_unref (state->auth);
if (state->review != NULL)
g_object_unref (state->review);
@@ -589,6 +593,16 @@ gs_plugin_loader_set_app_error (GsApp *app, GError *error)
}
}
+static gboolean
+gs_plugin_loader_is_auth_error (GError *err)
+{
+ if (g_error_matches (err, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_REQUIRED))
+ return TRUE;
+ if (g_error_matches (err, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_INVALID))
+ return TRUE;
+ return FALSE;
+}
+
/**
* gs_plugin_loader_run_action:
**/
@@ -630,6 +644,13 @@ gs_plugin_loader_run_action (GsPluginLoader *plugin_loader,
ret = plugin_func (plugin, app, cancellable, &error_local);
g_rw_lock_reader_unlock (&plugin->rwlock);
if (!ret) {
+ /* abort early to allow main thread to process */
+ if (gs_plugin_loader_is_auth_error (error_local)) {
+ g_propagate_error (error, error_local);
+ error_local = NULL;
+ return FALSE;
+ }
+
g_warning ("failed to call %s on %s: %s",
function_name, plugin->name,
error_local->message);
@@ -2445,6 +2466,13 @@ gs_plugin_loader_review_action_thread_cb (GTask *task,
cancellable, &error_local);
g_rw_lock_reader_unlock (&plugin->rwlock);
if (!ret) {
+ /* abort early to allow main thread to process */
+ if (gs_plugin_loader_is_auth_error (error_local)) {
+ g_task_return_error (task, error_local);
+ error_local = NULL;
+ return;
+ }
+
g_warning ("failed to call %s on %s: %s",
state->function_name, plugin->name,
error_local->message);
@@ -2766,6 +2794,134 @@ gs_plugin_loader_review_action_async (GsPluginLoader *plugin_loader,
g_task_run_in_thread (task, gs_plugin_loader_review_action_thread_cb);
}
+/******************************************************************************/
+
+/**
+ * gs_plugin_loader_auth_action_thread_cb:
+ **/
+static void
+gs_plugin_loader_auth_action_thread_cb (GTask *task,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GError *error = NULL;
+ GsPluginLoaderAsyncState *state = (GsPluginLoaderAsyncState *) task_data;
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
+ GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+ GsPlugin *plugin;
+ GsPluginAuthFunc plugin_func = NULL;
+ gboolean exists;
+ gboolean ret;
+ guint i;
+
+ /* run each plugin */
+ for (i = 0; i < priv->plugins->len; i++) {
+ g_autoptr(AsProfileTask) ptask = NULL;
+ g_autoptr(GError) error_local = NULL;
+
+ plugin = g_ptr_array_index (priv->plugins, i);
+ if (!plugin->enabled)
+ continue;
+ if (g_cancellable_set_error_if_cancelled (cancellable, &error))
+ g_task_return_error (task, error);
+
+ exists = g_module_symbol (plugin->module,
+ state->function_name,
+ (gpointer *) &plugin_func);
+ if (!exists)
+ continue;
+ ptask = as_profile_start (priv->profile,
+ "GsPlugin::%s(%s)",
+ plugin->name,
+ state->function_name);
+ g_rw_lock_reader_lock (&plugin->rwlock);
+ ret = plugin_func (plugin, state->auth, cancellable, &error_local);
+ g_rw_lock_reader_unlock (&plugin->rwlock);
+ if (!ret) {
+ /* badly behaved plugin */
+ if (error_local == NULL) {
+ g_critical ("%s did not set error for %s",
+ plugin->name,
+ state->function_name);
+ continue;
+ }
+
+ /* stop running other plugins on failure */
+ g_task_return_error (task, error_local);
+ error_local = NULL;
+ return;
+ }
+ gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
+ }
+
+ g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * gs_plugin_loader_auth_action_async:
+ **/
+void
+gs_plugin_loader_auth_action_async (GsPluginLoader *plugin_loader,
+ GsAuth *auth,
+ GsAuthAction action,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GsPluginLoaderAsyncState *state;
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader));
+ g_return_if_fail (GS_IS_AUTH (auth));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ /* save state */
+ state = g_slice_new0 (GsPluginLoaderAsyncState);
+ state->auth = g_object_ref (auth);
+
+ switch (action) {
+ case GS_AUTH_ACTION_LOGIN:
+ state->function_name = "gs_plugin_auth_login";
+ break;
+ case GS_AUTH_ACTION_LOGOUT:
+ state->function_name = "gs_plugin_auth_logout";
+ break;
+ case GS_AUTH_ACTION_REGISTER:
+ state->function_name = "gs_plugin_auth_register";
+ break;
+ case GS_AUTH_ACTION_LOST_PASSWORD:
+ state->function_name = "gs_plugin_auth_lost_password";
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ /* run in a thread */
+ task = g_task_new (plugin_loader, cancellable, callback, user_data);
+ g_task_set_task_data (task, state, (GDestroyNotify) gs_plugin_loader_free_async_state);
+ g_task_run_in_thread (task, gs_plugin_loader_auth_action_thread_cb);
+}
+
+/**
+ * gs_plugin_loader_auth_action_finish:
+ **/
+gboolean
+gs_plugin_loader_auth_action_finish (GsPluginLoader *plugin_loader,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), FALSE);
+ g_return_val_if_fail (G_IS_TASK (res), FALSE);
+ g_return_val_if_fail (g_task_is_valid (res, plugin_loader), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+/******************************************************************************/
+
/**
* gs_plugin_loader_app_action_finish:
*
@@ -2994,6 +3150,7 @@ gs_plugin_loader_open_plugin (GsPluginLoader *plugin_loader,
plugin->updates_changed_user_data = plugin_loader;
plugin->profile = g_object_ref (priv->profile);
plugin->soup_session = g_object_ref (priv->soup_session);
+ plugin->auth_array = g_ptr_array_ref (priv->auth_array);
plugin->scale = gs_plugin_loader_get_scale (plugin_loader);
g_debug ("opened plugin %s: %s", filename, plugin->name);
@@ -3034,6 +3191,25 @@ gs_plugin_loader_get_scale (GsPluginLoader *plugin_loader)
}
/**
+ * gs_plugin_loader_get_auth_by_id:
+ */
+GsAuth *
+gs_plugin_loader_get_auth_by_id (GsPluginLoader *plugin_loader,
+ const gchar *provider_id)
+{
+ GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+ guint i;
+
+ /* match on ID */
+ for (i = 0; i < priv->auth_array->len; i++) {
+ GsAuth *auth = g_ptr_array_index (priv->auth_array, i);
+ if (g_strcmp0 (gs_auth_get_provider_id (auth), provider_id) == 0)
+ return auth;
+ }
+ return NULL;
+}
+
+/**
* gs_plugin_loader_set_location:
*/
void
@@ -3308,6 +3484,7 @@ gs_plugin_loader_dispose (GObject *object)
g_clear_object (&priv->soup_session);
g_clear_object (&priv->profile);
g_clear_object (&priv->settings);
+ g_clear_pointer (&priv->auth_array, g_ptr_array_unref);
g_clear_pointer (&priv->pending_apps, g_ptr_array_unref);
G_OBJECT_CLASS (gs_plugin_loader_parent_class)->dispose (object);
@@ -3379,6 +3556,7 @@ gs_plugin_loader_init (GsPluginLoader *plugin_loader)
priv->plugins = g_ptr_array_new_with_free_func ((GDestroyNotify) gs_plugin_loader_plugin_free);
priv->status_last = GS_PLUGIN_STATUS_LAST;
priv->pending_apps = g_ptr_array_new_with_free_func ((GFreeFunc) g_object_unref);
+ priv->auth_array = g_ptr_array_new_with_free_func ((GFreeFunc) g_object_unref);
priv->profile = as_profile_new ();
priv->settings = g_settings_new ("org.gnome.software");
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index 2957079..01af82a 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -25,6 +25,7 @@
#include <glib-object.h>
#include "gs-app.h"
+#include "gs-auth.h"
#include "gs-category.h"
#include "gs-plugin.h"
@@ -196,6 +197,8 @@ gboolean gs_plugin_loader_get_enabled (GsPluginLoader *plugin_loader,
const gchar *plugin_name);
void gs_plugin_loader_set_location (GsPluginLoader *plugin_loader,
const gchar *location);
+GsAuth *gs_plugin_loader_get_auth_by_id (GsPluginLoader *plugin_loader,
+ const gchar *provider_id);
gint gs_plugin_loader_get_scale (GsPluginLoader *plugin_loader);
void gs_plugin_loader_set_scale (GsPluginLoader *plugin_loader,
gint scale);
@@ -227,6 +230,15 @@ void gs_plugin_loader_review_action_async (GsPluginLoader
*plugin_loader,
gboolean gs_plugin_loader_review_action_finish (GsPluginLoader *plugin_loader,
GAsyncResult *res,
GError **error);
+gboolean gs_plugin_loader_auth_action_finish (GsPluginLoader *plugin_loader,
+ GAsyncResult *res,
+ GError **error);
+void gs_plugin_loader_auth_action_async (GsPluginLoader *plugin_loader,
+ GsAuth *auth,
+ GsAuthAction action,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
gboolean gs_plugin_loader_refresh_finish (GsPluginLoader *plugin_loader,
GAsyncResult *res,
GError **error);
diff --git a/src/gs-plugin.c b/src/gs-plugin.c
index a4ef3da..5bbc805 100644
--- a/src/gs-plugin.c
+++ b/src/gs-plugin.c
@@ -79,6 +79,32 @@ gs_plugin_set_enabled (GsPlugin *plugin, gboolean enabled)
}
/**
+ * gs_plugin_add_auth:
+ **/
+void
+gs_plugin_add_auth (GsPlugin *plugin, GsAuth *auth)
+{
+ g_ptr_array_add (plugin->auth_array, g_object_ref (auth));
+}
+
+/**
+ * gs_plugin_get_auth_by_id:
+ **/
+GsAuth *
+gs_plugin_get_auth_by_id (GsPlugin *plugin, const gchar *provider_id)
+{
+ guint i;
+
+ /* match on ID */
+ for (i = 0; i < plugin->auth_array->len; i++) {
+ GsAuth *auth = g_ptr_array_index (plugin->auth_array, i);
+ if (g_strcmp0 (gs_auth_get_provider_id (auth), provider_id) == 0)
+ return auth;
+ }
+ return NULL;
+}
+
+/**
* gs_plugin_check_distro_id:
**/
gboolean
diff --git a/src/gs-plugin.h b/src/gs-plugin.h
index f13bd0e..08e8fd0 100644
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@ -31,6 +31,7 @@
#include "gs-app.h"
#include "gs-category.h"
+#include "gs-auth.h"
G_BEGIN_DECLS
@@ -79,6 +80,7 @@ struct GsPlugin {
gpointer updates_changed_user_data;
AsProfile *profile;
SoupSession *soup_session;
+ GPtrArray *auth_array;
GRWLock rwlock;
};
@@ -89,6 +91,10 @@ typedef enum {
GS_PLUGIN_ERROR_NO_NETWORK,
GS_PLUGIN_ERROR_NO_SECURITY,
GS_PLUGIN_ERROR_NO_SPACE,
+ GS_PLUGIN_ERROR_AUTH_REQUIRED,
+ GS_PLUGIN_ERROR_AUTH_INVALID,
+ GS_PLUGIN_ERROR_PIN_REQUIRED,
+ /*< private >*/
GS_PLUGIN_ERROR_LAST
} GsPluginError;
@@ -178,6 +184,10 @@ typedef gboolean (*GsPluginReviewFunc) (GsPlugin *plugin,
GsReview *review,
GCancellable *cancellable,
GError **error);
+typedef gboolean (*GsPluginAuthFunc) (GsPlugin *plugin,
+ GsAuth *auth,
+ GCancellable *cancellable,
+ GError **error);
typedef gboolean (*GsPluginRefineFunc) (GsPlugin *plugin,
GList **list,
GsPluginRefineFlags flags,
@@ -208,6 +218,10 @@ void gs_plugin_initialize (GsPlugin *plugin);
void gs_plugin_destroy (GsPlugin *plugin);
void gs_plugin_set_enabled (GsPlugin *plugin,
gboolean enabled);
+void gs_plugin_add_auth (GsPlugin *plugin,
+ GsAuth *auth);
+GsAuth *gs_plugin_get_auth_by_id (GsPlugin *plugin,
+ const gchar *provider_id);
GBytes *gs_plugin_download_data (GsPlugin *plugin,
GsApp *app,
const gchar *uri,
@@ -390,6 +404,22 @@ gboolean gs_plugin_update (GsPlugin *plugin,
GList *apps,
GCancellable *cancellable,
GError **error);
+gboolean gs_plugin_auth_login (GsPlugin *plugin,
+ GsAuth *auth,
+ GCancellable *cancellable,
+ GError **error);
+gboolean gs_plugin_auth_logout (GsPlugin *plugin,
+ GsAuth *auth,
+ GCancellable *cancellable,
+ GError **error);
+gboolean gs_plugin_auth_lost_password (GsPlugin *plugin,
+ GsAuth *auth,
+ GCancellable *cancellable,
+ GError **error);
+gboolean gs_plugin_auth_register (GsPlugin *plugin,
+ GsAuth *auth,
+ GCancellable *cancellable,
+ GError **error);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsAppList, gs_plugin_list_free)
diff --git a/src/gs-self-test.c b/src/gs-self-test.c
index 820d9e9..2958e76 100644
--- a/src/gs-self-test.c
+++ b/src/gs-self-test.c
@@ -456,6 +456,45 @@ gs_plugin_loader_dpkg_func (GsPluginLoader *plugin_loader)
g_assert (gs_app_get_local_file (app) != NULL);
}
+static void
+gs_auth_secret_func (void)
+{
+ gboolean ret;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GsAuth) auth1 = NULL;
+ g_autoptr(GsAuth) auth2 = NULL;
+
+ /* save secrets to disk */
+ auth1 = gs_auth_new ("self-test");
+ gs_auth_set_provider_schema (auth1, "org.gnome.Software.Dummy");
+ gs_auth_set_username (auth1, "hughsie");
+ gs_auth_set_password (auth1, "foobarbaz");
+ gs_auth_add_metadata (auth1, "day", "monday");
+ ret = gs_auth_store_save (auth1,
+ GS_AUTH_STORE_FLAG_USERNAME |
+ GS_AUTH_STORE_FLAG_PASSWORD |
+ GS_AUTH_STORE_FLAG_METADATA,
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ /* load secrets from disk */
+ auth2 = gs_auth_new ("self-test");
+ gs_auth_add_metadata (auth2, "day", NULL);
+ gs_auth_add_metadata (auth2, "notgoingtoexist", NULL);
+ gs_auth_set_provider_schema (auth2, "org.gnome.Software.Dummy");
+ ret = gs_auth_store_load (auth2,
+ GS_AUTH_STORE_FLAG_USERNAME |
+ GS_AUTH_STORE_FLAG_PASSWORD |
+ GS_AUTH_STORE_FLAG_METADATA,
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ g_assert_cmpstr (gs_auth_get_username (auth2), ==, "hughsie");
+ g_assert_cmpstr (gs_auth_get_password (auth2), ==, "foobarbaz");
+ g_assert_cmpstr (gs_auth_get_metadata_item (auth2, "day"), ==, "monday");
+}
+
int
main (int argc, char **argv)
{
@@ -541,6 +580,7 @@ main (int argc, char **argv)
/* generic tests go here */
g_test_add_func ("/gnome-software/app", gs_app_func);
g_test_add_func ("/gnome-software/plugin", gs_plugin_func);
+ g_test_add_func ("/gnome-software/auth{secret}", gs_auth_secret_func);
/* we can only load this once per process */
plugin_loader = gs_plugin_loader_new ();
diff --git a/src/gs-shell-details.c b/src/gs-shell-details.c
index 771981e..782bb84 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -30,6 +30,7 @@
#include "gs-shell-details.h"
#include "gs-app-addon-row.h"
+#include "gs-auth-dialog.h"
#include "gs-history-dialog.h"
#include "gs-screenshot-image.h"
#include "gs-progress-button.h"
@@ -976,24 +977,88 @@ gs_shell_details_refresh_addons (GsShellDetails *self)
static void gs_shell_details_refresh_reviews (GsShellDetails *self);
+typedef struct {
+ GsShellDetails *self;
+ GsReview *review;
+ GsApp *app;
+ GsReviewAction action;
+} GsShellDetailsReviewHelper;
+
+static void
+gs_shell_details_review_helper_free (GsShellDetailsReviewHelper *helper)
+{
+ g_object_unref (helper->self);
+ g_object_unref (helper->review);
+ g_object_unref (helper->app);
+ g_free (helper);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsShellDetailsReviewHelper, gs_shell_details_review_helper_free);
+
+static void
+gs_shell_details_app_set_review_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data);
+
+static void
+gs_shell_details_authenticate_cb (GtkDialog *dialog,
+ GtkResponseType response_type,
+ GsShellDetailsReviewHelper *helper)
+{
+ /* unmap the dialog */
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ if (response_type != GTK_RESPONSE_OK) {
+ gs_shell_details_review_helper_free (helper);
+ return;
+ }
+ gs_plugin_loader_review_action_async (helper->self->plugin_loader,
+ helper->app,
+ helper->review,
+ helper->action,
+ helper->self->cancellable,
+ gs_shell_details_app_set_review_cb,
+ helper);
+}
+
/**
* gs_shell_details_app_set_review_cb:
**/
static void
gs_shell_details_app_set_review_cb (GObject *source,
- GAsyncResult *res,
- gpointer user_data)
+ GAsyncResult *res,
+ gpointer user_data)
{
GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
- GsShellDetails *self = GS_SHELL_DETAILS (user_data);
+ g_autoptr(GsShellDetailsReviewHelper) helper = (GsShellDetailsReviewHelper *) user_data;
g_autoptr(GError) error = NULL;
if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
- g_warning ("failed to set review %s: %s",
- gs_app_get_id (self->app), error->message);
+ /* try to authenticate then retry */
+ if (g_error_matches (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_AUTH_REQUIRED)) {
+ g_autoptr(GError) error_local = NULL;
+ GtkWidget *dialog;
+ dialog = gs_auth_dialog_new (helper->self->plugin_loader,
+ helper->app,
+ gs_utils_get_error_value (error),
+ &error_local);
+ if (dialog == NULL) {
+ g_warning ("%s", error_local->message);
+ return;
+ }
+ gs_shell_modal_dialog_present (helper->self->shell, GTK_DIALOG (dialog));
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gs_shell_details_authenticate_cb),
+ g_steal_pointer (&helper));
+ return;
+ }
+ g_warning ("failed to set review on %s: %s",
+ gs_app_get_id (helper->app), error->message);
return;
}
- gs_shell_details_refresh_reviews (self);
+ gs_shell_details_refresh_reviews (helper->self);
}
static void
@@ -1001,13 +1066,18 @@ gs_shell_details_review_button_clicked_cb (GsReviewRow *row,
GsReviewAction action,
GsShellDetails *self)
{
+ GsShellDetailsReviewHelper *helper = g_new0 (GsShellDetailsReviewHelper, 1);
+ helper->self = g_object_ref (self);
+ helper->app = g_object_ref (self->app);
+ helper->review = g_object_ref (gs_review_row_get_review (row));
+ helper->action = action;
gs_plugin_loader_review_action_async (self->plugin_loader,
- self->app,
- gs_review_row_get_review (row),
- action,
+ helper->app,
+ helper->review,
+ helper->action,
self->cancellable,
gs_shell_details_app_set_review_cb,
- self);
+ helper);
}
static void
@@ -1446,14 +1516,14 @@ gs_shell_details_review_response_cb (GtkDialog *dialog,
g_autofree gchar *text = NULL;
g_autoptr(GDateTime) now = NULL;
g_autoptr(GsReview) review = NULL;
+ GsShellDetailsReviewHelper *helper;
GsReviewDialog *rdialog = GS_REVIEW_DIALOG (dialog);
- /* unmap the dialog */
- gtk_widget_destroy (GTK_WIDGET (dialog));
-
/* not agreed */
- if (response != GTK_RESPONSE_OK)
+ if (response != GTK_RESPONSE_OK) {
+ gtk_widget_destroy (GTK_WIDGET (dialog));
return;
+ }
review = gs_review_new ();
gs_review_set_summary (review, gs_review_dialog_get_summary (rdialog));
@@ -1465,13 +1535,21 @@ gs_shell_details_review_response_cb (GtkDialog *dialog,
gs_review_set_date (review, now);
/* call into the plugins to set the new value */
+ helper = g_new0 (GsShellDetailsReviewHelper, 1);
+ helper->self = g_object_ref (self);
+ helper->app = g_object_ref (self->app);
+ helper->review = g_object_ref (review);
+ helper->action = GS_REVIEW_ACTION_SUBMIT;
gs_plugin_loader_review_action_async (self->plugin_loader,
- self->app,
- review,
- GS_REVIEW_ACTION_SUBMIT,
+ helper->app,
+ helper->review,
+ helper->action,
self->cancellable,
gs_shell_details_app_set_review_cb,
- self);
+ helper);
+
+ /* unmap the dialog */
+ gtk_widget_destroy (GTK_WIDGET (dialog));
}
/**
diff --git a/src/gs-utils.c b/src/gs-utils.c
index 66b004e..3771d2d 100644
--- a/src/gs-utils.c
+++ b/src/gs-utils.c
@@ -703,6 +703,28 @@ insert_details_widget (GtkMessageDialog *dialog, const gchar *details)
}
/**
+ * gs_utils_get_error_value:
+ * @error: A GError
+ *
+ * Gets the machine-readable value stored in the error message.
+ * The machine readable string is after the first "@", e.g.
+ * message = "Requires authentication with @aaa"
+ *
+ * Returns: a string, or %NULL
+ */
+const gchar *
+gs_utils_get_error_value (const GError *error)
+{
+ gchar *str;
+ if (error == NULL)
+ return NULL;
+ str = g_strstr_len (error->message, -1, "@");
+ if (str == NULL)
+ return NULL;
+ return (const gchar *) str + 1;
+}
+
+/**
* gs_utils_show_error_dialog:
* @parent: transient parent, or NULL for none
* @title: the title for the dialog
diff --git a/src/gs-utils.h b/src/gs-utils.h
index b6b0ab5..d5b157b 100644
--- a/src/gs-utils.h
+++ b/src/gs-utils.h
@@ -70,6 +70,7 @@ gboolean gs_utils_is_current_desktop (const gchar *name);
gboolean gs_utils_is_current_desktop (const gchar *name);
void gs_utils_widget_set_custom_css (GtkWidget *widget,
const gchar *css);
+const gchar *gs_utils_get_error_value (const GError *error);
void gs_utils_show_error_dialog (GtkWindow *parent,
const gchar *title,
const gchar *msg,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]