mirror of
https://gitlab.gnome.org/World/Authenticator.git
synced 2025-03-04 08:44:40 +01:00
add QR code scanner
This commit is contained in:
parent
b88ec4365c
commit
f7339b895f
8 changed files with 144 additions and 56 deletions
|
@ -3,4 +3,5 @@ appdir = $(pythondir)/TwoFactorAuth/models
|
|||
app_PYTHON = \
|
||||
code.py \
|
||||
database.py \
|
||||
qr_reader.py \
|
||||
settings.py
|
||||
|
|
41
TwoFactorAuth/models/qr_reader.py
Normal file
41
TwoFactorAuth/models/qr_reader.py
Normal 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
|
|
@ -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")
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue