few missing files

This commit is contained in:
Bilal Elmoussaoui 2017-01-12 22:51:42 +01:00
parent 0037c6d227
commit f99922248d
43 changed files with 6081 additions and 0 deletions

20
Authenticator/__init__.py Normal file
View file

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""

View file

@ -0,0 +1,216 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
require_version("GnomeKeyring", "1.0")
from gi.repository import Gtk, GLib, Gio, Gdk, GObject, GnomeKeyring as GK
from Authenticator.widgets.window import Window
from Authenticator.models.database import Database
from Authenticator.widgets.settings import SettingsWindow
from Authenticator.models.settings import SettingsReader
from Authenticator.interfaces.application_observrable import ApplicaitonObservable
from Authenticator.utils import *
import logging
import signal
from gettext import gettext as _
from os import environ as env
class Application(Gtk.Application):
win = None
alive = True
locked = False
settings_action = None
def __init__(self):
Gtk.Application.__init__(self,
application_id="org.gnome.Authenticator",
flags=Gio.ApplicationFlags.FLAGS_NONE)
GLib.set_application_name(_("Gnome Authenticator"))
GLib.set_prgname("Gnome Authenticator")
self.observable = ApplicaitonObservable()
self.menu = Gio.Menu()
self.db = Database()
self.cfg = SettingsReader()
self.locked = self.cfg.read("state", "login")
result = GK.unlock_sync("org.gnome.Authenticator", None)
if result == GK.Result.CANCELLED:
self.quit()
if Gtk.get_major_version() >= 3 and Gtk.get_minor_version() >= 20:
cssFileName = "org.gnome.Authenticator-post3.20.css"
else:
cssFileName = "org.gnome.Authenticator-pre3.20.css"
cssProviderFile = Gio.File.new_for_uri(
'resource:///org/gnome/Authenticator/%s' % cssFileName)
cssProvider = Gtk.CssProvider()
screen = Gdk.Screen.get_default()
styleContext = Gtk.StyleContext()
try:
cssProvider.load_from_file(cssProviderFile)
styleContext.add_provider_for_screen(screen, cssProvider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
logging.debug("Loading css file ")
except Exception as e:
logging.error("Error message %s" % str(e))
def do_startup(self):
Gtk.Application.do_startup(self)
self.generate_menu()
def generate_menu(self):
# Settings section
settings_content = Gio.Menu.new()
settings_content.append_item(
Gio.MenuItem.new(_("Settings"), "app.settings"))
self.is_dark_mode_menu = Gio.MenuItem.new(_("Night mode"), "app.night_mode")
settings_content.append_item(self.is_dark_mode_menu)
settings_section = Gio.MenuItem.new_section(None, settings_content)
self.menu.append_item(settings_section)
# Help section
help_content = Gio.Menu.new()
if Gtk.get_major_version() >= 3 and Gtk.get_minor_version() >= 20:
help_content.append_item(Gio.MenuItem.new(
_("Shortcuts"), "app.shortcuts"))
help_content.append_item(Gio.MenuItem.new(_("About"), "app.about"))
help_content.append_item(Gio.MenuItem.new(_("Quit"), "app.quit"))
help_section = Gio.MenuItem.new_section(None, help_content)
self.menu.append_item(help_section)
self.dark_mode_action = Gio.SimpleAction.new("night_mode", None)
self.dark_mode_action.connect("activate", self.enable_dark_mode)
self.add_action(self.dark_mode_action)
self.settings_action = Gio.SimpleAction.new("settings", None)
self.settings_action.connect("activate", self.on_settings)
self.settings_action.set_enabled(not self.locked)
self.add_action(self.settings_action)
if Gtk.get_major_version() >= 3 and Gtk.get_minor_version() >= 20:
action = Gio.SimpleAction.new("shortcuts", None)
action.connect("activate", self.on_shortcuts)
self.add_action(action)
action = Gio.SimpleAction.new("about", None)
action.connect("activate", self.on_about)
self.add_action(action)
action = Gio.SimpleAction.new("quit", None)
action.connect("activate", self.on_quit)
self.add_action(action)
self.refresh_menu_night_mode()
if not show_app_menu():
self.set_app_menu(self.menu)
logging.debug("Adding gnome shell menu")
def enable_dark_mode(self, *args):
is_dark_mode = self.cfg.read("night-mode", "preferences")
self.cfg.update("night-mode", not is_dark_mode, "preferences")
self.refresh_menu_night_mode()
def refresh_menu_night_mode(self):
is_dark_mode = self.cfg.read("night-mode", "preferences")
settings = Gtk.Settings.get_default()
settings.set_property("gtk-application-prefer-dark-theme", is_dark_mode)
if is_dark_mode:
self.is_dark_mode_menu.set_icon(Gio.ThemedIcon.new("emblem-ok-symbolic"))
else:
self.is_dark_mode_menu.set_icon(Gio.ThemedIcon.new(""))
#self.dark_mode_action.set_state(GLib.Variant.new_boolean(is_dark_mode))
def do_activate(self, *args):
if not self.win:
self.win = Window(self)
self.win.show_all()
self.add_window(self.win)
else:
self.win.present()
def refresh_menu(self):
is_enabled = self.settings_action.get_enabled()
self.settings_action.set_enabled(not self.locked)
def on_shortcuts(self, *args):
"""
Shows keyboard shortcuts
"""
shortcuts = Application.shortcuts_dialog()
if shortcuts:
shortcuts.set_transient_for(self.win)
shortcuts.show()
@staticmethod
def shortcuts_dialog():
if Gtk.get_major_version() >= 3 and Gtk.get_minor_version() >= 20:
builder = Gtk.Builder()
builder.add_from_resource('/org/gnome/Authenticator/shortcuts.ui')
shortcuts = builder.get_object("shortcuts")
return shortcuts
return None
@staticmethod
def about_dialog():
"""
Shows about dialog
"""
builder = Gtk.Builder()
builder.add_from_resource('/org/gnome/Authenticator/about.ui')
dialog = builder.get_object("AboutDialog")
return dialog
def on_about(self, *args):
"""
Shows about dialog
"""
dialog = Application.about_dialog()
dialog.set_transient_for(self.win)
dialog.run()
dialog.destroy()
def on_settings(self, *args):
"""
Shows settings window
"""
settings_window = SettingsWindow(self.win)
settings_window.show_window()
settings_window.present()
def on_quit(self, *args):
"""
Close the application, stops all threads
and clear clipboard for safety reasons
"""
try:
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clipboard.clear()
except Exception as e:
logging.error(str(e))
self.observable.update_observers(alive=False)
if self.win:
self.win.save_window_state()
self.win.destroy()
self.quit()

View file

@ -0,0 +1,13 @@
from TwoFactorAuth.interfaces.observable import Observable
class AccountObservable(Observable):
def update_observers(self, *args, **kwargs):
for observer in self.observers:
observer.update(*args, **kwargs)
class AccountRowObservable(Observable):
def update_observers(self, *args, **kwargs):
for observer in self.observers:
observer.update(*args, **kwargs)

View file

@ -0,0 +1,7 @@
from TwoFactorAuth.interfaces.observable import Observable
class ApplicaitonObservable(Observable):
def update_observers(self, *args, **kwargs):
for observer in self.observers:
observer.update(*args, **kwargs)

View file

@ -0,0 +1,20 @@
class Observable(object):
def __init__(self):
self.observers = []
def register(self, observer):
if not observer in self.observers:
self.observers.append(observer)
def unregister(self, observer):
if observer in self.observers:
self.observers.remove(observer)
def unregister_all(self):
if self.observers:
del self.observers[:]
def update_observers(self, *args, **kwargs):
for observer in self.observers:
observer.update(*args, **kwargs)

View file

@ -0,0 +1,110 @@
from Authenticator.models.settings import SettingsReader
from Authenticator.models.code import Code
from Authenticator.models.database import Database
from Authenticator.models.observer import Observer
from Authenticator.interfaces.account_observrable import AccountObservable, AccountRowObservable
from gi.repository import GObject
from threading import Thread
from time import sleep
import logging
class Account(GObject.GObject, Thread, Observer):
__gsignals__ = {
'code_updated': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
'name_updated': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
'counter_updated': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
'removed': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
}
counter_max = 30
counter = 30
alive = True
code_generated = True
def __init__(self, app, db):
Thread.__init__(self)
GObject.GObject.__init__(self)
self.db = db
cfg = SettingsReader()
self.counter_max = cfg.read("refresh-time", "preferences")
self.counter = self.counter_max
self.account_id = app[0]
self.account_name = app[1]
self.secret_code = Database.fetch_secret_code(app[2])
if self.secret_code:
self.code = Code(self.secret_code)
else:
self.code_generated = False
logging.error("Could not read the secret code,"
"the keyring keys were reset manually")
self.logo = app[3]
self.account_observerable = AccountObservable()
self.row_observerable = AccountRowObservable()
self.start()
def do_name_updated(self, *args):
self.account_observerable.update_observers(name=self.get_name())
def do_counter_updated(self, *args):
self.account_observerable.update_observers(counter=self.get_counter())
def do_code_updated(self, *args):
self.account_observerable.update_observers(code=self.get_code())
def do_removed(self, *args):
self.row_observerable.update_observers(removed=self.get_id())
def update(self, alive):
if not alive:
self.kill()
def run(self):
while self.code_generated and self.alive:
self.counter -= 1
if self.counter == 0:
self.counter = self.counter_max
self.code.update()
self.emit("code_updated", True)
self.emit("counter_updated", True)
sleep(1)
def get_id(self):
"""
Get the application id
:return: (int): row id
"""
return self.account_id
def get_name(self):
"""
Get the application name
:return: (str): application name
"""
return self.account_name
def get_logo(self):
return self.logo
def get_code(self):
return self.code.get_secret_code()
def get_counter(self):
return self.counter
def get_counter_max(self):
return self.counter_max
def kill(self):
"""
Kill the row thread once it's removed
"""
self.alive = False
def remove(self):
self.db.remove_by_id(self.get_id())
self.emit("removed", True)
def set_name(self, name):
self.db.update_name_by_id(self.get_id(), name)
self.account_name = name
self.emit("name_updated", True)

View file

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
import logging
import binascii
from base64 import b32decode
try:
from pyotp import TOTP
except ImportError:
logging.error("Impossible to import TOTP, please install PyOTP first")
class Code:
password = None
def __init__(self, secret_code):
self.secret_code = secret_code
self.create()
@staticmethod
def is_valid(code):
"""
Check if the secret code is a valid one
"""
try:
b32decode(code, casefold=True)
return True
except (binascii.Error, ValueError):
return False
def create(self):
"""
Create a tfa code
"""
try:
self.totp = TOTP(self.secret_code)
self.password = self.totp.now()
except Exception as e:
logging.error("Couldn't generate two factor code : %s" % str(e))
def update(self):
"""
Update the code
"""
self.password = self.totp.now()
def get_secret_code(self):
try:
if self.password:
return self.password
else:
raise AttributeError
except AttributeError as e:
logging.error("Couldn't generate the code : %s " % str(e))
return None

View file

@ -0,0 +1,195 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
import sqlite3
import logging
from gi.repository import GnomeKeyring as GK, GLib
from hashlib import sha256
from Authenticator.utils import create_file
class Database:
def __init__(self):
database_file = GLib.get_home_dir() + '/.config/Authenticator/database.db'
if create_file(database_file):
logging.debug("Creating database file %s " % database_file)
self.conn = sqlite3.connect(database_file)
if not self.is_table_exists():
logging.debug(
"SQL: Table 'accounts' does not exists, creating it now...")
self.create_table()
logging.debug("SQL: Table 'accounts' created successfully")
@staticmethod
def fetch_secret_code(secret_code):
attr = GK.Attribute.list_new()
GK.Attribute.list_append_string(attr, 'id', secret_code)
result, value = GK.find_items_sync(GK.ItemType.GENERIC_SECRET, attr)
if result == GK.Result.OK:
return value[0].secret
else:
return None
def add_account(self, name, secret_code, image):
"""
Add an account to accounts table
:param name: (str) account name
:param secret_code: (str) ASCII Secret code
:param image: image path or icon name
:return:
"""
encrypted_secret = sha256(secret_code.encode('utf-8')).hexdigest()
t = (name, encrypted_secret, image,)
query = "INSERT INTO accounts (name, secret_code, image) VALUES (?, ?, ?)"
try:
GK.create_sync("org.gnome.Authenticator", None)
attr = GK.Attribute.list_new()
GK.Attribute.list_append_string(attr, 'id', encrypted_secret)
GK.Attribute.list_append_string(attr, 'secret_code', secret_code)
GK.item_create_sync("org.gnome.Authenticator", GK.ItemType.GENERIC_SECRET, repr(encrypted_secret), attr,
secret_code, False)
self.conn.execute(query, t)
self.conn.commit()
uid = self.get_latest_id()
return [uid, name, encrypted_secret, image]
except Exception as e:
logging.error("SQL: Couldn't add a new account : %s ", str(e))
def get_secret_code(self, uid):
"""
Count number of accounts
:return: (int) count
"""
c = self.conn.cursor()
query = "SELECT secret_code FROM accounts WHERE uid=?"
try:
data = c.execute(query, (uid,))
return data.fetchone()[0]
except Exception as e:
logging.error(
"SQL: Couldn't get account secret code : %s " % str(e))
return None
def remove_by_id(self, uid):
"""
Remove an account by uid
:param uid: (int) account uid
:return:
"""
secret_code = self.get_secret_code(uid)
if secret_code:
found = False
(result, ids) = GK.list_item_ids_sync("org.gnome.Authenticator")
for gid in ids:
(result, item) = GK.item_get_info_sync("org.gnome.Authenticator", gid)
if result == GK.Result.OK:
if item.get_display_name().strip("'") == secret_code:
found = True
break
if found:
GK.item_delete_sync("org.gnome.Authenticator", gid)
query = "DELETE FROM accounts WHERE uid=?"
try:
self.conn.execute(query, (uid,))
self.conn.commit()
except Exception as e:
logging.error(
"SQL: Couldn't remove account by uid : %s with error : %s" % (uid, str(e)))
def update_name_by_id(self, id, new_name):
query = "UPDATE accounts SET name=? WHERE uid=?"
try:
self.conn.execute(query, (new_name, id, ))
self.conn.commit()
except Exception as e:
logging.error("SQL: couldn't update account name by id : %s with error : %s" %(id, str(e)))
def count(self):
"""
Count number of accounts
:return: (int) count
"""
c = self.conn.cursor()
query = "SELECT COUNT(uid) AS count FROM accounts"
try:
data = c.execute(query)
return data.fetchone()[0]
except Exception as e:
logging.error(
"SQL: Couldn't count accounts list : %s " % str(e))
return None
def fetch_apps(self):
"""
Fetch list of accounts
:return: (tuple) list of accounts
"""
c = self.conn.cursor()
query = "SELECT * FROM accounts"
try:
data = c.execute(query)
return data.fetchall()
except Exception as e:
logging.error("SQL: Couldn't fetch accounts list %s" % str(e))
return None
def get_latest_id(self):
"""
Get the latest uid on accounts table
:return: (int) latest uid
"""
c = self.conn.cursor()
query = "SELECT uid FROM accounts ORDER BY uid DESC LIMIT 1;"
try:
data = c.execute(query)
return data.fetchone()[0]
except Exception as e:
logging.error("SQL: Couldn't fetch the latest uid %s" % str(e))
return None
def create_table(self):
"""
Create accounts table
"""
query = '''CREATE TABLE "accounts" (
"uid" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE ,
"name" VARCHAR NOT NULL ,
"secret_code" VARCHAR NOT NULL ,
"image" TEXT NOT NULL
)'''
try:
self.conn.execute(query)
self.conn.commit()
except Exception as e:
logging.error(
"SQL: impossible to create table 'accounts' %s " % str(e))
def is_table_exists(self):
"""
Check if accounts table exists
:return: (bool)
"""
query = "SELECT uid from accounts LIMIT 1"
c = self.conn.cursor()
try:
data = c.execute(query)
return True
except Exception as e:
return False

View file

@ -0,0 +1,8 @@
from abc import ABCMeta, abstractmethod
class Observer(object):
__metaclass__ = ABCMeta
@abstractmethod
def update(self, *args, **kwargs):
pass

View file

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from PIL import Image
from zbarlight import scan_codes
from urllib.parse import urlparse, parse_qsl
import logging
from os import remove, path
from Authenticator.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 = scan_codes('qrcode', image)
self.remove()
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(self.filename)
logging.debug("QR code image was removed for security reasons")
def is_valid(self):
"""
Validate if the QR code is a valid tfa
"""
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

@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi.repository import Gtk, Gio
import logging
class SettingsReader:
path = "org.gnome.Authenticator"
def __init__(self):
try:
# Check if the gsettings path exists
self.source = Gio.SettingsSchemaSource.get_default()
self.source.lookup(self.path, True)
except Exception as e:
logging.critical("Couldn't load gsettings source %s " % str(e))
def read(self, key, path):
"""
Read a 'key' from org.gnome.Authenticator.'path'
:param key: (str) key to read
:param path: login/user
:return: value
"""
gsettings = Gio.Settings.new(self.path + "." + path)
value = gsettings.get_value(key)
value_type = value.get_type_string()
value = str(value).strip("'")
if value_type == "i":
return int(value)
elif value_type == "b":
return value == "true"
else:
return value
def update(self, key, value, path):
"""
Update 'key' value to 'value' from org.gnome.Authenticator.'path'
:param key: (str) key to read
:param value: updated value
:param path: login/user
:return: value
"""
gsettings = Gio.Settings.new(self.path + "." + path)
if type(value) is int:
gsettings.set_int(key, value)
elif type(value) is bool:
gsettings.set_boolean(key, value)
else:
gsettings.set_string(key, value)
gsettings.apply()

112
Authenticator/utils.py Normal file
View file

@ -0,0 +1,112 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from os import path, mknod, makedirs, environ as env
from gi.repository import GdkPixbuf, Gtk, Gio, GLib
import logging
from subprocess import PIPE, Popen, call
from tempfile import NamedTemporaryFile
def is_gnome():
"""
Check if the current distro is gnome
"""
return env.get("XDG_CURRENT_DESKTOP").lower() == "gnome"
def show_app_menu():
try:
source = Gio.SettingsSchemaSource.get_default()
path = "org.gnome.desktop.wm.preferences"
key = "button-layout"
source.lookup(path, True)
gsettings = Gio.Settings.new(path)
value = gsettings.get_value(key)
value = str(value).strip("'")
# "appmenu" in value or
return not is_gnome()
except Exception as e:
logging.critical("Couldn't load gsettings source %s " % str(e))
def get_icon(image, size):
"""
Generate a GdkPixbuf image
:param image: icon name or image path
:return: GdkPixbux Image
"""
directory = path.join(env.get("DATA_DIR"), "applications", "images") + "/"
theme = Gtk.IconTheme.get_default()
if theme.has_icon(path.splitext(image)[0]):
icon = theme.load_icon(path.splitext(image)[0], size, 0)
elif path.exists(directory + image):
icon = GdkPixbuf.Pixbuf.new_from_file(directory + image)
elif path.exists(image):
icon = GdkPixbuf.Pixbuf.new_from_file(image)
else:
icon = theme.load_icon("image-missing", size, 0)
if icon.get_width() != size or icon.get_height() != size:
icon = icon.scale_simple(size, size, GdkPixbuf.InterpType.BILINEAR)
return icon
def create_file(file_path):
"""
Create a file and create parent folder if missing
"""
if not (path.isfile(file_path) and path.exists(file_path)):
dirs = file_path.split("/")
i = 0
while i < len(dirs) - 1:
directory = "/".join(dirs[0:i + 1]).strip()
if not path.exists(directory) and len(directory) != 0:
makedirs(directory)
logging.debug("Creating directory %s " % directory)
i += 1
mknod(file_path)
return True
else:
return False
def screenshot_area():
"""
Screenshot an area of the screen using gnome-screenshot
used to QR scan
"""
ink_flag = call(['which', 'gnome-screenshot'], stdout=PIPE, stderr=PIPE)
if ink_flag == 0:
file_name = path.join(GLib.get_tmp_dir(), NamedTemporaryFile().name)
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.error("\n".join([e for e in error]))
if not path.isfile(file_name):
logging.debug("The screenshot was not token")
return False
return file_name
else:
logging.error(
"Couldn't find gnome-screenshot, please install it first")
return False

View file

@ -0,0 +1,442 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, GLib, Pango
from Authenticator.widgets.confirmation import ConfirmationMessage
from Authenticator.utils import get_icon
from threading import Thread
from time import sleep
import logging
from gettext import gettext as _
from Authenticator.models.observer import Observer
class RowEntryName(Gtk.Entry):
def __init__(self, name):
self.name = name
self.generate()
def generate(self):
Gtk.Entry.__init__(self, xalign=0)
self.set_text(self.name)
self.set_width_chars(25)
self.set_max_width_chars(25)
self.hide()
def toggle(self, visible):
self.set_visible(visible)
self.set_no_show_all(not visible)
def is_visible(self):
return self.get_visible()
def hide(self):
self.toggle(False)
def show(self):
self.toggle(True)
def focus(self):
self.grab_focus_without_selecting()
class AccountRow(Observer):
notification = None
timer = 0
remove_timer = 0
def __init__(self, parent, window, account):
# Read default values
self.window = window
self.parent = parent
self.account = account
self.account.account_observerable.register(self)
# Create needed widgets
self.code_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.revealer = Gtk.Revealer()
self.checkbox = Gtk.CheckButton()
self.application_name = Gtk.Label(xalign=0)
self.code_label = Gtk.Label(xalign=0)
self.timer_label = Gtk.Label(xalign=0)
self.is_grid_view = isinstance(self, AccountRowGrid)
def update(self, *args, **kwargs):
self.set_account_code(kwargs.get("code", None))
self.set_account_name(kwargs.get("name", None))
self.set_account_counter(kwargs.get("counter", None))
def set_account_name(self, name):
if name:
self.application_name.props.tooltip_text = name
self.application_name .set_text(name)
def set_account_code(self, code):
if code:
self.code_label.set_text(str(code))
def set_account_counter(self, counter):
if counter:
if self.is_grid_view:
label = str(counter)
else:
label = _("Expires in %s seconds" % str(counter))
self.timer_label.set_label(label)
def get_code_label(self):
return self.code_label
def get_name(self):
return self.account.get_name()
def get_checkbox(self):
"""
Get ListBowRow's checkbox
:return: (Gtk.Checkbox)
"""
return self.checkbox
def get_code_box(self):
"""
Get code's box
:return: (Gtk.Box)
"""
return self.code_box
def toggle_code_box(self):
"""
Toggle code box
"""
self.revealer.set_reveal_child(not self.revealer.get_reveal_child())
def copy_code(self, *args):
"""
Copy code shows the code box for a while (10s by default)
"""
self.timer = 0
code = self.account.get_code()
try:
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
self.window.notification.update(
_('Code "{0}" copied to clipboard'.format(str(code))))
self.window.notification.show()
clipboard.clear()
clipboard.set_text(code, len(code))
logging.debug("Secret code copied to clipboard")
except Exception as e:
logging.error(str(e))
self.revealer.set_reveal_child(True)
GLib.timeout_add_seconds(1, self.update_timer)
def update_remove_countdown(self, *args):
if self.remove_timer > 0:
self.remove_timer -= 1
if self.remove_timer == 0:
self.account.remove()
return self.timer <= self.window.notification.timeout
def update_timer(self, *args):
"""
Update timer
"""
self.timer += 1
if self.timer > 10:
self.revealer.set_reveal_child(False)
return self.timer <= 10
def toggle_code(self, *args):
self.toggle_code_box()
def toggle_action_box(self, visible):
self.actions_box.set_visible(visible)
self.actions_box.set_no_show_all(not visible)
def remove(self, *args):
"""
Remove an account
"""
message = _('Do you really want to remove "%s"?' % self.account.get_name())
confirmation = ConfirmationMessage(self.window, message)
confirmation.show()
if confirmation.get_confirmation():
self.window.notification.update(_('"%s" was removed' % self.account.get_name()), self.undo_remove)
self.window.notification.show()
self.remove_timer = self.window.notification.timeout
GLib.timeout_add_seconds(1, self.update_remove_countdown)
confirmation.destroy()
def undo_remove(self):
self.remove_timer = 0
self.window.notification.hide()
class AccountRowList(AccountRow, Gtk.ListBoxRow):
def __init__(self, parent, window, account):
AccountRow.__init__(self, parent, window, account)
Gtk.ListBoxRow.__init__(self)
self.create_row()
self.window.connect("key-press-event", self.__on_key_press)
def create_row(self):
"""
Create ListBoxRow
"""
self.get_style_context().add_class("application-list-row")
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
h_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
box.pack_start(h_box, True, True, 0)
box.pack_start(self.revealer, True, True, 0)
# Checkbox
self.checkbox.set_visible(False)
self.checkbox.set_no_show_all(True)
self.checkbox.connect("toggled", self.parent.select_account)
h_box.pack_start(self.checkbox, False, True, 6)
# account logo
auth_icon = get_icon(self.account.get_logo(), 48)
auth_logo = Gtk.Image(xalign=0)
auth_logo.set_from_pixbuf(auth_icon)
h_box.pack_start(auth_logo, False, True, 6)
# Account name entry
name_entry_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.name_entry = RowEntryName(self.account.get_name())
name_entry_box.pack_start(self.name_entry, False, False, 6)
h_box.pack_start(name_entry_box, False, False, 0)
name_entry_box.set_visible(False)
# accout name
name_event = Gtk.EventBox()
self.application_name .get_style_context().add_class("application-name")
self.application_name.set_ellipsize(Pango.EllipsizeMode.END)
self.set_account_name(self.account.get_name())
name_event.connect("button-press-event", self.toggle_code)
name_event.add(self.application_name)
h_box.pack_start(name_event, False, True, 6)
# Remove button
self.actions_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
remove_event = Gtk.EventBox()
remove_button = Gtk.Image(xalign=0)
remove_button.set_from_icon_name("user-trash-symbolic",
Gtk.IconSize.SMALL_TOOLBAR)
remove_button.set_tooltip_text(_("Remove the account"))
remove_event.add(remove_button)
remove_event.connect("button-press-event", self.remove)
self.actions_box.pack_end(remove_event, False, False, 6)
# Copy button
copy_event = Gtk.EventBox()
copy_button = Gtk.Image(xalign=0)
copy_button.set_from_icon_name(
"edit-copy-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
copy_button.set_tooltip_text(_("Copy the generated code"))
copy_event.connect("button-press-event", self.copy_code)
copy_event.add(copy_button)
self.actions_box.pack_end(copy_event, False, False, 6)
# Edit button
edit_event = Gtk.EventBox()
self.edit_button = Gtk.Image(xalign=0)
self.edit_button.set_from_icon_name("document-edit-symbolic",
Gtk.IconSize.SMALL_TOOLBAR)
self.edit_button.set_tooltip_text(_("Edit the account"))
edit_event.add(self.edit_button)
edit_event.connect("button-press-event", self.edit)
self.actions_box.pack_end(edit_event, False, False, 6)
# Apply button
apply_event = Gtk.EventBox()
self.apply_button = Gtk.Image(xalign=0)
self.apply_button.set_from_icon_name("emblem-ok-symbolic",
Gtk.IconSize.SMALL_TOOLBAR)
self.apply_button.set_tooltip_text(_("Save the new account name"))
apply_event.add(self.apply_button)
apply_event.connect("button-press-event", self.apply_edit_name)
self.actions_box.pack_end(apply_event, False, False, 6)
h_box.pack_end(self.actions_box, False, False, 0)
self.toggle_action_box(True)
self.toggle_edit_mode(False)
self.set_account_counter(self.account.get_counter())
self.timer_label.get_style_context().add_class("account-timer")
self.code_label.get_style_context().add_class("account-secret-code")
if self.account.code_generated:
self.set_account_code(self.account.get_code())
else:
self.set_account_code(_("Error during the generation of code"))
self.code_box.pack_end(self.timer_label, False, True, 6)
self.code_box.pack_start(self.code_label, False, True, 6)
self.revealer.add(self.code_box)
self.revealer.set_reveal_child(False)
self.add(box)
def toggle_edit_mode(self, visible):
if visible:
self.name_entry.show()
self.name_entry.set_text(self.account.get_name())
self.name_entry.focus()
else:
self.name_entry.hide()
self.application_name.set_visible(not visible)
self.application_name.set_no_show_all(visible)
self.apply_button.set_visible(visible)
self.apply_button.set_no_show_all(not visible)
self.edit_button.get_parent().set_visible(not visible)
self.edit_button.get_parent().set_no_show_all(visible)
def __on_key_press(self, widget, event):
keyname = Gdk.keyval_name(event.keyval).lower()
if not self.window.is_locked():
if self.parent.get_selected_row_id() == self.account.get_id():
is_search_bar = self.window.search_bar.is_visible()
is_editing_name = self.name_entry.is_visible()
if keyname == "delete" and not is_search_bar and not is_editing_name:
self.remove()
return True
if keyname == "escape":
if is_editing_name:
self.toggle_edit_mode(False)
return True
if keyname == "return":
if is_editing_name:
self.apply_edit_name()
else:
self.toggle_code_box()
return True
if event.state & Gdk.ModifierType.CONTROL_MASK:
if keyname == 'c':
self.copy_code()
return True
return False
def edit(self, *args):
is_visible = self.name_entry.is_visible()
self.toggle_edit_mode(not is_visible)
self.parent.select_row(self)
def apply_edit_name(self, *args):
new_name = self.name_entry.get_text()
self.application_name.set_text(new_name)
self.account.set_name(new_name)
self.toggle_edit_mode(False)
class AccountRowGrid(AccountRow, Gtk.Box):
def __init__(self, parent, window, account):
AccountRow.__init__(self, parent, window, account)
Gtk.Box.__init__(self)
self.create_row()
self.window.connect("key-press-event", self.__on_key_press)
def create_row(self):
"""
Create ListBoxRow
"""
self.actions_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
v_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# Checkbox
overlay_box = Gtk.Overlay()
checkbox_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.checkbox.set_visible(False)
self.checkbox.get_style_context().add_class("checkbutton-grid")
self.checkbox.set_no_show_all(True)
self.checkbox.connect("toggled", self.parent.select_account)
checkbox_box.pack_end(self.checkbox, False, False, 0)
overlay_box.add_overlay(checkbox_box)
# account logo
logo_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
auth_icon = get_icon(self.account.get_logo(), 96)
auth_logo = Gtk.Image(xalign=0)
auth_logo.set_from_pixbuf(auth_icon)
logo_box.add(auth_logo)
overlay_box.add(logo_box)
v_box.pack_start(overlay_box, False, False, 6)
# accout name
name_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
name_event = Gtk.EventBox()
self.application_name .get_style_context().add_class("application-name-grid")
self.application_name.set_ellipsize(Pango.EllipsizeMode.END)
self.set_account_name(self.account.get_name())
name_event.connect("button-press-event", self.toggle_code)
name_event.add(self.application_name)
name_box.pack_start(name_event, True, False, 6)
v_box.pack_start(name_box, False, False, 0)
# Copy button
copy_event = Gtk.EventBox()
copy_button = Gtk.Image(xalign=0)
copy_button.set_from_icon_name(
"edit-copy-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
copy_button.set_tooltip_text(_("Copy the generated code"))
copy_event.connect("button-press-event", self.copy_code)
copy_event.add(copy_button)
self.actions_box.pack_end(copy_event, False, False, 6)
self.set_account_counter(self.account.get_counter())
self.timer_label.get_style_context().add_class("account-timer-grid")
self.code_label.get_style_context().add_class("account-secret-code-grid")
if self.account.code_generated:
self.set_account_code(self.account.get_code())
else:
self.set_account_code(_("Error during the generation of code"))
self.code_box.pack_end(self.timer_label, False, True, 6)
self.code_box.pack_start(self.code_label, True, False, 6)
self.revealer.add(self.code_box)
self.revealer.set_reveal_child(False)
v_box.pack_start(self.revealer, False, False, 0)
# self.pack_start(checkbox_box, False, False, 0)
self.pack_start(v_box, True, False, 0)
def __on_key_press(self, widget, event):
keyname = Gdk.keyval_name(event.keyval).lower()
if not self.window.is_locked():
if self.parent.get_selected_row_id() == self.account.get_id():
is_search_bar = self.window.search_bar.is_visible()
if keyname == "delete" and not is_search_bar:
self.remove()
return True
if keyname == "return":
self.toggle_code_box()
return True
if event.state & Gdk.ModifierType.CONTROL_MASK:
if keyname == 'c':
self.copy_code()
return True
return False

View file

@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, Gdk, GObject, GLib
from Authenticator.widgets.confirmation import ConfirmationMessage
from Authenticator.widgets.account_row import AccountRowGrid
from gettext import gettext as _
from hashlib import sha256
import logging
class AccountsGrid(Gtk.FlowBox):
scrolled_win = None
def __init__(self, window, accounts):
self.accounts = accounts
self.window = window
self.generate()
self.connect("child-activated", self.activate_child)
self.connect("selected-children-changed", self.selected_child)
GLib.timeout_add_seconds(1, self.refresh)
def generate(self):
Gtk.FlowBox.__init__(self,orientation=Gtk.Orientation.HORIZONTAL)
# Create a ScrolledWindow for accounts
self.set_min_children_per_line(2)
self.set_max_children_per_line(8)
self.set_valign(Gtk.Align.START)
self.set_column_spacing(0)
self.set_row_spacing(0)
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
self.scrolled_win = Gtk.ScrolledWindow()
self.scrolled_win.add_with_viewport(self)
self.set_homogeneous(True)
count = len(self.accounts)
for account in self.accounts:
self.add(AccountRowGrid(self, self.window, account))
if count != 0:
self.select_child(self.get_child_at_index(0))
self.show_all()
def selected_child(self, account_list):
for box in self.get_children():
row = box.get_children()[0]
checkbutton = row.get_checkbox()
if not checkbutton.get_active() and self.window.is_select_mode:
self.unselect_child(box)
def activate_child(self, account_list, selected_child):
if self.window.is_select_mode and selected_child:
selected_box = selected_child.get_children()[0]
self.select_account(selected_box.get_checkbox())
def toggle_select_mode(self):
is_select_mode = self.window.is_select_mode
if is_select_mode:
self.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
else:
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
if len(self.get_children()) != 0:
self.select_child(self.get_child_at_index(0))
for box in self.get_children():
row = box.get_children()[0]
checkbox = row.get_checkbox()
code_label = row.get_code_label()
visible = checkbox.get_visible()
style_context = code_label.get_style_context()
if is_select_mode:
self.select_account(checkbox)
row.toggle_action_box(visible)
checkbox.set_visible(not visible)
checkbox.set_no_show_all(visible)
def remove_selected(self, *args):
"""
Remove selected accounts
"""
for row in self.get_selected_children():
checkbox = row.get_checkbox()
if checkbox.get_active():
row.remove()
self.unselect_all()
self.window.toggle_select()
self.window.refresh_window()
def select_account(self, checkbutton):
"""
Select an account
:param checkbutton:
"""
is_active = checkbutton.get_active()
is_visible = checkbutton.get_visible()
flowbox_child = checkbutton.get_parent().get_parent().get_parent().get_parent().get_parent()
if is_active:
self.select_child(flowbox_child)
else:
self.unselect_child(flowbox_child)
selected_count = len(self.get_selected_children())
self.window.hb.remove_button.set_sensitive(selected_count > 0)
def get_selected_child_id(self):
selected_box = self.get_selected_children()
if selected_box:
return selected_box[0].get_children()[0].account.get_id()
else:
return None
def get_selected_row_id(self):
return self.get_selected_child_id()
def toggle(self, visible):
self.set_visible(visible)
self.set_no_show_all(not visible)
self.get_scrolled_win().set_visible(visible)
self.get_scrolled_win().set_no_show_all(not visible)
def is_visible(self):
return self.get_visible()
def hide(self):
self.toggle(False)
def show(self):
self.toggle(True)
def get_scrolled_win(self):
return self.scrolled_win
def refresh(self):
self.scrolled_win.hide()
self.scrolled_win.show_all()
def append(self, account):
self.add(AccountRowGrid(self, self.window, account))
self.refresh()
def remove_by_id(self, _id):
for row in self.get_children():
row = row.get_children()[0]
if row.account.get_id() == _id:
self.remove(row)
break
self.refresh()

View file

@ -0,0 +1,182 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, Gdk, GObject, GLib
from Authenticator.widgets.confirmation import ConfirmationMessage
from Authenticator.widgets.account_row import AccountRowList
from gettext import gettext as _
from hashlib import sha256
import logging
class AccountsList(Gtk.ListBox):
scrolled_win = None
def __init__(self, window, accounts):
self.accounts = accounts
self.window = window
self.generate()
self.window.connect("key-press-event", self.on_key_press)
self.connect("row-activated", self.activate_row)
self.connect("row-selected", self.selected_row)
GLib.timeout_add_seconds(1, self.refresh)
def generate(self):
Gtk.ListBox.__init__(self)
# Create a ScrolledWindow for accounts
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.get_style_context().add_class("applications-list")
self.set_adjustment()
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
box.pack_start(self, True, True, 0)
self.scrolled_win = Gtk.ScrolledWindow()
self.scrolled_win.add_with_viewport(box)
count = len(self.accounts)
for account in self.accounts:
self.add(AccountRowList(self, self.window, account))
if count != 0:
self.select_row(self.get_row_at_index(0))
self.show_all()
def selected_row(self, account_list, selected_row):
for row in self.get_children():
if row != selected_row:
row.toggle_edit_mode(False)
checkbutton = row.get_checkbox()
if not checkbutton.get_active() and self.window.is_select_mode:
self.unselect_row(row)
def activate_row(self, account_list, selected_row):
if self.window.is_select_mode and selected_row:
self.select_account(selected_row.get_checkbox())
def on_key_press(self, app, key_event):
"""
Keyboard Listener handling
"""
keyname = Gdk.keyval_name(key_event.keyval).lower()
if not self.window.is_locked():
if not self.window.no_account_box.is_visible():
if keyname == "up" or keyname == "down":
count = len(self.get_children())
dx = -1 if keyname == "up" else 1
selected_row = self.get_selected_row()
if selected_row is not None:
index = selected_row.get_index()
new_index = (index + dx) % count
self.select_row(self.get_row_at_index(new_index))
return True
return False
def toggle_select_mode(self):
is_select_mode = self.window.is_select_mode
if is_select_mode:
self.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
else:
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
if len(self.get_children()) != 0:
self.select_row(self.get_row_at_index(0))
for row in self.get_children():
checkbox = row.get_checkbox()
code_label = row.get_code_label()
visible = checkbox.get_visible()
style_context = code_label.get_style_context()
if is_select_mode:
self.select_account(checkbox)
style_context.add_class("application-secret-code-select-mode")
else:
style_context.remove_class("application-secret-code-select-mode")
row.toggle_action_box(visible)
checkbox.set_visible(not visible)
checkbox.set_no_show_all(visible)
def remove_selected(self, *args):
"""
Remove selected accounts
"""
for row in self.get_selected_rows():
checkbox = row.get_checkbox()
if checkbox.get_active():
row.remove()
self.unselect_all()
self.window.toggle_select()
def select_account(self, checkbutton):
"""
Select an account
:param checkbutton:
"""
is_active = checkbutton.get_active()
is_visible = checkbutton.get_visible()
listbox_row = checkbutton.get_parent().get_parent().get_parent()
if is_active:
self.select_row(listbox_row)
else:
self.unselect_row(listbox_row)
selected_count = len(self.get_selected_rows())
self.window.hb.remove_button.set_sensitive(selected_count > 0)
def get_selected_row_id(self):
selected_row = self.get_selected_row()
if selected_row:
return selected_row.account.get_id()
else:
return None
def get_scrolled_win(self):
return self.scrolled_win
def toggle(self, visible):
self.get_scrolled_win().set_visible(visible)
self.get_scrolled_win().set_no_show_all(not visible)
self.set_visible(visible)
self.set_no_show_all(not visible)
def is_visible(self):
return self.get_visible()
def hide(self):
self.toggle(False)
def show(self):
self.toggle(True)
def refresh(self):
self.scrolled_win.hide()
self.scrolled_win.show_all()
def append(self, account):
self.add(AccountRowList(self, self.window, account))
def remove_by_id(self, _id):
for row in self.get_children():
if row.account.get_id() == _id:
self.remove(row)
break
self.refresh()

View file

@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, Gdk, GObject, GLib
from Authenticator.widgets.accounts_list import AccountsList
from Authenticator.widgets.accounts_grid import AccountsGrid
from Authenticator.widgets.account_row import AccountRowGrid, AccountRowList
from Authenticator.widgets.search_bar import SearchBar
from Authenticator.models.account import Account
from gettext import gettext as _
from hashlib import sha256
import logging
from Authenticator.models.observer import Observer
class AccountsWindow(Gtk.Box, Observer):
def __init__(self, application, window):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
self.app = application
self.window = window
self.scrolled_win = None
self.generate()
def generate(self):
self.generate_accounts_list()
self.generate_search_bar()
self.pack_start(self.search_bar, False, True, 0)
self.reorder_child(self.search_bar, 0)
def generate_accounts_list(self):
"""
Generate an account ListBox inside of a ScrolledWindow
"""
apps = self.app.db.fetch_apps()
count = len(apps)
self.accounts = []
for app in apps:
account = Account(app, self.app.db)
account.row_observerable.register(self)
self.accounts.append(account)
self.app.observable.register(account)
self.accounts_list = AccountsList(self.window, self.accounts)
self.accounts_grid = AccountsGrid(self.window, self.accounts)
self.pack_start(self.accounts_list.get_scrolled_win(), True, True, 0)
self.pack_start(self.accounts_grid.get_scrolled_win(), True, True, 0)
is_grid = self.app.cfg.read(
"view-mode", "preferences").lower() == "grid"
self.set_mode_view(is_grid)
def generate_search_bar(self):
"""
Generate search bar box and entry
"""
self.search_bar = SearchBar(self.window, self.window.hb.search_button,
[self.accounts_list, self.accounts_grid])
def update(self, *args, **kwargs):
removed_id = kwargs.get("removed", None)
unlocked = kwargs.pop("unlocked", None)
locked = kwargs.pop("locked", None)
counter = kwargs.pop("counter", None)
view_mode = kwargs.pop("view_mode", None)
if counter == 0 or locked:
self.hide()
elif unlocked or counter != 0:
self.show()
if removed_id:
self.accounts_list.remove_by_id(removed_id)
self.accounts_grid.remove_by_id(removed_id)
self.window.emit("changed", True)
if view_mode:
self.set_mode_view(view_mode == "grid")
def get_accounts_list(self):
return self.accounts_list
def get_accounts_grid(self):
return self.accounts_grid
def set_mode_view(self, is_grid):
if is_grid:
self.scrolled_win = self.accounts_grid.get_scrolled_win()
self.accounts_list.hide()
self.accounts_grid.show()
self.accounts_grid.refresh()
else:
self.scrolled_win = self.accounts_list.get_scrolled_win()
self.accounts_grid.hide()
self.accounts_list.show()
self.accounts_list.refresh()
self.scrolled_win.set_no_show_all(False)
self.scrolled_win.set_visible(True)
def get_search_bar(self):
return self.search_bar
def toggle(self, visible):
self.set_visible(visible)
self.set_no_show_all(not visible)
def is_visible(self):
return self.get_visible()
def hide(self):
self.toggle(False)
def show(self):
self.toggle(True)
def refresh(self, *args):
self.accounts_list.refresh()
self.accounts_grid.refresh()
def append(self, app):
"""
Add an element to the ListBox
"""
if app:
account = Account(app, self.app.db)
account.row_observerable.register(self)
self.accounts.append(account)
self.app.observable.register(account)
self.accounts_list.append(account)
self.accounts_grid.append(account)
self.window.emit("changed", True)

View file

@ -0,0 +1,225 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
import logging
from Authenticator.utils import screenshot_area
from Authenticator.widgets.applications_list import ApplicationChooserWindow
from Authenticator.widgets.inapp_notification import InAppNotification
from Authenticator.models.code import Code
from Authenticator.models.qr_reader import QRReader
from Authenticator.utils import get_icon
from gettext import gettext as _
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, Gio
class AddAccount(Gtk.Window):
def __init__(self, window):
self.parent = window
self.selected_logo = None
self.step = 1
self.account_image = Gtk.Image(xalign=0)
self.secret_code = Gtk.Entry()
self.name_entry = Gtk.Entry()
self.hb = Gtk.HeaderBar()
self.generate_window()
self.generate_components()
self.generate_header_bar()
def generate_window(self):
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(500, 400)
self.set_border_width(18)
self.set_size_request(500, 300)
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
self.set_resizable(False)
self.set_transient_for(self.parent)
self.notification = InAppNotification()
self.connect("key_press_event", self.on_key_press)
def generate_header_bar(self):
"""
Generate the header bar box
"""
self.hb.props.title = _("Add a new account")
left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
cancel_button = Gtk.Button.new_with_label(_("Cancel"))
cancel_button.connect("clicked", self.close_window)
cancel_button.get_style_context().add_class("destructive-action")
left_box.add(cancel_button)
self.apply_button = Gtk.Button.new_with_label(_("Add"))
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)
def generate_components(self):
"""
Generate window components
"""
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
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_name = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
account_name = Gtk.Label()
account_name.set_text(_("Account Name"))
hbox_name.pack_end(self.name_entry, False, True, 0)
hbox_name.pack_end(account_name, False, True, 0)
hbox_secret_code = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
secret_code_label = Gtk.Label()
secret_code_label.set_text(_("Secret Code"))
self.secret_code.connect("changed", self.validate_ascii_code)
hbox_secret_code.pack_end(self.secret_code, False, True, 0)
hbox_secret_code.pack_end(secret_code_label, False, True, 0)
account_logo = get_icon("image-missing", 48)
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_name)
vbox.add(hbox_secret_code)
labels_box.pack_start(vbox, True, False, 6)
main_box.pack_start(self.notification, False, False, 0)
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 = screenshot_area()
if 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)
else:
self.notification.update(
_("Selected area is not a valid QR code"))
self.notification.set_message_type(Gtk.MessageType.ERROR)
self.notification.show()
def on_key_press(self, key, key_event):
"""
Keyboard Listener handler
"""
key_name = Gdk.keyval_name(key_event.keyval).lower()
if key_name == "escape":
self.close_window()
return True
if key_name == "return":
if self.apply_button.get_sensitive():
self.add_account()
return True
if key_event.state & Gdk.ModifierType.CONTROL_MASK:
if key_name == 's':
self.on_qr_scan()
return True
return False
def update_logo(self, image):
"""
Update image logo
"""
self.selected_logo = image
account_icon = get_icon(image, 48)
self.account_image.clear()
self.account_image.set_from_pixbuf(account_icon)
def validate_ascii_code(self, entry):
"""
Validate if the typed secret code is a valid ascii one
"""
ascii_code = entry.get_text().strip()
is_valid = Code.is_valid(ascii_code)
self.apply_button.set_sensitive(is_valid)
if not is_valid and len(ascii_code) != 0:
entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, "dialog-error-symbolic")
else:
entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, None)
def add_account(self, *args):
"""
Add a new application to the database
"""
name = self.name_entry.get_text()
secret_code = self.secret_code.get_text()
logo = self.selected_logo if self.selected_logo else "image-missing"
try:
new_account = self.parent.app.db.add_account(name, secret_code, logo)
self.parent.accounts_box.append(new_account)
self.close_window()
except Exception as e:
logging.error("Error in addin.accounts_boxg a new account")
logging.error(str(e))
def show_window(self):
if self.step == 1:
applications_choose_window = ApplicationChooserWindow(self)
applications_choose_window.show_window()
applications_choose_window.present()
self.step = 2
else:
self.secret_code.grab_focus_without_selecting()
self.show_all()
def close_window(self, *args):
"""
Close the window
"""
self.destroy()

