Skip to content
This repository
Browse code

Show multiple accounts

Added WIP code/comments for implementing configuration
  • Loading branch information...
commit 9fbaf901260125e05232dc690f391c6d6b3ef970 1 parent eb5ae84
Gerard Braad — 吉拉德 authored
10 README
... ... @@ -1 +1,9 @@
1   -Based on the JavaScript implementation as provided by Tin Isles: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/
  1 +A simple Gnome-shell extension for Google Authenticator
  2 +
  3 +Supports showing multiple accounts, but at the moment you need to configure it by editing the extension.js
  4 +
  5 +TODO:
  6 + * implementing configuration/preferences widget
  7 + * show countdown
  8 + * store secrets in keyring
  9 +
8 gnome_shell_google-authenticator.json
... ... @@ -1,8 +0,0 @@
1   -{
2   - "version": "0.1",
3   - "key":
4   - {
5   - "account": "alice@google.com",
6   - "secret": "JBSWY3DPEHPK3PXP"
7   - }
8   -}
65 google-authenticator@gbraad.nl/convenience.js
... ... @@ -0,0 +1,65 @@
  1 +const Gettext = imports.gettext;
  2 +const Gio = imports.gi.Gio;
  3 +
  4 +const Config = imports.misc.config;
  5 +const ExtensionUtils = imports.misc.extensionUtils;
  6 +
  7 +/**
  8 + * initTranslations:
  9 + * @domain: (optional): the gettext domain to use
  10 + *
  11 + * Initialize Gettext to load translations from extensionsdir/locale.
  12 + * If @domain is not provided, it will be taken from metadata['gettext-domain']
  13 + */
  14 +function initTranslations(domain) {
  15 + let extension = ExtensionUtils.getCurrentExtension();
  16 +
  17 + domain = domain || extension.metadata['gettext-domain'];
  18 +
  19 + // check if this extension was built with "make zip-file", and thus
  20 + // has the locale files in a subfolder
  21 + // otherwise assume that extension has been installed in the
  22 + // same prefix as gnome-shell
  23 + let localeDir = extension.dir.get_child('locale');
  24 + if (localeDir.query_exists(null))
  25 + Gettext.bindtextdomain(domain, localeDir.get_path());
  26 + else
  27 + Gettext.bindtextdomain(domain, Config.LOCALEDIR);
  28 +}
  29 +
  30 +/**
  31 + * getSettings:
  32 + * @schema: (optional): the GSettings schema id
  33 + *
  34 + * Builds and return a GSettings schema for @schema, using schema files
  35 + * in extensionsdir/schemas. If @schema is not provided, it is taken from
  36 + * metadata['settings-schema'].
  37 + */
  38 +function getSettings(schema) {
  39 + let extension = ExtensionUtils.getCurrentExtension();
  40 +
  41 + schema = schema || extension.metadata['settings-schema'];
  42 +
  43 + const GioSSS = Gio.SettingsSchemaSource;
  44 +
  45 + // check if this extension was built with "make zip-file", and thus
  46 + // has the schema files in a subfolder
  47 + // otherwise assume that extension has been installed in the
  48 + // same prefix as gnome-shell (and therefore schemas are available
  49 + // in the standard folders)
  50 + let schemaDir = extension.dir.get_child('schemas');
  51 + let schemaSource;
  52 + if (schemaDir.query_exists(null))
  53 + schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
  54 + GioSSS.get_default(),
  55 + false);
  56 + else
  57 + schemaSource = GioSSS.get_default();
  58 +
  59 + let schemaObj = schemaSource.lookup(schema, true);
  60 + if (!schemaObj)
  61 + throw new Error('Schema ' + schema + ' could not be found for extension '
  62 + + extension.metadata.uuid + '. Please check your installation.');
  63 +
  64 + return new Gio.Settings({ settings_schema: schemaObj });
  65 +}
146 google-authenticator@gbraad.nl/extension.js
@@ -18,73 +18,128 @@ const Lang = imports.lang;
18 18 const Mainloop = imports.mainloop;
19 19
20 20 const St = imports.gi.St;
21   -const Pango = imports.gi.Pango;
22   -const GLib = imports.gi.GLib;
23 21
24 22 const Main = imports.ui.main;
25 23 const MessageTray = imports.ui.messageTray;
26 24 const PanelMenu = imports.ui.panelMenu;
27 25 const PopupMenu = imports.ui.popupMenu;
28   -const Tweener = imports.ui.tweener;
29   -
  26 +//const Tweener = imports.ui.tweener;
30 27 const Extension = imports.misc.extensionUtils.getCurrentExtension();
31 28 const Totp = Extension.imports.totp;
32   -
33   -//const Gettext = imports.gettext.domain('gnome-shell-google-authenticator');
34   -//const _ = Gettext.gettext;
35   -//const ngettext = Gettext.ngettext;
36   -
37   -let _accounts = [
38   - { "name": "alice@google.com", "secret" : "JBSWY3DPEHPK3PXP"}
39   -];
  29 +const Convenience = Extension.imports.convenience;
  30 +
  31 +let _settings;
  32 +//const SETTINGS_ACCOUNTS_KEY = 'accounts';
  33 +let _accounts;
  34 +let _defaultAccounts = [{
  35 + "name": "alice@google.com",
  36 + "secret": "JBSWY3DPEHPK3PXP"
  37 +}, {
  38 + "name": "alice+again@google.com",
  39 + "secret": "JBSWY3DPEHPK3PXP"
  40 +}];
40 41
41 42 const Indicator = new Lang.Class({
42   - Name : 'GoogleAuthenticator',
  43 + Name: 'GoogleAuthenticator',
43 44 Extends: PanelMenu.SystemStatusButton,
44 45
45   - _init: function() {
  46 + _init: function () {
46 47 this.parent("changes-prevent");
47   -
48   - // Set default values of options, and then override from config file
49   - this._parseConfig();
50   -
51   - // This should occur every second
52   - this._timeout = Mainloop.timeout_add_seconds(1, Lang.bind(this, function() {
53   - this._updateTimer()
54   - return true;
55   - }));
56   -
57   - this.connect('destroy', Lang.bind(this, this._onDestroy));
  48 + this._parseSettings();
  49 +
  50 + // This should occur every second
  51 + this._timeout = Mainloop.timeout_add_seconds(1, Lang.bind(this, function () {
  52 + this._updateTimer()
  53 + return true;
  54 + }));
  55 + this._updateTimer(true)
58 56 },
59 57
60   - // Update the keys based on timer tick
61   - _updateTimer: function() {
62   - // Should indicate the countdown time and only update when needed
  58 + // Update the keys based on timer tick or forced
  59 + _updateTimer: function (force) {
  60 + // Should indicate the countdown time and only update when needed
63 61 let epoch = Math.round(new Date().getTime() / 1000.0);
64 62 let countDown = 30 - (epoch % 30);
65   - // If we need to update the OTP keys
66   - if (epoch % 30 == 0) {
67   - // Reset
68   - this.menu.removeAll();
69   -
70   - this.menu.addMenuItem(new PopupMenu.PopupMenuItem(this._keyAccount));
71   - let key = Totp.updateOtp(this._keySecret);
72   - this.menu.addMenuItem(new PopupMenu.PopupMenuItem(key));
73   - }
74 63
75   - // Weird way to show 2-digit number, but js doesn't have a native padding function
76   - //if (countDown < 10)
77   - // countDown = "0" + countDown.toString();
78   - //else
79   - // countDown = countDown.toString();
80   - // "[" + countDown + "]";
  64 + // If we need to update the OTP keys
  65 + if (force || epoch % 30 == 0) {
  66 + // Reset
  67 + this.menu.removeAll();
  68 +
  69 + for (let i = 0; i < _accounts.length; i++) {
  70 + let account = _accounts[i];
  71 + let index = i; // have to store local?
  72 + let key = Totp.updateOtp(account.secret);
  73 +
  74 + let accountMenu = new PopupMenu.PopupMenuItem(account.name + "\n" + key);
  75 + /*
  76 + let delButton = new St.Bin({reactive: true,
  77 + can_focus: true,
  78 + x_fill: true,
  79 + y_fill: false,
  80 + track_hover: true });
  81 + let delIcon = new St.Icon({icon_name: 'edit-delete',
  82 + icon_type: St.IconType.SYMBOLIC,
  83 + icon_size: Main.panel.actor.height * 0.8});
  84 + delButton.set_child(delIcon);
  85 + delButton.connect('button-press-event', Lang.bind(this, function() { this._delAccount(index); }));
  86 + accountMenu.addActor(delButton, {align: St.Align.END});
  87 + */
  88 + this.menu.addMenuItem(accountMenu);
  89 + }
  90 +
  91 + //this._menuElements();
  92 + }
81 93 },
82 94
83   - _parseConfig: function() {
  95 + _menuElements: function () {
  96 + // Need to find a convenient way to update only the elements itself
  97 + let addMenu = new PopupMenu.PopupMenuItem("Settings");
  98 + let addButton = new St.Bin({
  99 + reactive: true,
  100 + can_focus: true,
  101 + x_fill: true,
  102 + y_fill: false,
  103 + track_hover: true
  104 + });
  105 + let addIcon = new St.Icon({
  106 + icon_name: 'list-add',
  107 + icon_type: St.IconType.SYMBOLIC,
  108 + icon_size: Main.panel.actor.height * 0.8
  109 + });
  110 + addButton.set_child(addIcon);
  111 + addButton.connect('button-press-event', Lang.bind(this, function () {
  112 + this._addAccount();
  113 + }));
  114 + addMenu.addActor(addButton, {
  115 + align: St.Align.END
  116 + });
  117 + this.menu.addMenuItem(addMenu);
  118 + },
84 119
  120 + _delAccount: function (index) {
  121 + //_accounts.splice(index, 1);
  122 + //_settings.set_strv(SETTINGS_ACCOUNTS_KEY, _accounts);
  123 + this._updateTimer(true);
85 124 },
86 125
87   - _onDestroy: function() {
  126 + _addAccount: function () {},
  127 +
  128 + _parseSettings: function () {
  129 + // TODO: deal with gsettings and keyring
  130 + _acounts = null;
  131 + _settings = Convenience.getSettings();
  132 + try {
  133 + // TODO: parse account information
  134 + //_accounts = _settings.get_string(SETTINGS_ACCOUNTS_KEY);
  135 + } catch (e) {
  136 + global.logError("Google Authenticator: Error reading configuration = " + e);
  137 + } finally {
  138 + if (!_accounts) {
  139 + _accounts = _defaultAccounts;
  140 + //_settings.set_strv(SETTINGS_ACCOUNTS_KEY, _accounts);
  141 + }
  142 + }
88 143 }
89 144
90 145 });
@@ -92,6 +147,7 @@ const Indicator = new Lang.Class({
92 147 let indicator;
93 148
94 149 function init(metadata) {
  150 + //Convenience.initTranslations();
95 151 }
96 152
97 153 function enable() {
6 google-authenticator@gbraad.nl/metadata.json
... ... @@ -1,7 +1,11 @@
1 1 {
2 2 "shell-version": ["3.4"],
  3 + "settings-schema": "org.gnome.shell.extensions.google-authenticator",
  4 + "gettext-domain": "gnome-shell-extensions",
  5 + "extension-id": "google-authenticator",
3 6 "uuid": "google-authenticator@gbraad.nl",
4 7 "name": "Google Authenticator",
5 8 "description": "A simple Google Authenticator exension",
6   - "url": "https://github.com/gbraad/gnome-shell-google-authenticator"
  9 + "url": "https://github.com/gbraad/gnome-shell-google-authenticator",
  10 + "original-authors": [ "me@gbraad.nl" ]
7 11 }
9 google-authenticator@gbraad.nl/totp.js
@@ -200,12 +200,7 @@ jsSHA.prototype = {
200 200 }
201 201 };
202 202
203   -// Mock/stub code
204   -//jsSHA = function (srcString, inputFormat) { /* ignore input */ }
205   -//jsSHA.prototype = {
206   -// getHMAC : function (key, inputFormat, variant, outputFormat) { return "JUSTATEST"; }
207   -//}
208   -
  203 +// Based on the JavaScript implementation as provided by Tin Isles:
209 204 // http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/
210 205
211 206 function dec2hex(s) {
@@ -265,5 +260,5 @@ function updateOtp(secret) {
265 260 //var part3 = hmac.substr(offset * 2 + 8, hmac.length - offset);
266 261
267 262 let otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + '';
268   - return "" + (otp).substr(otp.length - 6, 6);
  263 + return (otp).substr(otp.length - 6, 6).toString();
269 264 }

0 comments on commit 9fbaf90

Please sign in to comment.
Something went wrong with that request. Please try again.