├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .gitlab-ci.yml ├── .gitmodules ├── Authenticator ├── __init__.py ├── application.py ├── models │ ├── __init__.py │ ├── account.py │ ├── accounts_manager.py │ ├── backup.py │ ├── clipboard.py │ ├── database.py │ ├── gnupg.py │ ├── keyring.py │ ├── logger.py │ ├── otp.py │ ├── qr_reader.py │ ├── screenshot.py │ └── settings.py ├── utils.py └── widgets │ ├── __init__.py │ ├── about.py.in │ ├── accounts │ ├── __init__.py │ ├── add.py │ ├── edit.py │ ├── list.py │ └── row.py │ ├── actions_bar.py │ ├── backup │ ├── __init__.py │ └── gnupg.py │ ├── headerbar.py │ ├── search_bar.py │ ├── settings.py │ ├── utils.py │ └── window.py ├── LICENSE ├── README.md ├── authenticator.py.in ├── com.github.bilelmoussaoui.Authenticator.json ├── data ├── com.github.bilelmoussaoui.Authenticator.appdata.xml.in ├── com.github.bilelmoussaoui.Authenticator.desktop.in ├── com.github.bilelmoussaoui.Authenticator.gresource.xml ├── com.github.bilelmoussaoui.Authenticator.gschema.xml ├── data.json ├── icons │ ├── hicolor │ │ ├── 16x16 │ │ │ └── apps │ │ │ │ └── com.github.bilelmoussaoui.Authenticator.png │ │ ├── 22x22 │ │ │ └── apps │ │ │ │ └── com.github.bilelmoussaoui.Authenticator.png │ │ ├── 24x24 │ │ │ └── apps │ │ │ │ └── com.github.bilelmoussaoui.Authenticator.png │ │ ├── 256x256 │ │ │ └── apps │ │ │ │ └── com.github.bilelmoussaoui.Authenticator.png │ │ ├── 32x32 │ │ │ └── apps │ │ │ │ └── com.github.bilelmoussaoui.Authenticator.png │ │ ├── 48x48 │ │ │ └── apps │ │ │ │ ├── amazon.svg │ │ │ │ ├── com.github.bilelmoussaoui.Authenticator.png │ │ │ │ ├── discord.svg │ │ │ │ ├── drive.svg │ │ │ │ ├── dropbox.svg │ │ │ │ ├── facebook.svg │ │ │ │ ├── github.svg │ │ │ │ ├── gmail.svg │ │ │ │ ├── nextcloud.svg │ │ │ │ ├── owncloud.svg │ │ │ │ ├── skype.svg │ │ │ │ ├── slack.svg │ │ │ │ ├── steam.svg │ │ │ │ ├── teamviewer.svg │ │ │ │ ├── twitch.svg │ │ │ │ ├── twitter.svg │ │ │ │ └── youtube.svg │ │ ├── scalable │ │ │ └── apps │ │ │ │ └── com.github.bilelmoussaoui.Authenticator.svg │ │ └── symbolic │ │ │ ├── actions │ │ │ └── qrscanner-symbolic.svg │ │ │ └── apps │ │ │ └── com.github.bilelmoussaoui.Authenticator-symbolic.svg │ └── meson.build ├── meson.build ├── screenshots │ └── screenshot1.png └── style.css ├── meson.build ├── meson_options.txt ├── po ├── Authenticator.pot ├── LINGUAS ├── POTFILES.in ├── ar.po ├── da.po ├── de.po ├── es.po ├── fr.po ├── hu.po ├── id.po ├── meson.build ├── nb_NO.po ├── nl.po ├── nl_BE.po ├── pl.po ├── sr.po ├── sr@latin.po └── sv.po ├── search-provider ├── authenticator-search-provider.ini ├── authenticator-search-provider.py.in ├── com.github.bilelmoussaoui.Authenticator.SearchProvider.service.in └── meson.build └── tools ├── autopep8.sh ├── build.sh ├── build └── meson_post_install.py ├── update_pot.sh ├── yaml2json.py └── zbar_configure.patch /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | indent_style = space 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | charset = utf-8 7 | 8 | [*.py] 9 | indent_size = 4 10 | 11 | [*.build] 12 | indent_size = 2 13 | [*.yml] 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Reporter info** 2 | 3 | ``` 4 | * Distribution - 5 | * Gtk+ 3.0 version - 6 | * Desktop environment - 7 | ``` 8 | 9 | **Authenticator version** 10 | 11 | 12 | **Actual issue** 13 | 14 | 15 | 16 | **Steps to reproduce (if you knew)** 17 | 18 | 19 | **Other Note (feature-request, question, etc...)** 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .flatpak-builder/ 6 | authenticator/ 7 | tags 8 | # C extensions 9 | *.so 10 | *.po~ 11 | test/ 12 | .auth/ 13 | auth/ 14 | _build/ 15 | 16 | # Distribution / packaging 17 | builddir/ 18 | .Python 19 | .idea/ 20 | env/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | m4/ 28 | autom4te.cache/ 29 | lib64/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | *.egg-info/ 34 | .installed.cfg 35 | *.egg 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *,cover 56 | .hypothesis/ 57 | 58 | # Translations 59 | *.mo 60 | 61 | # Django stuff: 62 | *.log 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | #Ipython Notebook 71 | .ipynb_checkpoints 72 | 73 | 74 | *.valid 75 | *.compiled 76 | *.desktop 77 | *.gresource 78 | *.ui~ 79 | .vscode/ 80 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - flatpak 3 | 4 | variables: 5 | BUNDLE: "authenticator-git.flatpak" 6 | 7 | 8 | flatpak: 9 | image: registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master 10 | stage: flatpak 11 | variables: 12 | MANIFEST_PATH: "com.github.bilelmoussaoui.Authenticator.json" 13 | RUNTIME_REPO: "https://sdk.gnome.org/gnome-nightly.flatpakrepo" 14 | FLATPAK_MODULE: "authenticator" 15 | script: 16 | - flatpak-builder --stop-at=${FLATPAK_MODULE} app ${MANIFEST_PATH} 17 | - flatpak build app meson --prefix=/app ${MESON_ARGS} _build 18 | - flatpak build app ninja -C _build install 19 | - flatpak-builder --finish-only --repo=repo app ${MANIFEST_PATH} 20 | - flatpak build-export repo app 21 | - flatpak build-bundle repo ${BUNDLE} --runtime-repo=${RUNTIME_REPO} com.github.bilelmoussaoui.Authenticato 22 | artifacts: 23 | paths: 24 | - ${BUNDLE} 25 | - _build/meson-logs/meson-log.txt 26 | expire_in: 30 days 27 | cache: 28 | paths: 29 | - .flatpak-builder/cache 30 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "subprojects/libgd"] 2 | path = subprojects/libgd 3 | url = https://gitlab.gnome.org/GNOME/libgd.git 4 | -------------------------------------------------------------------------------- /Authenticator/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from .application import Application 20 | from .utils import can_use_qrscanner, load_pixbuf, load_pixbuf_from_provider 21 | -------------------------------------------------------------------------------- /Authenticator/application.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distr ibuted in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from gettext import gettext as _ 20 | 21 | from gi import require_version 22 | 23 | require_version("Gtk", "3.0") 24 | from gi.repository import Gtk, GLib, Gio, Gdk 25 | from .widgets import Window, AboutDialog, import_json, export_json, import_pgp_json, export_pgp_json 26 | from .models import Settings, Clipboard, Logger 27 | 28 | 29 | class Application(Gtk.Application): 30 | """Authenticator application object.""" 31 | instance = None 32 | USE_QRSCANNER = True 33 | 34 | def __init__(self): 35 | Gtk.Application.__init__(self, 36 | application_id="com.github.bilelmoussaoui.Authenticator", 37 | flags=Gio.ApplicationFlags.FLAGS_NONE) 38 | GLib.set_application_name(_("Authenticator")) 39 | GLib.set_prgname("Authenticator") 40 | self.alive = True 41 | self._menu = Gio.Menu() 42 | 43 | @staticmethod 44 | def get_default(): 45 | if Application.instance is None: 46 | Application.instance = Application() 47 | return Application.instance 48 | 49 | def do_startup(self): 50 | """Startup the application.""" 51 | Gtk.Application.do_startup(self) 52 | # Unlock the keyring 53 | self.__generate_menu() 54 | self.__setup_actions() 55 | Application.__setup_css() 56 | 57 | # Set the default night mode 58 | is_night_mode = Settings.get_default().is_night_mode 59 | gtk_settings = Gtk.Settings.get_default() 60 | gtk_settings.set_property("gtk-application-prefer-dark-theme", 61 | is_night_mode) 62 | 63 | @staticmethod 64 | def __setup_css(): 65 | """Setup the CSS and load it.""" 66 | uri = 'resource:///com/github/bilelmoussaoui/Authenticator/style.css' 67 | provider_file = Gio.File.new_for_uri(uri) 68 | provider = Gtk.CssProvider() 69 | screen = Gdk.Screen.get_default() 70 | context = Gtk.StyleContext() 71 | provider.load_from_file(provider_file) 72 | context.add_provider_for_screen(screen, provider, 73 | Gtk.STYLE_PROVIDER_PRIORITY_USER) 74 | Logger.debug("Loading CSS") 75 | 76 | def __generate_menu(self): 77 | """Generate application menu.""" 78 | # Backup 79 | backup_content = Gio.Menu.new() 80 | import_menu = Gio.Menu.new() 81 | export_menu = Gio.Menu.new() 82 | 83 | import_menu.append_item(Gio.MenuItem.new(_("from a plain-text JSON file"), "app.import_json")) 84 | import_menu.append_item(Gio.MenuItem.new(_("from an OpenPGP-encrypted JSON file"), "app.import_pgp_json")) 85 | export_menu.append_item(Gio.MenuItem.new(_("in a plain-text JSON file"), "app.export_json")) 86 | export_menu.append_item(Gio.MenuItem.new(_("in an OpenPGP-encrypted JSON file"), "app.export_pgp_json")) 87 | 88 | backup_content.insert_submenu(0, _("Restore"), import_menu) 89 | backup_content.insert_submenu(1, _("Backup"), export_menu) 90 | 91 | backup_section = Gio.MenuItem.new_section(None, backup_content) 92 | self._menu.append_item(backup_section) 93 | 94 | # Main section 95 | main_content = Gio.Menu.new() 96 | # Night mode action 97 | main_content.append_item(Gio.MenuItem.new(_("Settings"), "app.settings")) 98 | main_content.append_item(Gio.MenuItem.new(_("About"), "app.about")) 99 | main_content.append_item(Gio.MenuItem.new(_("Quit"), "app.quit")) 100 | help_section = Gio.MenuItem.new_section(None, main_content) 101 | self._menu.append_item(help_section) 102 | 103 | def __setup_actions(self): 104 | settings = Settings.get_default() 105 | 106 | actions = { 107 | "about": self.__on_about, 108 | "quit": self.__on_quit, 109 | "settings": self.__on_settings, 110 | "import_json": self.__on_import_json, 111 | "import_pgp_json": self.__on_import_pgp_json, 112 | "export_json": self.__on_export_json, 113 | "export_pgp_json": self.__on_export_pgp_json 114 | } 115 | for key, value in actions.items(): 116 | action = Gio.SimpleAction.new(key, None) 117 | action.connect("activate", value) 118 | self.add_action(action) 119 | 120 | def do_activate(self, *_): 121 | """On activate signal override.""" 122 | resources_path = "/com/github/bilelmoussaoui/Authenticator/" 123 | Gtk.IconTheme.get_default().add_resource_path(resources_path) 124 | window = Window.get_default() 125 | window.set_application(self) 126 | window.set_menu(self._menu) 127 | window.connect("delete-event", lambda x, y: self.__on_quit()) 128 | self.add_window(window) 129 | window.show_all() 130 | window.present() 131 | 132 | @staticmethod 133 | def set_use_qrscanner(state): 134 | Application.USE_QRSCANNER = state 135 | 136 | @staticmethod 137 | def __on_about(*_): 138 | """ 139 | Shows about dialog 140 | """ 141 | dialog = AboutDialog() 142 | dialog.set_transient_for(Window.get_default()) 143 | dialog.run() 144 | dialog.destroy() 145 | 146 | @staticmethod 147 | def __on_import_json(*_): 148 | from .models import BackupJSON 149 | filename = import_json(Window.get_default()) 150 | if filename: 151 | BackupJSON.import_file(filename) 152 | Window.get_default().update_view() 153 | 154 | @staticmethod 155 | def __on_export_json(*_): 156 | from .models import BackupJSON 157 | filename = export_json(Window.get_default()) 158 | if filename: 159 | BackupJSON.export_file(filename) 160 | 161 | @staticmethod 162 | def __on_import_pgp_json(*_): 163 | from .widgets import GPGRestoreWindow 164 | filename = import_pgp_json(Window.get_default()) 165 | if filename: 166 | gpg_window = GPGRestoreWindow(filename) 167 | gpg_window.set_transient_for(Window.get_default()) 168 | gpg_window.show_all() 169 | 170 | @staticmethod 171 | def __on_export_pgp_json(*_): 172 | from .models import BackupPGPJSON 173 | filename = export_pgp_json(Window.get_default()) 174 | if filename: 175 | def export_pgp(_, fingerprint): 176 | BackupPGPJSON.export_file(filename, fingerprint) 177 | 178 | from .widgets.backup import FingprintPGPWindow 179 | fingerprint_window = FingprintPGPWindow(filename) 180 | fingerprint_window.set_transient_for(Window.get_default()) 181 | fingerprint_window.connect("selected", export_pgp) 182 | fingerprint_window.show_all() 183 | 184 | @staticmethod 185 | def __on_settings(*_): 186 | from .widgets import SettingsWindow 187 | settings_window = SettingsWindow() 188 | settings_window.present() 189 | settings_window.show_all() 190 | 191 | def __on_quit(self, *_): 192 | """ 193 | Close the application, stops all threads 194 | and clear clipboard for safety reasons 195 | """ 196 | Clipboard.clear() 197 | Window.get_default().close() 198 | self.quit() 199 | -------------------------------------------------------------------------------- /Authenticator/models/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from .account import Account 20 | from .accounts_manager import AccountsManager 21 | from .backup import BackupJSON, BackupPGPJSON 22 | from .clipboard import Clipboard 23 | from .database import Database 24 | from .keyring import Keyring 25 | from .logger import Logger 26 | from .otp import OTP 27 | from .qr_reader import QRReader 28 | from .screenshot import GNOMEScreenshot 29 | from .settings import Settings 30 | from .gnupg import GPG 31 | 32 | -------------------------------------------------------------------------------- /Authenticator/models/account.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from hashlib import sha256 20 | from gettext import gettext as _ 21 | from gi.repository import GObject 22 | 23 | from .clipboard import Clipboard 24 | from .database import Database 25 | from .keyring import Keyring 26 | from .logger import Logger 27 | from .otp import OTP 28 | 29 | 30 | class Account(GObject.GObject): 31 | __gsignals__ = { 32 | 'otp_out_of_date': (GObject.SignalFlags.RUN_LAST, None, ()), 33 | 'otp_updated': (GObject.SignalFlags.RUN_LAST, None, (str,)), 34 | 'removed': (GObject.SignalFlags.RUN_LAST, None, ()), 35 | } 36 | 37 | def __init__(self, _id, username, provider, secret_id): 38 | GObject.GObject.__init__(self) 39 | self.id = _id 40 | self.username = username 41 | self.provider = provider 42 | self.secret_id = secret_id 43 | token = Keyring.get_by_id(self.secret_id) 44 | self.connect("otp_out_of_date", self._on_otp_out_of_date) 45 | if token: 46 | self.otp = OTP(token) 47 | self._code_generated = True 48 | else: 49 | self.otp = None 50 | self._code_generated = False 51 | Logger.error("Could not read the secret code," 52 | "the keyring keys were reset manually") 53 | 54 | @staticmethod 55 | def create(username, provider, token): 56 | """ 57 | Create a new Account. 58 | :param username: the account's username 59 | :param provider: the account's provider 60 | :param token: the OTP secret token 61 | :return: Account object 62 | """ 63 | # Encrypt the token to create a secret_id 64 | secret_id = sha256(token.encode('utf-8')).hexdigest() 65 | # Save the account 66 | obj = Database.get_default().insert(username, provider, secret_id) 67 | Keyring.insert(secret_id, provider, username, token) 68 | return Account(obj['id'], username, provider, secret_id) 69 | 70 | @staticmethod 71 | def create_from_json(json_obj): 72 | provider = json_obj.get("tags", [_("Default")])[0].strip() 73 | return Account.create(json_obj["label"], provider, json_obj["secret"]) 74 | 75 | @staticmethod 76 | def get_by_id(id_): 77 | obj = Database.get_default().get_by_id(id_) 78 | return Account(obj['id'], obj['username'], obj['provider'], obj['secret_id']) 79 | 80 | def update(self, username, provider): 81 | """ 82 | Update the account name and/or provider. 83 | :param username: the account's username 84 | :param provider: the account's provider 85 | """ 86 | Database.get_default().update(username, provider, self.id) 87 | 88 | def remove(self): 89 | """ 90 | Remove the account. 91 | """ 92 | Database.get_default().remove(self.id) 93 | Keyring.remove(self.secret_id) 94 | self.emit("removed") 95 | Logger.debug("Account '{}' with id {} was removed".format(self.username, 96 | self.id)) 97 | 98 | def copy_pin(self): 99 | """Copy the OTP to the clipboard.""" 100 | Clipboard.set(self.otp.pin) 101 | 102 | def _on_otp_out_of_date(self, *_): 103 | if self._code_generated: 104 | self.otp.update() 105 | self.emit("otp_updated", self.otp.pin) 106 | 107 | def to_json(self): 108 | token = Keyring.get_by_id(self.secret_id) 109 | if token: 110 | return { 111 | "secret": token, 112 | "label": self.username, 113 | "period": 30, 114 | "digits": 6, 115 | "type": "OTP", 116 | "algorithm": "SHA1", 117 | "thumbnail": "Default", 118 | "last_used": 0, 119 | "tags": [self.provider] 120 | } 121 | return None 122 | -------------------------------------------------------------------------------- /Authenticator/models/accounts_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from threading import Thread 20 | from time import sleep 21 | 22 | from gi.repository import GObject 23 | 24 | 25 | class AccountsManager(GObject.GObject, Thread): 26 | __gsignals__ = { 27 | 'counter_updated': (GObject.SignalFlags.RUN_LAST, None, (str,)), 28 | } 29 | instance = None 30 | 31 | def __init__(self): 32 | GObject.GObject.__init__(self) 33 | Thread.__init__(self) 34 | self._accounts = [] 35 | self._alive = True 36 | self.__fill_accounts() 37 | 38 | self.counter_max = 30 39 | self.counter = self.counter_max 40 | self.start() 41 | 42 | @staticmethod 43 | def get_default(): 44 | if AccountsManager.instance is None: 45 | AccountsManager.instance = AccountsManager() 46 | return AccountsManager.instance 47 | 48 | def add(self, account): 49 | self._accounts.append(account) 50 | 51 | @property 52 | def accounts(self): 53 | return self._accounts 54 | 55 | def clear(self): 56 | self._accounts = [] 57 | 58 | def kill(self): 59 | self._alive = False 60 | 61 | def update_childes(self, signal, data=None): 62 | for child in self._accounts: 63 | if data: 64 | child.emit(signal, data) 65 | else: 66 | child.emit(signal) 67 | 68 | def run(self): 69 | while self._alive: 70 | self.counter -= 1 71 | if self.counter == 0: 72 | self.counter = self.counter_max 73 | self.update_childes("otp_out_of_date") 74 | self.emit("counter_updated", self.counter) 75 | sleep(1) 76 | 77 | def __fill_accounts(self): 78 | from .database import Database 79 | from .account import Account 80 | accounts = Database.get_default().accounts 81 | for account_obj in accounts: 82 | account = Account(account_obj["id"], account_obj["username"], account_obj["provider"], 83 | account_obj["secret_id"]) 84 | if account.otp: 85 | self.add(account) 86 | -------------------------------------------------------------------------------- /Authenticator/models/backup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | import json 20 | 21 | from .account import Account 22 | from .accounts_manager import AccountsManager 23 | from .keyring import Keyring 24 | 25 | 26 | class Backup: 27 | 28 | def __init__(self): 29 | pass 30 | 31 | @staticmethod 32 | def import_accounts(accounts): 33 | from ..widgets.accounts import AccountsWidget 34 | accounts_widget = AccountsWidget.get_default() 35 | accounts_manager = AccountsManager.get_default() 36 | for account in accounts: 37 | try: 38 | new_account = Account.create_from_json(account) 39 | accounts_widget.append(new_account) 40 | accounts_manager.add(new_account) 41 | except Exception: 42 | pass 43 | 44 | @staticmethod 45 | def export_accounts(): 46 | accounts = AccountsManager.get_default().accounts 47 | exported_accounts = [] 48 | for account in accounts: 49 | json_account = account.to_json() 50 | if json_account: 51 | exported_accounts.append(json_account) 52 | return exported_accounts 53 | 54 | 55 | class BackupJSON: 56 | 57 | def __init__(self): 58 | pass 59 | 60 | @staticmethod 61 | def export_file(filename): 62 | accounts = Backup.export_accounts() 63 | with open(filename, 'w') as outfile: 64 | json.dump(accounts, outfile, sort_keys=True, indent=4) 65 | 66 | @staticmethod 67 | def import_file(filename): 68 | with open(filename, 'r') as infile: 69 | accounts = json.load(infile) 70 | Backup.import_accounts(accounts) 71 | 72 | 73 | class BackupPGPJSON: 74 | def __init__(self): 75 | pass 76 | 77 | @staticmethod 78 | def export_file(filename, fingerprint): 79 | from .gnupg import GPG 80 | accounts = Backup.export_accounts() 81 | data = json.dumps(accounts, sort_keys=True, indent=4) 82 | encrypted_data = GPG.get_default().encrypt(data, fingerprint) 83 | with open(filename, 'w') as outfile: 84 | outfile.write(str(encrypted_data)) 85 | -------------------------------------------------------------------------------- /Authenticator/models/clipboard.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | 20 | from gi import require_version 21 | 22 | require_version("Gtk", "3.0") 23 | from gi.repository import Gdk, Gtk 24 | 25 | 26 | class Clipboard: 27 | """Clipboard handler.""" 28 | 29 | def __init__(self): 30 | pass 31 | 32 | @staticmethod 33 | def clear(): 34 | """Clear the clipboard.""" 35 | clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) 36 | clipboard.clear() 37 | 38 | @staticmethod 39 | def set(string): 40 | """ 41 | Copy a string to the clipboard. 42 | 43 | :param string: the string to copy. 44 | :type string: str 45 | """ 46 | clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) 47 | clipboard.set_text(string, -1) 48 | -------------------------------------------------------------------------------- /Authenticator/models/gnupg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You ould have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | import gnupg 20 | from .settings import Settings 21 | 22 | 23 | class GPG(gnupg.GPG): 24 | instance = None 25 | 26 | def __init__(self): 27 | gnupg.GPG.__init__(self, gnupghome=Settings.get_default().gpg_location) 28 | 29 | @staticmethod 30 | def get_default(): 31 | if GPG.instance is None: 32 | GPG.instance = GPG() 33 | return GPG.instance 34 | 35 | def get_keys(self): 36 | return { 37 | "public": self.list_keys(), 38 | "private": self.list_keys(True) 39 | } 40 | 41 | def decrypt_json(self, filename, paraphrase, out_file): 42 | with open(filename, 'rb') as infile: 43 | status = self.decrypt_file(infile, passphrase=paraphrase, output=out_file) 44 | return status 45 | 46 | def ecrypt_json(self, json_obj, fingerprint): 47 | return self.encrypt(json_obj, recipients=fingerprint) 48 | -------------------------------------------------------------------------------- /Authenticator/models/keyring.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from gi import require_version 20 | 21 | require_version('Secret', '1') 22 | from gi.repository import Secret 23 | 24 | 25 | class Keyring: 26 | ID = "com.github.bilelmoussaoui.Authenticator" 27 | instance = None 28 | 29 | def __init__(self): 30 | self.schema = Secret.Schema.new(Keyring.ID, 31 | Secret.SchemaFlags.NONE, 32 | { 33 | "id": Secret.SchemaAttributeType.STRING, 34 | "name": Secret.SchemaAttributeType.STRING, 35 | }) 36 | 37 | @staticmethod 38 | def get_default(): 39 | if Keyring.instance is None: 40 | Keyring.instance = Keyring() 41 | return Keyring.instance 42 | 43 | @staticmethod 44 | def get_by_id(secret_id): 45 | """ 46 | Return the OTP token based on a secret ID. 47 | 48 | :param secret_id: the secret ID associated to an OTP token 49 | :type secret_id: str 50 | :return: the secret OTP token. 51 | """ 52 | schema = Keyring.get_default().schema 53 | password = Secret.password_lookup_sync( 54 | schema, {"id": str(secret_id)}, None) 55 | return password 56 | 57 | @staticmethod 58 | def insert(secret_id, provider, username, token): 59 | """ 60 | Save a secret OTP token. 61 | 62 | :param secret_id: The secret ID associated to the OTP token 63 | :param provider: the provider name 64 | :param username: the username 65 | :param token: the secret OTP token. 66 | 67 | 68 | """ 69 | schema = Keyring.get_default().schema 70 | 71 | data = { 72 | "id": str(secret_id), 73 | "name": str(username), 74 | } 75 | Secret.password_store_sync( 76 | schema, 77 | data, 78 | Secret.COLLECTION_DEFAULT, 79 | "{provider} OTP ({username})".format( 80 | provider=provider, username=username), 81 | token, 82 | None 83 | ) 84 | 85 | @staticmethod 86 | def remove(secret_id): 87 | """ 88 | Remove a specific secret OTP token. 89 | 90 | :param secret_id: the secret ID associated to the OTP token 91 | :return bool: Either the token was removed successfully or not 92 | """ 93 | schema = Keyring.get_default().schema 94 | success = Secret.password_clear_sync( 95 | schema, {"id": str(secret_id)}, None) 96 | return success 97 | 98 | @staticmethod 99 | def clear(): 100 | """ 101 | Clear all existing accounts. 102 | 103 | :return bool: Either the token was removed successfully or not 104 | """ 105 | schema = Keyring.get_default().schema 106 | success = Secret.password_clear_sync(schema, {}, None) 107 | return success 108 | -------------------------------------------------------------------------------- /Authenticator/models/logger.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | import logging 20 | 21 | 22 | class Logger: 23 | """Logging handler.""" 24 | DEBUG = logging.DEBUG 25 | ERROR = logging.ERROR 26 | # Default instance of Logger 27 | instance = None 28 | # Message format 29 | FORMAT = "[%(levelname)-s] %(asctime)s %(message)s" 30 | # Date format 31 | DATE = "%Y-%m-%d %H:%M:%S" 32 | 33 | def __init__(self): 34 | pass 35 | 36 | @staticmethod 37 | def new(): 38 | """Create a new instance of Logger.""" 39 | logger = logging.getLogger('authenticator') 40 | handler = logging.StreamHandler() 41 | formatter = logging.Formatter(Logger.FORMAT, Logger.DATE) 42 | handler.setFormatter(formatter) 43 | logger.setLevel(logging.DEBUG) 44 | logger.addHandler(handler) 45 | return logger 46 | 47 | @staticmethod 48 | def get_default(): 49 | """Return the default instance of Logger.""" 50 | if Logger.instance is None: 51 | # Init the Logger 52 | Logger.instance = Logger.new() 53 | return Logger.instance 54 | 55 | @staticmethod 56 | def set_level(level): 57 | """Set the logging level.""" 58 | Logger.get_default().setLevel(level) 59 | 60 | @staticmethod 61 | def warning(msg): 62 | """Log a warning message.""" 63 | Logger.get_default().warning(msg) 64 | 65 | @staticmethod 66 | def debug(msg): 67 | """Log a debug message.""" 68 | Logger.get_default().debug(msg) 69 | 70 | @staticmethod 71 | def info(msg): 72 | """Log an info message.""" 73 | Logger.get_default().info(msg) 74 | 75 | @staticmethod 76 | def error(msg): 77 | """Log an error message.""" 78 | Logger.get_default().error(msg) 79 | -------------------------------------------------------------------------------- /Authenticator/models/otp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | import binascii 20 | 21 | from .logger import Logger 22 | 23 | try: 24 | from pyotp import TOTP 25 | except ImportError: 26 | Logger.error("Impossible to import TOTP, please install PyOTP first") 27 | 28 | 29 | class OTP(TOTP): 30 | """ 31 | OTP (One-time password) handler using PyOTP. 32 | """ 33 | 34 | def __init__(self, token): 35 | """ 36 | :param token: the OTP token. 37 | """ 38 | TOTP.__init__(self, token) 39 | self.pin = None 40 | self.update() 41 | 42 | @staticmethod 43 | def is_valid(token): 44 | """ 45 | Validate a OTP token. 46 | 47 | :param token: OTP token 48 | :type token: str 49 | 50 | :return: bool 51 | """ 52 | try: 53 | TOTP(token).now() 54 | return True 55 | except (binascii.Error, ValueError, TypeError): 56 | return False 57 | 58 | def update(self): 59 | """ 60 | Generate a new OTP based on the same token. 61 | """ 62 | try: 63 | self.pin = self.now() 64 | except binascii.Error: 65 | self.pin = None 66 | -------------------------------------------------------------------------------- /Authenticator/models/qr_reader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from os import remove, path 20 | from urllib.parse import urlparse, parse_qsl 21 | 22 | from .logger import Logger 23 | from .otp import OTP 24 | 25 | 26 | class QRReader: 27 | ZBAR_FOUND = True 28 | def __init__(self, filename): 29 | self.filename = filename 30 | self._codes = None 31 | 32 | def read(self): 33 | try: 34 | from PIL import Image 35 | from pyzbar.pyzbar import decode 36 | decoded_data = decode(Image.open(self.filename)) 37 | if path.isfile(self.filename): 38 | remove(self.filename) 39 | try: 40 | url = urlparse(decoded_data[0].data.decode()) 41 | query_params = parse_qsl(url.query) 42 | self._codes = dict(query_params) 43 | return self._codes.get("secret") 44 | except (KeyError, IndexError): 45 | Logger.error("Invalid QR image") 46 | return None 47 | except ImportError: 48 | from ..application import Application 49 | Application.USE_QRSCANNER = False 50 | QRReader.ZBAR_FOUND = False 51 | 52 | def is_valid(self): 53 | """ 54 | Validate if the QR code is a valid tfa 55 | """ 56 | if isinstance(self._codes, dict): 57 | return OTP.is_valid(self._codes.get("secret")) 58 | return False 59 | -------------------------------------------------------------------------------- /Authenticator/models/screenshot.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from os import path 20 | from tempfile import NamedTemporaryFile 21 | 22 | from gi.repository import Gio, GLib 23 | 24 | 25 | class GNOMEScreenshot: 26 | """ 27 | GNOME Screenshot interface implementation. 28 | Currently implements the only needed method by Authenticator. 29 | """ 30 | interface = "org.gnome.Shell.Screenshot" 31 | path = "/org/gnome/Shell/Screenshot" 32 | 33 | def __init__(self): 34 | pass 35 | 36 | @staticmethod 37 | def area(filename=None): 38 | """ 39 | Take a screen shot of an area and save it to a specific filename 40 | Using GNOME Shell Screenshot Interface. 41 | :param filename: output filename 42 | :type filename: str 43 | """ 44 | if not filename: 45 | filename = path.join(GLib.get_user_cache_dir(), 46 | path.basename(NamedTemporaryFile().name)) 47 | bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) 48 | screen_proxy = Gio.DBusProxy.new_sync(bus, 49 | Gio.DBusProxyFlags.NONE, 50 | None, 51 | GNOMEScreenshot.interface, 52 | GNOMEScreenshot.path, 53 | GNOMEScreenshot.interface, 54 | None) 55 | x, y, width, height = screen_proxy.call_sync('SelectArea', None, Gio.DBusCallFlags.NONE, 56 | -1, None).unpack() 57 | 58 | args = GLib.Variant('(iiiibs)', (x, y, width, height, False, filename 59 | ) 60 | ) 61 | screenshot = screen_proxy.call_sync('ScreenshotArea', args, 62 | Gio.DBusCallFlags.NONE, -1, None) 63 | success, filename = screenshot.unpack() 64 | if success: 65 | return filename 66 | return None 67 | -------------------------------------------------------------------------------- /Authenticator/models/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from gi.repository import Gio, GLib 20 | 21 | 22 | class Settings(Gio.Settings): 23 | """ 24 | Gio.Settings handler. 25 | Implements the basic dconf-settings as properties 26 | """ 27 | 28 | # Default Settings instance 29 | instance = None 30 | # Settings schema 31 | SCHEMA = "com.github.bilelmoussaoui.Authenticator" 32 | 33 | def __init__(self): 34 | Gio.Settings.__init__(self) 35 | 36 | @staticmethod 37 | def new(): 38 | """Create a new Settings object""" 39 | g_settings = Gio.Settings.new(Settings.SCHEMA) 40 | g_settings.__class__ = Settings 41 | return g_settings 42 | 43 | @staticmethod 44 | def get_default(): 45 | """Return the default instance of Settings.""" 46 | if Settings.instance is None: 47 | Settings.instance = Settings.new() 48 | return Settings.instance 49 | 50 | @property 51 | def window_position(self): 52 | """Return the window's position.""" 53 | return tuple(self.get_value('window-position')) 54 | 55 | @window_position.setter 56 | def window_position(self, position): 57 | """ 58 | Set the window position. 59 | 60 | :param position: [x, y] window's position 61 | :type position: list 62 | """ 63 | position = GLib.Variant('ai', list(position)) 64 | self.set_value('window-position', position) 65 | 66 | @property 67 | def is_night_mode(self): 68 | """Is night mode?""" 69 | return self.get_boolean('night-mode') 70 | 71 | @is_night_mode.setter 72 | def is_night_mode(self, state): 73 | """ 74 | Set the night mode. 75 | 76 | :param state: Night mode state 77 | :type state: bool 78 | """ 79 | self.set_boolean('night-mode', state) 80 | 81 | @property 82 | def window_maximized(self): 83 | """Was the window maximized?.""" 84 | return self.get_boolean("is-maximized") 85 | 86 | @window_maximized.setter 87 | def window_maximized(self, is_maximized): 88 | """ 89 | Set the window as maximized or not. 90 | 91 | :param is_maximized: the current state of the window 92 | :type is_maximized: bool 93 | """ 94 | self.set_boolean("is-maximized", is_maximized) 95 | 96 | @property 97 | def gpg_location(self): 98 | return self.get_string('gpg-location') 99 | 100 | @gpg_location.setter 101 | def gpg_location(self, new_location): 102 | self.set_string("gpg-location", new_location) 103 | -------------------------------------------------------------------------------- /Authenticator/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distr ibuted in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from os import environ 20 | 21 | from gi import require_version 22 | 23 | require_version('Gtk', '3.0') 24 | from gi.repository import Gtk, GdkPixbuf, GLib, Gio 25 | 26 | 27 | def can_use_qrscanner(): 28 | desktop = environ.get("XDG_CURRENT_DESKTOP", "").lower() 29 | return desktop == "gnome" 30 | 31 | 32 | def load_pixbuf(icon_name, size): 33 | pixbuf = None 34 | theme = Gtk.IconTheme.get_default() 35 | if icon_name: 36 | try: 37 | icon_info = theme.lookup_icon(icon_name, size, 0) 38 | if icon_info: 39 | pixbuf = icon_info.load_icon() 40 | except GLib.Error: 41 | pass 42 | if not pixbuf: 43 | pixbuf = theme.load_icon("com.github.bilelmoussaoui.Authenticator", 44 | size, 0) 45 | 46 | if pixbuf and (pixbuf.props.width != size or pixbuf.props.height != size): 47 | pixbuf = pixbuf.scale_simple(size, size, 48 | GdkPixbuf.InterpType.BILINEAR) 49 | return pixbuf 50 | 51 | 52 | def load_pixbuf_from_provider(provider_name, icon_size=48): 53 | if provider_name: 54 | provider_name = provider_name.lower().strip().replace(" ", "-") 55 | return load_pixbuf(provider_name, icon_size) 56 | else: 57 | return load_pixbuf(None, icon_size) 58 | -------------------------------------------------------------------------------- /Authenticator/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from .about import AboutDialog 20 | from .accounts import AccountsList, AccountRow, AccountsListState 21 | from .actions_bar import ActionsBar 22 | from .headerbar import HeaderBar 23 | from .search_bar import SearchBar 24 | from .window import Window 25 | from .settings import SettingsWindow 26 | from .utils import import_json, export_json, import_pgp_json, export_pgp_json 27 | from .backup import GPGRestoreWindow 28 | -------------------------------------------------------------------------------- /Authenticator/widgets/about.py.in: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from gettext import gettext as _ 20 | 21 | from gi import require_version 22 | require_version("Gtk", "3.0") 23 | from gi.repository import Gtk 24 | 25 | 26 | class AboutDialog(Gtk.AboutDialog): 27 | """ 28 | AboutDialog Widget. 29 | """ 30 | 31 | def __init__(self): 32 | Gtk.AboutDialog.__init__(self) 33 | self.set_modal(True) 34 | self._build_widgets() 35 | 36 | def _build_widgets(self): 37 | """ 38 | Build the AboutDialog widget. 39 | """ 40 | self.set_authors(["Bilal Elmoussaoui"]) 41 | self.set_artists(["Alexandros Felekidis"]) 42 | self.set_logo_icon_name("com.github.bilelmoussaoui.Authenticator") 43 | self.set_license_type(Gtk.License.GPL_3_0) 44 | self.set_program_name(_("Authenticator")) 45 | self.set_translator_credits(_("translator-credits")) 46 | self.set_version("@VERSION@") 47 | self.set_comments(_("Two-factor authentication code generator.")) 48 | self.set_website("https://github.com/bilelmoussaoui/Authenticator") 49 | -------------------------------------------------------------------------------- /Authenticator/widgets/accounts/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from .add import AddAccountWindow 20 | from .list import AccountsWidget, AccountsList, EmptyAccountsList, AccountsListState 21 | from .row import AccountRow 22 | -------------------------------------------------------------------------------- /Authenticator/widgets/accounts/edit.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from gettext import gettext as _ 20 | 21 | from gi import require_version 22 | 23 | require_version("Gtk", "3.0") 24 | from gi.repository import Gtk, Gdk, GObject 25 | 26 | from .add import AccountConfig 27 | 28 | 29 | class EditAccountWindow(Gtk.Window, GObject.GObject): 30 | __gsignals__ = { 31 | 'updated': (GObject.SignalFlags.RUN_LAST, None, (str, str,)), 32 | } 33 | 34 | def __init__(self, account): 35 | Gtk.Window.__init__(self) 36 | GObject.GObject.__init__(self) 37 | self._account = account 38 | self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) 39 | self.set_size_request(400, 600) 40 | self.resize(400, 600) 41 | self.connect('key_press_event', self._on_key_press) 42 | self._build_widgets() 43 | 44 | def _build_widgets(self): 45 | header_bar = Gtk.HeaderBar() 46 | header_bar.set_show_close_button(False) 47 | header_bar.set_title(_("Edit {} - {}".format(self._account.username, 48 | self._account.provider))) 49 | self.set_titlebar(header_bar) 50 | # Save btn 51 | self.save_btn = Gtk.Button() 52 | self.save_btn.set_label(_("Save")) 53 | self.save_btn.connect("clicked", self._on_save) 54 | self.save_btn.get_style_context().add_class("suggested-action") 55 | header_bar.pack_end(self.save_btn) 56 | 57 | self.close_btn = Gtk.Button() 58 | self.close_btn.set_label(_("Close")) 59 | self.close_btn.connect("clicked", self._on_quit) 60 | 61 | header_bar.pack_start(self.close_btn) 62 | 63 | self.account_config = AccountConfig(edit=True, account=self._account) 64 | self.account_config.connect("changed", self._on_account_config_changed) 65 | 66 | self.add(self.account_config) 67 | 68 | def _on_account_config_changed(self, _, state): 69 | """ 70 | Set the sensitivity of the AddButton 71 | depends on the AccountConfig. 72 | 73 | :param state: the state of the save button 74 | :type state: bool 75 | """ 76 | self.save_btn.set_sensitive(state) 77 | 78 | def _on_save(self, *_): 79 | """ 80 | Save Button clicked signal handler. 81 | """ 82 | new_account = self.account_config.account 83 | username = new_account["username"] 84 | provider = new_account["provider"] 85 | old_provider = self._account.provider 86 | # Update the AccountRow widget 87 | self.emit("updated", username, provider) 88 | # Update the providers list 89 | if provider != old_provider: 90 | from .list import AccountsWidget 91 | ac_widget = AccountsWidget.get_default() 92 | ac_widget.update_provider(self._account, provider) 93 | self._on_quit() 94 | 95 | def _on_quit(self, *_): 96 | """ 97 | Close the window. 98 | """ 99 | self.destroy() 100 | 101 | def _on_key_press(self, _, event): 102 | """ 103 | KeyPress event handler. 104 | """ 105 | _, key_val = event.get_keyval() 106 | if key_val == Gdk.KEY_Escape: 107 | self._on_quit() 108 | -------------------------------------------------------------------------------- /Authenticator/widgets/accounts/row.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from gettext import gettext as _ 20 | 21 | from gi import require_version 22 | 23 | require_version("Gtk", "3.0") 24 | from gi.repository import Gio, Gtk, GObject, Pango 25 | 26 | from .edit import EditAccountWindow 27 | 28 | 29 | class ActionButton(Gtk.Button): 30 | 31 | def __init__(self, icon_name, tooltip): 32 | Gtk.Button.__init__(self) 33 | self._build_widget(icon_name, tooltip) 34 | 35 | def _build_widget(self, icon_name, tooltip): 36 | icon = Gio.ThemedIcon(name=icon_name) 37 | image = Gtk.Image.new_from_gicon(icon, 38 | Gtk.IconSize.BUTTON) 39 | self.set_tooltip_text(tooltip) 40 | self.set_image(image) 41 | 42 | def hide(self): 43 | self.set_visible(False) 44 | self.set_no_show_all(True) 45 | 46 | def show(self): 47 | self.set_visible(True) 48 | self.set_no_show_all(False) 49 | 50 | 51 | class ActionsBox(Gtk.Box): 52 | """ 53 | AccountRow's Actions Box 54 | """ 55 | 56 | def __init__(self): 57 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL) 58 | self.copy_btn = ActionButton("edit-copy-symbolic", _("Copy")) 59 | self.edit_btn = ActionButton("document-edit-symbolic", _("Edit")) 60 | self._build_widget() 61 | 62 | def _build_widget(self): 63 | """Build ActionsBox widgets.""" 64 | self.pack_start(self.copy_btn, False, False, 3) 65 | self.pack_start(self.edit_btn, False, False, 3) 66 | 67 | 68 | class AccountRow(Gtk.ListBoxRow, GObject.GObject): 69 | """ 70 | AccountRow widget. 71 | """ 72 | 73 | __gsignals__ = { 74 | 'on_selected': (GObject.SignalFlags.RUN_LAST, None, ()), 75 | } 76 | 77 | def __init__(self, account): 78 | """ 79 | :param account: Account 80 | """ 81 | Gtk.ListBoxRow.__init__(self) 82 | self.get_style_context().add_class("account-row") 83 | self._account = account 84 | self.check_btn = Gtk.CheckButton() 85 | 86 | self._account.connect("otp_updated", self._on_pin_updated) 87 | self._build_widget() 88 | self.show_all() 89 | 90 | @property 91 | def account(self): 92 | """ 93 | The account related to the AccountRow 94 | 95 | :return: Account Object 96 | """ 97 | return self._account 98 | 99 | @property 100 | def checked(self): 101 | """ 102 | Whether the CheckButton is active or not. 103 | :return: bool 104 | """ 105 | return self.check_btn.get_active() 106 | 107 | def _build_widget(self): 108 | """Build the Account Row widget.""" 109 | container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, 110 | spacing=6) 111 | 112 | container.pack_start(self.check_btn, False, False, 3) 113 | self.check_btn.set_visible(False) 114 | self.check_btn.get_style_context().add_class("account-row-checkbtn") 115 | self.check_btn.connect("toggled", self._check_btn_toggled) 116 | self.check_btn.set_no_show_all(True) 117 | 118 | # Account Name & Two factor code: 119 | info_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 120 | 121 | # Account Name 122 | self.username_lbl = Gtk.Label(label=self.account.username) 123 | self.username_lbl.set_tooltip_text(self.account.username) 124 | self.username_lbl.set_ellipsize(Pango.EllipsizeMode.END) 125 | self.username_lbl.set_halign(Gtk.Align.START) 126 | self.username_lbl.get_style_context().add_class("username") 127 | 128 | info_container.pack_start(self.username_lbl, False, False, 0) 129 | info_container.set_valign(Gtk.Align.CENTER) 130 | container.pack_start(info_container, True, True, 6) 131 | 132 | # Actions container 133 | actions = ActionsBox() 134 | actions.copy_btn.connect("clicked", self._on_copy) 135 | actions.edit_btn.connect("clicked", self._on_edit) 136 | actions.set_valign(Gtk.Align.CENTER) 137 | container.pack_end(actions, False, False, 6) 138 | 139 | # Secret code 140 | otp_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 141 | pin = self.account.otp.pin 142 | self.pin_label = Gtk.Label() 143 | self.pin_label.set_halign(Gtk.Align.START) 144 | if pin: 145 | self.pin_label.set_text(pin) 146 | else: 147 | self.pin_label.set_text("??????") 148 | self.pin_label.set_tooltip_text( 149 | _("Couldn't generate the secret code")) 150 | self.pin_label.get_style_context().add_class("token-label") 151 | 152 | otp_container.pack_start(self.pin_label, False, False, 6) 153 | otp_container.set_valign(Gtk.Align.CENTER) 154 | otp_container.set_halign(Gtk.Align.START) 155 | container.pack_end(otp_container, False, False, 6) 156 | 157 | self.add(container) 158 | 159 | def _check_btn_toggled(self, *_): 160 | """ 161 | CheckButton signal Handler. 162 | """ 163 | self.emit("on_selected") 164 | 165 | def _on_copy(self, *_): 166 | """ 167 | Copy button clicked signal handler. 168 | Copies the OTP pin to the clipboard 169 | """ 170 | self._account.copy_pin() 171 | 172 | def _on_edit(self, *_): 173 | """ 174 | Edit Button clicked signal handler. 175 | Opens a new Window to edit the current account. 176 | """ 177 | from ..window import Window 178 | edit_window = EditAccountWindow(self._account) 179 | edit_window.set_transient_for(Window.get_default()) 180 | edit_window.connect("updated", self._on_update) 181 | edit_window.show_all() 182 | edit_window.present() 183 | 184 | def _on_update(self, _, username, provider): 185 | """ 186 | On account update signal handler. 187 | Updates the account username and provider 188 | 189 | :param username: the new account's username 190 | :type username: str 191 | 192 | :param provider: the new account's provider 193 | :type provider: str 194 | """ 195 | self.username_lbl.set_text(username) 196 | self.account.update(username, provider) 197 | 198 | def _on_pin_updated(self, _, pin): 199 | """ 200 | Updates the pin label each time a new OTP is generated. 201 | otp_updated signal handler. 202 | 203 | :param pin: the new OTP 204 | :type pin: str 205 | """ 206 | if pin: 207 | self.pin_label.set_text(pin) 208 | -------------------------------------------------------------------------------- /Authenticator/widgets/actions_bar.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from gettext import gettext as _ 20 | 21 | from gi import require_version 22 | 23 | require_version("Gtk", "3.0") 24 | from gi.repository import Gtk 25 | 26 | 27 | class ActionsBar(Gtk.ActionBar): 28 | """ 29 | ActionsBar Widget 30 | """ 31 | 32 | # Default instance of ActionsBar 33 | instance = None 34 | 35 | def __init__(self): 36 | Gtk.ActionBar.__init__(self) 37 | self._build_widgets() 38 | self.show_all() 39 | self.set_visible(False) 40 | self.set_no_show_all(True) 41 | 42 | @staticmethod 43 | def get_default(): 44 | if ActionsBar.instance is None: 45 | ActionsBar.instance = ActionsBar() 46 | return ActionsBar.instance 47 | 48 | def _build_widgets(self): 49 | """ 50 | Build the ActionsBar widgets. 51 | """ 52 | self.delete_btn = Gtk.Button(label=_("Delete")) 53 | self.delete_btn.set_sensitive(False) 54 | self.pack_end(self.delete_btn) 55 | 56 | def on_selected_rows_changed(self, _, selected_rows): 57 | """ 58 | Set the sensitivity of the delete button depending 59 | on the total selected rows 60 | 61 | :param selected_rows: the total number of selected rows 62 | :type selected_rows: int 63 | """ 64 | self.delete_btn.set_sensitive(selected_rows > 0) 65 | -------------------------------------------------------------------------------- /Authenticator/widgets/backup/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from .gnupg import GPGRestoreWindow, FingprintPGPWindow 20 | -------------------------------------------------------------------------------- /Authenticator/widgets/backup/gnupg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from gettext import gettext as _ 20 | 21 | from gi import require_version 22 | 23 | require_version("Gd", "1.0") 24 | require_version("Gtk", "3.0") 25 | from gi.repository import Gd, Gtk, GObject, GLib 26 | from os import path 27 | from tempfile import NamedTemporaryFile 28 | 29 | from ...models import GPG, Logger 30 | from ..settings import SettingsBoxWithEntry, ClickableSettingsBox 31 | 32 | 33 | class GPGRestoreWindow(Gtk.Window): 34 | def __init__(self, filename): 35 | Gtk.Window.__init__(self) 36 | self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) 37 | self.set_size_request(400, 200) 38 | self.set_title(_("GPG paraphrase")) 39 | self.resize(400, 200) 40 | self._filename = filename 41 | self._build_widgets() 42 | 43 | def _build_widgets(self): 44 | header_bar = Gtk.HeaderBar() 45 | header_bar.set_show_close_button(True) 46 | header_bar.set_title(_("GPG paraphrase")) 47 | apply_btn = Gtk.Button() 48 | apply_btn.set_label(_("Import")) 49 | apply_btn.get_style_context().add_class("suggested-action") 50 | apply_btn.connect("clicked", self.__on_apply) 51 | header_bar.pack_end(apply_btn) 52 | self.set_titlebar(header_bar) 53 | 54 | container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 55 | self.paraphrase_widget = SettingsBoxWithEntry(_("Paraphrase"), True) 56 | container.pack_start(self.paraphrase_widget, False, False, 0) 57 | container.get_style_context().add_class("settings-main-container") 58 | self.add(container) 59 | 60 | def __on_apply(self, *__): 61 | from ...models import BackupJSON 62 | try: 63 | paraphrase = self.paraphrase_widget.entry.get_text() 64 | if not paraphrase: 65 | paraphrase = " " 66 | output_file = path.join(GLib.get_user_cache_dir(), 67 | path.basename(NamedTemporaryFile().name)) 68 | status = GPG.get_default().decrypt_json(self._filename, paraphrase, output_file) 69 | if status.ok: 70 | BackupJSON.import_file(output_file) 71 | self.destroy() 72 | else: 73 | self.__send_notification(_("There was an error during the import of the encrypted file.")) 74 | 75 | except AttributeError: 76 | Logger.error("[GPG] Invalid JSON file.") 77 | 78 | def __send_notification(self, message): 79 | notification = Gd.Notification() 80 | notification.set_timeout(5) 81 | container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) 82 | 83 | notification_lbl = Gtk.Label() 84 | notification_lbl.set_text(message) 85 | container.pack_start(notification_lbl, False, False, 3) 86 | 87 | notification.add(container) 88 | notification_parent = self.get_children()[-1] 89 | notification_parent.add(notification) 90 | notification_parent.reorder_child(notification, 0) 91 | self.show_all() 92 | 93 | 94 | class FingprintPGPWindow(Gtk.Window, GObject.GObject): 95 | """Main Window object.""" 96 | __gsignals__ = { 97 | 'selected': (GObject.SignalFlags.RUN_LAST, None, (str,)) 98 | } 99 | 100 | def __init__(self, filename): 101 | Gtk.Window.__init__(self) 102 | self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) 103 | self.set_size_request(600, 600) 104 | self.set_title(_("GPG fingerprint")) 105 | self.resize(600, 600) 106 | self._filename = filename 107 | self._build_widgets() 108 | 109 | def _build_widgets(self): 110 | header_bar = Gtk.HeaderBar() 111 | header_bar.set_show_close_button(True) 112 | header_bar.set_title(_("GPG fingerprint")) 113 | self.set_titlebar(header_bar) 114 | 115 | container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 116 | keys = GPG.get_default().get_keys() 117 | self.__add_keys(keys["public"], _("Public keys"), container) 118 | self.__add_keys(keys["private"], _("Private keys"), container) 119 | 120 | container.get_style_context().add_class("settings-main-container") 121 | self.add(container) 122 | 123 | def __add_keys(self, keys, label, container): 124 | key_label = Gtk.Label() 125 | key_label.set_halign(Gtk.Align.START) 126 | key_label.get_style_context().add_class("gpg-key-lbl") 127 | key_label.set_text(label) 128 | container.pack_start(key_label, False, False, 0) 129 | 130 | for key in keys: 131 | uid = key.get("uids", [""])[0] 132 | fingerprint = key.get("fingerprint", "") 133 | key_widget = ClickableSettingsBox(uid, fingerprint) 134 | key_widget.connect("button-press-event", self.__finger_print_selected, fingerprint) 135 | container.pack_start(key_widget, False, False, 0) 136 | 137 | def __finger_print_selected(self, _, __, fingerprint): 138 | self.emit("selected", fingerprint) 139 | self.destroy() 140 | -------------------------------------------------------------------------------- /Authenticator/widgets/headerbar.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from abc import abstractmethod, ABCMeta 20 | 21 | from gi import require_version 22 | 23 | require_version("Gtk", "3.0") 24 | from gi.repository import Gtk, Gio 25 | from gettext import gettext as _ 26 | 27 | from ..models import Database 28 | 29 | 30 | class HeaderBarState: 31 | EMPTY = 0 32 | NORMAL = 2 33 | SELECT = 3 34 | 35 | 36 | class HeaderBarBtn: 37 | __metaclass__ = ABCMeta 38 | 39 | def __init__(self, icon_name, tooltip): 40 | self._build(icon_name, tooltip) 41 | 42 | @abstractmethod 43 | def set_image(self, image): 44 | """Set an image""" 45 | 46 | @abstractmethod 47 | def set_tooltip_text(self, tooltip): 48 | """Set the tooltip text""" 49 | 50 | def _build(self, icon_name, tooltip): 51 | """ 52 | :param icon_name: 53 | :param tooltip: 54 | """ 55 | icon = Gio.ThemedIcon(name=icon_name) 56 | image = Gtk.Image.new_from_gicon(icon, 57 | Gtk.IconSize.BUTTON) 58 | self.set_tooltip_text(tooltip) 59 | self.set_image(image) 60 | 61 | def hide_(self): 62 | """Set a button visible or not?.""" 63 | self.set_visible(False) 64 | self.set_no_show_all(True) 65 | 66 | def show_(self): 67 | self.set_visible(True) 68 | self.set_no_show_all(False) 69 | 70 | 71 | class HeaderBarButton(Gtk.Button, HeaderBarBtn): 72 | """HeaderBar Button widget""" 73 | 74 | def __init__(self, icon_name, tooltip): 75 | Gtk.Button.__init__(self) 76 | HeaderBarBtn.__init__(self, icon_name, tooltip) 77 | 78 | 79 | class HeaderBarToggleButton(Gtk.ToggleButton, HeaderBarBtn): 80 | """HeaderBar Toggle Button widget""" 81 | 82 | def __init__(self, icon_name, tooltip): 83 | Gtk.ToggleButton.__init__(self) 84 | HeaderBarBtn.__init__(self, icon_name, tooltip) 85 | 86 | 87 | class HeaderBar(Gtk.HeaderBar): 88 | """ 89 | HeaderBar widget 90 | """ 91 | instance = None 92 | state = HeaderBarState.NORMAL 93 | 94 | def __init__(self): 95 | Gtk.HeaderBar.__init__(self) 96 | 97 | self.search_btn = HeaderBarToggleButton("system-search-symbolic", 98 | _("Search")) 99 | self.add_btn = HeaderBarButton("list-add-symbolic", 100 | _("Add a new account")) 101 | self.settings_btn = HeaderBarButton("open-menu-symbolic", 102 | _("Settings")) 103 | self.select_btn = HeaderBarButton("object-select-symbolic", 104 | _("Selection mode")) 105 | 106 | self.cancel_btn = Gtk.Button(label=_("Cancel")) 107 | 108 | self.popover = None 109 | 110 | self._build_widgets() 111 | 112 | @staticmethod 113 | def get_default(): 114 | """ 115 | :return: Default instance of HeaderBar 116 | """ 117 | if HeaderBar.instance is None: 118 | HeaderBar.instance = HeaderBar() 119 | return HeaderBar.instance 120 | 121 | def _build_widgets(self): 122 | """ 123 | Generate the HeaderBar widgets 124 | """ 125 | self.set_show_close_button(True) 126 | 127 | left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) 128 | right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) 129 | 130 | # Hide the search button if nothing is found 131 | if Database.get_default().count > 0: 132 | self.search_btn.show_() 133 | else: 134 | self.search_btn.hide_() 135 | 136 | left_box.add(self.add_btn) 137 | 138 | right_box.pack_start(self.search_btn, False, False, 0) 139 | right_box.pack_start(self.select_btn, False, False, 0) 140 | right_box.pack_start(self.cancel_btn, False, False, 0) 141 | right_box.pack_end(self.settings_btn, False, False, 3) 142 | 143 | self.pack_start(left_box) 144 | self.pack_end(right_box) 145 | 146 | def generate_popover_menu(self, menu): 147 | self.settings_btn.connect("clicked", self.toggle_popover) 148 | self.popover = Gtk.Popover.new_from_model(self.settings_btn, 149 | menu) 150 | self.popover.props.width_request = 200 151 | 152 | def toggle_popover(self, *_): 153 | if self.popover: 154 | if self.popover.get_visible(): 155 | self.popover.hide() 156 | else: 157 | self.popover.show_all() 158 | 159 | def toggle_settings_button(self, visible): 160 | self.settings_button.set_visible(visible) 161 | self.settings_button.set_no_show_all(not visible) 162 | 163 | def set_state(self, state): 164 | if state != HeaderBarState.SELECT: 165 | self.cancel_btn.set_visible(False) 166 | self.cancel_btn.set_no_show_all(True) 167 | if state == HeaderBarState.EMPTY: 168 | self.add_btn.show_() 169 | self.search_btn.hide_() 170 | self.select_btn.hide_() 171 | self.settings_btn.show_() 172 | elif state == HeaderBarState.SELECT: 173 | self.search_btn.show_() 174 | self.add_btn.hide_() 175 | self.select_btn.hide_() 176 | self.set_show_close_button(False) 177 | self.get_style_context().add_class("selection-mode") 178 | self.cancel_btn.set_visible(True) 179 | self.cancel_btn.set_no_show_all(False) 180 | self.settings_btn.hide_() 181 | self.set_title(_("Click on items to select them")) 182 | else: 183 | self.search_btn.show_() 184 | self.add_btn.show_() 185 | self.select_btn.show_() 186 | self.settings_btn.show_() 187 | if self.state == HeaderBarState.SELECT: 188 | self.get_style_context().remove_class("selection-mode") 189 | self.set_show_close_button(True) 190 | self.set_title("") 191 | self.state = state 192 | -------------------------------------------------------------------------------- /Authenticator/widgets/search_bar.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from gi import require_version 20 | 21 | require_version("Gtk", "3.0") 22 | from gi.repository import Gtk, GObject 23 | 24 | 25 | class SearchBar(Gtk.SearchBar): 26 | """ 27 | Search Bar widget. 28 | """ 29 | _search_button = None 30 | 31 | def __init__(self, search_button=None, search_list=[]): 32 | Gtk.SearchBar.__init__(self) 33 | self.search_list = search_list 34 | self.search_entry = Gtk.SearchEntry() 35 | self.search_button = search_button 36 | self._build_widgets() 37 | 38 | @property 39 | def search_button(self): 40 | return self._search_button 41 | 42 | @search_button.setter 43 | def search_button(self, widget): 44 | if widget: 45 | self._search_button = widget 46 | self.bind_property("search-mode-enabled", self._search_button, 47 | "active", GObject.BindingFlags.BIDIRECTIONAL) 48 | 49 | def _build_widgets(self): 50 | """ 51 | Build the SearchBar widgets 52 | """ 53 | self.set_show_close_button(True) 54 | 55 | self.search_entry.set_width_chars(28) 56 | self.search_entry.connect("search-changed", 57 | self.set_filter_func, 58 | self.filter_func) 59 | self.connect_entry(self.search_entry) 60 | self.add(self.search_entry) 61 | 62 | @staticmethod 63 | def filter_func(row, data, *_): 64 | """ 65 | Filter function, used to check if the entered data exists on the application ListBox 66 | """ 67 | data = data.lower() 68 | if len(data) > 0: 69 | return ( 70 | data in row.account.username.lower() 71 | or 72 | data in row.account.provider.lower() 73 | ) 74 | else: 75 | return True 76 | 77 | def set_filter_func(self, entry, filter_func): 78 | """ 79 | Filter the data of a listbox from an entry 80 | :param entry: Gtk.Entry 81 | :param filter_func: The function to use as filter 82 | """ 83 | data = entry.get_text().strip() 84 | for search_list in self.search_list: 85 | search_list.set_filter_func(filter_func, 86 | data, False) 87 | -------------------------------------------------------------------------------- /Authenticator/widgets/utils.py: -------------------------------------------------------------------------------- 1 | from gettext import gettext as _ 2 | 3 | from gi import require_version 4 | 5 | require_version("Gtk", "3.0") 6 | from gi.repository import Gtk 7 | 8 | 9 | def __open_file_chooser(parent, mimetype, action=Gtk.FileChooserAction.OPEN): 10 | file_chooser = Gtk.FileChooserNative() 11 | file_chooser.set_action(action) 12 | file_chooser.set_transient_for(parent) 13 | filter_json = Gtk.FileFilter() 14 | filter_json.set_name(mimetype["name"]) 15 | filter_json.add_mime_type(mimetype["type"]) 16 | file_chooser.add_filter(filter_json) 17 | response = file_chooser.run() 18 | if response == Gtk.ResponseType.ACCEPT: 19 | filename = file_chooser.get_filename() 20 | return filename 21 | file_chooser.destroy() 22 | return None 23 | 24 | 25 | def import_json(parent): 26 | mimetype = {'type': "application/json", 'name': _("JSON files")} 27 | return __open_file_chooser(parent, mimetype) 28 | 29 | 30 | def export_json(parent): 31 | mimetype = {'type': "application/json", 'name': _("JSON files")} 32 | return __open_file_chooser(parent, mimetype, Gtk.FileChooserAction.SAVE) 33 | 34 | 35 | def import_pgp_json(parent): 36 | mimetype = {'type': "application/pgp-encrypted", 'name': _("Encrypted GPG files")} 37 | return __open_file_chooser(parent, mimetype) 38 | 39 | 40 | def export_pgp_json(parent): 41 | mimetype = {'type': "application/pgp-encrypted", 'name': _("Encrypted GPG files")} 42 | return __open_file_chooser(parent, mimetype, Gtk.FileChooserAction.SAVE) 43 | 44 | 45 | def open_directory(parent): 46 | file_chooser = Gtk.FileChooserNative() 47 | file_chooser.set_action(Gtk.FileChooserAction.SELECT_FOLDER) 48 | file_chooser.set_transient_for(parent) 49 | file_chooser.set_show_hidden(True) 50 | response = file_chooser.run() 51 | if response == Gtk.ResponseType.ACCEPT: 52 | filename = file_chooser.get_filename() 53 | return filename 54 | file_chooser.destroy() 55 | return None 56 | -------------------------------------------------------------------------------- /Authenticator/widgets/window.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2017 Bilal Elmoussaoui 3 | 4 | This file is part of Authenticator. 5 | 6 | Authenticator is free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published 8 | by the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Authenticator is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Authenticator. If not, see . 18 | """ 19 | from gi import require_version 20 | 21 | require_version("Gtk", "3.0") 22 | from gi.repository import Gtk, GObject 23 | from ..models import Logger, Settings, Database, AccountsManager 24 | from .headerbar import HeaderBar, HeaderBarState 25 | from .accounts import AccountsWidget, AccountsListState, AddAccountWindow, EmptyAccountsList 26 | from .search_bar import SearchBar 27 | from .actions_bar import ActionsBar 28 | 29 | 30 | class Window(Gtk.ApplicationWindow, GObject.GObject): 31 | """Main Window object.""" 32 | __gsignals__ = { 33 | 'changed': (GObject.SignalFlags.RUN_LAST, None, (bool,)) 34 | } 35 | 36 | # Default Window instance 37 | instance = None 38 | 39 | def __init__(self): 40 | Gtk.ApplicationWindow.__init__(self, type=Gtk.WindowType.TOPLEVEL) 41 | 42 | self.set_icon_name("com.github.bilelmoussaoui.Authenticator") 43 | self.resize(550, 600) 44 | self.restore_state() 45 | AccountsManager.get_default() 46 | self._build_widgets() 47 | self.show_all() 48 | 49 | @staticmethod 50 | def get_default(): 51 | """Return the default instance of Window.""" 52 | if Window.instance is None: 53 | Window.instance = Window() 54 | return Window.instance 55 | 56 | def close(self): 57 | self.save_state() 58 | AccountsManager.get_default().kill() 59 | self.destroy() 60 | 61 | def set_menu(self, gio_menu): 62 | """Set Headerbar popover menu.""" 63 | HeaderBar.get_default().generate_popover_menu(gio_menu) 64 | 65 | def add_account(self, *_): 66 | add_window = AddAccountWindow() 67 | add_window.set_transient_for(self) 68 | add_window.show_all() 69 | add_window.present() 70 | 71 | def update_view(self, *_): 72 | header_bar = HeaderBar.get_default() 73 | count = Database.get_default().count 74 | if count != 0: 75 | child_name = "accounts-list" 76 | header_bar.set_state(HeaderBarState.NORMAL) 77 | else: 78 | header_bar.set_state(HeaderBarState.EMPTY) 79 | child_name = "empty-accounts-list" 80 | child = self.main_stack.get_child_by_name(child_name) 81 | child.show_all() 82 | self.main_stack.set_visible_child(child) 83 | 84 | @staticmethod 85 | def toggle_select(*_): 86 | """ 87 | Toggle select mode 88 | """ 89 | header_bar = HeaderBar.get_default() 90 | accounts_widget = AccountsWidget.get_default() 91 | if header_bar.state == HeaderBarState.NORMAL: 92 | header_bar.set_state(HeaderBarState.SELECT) 93 | accounts_widget.set_state(AccountsListState.SELECT) 94 | else: 95 | header_bar.set_state(HeaderBarState.NORMAL) 96 | accounts_widget.set_state(AccountsListState.NORMAL) 97 | 98 | def save_state(self): 99 | """Save window position & size.""" 100 | settings = Settings.get_default() 101 | settings.window_position = self.get_position() 102 | settings.window_maximized = self.is_maximized() 103 | 104 | def restore_state(self): 105 | """Restore the window's state.""" 106 | settings = Settings.get_default() 107 | # Restore the window position 108 | position_x, position_y = settings.window_position 109 | if position_x != 0 and position_y != 0: 110 | self.move(position_x, position_y) 111 | Logger.debug("[Window] Restore position x: {}, y: {}".format(position_x, 112 | position_y)) 113 | else: 114 | # Fallback to the center 115 | self.set_position(Gtk.WindowPosition.CENTER) 116 | 117 | if settings.window_maximized: 118 | self.maximize() 119 | 120 | def _build_widgets(self): 121 | """Build main window widgets.""" 122 | # HeaderBar 123 | header_bar = HeaderBar.get_default() 124 | header_bar.select_btn.connect("clicked", Window.toggle_select) 125 | header_bar.add_btn.connect("clicked", self.add_account) 126 | header_bar.cancel_btn.connect("clicked", Window.toggle_select) 127 | self.set_titlebar(header_bar) 128 | 129 | # Main Container 130 | self.main_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 131 | 132 | # Main Stack 133 | self.main_stack = Gtk.Stack() 134 | 135 | # Accounts List 136 | account_list_cntr = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 137 | 138 | accounts_widget = AccountsWidget.get_default() 139 | accounts_widget.connect("changed", self.update_view) 140 | 141 | # Search Bar 142 | search_bar = SearchBar() 143 | self.connect("key-press-event", lambda x, 144 | y: search_bar.handle_event(y)) 145 | search_bar.search_button = header_bar.search_btn 146 | search_bar.search_list = accounts_widget.accounts_lists 147 | 148 | # Actions Bar 149 | actions_bar = ActionsBar.get_default() 150 | actions_bar.delete_btn.connect("clicked", 151 | accounts_widget.delete_selected) 152 | accounts_widget.connect("selected-rows-changed", 153 | actions_bar.on_selected_rows_changed) 154 | 155 | account_list_cntr.pack_start(search_bar, False, False, 0) 156 | account_list_cntr.pack_start(accounts_widget, True, True, 0) 157 | account_list_cntr.pack_start(actions_bar, False, False, 0) 158 | 159 | self.main_stack.add_named(account_list_cntr, 160 | "accounts-list") 161 | 162 | # Empty accounts list 163 | self.main_stack.add_named(EmptyAccountsList.get_default(), 164 | "empty-accounts-list") 165 | 166 | self.main_container.pack_start(self.main_stack, True, True, 0) 167 | self.add(self.main_container) 168 | self.update_view() 169 | 170 | actions_bar.bind_property("visible", header_bar.cancel_btn, 171 | "visible", 172 | GObject.BindingFlags.BIDIRECTIONAL) 173 | actions_bar.bind_property("no_show_all", header_bar.cancel_btn, 174 | "no_show_all", 175 | GObject.BindingFlags.BIDIRECTIONAL) 176 | 177 | def _on_account_delete(self, *_): 178 | Window.toggle_select() 179 | self.update_view() 180 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## The code is now hosted on GNOME Gitlab https://gitlab.gnome.org/World/Authenticator 2 | -------------------------------------------------------------------------------- /authenticator.py.in: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Copyright © 2017 Bilal Elmoussaoui 5 | 6 | This file is part of Authenticator. 7 | 8 | Authenticator is free software: you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License as published 10 | by the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | Authenticator is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with Authenticator. If not, see . 20 | """ 21 | 22 | import argparse 23 | import gettext 24 | import locale 25 | import sys 26 | from os import path 27 | 28 | from gi import require_version 29 | 30 | require_version('GIRepository', '2.0') 31 | from gi.repository import Gio, GIRepository 32 | 33 | sys.path.insert(1, '@PYTHON_DIR@') 34 | 35 | _ = gettext.gettext 36 | 37 | if __name__ == "__main__": 38 | locale.bindtextdomain('Authenticator', '@LOCALE_DIR@') 39 | locale.textdomain('Authenticator') 40 | gettext.bindtextdomain('Authenticator', '@LOCALE_DIR@') 41 | gettext.textdomain('Authenticator') 42 | VERSION = "@VERSION@" 43 | GIRepository.Repository.prepend_search_path( 44 | path.join('@LIB_DIR@', 'girepository-1.0')) 45 | GIRepository.Repository.prepend_library_path('@LIB_DIR@') 46 | 47 | parser = argparse.ArgumentParser(prog="Authenticator") 48 | parser.add_argument("--debug", "-d", action="store_true", 49 | help=_("Start in debug mode")) 50 | parser.add_argument("--version", "-v", action="store_true", 51 | help=_("Authenticator version number")) 52 | args = parser.parse_args() 53 | 54 | resource = Gio.resource_load(path.join('@DATA_DIR@', 55 | 'com.github.bilelmoussaoui.Authenticator.gresource')) 56 | Gio.Resource._register(resource) 57 | 58 | from Authenticator.models import Logger 59 | 60 | level = Logger.ERROR 61 | if args.debug: 62 | level = Logger.DEBUG 63 | import faulthandler 64 | 65 | faulthandler.enable() 66 | Logger.set_level(level) 67 | if args.version: 68 | sys.exit("Version : " + str(VERSION)) 69 | else: 70 | try: 71 | 72 | from Authenticator import Application 73 | 74 | app = Application.get_default() 75 | app.set_use_qrscanner(bool("@ENABLE_QRSCANNER@")) 76 | exit_status = app.run(None) 77 | sys.exit(exit_status) 78 | except KeyboardInterrupt: 79 | exit() 80 | -------------------------------------------------------------------------------- /com.github.bilelmoussaoui.Authenticator.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-id": "com.github.bilelmoussaoui.Authenticator", 3 | "runtime": "org.gnome.Platform", 4 | "runtime-version": "3.28", 5 | "sdk": "org.gnome.Sdk", 6 | "command": "authenticator", 7 | "finish-args": [ 8 | /* X11 + XShm */ 9 | "--share=ipc", "--socket=x11", 10 | /* Wayland */ 11 | "--socket=wayland", 12 | /* Filesystem */ 13 | "--filesystem=home", 14 | /* Keyring */ 15 | "--talk-name=org.freedesktop.secrets", 16 | /* Screenshot (used to scan QR code)*/ 17 | "--talk-name=org.gnome.Shell.Screenshot", 18 | /* dconf */ 19 | "--filesystem=xdg-run/dconf", "--filesystem=~/.config/dconf:ro", 20 | "--talk-name=ca.desrt.dconf", "--env=DCONF_USER_CONFIG_DIR=.config/dconf" 21 | ], 22 | "build-options": { 23 | "cflags": "-O2 -g", 24 | "cxxflags": "-O2 -g", 25 | "env": { 26 | "V": "1" 27 | } 28 | }, 29 | "modules": [{ 30 | "name": "zbar", 31 | "rm-configure": true, 32 | "config-opts": [ 33 | "--without-qt", 34 | "--without-gtk", 35 | "--without-xv", 36 | "--without-imagemagick", 37 | "--disable-video", 38 | "--without-python", 39 | "--enable-codes=qrcode" 40 | ], 41 | "sources": [{ 42 | "type": "archive", 43 | "url": "http://downloads.sourceforge.net/project/zbar/zbar/0.10/zbar-0.10.tar.bz2", 44 | "sha256": "234efb39dbbe5cef4189cc76f37afbe3cfcfb45ae52493bfe8e191318bdbadc6" 45 | }, 46 | { 47 | "type": "script", 48 | "dest-filename": "autogen.sh", 49 | "commands": [ 50 | "autoreconf -vfi -W none" 51 | ] 52 | }, 53 | { 54 | "type": "patch", 55 | "path": "tools/zbar_configure.patch" 56 | } 57 | ] 58 | }, 59 | { 60 | "name": "pyotp", 61 | "buildsystem": "simple", 62 | "build-commands": [ 63 | "python3 setup.py install --prefix=/app" 64 | ], 65 | "ensure-writable": [ 66 | "/lib/python*/site-packages/easy-install.pth", 67 | "/lib/python*/site-packages/setuptools.pth", 68 | "/app/lib/python*/site-packages/easy-install.pth", 69 | "/app/lib/python*/site-packages/setuptools.pth" 70 | ], 71 | "sources": [{ 72 | "type": "archive", 73 | "url": "https://pypi.python.org/packages/ac/0c/bd96508e36956ae627e527a7a7fba486865a738b4682e7290cd0e7c34f52/pyotp-2.2.4.tar.gz#md5=16cb1a08d38777ca74b5e9c7803810b6", 74 | "sha256": "92c3973ba91273e7e4a7fd4a1020ae4b050ccd2e149b554911e1b45ca458ac2d" 75 | }] 76 | }, 77 | { 78 | "name": "python-pillow", 79 | "buildsystem": "simple", 80 | "build-options": { 81 | "arch": { 82 | "i386": { 83 | "env": { 84 | "MAX_CONCURRENCY": "1" 85 | } 86 | }, 87 | "arm": { 88 | "env": { 89 | "MAX_CONCURRENCY": "1" 90 | } 91 | } 92 | } 93 | }, 94 | "ensure-writable": [ 95 | "/lib/python*/site-packages/easy-install.pth", 96 | "/lib/python*/site-packages/setuptools.pth", 97 | "/app/lib/python*/site-packages/easy-install.pth", 98 | "/app/lib/python*/site-packages/setuptools.pth" 99 | ], 100 | "build-commands": [ 101 | "python3 setup.py install --prefix=/app" 102 | ], 103 | "sources": [{ 104 | "type": "archive", 105 | "url": "https://github.com/python-pillow/Pillow/archive/5.0.0.tar.gz", 106 | "sha256": "e85301adaa827b9f29ab518eff99d5719e572ca5f369866ebfe57a92e7135aec" 107 | }, 108 | { 109 | "type": "shell", 110 | "commands": [ 111 | "sed -i 's/if not platform_/if not dirs/' setup.py" 112 | ] 113 | } 114 | ] 115 | }, 116 | { 117 | "name": "pyzbar", 118 | "buildsystem": "simple", 119 | "build-commands": [ 120 | "python3 setup.py install --prefix=/app" 121 | ], 122 | "ensure-writable": [ 123 | "/lib/python*/site-packages/easy-install.pth", 124 | "/lib/python*/site-packages/setuptools.pth", 125 | "/app/lib/python*/site-packages/easy-install.pth", 126 | "/app/lib/python*/site-packages/setuptools.pth" 127 | ], 128 | "sources": [{ 129 | "type": "archive", 130 | "url": "https://github.com/NaturalHistoryMuseum/pyzbar/archive/v0.1.7.tar.gz", 131 | "sha256": "4dbbece533650f2aeb6c8d1f41cf424614d2877d7331c48a9eed35ae9f949626" 132 | }] 133 | }, 134 | { 135 | "name": "python-gnupg", 136 | "buildsystem": "simple", 137 | "build-commands": [ 138 | "pip3 install --prefix=/app python_gnupg-0.4.3-py2.py3-none-any.whl" 139 | ], 140 | "sources": [ 141 | { 142 | "type": "file", 143 | "url": "https://files.pythonhosted.org/packages/4a/87/76ead690afc4c7710012ede242537cd9807dde9de6299e65d075925c0b02/python_gnupg-0.4.3-py2.py3-none-any.whl", 144 | "sha256": "faa69bab58ed0936f0ccf96c99b92369b7a1819305d37dfe5c927d21a437a09d" 145 | } 146 | ] 147 | }, 148 | { 149 | "name": "Authenticator", 150 | "buildsystem": "meson", 151 | "sources": [{ 152 | "type": "git", 153 | "url": "https://gitlab.gnome.org/bilelmoussaoui/Authenticator", 154 | "branch": "master" 155 | }] 156 | } 157 | ] 158 | } 159 | -------------------------------------------------------------------------------- /data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.github.bilelmoussaoui.Authenticator.desktop 4 | CC0 5 | GPL-3.0+ 6 | Authenticator 7 | Two-factor authentication code generator 8 | 9 |

Simple application that generates a two-factor authentication code, created for GNOME.

10 |

Features:

11 |
    12 |
  • QR code scanner
  • 13 |
  • Beautiful UI
  • 14 |
  • Huge database of (290+) websites/applications
  • 15 |
16 |
17 | 18 | 19 | https://raw.githubusercontent.com/bilelmoussaoui/Authenticator/master/data/screenshots/screenshot1.png 20 | 21 | 22 | https://github.com/bilelmoussaoui/Authenticator 23 | https://github.com/bilelmoussaoui/Authenticator/issues 24 | https://hosted.weblate.org/projects/authenticator/translation/ 25 | https://www.paypal.me/BilalELMoussaoui 26 | 27 | Utility 28 | 29 | 30 | 31 | 32 | 33 |
    34 |
  • GNOME Shell Search provider
  • 35 |
  • Codes expire simultaneously #91
  • 36 |
37 |
38 |
39 | 40 | 41 |
    42 |
  • Revamped main window to more closely follow the GNOME HIG
  • 43 |
  • Revamped add a new account window to make it easier to use
  • 44 |
  • Possibility to add an account from a provider not listed in the shipped database
  • 45 |
  • Possibilty to edit an account
  • 46 |
  • One Time Password now visible by default
  • 47 |
48 |
49 |
50 | 51 | 52 |
    53 |
  • Fix python-dbus by using GDbus instead
  • 54 |
55 |
56 |
57 | 58 | 59 |
    60 |
  • Fix the QRScanner on GNOME Shell
  • 61 |
  • Add a new entry for the account's username
  • 62 |
  • Updated database of supported accounts
  • 63 |
64 |
65 |
66 | 67 | 68 |
    69 |
  • HOTFIX: App not running in DE other than GNOME
  • 70 |
71 |
72 |
73 | 74 | 75 |
    76 |
  • Rename project to Authenticator
  • 77 |
  • Cleaner code base
  • 78 |
  • Faster startup
  • 79 |
  • Remove unneeded features
  • 80 |
  • Switch to pyzbar instead of zbarlight
  • 81 |
  • Flatpak package
  • 82 |
83 |
84 |
85 | 86 | 87 |
88 | Bilal Elmoussaoui 89 | bil.elmoussaoui@gmail.com 90 | Authenticator 91 |
92 | -------------------------------------------------------------------------------- /data/com.github.bilelmoussaoui.Authenticator.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Authenticator 3 | GenericName=Two-factor authentication 4 | Comment=Two-factor authentication code generator 5 | Type=Application 6 | Exec=authenticator 7 | Terminal=false 8 | Categories=GNOME;GTK;Security;Network; 9 | Keywords=Gnome;GTK;Verification; 10 | Icon=com.github.bilelmoussaoui.Authenticator 11 | StartupNotify=true 12 | StartupWMClass=com.github.bilelmoussaoui.Authenticator 13 | -------------------------------------------------------------------------------- /data/com.github.bilelmoussaoui.Authenticator.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | style.css 5 | data.json 6 | icons/hicolor/symbolic/actions/qrscanner-symbolic.svg 7 | 8 | 9 | icons/hicolor/48x48/apps/com.github.bilelmoussaoui.Authenticator.png 10 | 11 | 12 | icons/hicolor/48x48/apps/amazon.svg 13 | icons/hicolor/48x48/apps/discord.svg 14 | icons/hicolor/48x48/apps/dropbox.svg 15 | icons/hicolor/48x48/apps/facebook.svg 16 | icons/hicolor/48x48/apps/github.svg 17 | icons/hicolor/48x48/apps/gmail.svg 18 | icons/hicolor/48x48/apps/nextcloud.svg 19 | icons/hicolor/48x48/apps/owncloud.svg 20 | icons/hicolor/48x48/apps/twitter.svg 21 | icons/hicolor/48x48/apps/youtube.svg 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /data/com.github.bilelmoussaoui.Authenticator.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | [0,0] 6 | Default window postiton 7 | 8 | Default window postiton 9 | 10 | 11 | 12 | false 13 | Night mode 14 | 15 | Enable/disable night mode within the application 16 | 17 | 18 | 19 | false 20 | Default window maximized behaviour 21 | 22 | 23 | 24 | 25 | '~/.gnupg/' 26 | GPG keys location 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /data/icons/hicolor/16x16/apps/com.github.bilelmoussaoui.Authenticator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilelmoussaoui/Authenticator/af7aed5c6c57f82db02a71ea3cf9ede4a0bcf76b/data/icons/hicolor/16x16/apps/com.github.bilelmoussaoui.Authenticator.png -------------------------------------------------------------------------------- /data/icons/hicolor/22x22/apps/com.github.bilelmoussaoui.Authenticator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilelmoussaoui/Authenticator/af7aed5c6c57f82db02a71ea3cf9ede4a0bcf76b/data/icons/hicolor/22x22/apps/com.github.bilelmoussaoui.Authenticator.png -------------------------------------------------------------------------------- /data/icons/hicolor/24x24/apps/com.github.bilelmoussaoui.Authenticator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilelmoussaoui/Authenticator/af7aed5c6c57f82db02a71ea3cf9ede4a0bcf76b/data/icons/hicolor/24x24/apps/com.github.bilelmoussaoui.Authenticator.png -------------------------------------------------------------------------------- /data/icons/hicolor/256x256/apps/com.github.bilelmoussaoui.Authenticator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilelmoussaoui/Authenticator/af7aed5c6c57f82db02a71ea3cf9ede4a0bcf76b/data/icons/hicolor/256x256/apps/com.github.bilelmoussaoui.Authenticator.png -------------------------------------------------------------------------------- /data/icons/hicolor/32x32/apps/com.github.bilelmoussaoui.Authenticator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilelmoussaoui/Authenticator/af7aed5c6c57f82db02a71ea3cf9ede4a0bcf76b/data/icons/hicolor/32x32/apps/com.github.bilelmoussaoui.Authenticator.png -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/amazon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/com.github.bilelmoussaoui.Authenticator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilelmoussaoui/Authenticator/af7aed5c6c57f82db02a71ea3cf9ede4a0bcf76b/data/icons/hicolor/48x48/apps/com.github.bilelmoussaoui.Authenticator.png -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/discord.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/drive.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/dropbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/gmail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/nextcloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/owncloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/skype.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/slack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/steam.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/teamviewer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/twitch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /data/icons/hicolor/48x48/apps/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /data/icons/hicolor/symbolic/apps/com.github.bilelmoussaoui.Authenticator-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 37 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 55 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /data/icons/meson.build: -------------------------------------------------------------------------------- 1 | icon_themes = ['hicolor'] 2 | sizes = ['16x16', '22x22', '24x24', '32x32', '48x48', '256x256', 'scalable'] 3 | foreach theme : icon_themes 4 | foreach size: sizes 5 | extension = '.png' 6 | if size == 'scalable' 7 | extension = '.svg' 8 | endif 9 | icon_dir = join_paths(theme, size , 'apps') 10 | icon_name = join_paths(icon_dir, meson.project_name() + extension) 11 | dest = join_paths(get_option('prefix'), 'share/icons', icon_dir) 12 | install_data(icon_name, install_dir: dest) 13 | endforeach 14 | symbolic_dir = join_paths(theme, 'symbolic/apps/') 15 | symbolic_icon = join_paths(symbolic_dir, meson.project_name() + '-symbolic.svg') 16 | dest_symbolic = join_paths(get_option('prefix'), 'share/icons', symbolic_dir) 17 | install_data(symbolic_icon, 18 | install_dir: dest_symbolic) 19 | endforeach 20 | 21 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | 2 | message('TODO: Making a list of icons') 3 | subdir('icons') 4 | 5 | message('TODO: Compiling resources') 6 | 7 | gnome.compile_resources( 8 | meson.project_name(), 9 | meson.project_name() + '.gresource.xml', 10 | gresource_bundle: true, 11 | source_dir: '.', 12 | install_dir: DATA_DIR, 13 | install: true 14 | ) 15 | 16 | message('Compiling schemas') 17 | install_data( 18 | meson.project_name() + '.gschema.xml', 19 | install_dir : join_paths(get_option('prefix'),get_option('datadir'), 'glib-2.0/schemas') 20 | ) 21 | 22 | i18n.merge_file( 23 | output: meson.project_name() + '.desktop', 24 | input: meson.project_name() + '.desktop.in', 25 | po_dir: join_paths(meson.source_root(), 'po'), 26 | type: 'desktop', 27 | install: true, 28 | install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'applications') 29 | ) 30 | 31 | i18n.merge_file( 32 | output: meson.project_name() + '.appdata.xml', 33 | input: meson.project_name() + '.appdata.xml.in', 34 | po_dir: join_paths(meson.source_root(), 'po'), 35 | install: true, 36 | install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'appdata') 37 | ) 38 | 39 | 40 | 41 | desktop_file_validate = find_program('desktop-file-validate', required:false) 42 | 43 | if desktop_file_validate.found() 44 | test ( 45 | 'Validate desktop file', 46 | desktop_file_validate, 47 | args: join_paths(meson.current_build_dir (), meson.project_name() + '.desktop') 48 | ) 49 | endif 50 | 51 | appstreamcli = find_program('appstream-util', required:false) 52 | 53 | if appstreamcli.found() 54 | test ( 55 | 'Validate appdata file', 56 | appstreamcli, 57 | args: ['validate-relax', join_paths(meson.current_build_dir (), meson.project_name() + '.appdata.xml')] 58 | ) 59 | endif 60 | -------------------------------------------------------------------------------- /data/screenshots/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilelmoussaoui/Authenticator/af7aed5c6c57f82db02a71ea3cf9ede4a0bcf76b/data/screenshots/screenshot1.png -------------------------------------------------------------------------------- /data/style.css: -------------------------------------------------------------------------------- 1 | .accounts-widget{ 2 | margin-top: 0px; 3 | } 4 | .accounts-container { 5 | margin-left: 32px; 6 | margin-right: 32px; 7 | } 8 | 9 | .accounts-list{ 10 | background-color: @theme_bg_color; 11 | } 12 | .account-row, 13 | .settings-box{ 14 | padding: 6px; 15 | border: 1px solid mix(@theme_base_color,@theme_fg_color,0.3); 16 | background-color: mix(@theme_base_color,@theme_bg_color,0.3); 17 | margin: 6px; 18 | } 19 | .account-row:selected GtkImage { 20 | color: mix(@theme_base_color,@theme_fg_color,0.3); 21 | } 22 | 23 | .account-row-checkbtn{ 24 | padding-left: 6px; 25 | } 26 | 27 | .provider-lbl, 28 | .gpg-key-lbl{ 29 | font-size: 18px; 30 | font-weight: bold; 31 | padding: 3px 6px; 32 | } 33 | .application-name { 34 | font-size: 14px; 35 | color: @theme_fg_color; 36 | font-style: italic; 37 | } 38 | 39 | .account-name { 40 | font-weight: bold; 41 | font-size: 16px; 42 | } 43 | 44 | .token-label{ 45 | font-size: 14px; 46 | } 47 | .counter-label { 48 | font-size: 12px; 49 | } 50 | .settings-box-main-label{ 51 | font-size: 14px; 52 | padding: 3px 6px; 53 | } 54 | 55 | .settings-box-secondary-label { 56 | font-size:12px; 57 | font-style:italic; 58 | color: mix(@theme_base_color,@theme_fg_color,0.6); 59 | padding: 3px 6px; 60 | } 61 | .settings-main-container{ 62 | margin-left: 36px; 63 | margin-right: 36px; 64 | } 65 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('com.github.bilelmoussaoui.Authenticator', 2 | version: '0.2.4', 3 | meson_version: '>= 0.42', 4 | license: 'GPL+-3.0', 5 | default_options: ['prefix=/usr'] 6 | ) 7 | python = import('python3') 8 | gnome = import('gnome') 9 | i18n = import('i18n') 10 | 11 | message('Looking for dependencies') 12 | dependency('glib-2.0') 13 | dependency('gobject-2.0') 14 | dependency('gobject-introspection-1.0') 15 | dependency('gtk+-3.0', version : '>=3.16') 16 | 17 | 18 | use_zbar = false 19 | if get_option('enable-zbar') 20 | use_zebar = true 21 | endif 22 | 23 | python_bin = python.find_python() 24 | if not python_bin.found() 25 | error('No valid python3 binary found') 26 | else 27 | message('Found python3 binary') 28 | endif 29 | 30 | PYTHON_DIR = join_paths(get_option('prefix'), python.sysconfig_path('purelib')) 31 | DATA_DIR = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name()) 32 | LOCALE_DIR = join_paths(get_option('prefix'), get_option('localedir')) 33 | LIB_DIR = join_paths(get_option('prefix'), get_option('libdir'), meson.project_name()) 34 | SERVICES_DIR = join_paths(get_option('datadir'), 'dbus-1', 'services') 35 | SEARCH_PROVIDER_DIR = join_paths(get_option('datadir'), 'gnome-shell', 'search-providers') 36 | LIBEXEC_DIR = join_paths(get_option('prefix'), get_option('libexecdir')) 37 | 38 | subproject('libgd', 39 | default_options: [ 40 | 'with-introspection=true', 41 | 'with-notification=true', 42 | 'static=false', 43 | 'pkgdatadir=' + DATA_DIR, 44 | 'pkglibdir=' + LIB_DIR 45 | ] 46 | ) 47 | 48 | 49 | 50 | # Configuration params 51 | conf = configuration_data() 52 | conf.set('DATA_DIR', DATA_DIR) 53 | conf.set('LOCALE_DIR', LOCALE_DIR) 54 | conf.set('PYTHON_DIR', PYTHON_DIR) 55 | conf.set('PYTHON_EXEC_DIR', join_paths(get_option('prefix'), python.sysconfig_path('stdlib'))) 56 | conf.set('VERSION', meson.project_version()) 57 | conf.set('ENABLE_QRSCANNER', use_zbar) 58 | conf.set('LIB_DIR', LIB_DIR) 59 | conf.set('libexecdir', LIBEXEC_DIR) 60 | conf.set('PYTHON', python_bin.path()) 61 | 62 | subdir('data') 63 | subdir('po') 64 | subdir('search-provider') 65 | 66 | configure_file( 67 | input: 'authenticator.py.in', 68 | output: 'authenticator', 69 | configuration: conf, 70 | install_dir: join_paths(get_option('prefix'), get_option('bindir')) 71 | ) 72 | 73 | configure_file( 74 | input: 'Authenticator/widgets/about.py.in', 75 | output: 'about.py', 76 | configuration: conf, 77 | install_dir : join_paths(PYTHON_DIR, 'Authenticator/widgets') 78 | ) 79 | 80 | 81 | install_subdir( 82 | 'Authenticator', 83 | install_dir: PYTHON_DIR, 84 | exclude_files: 'widgets/about.py.in' 85 | ) 86 | 87 | meson.add_install_script('tools/build/meson_post_install.py') 88 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('enable-zbar', type : 'boolean', value : true, description : 'enable QR Scanner using zbar') 2 | -------------------------------------------------------------------------------- /po/Authenticator.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the Authenticator package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: Authenticator\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2018-09-10 17:56+0200\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: Authenticator/application.py:38 Authenticator/widgets/about.py.in:44 21 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:6 22 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:3 23 | msgid "Authenticator" 24 | msgstr "" 25 | 26 | #. Night mode action 27 | #: Authenticator/application.py:81 28 | msgid "Night Mode" 29 | msgstr "" 30 | 31 | #: Authenticator/application.py:84 32 | msgid "About" 33 | msgstr "" 34 | 35 | #: Authenticator/application.py:85 36 | msgid "Quit" 37 | msgstr "" 38 | 39 | #: Authenticator/widgets/accounts/add.py:49 40 | #: Authenticator/widgets/headerbar.py:100 41 | msgid "Add a new account" 42 | msgstr "" 43 | 44 | #: Authenticator/widgets/accounts/add.py:53 45 | msgid "Add" 46 | msgstr "" 47 | 48 | #: Authenticator/widgets/accounts/add.py:62 49 | msgid "Scan QR code" 50 | msgstr "" 51 | 52 | #: Authenticator/widgets/accounts/add.py:69 53 | #: Authenticator/widgets/accounts/edit.py:58 54 | msgid "Close" 55 | msgstr "" 56 | 57 | #: Authenticator/widgets/accounts/add.py:160 58 | msgid "Provider" 59 | msgstr "" 60 | 61 | #: Authenticator/widgets/accounts/add.py:170 62 | msgid "Account name" 63 | msgstr "" 64 | 65 | #: Authenticator/widgets/accounts/add.py:176 66 | msgid "Secret token" 67 | msgstr "" 68 | 69 | #: Authenticator/widgets/accounts/add.py:258 70 | msgid "Invalid QR code" 71 | msgstr "" 72 | 73 | #: Authenticator/widgets/accounts/edit.py:47 74 | msgid "Edit {} - {}" 75 | msgstr "" 76 | 77 | #: Authenticator/widgets/accounts/edit.py:52 78 | msgid "Save" 79 | msgstr "" 80 | 81 | #: Authenticator/widgets/accounts/list.py:163 82 | msgid "The One-Time Passwords expires in {} seconds" 83 | msgstr "" 84 | 85 | #. Label 86 | #: Authenticator/widgets/accounts/list.py:286 87 | msgid "There are no accounts yet…" 88 | msgstr "" 89 | 90 | #: Authenticator/widgets/accounts/row.py:58 91 | msgid "Copy" 92 | msgstr "" 93 | 94 | #: Authenticator/widgets/accounts/row.py:59 95 | msgid "Edit" 96 | msgstr "" 97 | 98 | #: Authenticator/widgets/accounts/row.py:149 99 | msgid "Couldn't generate the secret code" 100 | msgstr "" 101 | 102 | #: Authenticator/widgets/about.py.in:45 103 | msgid "translator-credits" 104 | msgstr "" 105 | 106 | #: Authenticator/widgets/about.py.in:47 107 | msgid "Two-factor authentication code generator." 108 | msgstr "" 109 | 110 | #: Authenticator/widgets/actions_bar.py:52 111 | msgid "Delete" 112 | msgstr "" 113 | 114 | #: Authenticator/widgets/headerbar.py:98 115 | msgid "Search" 116 | msgstr "" 117 | 118 | #: Authenticator/widgets/headerbar.py:102 119 | msgid "Settings" 120 | msgstr "" 121 | 122 | #: Authenticator/widgets/headerbar.py:104 123 | msgid "Selection mode" 124 | msgstr "" 125 | 126 | #: Authenticator/widgets/headerbar.py:106 127 | msgid "Cancel" 128 | msgstr "" 129 | 130 | #: Authenticator/widgets/headerbar.py:181 131 | msgid "Click on items to select them" 132 | msgstr "" 133 | 134 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:7 135 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:5 136 | msgid "Two-factor authentication code generator" 137 | msgstr "" 138 | 139 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:9 140 | msgid "" 141 | "Simple application that generates a two-factor authentication code, created " 142 | "for GNOME." 143 | msgstr "" 144 | 145 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:10 146 | msgid "Features:" 147 | msgstr "" 148 | 149 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:12 150 | msgid "QR code scanner" 151 | msgstr "" 152 | 153 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:13 154 | msgid "Beautiful UI" 155 | msgstr "" 156 | 157 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:14 158 | msgid "Huge database of (290+) websites/applications" 159 | msgstr "" 160 | 161 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:34 162 | msgid "GNOME Shell Search provider" 163 | msgstr "" 164 | 165 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:35 166 | msgid "Codes expire simultaneously #91" 167 | msgstr "" 168 | 169 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:42 170 | msgid "Revamped main window to more closely follow the GNOME HIG" 171 | msgstr "" 172 | 173 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:43 174 | msgid "Revamped add a new account window to make it easier to use" 175 | msgstr "" 176 | 177 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:44 178 | msgid "" 179 | "Possibility to add an account from a provider not listed in the shipped " 180 | "database" 181 | msgstr "" 182 | 183 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:45 184 | msgid "Possibilty to edit an account" 185 | msgstr "" 186 | 187 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:46 188 | msgid "One Time Password now visible by default" 189 | msgstr "" 190 | 191 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:53 192 | msgid "Fix python-dbus by using GDbus instead" 193 | msgstr "" 194 | 195 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:60 196 | msgid "Fix the QRScanner on GNOME Shell" 197 | msgstr "" 198 | 199 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:61 200 | msgid "Add a new entry for the account's username" 201 | msgstr "" 202 | 203 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:62 204 | msgid "Updated database of supported accounts" 205 | msgstr "" 206 | 207 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:69 208 | msgid "HOTFIX: App not running in DE other than GNOME" 209 | msgstr "" 210 | 211 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:76 212 | msgid "Rename project to Authenticator" 213 | msgstr "" 214 | 215 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:77 216 | msgid "Cleaner code base" 217 | msgstr "" 218 | 219 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:78 220 | msgid "Faster startup" 221 | msgstr "" 222 | 223 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:79 224 | msgid "Remove unneeded features" 225 | msgstr "" 226 | 227 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:80 228 | msgid "Switch to pyzbar instead of zbarlight" 229 | msgstr "" 230 | 231 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:81 232 | msgid "Flatpak package" 233 | msgstr "" 234 | 235 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:88 236 | msgid "Bilal Elmoussaoui" 237 | msgstr "" 238 | 239 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:4 240 | msgid "Two-factor authentication" 241 | msgstr "" 242 | 243 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:10 244 | msgid "Gnome;GTK;Verification;" 245 | msgstr "" 246 | 247 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:11 248 | msgid "com.github.bilelmoussaoui.Authenticator" 249 | msgstr "" 250 | 251 | #: authenticator.py.in:49 252 | msgid "Start in debug mode" 253 | msgstr "" 254 | 255 | #: authenticator.py.in:51 256 | msgid "Authenticator version number" 257 | msgstr "" 258 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | fr 2 | sv 3 | id 4 | hu 5 | nl 6 | ar 7 | da 8 | nb_NO 9 | de 10 | sr 11 | sr@latin 12 | pl 13 | nl_BE 14 | es 15 | -------------------------------------------------------------------------------- /po/POTFILES.in: -------------------------------------------------------------------------------- 1 | Authenticator/application.py 2 | Authenticator/widgets/accounts/add.py 3 | Authenticator/widgets/accounts/edit.py 4 | Authenticator/widgets/accounts/list.py 5 | Authenticator/widgets/accounts/row.py 6 | Authenticator/widgets/about.py.in 7 | Authenticator/widgets/actions_bar.py 8 | Authenticator/widgets/headerbar.py 9 | Authenticator/widgets/search_bar.py 10 | Authenticator/widgets/window.py 11 | data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in 12 | data/com.github.bilelmoussaoui.Authenticator.desktop.in 13 | authenticator.py.in 14 | -------------------------------------------------------------------------------- /po/ar.po: -------------------------------------------------------------------------------- 1 | # Arabic translations for Authenticator package. 2 | # Copyright (C) 2018 THE Authenticator'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the Authenticator package. 4 | # Automatically generated, 2018. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: Authenticator\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2018-09-10 17:56+0200\n" 11 | "PO-Revision-Date: 2018-08-21 09:34+0000\n" 12 | "Last-Translator: ButterflyOfFire \n" 13 | "Language-Team: Arabic \n" 15 | "Language: ar\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " 20 | "&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" 21 | "X-Generator: Weblate 3.2-dev\n" 22 | 23 | #: Authenticator/application.py:38 Authenticator/widgets/about.py.in:44 24 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:6 25 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:3 26 | msgid "Authenticator" 27 | msgstr "الموثق" 28 | 29 | #. Night mode action 30 | #: Authenticator/application.py:81 31 | msgid "Night Mode" 32 | msgstr "الوضع الليلي" 33 | 34 | #: Authenticator/application.py:84 35 | msgid "About" 36 | msgstr "عن" 37 | 38 | #: Authenticator/application.py:85 39 | msgid "Quit" 40 | msgstr "خروج" 41 | 42 | #: Authenticator/widgets/accounts/add.py:49 43 | #: Authenticator/widgets/headerbar.py:100 44 | msgid "Add a new account" 45 | msgstr "اضافة حساب جديد" 46 | 47 | #: Authenticator/widgets/accounts/add.py:53 48 | msgid "Add" 49 | msgstr "اضافة" 50 | 51 | #: Authenticator/widgets/accounts/add.py:62 52 | msgid "Scan QR code" 53 | msgstr "امسح كود QR" 54 | 55 | #: Authenticator/widgets/accounts/add.py:69 56 | #: Authenticator/widgets/accounts/edit.py:58 57 | msgid "Close" 58 | msgstr "اغلاق" 59 | 60 | #: Authenticator/widgets/accounts/add.py:160 61 | msgid "Provider" 62 | msgstr "" 63 | 64 | #: Authenticator/widgets/accounts/add.py:170 65 | msgid "Account name" 66 | msgstr "اسم الحساب" 67 | 68 | #: Authenticator/widgets/accounts/add.py:176 69 | #, fuzzy 70 | msgid "Secret token" 71 | msgstr "الرمز السري" 72 | 73 | #: Authenticator/widgets/accounts/add.py:258 74 | msgid "Invalid QR code" 75 | msgstr "رمز QR خاطئ" 76 | 77 | #: Authenticator/widgets/accounts/edit.py:47 78 | msgid "Edit {} - {}" 79 | msgstr "" 80 | 81 | #: Authenticator/widgets/accounts/edit.py:52 82 | msgid "Save" 83 | msgstr "" 84 | 85 | #: Authenticator/widgets/accounts/list.py:163 86 | msgid "The One-Time Passwords expires in {} seconds" 87 | msgstr "" 88 | 89 | #. Label 90 | #: Authenticator/widgets/accounts/list.py:286 91 | #, fuzzy 92 | msgid "There are no accounts yet…" 93 | msgstr "ليس هناك حساب بعد …" 94 | 95 | #: Authenticator/widgets/accounts/row.py:58 96 | msgid "Copy" 97 | msgstr "نسخ" 98 | 99 | #: Authenticator/widgets/accounts/row.py:59 100 | msgid "Edit" 101 | msgstr "" 102 | 103 | #: Authenticator/widgets/accounts/row.py:149 104 | msgid "Couldn't generate the secret code" 105 | msgstr "لم يتم توليد المفتاح السري" 106 | 107 | #: Authenticator/widgets/about.py.in:45 108 | msgid "translator-credits" 109 | msgstr "translator-credits" 110 | 111 | #: Authenticator/widgets/about.py.in:47 112 | #, fuzzy 113 | msgid "Two-factor authentication code generator." 114 | msgstr "كود توليد التوثيق الثنائي." 115 | 116 | #: Authenticator/widgets/actions_bar.py:52 117 | msgid "Delete" 118 | msgstr "حذف" 119 | 120 | #: Authenticator/widgets/headerbar.py:98 121 | msgid "Search" 122 | msgstr "بحث" 123 | 124 | #: Authenticator/widgets/headerbar.py:102 125 | msgid "Settings" 126 | msgstr "الاعدادات" 127 | 128 | #: Authenticator/widgets/headerbar.py:104 129 | msgid "Selection mode" 130 | msgstr "وضع الاختيار" 131 | 132 | #: Authenticator/widgets/headerbar.py:106 133 | msgid "Cancel" 134 | msgstr "الغاء" 135 | 136 | #: Authenticator/widgets/headerbar.py:181 137 | msgid "Click on items to select them" 138 | msgstr "اضغط على العتاصر ليتم اختيارهم" 139 | 140 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:7 141 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:5 142 | #, fuzzy 143 | msgid "Two-factor authentication code generator" 144 | msgstr "كود توليد التوثيق الثنائي." 145 | 146 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:9 147 | #, fuzzy 148 | msgid "" 149 | "Simple application that generates a two-factor authentication code, created " 150 | "for GNOME." 151 | msgstr "برنامج بسيط يولد كود توثيق ثنائي، صمم لنظام Gnome" 152 | 153 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:10 154 | msgid "Features:" 155 | msgstr "" 156 | 157 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:12 158 | msgid "QR code scanner" 159 | msgstr "" 160 | 161 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:13 162 | msgid "Beautiful UI" 163 | msgstr "" 164 | 165 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:14 166 | msgid "Huge database of (290+) websites/applications" 167 | msgstr "" 168 | 169 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:34 170 | msgid "GNOME Shell Search provider" 171 | msgstr "" 172 | 173 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:35 174 | msgid "Codes expire simultaneously #91" 175 | msgstr "" 176 | 177 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:42 178 | msgid "Revamped main window to more closely follow the GNOME HIG" 179 | msgstr "" 180 | 181 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:43 182 | msgid "Revamped add a new account window to make it easier to use" 183 | msgstr "" 184 | 185 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:44 186 | msgid "" 187 | "Possibility to add an account from a provider not listed in the shipped " 188 | "database" 189 | msgstr "" 190 | 191 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:45 192 | msgid "Possibilty to edit an account" 193 | msgstr "" 194 | 195 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:46 196 | msgid "One Time Password now visible by default" 197 | msgstr "" 198 | 199 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:53 200 | msgid "Fix python-dbus by using GDbus instead" 201 | msgstr "" 202 | 203 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:60 204 | msgid "Fix the QRScanner on GNOME Shell" 205 | msgstr "" 206 | 207 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:61 208 | msgid "Add a new entry for the account's username" 209 | msgstr "" 210 | 211 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:62 212 | msgid "Updated database of supported accounts" 213 | msgstr "" 214 | 215 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:69 216 | msgid "HOTFIX: App not running in DE other than GNOME" 217 | msgstr "" 218 | 219 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:76 220 | #, fuzzy 221 | msgid "Rename project to Authenticator" 222 | msgstr "عن الموثق" 223 | 224 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:77 225 | msgid "Cleaner code base" 226 | msgstr "" 227 | 228 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:78 229 | msgid "Faster startup" 230 | msgstr "" 231 | 232 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:79 233 | msgid "Remove unneeded features" 234 | msgstr "" 235 | 236 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:80 237 | msgid "Switch to pyzbar instead of zbarlight" 238 | msgstr "" 239 | 240 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:81 241 | msgid "Flatpak package" 242 | msgstr "" 243 | 244 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:88 245 | msgid "Bilal Elmoussaoui" 246 | msgstr "" 247 | 248 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:4 249 | #, fuzzy 250 | msgid "Two-factor authentication" 251 | msgstr "التوثيق الثنائي" 252 | 253 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:10 254 | msgid "Gnome;GTK;Verification;" 255 | msgstr "" 256 | 257 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:11 258 | msgid "com.github.bilelmoussaoui.Authenticator" 259 | msgstr "com.github.bilelmoussaoui.Authenticator" 260 | 261 | #: authenticator.py.in:49 262 | msgid "Start in debug mode" 263 | msgstr "البدء في وضع التصحيح" 264 | 265 | #: authenticator.py.in:51 266 | msgid "Authenticator version number" 267 | msgstr "رقم نسخة الموثق" 268 | 269 | #~ msgid "Next" 270 | #~ msgstr "التالي" 271 | 272 | #~ msgid "Back" 273 | #~ msgstr "رجوع" 274 | 275 | #~ msgid "Undo" 276 | #~ msgstr "تراجع" 277 | 278 | #~ msgid "Two Factor Authentication code generator" 279 | #~ msgstr "كود توليد التوثيق الثنائي" 280 | 281 | #~ msgid "Two-Factor Authentication code generator" 282 | #~ msgstr "كود توليد التوثيق الثنائي" 283 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n.gettext('Authenticator', preset: 'glib') 2 | -------------------------------------------------------------------------------- /po/nl_BE.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the Authenticator package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: Authenticator\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2018-09-10 17:56+0200\n" 11 | "PO-Revision-Date: 2018-04-23 23:37+0000\n" 12 | "Last-Translator: Nathan Follens \n" 13 | "Language-Team: Flemish \n" 15 | "Language: nl_BE\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 20 | "X-Generator: Weblate 3.0-dev\n" 21 | 22 | #: Authenticator/application.py:38 Authenticator/widgets/about.py.in:44 23 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:6 24 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:3 25 | msgid "Authenticator" 26 | msgstr "Authenticator" 27 | 28 | #. Night mode action 29 | #: Authenticator/application.py:81 30 | msgid "Night Mode" 31 | msgstr "Nachtmodus" 32 | 33 | #: Authenticator/application.py:84 34 | msgid "About" 35 | msgstr "Over" 36 | 37 | #: Authenticator/application.py:85 38 | msgid "Quit" 39 | msgstr "Afsluiten" 40 | 41 | #: Authenticator/widgets/accounts/add.py:49 42 | #: Authenticator/widgets/headerbar.py:100 43 | msgid "Add a new account" 44 | msgstr "Nieuwen account toevoegen" 45 | 46 | #: Authenticator/widgets/accounts/add.py:53 47 | msgid "Add" 48 | msgstr "Toevoegen" 49 | 50 | #: Authenticator/widgets/accounts/add.py:62 51 | msgid "Scan QR code" 52 | msgstr "QR-code scannen" 53 | 54 | #: Authenticator/widgets/accounts/add.py:69 55 | #: Authenticator/widgets/accounts/edit.py:58 56 | msgid "Close" 57 | msgstr "Sluiten" 58 | 59 | #: Authenticator/widgets/accounts/add.py:160 60 | msgid "Provider" 61 | msgstr "" 62 | 63 | #: Authenticator/widgets/accounts/add.py:170 64 | msgid "Account name" 65 | msgstr "Accountnaam" 66 | 67 | #: Authenticator/widgets/accounts/add.py:176 68 | #, fuzzy 69 | msgid "Secret token" 70 | msgstr "Geheim token" 71 | 72 | #: Authenticator/widgets/accounts/add.py:258 73 | msgid "Invalid QR code" 74 | msgstr "Ongeldige QR-code" 75 | 76 | #: Authenticator/widgets/accounts/edit.py:47 77 | msgid "Edit {} - {}" 78 | msgstr "" 79 | 80 | #: Authenticator/widgets/accounts/edit.py:52 81 | msgid "Save" 82 | msgstr "" 83 | 84 | #: Authenticator/widgets/accounts/list.py:163 85 | msgid "The One-Time Passwords expires in {} seconds" 86 | msgstr "" 87 | 88 | #. Label 89 | #: Authenticator/widgets/accounts/list.py:286 90 | #, fuzzy 91 | msgid "There are no accounts yet…" 92 | msgstr "Der zijn nog geen accounts…" 93 | 94 | #: Authenticator/widgets/accounts/row.py:58 95 | msgid "Copy" 96 | msgstr "Kopiëren" 97 | 98 | #: Authenticator/widgets/accounts/row.py:59 99 | msgid "Edit" 100 | msgstr "" 101 | 102 | #: Authenticator/widgets/accounts/row.py:149 103 | msgid "Couldn't generate the secret code" 104 | msgstr "Aanmaken van geheime code mislukt" 105 | 106 | #: Authenticator/widgets/about.py.in:45 107 | msgid "translator-credits" 108 | msgstr "Nathan Follens" 109 | 110 | #: Authenticator/widgets/about.py.in:47 111 | #, fuzzy 112 | msgid "Two-factor authentication code generator." 113 | msgstr "Generator voor tweefactorauthenticatiecodes." 114 | 115 | #: Authenticator/widgets/actions_bar.py:52 116 | msgid "Delete" 117 | msgstr "Verwijderen" 118 | 119 | #: Authenticator/widgets/headerbar.py:98 120 | msgid "Search" 121 | msgstr "Zoeken" 122 | 123 | #: Authenticator/widgets/headerbar.py:102 124 | msgid "Settings" 125 | msgstr "Instellingen" 126 | 127 | #: Authenticator/widgets/headerbar.py:104 128 | msgid "Selection mode" 129 | msgstr "Selectiemodus" 130 | 131 | #: Authenticator/widgets/headerbar.py:106 132 | msgid "Cancel" 133 | msgstr "Annuleren" 134 | 135 | #: Authenticator/widgets/headerbar.py:181 136 | msgid "Click on items to select them" 137 | msgstr "Klikt op items voor ze te selecteren" 138 | 139 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:7 140 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:5 141 | #, fuzzy 142 | msgid "Two-factor authentication code generator" 143 | msgstr "Generator voor tweefactorauthenticatiecodes." 144 | 145 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:9 146 | #, fuzzy 147 | msgid "" 148 | "Simple application that generates a two-factor authentication code, created " 149 | "for GNOME." 150 | msgstr "" 151 | "Eenvoudige toepassing voor het genereren van tweefactorauthenticatiecodes, " 152 | "gemaakt voor GNOME" 153 | 154 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:10 155 | msgid "Features:" 156 | msgstr "" 157 | 158 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:12 159 | msgid "QR code scanner" 160 | msgstr "" 161 | 162 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:13 163 | msgid "Beautiful UI" 164 | msgstr "" 165 | 166 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:14 167 | msgid "Huge database of (290+) websites/applications" 168 | msgstr "" 169 | 170 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:34 171 | msgid "GNOME Shell Search provider" 172 | msgstr "" 173 | 174 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:35 175 | msgid "Codes expire simultaneously #91" 176 | msgstr "" 177 | 178 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:42 179 | msgid "Revamped main window to more closely follow the GNOME HIG" 180 | msgstr "" 181 | 182 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:43 183 | msgid "Revamped add a new account window to make it easier to use" 184 | msgstr "" 185 | 186 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:44 187 | msgid "" 188 | "Possibility to add an account from a provider not listed in the shipped " 189 | "database" 190 | msgstr "" 191 | 192 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:45 193 | msgid "Possibilty to edit an account" 194 | msgstr "" 195 | 196 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:46 197 | msgid "One Time Password now visible by default" 198 | msgstr "" 199 | 200 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:53 201 | msgid "Fix python-dbus by using GDbus instead" 202 | msgstr "" 203 | 204 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:60 205 | msgid "Fix the QRScanner on GNOME Shell" 206 | msgstr "" 207 | 208 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:61 209 | msgid "Add a new entry for the account's username" 210 | msgstr "" 211 | 212 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:62 213 | msgid "Updated database of supported accounts" 214 | msgstr "" 215 | 216 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:69 217 | msgid "HOTFIX: App not running in DE other than GNOME" 218 | msgstr "" 219 | 220 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:76 221 | #, fuzzy 222 | msgid "Rename project to Authenticator" 223 | msgstr "Over Authenticator" 224 | 225 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:77 226 | msgid "Cleaner code base" 227 | msgstr "" 228 | 229 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:78 230 | msgid "Faster startup" 231 | msgstr "" 232 | 233 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:79 234 | msgid "Remove unneeded features" 235 | msgstr "" 236 | 237 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:80 238 | msgid "Switch to pyzbar instead of zbarlight" 239 | msgstr "" 240 | 241 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:81 242 | msgid "Flatpak package" 243 | msgstr "" 244 | 245 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:88 246 | msgid "Bilal Elmoussaoui" 247 | msgstr "" 248 | 249 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:4 250 | #, fuzzy 251 | msgid "Two-factor authentication" 252 | msgstr "Tweefactorauthenticatie" 253 | 254 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:10 255 | msgid "Gnome;GTK;Verification;" 256 | msgstr "Gnome;GTK;Verification;Verificatie;" 257 | 258 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:11 259 | msgid "com.github.bilelmoussaoui.Authenticator" 260 | msgstr "com.github.bilelmoussaoui.Authenticator" 261 | 262 | #: authenticator.py.in:49 263 | msgid "Start in debug mode" 264 | msgstr "Starten in debugmodus" 265 | 266 | #: authenticator.py.in:51 267 | msgid "Authenticator version number" 268 | msgstr "Versienummer van Authenticator" 269 | 270 | #~ msgid "Next" 271 | #~ msgstr "Volgende" 272 | 273 | #~ msgid "Back" 274 | #~ msgstr "Terug" 275 | 276 | #~ msgid "Undo" 277 | #~ msgstr "Ongedaan maken" 278 | 279 | #~ msgid "Two Factor Authentication code generator" 280 | #~ msgstr "Generator voor tweefactorauthenticatiecodes" 281 | 282 | #~ msgid "Two-Factor Authentication code generator" 283 | #~ msgstr "Generator voor tweefactorauthenticatiecodes" 284 | -------------------------------------------------------------------------------- /po/pl.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the Authenticator package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: Authenticator\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2018-09-10 17:56+0200\n" 11 | "PO-Revision-Date: 2018-07-26 19:37+0000\n" 12 | "Last-Translator: WaldiS \n" 13 | "Language-Team: Polish \n" 15 | "Language: pl\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " 20 | "|| n%100>=20) ? 1 : 2;\n" 21 | "X-Generator: Weblate 3.1-dev\n" 22 | 23 | #: Authenticator/application.py:38 Authenticator/widgets/about.py.in:44 24 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:6 25 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:3 26 | msgid "Authenticator" 27 | msgstr "Wystawca uwierzytelnienia" 28 | 29 | #. Night mode action 30 | #: Authenticator/application.py:81 31 | msgid "Night Mode" 32 | msgstr "Tryb nocny" 33 | 34 | #: Authenticator/application.py:84 35 | msgid "About" 36 | msgstr "Informacje" 37 | 38 | #: Authenticator/application.py:85 39 | msgid "Quit" 40 | msgstr "Zakończ" 41 | 42 | #: Authenticator/widgets/accounts/add.py:49 43 | #: Authenticator/widgets/headerbar.py:100 44 | msgid "Add a new account" 45 | msgstr "Dodaj nowe konto" 46 | 47 | #: Authenticator/widgets/accounts/add.py:53 48 | msgid "Add" 49 | msgstr "Dodaj" 50 | 51 | #: Authenticator/widgets/accounts/add.py:62 52 | msgid "Scan QR code" 53 | msgstr "Skanuj kod QR" 54 | 55 | #: Authenticator/widgets/accounts/add.py:69 56 | #: Authenticator/widgets/accounts/edit.py:58 57 | msgid "Close" 58 | msgstr "Zamknij" 59 | 60 | #: Authenticator/widgets/accounts/add.py:160 61 | msgid "Provider" 62 | msgstr "" 63 | 64 | #: Authenticator/widgets/accounts/add.py:170 65 | msgid "Account name" 66 | msgstr "Nazwa konta" 67 | 68 | #: Authenticator/widgets/accounts/add.py:176 69 | #, fuzzy 70 | msgid "Secret token" 71 | msgstr "Sekretny token" 72 | 73 | #: Authenticator/widgets/accounts/add.py:258 74 | msgid "Invalid QR code" 75 | msgstr "Nieprawidłowy kod QR" 76 | 77 | #: Authenticator/widgets/accounts/edit.py:47 78 | msgid "Edit {} - {}" 79 | msgstr "" 80 | 81 | #: Authenticator/widgets/accounts/edit.py:52 82 | msgid "Save" 83 | msgstr "" 84 | 85 | #: Authenticator/widgets/accounts/list.py:163 86 | msgid "The One-Time Passwords expires in {} seconds" 87 | msgstr "" 88 | 89 | #. Label 90 | #: Authenticator/widgets/accounts/list.py:286 91 | #, fuzzy 92 | msgid "There are no accounts yet…" 93 | msgstr "Nie dodano jeszcze żadnego konta..." 94 | 95 | #: Authenticator/widgets/accounts/row.py:58 96 | msgid "Copy" 97 | msgstr "Kopiuj" 98 | 99 | #: Authenticator/widgets/accounts/row.py:59 100 | msgid "Edit" 101 | msgstr "" 102 | 103 | #: Authenticator/widgets/accounts/row.py:149 104 | msgid "Couldn't generate the secret code" 105 | msgstr "Nie udało się wygenerować sekretnego kodu" 106 | 107 | #: Authenticator/widgets/about.py.in:45 108 | msgid "translator-credits" 109 | msgstr "tłumacz-uznanie" 110 | 111 | #: Authenticator/widgets/about.py.in:47 112 | #, fuzzy 113 | msgid "Two-factor authentication code generator." 114 | msgstr "Generator kodów uwierzytelniania dwuetapowego." 115 | 116 | #: Authenticator/widgets/actions_bar.py:52 117 | msgid "Delete" 118 | msgstr "Usuń" 119 | 120 | #: Authenticator/widgets/headerbar.py:98 121 | msgid "Search" 122 | msgstr "Szukaj" 123 | 124 | #: Authenticator/widgets/headerbar.py:102 125 | msgid "Settings" 126 | msgstr "Ustawienia" 127 | 128 | #: Authenticator/widgets/headerbar.py:104 129 | msgid "Selection mode" 130 | msgstr "Tryb zaznaczania" 131 | 132 | #: Authenticator/widgets/headerbar.py:106 133 | msgid "Cancel" 134 | msgstr "Anuluj" 135 | 136 | #: Authenticator/widgets/headerbar.py:181 137 | msgid "Click on items to select them" 138 | msgstr "Kliknij na elementy aby je wybrać" 139 | 140 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:7 141 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:5 142 | #, fuzzy 143 | msgid "Two-factor authentication code generator" 144 | msgstr "Generator kodów uwierzytelniania dwuetapowego." 145 | 146 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:9 147 | #, fuzzy 148 | msgid "" 149 | "Simple application that generates a two-factor authentication code, created " 150 | "for GNOME." 151 | msgstr "" 152 | "Prosta aplikacja generująca kody uwierzytelniania dwuetapowego, stworzona " 153 | "dla Gnome" 154 | 155 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:10 156 | msgid "Features:" 157 | msgstr "" 158 | 159 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:12 160 | msgid "QR code scanner" 161 | msgstr "" 162 | 163 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:13 164 | msgid "Beautiful UI" 165 | msgstr "" 166 | 167 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:14 168 | msgid "Huge database of (290+) websites/applications" 169 | msgstr "" 170 | 171 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:34 172 | msgid "GNOME Shell Search provider" 173 | msgstr "" 174 | 175 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:35 176 | msgid "Codes expire simultaneously #91" 177 | msgstr "" 178 | 179 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:42 180 | msgid "Revamped main window to more closely follow the GNOME HIG" 181 | msgstr "" 182 | 183 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:43 184 | msgid "Revamped add a new account window to make it easier to use" 185 | msgstr "" 186 | 187 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:44 188 | msgid "" 189 | "Possibility to add an account from a provider not listed in the shipped " 190 | "database" 191 | msgstr "" 192 | 193 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:45 194 | msgid "Possibilty to edit an account" 195 | msgstr "" 196 | 197 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:46 198 | msgid "One Time Password now visible by default" 199 | msgstr "" 200 | 201 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:53 202 | msgid "Fix python-dbus by using GDbus instead" 203 | msgstr "" 204 | 205 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:60 206 | msgid "Fix the QRScanner on GNOME Shell" 207 | msgstr "" 208 | 209 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:61 210 | msgid "Add a new entry for the account's username" 211 | msgstr "" 212 | 213 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:62 214 | msgid "Updated database of supported accounts" 215 | msgstr "" 216 | 217 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:69 218 | msgid "HOTFIX: App not running in DE other than GNOME" 219 | msgstr "" 220 | 221 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:76 222 | #, fuzzy 223 | msgid "Rename project to Authenticator" 224 | msgstr "O Authenticatorze" 225 | 226 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:77 227 | msgid "Cleaner code base" 228 | msgstr "" 229 | 230 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:78 231 | msgid "Faster startup" 232 | msgstr "" 233 | 234 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:79 235 | msgid "Remove unneeded features" 236 | msgstr "" 237 | 238 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:80 239 | msgid "Switch to pyzbar instead of zbarlight" 240 | msgstr "" 241 | 242 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:81 243 | msgid "Flatpak package" 244 | msgstr "" 245 | 246 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:88 247 | msgid "Bilal Elmoussaoui" 248 | msgstr "" 249 | 250 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:4 251 | #, fuzzy 252 | msgid "Two-factor authentication" 253 | msgstr "Uwierzytelnianie dwuetapowe" 254 | 255 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:10 256 | msgid "Gnome;GTK;Verification;" 257 | msgstr "Gnome;GTK;Weryfikacja;" 258 | 259 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:11 260 | msgid "com.github.bilelmoussaoui.Authenticator" 261 | msgstr "com.github.bilelmoussaoui.Authenticator" 262 | 263 | #: authenticator.py.in:49 264 | msgid "Start in debug mode" 265 | msgstr "Uruchom w trybie debugowania" 266 | 267 | #: authenticator.py.in:51 268 | msgid "Authenticator version number" 269 | msgstr "Wersja Authenticatora" 270 | 271 | #~ msgid "Next" 272 | #~ msgstr "Następny" 273 | 274 | #~ msgid "Back" 275 | #~ msgstr "Wstecz" 276 | 277 | #~ msgid "Undo" 278 | #~ msgstr "Cofnij" 279 | 280 | #~ msgid "Two Factor Authentication code generator" 281 | #~ msgstr "Generator kodów uwierzytelniania dwuetapowego" 282 | 283 | #~ msgid "Two-Factor Authentication code generator" 284 | #~ msgstr "Generator kodów uwierzytelniania dwuetapowego" 285 | -------------------------------------------------------------------------------- /po/sr@latin.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: Serbian (Authenticator)\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2018-09-10 17:56+0200\n" 6 | "PO-Revision-Date: 2018-03-24 20:59+0000\n" 7 | "Last-Translator: Slobodan Terzić \n" 8 | "Language-Team: Serbian (latin) \n" 10 | "Language: sr_Latn\n" 11 | "MIME-Version: 1.0\n" 12 | "Content-Type: text/plain; charset=UTF-8\n" 13 | "Content-Transfer-Encoding: 8bit\n" 14 | "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 15 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" 16 | "X-Generator: Weblate 2.20-dev\n" 17 | 18 | #: Authenticator/application.py:38 Authenticator/widgets/about.py.in:44 19 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:6 20 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:3 21 | msgid "Authenticator" 22 | msgstr "Autentifikator" 23 | 24 | #. Night mode action 25 | #: Authenticator/application.py:81 26 | msgid "Night Mode" 27 | msgstr "Noćni režim" 28 | 29 | #: Authenticator/application.py:84 30 | msgid "About" 31 | msgstr "O programu" 32 | 33 | #: Authenticator/application.py:85 34 | msgid "Quit" 35 | msgstr "Napusti" 36 | 37 | #: Authenticator/widgets/accounts/add.py:49 38 | #: Authenticator/widgets/headerbar.py:100 39 | msgid "Add a new account" 40 | msgstr "Dodaj novi nalog" 41 | 42 | #: Authenticator/widgets/accounts/add.py:53 43 | msgid "Add" 44 | msgstr "Dodaj" 45 | 46 | #: Authenticator/widgets/accounts/add.py:62 47 | msgid "Scan QR code" 48 | msgstr "Skeniraj QR kod" 49 | 50 | #: Authenticator/widgets/accounts/add.py:69 51 | #: Authenticator/widgets/accounts/edit.py:58 52 | msgid "Close" 53 | msgstr "Zatvori" 54 | 55 | #: Authenticator/widgets/accounts/add.py:160 56 | msgid "Provider" 57 | msgstr "" 58 | 59 | #: Authenticator/widgets/accounts/add.py:170 60 | msgid "Account name" 61 | msgstr "Ime naloga" 62 | 63 | #: Authenticator/widgets/accounts/add.py:176 64 | #, fuzzy 65 | msgid "Secret token" 66 | msgstr "Tajni token" 67 | 68 | #: Authenticator/widgets/accounts/add.py:258 69 | msgid "Invalid QR code" 70 | msgstr "Neispravan QR kod" 71 | 72 | #: Authenticator/widgets/accounts/edit.py:47 73 | msgid "Edit {} - {}" 74 | msgstr "" 75 | 76 | #: Authenticator/widgets/accounts/edit.py:52 77 | msgid "Save" 78 | msgstr "" 79 | 80 | #: Authenticator/widgets/accounts/list.py:163 81 | msgid "The One-Time Passwords expires in {} seconds" 82 | msgstr "" 83 | 84 | #. Label 85 | #: Authenticator/widgets/accounts/list.py:286 86 | #, fuzzy 87 | msgid "There are no accounts yet…" 88 | msgstr "Još uvek nema naloga..." 89 | 90 | #: Authenticator/widgets/accounts/row.py:58 91 | msgid "Copy" 92 | msgstr "Umnoži" 93 | 94 | #: Authenticator/widgets/accounts/row.py:59 95 | msgid "Edit" 96 | msgstr "" 97 | 98 | #: Authenticator/widgets/accounts/row.py:149 99 | msgid "Couldn't generate the secret code" 100 | msgstr "Ne mogu da napravim tajni kod" 101 | 102 | #: Authenticator/widgets/about.py.in:45 103 | msgid "translator-credits" 104 | msgstr "Slobodan Terzić " 105 | 106 | #: Authenticator/widgets/about.py.in:47 107 | #, fuzzy 108 | msgid "Two-factor authentication code generator." 109 | msgstr "Stvaralac kodova za dvofaktornu autentifikaciju." 110 | 111 | #: Authenticator/widgets/actions_bar.py:52 112 | msgid "Delete" 113 | msgstr "Obriši" 114 | 115 | #: Authenticator/widgets/headerbar.py:98 116 | msgid "Search" 117 | msgstr "Pretraži" 118 | 119 | #: Authenticator/widgets/headerbar.py:102 120 | msgid "Settings" 121 | msgstr "Podešavanja" 122 | 123 | #: Authenticator/widgets/headerbar.py:104 124 | msgid "Selection mode" 125 | msgstr "Režim izbora" 126 | 127 | #: Authenticator/widgets/headerbar.py:106 128 | msgid "Cancel" 129 | msgstr "Otkaži" 130 | 131 | #: Authenticator/widgets/headerbar.py:181 132 | msgid "Click on items to select them" 133 | msgstr "Kliknite na stavke da ih izaberete" 134 | 135 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:7 136 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:5 137 | #, fuzzy 138 | msgid "Two-factor authentication code generator" 139 | msgstr "Stvaralac kodova za dvofaktornu autentifikaciju." 140 | 141 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:9 142 | #, fuzzy 143 | msgid "" 144 | "Simple application that generates a two-factor authentication code, created " 145 | "for GNOME." 146 | msgstr "" 147 | "Jednostavan program koji stvara kod za dvofaktornu autentifikaciju, " 148 | "napravljen za Gnom" 149 | 150 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:10 151 | msgid "Features:" 152 | msgstr "" 153 | 154 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:12 155 | msgid "QR code scanner" 156 | msgstr "" 157 | 158 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:13 159 | msgid "Beautiful UI" 160 | msgstr "" 161 | 162 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:14 163 | msgid "Huge database of (290+) websites/applications" 164 | msgstr "" 165 | 166 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:34 167 | msgid "GNOME Shell Search provider" 168 | msgstr "" 169 | 170 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:35 171 | msgid "Codes expire simultaneously #91" 172 | msgstr "" 173 | 174 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:42 175 | msgid "Revamped main window to more closely follow the GNOME HIG" 176 | msgstr "" 177 | 178 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:43 179 | msgid "Revamped add a new account window to make it easier to use" 180 | msgstr "" 181 | 182 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:44 183 | msgid "" 184 | "Possibility to add an account from a provider not listed in the shipped " 185 | "database" 186 | msgstr "" 187 | 188 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:45 189 | msgid "Possibilty to edit an account" 190 | msgstr "" 191 | 192 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:46 193 | msgid "One Time Password now visible by default" 194 | msgstr "" 195 | 196 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:53 197 | msgid "Fix python-dbus by using GDbus instead" 198 | msgstr "" 199 | 200 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:60 201 | msgid "Fix the QRScanner on GNOME Shell" 202 | msgstr "" 203 | 204 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:61 205 | msgid "Add a new entry for the account's username" 206 | msgstr "" 207 | 208 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:62 209 | msgid "Updated database of supported accounts" 210 | msgstr "" 211 | 212 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:69 213 | msgid "HOTFIX: App not running in DE other than GNOME" 214 | msgstr "" 215 | 216 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:76 217 | #, fuzzy 218 | msgid "Rename project to Authenticator" 219 | msgstr "O Autentifikatoru" 220 | 221 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:77 222 | msgid "Cleaner code base" 223 | msgstr "" 224 | 225 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:78 226 | msgid "Faster startup" 227 | msgstr "" 228 | 229 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:79 230 | msgid "Remove unneeded features" 231 | msgstr "" 232 | 233 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:80 234 | msgid "Switch to pyzbar instead of zbarlight" 235 | msgstr "" 236 | 237 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:81 238 | msgid "Flatpak package" 239 | msgstr "" 240 | 241 | #: data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in:88 242 | msgid "Bilal Elmoussaoui" 243 | msgstr "" 244 | 245 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:4 246 | #, fuzzy 247 | msgid "Two-factor authentication" 248 | msgstr "Dvofaktorna autentifikacija" 249 | 250 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:10 251 | msgid "Gnome;GTK;Verification;" 252 | msgstr "Gnome;GTK;Verification;Overa;" 253 | 254 | #: data/com.github.bilelmoussaoui.Authenticator.desktop.in:11 255 | msgid "com.github.bilelmoussaoui.Authenticator" 256 | msgstr "com.github.bilelmoussaoui.Authenticator" 257 | 258 | #: authenticator.py.in:49 259 | msgid "Start in debug mode" 260 | msgstr "Pokreni u režimu za ispravljanje grešaka" 261 | 262 | #: authenticator.py.in:51 263 | msgid "Authenticator version number" 264 | msgstr "Verzija Autentifikatora" 265 | 266 | #~ msgid "Next" 267 | #~ msgstr "Sledeće" 268 | 269 | #~ msgid "Back" 270 | #~ msgstr "Nazad" 271 | 272 | #~ msgid "Undo" 273 | #~ msgstr "Opozovi" 274 | 275 | #~ msgid "Two Factor Authentication code generator" 276 | #~ msgstr "Stvaralac koda za dvofaktornu autentifikaciju" 277 | 278 | #~ msgid "Two-Factor Authentication code generator" 279 | #~ msgstr "Stvaralac koda za dvofaktornu autentifikaciju" 280 | -------------------------------------------------------------------------------- /search-provider/authenticator-search-provider.ini: -------------------------------------------------------------------------------- 1 | [Shell Search Provider] 2 | DesktopId=com.github.bilelmoussaoui.Authenticator.desktop 3 | BusName=com.github.bilelmoussaoui.Authenticator.SearchProvider 4 | ObjectPath=/com/github/bilelmoussaoui/Authenticator/SearchProvider 5 | Version=2 6 | -------------------------------------------------------------------------------- /search-provider/authenticator-search-provider.py.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | # Copyright (c) 2018 Bilal Elmoussaoui 3 | # Copyright (c) 2014-2016 Cedric Bellegarde 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # You should have received a copy of the GNU General Public License 13 | # along with this program. If not, see . 14 | 15 | import sys 16 | sys.path.insert(1, '@PYTHON_EXEC_DIR@') 17 | sys.path.insert(1, '@PYTHON_DIR@') 18 | from os import path 19 | import gi 20 | gi.require_version('Gtk', '3.0') 21 | gi.require_version('GIRepository', '2.0') 22 | 23 | from gi.repository import Gio, GIRepository, GLib 24 | 25 | GIRepository.Repository.prepend_search_path( 26 | path.join('@LIB_DIR@', 'girepository-1.0')) 27 | GIRepository.Repository.prepend_library_path('@LIB_DIR@') 28 | 29 | 30 | from Authenticator.models.database import Database 31 | from Authenticator.models.account import Account 32 | 33 | 34 | class Server: 35 | def __init__(self, con, path): 36 | method_outargs = {} 37 | method_inargs = {} 38 | for interface in Gio.DBusNodeInfo.new_for_xml(self.__doc__).interfaces: 39 | 40 | for method in interface.methods: 41 | method_outargs[method.name] = '(' + ''.join( 42 | [arg.signature for arg in method.out_args]) + ')' 43 | method_inargs[method.name] = tuple( 44 | arg.signature for arg in method.in_args) 45 | 46 | con.register_object(object_path=path, 47 | interface_info=interface, 48 | method_call_closure=self.on_method_call) 49 | 50 | self.method_inargs = method_inargs 51 | self.method_outargs = method_outargs 52 | 53 | def on_method_call(self, 54 | connection, 55 | sender, 56 | object_path, 57 | interface_name, 58 | method_name, 59 | parameters, 60 | invocation): 61 | 62 | args = list(parameters.unpack()) 63 | for i, sig in enumerate(self.method_inargs[method_name]): 64 | if sig is 'h': 65 | msg = invocation.get_message() 66 | fd_list = msg.get_unix_fd_list() 67 | args[i] = fd_list.get(args[i]) 68 | 69 | try: 70 | result = getattr(self, method_name)(*args) 71 | 72 | # out_args is atleast (signature1). 73 | # We therefore always wrap the result as a tuple. 74 | # Refer to https://bugzilla.gnome.org/show_bug.cgi?id=765603 75 | result = (result,) 76 | 77 | out_args = self.method_outargs[method_name] 78 | if out_args != '()': 79 | variant = GLib.Variant(out_args, result) 80 | invocation.return_value(variant) 81 | else: 82 | invocation.return_value(None) 83 | except Exception as e: 84 | pass 85 | 86 | 87 | class AuthenticatorSearchProvider(Server, Gio.Application): 88 | ''' 89 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | ''' 125 | __AUTHENTICATOR_BUS = 'com.github.bilelmoussaoui.Authenticator.SearchProvider' 126 | __SEARCH_BUS = 'org.gnome.Shell.SearchProvider2' 127 | __PATH_BUS = '/com/github/bilelmoussaoui/Authenticator/SearchProvider' 128 | 129 | def __init__(self): 130 | Gio.Application.__init__( 131 | self, 132 | application_id=self.__AUTHENTICATOR_BUS, 133 | flags=Gio.ApplicationFlags.IS_SERVICE) 134 | self.__bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) 135 | Gio.bus_own_name_on_connection(self.__bus, 136 | self.__SEARCH_BUS, 137 | Gio.BusNameOwnerFlags.NONE, 138 | None, 139 | None) 140 | Server.__init__(self, self.__bus, self.__PATH_BUS) 141 | 142 | def ActivateResult(self, *_): 143 | self.LaunchSearch() 144 | 145 | def GetInitialResultSet(self, terms): 146 | return self.__search(terms) 147 | 148 | def GetResultMetas(self, ids): 149 | results = [] 150 | try: 151 | for search_id in ids: 152 | account = Account.get_by_id(int(search_id)) 153 | name = account.username 154 | description = "{} - {}".format( 155 | account.provider.strip(), account.username) 156 | d = {'id': GLib.Variant('s', search_id), 157 | 'description': GLib.Variant('s', description), 158 | 'name': GLib.Variant('s', name) 159 | } 160 | results.append(d) 161 | except Exception as e: 162 | print("AuthenticatorSearchProvider::GetResultMetas():", e) 163 | return [] 164 | return results 165 | 166 | def GetSubsearchResultSet(self, _, new_terms): 167 | return self.__search(new_terms) 168 | 169 | def LaunchSearch(self, *_): 170 | GLib.spawn_async_with_pipes( 171 | None, ["authenticator"], None, 172 | GLib.SpawnFlags.SEARCH_PATH | 173 | GLib.SpawnFlags.DO_NOT_REAP_CHILD, None) 174 | 175 | def __search(self, terms): 176 | ids = [] 177 | try: 178 | ids = Database.get_default().search(terms) 179 | except Exception as e: 180 | print("AuthenticatorSearchProvider::__search():", e) 181 | return ids 182 | 183 | 184 | if __name__ == '__main__': 185 | service = AuthenticatorSearchProvider() 186 | service.hold() 187 | service.run() 188 | -------------------------------------------------------------------------------- /search-provider/com.github.bilelmoussaoui.Authenticator.SearchProvider.service.in: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=com.github.bilelmoussaoui.Authenticator.SearchProvider 3 | Exec=@libexecdir@/authenticator-search-provider 4 | -------------------------------------------------------------------------------- /search-provider/meson.build: -------------------------------------------------------------------------------- 1 | configure_file( 2 | input: 'authenticator-search-provider.py.in', 3 | output: 'authenticator-search-provider', 4 | configuration: conf, 5 | install_dir: LIBEXEC_DIR 6 | ) 7 | configure_file( 8 | input: meson.project_name() + '.SearchProvider.service.in', 9 | output: meson.project_name() + '.SearchProvider.service', 10 | configuration: conf, 11 | install_dir: SERVICES_DIR 12 | ) 13 | install_data( 14 | 'authenticator-search-provider.ini', 15 | install_dir: SEARCH_PROVIDER_DIR 16 | ) 17 | -------------------------------------------------------------------------------- /tools/autopep8.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | find . -name "*.py" -exec autopep8 --in-place {} \; 4 | -------------------------------------------------------------------------------- /tools/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | rm -rf builddir 3 | meson builddir --prefix=/usr 4 | sudo ninja -C builddir install 5 | 6 | -------------------------------------------------------------------------------- /tools/build/meson_post_install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from os import environ, path 4 | from subprocess import call 5 | 6 | if not environ.get('DESTDIR', ''): 7 | PREFIX = environ.get('MESON_INSTALL_PREFIX', '/usr/local') 8 | DATA_DIR = path.join(PREFIX, 'share') 9 | print('Updating icon cache...') 10 | call(['gtk-update-icon-cache', '-qtf', path.join(DATA_DIR, 'icons/hicolor')]) 11 | print("compiling new schemas") 12 | call(["glib-compile-schemas", path.join(DATA_DIR, 'glib-2.0/schemas/')]) 13 | -------------------------------------------------------------------------------- /tools/update_pot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # A modified code from the Terminix project. 3 | DOMAIN=Authenticator 4 | BASEDIR=$(dirname $0) 5 | OUTPUT_FILE=${BASEDIR}/po/${DOMAIN}.pot 6 | 7 | echo "Extracting translatable strings... " 8 | 9 | find ${BASEDIR}/ -name '*.py' | xgettext \ 10 | --output $OUTPUT_FILE \ 11 | --files-from=- \ 12 | --directory=$BASEDIR \ 13 | --language=Python \ 14 | --keyword=C_:1c,2 \ 15 | --from-code=utf-8 16 | 17 | xgettext \ 18 | --join-existing \ 19 | --output $OUTPUT_FILE \ 20 | --default-domain=$DOMAIN \ 21 | --package-name=$DOMAIN \ 22 | --directory=$BASEDIR \ 23 | --foreign-user \ 24 | --language=Desktop \ 25 | ${BASEDIR}/data/com.github.bilelmoussaoui.Authenticator.desktop.in 26 | 27 | 28 | xgettext \ 29 | --join-existing \ 30 | --output $OUTPUT_FILE \ 31 | --default-domain=$DOMAIN \ 32 | --package-name=$DOMAIN \ 33 | --directory=$BASEDIR \ 34 | --foreign-user \ 35 | --language=appdata \ 36 | ${BASEDIR}/data/com.github.bilelmoussaoui.Authenticator.appdata.xml.in 37 | 38 | # Merge the messages with existing po files 39 | echo "Merging with existing translations... " 40 | for file in ${BASEDIR}/po/*.po 41 | do 42 | echo -n $file 43 | msgmerge --update $file $OUTPUT_FILE 44 | done 45 | -------------------------------------------------------------------------------- /tools/yaml2json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | YAML database to JSON converter. 4 | """ 5 | import json 6 | import tempfile 7 | from collections import OrderedDict 8 | from glob import glob 9 | from os import path, remove 10 | from shutil import rmtree 11 | from subprocess import call 12 | 13 | import yaml 14 | 15 | GIT_CLONE_URI = "https://github.com/2factorauth/twofactorauth" 16 | TMP_FOLDER = path.join(tempfile.gettempdir(), "Authenticator") 17 | DATA_DIR = path.join(TMP_FOLDER, "_data") 18 | OUTPUT_DIR = path.join(path.dirname( 19 | path.realpath(__file__)), "../data/data.json") 20 | 21 | print("Cloning the repository...") 22 | if path.exists(TMP_FOLDER): 23 | rmtree(TMP_FOLDER) 24 | call(["git", "clone", "--depth=1", GIT_CLONE_URI, TMP_FOLDER]) 25 | 26 | if path.exists(OUTPUT_DIR): 27 | remove(OUTPUT_DIR) 28 | 29 | 30 | def is_valid(provider): 31 | if set(["tfa", "software"]).issubset(provider.keys()): 32 | return provider["tfa"] and provider["software"] 33 | else: 34 | return False 35 | 36 | 37 | output = {} 38 | 39 | for db_file in glob(DATA_DIR + "/*.yml"): 40 | with open(db_file, 'r') as file_data: 41 | try: 42 | providers = yaml.load(file_data)["websites"] 43 | for provider in providers: 44 | if is_valid(provider): 45 | output[provider.get("name")] = path.splitext( 46 | provider.get("img"))[0] 47 | except (yaml.YAMLError, TypeError, KeyError) as error: 48 | pass 49 | output = OrderedDict(sorted(output.items(), key=lambda x: x[0].lower())) 50 | 51 | with open(OUTPUT_DIR, 'w') as data: 52 | json.dump(output, data, indent=4) 53 | 54 | rmtree(TMP_FOLDER) 55 | -------------------------------------------------------------------------------- /tools/zbar_configure.patch: -------------------------------------------------------------------------------- 1 | --- a/configure.ac 2018-03-17 18:01:02.192289415 +0100 2 | +++ b/configure.ac 2018-03-17 18:03:34.513228629 +0100 3 | @@ -3,12 +3,13 @@ 4 | AC_INIT([zbar], [0.10], [spadix@users.sourceforge.net]) 5 | AC_CONFIG_AUX_DIR(config) 6 | AC_CONFIG_MACRO_DIR(config) 7 | -AM_INIT_AUTOMAKE([1.10 -Wall -Werror foreign subdir-objects std-options dist-bzip2]) 8 | +AM_INIT_AUTOMAKE([1.10 -Werror foreign subdir-objects std-options dist-bzip2]) 9 | AC_CONFIG_HEADERS([include/config.h]) 10 | AC_CONFIG_SRCDIR(zbar/scanner.c) 11 | LT_PREREQ([2.2]) 12 | LT_INIT([dlopen win32-dll]) 13 | LT_LANG([Windows Resource]) 14 | +m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) 15 | 16 | dnl update these just before each release (along w/pacakge version above) 17 | dnl LIB_VERSION update instructions copied from libtool docs: 18 | --------------------------------------------------------------------------------