View file

@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk
from Authenticator.utils import get_icon
from gettext import gettext as _
import logging
class ApplicationRow(Gtk.ListBoxRow):
def __init__(self, name, image):
Gtk.ListBoxRow.__init__(self)
self.name = name
self.image = image
# Create the list row
self.create_row()
def get_name(self):
"""
Get the application label
:return: (str): application label
"""
return self.name
def get_icon_name(self):
return self.image
def create_row(self):
"""
Create ListBoxRow
"""
self.get_style_context().add_class("application-list-row")
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
# Application logo
application_logo = get_icon(self.image, 48)
application_image = Gtk.Image(xalign=0)
application_image.set_from_pixbuf(application_logo)
hbox.pack_start(application_image, False, True, 6)
# Application name
application_name = Gtk.Label(xalign=0)
application_name.get_style_context().add_class("application-name")
application_name.set_text(self.name)
hbox.pack_start(application_name, True, True, 6)
vbox.pack_start(hbox, True, True, 6)
self.add(vbox)

View file

@ -0,0 +1,245 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, GObject, Gio, Gdk, GLib
from Authenticator.widgets.search_bar import SearchBar
from Authenticator.widgets.application_row import ApplicationRow
from os import path, environ as env
from gettext import gettext as _
import yaml
from glob import glob
from threading import Thread
import logging
class ApplicationChooserWindow(Gtk.Window, Thread, GObject.GObject):
__gsignals__ = {
'db_updated': (GObject.SignalFlags.RUN_LAST, None, (bool,))
}
def __init__(self, window):
Thread.__init__(self)
GObject.GObject.__init__(self)
self.nom = "applications-db-reader"
Gtk.Window.__init__(self, type=Gtk.WindowType.TOPLEVEL, modal=True,
destroy_with_parent=True)
self.parent = window
self.db = []
self.spinner = Gtk.Spinner()
self.search_button = Gtk.ToggleButton()
self.listbox = Gtk.ListBox()
self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.generate_window()
self.generate_search_bar()
self.generate_components()
self.generate_header_bar()
self.start()
def emit(self, *args):
GLib.idle_add(GObject.GObject.emit, self, *args)
def run(self):
# Load applications list using a Thread
self.read_database()
self.add_apps()
self.emit("db_updated", True)
def generate_window(self):
"""
Generate the main window
"""
self.connect("destroy", self.close_window)
self.resize(500, 650)
self.set_size_request(500, 650)
x, y = self.parent.parent.get_position()
if x and y:
self.move(x, y)
self.set_resizable(False)
self.set_transient_for(self.parent.parent)
self.connect("key_press_event", self.on_key_press)
self.add(self.main_box)
def generate_components(self):
"""
Generate window compenents
"""
box_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
# Create a ScrolledWindow
self.scrolled_win = Gtk.ScrolledWindow()
self.scrolled_win.add_with_viewport(box_outer)
self.scrolled_win.hide()
self.main_box.pack_start(self.scrolled_win, True, True, 0)
self.listbox.get_style_context().add_class("applications-list")
self.listbox.set_adjustment()
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
box_outer.pack_start(self.listbox, True, True, 0)
self.spinner_box_outer = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL)
spinner_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.spinner.start()
self.spinner.show()
spinner_box.pack_start(self.spinner, False, False, 6)
self.spinner_box_outer.pack_start(spinner_box, True, True, 6)
self.main_box.pack_start(self.spinner_box_outer, True, True, 0)
def generate_header_bar(self):
"""
Generate header bar box
"""
self.hb = Gtk.HeaderBar()
self.hb.props.title = _("Select an application")
left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
cancel_button = Gtk.Button.new_with_label(_("Cancel"))
cancel_button.connect("clicked", self.close_window)
left_box.add(cancel_button)
search_icon = Gio.ThemedIcon(name="system-search-symbolic")
search_image = Gtk.Image.new_from_gicon(
search_icon, Gtk.IconSize.BUTTON)
self.search_button.set_tooltip_text(_("Search"))
self.search_button.set_image(search_image)
next_button = Gtk.Button.new_with_label(_("Next"))
next_button.get_style_context().add_class("suggested-action")
next_button.connect("clicked", self.select_application)
right_box.pack_start(self.search_button, False, False, 6)
right_box.pack_start(next_button, False, False, 6)
self.hb.pack_end(right_box)
self.hb.pack_start(left_box)
self.set_titlebar(self.hb)
def generate_search_bar(self):
"""
Generate the search bar
"""
self.search_bar = SearchBar(self, self.search_button, [self.listbox])
self.main_box.pack_start(self.search_bar, False, True, 0)
def is_valid_app(self, app):
"""
Check if the application supports tfa
"""
if set(["tfa", "software"]).issubset(app.keys()):
return app["tfa"] and app["software"]
else:
return False
def on_key_press(self, label, key_event):
"""
Keyboard listener handling
"""
keyname = Gdk.keyval_name(key_event.keyval).lower()
if keyname == "escape":
if not self.search_bar.is_visible():
self.close_window()
return True
if keyname == "up" or keyname == "down":
dx = -1 if keyname == "up" else 1
index = self.listbox.get_selected_row().get_index()
index = (index + dx) % len(self.db)
selected_row = self.listbox.get_row_at_index(index)
self.listbox.select_row(selected_row)
return True
if keyname == "return":
self.select_application()
return True
return False
def do_db_updated(self, *args):
"""
Hide and stop the spinner and show the scrolled window
"""
self.spinner.stop()
self.spinner_box_outer.hide()
self.scrolled_win.show()
self.listbox.hide()
if len(self.listbox.get_children()) != 0:
self.listbox.show_all()
logging.debug("UI updated")
def read_database(self):
"""
Read .yml database files provided by 2factorauth guys!
"""
db_dir = path.join(env.get("DATA_DIR"), "applications") + "/data/*.yml"
logging.debug("Database folder is {0}".format(db_dir))
db_files = glob(db_dir)
logging.debug("Reading database files started")
for db_file in db_files:
with open(db_file, 'r') as data:
try:
websites = yaml.load(data)["websites"]
for app in websites:
if self.is_valid_app(app):
self.db.append(app)
except yaml.YAMLError as error:
logging.error("Error loading yml file : %s " % str(error))
logging.debug("Reading database files finished")
def add_apps(self):
"""
Add database applications to the Gtk.ListBox
"""
self.db = sorted(self.db, key=lambda k: k['name'].lower())
logging.debug("Application list was ordered alphabetically")
for app in self.db:
img_path = app["img"]
app_name = app["name"]
self.listbox.add(ApplicationRow(app_name, img_path))
if len(self.db) != 0:
self.listbox.select_row(self.listbox.get_row_at_index(0))
def select_application(self, *args):
"""
Select a logo and return its path to the add application window
"""
selected_row = self.listbox.get_selected_row()
if selected_row:
img_path = selected_row.get_icon_name()
app_name = selected_row.get_name()
logging.debug("%s was selected" % app_name)
self.parent.update_logo(img_path)
self.parent.name_entry.set_text(app_name)
self.parent.show_window()
self.parent.present()
self.close_window()
def show_window(self):
self.show_all()
def close_window(self, *args):
"""
Close the window
"""
logging.debug("Closing ApplicationChooserWindow")
self.destroy()

