1/* 2 * Seahorse 3 * 4 * Copyright (C) 2008 Stefan Walter 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU Lesser General Public License as 8 * published by the Free Software Foundation; either version 2.1 of 9 * the License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, but 12 * WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this program; if not, see <http://www.gnu.org/licenses/>. 18 */ 19 20namespace Seahorse { 21namespace Gkr { 22 23private class MyService : Secret.Service { 24 public override GLib.Type get_collection_gtype() { 25 return typeof(Keyring); 26 } 27 28 public override GLib.Type get_item_gtype() { 29 return typeof(Item); 30 } 31} 32 33public class Backend: GLib.Object , Gcr.Collection, Seahorse.Backend { 34 public string name { 35 get { return NAME; } 36 } 37 38 public string label { 39 get { return _("Passwords"); } 40 } 41 42 public string description { 43 get { return _("Stored personal passwords, credentials and secrets"); } 44 } 45 46 public ActionGroup actions { 47 owned get { return this._actions; } 48 } 49 50 public GLib.HashTable<string, string> aliases { 51 get { return this._aliases; } 52 } 53 54 private bool _loaded; 55 public bool loaded { 56 get { return this._loaded; } 57 } 58 59 public Secret.Service? service { 60 get { return this._service; } 61 } 62 63 private static Backend? _instance = null; 64 private Secret.Service _service; 65 private GLib.HashTable<string, Keyring> _keyrings; 66 private GLib.HashTable<string, string> _aliases; 67 private ActionGroup _actions; 68 69 construct { 70 return_val_if_fail(_instance == null, null); 71 Backend._instance = this; 72 73 this._actions = BackendActions.instance(this); 74 this._keyrings = new GLib.HashTable<string, Keyring>(GLib.str_hash, GLib.str_equal); 75 this._aliases = new GLib.HashTable<string, string>(GLib.str_hash, GLib.str_equal); 76 77 Secret.Service.open.begin(typeof(MyService), null, 78 Secret.ServiceFlags.OPEN_SESSION, null, (obj, res) => { 79 try { 80 this._service = Secret.Service.open.end(res); 81 this._service.notify["collections"].connect((obj, pspec) => { 82 refresh_collections(); 83 }); 84 this._service.load_collections.begin(null, (obj, res) => { 85 try { 86 this._service.load_collections.end(res); 87 refresh_collections(); 88 } catch (GLib.Error e) { 89 warning("couldn't load all secret collections: %s", e.message); 90 } 91 }); 92 refresh_aliases(); 93 } catch (GLib.Error err) { 94 GLib.warning("couldn't connect to secret service: %s", err.message); 95 } 96 notify_property("service"); 97 }); 98 } 99 100 public override void dispose() { 101 this._aliases.remove_all(); 102 this._keyrings.remove_all(); 103 base.dispose(); 104 } 105 106 public void refresh_collections() { 107 var seen = new GLib.GenericSet<string>(GLib.str_hash, GLib.str_equal); 108 var keyrings = this._service.get_collections(); 109 110 string object_path; 111 foreach (var keyring in keyrings) { 112 object_path = keyring.get_object_path(); 113 114 /* Don't list the session keyring */ 115 if (this._aliases.lookup("session") == object_path) 116 continue; 117 118 var uri = "secret-service://%s".printf(object_path); 119 seen.add(uri); 120 if (this._keyrings.lookup(uri) == null) { 121 this._keyrings.insert(uri, (Keyring)keyring); 122 emit_added(keyring); 123 } 124 } 125 126 /* Remove any that we didn't find */ 127 var iter = GLib.HashTableIter<string, Keyring>(this._keyrings); 128 string uri; 129 while (iter.next(out uri, null)) { 130 if (!seen.contains(uri)) { 131 var keyring = this._keyrings.lookup(uri); 132 iter.remove(); 133 emit_removed(keyring); 134 } 135 } 136 137 if (!_loaded) { 138 _loaded = true; 139 notify_property("loaded"); 140 } 141 } 142 143 public uint get_length() { 144 return this._keyrings.size(); 145 } 146 147 public GLib.List<weak GLib.Object> get_objects() { 148 return get_keyrings(); 149 } 150 151 public bool contains(GLib.Object object) { 152 var keyring = object as Gkr.Keyring; 153 if (keyring == null) 154 return false; 155 156 return this._keyrings.lookup(keyring.uri) == keyring; 157 } 158 159 public Place? lookup_place(string uri) { 160 return this._keyrings.lookup(uri); 161 } 162 163 public static void initialize() { 164 return_if_fail(Backend._instance == null); 165 (new Backend()).register(); 166 return_if_fail(Backend._instance != null); 167 } 168 169 public static Backend instance() { 170 return_val_if_fail(Backend._instance != null, null); 171 return Backend._instance; 172 } 173 174 public GLib.List<unowned Keyring> get_keyrings() { 175 return this._keyrings.get_values(); 176 } 177 178 private async void read_alias(string name) { 179 if (this._service == null) 180 return; 181 182 try { 183 var object_path = this._service.read_alias_dbus_path_sync(name); 184 if (object_path != null) { 185 this._aliases[name] = object_path; 186 notify_property("aliases"); 187 } 188 } catch (GLib.Error err) { 189 warning("Couldn't read secret service alias %s: %s", name, err.message); 190 } 191 } 192 193 private void refresh_aliases() { 194 read_alias.begin ("default"); 195 read_alias.begin ("session"); 196 read_alias.begin ("login"); 197 } 198 199 public void refresh() { 200 refresh_aliases(); 201 refresh_collections(); 202 } 203 public bool has_alias(string alias, 204 Keyring keyring) { 205 string object_path = keyring.get_object_path(); 206 return this._aliases.lookup(alias) == object_path; 207 } 208} 209 210public class BackendActions : Seahorse.ActionGroup { 211 public Backend backend { construct; get; } 212 private static WeakRef _instance; 213 private bool _initialized; 214 215 private const ActionEntry[] BACKEND_ACTIONS = { 216 { "keyring-new", on_new_keyring }, 217 { "keyring-item-new", on_new_item }, 218 { "copy-secret", on_copy_secret }, 219 }; 220 221 construct { 222 this._initialized = false; 223 224 this.backend.notify.connect_after((pspec) => { 225 if (pspec.name == "service") 226 return; 227 if (this._initialized) 228 return; 229 if (this.backend.service == null) 230 return; 231 232 this._initialized = true; 233 add_action_entries(BACKEND_ACTIONS, this); 234 }); 235 236 this.backend.notify_property("service"); 237 } 238 239 private BackendActions(Backend backend) { 240 GLib.Object( 241 prefix: "gkr", 242 backend: backend 243 ); 244 } 245 246 private void on_new_keyring(SimpleAction action, Variant? param) { 247 var dialog = new KeyringAdd(this.catalog); 248 249 int response = dialog.run(); 250 if (response == Gtk.ResponseType.ACCEPT) 251 this.catalog.activate_action("focus-place", "secret-service"); 252 dialog.destroy(); 253 } 254 255 private void on_new_item(SimpleAction action, Variant? param) { 256 var dialog = new ItemAdd(this.catalog); 257 258 int response = dialog.run(); 259 if (response == Gtk.ResponseType.ACCEPT) 260 this.catalog.activate_action("focus-place", "secret-service"); 261 dialog.destroy(); 262 } 263 264 private void on_copy_secret(SimpleAction action, Variant? param) { 265 return_if_fail (this.catalog != null); 266 267 var selected = this.catalog.get_selected_objects(); 268 // We checked for this in set_actions_for_selected_objects() 269 return_if_fail (selected.length() == 1); 270 271 var selected_item = selected.data as Gkr.Item; 272 return_if_fail (selected_item != null); 273 274 var clipboard = Gtk.Clipboard.get_default(this.catalog.get_display()); 275 selected_item.copy_secret_to_clipboard.begin(clipboard, (obj, res) => { 276 try { 277 selected_item.copy_secret_to_clipboard.end(res); 278 } catch (GLib.Error e) { 279 Util.show_error(this.catalog, "Couldn't copy secret", e.message); 280 } 281 }); 282 } 283 284 public override void set_actions_for_selected_objects(List<GLib.Object> objects) { 285 // Allow "copy secret" if there is only 1 Gkr.Item selected 286 bool can_copy_secret = false; 287 288 if (objects.length() == 1) { 289 can_copy_secret = objects.data is Gkr.Item; 290 } 291 292 ((SimpleAction) lookup_action("copy-secret")).set_enabled(can_copy_secret); 293 } 294 295 public static ActionGroup instance(Backend backend) { 296 BackendActions? actions = (BackendActions?)_instance.get(); 297 if (actions != null) 298 return actions; 299 actions = new BackendActions(backend); 300 _instance.set(actions); 301 return actions; 302 } 303} 304 305} 306} 307