[gitg] Implement basic user/pass auth
- From: Jesse van den Kieboom <jessevdk src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gitg] Implement basic user/pass auth
- Date: Wed, 24 Dec 2014 13:32:42 +0000 (UTC)
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]