add QR code scanner

This commit is contained in:
Bilal Elmoussaoui 2016-06-19 15:33:39 +02:00
parent b88ec4365c
commit f7339b895f
8 changed files with 144 additions and 56 deletions

View file

@ -3,4 +3,5 @@ appdir = $(pythondir)/TwoFactorAuth/models
app_PYTHON = \
code.py \
database.py \
qr_reader.py \
settings.py

View file

@ -0,0 +1,41 @@
from PIL import Image
import zbarlight
from urllib.parse import urlparse, parse_qsl
import logging
from os import remove, path
from TwoFactorAuth.models.code import Code
class QRReader:
def __init__(self, filename):
self.filename = filename
def read(self):
with open(self.filename, 'rb') as image_file:
image = Image.open(image_file)
image.load()
self.codes = zbarlight.scan_codes('qrcode', image)
if self.codes:
otpauth_url = self.codes[0].decode()
self.codes = dict(parse_qsl(urlparse(otpauth_url)[4]))
return self.codes
else:
logging.error("Invalid QR image")
return None
def remove(self):
"""
remove image file for security reasons
"""
if path.isfile(self.filename):
remove(filename)
logging.debug("QR code image was removed for security reasons")
def is_valid(self):
if isinstance(self.codes, dict):
if set(["issuer", "secret"]).issubset(self.codes.keys()):
return Code.is_valid(self.codes["secret"])
else:
return False
else:
return False

View file

@ -1,12 +1,12 @@
from os import path, mknod, makedirs, environ as env
from gi.repository import GdkPixbuf, Gtk
import logging
from subprocess import PIPE, Popen, call
from time import strftime
def is_gnome():
return env.get("XDG_CURRENT_DESKTOP").lower() == "gnome"
def get_home_path():
return path.expanduser("~")
@ -44,3 +44,23 @@ def create_file(file_path):
return True
else:
return False
def screenshot_area(file_name):
ink_flag = call(['which', 'gnome-screenshot'], stdout=PIPE, stderr=PIPE)
if ink_flag == 0:
p = Popen(["gnome-screenshot", "-a" , "-f", file_name],
stdout=PIPE, stderr=PIPE)
output, error = p.communicate()
if error:
error = error.decode("utf-8").split("\n")
logging.debug("\n".join([e for e in error]))
if not path.isfile(file_name):
logging.debug("The screenshot was not token")
return False
return True
else:
logging.error("Couldn't find gnome-screenshot, please install it first")
return False
def current_date_time():
return strftime("%d_%m_%Y-%H:%M:%S")

View file

@ -60,8 +60,7 @@ class AccountsList(Gtk.ListBox):
return False
def toggle_select_mode(self):
pass_enabled = self.app.cfg.read("state", "login")
is_select_mode = self.window.hb.is_on_select_mode()
is_select_mode = self.window.is_select_mode
if is_select_mode:
self.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
else:
@ -102,7 +101,7 @@ class AccountsList(Gtk.ListBox):
if checkbox.get_active():
row.remove()
self.unselect_all()
self.toggle_select_mode()
self.window.toggle_select()
self.window.refresh_window()
def select_account(self, checkbutton):

View file

