New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add YubiKey module (show a visible indication that YubiKey is waiting for a touch) #1110
Conversation
This module is designed to show a visible indication that YubiKey is waiting for a touch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dunno about thread stuffs. Here's the comments on formatter
stuffs though. 👍
py3status/modules/yubikey.py
Outdated
state_off: Message when the touch is not needed. | ||
(default '') | ||
state_on: Message when the YubiKey is waiting for a touch. | ||
(default 'Y') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could remove configs by doing this...
format = '\?if=is_waiting Y' # Default
format = '\?if=is_waiting Y|N'
py3status/modules/yubikey.py
Outdated
'cached_until': self.py3.time_in(self.cache_timeout), | ||
'full_text': self.py3.safe_format(self.format, {'state': state}), | ||
'color': self.py3.COLOR_BAD | ||
if pending_touch else self.py3.COLOR_GOOD |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could remove color too.
format = '\?if=is_waiting&color=good Y' # Default
format = '\?if=is_waiting&color=good Y|\?color=bad N'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it!
py3status/modules/yubikey.py
Outdated
state_on: Message when the YubiKey is waiting for a touch. | ||
(default 'Y') | ||
u2f_keys_path: Full path to u2f_keys if you want to monitor sudo access. | ||
(default '') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think (default None)
would look more nice... and then do if None: ''
thing in post_config_hook
. We could add os.path.expanduser(path)
in post_config_hook
too.
py3status/modules/yubikey.py
Outdated
(default '') | ||
state_on: Message when the YubiKey is waiting for a touch. | ||
(default 'Y') | ||
u2f_keys_path: Full path to u2f_keys if you want to monitor sudo access. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be generic. key_path
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather keep it like this, because this is the file name used by pam-u2f and all online tutorials use this name. If I saw key_path
option I would never guess that I need to provide path to u2f_keys
file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem. The pam-u2f
link seems to use authfile
. If we parse key_path
option, then we probably did parse the description too. Also... Using auth_file
or auth_key
would put it on top of the config list. Just stating things.
I wonder... I see that authfile
points to $HOME/.config/Yubico/u2f_keys
. Is that your path too? And if it is useful to default $HOME/.config/Yubico/u2f_keys
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I use the default, and thanks for the hint with os.path.expanduser
, I thought that it will expand ~
to /root
, but no, it expands to my own user - sweet! $HOME
is not being expanded, only ~
by the way.
Although they use authfile
in their README, the rest of the internet refers to this as u2f_keys
, so I'll still keep the configuration name. Thought hopefully most people don't need to configure this anymore :)
py3status/modules/yubikey.py
Outdated
(default '~/.config/Yubico/u2f_keys') | ||
|
||
SAMPLE OUTPUT | ||
{'color': '#FF0000', 'full_text': 'Y'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd like something more readable like...
Waiting
Key Waiting
Key waiting
<-- @lasers's pick 🥇YubiKey
Key
KEY
KEY!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel it's a bit on the nose and I'd like to stay with with subtle approach for QA purposes. Maybe just YubiKey
or Key
as I believe people will pick this up quicker than our expectations... And I feel that seeing Touch YubiKey
repeatedly could be tiresome. It is akin to seeing Check Gmail
repeatedly too... when Gmail
or Mail
would work just fine here.
Same thing with key_path
. Make yubikey
look clean in the config. (All my QA opinions).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
YubiKey
it is then. Config name u2f_keys_path
will leave for the reasons described in another comment.
py3status/modules/yubikey.py
Outdated
self.pending_sudo = False | ||
self.sudo_reset_timer = None | ||
|
||
class GpgThread(threading.Thread): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know anything about threading, but I ponder if we need classes here... And if this could be done by using methods alone. @tobes can give you better feedbacks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm more concerned by the approaches that this code takes, these are very hacky tricks... I hope someone from Yubico would give a hint on how to make it better. Especially the check for gpg
...
py3status/modules/yubikey.py
Outdated
'full_text': | ||
self.py3.safe_format(self.format, {'is_waiting': is_waiting}) | ||
} | ||
return response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to include 'urgent': is_waiting
here too? If users does not like it, they can disable this by adding allow_urgent = False
in the module config section. This might help with your ADHD (jk) attention too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
py3status/modules/yubikey.py
Outdated
'cached_until': | ||
self.py3.time_in(self.cache_timeout), | ||
'full_text': | ||
self.py3.safe_format(self.format, {'is_waiting': is_waiting}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Nit)
return {
'cached_until': self.py3.time_in(self.cache_timeout),
'full_text': self.py3.safe_format(
self.format, {'is_waiting': is_waiting})
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have yapf
automatically formatting the code here (using pep8 rules) - don't want to interfere with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extracted params in a separate variable, this lead yaps
to decide to change to a more pleasant styling 😉
py3status/modules/yubikey.py
Outdated
(default '~/.config/Yubico/u2f_keys') | ||
|
||
SAMPLE OUTPUT | ||
{'color': '#FF0000', 'full_text': 'Y'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel it's a bit on the nose and I'd like to stay with with subtle approach for QA purposes. Maybe just YubiKey
or Key
as I believe people will pick this up quicker than our expectations... And I feel that seeing Touch YubiKey
repeatedly could be tiresome. It is akin to seeing Check Gmail
repeatedly too... when Gmail
or Mail
would work just fine here.
Same thing with key_path
. Make yubikey
look clean in the config. (All my QA opinions).
py3status/modules/yubikey.py
Outdated
'full_text': self.py3.safe_format(self.format, format_params), | ||
'urgent': is_waiting | ||
} | ||
return response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. Now I know you like this. Can we do this instead?
response = {
'cached_until': self.py3.time_in(self.cache_timeout),
'full_text': self.py3.safe_format(self.format, format_params),
}
if is_waiting:
response['urgent'] = True
return response
This prevents us from alway seeing urgent: False
in the log / testing. It's all about simple pleasures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure
py3status/modules/yubikey.py
Outdated
(default '\?if=is_waiting Touch YubiKey') | ||
u2f_keys_path: Full path to u2f_keys if you want to monitor sudo access. | ||
(default '~/.config/Yubico/u2f_keys') | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing.
Control placeholders:
{is_waiting} a boolean based on gpg content and sudo access
It just got much more performant, but more complicated as well. The long explanation is posted here, where I'm also hoping to get some ideas on how to improve/simplify stuff here. I'll be testing a lot more during the following days, but so far I like it a lot, it is almost perfect. I'm also listening to this ticket on how to get rid of Regardless, this PR doesn't have to wait for any of those tickets, review and merge when you have time, and if I hear anything interesting I'll always make a separate PR. |
py3status/modules/yubikey.py
Outdated
is_waiting = self.pending_gpg or self.pending_sudo | ||
format_params = {'is_waiting': is_waiting} | ||
response = { | ||
'cached_until': self.py3.time_in(self.cache_timeout), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tobes, @lasers: I have to put a small number here, 1
or even smaller, but most of the time it would be wasting cycles and just redrawing the same output over and over again. I would really love to put here self.py3.CACHE_FOREVER
, and instead just manually force module refresh when at least one checks is triggered in other threads - is this possible to achieve?
In other words, is there some kind of function self.py3.force_redraw(<module_name>)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you're looking for self.py3.update()
? scratchpad_async
and window_title_async
might be interesting to you as both modules uses the function and the threading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly, thanks! Now it is instantly reacting only when the value changes - awesome!!
I did not get a support from folks at pam-u2f (because that project is being rewritten), but I made my own fork that adds this functionality. Now I'm fully happy with the code, ready to be merged. I'll revisit the approaches here if I figure out better ways to run a detection. |
py3status/modules/yubikey.py
Outdated
if len(self.gpg_check_watch_paths) > 0: | ||
GpgThread().start() | ||
|
||
if len(self.sudo_check_watch_paths) > 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Nit). if len(self.sudo_check_watch_paths):
would work okay here too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, but I like it explicit
py3status/modules/yubikey.py
Outdated
self.pending_gpg = False | ||
self.pending_sudo = False | ||
|
||
class GpgThread(threading.Thread): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Useful?) If you look at volume_status
, you'll find several classes outside of class Py3status
. If you are able to do the same here, then you could reclaim some indentations... and some things might be more readable... I recall we have to use parent
for py3 stuffs... eg self.parent.py3.update()
. Hopefully that's useful.
Something to try if we don't like how it's sitting inside post_config_hook
instead of a line or two. EDIT: Another example is sysdata
. EDIT: Maybe we wait.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It started as two very simple classes with just few lines, but since then they grew... Now it is time to extract them indeed, makes total sense to me.
py3status/modules/yubikey.py
Outdated
(default '\?if=is_waiting YubiKey') | ||
gpg_check_timeout: Time to wait for gpg check response. | ||
Use value as small as possible that doesn't introduce false positives. | ||
(default 0.1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Q) Is it useful for users to have gpg_check_timeout
config if the default is 0.1
? The message also say Use value as small as possible that doesn't introduce false positives
. Another thing... I see a hardcoded time.sleep(0.25)
too... right after that config too... so what can they do with this config?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a value of keeping it configurable, the default 0.1
works well on my laptop, but I can imagine that slower machines would not be able to do the detection within 0.1
seconds and those people would have to start slowly increasing the value until false positives disappear. The larger value you put, the larger would be a time delay between Yubikey begins to ask for touch and this module waking up. With the default value of 0.1
it is almost instant, which I like 🙂
time.sleep(0.25)
has no value for being configurable, this is an internal value to the algorithm to prevent false positives and I cannot imagine why would someone want to change them. The same goes for a constant 20
nearby. These are just two very good values that work well.
py3status/modules/yubikey.py
Outdated
gpg: to check for pending gpg access request | ||
inotify-simple: to check for pending gpg and u2f requests | ||
github.com/maximbaz/pam-u2f: a fork that adds watch capability to pam-u2f module | ||
subprocess32: if after all these years you are still using python2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modules uses Requires:
and this gpg
line might not be needed (installed everywhere).
Not sure if we can use (optional) thing like this.
python-inotify-simple: a simple wrapper around inotify
github.com/maximbaz/pam-u2f: (is this optional?) a fork that adds...
python2-subprocess32: (optional) backported subprocess module for python2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good ideas:
- Yubikey is bought primarily for gpg operations, so gpg is definitely installed on the system 🙂
- only patched pam-u2f is optional, because that's the only dependency for u2f, will mark it so.
py3status/modules/yubikey.py
Outdated
if os.name == 'posix' and sys.version_info[0] < 3: | ||
import subprocess32 as subprocess | ||
else: | ||
import subprocess |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We import sys
for this. Wanting to see if we can live w/o sys
.
try:
import subprocess # py3
except:
import subprocess32 as subprocess # py2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really, because subprocess
exists in both py2 and py3, it's just that the module in py2 doesn't contain timeout functionality that was introduced in py3, and I need it :)
Who is using py2 anyway, and when will py3status deprecate py2? It's not called py2status
anyway 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flip the import subprocess
lines could be sufficent to omit sys
... then I wonder if we should explicitly throw an exception on subprocess32 not imported and is python2... yubikey: subprocess32 not installed
. py3status support python2 until or after https://pythonclock.org and I think py3status
is a wordplay on i3wm
which is also a wordplay on wmii
. ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exception will happen with current implementation, right? Python3 will import subprocess and nothing else, python2 will import subprocess32 and throw an exception if it cannot do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exception will happen with current implementation, right?
Yup. Sharing the findings.
Packages: inotify_simple
, subprocess32
2017-10-17 INFO loading module "yubikey" from .../yubikey.py
2017-10-17 INFO Module `yubikey` could not be loaded
No exceptions... The bar will report:
yubikey // yubikey: Import Error, No module named 'inotify_simple'
yubikey // yubikey: Import Error, No module named 'subprocess32'
It's just a matter of if we should need sys
... and/or if we should make this nice like module: not installed
... eg yubikey: subprocess32 not imported
or yubikey: inotify_simple not imported
. Anyhow, I leave this to others. Same with the formatting. :-)
py3status/modules/yubikey.py
Outdated
|
||
def yubikey(self): | ||
is_waiting = self.pending_gpg or self.pending_u2f | ||
format_params = {'is_waiting': is_waiting} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do more with formatting tricks by replacing is_waiting
with gpg
and u2f
, eg...
format = '[\?color=#fa0 YubiKey [\?if=gpg&color=#aaf GPG][\?if=u2f&color=#faf U2F]]'
format = '[\?if=gpg&color=degraded YubiKey GPG][\?if=u2f&color=#fa0 YubiKey U2F]]'
format = '[\?color=good YubiKey [\?if=gpg GPG][\?if=u2f U2F]]'
format = '[\?color=good [\?if=gpg GPG][\?if=u2f U2F]]'
format = '[\?color=good&u2f U2F]' # ignore GPG
EDIT: Add some examples in the docs too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is interesting thought, but in practice this would be just too much. Users don't care whether the module is flashing because of gpg
or u2f
, once it flashes for whatever reason they need to figure out manually what caused this and decide on the spot, touch or not touch.
What could have been interesting is to see the process name that requests the touch, e.g. I can configure using both gpg
or u2f
technology that ssh
'ing somewhere will ask me to touch the device for confirmation, and in real life I don't care which technology is being used, instead I want to know which process wants this confirmation. But I don't know how to detect that from the module, we will leave this to a different time 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Users don't care whether the module is flashing because of gpg or u2f, once it flashes for whatever reason they need to figure out manually what caused this and decide on the spot, touch or not touch.
Possibly all the more reasons to do... as GPG is related to security, keys, encryption, logins, credentials, etc. Some users may like that option or may use nice icons to differentiate.
once it flashes for whatever reason they need to figure out manually what caused this
It at least differentiates Something to do with GPG
. Hopefully it will grow more from there... to support {process_name}
, {pid}
, Idk what else.
maximbaz (in another thread): Of course it would be awesome if one of the yubikey CLI tools provided such information out of the box, it is a really useful feature in my mind...
If that happen, then we should use it to expose everything for users... just like cmus
, moc
and coin_market
as they all have placeholders the users may not care about. We included them all because the data is there. :o
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's first find at least one person who needs that 🙂
I'm just quickly coming around this PR to say big thanks to you @maximbaz since I'm also a Yubikey lover and user and I dreamed of this very module ! 👍 I'll do my best to try it asap |
Looking forward 🙂 |
@ultrabug when you get to testing the module, let me know what you think of the default |
@maximbaz I wanted to try this out and got stuck in the setup process. What are those options supposed to refer to and are used for?
The GPG ones I got, the u2f_keys file, I don't. What should be in there? |
The defaults will work just work fine, if you don't have the U2F you probably don't use pam-u2f, the code will ignore all missing files. Why these files are needed to be configured I'll explain a bit later. |
I understand, but your patch is applied upon v1.0.5 of pam_u2f which has not been released so it's hard for users to get your commit into their pam_u2f lib. Can you provide a patch on v1.0.4 ? |
I think this is a sensible approach indeed. Unix sockets look the right way to move forward yes (at least that's what comes to my mind as well). Thanks! |
@ultrabug it's time to give it another try 😉 I extracted the tool into maximbaz/yubikey-touch-detector and made this module subscribe to notifications from it via unix sockets. The message is always of a fixed length of 5 bytes, so the parsing code here is very simple. This module is quite resilient, it will detect if the Finally I also added the |
c17fdd6
to
92464d4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👌
py3status/modules/yubikey.py
Outdated
Control placeholders: | ||
{is_waiting} a boolean, True if YubiKey is waiting for a touch for any reason. | ||
{is_waiting_gpg} a boolean, True if YubiKey is waiting for a touch due to a gpg operation. | ||
{is_waiting_u2f} a boolean, True if YubiKey is waiting for a touch due to a pam-u2f request. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then you need to be very careful about adding things you may later want to remove
from @tobes
This is akin to me supporting both markets = 'btc, eth'
and markets = ['btc', eth']
in coin_market
when you said that a list alone is sufficent. We could do same thing here with two {is_gpg}
and {is_u2f}
and have them both in default format
?
The exception I made so far is with numbers... In new apt_updates
module, I combined {new}
packages and {upgrade}
packages to make {count}
packages to take advantage of color threshold. That and the formatter does not do any basic mathematics.
I like that you killed off several config params above. 🥂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mean to remove the is_waiting
and have the default format be something like this?
format = '\?if=is_waiting_gpg OR is_waiting_u2f YubiKey'
what's the correct syntax for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
format = '[\?if=is_gpg KEY][\?is_u2f KEY]'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lasers just stumbled upon an issue with this format, it is possible that there are two commands issued for Yubikey at the same time (e.g. pacman is updating packages and requesting sudo via u2f, while at the same time on a different tab you ssh somewhere and this triggers gpg check). This results in module output "YubiKeyYubiKey", because is_gpg
and is_u2f
are both active at the same time. I want a true OR, not two separate ifs - how can we achieve that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recall it isn't possible. Pinging @tobes just in case. Maybe we could do this?
format = '[YubiKey [\?if=is_gpg GPG][\?soft ,][\?if=is_u2f U2F]]'
*
* YubiKey GPG
* YubiKey U2F
* YubiKey GPG,U2F
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need this for a case when I don't want to see GPG
or U2F
, I just want to see YubiKey
(or in my case, I use a symbol of key).
But using your example I think I solved it, this seems to work like a true OR:
format = '[[\?if=is_gpg ][\?if=is_u2f ] Yubikey]'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. format = '[YubiKey[\?if=is_gpg ][\?if=is_u2f ]]'
seems to work too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, I'll use your way as a default, it's easy to understand how to go from it to additionally showing GPG
or U2F
.
py3status/modules/yubikey.py
Outdated
{is_waiting_u2f} a boolean, True if YubiKey is waiting for a touch due to a pam-u2f request. | ||
|
||
SAMPLE OUTPUT | ||
{'color': '#00FF00', 'full_text': 'YubiKey'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{'full_text': 'YubiKey', 'urgent': True}
@@ -0,0 +1,121 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
Show an indicator when YubiKey is waiting for a touch. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Tossing it out) Display an indicator for YubiKey
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This way it's not very obvious, let's have it as it is to spell out what exactly this indicator is doing.
py3status/modules/yubikey.py
Outdated
|
||
self.killed = threading.Event() | ||
|
||
self.waiting_gpg = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Nit) I see 3 blank lines. See if we can kill one or two.
py3status/modules/yubikey.py
Outdated
{is_gpg} a boolean, True if YubiKey is waiting for a touch due to a gpg operation. | ||
{is_u2f} a boolean, True if YubiKey is waiting for a touch due to a pam-u2f request. | ||
|
||
SAMPLE OUTPUT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should come after the author.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be part of travis check
py3status/modules/yubikey.py
Outdated
elif data == b"U2F_1": | ||
self.parent.is_waiting_u2f = True | ||
elif data == b"U2F_0": | ||
self.parent.is_waiting_u2f = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Nit) Maybe we get rid of all is_waiting
. I barely can see the difference, eg self.parent.is_u2f
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true that
Awesome @maximbaz I will try tomorrow 👍 |
py3status/modules/yubikey.py
Outdated
def yubikey(self): | ||
format_params = { | ||
'is_gpg': self.is_gpg, | ||
'is_u2f': self.is_u2f, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Suggestion). We can make self.operation = {'is_gpg': None, 'is_u2f' None}
in post_config_hook
then use self.operation['is_gpg'] = True
to update the values. No need to make new dict on loops. This removes few lines too.
{is_u2f} a boolean, True if YubiKey is waiting for a touch due to a pam-u2f request. | ||
|
||
Requires: | ||
github.com/maximbaz/yubikey-touch-detector: tool to detect when YubiKey is waiting for a touch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Nit) Three lines here are over column 80. I don't have 4K monitors. ;__;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the line length is enforced by Travis, it was long but I compressed it enough so that Travis is green and happy now.
you should by a 4K screen, and a yubikey 😜
A friendly ping 😜 You should consider giving @lasers push access to this repo, he is very active at making improvements and code reviews 😄 |
Maybe let's not. I'm a noob. Having more eyes on the code is always good. I enjoy the efforts of fixing and/or improving things, but not so much in the deprecation department. If they're occupied with things, then I guess we wait patiently since they're not noobs. :-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🦃
if self.socket is None: | ||
# Socket is not available, try again soon | ||
time.sleep(60) | ||
continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Thought) Do we need time.sleep(60)
here?
- Would the thread not keep going and failing until it's finally made?
- What happen if ssh or gpg is made, but we're in this time.sleep period? I think it could not show up... or even could show up several seconds late! 😡
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- It's not a good idea to completely avoid
sleep
, if the socket connection cannot be established, there is likely an issue with the backend service and manual intervention is needed, and until then connection will probably fail next time as well. Trying to reconnect too often will only result if wasting lots of cycles. One minute is a sweet spot that I hardcode on purpose, not too often and yet fast enough for an automatic recovery - in real life YubiKey is used much rarely than once per minute.
However your question lead me to the following thought, since manual intervention is likely needed, perhaps silently sleeping is not enough, and we need to show an error message in red color. I saw other modules do this when they fail. Could you point me to how to achieve this?
- It will not show up indeed, whether the problem is on the backend side (not publishing events) or on the module side (not listening to the events), no connection means no events received and no messages shown. However the notification will not show up several seconds late, when connection is finally established, it is starting from the clean state, only next even will trigger a message to be shown.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.py3.error
. I think it'll keep going behind the scene until #1114 gets addressed. @tobes also said something about extending the self.py3.error
exceptions to threads.
./bluetooth.py:130: self.py3.error(STRING_NOT_STARTED)
./yandexdisk_status.py:58: self.py3.error(STRING_ERROR)
./coin_market.py:170: self.py3.error('bad markets')
./external_script.py:76: self.py3.error(output)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know this well at all, but quick stab...
diff --git a/py3status/modules/yubikey.py b/py3status/modules/yubikey.py
index 621a3c90..ba088d52 100644
--- a/py3status/modules/yubikey.py
+++ b/py3status/modules/yubikey.py
@@ -22,7 +22,6 @@ SAMPLE OUTPUT
{'full_text': 'YubiKey', 'urgent': True}
"""
-import time
import os
import socket
import threading
@@ -41,17 +40,15 @@ class YubiKeyTouchDetectorListener(threading.Thread):
try:
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.socket.connect(self.parent.socket_path)
- except:
- self.socket = None
+ except Exception as e:
+ self.error = e
def run(self):
while not self.parent.killed.is_set():
self._connect_socket()
- if self.socket is None:
- # Socket is not available, try again soon
- time.sleep(60)
- continue
+ if self.error:
+ self.parent.py3.update()
while not self.parent.killed.is_set():
data = self.socket.recv(5)
@@ -77,6 +74,7 @@ class Py3status:
socket_path = '$XDG_RUNTIME_DIR/yubikey-touch-detector.socket'
def post_config_hook(self):
+ self.error = None
self.socket_path = os.path.expanduser(self.socket_path)
self.socket_path = os.path.expandvars(self.socket_path)
@@ -89,6 +87,8 @@ class Py3status:
YubiKeyTouchDetectorListener(self).start()
def yubikey(self):
+ if self.error:
+ raise self.error
response = {
'cached_until': self.py3.CACHE_FOREVER,
'full_text': self.py3.safe_format(self.format, self.status),
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome, thanks, it is very beautiful and descriptive now, clear to the user when this happens and what to investigate 🙂
py3status/modules/yubikey.py
Outdated
|
||
def run(self): | ||
while not self.parent.killed.is_set(): | ||
self._connect_socket() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could put everything here for better readability. Less lines and we skip = None
too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can skip = None
even if we inline the code here, the exception is likely happening during the connect()
, and I want to cleanup the dangling socket immediately in that case. I like it as it is.
self.status = { | ||
'is_gpg': False, | ||
'is_u2f': False, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Nit) Maybe just self.status = {'is_gpg': False, 'is_u2f': False}
here and kill a line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for me it is more readable as it is.
Readability is better than compactness every time
py3status/modules/yubikey.py
Outdated
|
||
def yubikey(self): | ||
if self.error: | ||
raise self.error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we use self.py3.error(str(self.err), self.py3.CACHE_FOREVER)
here instead due to py3ness... and If I understand this okay... that we don't need timeout anymore too due to error? How come we don't use str(self.err)
-- a potentially better exception message(s) via except Exception as err
-- over a hardcoded self.error
message?
Ok I finally tested the module fully and it works good 👍 As for the error report that @lasers is pointing out, I think @maximbaz did this as-is because py3status will be handling the error report on the bar. The drawback is that the user will have to click on the red yubikey string that will appear to display the error message. It could be misleading because the actual behaviour of the module is to display a red yubikey string to alert of a touch requirement... So I'd rather the module outputs the error itself in the full_text field than relying on py3status to do so, a bit like @lasers proposes. |
socket_path = '$XDG_RUNTIME_DIR/yubikey-touch-detector.socket' | ||
|
||
def post_config_hook(self): | ||
self.socket_path = os.path.expanduser(self.socket_path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't this overridden by the (re)definition just below on line 85?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, on purpose - first expanduser
expands ~
in the path if it is present, then expandvars
expands environment variables (again, if they are present). We need a full path, and this allows to define the path both as $XDG_RUNTIME_DIR/bla
and ~/bla
.
I will play with py3.error, The actual exception is very cryptic, I would never guess what it means, that's why I put my custom one - at least people will know what to investigate next if they see it. |
That old thing? We switched away from that... to this new thing. I'd suggest we stay with the new thing for QA / consistency. We also could just make error message much much shorter... like We should see if it's possible to expand the exceptions right away using global / module config. Maybe make it a default too for the users. That way, we can keep ours toggled off... or even disappear... on account of switching modules non-stop. Just a thought. |
I used py3.error to refresh module less often, this is cool. Visually it looks exactly the same, you see "yubikey" in red, you click on it and see full error message. I like to keep the error message as it is to make it clear what to investigate, "no socket" or "no connection" aren't explanatory enough to me. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice job, good module. Maybe a last-minute addition of showing how to turn off urgent and adding GPG/U2F strings in one example would be nice/helpful for users. Let's get this puppy in. 🐩
Impressive work @maximbaz congratulations and thank you! I'm using it 👍 |
This module is designed to show a visible indication that YubiKey is waiting for a touch.
Basically it uses a bunch of hacks to solve this issue.
Owners of YubiKey, especially YubiKey 4 Nano would be extremely glad for this module 🙂
How it works:
DEMO: