[gitg] Implement basic user/pass auth



commit 17a60f9c220bd5fdba34bd588a7110dd22385ac4
Author: Jesse van den Kieboom <jessevdk gmail com>
Date:   Wed Dec 24 14:32:00 2014 +0100

    Implement basic user/pass auth

 configure.ac                                    |    2 +
 gitg/gitg-remote-manager.vala                   |   38 +---
 libgitg/Makefile.am                             |    3 +
 libgitg/gitg-authentication-dialog.vala         |   95 ++++++++
 libgitg/gitg-credentials-manager.vala           |  290 +++++++++++++++++++++++
 libgitg/resources/gitg-authentication-dialog.ui |  205 ++++++++++++++++
 libgitg/resources/resources.xml                 |    1 +
 7 files changed, 600 insertions(+), 34 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 9cc66c2..179b381 100644
--- a/configure.ac
+++ b/configure.ac
@@ -115,6 +115,7 @@ PKG_CHECK_MODULES(LIBGITG, [
        gsettings-desktop-schemas
        gee-0.8
        json-glib-1.0
+       libsecret-1
 ])
 
 AC_MSG_CHECKING([for libgit2-glib threading support])
@@ -274,6 +275,7 @@ GITG_PLUGIN_VALAFLAGS="                             \
        --pkg gee-0.8                           \
        --pkg json-glib-1.0                     \
        --pkg libsoup-2.4                       \
+       --pkg libsecret-1                       \
        --pkg $WEBKIT_PKGCONFIG                 \
        --pkg config                            \
        --pkg gd-1.0                            \
diff --git a/gitg/gitg-remote-manager.vala b/gitg/gitg-remote-manager.vala
index a7d1a09..faf62ab 100644
--- a/gitg/gitg-remote-manager.vala
+++ b/gitg/gitg-remote-manager.vala
@@ -22,50 +22,20 @@ namespace Gitg
 
 class RemoteManager : Object, GitgExt.RemoteLookup
 {
-       class CredSshInteractive : Ggit.CredSshInteractive
-       {
-               public CredSshInteractive(string username) throws Error
-               {
-                       Object(username: username);
-
-                       ((Initable)this).init(null);
-               }
-
-               protected override void prompt(Ggit.CredSshInteractivePrompt[] prompts)
-               {
-                       // TODO
-               }
-       }
-
        class Callbacks : Ggit.RemoteCallbacks
        {
-               private weak Remote d_remote;
-               private Window d_window;
+               private CredentialsManager d_credentials;
 
-               public Callbacks(Remote remote, Window window)
+               public Callbacks(Gitg.Remote remote, Gtk.Window window)
                {
-                       d_remote = remote;
-                       d_window = window;
+                       d_credentials = new CredentialsManager(remote, window);
                }
 
                protected override Ggit.Cred? credentials(string        url,
                                                          string?       username,
                                                          Ggit.Credtype allowed_types) throws Error
                {
-                       if ((allowed_types & Ggit.Credtype.SSH_KEY) != 0)
-                       {
-                               return new Ggit.CredSshKeyFromAgent(username);
-                       }
-                       else if ((allowed_types & Ggit.Credtype.SSH_INTERACTIVE) != 0)
-                       {
-                               return new CredSshInteractive(username);
-                       }
-                       else if ((allowed_types & Ggit.Credtype.USERPASS_PLAINTEXT) != 0)
-                       {
-                               // TODO: query for user + pass
-                       }
-
-                       return null;
+                       return d_credentials.credentials(url, username, allowed_types);
                }
        }
 
diff --git a/libgitg/Makefile.am b/libgitg/Makefile.am
index 26c58eb..e96579a 100644
--- a/libgitg/Makefile.am
+++ b/libgitg/Makefile.am
@@ -27,6 +27,7 @@ libgitg_libgitg_1_0_la_VALAFLAGS =    \
        --pkg $(WEBKIT_PKGCONFIG)       \
        --pkg gee-0.8                   \
        --pkg json-glib-1.0             \
+       --pkg libsecret-1               \
        --pkg gio-unix-2.0              \
        --pkg gitg-js-utils             \
        --pkg gdesktop-enums-3.0        \
@@ -43,6 +44,7 @@ libgitg_libgitg_1_0_la_VALAFLAGS =    \
 libgitg_libgitg_1_0_la_VALASOURCES =                   \
        libgitg/gitg-assembly-info.vala                 \
        libgitg/gitg-async.vala                         \
+       libgitg/gitg-authentication-dialog.vala         \
        libgitg/gitg-branch.vala                        \
        libgitg/gitg-branch-base.vala                   \
        libgitg/gitg-repository.vala                    \
@@ -57,6 +59,7 @@ libgitg_libgitg_1_0_la_VALASOURCES =                  \
        libgitg/gitg-label-renderer.vala                \
        libgitg/gitg-cell-renderer-lanes.vala           \
        libgitg/gitg-commit-list-view.vala              \
+       libgitg/gitg-credentials-manager.vala           \
        libgitg/gitg-diff-view.vala                     \
        libgitg/gitg-diff-view-options.vala             \
        libgitg/gitg-diff-view-request.vala             \
diff --git a/libgitg/gitg-authentication-dialog.vala b/libgitg/gitg-authentication-dialog.vala
new file mode 100644
index 0000000..eef47dc
--- /dev/null
+++ b/libgitg/gitg-authentication-dialog.vala
@@ -0,0 +1,95 @@
+/*
+ * This file is part of gitg
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gitg 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.
+ *
+ * gitg 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 gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Gitg
+{
+
+public enum AuthenticationLifeTime
+{
+       FORGET,
+       SESSION,
+       FOREVER
+}
+
+[GtkTemplate ( ui = "/org/gnome/gitg/gtk/gitg-authentication-dialog.ui" )]
+public class AuthenticationDialog : Gtk.Dialog
+{
+       [GtkChild ( name = "label_title" )]
+       private Gtk.Label d_label_title;
+
+       [GtkChild ( name = "entry_username" )]
+       private Gtk.Entry d_entry_username;
+
+       [GtkChild ( name = "entry_password" )]
+       private Gtk.Entry d_entry_password;
+
+       [GtkChild ( name = "radio_button_forget" )]
+       private Gtk.RadioButton d_radio_button_forget;
+
+       [GtkChild ( name = "radio_button_session" )]
+       private Gtk.RadioButton d_radio_button_session;
+
+       public AuthenticationDialog(string url, string? username)
+       {
+               Object(use_header_bar: 1);
+
+               set_default_response(Gtk.ResponseType.OK);
+
+               /* Translators: %s will be replaced with a URL indicating the resource
+                  for which the authentication is required. */
+               d_label_title.label = _("Password required for %s").printf(url);
+
+               if (username != null)
+               {
+                       d_entry_username.text = username;
+                       d_entry_password.grab_focus();
+               }
+       }
+
+       public string username
+       {
+               get { return d_entry_username.text; }
+       }
+
+       public string password
+       {
+               get { return d_entry_password.text; }
+       }
+
+       public AuthenticationLifeTime life_time
+       {
+               get
+               {
+                       if (d_radio_button_forget.active)
+                       {
+                               return AuthenticationLifeTime.FORGET;
+                       }
+                       else if (d_radio_button_session.active)
+                       {
+                               return AuthenticationLifeTime.SESSION;
+                       }
+                       else
+                       {
+                               return AuthenticationLifeTime.FOREVER;
+                       }
+               }
+       }
+}
+
+}
diff --git a/libgitg/gitg-credentials-manager.vala b/libgitg/gitg-credentials-manager.vala
new file mode 100644
index 0000000..8250ea6
--- /dev/null
+++ b/libgitg/gitg-credentials-manager.vala
@@ -0,0 +1,290 @@
+/*
+ * This file is part of gitg
+ *
+ * Copyright (C) 2012 - Jesse van den Kieboom
+ *
+ * gitg 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.
+ *
+ * gitg 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 gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Gitg
+{
+
+public errordomain CredentialsError
+{
+       CANCELLED
+}
+
+public class CredentialsManager
+{
+       class CredSshInteractive : Ggit.CredSshInteractive
+       {
+               private weak Remote d_remote;
+
+               public CredSshInteractive(Remote remote, string username) throws Error
+               {
+                       Object(username: username);
+
+                       d_remote = remote;
+
+                       ((Initable)this).init(null);
+               }
+
+               protected override void prompt(Ggit.CredSshInteractivePrompt[] prompts)
+               {
+                       // TODO
+               }
+       }
+
+       private weak Remote d_remote;
+       private Gtk.Window d_window;
+       private Gee.HashMap<string, string>? d_usermap;
+
+       private static Secret.Schema s_secret_schema;
+
+       static construct
+       {
+               s_secret_schema = new Secret.Schema("org.gnome.Gitg.Credentials",
+                                                   Secret.SchemaFlags.NONE,
+                                                   "scheme", Secret.SchemaAttributeType.STRING,
+                                                   "host", Secret.SchemaAttributeType.STRING,
+                                                   "user", Secret.SchemaAttributeType.STRING);
+       }
+
+       public CredentialsManager(Remote remote, Gtk.Window window)
+       {
+               d_remote = remote;
+               d_window = window;
+       }
+
+       private string? lookup_user(string host)
+       {
+               if (d_usermap == null)
+               {
+                       d_usermap = new Gee.HashMap<string, string?>();
+
+                       try
+                       {
+                               var config = d_remote.get_owner().get_config();
+                               var r = new Regex("credential\\.(.*)\\.username");
+
+                               config.match_foreach(r, (info, value) => {
+                                       d_usermap[info.fetch(1)] = value;
+                                       return 0;
+                               });
+                       }
+                       catch (Error e)
+                       {
+                               stderr.printf("Could not get username from git config: %s\n", e.message);
+                       }
+               }
+
+               return d_usermap[host];
+       }
+
+       private Ggit.Cred? user_pass_dialog(string url, string scheme, string host, string? username) throws 
Error
+       {
+               var mutex = Mutex();
+               mutex.lock();
+
+               var cond = Cond();
+
+               Gtk.ResponseType response = Gtk.ResponseType.CANCEL;
+
+               string password = "";
+               string newusername = "";
+               AuthenticationLifeTime lifetime = AuthenticationLifeTime.FORGET;
+
+               Idle.add(() => {
+                       var d = new AuthenticationDialog(url, username);
+                       d.set_transient_for(d_window);
+
+                       response = (Gtk.ResponseType)d.run();
+
+                       if (response == Gtk.ResponseType.OK)
+                       {
+                               newusername = d.username;
+                               password = d.password;
+                               lifetime = d.life_time;
+                       }
+
+                       d.destroy();
+
+                       mutex.lock();
+                       cond.signal();
+                       mutex.unlock();
+
+                       return false;
+               });
+
+               cond.wait(mutex);
+               mutex.unlock();
+
+               if (response != Gtk.ResponseType.OK)
+               {
+                       throw new CredentialsError.CANCELLED("cancelled by user");
+               }
+
+               // Save username in config
+               if (username == null || newusername != username)
+               {
+                       if (d_usermap == null)
+                       {
+                               d_usermap = new Gee.HashMap<string, string?>();
+                       }
+
+                       try
+                       {
+                               var repo = d_remote.get_owner();
+                               var config = repo.get_config();
+                               var hid = @"$(scheme)://$(host)";
+
+                               config.set_string(@"credential.$(hid).username", newusername);
+
+                               d_usermap[hid] = newusername;
+                       }
+                       catch (Error e)
+                       {
+                               stderr.printf("Failed to store username in config: %s\n", e.message);
+                       }
+               }
+
+               var attributes = new HashTable<string, string>(str_hash, str_equal);
+               attributes["scheme"] = scheme;
+               attributes["host"] = host;
+               attributes["user"] = newusername;
+
+
+               // Save secret
+               if (lifetime != AuthenticationLifeTime.FORGET)
+               {
+                       string? collection = null;
+
+                       if (lifetime == AuthenticationLifeTime.SESSION)
+                       {
+                               collection = Secret.COLLECTION_SESSION;
+                       }
+
+                       Secret.password_storev.begin(s_secret_schema,
+                                                    attributes,
+                                                    collection,
+                                                    @"$(scheme)://$(host)",
+                                                    password,
+                                                    null,
+                                                    (obj, res) => {
+                               try
+                               {
+                                       Secret.password_storev.end(res);
+                               }
+                               catch (Error e)
+                               {
+                                       stderr.printf("Failed to store secret in keyring: %s\n", e.message);
+                               }
+                       });
+               }
+               else
+               {
+                       Secret.password_clearv.begin(s_secret_schema, attributes, null, (obj, res) => {
+                               try
+                               {
+                                       Secret.password_clearv.end(res);
+                               }
+                               catch (Error e)
+                               {
+                                       stderr.printf("Failed to clear secret from keyring: %s\n", e.message);
+                               }
+                       });
+               }
+
+               return new Ggit.CredPlaintext(newusername, password);
+       }
+
+       private Ggit.Cred? query_user_pass(string url, string? username) throws Error
+       {
+               string? user;
+
+               var uri = new Soup.URI(url);
+               var host = uri.get_host();
+
+               if (!uri.uses_default_port())
+               {
+                       host = @"$(host):$(uri.get_port())";
+               }
+
+               var scheme = uri.get_scheme();
+
+               if (username == null)
+               {
+                       // Try to obtain username from config
+                       user = lookup_user(@"$scheme://$host");
+               }
+               else
+               {
+                       user = username;
+               }
+
+               if (user != null)
+               {
+                       string? secret = null;
+
+                       try
+                       {
+                               secret = Secret.password_lookup_sync(s_secret_schema, null,
+                                                                    "scheme", scheme,
+                                                                    "host", host,
+                                                                    "user", user);
+                       }
+                       catch {}
+
+                       if (secret == null)
+                       {
+                               return user_pass_dialog(url, scheme, host, user);
+                       }
+
+                       try
+                       {
+                               return new Ggit.CredPlaintext(user, secret);
+                       }
+                       catch (Error e)
+                       {
+                               return user_pass_dialog(url, scheme, host, user);
+                       }
+               }
+               else
+               {
+                       return user_pass_dialog(url, scheme, host, user);
+               }
+       }
+
+       public Ggit.Cred? credentials(string        url,
+                                     string?       username,
+                                     Ggit.Credtype allowed_types) throws Error
+       {
+               if ((allowed_types & Ggit.Credtype.SSH_KEY) != 0)
+               {
+                       return new Ggit.CredSshKeyFromAgent(username);
+               }
+               else if ((allowed_types & Ggit.Credtype.SSH_INTERACTIVE) != 0)
+               {
+                       return new CredSshInteractive(d_remote, username);
+               }
+               else if ((allowed_types & Ggit.Credtype.USERPASS_PLAINTEXT) != 0)
+               {
+                       return query_user_pass(url, username);
+               }
+
+               return null;
+       }
+}
+
+}
diff --git a/libgitg/resources/gitg-authentication-dialog.ui b/libgitg/resources/gitg-authentication-dialog.ui
new file mode 100644
index 0000000..5ce69e0
--- /dev/null
+++ b/libgitg/resources/gitg-authentication-dialog.ui
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+  <requires lib="gtk+" version="3.12"/>
+  <template class="GitgAuthenticationDialog" parent="GtkDialog">
+    <property name="can_focus">False</property>
+    <property name="type_hint">dialog</property>
+    <child type="action">
+      <object class="GtkButton" id="button_cancel">
+        <property name="visible">True</property>
+        <property name="label" translatable="yes">_Cancel</property>
+        <property name="use_underline">True</property>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="button_ok">
+        <property name="visible">True</property>
+        <property name="label" translatable="yes">_Authenticate</property>
+        <property name="use_underline">True</property>
+        <property name="can_default">True</property>
+        <property name="receives_default">True</property>
+      </object>
+    </child>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox1">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkGrid" id="grid1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="margin_left">12</property>
+            <property name="margin_right">12</property>
+            <property name="margin_top">12</property>
+            <property name="margin_bottom">12</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <property name="row_spacing">6</property>
+            <property name="column_spacing">6</property>
+            <child>
+              <object class="GtkImage" id="image1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="valign">start</property>
+                <property name="icon_name">gtk-dialog-authentication</property>
+                <property name="icon_size">6</property>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">0</property>
+                <property name="height">4</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label_title">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">start</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">0</property>
+                <property name="width">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label_username">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">end</property>
+                <property name="label" translatable="yes">_Username:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">entry_username</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label_password">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">end</property>
+                <property name="label" translatable="yes">_Password:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">entry_password</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="entry_username">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="hexpand">True</property>
+                <property name="input_purpose">name</property>
+                <property name="activates_default">True</property>
+              </object>
+              <packing>
+                <property name="left_attach">2</property>
+                <property name="top_attach">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="entry_password">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="hexpand">True</property>
+                <property name="visibility">False</property>
+                <property name="invisible_char">●</property>
+                <property name="input_purpose">password</property>
+                <property name="activates_default">True</property>
+              </object>
+              <packing>
+                <property name="left_attach">2</property>
+                <property name="top_attach">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="box1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_top">12</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkRadioButton" id="radio_button_forget">
+                    <property name="label" translatable="yes">Forget password _immediately</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="use_underline">True</property>
+                    <property name="xalign">0</property>
+                    <property name="draw_indicator">True</property>
+                    <property name="group">radio_button_session</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkRadioButton" id="radio_button_session">
+                    <property name="label" translatable="yes">Remember password until you _logout</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="use_underline">True</property>
+                    <property name="xalign">0</property>
+                    <property name="active">True</property>
+                    <property name="draw_indicator">True</property>
+
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkRadioButton" id="radio_button_forever">
+                    <property name="label" translatable="yes">Remember _forever</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="use_underline">True</property>
+                    <property name="xalign">0</property>
+                    <property name="draw_indicator">True</property>
+                    <property name="group">radio_button_session</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">3</property>
+                <property name="width">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="ok">button_ok</action-widget>
+      <action-widget response="cancel">button_cancel</action-widget>
+    </action-widgets>
+  </template>
+</interface>
diff --git a/libgitg/resources/resources.xml b/libgitg/resources/resources.xml
index 010ed19..3c59adc 100644
--- a/libgitg/resources/resources.xml
+++ b/libgitg/resources/resources.xml
@@ -9,6 +9,7 @@
   </gresource>
   <gresource prefix="/org/gnome/gitg/gtk">
     <file compressed="true" preprocess="xml-stripblanks">gitg-repository-list-box-row.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">gitg-authentication-dialog.ui</file>
   </gresource>
   <gresource prefix="/org/gnome/gitg/gtk/sidebar">
     <file compressed="true">sidebar-view.ui</file>


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