/* * 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 . */ namespace Gitg { public errordomain CredentialsError { CANCELLED } public class CredentialsManager { private Ggit.Config? d_config; private Gtk.Window d_window; private Gee.HashMap? d_usermap; private bool d_save_user_in_config; private string d_last_user; private Gee.HashMap d_auth_tried; 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(Ggit.Config? config, Gtk.Window window, bool save_user_in_config) { d_config = config; d_save_user_in_config = save_user_in_config; d_auth_tried = new Gee.HashMap(); d_window = window; } private string? lookup_user(string host) { if (d_usermap == null) { d_usermap = new Gee.HashMap(); if (d_config != null) { try { var r = new Regex("credential\\.(.*)\\.username"); d_config.snapshot().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_auth_tried[username] != 0); 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"); } d_last_user = newusername; // Save username in config if (username == null || newusername != username && d_config != null && d_save_user_in_config) { if (d_usermap == null) { d_usermap = new Gee.HashMap(); } try { var hid = @"$(scheme)://$(host)"; d_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(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); } }); } d_auth_tried[newusername] |= Ggit.Credtype.USERPASS_PLAINTEXT; 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) { var tried = d_auth_tried[user]; if ((tried & Ggit.Credtype.USERPASS_PLAINTEXT) == 0) { 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); } d_auth_tried[user] |= Ggit.Credtype.USERPASS_PLAINTEXT; 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); } } else { return user_pass_dialog(url, scheme, host, user); } } public Ggit.Cred? credentials(string url, string? username, Ggit.Credtype allowed_types) throws Error { var uslookup = username != null ? username : ""; var tried = d_auth_tried[uslookup]; var untried_allowed_types = allowed_types & ~tried; if ((untried_allowed_types & Ggit.Credtype.SSH_KEY) != 0) { d_auth_tried[uslookup] = tried | Ggit.Credtype.SSH_KEY; return new Ggit.CredSshKeyFromAgent(username); } else if ((allowed_types & Ggit.Credtype.USERPASS_PLAINTEXT) != 0) { return query_user_pass(url, username); } return null; } } }