Merge lp:~michaelaquilina/synapse-project/pass-plugin into lp:synapse-project

Proposed by Michael Aquilina
Status: Merged
Merged at revision: 651
Proposed branch: lp:~michaelaquilina/synapse-project/pass-plugin
Merge into: lp:synapse-project
Diff against target: 280 lines (+232/-0)
5 files modified
po/POTFILES.in (+1/-0)
po/POTFILES.skip (+1/-0)
src/plugins/Makefile.am (+1/-0)
src/plugins/pass-plugin.vala (+228/-0)
src/ui/synapse-main.vala (+1/-0)
To merge this branch: bzr merge lp:~michaelaquilina/synapse-project/pass-plugin
Reviewer Review Type Date Requested Status
Rico Tzschichholz Approve
Review via email: mp+281297@code.launchpad.net

Description of the change

Add pass (http://www.passwordstore.org/) plugin to synapse. Allows the user to place the contents of a password in their store in the clipboard as an action. Matching is simply performed based on the name of the password.

See this launchpad question for more detail: https://answers.launchpad.net/synapse-project/+question/287038

To post a comment you must log in.
Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

Any comments on this? Is there an active maintainer to merge this in?

Revision history for this message
Rico Tzschichholz (ricotz) wrote :

It looks straight forward.

There are bunch of indentation problems, codestyle issues with missing space before "(" and some TODOs.

Better hold the GFile reference to ".password_store" instead of creating it every time.

review: Needs Fixing
Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

Is there a style guide I could follow? I mainly code in python nowadays so I stuck to the conventions I'm used to.

Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

Looking at the diff, I now see what you're referring to in terms of indents. It looks fine in my editor funnily enough (atom). So I'm not sure what the issue is.

Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

The indentation issues should be solved. The issue was that piece of code was copied from somewhere to start off with and modified. It contained hard tabs which my editor tried to maintain. I changed it so that it uses "soft" tabs instead which seems to have worked.

Revision history for this message
Rico Tzschichholz (ricotz) wrote :

Still a bunch of missing spaces before "(" like "printf(this.title)" >> "printf (this.title)"

Avoid removing a line from Makefile.am.

Review your strings. Use lower-cases as usual in english.
"Copy encrypted GPG password to clipboard" < this is OK.
"Copied %s Password to Clipboard" < this is not OK.

Not sure what you mean with "soft" tabs, but the synapse source doesn't use tabs only spaces.

Redo you branch in the end to make it consist of only one commit again.

review: Needs Fixing
Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

That extra line being removed is completely extra (it's automatically removed in all modern editors). I feel like that should be left there unless you see a strong reason to keep it? Most people contributing will experience it being removed automatically otherwise - which is minor but still annoying.

As for squashing the commits, I am completely aware of how to do this in git but I cant find any reference for how to do it in bzr. Is there anything you could point me at?

Revision history for this message
Rico Tzschichholz (ricotz) wrote :

Still some "(" :\

Better call list_passwords() when it would provide actual changes. E.g. on start up and on changes while using a GFileMonitor on the folder.

Regarding "standard::*" only retrieve the information you need, so standard::name and stardard::type.

You will need to use bzr uncommit multiple times and bzr commit again afterwards.
I really dislike when unrelated code-portions are touched for no reason. So please keep the line.

review: Needs Fixing
Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

* Passwords only listed on activation and on changed event in password directory
* Only 'name' 'type' and 'content-type' retrieved by enumerate_children
* Fixed more styling issues
* Reverted removal of extra line at the end of MakeFile.am

Ready for review!

Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

Hi, is there anything I need to do/fix?

Revision history for this message
Rico Tzschichholz (ricotz) wrote :

No need for password_monitor and password_store to be public properties, private fields are sufficient.

file.resolve_relative_path (info.get_name ()); -> file.get_child (info.get_name ());

if the ordering of file-list is not important then GLib.List.prepend() is sufficient and faster.

Decide between "... password %s" or "... %s password".

review: Needs Fixing
Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

Fixed according to your review comments

Revision history for this message
Rico Tzschichholz (ricotz) wrote :

No need for password_monitor and password_store to be *properties*, make them *fields*.

review: Needs Fixing
Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

Sorry about that, didnt realise you also meant I should convert the properties to fields. Fixed.

Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

I seem to have found a bug using FileMonitor, it (obviously now thinking about it) does not recursively track changes in children directories and for that reasons changes to for example "Web/SomeSite/ABC" will not be reflected in the code. Is there a standard approach for dealing with tracking of folders recursively or will I have to write this myself?

Revision history for this message
Rico Tzschichholz (ricotz) wrote :
Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

Fixed the file monitoring bug. Should be good for merging now.

Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

@Rico is there anything that needs fixing?

Revision history for this message
Rico Tzschichholz (ricotz) wrote :

https://paste.debian.net/plain/405740

I really don't like the plain re-invocation of update_passwords() you updating all password that is fine, but recreating all monitors without stopping/destroying them is a not good.

Also the infinite/unrestricted recursion might bite you, so better limit it to a depth of 2 or 3.

And use explicitly use GLib.File.monitor_directory() as suggested. While not caring about updating existing data and just recreating things from the bottom make use of the monitor'S rate-limit.

review: Needs Fixing
Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

Limiting the recursion depth to something as low as 2 or 3 wont really work since its common to have passwords organised at that depth. Why would recursing directories cause a problem though? You cant really have an infinite directory structure since following symbolic links is disabled.

I'll add disabling of previous file monitors if that's the main issue.

Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

I've adding code to cancel previous FileMonitors when they're no longer being used and the other requests you've made. I tried updating to code to avoid re-generating the monitors each time a change is detected. I attempted this by adding/removing a FileMoinitor on a changed event on a directory but this immediately became overly complicated.

If we believe using recursive FileMonitors is the best approach, then I feel like this is as good as it can get unless you know of an alternative?

Revision history for this message
Rico Tzschichholz (ricotz) wrote :

Alright one more clean up and it should be fine.

https://paste.debian.net/plain/411306

637. By Michael Aquilina

Add pass plugin

Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

Updated according to your diff

Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

@Rico, does everything look good?

Revision history for this message
Michael Aquilina (michaelaquilina) wrote :

Sorry to annoy you @Rico, but any chance you could give this a look some time?

Revision history for this message
Rico Tzschichholz (ricotz) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'po/POTFILES.in'
2--- po/POTFILES.in 2016-02-26 16:57:32 +0000
3+++ po/POTFILES.in 2016-03-03 11:50:29 +0000
4@@ -33,6 +33,7 @@
5 src/plugins/launchpad-plugin.vala
6 src/plugins/locate-plugin.vala
7 src/plugins/opensearch.vala
8+src/plugins/pass-plugin.vala
9 src/plugins/pastebin-plugin.vala
10 src/plugins/pidgin-plugin.vala
11 src/plugins/rhythmbox-plugin.vala
12
13=== modified file 'po/POTFILES.skip'
14--- po/POTFILES.skip 2016-02-26 16:57:32 +0000
15+++ po/POTFILES.skip 2016-03-03 11:50:29 +0000
16@@ -34,6 +34,7 @@
17 src/plugins/launchpad-plugin.c
18 src/plugins/locate-plugin.c
19 src/plugins/opensearch.c
20+src/plugins/pass-plugin.c
21 src/plugins/pastebin-plugin.c
22 src/plugins/pidgin-plugin.c
23 src/plugins/rhythmbox-plugin.c
24
25=== modified file 'src/plugins/Makefile.am'
26--- src/plugins/Makefile.am 2016-02-26 16:57:32 +0000
27+++ src/plugins/Makefile.am 2016-03-03 11:50:29 +0000
28@@ -45,6 +45,7 @@
29 launchpad-plugin.vala \
30 locate-plugin.vala \
31 opensearch.vala \
32+ pass-plugin.vala \
33 pastebin-plugin.vala \
34 pidgin-plugin.vala \
35 rhythmbox-plugin.vala \
36
37=== added file 'src/plugins/pass-plugin.vala'
38--- src/plugins/pass-plugin.vala 1970-01-01 00:00:00 +0000
39+++ src/plugins/pass-plugin.vala 2016-03-03 11:50:29 +0000
40@@ -0,0 +1,228 @@
41+/*
42+ * Copyright (C) 2015 Michael Aquilina <michaelaquilina@gmail.com>
43+
44+ * This program is free software; you can redistribute it and/or modify
45+ * it under the terms of the GNU General Public License as published by
46+ * the Free Software Foundation; either version 2 of the License, or
47+ * (at your option) any later version.
48+ *
49+ * This program is distributed in the hope that it will be useful,
50+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
51+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
52+ * GNU General Public License for more details.
53+ *
54+ * You should have received a copy of the GNU General Public License
55+ * along with this program; if not, write to the Free Software
56+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
57+ *
58+ * Authored by Michael Aquilina <michaelaquilina@gmail.com>
59+ *
60+ */
61+
62+namespace Synapse
63+{
64+ public class PassPlugin : Object, Activatable, ItemProvider
65+ {
66+ private class PassMatch : ActionMatch
67+ {
68+ public PassMatch (string password_name)
69+ {
70+ Object (title: password_name,
71+ description: _("Copy decrypted PGP password to clipboard"),
72+ has_thumbnail: false, icon_name: "dialog-password");
73+ }
74+
75+ public override void do_action ()
76+ {
77+ Pid child_pid;
78+ int standard_output;
79+ int standard_error;
80+
81+ try
82+ {
83+ Process.spawn_async_with_pipes (null,
84+ {"pass", "-c", this.title},
85+ null, SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD,
86+ null, out child_pid,
87+ null, out standard_output, out standard_error
88+ );
89+ ChildWatch.add (child_pid, (pid, status) => {
90+ Process.close_pid (pid);
91+
92+ try
93+ {
94+ string message, icon_name;
95+ if (status == 0) {
96+ message = _("Copied %s password to clipboard").printf (this.title);
97+ icon_name = "dialog-password";
98+ } else {
99+ message = _("Unable to decrypt %s password").printf (this.title);
100+ icon_name = "dialog-error";
101+ }
102+
103+ var notification = (Notify.Notification) Object.new (
104+ typeof (Notify.Notification),
105+ summary: _("Synapse - Pass"),
106+ body: message,
107+ icon_name: icon_name,
108+ null
109+ );
110+ notification.show ();
111+ }
112+ catch (Error err) {
113+ warning ("%s", err.message);
114+ }
115+ });
116+ }
117+ catch (SpawnError err) {
118+ warning ("%s", err.message);
119+ }
120+ }
121+ }
122+
123+ public bool enabled { get; set; default = true; }
124+
125+ private File password_store;
126+ private List<string> passwords;
127+ private List<FileMonitor> monitors;
128+
129+ public void activate ()
130+ {
131+ password_store = File.new_for_path (
132+ "%s/.password-store".printf (Environment.get_home_dir ())
133+ );
134+ update_passwords ();
135+ }
136+
137+ public void deactivate ()
138+ {
139+ }
140+
141+ static void register_plugin ()
142+ {
143+ PluginRegistry.get_default ().register_plugin (
144+ typeof (PassPlugin),
145+ _("Pass Integration"),
146+ _("Quickly place passwords from your password store in the clipboard."),
147+ "dialog-password",
148+ register_plugin,
149+ Environment.find_program_in_path ("pass") != null,
150+ _("pass is not installed")
151+ );
152+ }
153+
154+ static construct
155+ {
156+ register_plugin ();
157+ }
158+
159+ public bool handles_query (Query query)
160+ {
161+ return (QueryFlags.ACTIONS in query.query_type);
162+ }
163+
164+ private void update_passwords () {
165+ foreach (unowned FileMonitor monitor in monitors) {
166+ monitor.cancel ();
167+ }
168+ monitors = null;
169+
170+ try {
171+ monitors = activate_monitors (password_store);
172+ } catch (Error err) {
173+ warning ("Unable to monitor password directory: %s", err.message);
174+ }
175+ try {
176+ passwords = list_passwords (password_store, password_store);
177+ } catch (Error err) {
178+ warning ("Unable to list passwords: %s", err.message);
179+ }
180+ }
181+
182+ private List<FileMonitor> activate_monitors (File directory) throws Error {
183+ List<FileMonitor> result = new List<FileMonitor> ();
184+
185+ FileEnumerator enumerator = directory.enumerate_children (
186+ FileAttribute.STANDARD_NAME + "," +
187+ FileAttribute.STANDARD_TYPE + "," +
188+ FileAttribute.STANDARD_IS_HIDDEN,
189+ FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
190+ null
191+ );
192+
193+ FileMonitor monitor = directory.monitor_directory (FileMonitorFlags.NONE, null);
194+ monitor.set_rate_limit (500);
195+ monitor.changed.connect ((src, dest, event) => {
196+ message ("Detected a change (%s) in password store. Reloading", event.to_string ());
197+ update_passwords ();
198+ });
199+ result.append (monitor);
200+
201+ FileInfo? info = null;
202+ while ((info = enumerator.next_file (null)) != null) {
203+ if (info.get_is_hidden ()) continue;
204+
205+ var target_name = info.get_name ();
206+ File target_file = directory.get_child (target_name);
207+ if (info.get_file_type () == FileType.DIRECTORY) {
208+ result.concat (activate_monitors (target_file));
209+ }
210+ }
211+
212+ return result;
213+ }
214+
215+ private List<string> list_passwords (File root, File directory) throws Error {
216+ List<string> result = new List<string> ();
217+
218+ FileEnumerator enumerator = directory.enumerate_children (
219+ FileAttribute.STANDARD_NAME + "," +
220+ FileAttribute.STANDARD_TYPE + "," +
221+ FileAttribute.STANDARD_CONTENT_TYPE,
222+ FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
223+ null
224+ );
225+
226+ FileInfo? info = null;
227+ while ((info = enumerator.next_file (null)) != null) {
228+ File target_file = directory.get_child (info.get_name ());
229+ if (info.get_file_type () == FileType.DIRECTORY) {
230+ result.concat (list_passwords (root, target_file));
231+ }
232+ else if (info.get_content_type () == "application/pgp-encrypted") {
233+ var path = root.get_relative_path (target_file);
234+ result.prepend (path.replace (".gpg", ""));
235+ }
236+ }
237+ return result;
238+ }
239+
240+ public async ResultSet? search (Query query) throws SearchError
241+ {
242+ var matchers = Query.get_matchers_for_query (
243+ query.query_string, 0,
244+ RegexCompileFlags.OPTIMIZE | RegexCompileFlags.CASELESS
245+ );
246+
247+ var results = new ResultSet ();
248+ foreach (unowned string password in passwords)
249+ {
250+ foreach (var matcher in matchers)
251+ {
252+ if (matcher.key.match (password)) {
253+ results.add (new PassMatch (password), MatchScore.GOOD);
254+ break;
255+ }
256+ }
257+ }
258+
259+ // make sure this method is called before returning any results
260+ query.check_cancellable ();
261+ if (results.size > 0) {
262+ return results;
263+ } else {
264+ return null;
265+ }
266+ }
267+ }
268+}
269
270=== modified file 'src/ui/synapse-main.vala'
271--- src/ui/synapse-main.vala 2016-02-27 15:19:21 +0000
272+++ src/ui/synapse-main.vala 2016-03-03 11:50:29 +0000
273@@ -174,6 +174,7 @@
274 typeof (ChromiumPlugin),
275 typeof (FileOpPlugin),
276 typeof (PidginPlugin),
277+ typeof (PassPlugin),
278 typeof (ChatActions),
279 typeof (ZealPlugin),
280 #if HAVE_ZEITGEIST