View file

@ -0,0 +1,181 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk
from Authenticator.models.settings import SettingsReader
from gettext import gettext as _
from hashlib import sha256
import logging
class PasswordWindow(Gtk.Window):
def __init__(self, window):
self.parent = window
self.cfg = SettingsReader()
self.hb = Gtk.HeaderBar()
self.apply_button = Gtk.Button.new_with_label(_("Apply"))
self.new_entry = Gtk.Entry()
self.new2_entry = Gtk.Entry()
self.old_entry = Gtk.Entry()
self.generate_window()
self.generate_components()
self.generate_header_bar()
def generate_window(self):
Gtk.Window.__init__(self, type=Gtk.WindowType.TOPLEVEL, title=_("Change password"),
modal=True, destroy_with_parent=True)
self.connect("delete-event", self.close_window)
self.resize(300, 100)
self.set_border_width(18)
self.set_size_request(300, 100)
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
self.set_resizable(False)
self.set_transient_for(self.parent)
self.connect("key_press_event", self.on_key_press)
def show_window(self):
self.show_all()
def on_key_press(self, key, key_event):
"""
Keyboard listener handler
"""
if Gdk.keyval_name(key_event.keyval) == "Escape":
self.close_window()
def generate_components(self):
"""
Generate window components
"""
main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
if len(self.cfg.read("password", "login")) != 0:
box_old = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
old_label = Gtk.Label()
old_label.set_text(_("Old password"))
self.old_entry.connect("changed", self.on_type_password)
self.old_entry.set_visibility(False)
box_old.pack_end(self.old_entry, False, True, 0)
box_old.pack_end(old_label, False, True, 0)
box.add(box_old)
box_new = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
new_label = Gtk.Label()
new_label.set_text(_("New password"))
self.new_entry.connect("changed", self.on_type_password)
self.new_entry.set_visibility(False)
box_new.pack_end(self.new_entry, False, True, 0)
box_new.pack_end(new_label, False, True, 0)
box_new2 = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
new2_label = Gtk.Label()
new2_label.set_text(_("Repeat new password"))
self.new2_entry.connect("changed", self.on_type_password)
self.new2_entry.set_visibility(False)
box_new2.pack_end(self.new2_entry, False, True, 0)
box_new2.pack_end(new2_label, False, True, 0)
box.add(box_new)
box.add(box_new2)
main_box.pack_start(box, False, True, 6)
self.add(main_box)
def update_password(self, *args):
"""
Update user password
"""
password = sha256(
self.new_entry.get_text().encode("utf-8")).hexdigest()
self.cfg.update("password", password, "login")
logging.debug("Password changed successfully")
self.close_window()
def on_type_password(self, entry):
"""
Validate the old & new password
"""
pwd = self.cfg.read("password", "login")
old_is_ok = True
if self.new_entry.get_text() != self.new2_entry.get_text():
self.new_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
"dialog-error-symbolic")
self.new2_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
"dialog-error-symbolic")
are_diff = True
elif len(self.new_entry.get_text()) == 0:
are_diff = True
elif len(self.new_entry.get_text()) == 0:
are_diff = True
else:
are_diff = False
if len(pwd) != 0:
if sha256(self.old_entry.get_text().encode('utf-8')).hexdigest() != pwd:
self.old_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
"dialog-error-symbolic")
old_is_ok = False
else:
old_is_ok = True
if old_is_ok:
self.old_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, None)
if not are_diff:
self.new_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, None)
self.new2_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, None)
self.apply_button.set_sensitive(not are_diff and old_is_ok)
def generate_header_bar(self):
"""
Generate header bar box
"""
left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
cancel_button = Gtk.Button.new_with_label(_("Cancel"))
cancel_button.connect("clicked", self.close_window)
cancel_button.get_style_context().add_class("destructive-action")
left_box.add(cancel_button)
self.apply_button.get_style_context().add_class("suggested-action")
self.apply_button.connect("clicked", self.update_password)
self.apply_button.set_sensitive(False)
right_box.add(self.apply_button)
self.hb.pack_start(left_box)
self.hb.pack_end(right_box)
self.set_titlebar(self.hb)
def close_window(self, *args):
"""
Close the window
"""
logging.debug("Closing PasswordWindow")
self.destroy()

View file

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gio, Gdk
import logging
class ConfirmationMessage(Gtk.Window):
result = None
def __init__(self, parent, message):
try:
self.dialog = Gtk.MessageDialog(
transient_for=parent,
modal=True,
destroy_with_parent=True,
text=message,
buttons=Gtk.ButtonsType.OK_CANCEL)
logging.debug("Confirmation message created successfully")
except Exception as e:
logging.error(str(e))
def show(self):
"""
Show confirmation dialog
"""
try:
self.result = self.dialog.run()
self.dialog.hide()
except AttributeError:
logging.error("Confiramation message was not created correctly")
return None
def get_confirmation(self):
"""
get confirmation
"""
return self.result == Gtk.ResponseType.OK
def destroy(self):
"""
Destroy the message
"""
self.dialog.destroy()

View file

@ -0,0 +1,233 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio
from Authenticator.utils import show_app_menu
import logging
from gettext import gettext as _
from Authenticator.models.observer import Observer
class HeaderBar(Gtk.HeaderBar, Observer):
search_button = Gtk.ToggleButton()
add_button = Gtk.Button()
view_mode_button = Gtk.Button()
settings_button = Gtk.Button()
remove_button = Gtk.Button()
cancel_button = Gtk.Button()
select_button = Gtk.Button()
lock_button = Gtk.Button()
popover = None
def __init__(self, app, window):
self.app = app
self.window = window
Gtk.HeaderBar.__init__(self)
self.generate()
def generate(self):
self.set_show_close_button(True)
right_box = self.generate_right_box()
left_box = self.generate_left_box()
if show_app_menu():
# add settings menu
self.generate_popover(right_box)
self.pack_start(left_box)
self.pack_end(right_box)
self.refresh()
def generate_left_box(self):
left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
remove_icon = Gio.ThemedIcon(name="user-trash-symbolic")
remove_image = Gtk.Image.new_from_gicon(
remove_icon, Gtk.IconSize.BUTTON)
self.remove_button.set_tooltip_text(_("Remove selected accounts"))
self.remove_button.set_image(remove_image)
self.remove_button.set_sensitive(False)
add_icon = Gio.ThemedIcon(name="list-add-symbolic")
add_image = Gtk.Image.new_from_gicon(add_icon, Gtk.IconSize.BUTTON)
self.add_button.set_tooltip_text(_("Add a new account"))
self.add_button.set_image(add_image)
lock_icon = Gio.ThemedIcon(name="changes-prevent-symbolic")
lock_image = Gtk.Image.new_from_gicon(lock_icon, Gtk.IconSize.BUTTON)
self.lock_button.set_tooltip_text(_("Lock the Application"))
self.lock_button.set_image(lock_image)
left_box.add(self.remove_button)
left_box.add(self.add_button)
left_box.add(self.lock_button)
return left_box
def generate_right_box(self):
count = self.app.db.count()
is_grid = self.app.cfg.read("view-mode", "preferences").lower() == "grid"
right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
select_icon = Gio.ThemedIcon(name="object-select-symbolic")
select_image = Gtk.Image.new_from_gicon(
select_icon, Gtk.IconSize.BUTTON)
self.select_button.set_tooltip_text(_("Selection mode"))
self.select_button.set_image(select_image)
search_icon = Gio.ThemedIcon(name="system-search-symbolic")
search_image = Gtk.Image.new_from_gicon(
search_icon, Gtk.IconSize.BUTTON)
self.search_button.set_tooltip_text(_("Search"))
self.search_button.set_image(search_image)
self.search_button.set_visible(count > 0)
self.set_toggle_view_mode_icon(is_grid)
self.view_mode_button.connect("clicked", self.toggle_view_mode)
self.cancel_button.set_label(_("Cancel"))
right_box.add(self.search_button)
right_box.add(self.view_mode_button)
right_box.add(self.select_button)
right_box.add(self.cancel_button)
return right_box
def generate_popover(self, box):
settings_icon = Gio.ThemedIcon(name="open-menu-symbolic")
settings_image = Gtk.Image.new_from_gicon(
settings_icon, Gtk.IconSize.BUTTON)
self.settings_button.set_tooltip_text(_("Settings"))
self.settings_button.set_image(settings_image)
self.settings_button.connect("clicked", self.toggle_popover)
self.popover = Gtk.Popover.new_from_model(
self.settings_button, self.app.menu)
self.popover.props.width_request = 200
box.add(self.settings_button)
def toggle_popover(self, *args):
if self.popover:
if self.popover.get_visible():
self.popover.hide()
else:
self.popover.show_all()
def update(self, *args, **kwargs):
locked = kwargs.pop("locked", None)
unlocked = kwargs.pop("unlocked", None)
counter = kwargs.pop("counter", -1)
if locked:
self.view_mode_button.set_visible(False)
self.select_button.set_visible(False)
self.search_button.set_visible(False)
self.add_button.set_visible(False)
self.lock_button.set_visible(False)
self.lock_button.set_no_show_all(True)
self.view_mode_button.set_no_show_all(True)
self.select_button.set_no_show_all(True)
self.search_button.set_no_show_all(True)
self.add_button.set_no_show_all(True)
elif unlocked or counter >= 0:
self.view_mode_button.set_visible(counter > 0)
self.select_button.set_visible(counter > 0)
self.search_button.set_visible(counter > 0)
self.add_button.set_visible(True)
self.view_mode_button.set_no_show_all(not counter > 0)
self.select_button.set_no_show_all(not counter > 0)
self.search_button.set_no_show_all(not counter > 0)
self.add_button.set_no_show_all(False)
if self.app.cfg.read("state", "login"):
self.lock_button.set_visible(True)
self.lock_button.set_no_show_all(False)
else:
self.lock_button.set_visible(False)
self.lock_button.set_no_show_all(True)
def toggle_view_mode(self, *args):
is_grid = self.app.cfg.read("view-mode", "preferences").lower() == "grid"
if not is_grid:
view_mode = "grid"
self.app.cfg.update("view-mode", "grid" , "preferences")
else:
view_mode = "list"
self.app.cfg.update("view-mode", "list" , "preferences")
self.set_toggle_view_mode_icon(not is_grid)
self.window.emit("view_mode_changed", view_mode)
def set_toggle_view_mode_icon(self, is_grid):
if not is_grid:
view_mode_icon = Gio.ThemedIcon(name="view-grid-symbolic")
self.view_mode_button.set_tooltip_text(_("Grid mode"))
else:
view_mode_icon = Gio.ThemedIcon(name="view-list-symbolic")
self.view_mode_button.set_tooltip_text(_("List mode"))
view_mode_image = Gtk.Image.new_from_gicon(view_mode_icon, Gtk.IconSize.BUTTON)
self.view_mode_button.set_image(view_mode_image)
def toggle_select_mode(self):
is_select_mode = self.window.is_select_mode
pass_enabled = self.app.cfg.read("state", "login")
self.remove_button.set_visible(is_select_mode)
self.cancel_button.set_visible(is_select_mode)
self.set_show_close_button(not is_select_mode)
self.settings_button.set_visible(not is_select_mode)
self.lock_button.set_visible(not is_select_mode and pass_enabled)
self.add_button.set_visible(not is_select_mode)
self.select_button.set_visible(not is_select_mode)
if is_select_mode:
self.get_style_context().add_class("selection-mode")
else:
self.get_style_context().remove_class("selection-mode")
def toggle_search(self):
self.search_button.set_active(not self.search_button.get_active())
def toggle_settings_button(self, visible):
if show_app_menu():
self.settings_button.set_visible(visible)
self.settings_button.set_no_show_all(not visible)
def refresh(self):
is_locked = self.app.locked
pass_enabled = self.app.cfg.read("state", "login")
can_be_locked = not is_locked and pass_enabled
count = self.app.db.count()
self.view_mode_button.set_visible(not count == 0 and not is_locked)
self.select_button.set_visible(not count == 0 and not is_locked)
self.search_button.set_visible(not count == 0 and not is_locked)
self.view_mode_button.set_no_show_all(not (not count == 0 and not is_locked))
self.select_button.set_no_show_all(not(not count == 0 and not is_locked))
self.search_button.set_no_show_all(not(not count == 0 and not is_locked))
self.lock_button.set_visible(can_be_locked)
self.add_button.set_visible(not is_locked)
self.lock_button.set_no_show_all(not can_be_locked)
self.add_button.set_no_show_all(is_locked)
self.toggle_settings_button(True)
self.cancel_button.set_visible(False)
self.remove_button.set_visible(False)
self.cancel_button.set_no_show_all(True)
self.remove_button.set_no_show_all(True)

View file

@ -0,0 +1,82 @@
from gettext import gettext as _
import logging
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, GLib
class InAppNotification(Gtk.Revealer):
timer = 0
def __init__(self, message="", undo_action=None, timeout=5):
Gtk.Revealer.__init__(self)
self.timeout = timeout
self.set_transition_type(Gtk.RevealerTransitionType.SLIDE_DOWN)
self.message = message
self.undo_action = undo_action
self.generate_components()
GLib.timeout_add_seconds(1, self.update_timer)
def generate_components(self):
self.get_style_context().add_class("top")
self.infobar = Gtk.InfoBar()
self.infobar.set_show_close_button(True)
self.infobar.connect("response", self.response)
self.infobar.set_default_response(Gtk.ResponseType.CLOSE)
self.infobar.set_message_type(Gtk.MessageType.INFO)
content_area = self.infobar.get_content_area()
action_area = self.infobar.get_action_area()
self.undo_button = None
if self.undo_action:
self.undo_button = self.infobar.add_button(
_("Undo"), Gtk.ResponseType.CANCEL)
self.message_label = Gtk.Label()
self.message_label.set_text(self.message)
content_area.add(self.message_label)
self.add(self.infobar)
def set_message_type(self, message_type):
self.infobar.set_message_type(message_type)
def on_hide_notification(self, *args):
self.hide()
def show(self):
self.set_reveal_child(True)
def hide(self):
self.set_reveal_child(False)
def update(self, message, undo_action=None):
self.message_label.set_text(message)
self.timer = 0
if undo_action:
if not self.undo_button:
self.undo_button = self.infobar.add_button(
_("Undo"), Gtk.ResponseType.CANCEL)
else:
self.undo_button.set_visible(True)
self.undo_button.set_no_show_all(False)
self.undo_action = undo_action
else:
if self.undo_button:
self.undo_button.set_visible(False)
self.undo_button.set_no_show_all(True)
def response(self, infobar, response_id):
if response_id == Gtk.ResponseType.CLOSE:
self.hide()
elif response_id == Gtk.ResponseType.CANCEL:
self.undo_action()
def update_timer(self):
if self.get_reveal_child():
if self.timer == self.timeout:
self.hide()
self.timer = 0
else:
self.timer += 1
return True

View file

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk
import logging
from hashlib import sha256
from gettext import gettext as _
from Authenticator.models.observer import Observer
class LoginWindow(Gtk.Box, Observer):
password_entry = None
unlock_button = None
def __init__(self, application, window):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
self.app = application
self.window = window
self.password_entry = Gtk.Entry()
self.unlock_button = Gtk.Button()
self.generate()
self.window.connect("key-press-event", self.__on_key_press)
def generate(self):
password_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.password_entry.set_visibility(False)
self.password_entry.set_placeholder_text(_("Enter your password"))
password_box.pack_start(self.password_entry, False, False, 6)
self.unlock_button.set_label(_("Unlock"))
self.unlock_button.connect("clicked", self.on_unlock)
password_box.pack_start(self.unlock_button, False, False, 6)
self.pack_start(password_box, True, False, 6)
def on_unlock(self, *args):
"""
Password check and unlock
"""
typed_pass = self.password_entry.get_text()
ecrypted_pass = sha256(typed_pass.encode("utf-8")).hexdigest()
login_pass = self.app.cfg.read("password", "login")
if ecrypted_pass == login_pass or login_pass == typed_pass == "":
self.password_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, None)
self.toggle_lock()
self.password_entry.set_text("")
else:
self.password_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, "dialog-error-symbolic")
def __on_key_press(self, widget, event):
keyname = Gdk.keyval_name(event.keyval).lower()
if self.window.is_locked():
if keyname == "return":
self.on_unlock()
return True
else:
pass_enabled = self.app.cfg.read("state", "login")
if keyname == "l" and pass_enabled:
if event.state & Gdk.ModifierType.CONTROL_MASK:
self.toggle_lock()
return True
return False
def toggle_lock(self, *args):
"""
Lock/unlock the application
"""
pass_enabled = self.app.cfg.read("state", "login")
if pass_enabled:
self.app.locked = not self.app.locked
self.window.counter = 0
if self.app.locked:
self.focus()
self.window.emit("locked", True)
else:
self.window.emit("unlocked", True)
def update(self, *args, **kwargs):
is_locked = kwargs.pop("locked", None)
is_unlocked = kwargs.pop("unlocked", None)
if is_locked:
self.show()
elif is_unlocked:
self.hide()
def toggle(self, visible):
self.set_visible(visible)
self.set_no_show_all(not visible)
def hide(self):
self.toggle(False)
def show(self):
self.toggle(True)
def focus(self):
self.password_entry.grab_focus_without_selecting()

View file

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk
import logging
from gettext import gettext as _
from Authenticator.models.observer import Observer
class NoAccountWindow(Gtk.Box, Observer):
def __init__(self):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL,
spacing=6)
self.generate()
def generate(self):
logo_image = Gtk.Image()
logo_image.set_from_icon_name("dialog-information-symbolic",
Gtk.IconSize.DIALOG)
no_apps_label = Gtk.Label()
no_apps_label.set_text(_("There's no account at the moment"))
self.pack_start(logo_image, False, False, 6)
self.pack_start(no_apps_label, False, False, 6)
def update(self, *args, **kwargs):
unlocked = kwargs.pop("unlocked", None)
locked = kwargs.pop("locked", None)
counter = kwargs.pop("counter", None)
if counter != 0 or locked:
self.hide()
elif unlocked or counter == 0:
self.show()
def toggle(self, visible):
self.set_visible(visible)
self.set_no_show_all(not visible)
def is_visible(self):
return self.get_visible()
def hide(self):
self.toggle(False)
def show(self):
self.toggle(True)

View file

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, Gdk
import logging
class SearchBar(Gtk.Revealer):
def __init__(self, window, search_button, *args):
self.search_entry = Gtk.SearchEntry()
self.search_list = args[0]
self.search_button = search_button
self.window = window
self.generate()
self.search_button.connect("toggled", self.toggle)
self.window.connect("key-press-event", self.__on_key_press)
def generate(self):
Gtk.Revealer.__init__(self)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.search_entry.set_width_chars(28)
self.search_entry.connect("search-changed", self.filter_applications)
box.pack_start(self.search_entry, True, False, 12)
box.props.margin = 6
self.add(box)
self.set_reveal_child(False)
def toggle(self, *args):
if self.is_visible():
self.search_entry.set_text("")
self.set_reveal_child(False)
for search_list in self.search_list:
search_list.set_filter_func(lambda x, y, z: True, None, False)
else:
self.set_reveal_child(True)
self.focus()
def filter_func(self, row, data, notify_destroy):
"""
Filter function, used to check if the entered data exists on the application ListBox
"""
if isinstance(row, Gtk.FlowBoxChild):
row = row.get_children()[0]
app_label = row.get_name()
data = data.lower()
if len(data) > 0:
return data in app_label.lower()
else:
return True
def __on_key_press(self, widget, event):
keyname = Gdk.keyval_name(event.keyval).lower()
if keyname == 'escape' and self.search_button.get_active():
if self.search_entry.is_focus():
self.search_button.set_active(False)
self.search_entry.set_text("")
else:
self.focus()
if not "is_locked" in dir(self.window) or not self.window.is_locked():
if keyname == "backspace":
if self.is_empty() and self.is_visible():
self.search_button.set_active(False)
return True
if event.state & Gdk.ModifierType.CONTROL_MASK:
if keyname == 'f':
self.search_button.set_active(
not self.search_button.get_active())
return True
return False
def focus(self):
self.search_entry.grab_focus_without_selecting()
def is_visible(self):
return self.get_reveal_child()
def is_empty(self):
return len(self.search_entry.get_text()) == 0
def filter_applications(self, entry):
data = entry.get_text().strip()
for search_list in self.search_list:
search_list.set_filter_func(self.filter_func, data, False)

View file

@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk
from Authenticator.models.settings import SettingsReader
from Authenticator.widgets.change_password import PasswordWindow
from gettext import gettext as _
import logging
class SettingsWindow(Gtk.Window):
def __init__(self, parent):
self.parent = parent
self.cfg = SettingsReader()
self.auto_lock_time = Gtk.SpinButton()
self.enable_switch = Gtk.CheckButton()
self.auto_lock_switch = Gtk.CheckButton()
self.password_button = Gtk.Button()
self.hb = Gtk.HeaderBar()
self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.generate_window()
self.generate_components()
def generate_window(self):
Gtk.Window.__init__(self, title=_("Settings"), type=Gtk.WindowType.TOPLEVEL,
destroy_with_parent=True, modal=True)
self.connect("delete-event", self.close_window)
self.resize(400, 300)
self.set_size_request(400, 300)
self.set_border_width(18)
self.set_resizable(False)
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
self.set_transient_for(self.parent)
self.connect("key_press_event", self.on_key_press)
self.hb.set_show_close_button(True)
self.set_titlebar(self.hb)
self.add(self.main_box)
def show_window(self):
self.show_all()
def on_key_press(self, key, key_event):
"""
Keyboard Listener handler
"""
if Gdk.keyval_name(key_event.keyval) == "Escape":
self.close_window()
def generate_components(self):
"""
Generate all the components
"""
self.stack = Gtk.Stack()
self.stack.set_vexpand(True)
self.stack.set_hexpand(True)
self.stack.set_transition_type(
Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
self.stack.set_transition_duration(1000)
stack_switcher = Gtk.StackSwitcher()
stack_switcher.set_stack(self.stack)
self.hb.set_custom_title(stack_switcher)
behavior_settings = self.generate_behavior_settings()
account_settings = self.generate_account_settings()
self.stack.add_titled(behavior_settings, "behavior", _("Behavior"))
self.stack.add_titled(account_settings, "account", _("Account"))
self.main_box.add(self.stack)
def generate_account_settings(self):
"""
Create a box with login settings components
:return (Gtk.Box): Box contains all the components
"""
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
password_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
lock_enabled = bool(self.cfg.read("state", "login"))
self.enable_switch.set_active(lock_enabled)
self.enable_switch.connect("toggled", self.on_switch_activated)
password_label = Gtk.Label()
password_label.set_label(_("Password protection"))
self.password_button.get_style_context().add_class("flat")
self.password_button.get_style_context().add_class("text-button")
self.password_button.set_label("******")
self.password_button.connect("clicked", self.new_password_window)
self.password_button.set_sensitive(lock_enabled)
password_box.pack_start(self.enable_switch, False, True, 6)
password_box.pack_start(password_label, False, True, 6)
password_box.pack_start(self.password_button, False, True, 6)
main_box.pack_start(password_box, False, True, 6)
return main_box
def generate_behavior_settings(self):
"""
Create a box with user settings components
:return (Gtk.Box): Box contains all the components
"""
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
is_auto_lock_active = bool(self.cfg.read("auto-lock", "preferences"))
auto_lock_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
auto_lock_label = Gtk.Label().new(_("Auto-lock the application (m):"))
self.auto_lock_switch.set_sensitive(self.cfg.read("state", "login"))
self.auto_lock_switch.set_active(is_auto_lock_active)
self.auto_lock_switch.connect("toggled", self.on_auto_lock_activated)
default_value = self.cfg.read("auto-lock-time", "preferences")
if default_value < 1 or default_value > 10:
default_value = 3
adjustment = Gtk.Adjustment(value=default_value, lower=1, upper=10,
step_increment=1, page_increment=1, page_size=0)
self.auto_lock_time.connect(
"value-changed", self.on_auto_lock_time_changed)
self.auto_lock_time.set_adjustment(adjustment)
self.auto_lock_time.set_sensitive(is_auto_lock_active)
self.auto_lock_time.set_value(default_value)
auto_lock_box.pack_start(self.auto_lock_switch, False, True, 6)
auto_lock_box.pack_start(auto_lock_label, False, True, 6)
auto_lock_box.pack_start(self.auto_lock_time, False, True, 12)
main_box.pack_start(auto_lock_box, False, True, 6)
return main_box
def new_password_window(self, *args):
"""
Show a new password window
"""
pass_window = PasswordWindow(self)
pass_window.show_window()
def on_auto_lock_time_changed(self, spin_button):
"""
Update auto lock time
"""
self.cfg.update("auto-lock-time",
spin_button.get_value_as_int(), "preferences")
logging.info("Auto lock time updated")
def on_switch_activated(self, switch, *args):
"""
Update password state : enabled/disabled
"""
self.password_button.set_sensitive(switch.get_active())
self.cfg.update("state", switch.get_active(), "login")
if switch.get_active():
password = self.cfg.read("password", "login")
if len(password) == 0:
self.new_password_window()
self.auto_lock_switch.set_sensitive(switch.get_active())
logging.info("Password enabled/disabled")
self.parent.refresh_window()
def on_auto_lock_activated(self, switch, *args):
"""
Update auto-lock state : enabled/disabled
"""
self.auto_lock_time.set_sensitive(switch.get_active())
self.cfg.update("auto-lock", switch.get_active(), "preferences")
logging.info("Auto lock state updated")
def close_window(self, *args):
"""
Close the window
"""
logging.debug("SettingsWindow closed")
self.destroy()

View file

@ -0,0 +1,222 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, Gdk, GObject, GLib
from Authenticator.widgets.add_account import AddAccount
from Authenticator.widgets.accounts_window import AccountsWindow
from Authenticator.widgets.login_window import LoginWindow
from Authenticator.widgets.no_account_window import NoAccountWindow
from Authenticator.widgets.headerbar import HeaderBar
from Authenticator.widgets.inapp_notification import InAppNotification
from Authenticator.interfaces.observable import Observable
from hashlib import sha256
from gettext import gettext as _
import logging
class Window(Gtk.ApplicationWindow, GObject.GObject):
__gsignals__ = {
'changed': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
'locked': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
'unlocked': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
'view_mode_changed': (GObject.SignalFlags.RUN_LAST, None, (str,))
}
counter = 0
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
is_select_mode = False
def __init__(self, application):
self.app = application
self.observable = Observable()
self.generate_window()
self.generate_header_bar()
self.generate_accounts_box()
self.generate_no_accounts_box()
self.generate_login_box()
if self.app.locked:
self.emit("locked", True)
else:
self.emit("unlocked", True)
GLib.timeout_add_seconds(60, self.refresh_counter)
def generate_window(self, *args):
"""
Generate application window (Gtk.Window)
"""
Gtk.ApplicationWindow.__init__(self, type=Gtk.WindowType.TOPLEVEL,
application=self.app)
self.move_latest_position()
self.set_wmclass("org.gnome.Authenticator", "Gnome Authenticator")
self.set_icon_name("org.gnome.Authenticator")
self.use_latest_size()
self.set_resizable(True)
self.connect("key_press_event", self.on_key_press)
self.connect("delete-event", lambda x, y: self.app.on_quit())
self.notification = InAppNotification()
self.main_box.pack_start(self.notification, False, False, 0)
self.add(self.main_box)
def on_key_press(self, app, key_event):
"""
Keyboard Listener handling
"""
keyname = Gdk.keyval_name(key_event.keyval).lower()
if not self.is_locked():
if not self.no_account_box.is_visible():
if keyname == "s" or keyname == "escape":
if key_event.state == Gdk.ModifierType.CONTROL_MASK or not self.hb.select_button.get_visible():
self.toggle_select()
return True
if keyname == "n":
if key_event.state == Gdk.ModifierType.CONTROL_MASK:
self.add_account()
return True
if keyname == "m":
if key_event.state == Gdk.ModifierType.CONTROL_MASK:
self.hb.toggle_view_mode()
return True
return False
def refresh_counter(self):
"""
Add a value to the counter each 60 seconds
"""
if not self.app.locked:
self.counter += 1
if self.app.cfg.read("auto-lock", "preferences"):
if self.counter == self.app.cfg.read("auto-lock-time", "preferences") - 1:
self.counter = 0
self.emit("locked", True)
return True
def generate_login_box(self):
"""
Generate login form
"""
self.login_box = LoginWindow(self.app, self)
self.observable.register(self.login_box)
self.hb.lock_button.connect("clicked", lambda x : self.emit("locked", True))
self.main_box.pack_start(self.login_box, True, False, 0)
def generate_accounts_box(self):
self.accounts_box = AccountsWindow(self.app, self)
self.observable.register(self.accounts_box)
self.accounts_list = self.accounts_box.get_accounts_list()
self.accounts_grid = self.accounts_box.get_accounts_grid()
self.hb.remove_button.connect(
"clicked", self.accounts_list.remove_selected)
self.search_bar = self.accounts_box.get_search_bar()
self.main_box.pack_start(self.accounts_box, True, True, 0)
def generate_header_bar(self):
"""
Generate a header bar box
"""
self.hb = HeaderBar(self.app, self)
self.observable.register(self.hb)
# connect signals
self.hb.cancel_button.connect("clicked", self.toggle_select)
self.hb.select_button.connect("clicked", self.toggle_select)
self.hb.add_button.connect("clicked", self.add_account)
self.set_titlebar(self.hb)
def add_account(self, *args):
"""
Create add application window
"""
add_account = AddAccount(self)
add_account.show_window()
def toggle_view_mode(self, is_grid):
self.accounts_box.set_mode_view(is_grid)
def toggle_select(self, *args):
"""
Toggle select mode
"""
self.is_select_mode = not self.is_select_mode
self.hb.toggle_select_mode()
self.accounts_list.toggle_select_mode()
self.accounts_grid.toggle_select_mode()
def generate_no_accounts_box(self):
"""
Generate a box with no accounts message
"""
self.no_account_box = NoAccountWindow()
self.observable.register(self.no_account_box)
self.main_box.pack_start(self.no_account_box, True, False, 0)
def do_view_mode_changed(self, *args):
self.observable.update_observers(view_mode=args[0])
def do_changed(self, *args):
counter = self.app.db.count()
self.observable.update_observers(counter=counter)
self.main_box.show_all()
def do_locked(self, *args):
self.app.locked = True
self.app.refresh_menu()
self.observable.update_observers(locked=True)
def do_unlocked(self, *args):
self.app.locked = False
self.app.refresh_menu()
counter = self.app.db.count()
self.observable.update_observers(unlocked=True, counter=counter)
self.main_box.show_all()
def is_locked(self):
return self.app.locked
def save_window_state(self):
"""
Save window position
"""
pos_x, pos_y = self.get_position()
size_x, size_y = self.get_size()
self.app.cfg.update("position-x", pos_x, "preferences")
self.app.cfg.update("position-y", pos_y, "preferences")
self.app.cfg.update("size-x", size_x, "preferences")
self.app.cfg.update("size-y", size_y, "preferences")
def move_latest_position(self):
"""
move the application window to the latest position
"""
x = self.app.cfg.read("position-x", "preferences")
y = self.app.cfg.read("position-y", "preferences")
if x != 0 and y != 0:
self.move(x, y)
else:
self.set_position(Gtk.WindowPosition.CENTER)
def use_latest_size(self):
x = self.app.cfg.read("size-x", "preferences")
y = self.app.cfg.read("size-y", "preferences")
self.resize(x, y)
self.props.width_request = 500

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 79 KiB

View file

@ -0,0 +1,39 @@
.applications-list {
background-color: @theme_bg_color;
}
.application-list-row:selected GtkImage {
color: @theme_fg_selected_color;
}
.application-name,
.application-name-grid {
font-weight: bold;
font-size: 14px;
}
.application-logo-add {
margin-right: 5px;
margin-bottom: 10px;
}
.account-secret-code,
.account-timer {
font-size: 14px;
margin-top: 0;
margin-bottom: 5px;
margin-left: 60px;
}
.account-secret-code-grid,
.account-timer-grid {
font-size: 14px;
margin-top: 10px;
margin-bottom: 5px;
}
.application-secret-code-select-mode {
margin-left: 98px;
}
.choose-popover GtkLabel {
padding: 10px;
}

View file

@ -0,0 +1,39 @@
.applications-list {
background-color: @theme_bg_color;
}
.application-list-row:selected GtkImage {
color: @theme_fg_selected_color;
}
.application-name,
.application-name-grid {
font-weight: bold;
font-size: 14px;
}
.application-logo-add {
margin-right: 5px;
margin-bottom: 10px;
}
.account-secret-code,
.account-timer {
font-size: 14px;
margin-top: 0;
margin-bottom: 5px;
margin-left: 60px;
}
.account-secret-code-grid,
.account-timer-grid {
font-size: 14px;
margin-top: 10px;
margin-bottom: 5px;
}
.application-secret-code-select-mode {
margin-left: 98px;
}
.choose-popover GtkLabel {
padding: 10px;
}

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<application>
<id type="desktop">org.gnome.Authenticator.desktop</id>
<licence>GPL3</licence>
<_name>Gnome Authenticator</_name>
<_summary>Two Factor Authentication code generator</_summary>
<description>
<_p>Simple application that generates 2 factor authentication code, created for Gnome</_p>
</description>
<screenshots>
<screenshot type="default" width="350" height="500">https://raw.githubusercontent.com/bil-elmoussaoui/Gnome-Authenticator/master/screenshots/screenshot1.png</screenshot>
<screenshot width="350" height="500">https://raw.githubusercontent.com/bil-elmoussaoui/Gnome-Authenticator/master/screenshots/screenshot2.png</screenshot>
<screenshot width="350" height="500">https://raw.githubusercontent.com/bil-elmoussaoui/Gnome-Authenticator/master/screenshots/screenshot7.png</screenshot>
</screenshots>
<url type="homepage">https://github.com/bil-elmoussaoui/Gnome-Authenticator</url>
<updatecontact>bil.elmoussaoui@gmail.com</updatecontact>
</application>

View file

@ -0,0 +1,19 @@
[Desktop Entry]
_Name=Gnome Authenticator
_GenericName=Two Factor Authentication
_Comment=Two-Factor Authentication code generator
Type=Application
Exec=gnome-authenticator
Terminal=false
Categories=GNOME;GTK;
Keywords=Gnome;GTK;Verification;
Icon=gnome-twofactorauth
StartupNotify=true
X-GNOME-Gettext-Domain=Authenticator
StartupWMClass=org.gnome.Authenticator
Actions=AboutDialog;
[Desktop Action AboutDialog]
Exec=gnome-authenticator --about
_Name=About Gnome Authenticator

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/Authenticator">
<file>org.gnome.Authenticator-post3.20.css</file>
<file>org.gnome.Authenticator-pre3.20.css</file>
<file preprocess="xml-stripblanks">about.ui</file>
<file preprocess="xml-stripblanks">shortcuts.ui</file>
</gresource>
</gresources>

View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema path="/org/gnome/Authenticator/preferences/" id="org.gnome.Authenticator.preferences" gettext-domain="Authenticator">
<key name="refresh-time" type="i">
<default>30</default>
<summary>Time to generate an other password</summary>
<description>
Time on seconds to generate a new password
</description>
</key>
<key name="auto-lock" type="b">
<default>false</default>
<summary>Auto lock the application</summary>
<description>
Auto lock the application after a (auto-lock-time)s
</description>
</key>
<key name="auto-lock-time" type="i">
<default>3</default>
<summary>Auto lock the application after x seconds</summary>
<description>
Auto lock the application after a x seconds
</description>
</key>
<key name="position-x" type="i">
<default>0</default>
<summary>Default window postiton on x axes</summary>
<description>
Default window postiton on x axes
</description>
</key>
<key name="position-y" type="i">
<default>0</default>
<summary>Default window postiton on y axes</summary>
<description>
Default window postiton on y axes
</description>
</key>
<key name="size-x" type="i">
<default>500</default>
<summary>Default window postiton on x axes</summary>
<description>
Default window postiton on x axes
</description>
</key>
<key name="size-y" type="i">
<default>650</default>
<summary>Default window postiton on y axes</summary>
<description>
Default window postiton on y axes
</description>
</key>
<key name="view-mode" type="s">
<default>"list"</default>
<summary>Default view mode grid/list</summary>
<description>
The way the generated codes will be shown list/grid mode
</description>
</key>
<key name="night-mode" type="b">
<default>false</default>
<summary>Night mode</summary>
<description>
Enable/disable night mode within the application
</description>
</key>
</schema>
<schema path="/org/gnome/Authenticator/login/" id="org.gnome.Authenticator.login" gettext-domain="Authenticator">
<key name="password" type="s">
<default>""</default>
<summary>Access code to user interface</summary>
<description>
Password to secure generated codes
</description>
</key>
<key name="state" type="b">
<default>false</default>
<summary>Use password to access to user interface</summary>
<description>
Show a password form whenever the application is opened
</description>
</key>
</schema>
</schemalist>

View file

@ -0,0 +1,103 @@
{
"app-id":"org.gnome.Authenticator",
"branch":"0.1.1",
"runtime":"org.gnome.Platform",
"runtime-version":"3.22",
"sdk":"org.gnome.Sdk",
"command":"gnome-authenticator",
"copy-icon":true,
"separate-locales":false,
"no-debuginfo":true,
"rename-appdata-file":"org.gnome.Authenticator.appdata.xml",
"rename-desktop-file":"org.gnome.Authenticator.desktop",
"desktop-file-name-prefix":"",
"finish-args":[
"--socket=x11",
"--filesystem=host",
"--talk-name=org.freedesktop.Notifications",
"--talk-name=org.freedesktop.secrets",
"--talk-name=ca.desrt.dconf",
"--own-name=org.gnome.TwoFactorAuth"
],
"modules":[
{
"name":"zbar",
"config-opts":[
"--prefix=/usr",
"--without-qt",
"--without-gtk",
"--disable-video",
"CFLAGS=-DNDEBUG"
],
"sources":[
{
"type":"git",
"url":"https://github.com/ZBar/ZBar",
"branch":"master"
}
]
},
{
"name":"pyotp",
"sources":[
{
"type":"archive",
"url":"https://pypi.python.org/packages/ac/0c/bd96508e36956ae627e527a7a7fba486865a738b4682e7290cd0e7c34f52/pyotp-2.2.4.tar.gz#md5=16cb1a08d38777ca74b5e9c7803810b6",
"sha256":"92c3973ba91273e7e4a7fd4a1020ae4b050ccd2e149b554911e1b45ca458ac2d"
}
]
},
{
"name":"pyaml",
"sources":[
{
"type":"archive",
"url":"https://pypi.python.org/packages/aa/bc/68c34bd6c5a7bd6d2ecf94ba7cd2337c9f9be58d670e2edef16fa1e0d6a2/pyaml-16.12.2.tar.gz#md5=3f2f2f113b65be2e33272ffff411ecc2",
"sha256":"b865e4f53a85f4d8a092e7701f759a3237fb3ee8a928627401914aafadc00907"
}
]
},
{
"name":"Pillow",
"sources":[
{
"type":"archive",
"url":"https://pypi.python.org/packages/8d/80/eca7a2d1a3c2dafb960f32f844d570de988e609f5fd17de92e1cf6a01b0a/Pillow-4.0.0.tar.gz#md5=d5af224b0fa2c66dacc9814785fef9e7",
"sha256":"ee26d2d7e7e300f76ba7b796014c04011394d0c4a5ed9a288264a3e443abca50"
}
]
},
{
"name":"gnome-screenshot",
"sources":[
{
"type":"git",
"url":"https://git.gnome.org/browse/gnome-screenshot",
"branch":"gnome-3-22"
}
]
},
{
"name":"gnome-keyring",
"sources":[
{
"type":"git",
"url":"https://git.gnome.org/browse/gnome-keyring/",
"branch":"gnome-3-22"
}
]
},
{
"name":"Gnome Authenticator",
"buildsystem":"meson",
"builddir":true,
"sources":[
{
"type":"git",
"url":"https://github.com/bil-elmoussaoui/Gnome-Authenticator",
"branch":"master"
}
]
}
]
}

81
gnome-authenticator.in Normal file
View file

@ -0,0 +1,81 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gnome-TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk
import sys
sys.path.insert(1, '@PYTHONDIR@')
from gi.repository import Gio
from Authenticator import application
import logging
import locale
import gettext
_ = gettext.gettext
import argparse
import faulthandler
from os import path, environ as env
if __name__ == "__main__":
locale.bindtextdomain('Authenticator', '@LOCALE_DIR@')
locale.textdomain('Authenticator')
gettext.bindtextdomain('Authenticator', '@LOCALE_DIR@')
gettext.textdomain('Authenticator')
GIT_VERSION = "@GITVERSION@"
VERSION = "@VERSION@"
env["DATA_DIR"] = "@DATADIR@"
env["LOCALE_DIR"] = '@LOCALE_DIR@'
parser = argparse.ArgumentParser(prog="Gnome Authenticator")
parser.add_argument("--debug", "-d", action="store_true",
help=_("Start in debug mode"))
parser.add_argument("--version", "-v", action="store_true",
help=_("Gnome Authenticator version number"))
parser.add_argument("--about", action="store_true",
help=_("Show about dialog"))
args = parser.parse_args()
level = logging.ERROR
if args.debug:
level = logging.DEBUG
faulthandler.enable()
logging.basicConfig(level=level,
format='[%(levelname)s] - %(asctime)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
resource = Gio.resource_load(path.join('@DATADIR@',
'org.gnome.Authenticator.gresource'))
Gio.Resource._register(resource)
if args.version:
if GIT_VERSION:
print("Git current commit : " + str(GIT_VERSION))
sys.exit("Version : " + str(VERSION))
elif args.about:
about_dialog = application.Application.about_dialog()
about_dialog.run()
about_dialog.destroy()
sys.exit()
else:
try:
app = application.Application()
exit_status = app.run(None)
sys.exit(exit_status)
except KeyboardInterrupt:
exit()

View file

@ -0,0 +1,237 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-13 00:20+0100\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"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: ../TwoFactorAuth/application.py:46
msgid "TwoFactorAuth"
msgstr ""
#: ../TwoFactorAuth/application.py:82 ../TwoFactorAuth/widgets/headerbar.py:117
#: ../TwoFactorAuth/widgets/settings.py:42
msgid "Settings"
msgstr ""
#: ../TwoFactorAuth/application.py:90
msgid "Shortcuts"
msgstr ""
#: ../TwoFactorAuth/application.py:92
msgid "About"
msgstr ""
#: ../TwoFactorAuth/application.py:93
msgid "Quit"
msgstr ""
#: ../TwoFactorAuth/widgets/account_row.py:157
#, python-format
msgid "Code \"%s\" copied to clipboard"
msgstr ""
#: ../TwoFactorAuth/widgets/account_row.py:222
msgid "Remove the account"
msgstr ""
#: ../TwoFactorAuth/widgets/account_row.py:232
msgid "Copy the generated code"
msgstr ""
#: ../TwoFactorAuth/widgets/account_row.py:242
msgid "Edit the account"
msgstr ""
#: ../TwoFactorAuth/widgets/account_row.py:252
msgid "Save the new account name"
msgstr ""
#: ../TwoFactorAuth/widgets/account_row.py:260
#: ../TwoFactorAuth/widgets/account_row.py:386
#, python-format
msgid "Expires in %s seconds"
msgstr ""
#: ../TwoFactorAuth/widgets/account_row.py:266
msgid "Error during the generation of code"
msgstr ""
#: ../TwoFactorAuth/widgets/account_row.py:311
msgid "Couldn't generate the secret code"
msgstr ""
#: ../TwoFactorAuth/widgets/account_row.py:373
#, python-format
msgid "Do you really want to remove \"%s\"?"
msgstr ""
#: ../TwoFactorAuth/widgets/account_row.py:377
#, python-format
msgid "\"%s\" was removed"
msgstr ""
#: ../TwoFactorAuth/widgets/add_account.py:50
#: ../TwoFactorAuth/widgets/add_account.py:67
#: ../TwoFactorAuth/widgets/headerbar.py:71
msgid "Add a new account"
msgstr ""
#: ../TwoFactorAuth/widgets/add_account.py:72
#: ../TwoFactorAuth/widgets/applications_list.py:115
#: ../TwoFactorAuth/widgets/change_password.py:162
#: ../TwoFactorAuth/widgets/headerbar.py:105
msgid "Cancel"
msgstr ""
#: ../TwoFactorAuth/widgets/add_account.py:77
msgid "Add"
msgstr ""
#: ../TwoFactorAuth/widgets/add_account.py:85
msgid "Scan a QR code"
msgstr ""
#: ../TwoFactorAuth/widgets/add_account.py:107
msgid "Account Name"
msgstr ""
#: ../TwoFactorAuth/widgets/add_account.py:115
msgid "Secret Code"
msgstr ""
#: ../TwoFactorAuth/widgets/add_account.py:145
msgid "Selected area is not a valid QR code"
msgstr ""
#: ../TwoFactorAuth/widgets/applications_list.py:110
msgid "Select an application"
msgstr ""
#: ../TwoFactorAuth/widgets/applications_list.py:122
#: ../TwoFactorAuth/widgets/headerbar.py:101
msgid "Search"
msgstr ""
#: ../TwoFactorAuth/widgets/applications_list.py:125
msgid "Next"
msgstr ""
#: ../TwoFactorAuth/widgets/change_password.py:36
msgid "Apply"
msgstr ""
#: ../TwoFactorAuth/widgets/change_password.py:46
msgid "Change password"
msgstr ""
#: ../TwoFactorAuth/widgets/change_password.py:78
msgid "Old password"
msgstr ""
#: ../TwoFactorAuth/widgets/change_password.py:88
msgid "New password"
msgstr ""
#: ../TwoFactorAuth/widgets/change_password.py:98
msgid "Repeat new password"
msgstr ""
#: ../TwoFactorAuth/widgets/headerbar.py:64
msgid "Remove selected accounts"
msgstr ""
#: ../TwoFactorAuth/widgets/headerbar.py:78
msgid "Lock the Application"
msgstr ""
#: ../TwoFactorAuth/widgets/headerbar.py:94
msgid "Selection mode"
msgstr ""
#: ../TwoFactorAuth/widgets/inapp_notification.py:28
msgid "Undo"
msgstr ""
#: ../TwoFactorAuth/widgets/inapp_notification.py:39
msgid "Hide notification"
msgstr ""
#: ../TwoFactorAuth/widgets/login_window.py:45
msgid "Enter your password"
msgstr ""
#: ../TwoFactorAuth/widgets/login_window.py:48
msgid "Unlock"
msgstr ""
#: ../TwoFactorAuth/widgets/no_account_window.py:39
msgid "There's no account at the moment"
msgstr ""
#: ../TwoFactorAuth/widgets/settings.py:70
msgid "Behavior"
msgstr ""
#: ../TwoFactorAuth/widgets/settings.py:75
msgid "Account"
msgstr ""
#: ../TwoFactorAuth/widgets/settings.py:89
msgid "Password protection"
msgstr ""
#: ../TwoFactorAuth/widgets/settings.py:113
msgid "Auto-lock the application (m):"
msgstr ""
#: ../data/gnome-twofactorauth.appdata.xml.in.h:1
#: ../data/gnome-twofactorauth.desktop.in.h:1
msgid "Gnome TwoFactorAuth"
msgstr ""
#: ../data/gnome-twofactorauth.appdata.xml.in.h:2
msgid "Two Factor Authentication code generator"
msgstr ""
#: ../data/gnome-twofactorauth.appdata.xml.in.h:3
msgid ""
"Simple application that generates two-factor authentication code, created "
"for Gnome"
msgstr ""
#: ../data/gnome-twofactorauth.desktop.in.h:2
msgid "Two Factor Authentication"
msgstr ""
#: ../data/gnome-twofactorauth.desktop.in.h:3
msgid "Two-Factor Authentication code generator"
msgstr ""
#: ../data/gnome-twofactorauth.desktop.in.h:4
msgid "About Gnome TwoFactorAuth"
msgstr ""
#: ../gnome-twofactorauth.in:50
msgid "Start in debug mode"
msgstr ""
#: ../gnome-twofactorauth.in:52
msgid "Gnome-TwoFactorAuth version number"
msgstr ""
#: ../gnome-twofactorauth.in:54
msgid "Show about dialog"
msgstr ""