Source code for i3pystatus.yubikey

import re
import os
import time

from i3pystatus import IntervalModule
from i3pystatus.core.command import run_through_shell


[docs]class Yubikey(IntervalModule): """ This module allows you to lock and unlock your Yubikey in order to avoid the OTP to be triggered accidentally. @author Daniel Theodoro <daniel.theodoro AT gmail.com> """ interval = 1 format = "Yubikey: 🔒" unlocked_format = "Yubikey: 🔓" timeout = 5 color = "#00FF00" unlock_color = "#FF0000" settings = ( ("format", "Format string"), ("unlocked_format", "Format string when the key is unlocked"), ("timeout", "How long the Yubikey will be unlocked (default: 5)"), ("color", "Standard color"), ("unlock_color", "Set the color used when the Yubikey is unlocked"), ) on_leftclick = ["set_lock", True] find_regex = re.compile( r".*yubikey.*id=(?P<yubid>\d+).*$", re.IGNORECASE ) status_regex = re.compile( r".*device enabled.*(?P<status>\d)$", re.IGNORECASE ) lock_file = f"/var/tmp/Yubikey-{os.geteuid()}.lock" def __init__(self): super().__init__() @property def _device_id(self): command = run_through_shell("xinput list") rval = "" if command.rc == 0: for line in command.out.splitlines(): match = self.find_regex.match(line) if match: rval = match.groupdict().get("yubid", "") break return rval def device_status(self): rval = "notfound" if not self._device_id: return rval result = run_through_shell(f"xinput list-props {self._device_id}") if result.rc == 0: match = self.status_regex.match(result.out.splitlines()[1]) if match and "status" in match.groupdict(): status = int(match.groupdict()["status"]) if status: rval = "unlocked" else: rval = "locked" return rval def _check_lock(self): try: st = os.stat(self.lock_file) if int(time.time() - st.st_ctime) > self.timeout: self.set_lock() except IOError: self.set_lock() def set_lock(self, unlock=False): if unlock: command = "enable" else: command = "disable" run_through_shell(f"xinput {command} {self._device_id}") open(self.lock_file, mode="w").close() def _clear_lock(self): try: os.unlink(self.lock_file) except FileNotFoundError: pass def run(self): status = self.device_status() if status == "notfound": self._clear_lock() self.output = { "full_text": "", } else: if status == "unlocked": self.output = { "full_text": self.unlocked_format, "color": self.unlock_color } self._check_lock() elif status == "locked": self.output = { "full_text": self.format, "color": self.color } else: self.output = { "full_text": f"Error: {status}", }