@ -1,9 +1,11 @@
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk
from gi.repository import Gtk, Gdk, Gio
import logging
from TwoFactorAuth.utils import screenshot_area, current_date_time
from TwoFactorAuth.widgets.applications_list import ApplicationChooserWindow
from TwoFactorAuth.models.code import Code
from TwoFactorAuth.models.qr_reader import QRReader
from TwoFactorAuth.utils import get_icon
from gettext import gettext as _
@ -15,7 +17,7 @@ class AddAccount(Gtk.Window):
self.selected_logo = None
self.step = 1
self.logo_image = Gtk.Image(xalign=0)
self.account_image = Gtk.Image(xalign=0)
self.secret_code = Gtk.Entry()
self.name_entry = Gtk.Entry()
self.hb = Gtk.HeaderBar()
@ -28,9 +30,9 @@ class AddAccount(Gtk.Window):
Gtk.Window.__init__(self, type=Gtk.WindowType.TOPLEVEL, title=_("Add a new account"),
modal=True, destroy_with_parent=True)
self.connect("delete-event", self.close_window)
self.resize(410, 300)
self.resize(420, 300)
self.set_border_width(18)
self.set_size_request(410, 300)
self.set_size_request(420, 300)
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
self.set_resizable(False)
self.set_transient_for(self.parent)
@ -54,8 +56,19 @@ class AddAccount(Gtk.Window):
self.apply_button.get_style_context().add_class("suggested-action")
self.apply_button.connect("clicked", self.add_account)
self.apply_button.set_sensitive(False)
qr_button = Gtk.Button()
qr_icon = Gio.ThemedIcon(name="camera-photo-symbolic")
qr_image = Gtk.Image.new_from_gicon(qr_icon, Gtk.IconSize.BUTTON)
qr_button.set_tooltip_text(_("Scan a QR code"))
qr_button.set_image(qr_image)
qr_button.connect("clicked", self.on_qr_scan)
right_box.add(qr_button)
right_box.add(self.apply_button)
self.hb.pack_start(left_box)
self.hb.pack_end(right_box)
self.set_titlebar(self.hb)
@ -68,36 +81,46 @@ class AddAccount(Gtk.Window):
labels_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
logo_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
hbox_title = Gtk.Box(
hbox_name = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
title_label = Gtk.Label()
title_label.set_text(_("Account Name"))
account_name = Gtk.Label()
account_name.set_text(_("Account Name"))
hbox_title.pack_end(self.name_entry, False, True, 0)
hbox_title.pack_end(title_label, False, True, 0)
hbox_name.pack_end(self.name_entry, False, True, 0)
hbox_name.pack_end(account_name, False, True, 0)
hbox_two_factor = Gtk.Box(
hbox_secret_code = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
two_factor_label = Gtk.Label()
two_factor_label.set_text(_("Secret Code"))
secret_code_label = Gtk.Label()
secret_code_label.set_text(_("Secret Code"))
self.secret_code.connect("changed", self.validate_ascii_code)
hbox_two_factor.pack_end(self.secret_code, False, True, 0)
hbox_two_factor.pack_end(two_factor_label, False, True, 0)
hbox_secret_code.pack_end(self.secret_code, False, True, 0)
hbox_secret_code.pack_end(secret_code_label, False, True, 0)
auth_icon = get_icon("image-missing")
self.logo_image.set_from_pixbuf(auth_icon)
self.logo_image.get_style_context().add_class("application-logo-add")
logo_box.pack_start(self.logo_image, True, False, 6)
account_logo = get_icon("image-missing")
self.account_image.set_from_pixbuf(account_logo)
self.account_image.get_style_context().add_class("application-logo-add")
logo_box.pack_start(self.account_image, True, False, 6)
logo_box.set_property("margin-bottom", 20)
vbox.add(hbox_title)
vbox.add(hbox_two_factor)
vbox.add(hbox_name)
vbox.add(hbox_secret_code)
labels_box.pack_start(vbox, True, False, 6)
main_box.pack_start(logo_box, False, True, 6)
main_box.pack_start(labels_box, False, True, 6)
self.add(main_box)
def on_qr_scan(self, *args):
filename = "/tmp/TwoFactorAuth-%s.png" % current_date_time()
if screenshot_area(filename):
qr = QRReader(filename)
data = qr.read()
if qr.is_valid():
self.name_entry.set_text(data["issuer"])
self.secret_code.set_text(data["secret"])
self.apply_button.set_sensitive(True)
def on_key_press(self, key, key_event):
"""
Keyboard Listener handler
@ -110,9 +133,9 @@ class AddAccount(Gtk.Window):
Update image logo
"""
self.selected_logo = image
auth_icon = get_icon(image)
self.logo_image.clear()
self.logo_image.set_from_pixbuf(auth_icon)
account_icon = get_icon(image)
self.account_image.clear()
self.account_image.set_from_pixbuf(account_icon)
def validate_ascii_code(self, entry):
"""

View file

@ -17,8 +17,9 @@ class HeaderBar(Gtk.HeaderBar):
popover = None
def __init__(self, app):
def __init__(self, app, window):
self.app = app
self.window = window
Gtk.HeaderBar.__init__(self)
self.generate()
@ -110,19 +111,19 @@ class HeaderBar(Gtk.HeaderBar):
self.popover.show_all()
def toggle_select_mode(self):
is_visible = self.remove_button.get_visible()
is_select_mode = self.window.is_select_mode
pass_enabled = self.app.cfg.read("state", "login")
self.toggle_remove_button(not is_visible)
self.toggle_cancel_button(not is_visible)
self.set_show_close_button(is_visible)
self.toggle_settings_button(is_visible)
self.toggle_remove_button(is_select_mode)
self.toggle_cancel_button(is_select_mode)
self.set_show_close_button(not is_select_mode)
self.toggle_settings_button(not is_select_mode)
self.toggle_lock_button(is_visible and pass_enabled)
self.toggle_add_button(is_visible)
self.toggle_select_button(is_visible)
self.toggle_lock_button(not is_select_mode and pass_enabled)
self.toggle_add_button(not is_select_mode)
self.toggle_select_button(not is_select_mode)
if not is_visible:
if is_select_mode:
self.get_style_context().add_class("selection-mode")
else:
self.get_style_context().remove_class("selection-mode")
@ -168,9 +169,6 @@ class HeaderBar(Gtk.HeaderBar):
self.toggle_settings_button(True)
self.toggle_select_button(False)
def is_on_select_mode(self):
return self.remove_button.get_visible()
def refresh(self):
is_locked = self.app.locked
pass_enabled = self.app.cfg.read("state", "login")

View file

@ -14,6 +14,7 @@ from gettext import gettext as _
class Window(Gtk.ApplicationWindow):
counter = 0
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
is_select_mode = False
def __init__(self, application):
self.app = application
@ -92,7 +93,7 @@ class Window(Gtk.ApplicationWindow):
"""
Generate a header bar box
"""
self.hb = HeaderBar(self.app)
self.hb = HeaderBar(self.app, self)
# connect signals
self.hb.cancel_button.connect("clicked", self.toggle_select)
self.hb.select_button.connect("clicked", self.toggle_select)
@ -110,6 +111,7 @@ class Window(Gtk.ApplicationWindow):
"""
Toggle select mode
"""
self.is_select_mode = not self.is_select_mode
self.hb.toggle_select_mode()
self.accounts_list.toggle_select_mode()

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-06-19 12:58+0200\n"
"POT-Creation-Date: 2016-06-19 15:33+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -68,37 +68,37 @@ msgid ""
"later</a> for details."
msgstr ""
#: TwoFactorAuth/widgets/headerbar.py:43
#: TwoFactorAuth/widgets/headerbar.py:44
msgid "Remove selected accounts"
msgstr ""
#: TwoFactorAuth/widgets/headerbar.py:50
#: TwoFactorAuth/widgets/add_account.py:28
#: TwoFactorAuth/widgets/add_account.py:43
#: TwoFactorAuth/widgets/headerbar.py:51
#: TwoFactorAuth/widgets/add_account.py:30
#: TwoFactorAuth/widgets/add_account.py:45
msgid "Add a new account"
msgstr ""
#: TwoFactorAuth/widgets/headerbar.py:57
#: TwoFactorAuth/widgets/headerbar.py:58
msgid "Lock the Application"
msgstr ""
#: TwoFactorAuth/widgets/headerbar.py:73
#: TwoFactorAuth/widgets/headerbar.py:74
msgid "Selection mode"
msgstr ""
#: TwoFactorAuth/widgets/headerbar.py:80
#: TwoFactorAuth/widgets/headerbar.py:81
#: TwoFactorAuth/widgets/applications_list.py:89
msgid "Search"
msgstr ""
#: TwoFactorAuth/widgets/headerbar.py:84
#: TwoFactorAuth/widgets/add_account.py:48
#: TwoFactorAuth/widgets/headerbar.py:85
#: TwoFactorAuth/widgets/add_account.py:50
#: TwoFactorAuth/widgets/change_password.py:143
#: TwoFactorAuth/widgets/applications_list.py:81
msgid "Cancel"
msgstr ""
#: TwoFactorAuth/widgets/headerbar.py:96 TwoFactorAuth/widgets/settings.py:24
#: TwoFactorAuth/widgets/headerbar.py:97 TwoFactorAuth/widgets/settings.py:24
#: TwoFactorAuth/application.py:60
msgid "Settings"
msgstr ""
@ -107,15 +107,19 @@ msgstr ""
msgid "There's no account at the moment"
msgstr ""
#: TwoFactorAuth/widgets/add_account.py:53
#: TwoFactorAuth/widgets/add_account.py:55
msgid "Add"
msgstr ""
#: TwoFactorAuth/widgets/add_account.py:74
#: TwoFactorAuth/widgets/add_account.py:64
msgid "Scan a QR code"
msgstr ""
#: TwoFactorAuth/widgets/add_account.py:87
msgid "Account Name"
msgstr ""
#: TwoFactorAuth/widgets/add_account.py:82
#: TwoFactorAuth/widgets/add_account.py:95
msgid "Secret Code"
msgstr ""