diff --git a/plugins/kolab_2fa/kolab2fa.js b/plugins/kolab_2fa/kolab2fa.js
index d471aecd..dd0bf8b8 100644
--- a/plugins/kolab_2fa/kolab2fa.js
+++ b/plugins/kolab_2fa/kolab2fa.js
@@ -45,29 +45,28 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
table.html('');
var rows = 0;
- $.each(rcmail.env.kolab_2fa_factors, function(method, props) {
+ $.each(rcmail.env.kolab_2fa_factors, function(id, props) {
if (props.active) {
- var tr = $('
').addClass(props.method).appendTo(table);
+ $('').addClass('name').text(props.label || props.name).appendTo(tr);
$(' | ').addClass('created').text(props.created || '??').appendTo(tr);
- $(' | ').addClass('actions').html('' + rcmail.get_label('remove','kolab_2fa') + '').appendTo(tr);
+ $(' | ').addClass('actions').html('' + rcmail.get_label('remove','kolab_2fa') + '').appendTo(tr);
rows++;
}
});
table.parent()[(rows > 0 ? 'show' : 'hide')]();
-
+/*
var remaining = 0;
$('#kolab2fa-add option').each(function(i, elem) {
var method = elem.value;
- if (rcmail.env.kolab_2fa_factors[method]) {
- $(elem).prop('disabled', rcmail.env.kolab_2fa_factors[method].active);
- if (!rcmail.env.kolab_2fa_factors[method].active) {
- remaining++;
- }
+ $(elem).prop('disabled', active[method]);
+ if (!active[method]) {
+ remaining++;
}
});
$('#kolab2fa-add').prop('disabled', !remaining).get(0).selectedIndex = 0;
+*/
}
/**
@@ -102,7 +101,11 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
],
{
open: function(event, ui) {
-
+ $(event.target).find('input[name="_verify_code"]').keypress(function(e) {
+ if (e.which == 13) {
+ $(e.target).closest('.ui-dialog').find('.ui-button.mainaction').click();
+ }
+ });
},
close: function(event, ui) {
form.hide().appendTo(document.body);
@@ -128,21 +131,21 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
/**
* Remove the given factor from the account
*/
- function remove_factor(method) {
- if (rcmail.env.kolab_2fa_factors[method]) {
- rcmail.env.kolab_2fa_factors[method].active = false;
+ function remove_factor(id) {
+ if (rcmail.env.kolab_2fa_factors[id]) {
+ rcmail.env.kolab_2fa_factors[id].active = false;
}
render();
var lock = rcmail.set_busy(true, 'saving');
- rcmail.http_post('plugin.kolab-2fa-save', { _method: method, _data: 'false' }, lock);
+ rcmail.http_post('plugin.kolab-2fa-save', { _method: id, _data: 'false' }, lock);
}
/**
* Submit factor settings form
*/
function save_data(method) {
- var lock, form = $('#kolab2fa-prop-' + method),
+ var lock, data, form = $('#kolab2fa-prop-' + method),
verify = form.find('input[name="_verify_code"]');
if (verify.length && !verify.val().length) {
@@ -151,10 +154,11 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
return false;
}
+ data = form_data(form);
lock = rcmail.set_busy(true, 'saving');
rcmail.http_post('plugin.kolab-2fa-save', {
- _method: method,
- _data: JSON.stringify(form_data(form)),
+ _method: data.id || method,
+ _data: JSON.stringify(data),
_verify_code: verify.val(),
_timestamp: factor_dialog ? factor_dialog.data('timestamp') : null
}, lock);
@@ -181,18 +185,20 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
/**
* Execute the given function after the user authorized the session with a 2nd factor
*/
- function require_high_security(func)
+ function require_high_security(func, exclude)
{
// request 2nd factor auth
if (!rcmail.env.session_secured || rcmail.env.session_secured < time() - 120) {
var method, name;
// find an active factor
- $.each(rcmail.env.kolab_2fa_factors, function(m, prop) {
- if (prop.active) {
- method = m;
- name = prop.name;
- return true;
+ $.each(rcmail.env.kolab_2fa_factors, function(id, prop) {
+ if (prop.active && !method || method == exclude) {
+ method = id;
+ name = prop.label || prop.name;
+ if (!exclude || id !== exclude) {
+ return true;
+ }
}
});
@@ -259,12 +265,12 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
// callback for factor data provided by the server
rcmail.addEventListener('plugin.render_data', function(data) {
- var method = data._method,
+ var method = data.method,
form = $('#kolab2fa-prop-' + method);
if (form.length) {
$.each(data, function(field, value) {
- form.find('[name="_prop[' + method + '][' + field + ']"]').val(value);
+ form.find('[name="_prop[' + field + ']"]').val(value);
});
if (data.qrcode) {
@@ -278,8 +284,14 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
// callback for save action
rcmail.addEventListener('plugin.save_success', function(data) {
- if (rcmail.env.kolab_2fa_factors[data.method]) {
- $.extend(rcmail.env.kolab_2fa_factors[data.method], data);
+ if (!data.active && rcmail.env.kolab_2fa_factors[data.id]) {
+ delete rcmail.env.kolab_2fa_factors[data.id];
+ }
+ else if (rcmail.env.kolab_2fa_factors[data.id]) {
+ $.extend(rcmail.env.kolab_2fa_factors[data.id], data);
+ }
+ else {
+ rcmail.env.kolab_2fa_factors[data.id] = data;
}
if (factor_dialog) {
@@ -334,14 +346,14 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
// handler for delete button clicks
$('#kolab2fa-factors tbody').on('click', '.button.delete', function(e) {
- var method = $(this).attr('rel');
+ var id = $(this).attr('rel');
// require auth verification
require_high_security(function() {
if (confirm(rcmail.get_label('authremoveconfirm', 'kolab_2fa'))) {
- remove_factor(method);
+ remove_factor(id);
}
- });
+ }, id);
return false;
});
diff --git a/plugins/kolab_2fa/kolab_2fa.php b/plugins/kolab_2fa/kolab_2fa.php
index 43c0397a..4ee7e8d8 100644
--- a/plugins/kolab_2fa/kolab_2fa.php
+++ b/plugins/kolab_2fa/kolab_2fa.php
@@ -107,7 +107,7 @@ class kolab_2fa extends rcube_plugin
}
// 2b. check storage if this user has 2FA enabled
else if ($storage = $this->get_storage($args['user'])) {
- $factors = (array)$storage->read('active');
+ $factors = (array)$storage->enumerate();
}
if (count($factors) > 0) {
@@ -155,19 +155,20 @@ class kolab_2fa extends rcube_plugin
$time = $_SESSION['kolab_2fa_time'];
$nonce = $_SESSION['kolab_2fa_nonce'];
$factors = (array)$_SESSION['kolab_2fa_factors'];
- $sign = rcube_utils::get_input_value('_sign', rcube_utils::INPUT_POST);
$this->login_verified = false;
$expired = $time < time() - $rcmail->config->get('kolab_2fa_timeout', 120);
- if (!empty($sign) && !empty($factors) && !empty($nonce) && !$expired) {
+ if (!empty($factors) && !empty($nonce) && !$expired) {
// TODO: check signature
// try to verify each configured factor
- foreach ($factors as $method) {
+ foreach ($factors as $factor) {
+ list($method) = explode(':', $factor, 2);
+
// verify the submitted code
$code = rcube_utils::get_input_value("_${nonce}_${method}", rcube_utils::INPUT_POST);
- $this->login_verified = $this->verify_factor_auth($method, $code);
+ $this->login_verified = $this->verify_factor_auth($factor, $code);
// accept first successful method
if ($this->login_verified) {
@@ -225,21 +226,26 @@ class kolab_2fa extends rcube_plugin
$form_name = !empty($attrib['form']) ? $attrib['form'] : 'form';
$nonce = $_SESSION['kolab_2fa_nonce'];
+ $methods = array_unique(array_map(function($factor) {
+ list($method, $id) = explode(':', $factor);
+ return $method;
+ },
+ $this->login_factors
+ ));
+
// forward these values as the regular login screen would submit them
$input_task = new html_hiddenfield(array('name' => '_task', 'value' => 'login'));
$input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'plugin.kolab-2fa-login'));
$input_tzone = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => rcube_utils::get_input_value('_timezone', rcube_utils::INPUT_POST)));
$input_url = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => rcube_utils::get_input_value('_url', rcube_utils::INPUT_POST)));
- // TODO: generate request signature
- $input_sign = new html_hiddenfield(array('name' => '_sign', 'id' => 'rcmloginsign', 'value' => 'XXX'));
// create HTML table with two cols
$table = new html_table(array('cols' => 2));
- $required = count($this->login_factors) > 1 ? null : 'required';
+ $required = count($methods) > 1 ? null : 'required';
// render input for each configured auth method
- foreach ($this->login_factors as $i => $method) {
- if ($i > 0) {
+ foreach ($methods as $i => $method) {
+ if ($row++ > 0) {
$table->add(array('colspan' => 2, 'class' => 'title hint', 'style' => 'text-align:center'),
$this->gettext('or'));
}
@@ -255,7 +261,6 @@ class kolab_2fa extends rcube_plugin
$out .= $input_action->show();
$out .= $input_tzone->show();
$out .= $input_url->show();
- $out .= $input_sign->show();
$out .= $table->show();
// add submit button
@@ -279,12 +284,6 @@ class kolab_2fa extends rcube_plugin
public function get_driver($method)
{
$rcmail = rcmail::get_instance();
- $method = strtolower($method);
- $valid = in_array($method, $rcmail->config->get('kolab_2fa_drivers', array()));
-
- if (!$valid) {
- return false;
- }
if ($this->drivers[$method]) {
return $this->drivers[$method];
@@ -404,13 +403,11 @@ class kolab_2fa extends rcube_plugin
public function settings_factoradder($attrib)
{
$rcmail = rcmail::get_instance();
- $storage = $this->get_storage($rcmail->get_user_name());
- $active = $storage ? (array)$storage->read('active') : array();
$select = new html_select(array('id' => 'kolab2fa-add'));
$select->add($this->gettext('addfactor') . '...', '');
foreach ((array)$rcmail->config->get('kolab_2fa_drivers', array()) as $method) {
- $select->add($this->gettext($method), $method, array('disabled' => in_array($method, $active)));
+ $select->add($this->gettext($method), $method);
}
return $select->show();
@@ -438,30 +435,37 @@ class kolab_2fa extends rcube_plugin
{
$rcmail = rcmail::get_instance();
$storage = $this->get_storage($rcmail->get_user_name());
- $factors = $storage ? (array)$storage->read('active') : array();
+ $factors = $storage ? (array)$storage->enumerate() : array();
$drivers = (array)$rcmail->config->get('kolab_2fa_drivers', array());
+ $env_methods = array();
foreach ($drivers as $j => $method) {
$out .= $this->settings_factor($method, $attrib);
+ $env_methods[$method] = array(
+ 'name' => $this->gettext($method),
+ 'active' => 0,
+ );
}
$me = $this;
$this->api->output->set_env('kolab_2fa_factors', array_combine(
- $drivers,
- array_map(function($method) use ($me, $factors) {
- $props = array(
- 'name' => $me->gettext($method),
- 'active' => in_array($method, $factors),
- );
+ $factors,
+ array_map(function($id) use ($me, &$env_methods) {
+ $props = array('id' => $id);
- if ($props['active'] && ($driver = $me->get_driver($method))) {
+ if ($driver = $me->get_driver($id)) {
$props += $this->format_props($driver->props());
+ $props['method'] = $driver->method;
+ $props['name'] = $me->gettext($driver->method);
+ $env_methods[$driver->method]['active']++;
}
return $props;
- }, $drivers)
+ }, $factors)
));
+ $this->api->output->set_env('kolab_2fa_methods', $env_methods);
+
return html::div(array('id' => 'kolab2fapropform'), $out);
}
@@ -528,6 +532,8 @@ class kolab_2fa extends rcube_plugin
}
+ $input_id = new html_hiddenfield(array('name' => '_prop[id]', 'value' => ''));
+
$out .= html::tag('form', array(
'method' => 'post',
'action' => '#',
@@ -536,7 +542,8 @@ class kolab_2fa extends rcube_plugin
),
html::tag('fieldset', array(),
html::tag('legend', array(), $this->gettext($method)) .
- html::div('factorprop', $table->show())
+ html::div('factorprop', $table->show()) .
+ $input_id->show()
)
);
}
@@ -566,11 +573,10 @@ class kolab_2fa extends rcube_plugin
public function settings_save()
{
$method = rcube_utils::get_input_value('_method', rcube_utils::INPUT_POST);
- $data = @json_decode(rcube_utils::get_input_value('_data', rcube_utils::INPUT_POST), true);
+ $data = @json_decode(rcube_utils::get_input_value('_data', rcube_utils::INPUT_POST), true);
$rcmail = rcmail::get_instance();
$storage = $this->get_storage($rcmail->get_user_name());
- $active = $storage ? (array)$storage->read('active') : array();
$success = false;
$errors = 0;
$save_data = array();
@@ -579,8 +585,7 @@ class kolab_2fa extends rcube_plugin
if ($data === false) {
if ($this->check_secure_mode()) {
// remove method from active factors and clear stored settings
- $active = array_filter($active, function($f) use ($method) { return $f != $method; });
- $driver->clear();
+ $success = $driver->clear();
}
else {
$errors++;
@@ -593,9 +598,10 @@ class kolab_2fa extends rcube_plugin
if (!empty($verify_code)) {
if (!$driver->verify($verify_code, $timestamp)) {
$this->api->output->command('plugin.verify_response', array(
- 'method' => $method,
+ 'id' => $driver->id,
+ 'method' => $driver->method,
'success' => false,
- 'message' => str_replace('$method', $this->gettext($method), $this->gettext('codeverificationfailed'))
+ 'message' => str_replace('$method', $this->gettext($driver->method), $this->gettext('codeverificationfailed'))
));
$this->api->output->send();
}
@@ -607,14 +613,12 @@ class kolab_2fa extends rcube_plugin
}
}
- if (!in_array($method, $active)) {
- $active[] = $method;
- }
+ $driver->set('active', true);
}
// update list of active factors for this user
if (!$errors) {
- $success = $storage && $storage->write('active', $active);
+ $success = $driver->commit();
$save_data = $data !== false ? $this->format_props($driver->props()) : array();
}
}
@@ -639,14 +643,7 @@ class kolab_2fa extends rcube_plugin
$method = rcube_utils::get_input_value('_method', rcube_utils::INPUT_POST);
if ($driver = $this->get_driver($method)) {
- $data = array('_method' => $method);
-
- // abort if session is not authorized
- /*
- if ($driver->active && !$this->check_secure_mode()) {
- $this->api->output->send();
- }
- */
+ $data = array('method' => $method, 'id' => $driver->id);
foreach ($driver->props(true) as $field => $prop) {
$data[$field] = $prop['text'] ?: $prop['value'];
@@ -696,6 +693,7 @@ class kolab_2fa extends rcube_plugin
}
}
$success = $driver->verify(rcube_utils::get_input_value('_code', rcube_utils::INPUT_POST), $timestamp);
+ $method = $driver->method;
}
// put session into high-security mode
@@ -726,10 +724,6 @@ class kolab_2fa extends rcube_plugin
$value = $rcmail->format_date($prop['value']);
break;
- case 'boolean':
- $value = $this->gettext($prop['value'] ? 'yes' : 'no');
- break;
-
default:
$value = $prop['value'];
}
diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Driver/Base.php b/plugins/kolab_2fa/lib/Kolab2FA/Driver/Base.php
index 77bd2240..89488231 100644
--- a/plugins/kolab_2fa/lib/Kolab2FA/Driver/Base.php
+++ b/plugins/kolab_2fa/lib/Kolab2FA/Driver/Base.php
@@ -26,21 +26,45 @@ namespace Kolab2FA\Driver;
abstract class Base
{
public $method = null;
+ public $id = null;
public $storage;
protected $config = array();
protected $props = array();
protected $user_props = array();
+ protected $pending_changes = false;
protected $allowed_props = array('username');
- public $user_settings = array();
+ public $user_settings = array(
+ 'active' => array(
+ 'type' => 'boolean',
+ 'editable' => false,
+ 'hidden' => false,
+ 'default' => false,
+ ),
+ 'label' => array(
+ 'type' => 'text',
+ 'editable' => true,
+ 'label' => 'label',
+ 'generator' => 'default_label',
+ ),
+ 'created' => array(
+ 'type' => 'datetime',
+ 'editable' => false,
+ 'hidden' => false,
+ 'label' => 'created',
+ 'generator' => 'time',
+ ),
+ );
/**
* Static factory method
*/
- public static function factory($method, $config)
+ public static function factory($id, $config)
{
+ list($method) = explode(':', $id);
+
$classmap = array(
'totp' => '\\Kolab2FA\\Driver\\TOTP',
'hotp' => '\\Kolab2FA\\Driver\\HOTP',
@@ -49,7 +73,7 @@ abstract class Base
$cls = $classmap[strtolower($method)];
if ($cls && class_exists($cls)) {
- return new $cls($config);
+ return new $cls($config, $id);
}
throw new Exception("Unknown 2FA driver '$method'");
@@ -58,19 +82,26 @@ abstract class Base
/**
* Default constructor
*/
- public function __construct($config = null)
+ public function __construct($config = null, $id = null)
{
- if (is_array($config)) {
- $this->init($config);
+ $this->init($config);
+
+ if (!empty($id) && $id != $this->method) {
+ $this->id = $id;
+ }
+ else { // generate random ID
+ $this->id = $this->method . ':' . bin2hex(openssl_random_pseudo_bytes(12));
}
}
/**
* Initialize the driver with the given config options
*/
- public function init(array $config)
+ public function init($config)
{
- $this->config = array_merge($this->config, $config);
+ if (is_array($config)) {
+ $this->config = array_merge($this->config, $config);
+ }
if ($config['storage']) {
$this->storage = \Kolab2FA\Storage\Base::factory($config['storage'], $config['storage_config']);
@@ -152,6 +183,18 @@ abstract class Base
return $secret;
}
+ /**
+ * Generate the default label based on the method
+ */
+ public function default_label()
+ {
+ if (class_exists('\\rcmail', false)) {
+ return \rcmail::get_instance()->gettext($this->method, 'kolab_2fa');
+ }
+
+ return strtoupper($this->method);
+ }
+
/**
* Getter for read-only access to driver properties
*/
@@ -170,7 +213,7 @@ abstract class Base
if (is_callable($func)) {
$value = call_user_func($func);
}
- if (!isset($value)) {
+ if (isset($value)) {
$this->set_user_prop($key, $value);
}
}
@@ -206,6 +249,20 @@ abstract class Base
return true;
}
+ /**
+ * Commit changes to storage
+ */
+ public function commit()
+ {
+ if (!empty($this->user_props) && $this->storage && $this->pending_changes) {
+ if ($this->storage->write($this->id, $this->user_props)) {
+ $this->pending_changes = false;
+ }
+ }
+
+ return !$this->pending_changes;
+ }
+
/**
* Dedicated setter for the username property
*/
@@ -226,8 +283,10 @@ abstract class Base
public function clear()
{
if ($this->storage) {
- $this->storage->remove($this->method);
+ return $this->storage->remove($this->id);
}
+
+ return false;
}
/**
@@ -235,8 +294,8 @@ abstract class Base
*/
protected function get_user_prop($key)
{
- if (!isset($this->user_props[$key]) && $this->storage) {
- $this->user_props = (array)$this->storage->read($this->method);
+ if (!isset($this->user_props[$key]) && $this->storage && !$this->pending_changes) {
+ $this->user_props = (array)$this->storage->read($this->id);
}
return $this->user_props[$key];
@@ -247,15 +306,17 @@ abstract class Base
*/
protected function set_user_prop($key, $value)
{
- $success = false;
+ $success = true;
+ $this->pending_changes |= ($this->user_props[$key] !== $value);
$this->user_props[$key] = $value;
+/*
if ($this->user_settings[$key] && $this->storage) {
- $props = (array)$this->storage->read($this->method);
+ $props = (array)$this->storage->read($this->id);
$props[$key] = $value;
- $success = $this->storage->write($this->method, $props);
+ $success = $this->storage->write($this->id, $props);
}
-
+*/
return $success;
}
diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Driver/HOTP.php b/plugins/kolab_2fa/lib/Kolab2FA/Driver/HOTP.php
index dc1e0a25..89542242 100644
--- a/plugins/kolab_2fa/lib/Kolab2FA/Driver/HOTP.php
+++ b/plugins/kolab_2fa/lib/Kolab2FA/Driver/HOTP.php
@@ -33,37 +33,30 @@ class HOTP extends Base
'digest' => 'sha1',
);
- public $user_settings = array(
- 'secret' => array(
- 'type' => 'text',
- 'private' => true,
- 'label' => 'secret',
- 'generator' => 'generate_secret',
- ),
- 'created' => array(
- 'type' => 'datetime',
- 'editable' => false,
- 'hidden' => false,
- 'label' => 'created',
- 'generator' => 'time',
- ),
- 'counter' => array(
- 'type' => 'integer',
- 'editable' => false,
- 'hidden' => true,
- 'generator' => 'random_counter',
- ),
- );
-
protected $backend;
/**
*
*/
- public function init(array $config)
+ public function init($config)
{
parent::init($config);
+ $this->user_settings += array(
+ 'secret' => array(
+ 'type' => 'text',
+ 'private' => true,
+ 'label' => 'secret',
+ 'generator' => 'generate_secret',
+ ),
+ 'counter' => array(
+ 'type' => 'integer',
+ 'editable' => false,
+ 'hidden' => true,
+ 'generator' => 'random_counter',
+ ),
+ );
+
// copy config options
$this->backend = new \Kolab2FA\OTP\HOTP();
$this->backend
@@ -108,6 +101,7 @@ class HOTP extends Base
$this->set('secret', $this->get('secret', true));
$this->set('counter', $this->get('counter', true));
$this->set('created', $this->get('created', true));
+ $this->commit();
}
// TODO: deny call if already active?
diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Driver/TOTP.php b/plugins/kolab_2fa/lib/Kolab2FA/Driver/TOTP.php
index 12771afb..8fe9654f 100644
--- a/plugins/kolab_2fa/lib/Kolab2FA/Driver/TOTP.php
+++ b/plugins/kolab_2fa/lib/Kolab2FA/Driver/TOTP.php
@@ -33,31 +33,24 @@ class TOTP extends Base
'digest' => 'sha1',
);
- public $user_settings = array(
- 'secret' => array(
- 'type' => 'text',
- 'private' => true,
- 'label' => 'secret',
- 'generator' => 'generate_secret',
- ),
- 'created' => array(
- 'type' => 'datetime',
- 'editable' => false,
- 'hidden' => false,
- 'label' => 'created',
- 'generator' => 'time',
- ),
- );
-
protected $backend;
/**
*
*/
- public function init(array $config)
+ public function init($config)
{
parent::init($config);
+ $this->user_settings += array(
+ 'secret' => array(
+ 'type' => 'text',
+ 'private' => true,
+ 'label' => 'secret',
+ 'generator' => 'generate_secret',
+ ),
+ );
+
// copy config options
$this->backend = new \Kolab2FA\OTP\TOTP();
$this->backend
@@ -103,10 +96,13 @@ class TOTP extends Base
*/
public function get_provisioning_uri()
{
+ console('PROV', $this->secret);
if (!$this->secret) {
// generate new secret and store it
$this->set('secret', $this->get('secret', true));
$this->set('created', $this->get('created', true));
+ console('PROV2', $this->secret);
+ $this->commit();
}
// TODO: deny call if already active?
diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Driver/Yubikey.php b/plugins/kolab_2fa/lib/Kolab2FA/Driver/Yubikey.php
index 15156896..2f227da9 100644
--- a/plugins/kolab_2fa/lib/Kolab2FA/Driver/Yubikey.php
+++ b/plugins/kolab_2fa/lib/Kolab2FA/Driver/Yubikey.php
@@ -33,21 +33,6 @@ class Yubikey extends Base
'hosts' => null,
);
- public $user_settings = array(
- 'yubikeyid' => array(
- 'type' => 'text',
- 'editable' => true,
- 'label' => 'secret',
- ),
- 'created' => array(
- 'type' => 'datetime',
- 'editable' => false,
- 'hidden' => false,
- 'label' => 'created',
- 'generator' => 'time',
- ),
- );
-
protected $backend;
/**
@@ -57,6 +42,14 @@ class Yubikey extends Base
{
parent::init($config);
+ $this->user_settings += array(
+ 'yubikeyid' => array(
+ 'type' => 'text',
+ 'editable' => true,
+ 'label' => 'secret',
+ ),
+ );
+
// initialize validator
$this->backend = new \Yubikey\Validate($this->config['apikey'], $this->config['clientid']);
diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Storage/Base.php b/plugins/kolab_2fa/lib/Kolab2FA/Storage/Base.php
index dd11f183..ba9ac8a7 100644
--- a/plugins/kolab_2fa/lib/Kolab2FA/Storage/Base.php
+++ b/plugins/kolab_2fa/lib/Kolab2FA/Storage/Base.php
@@ -73,6 +73,11 @@ abstract class Base
$this->username = $username;
}
+ /**
+ * List keys holding settings for 2-factor-authentication
+ */
+ abstract public function enumerate();
+
/**
* Read data for the given key
*/
diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Storage/RcubeUser.php b/plugins/kolab_2fa/lib/Kolab2FA/Storage/RcubeUser.php
index cdf47393..f0664bb1 100644
--- a/plugins/kolab_2fa/lib/Kolab2FA/Storage/RcubeUser.php
+++ b/plugins/kolab_2fa/lib/Kolab2FA/Storage/RcubeUser.php
@@ -30,9 +30,7 @@ class RcubeUser extends Base
{
// sefault config
protected $config = array(
- 'keymap' => array(
- 'active' => 'kolab_2fa_factors',
- ),
+ 'keymap' => array(),
);
private $cache = array();
@@ -46,15 +44,29 @@ class RcubeUser extends Base
$this->config['hostname'] = $rcmail->user->ID ? $rcmail->user->data['mail_host'] : $_SESSION['hostname'];
}
+ /**
+ * List/set methods activated for this user
+ */
+ public function enumerate()
+ {
+ if ($factors = $this->get_factors()) {
+ return array_keys(array_filter($factors, function($prop) {
+ return !empty($prop['active']);
+ }));
+ }
+
+ return array();
+ }
+
/**
* Read data for the given key
*/
public function read($key)
{
- if (!isset($this->cache[$key]) && ($user = $this->get_user($this->username))) {
- $prefs = $user->get_prefs();
- $pkey = $this->key2property($key);
- $this->cache[$key] = $prefs[$pkey];
+ if (!isset($this->cache[$key])) {
+ $factors = $this->get_factors();
+ console('READ', $key, $factors);
+ $this->cache[$key] = $factors[$key];
}
return $this->cache[$key];
@@ -67,8 +79,37 @@ class RcubeUser extends Base
{
if ($user = $this->get_user($this->username)) {
$this->cache[$key] = $value;
- $pkey = $this->key2property($key);
- return $user->save_prefs(array($pkey => $value), true);
+
+ $factors = $this->get_factors();
+ $factors[$key] = $value;
+
+ $pkey = $this->key2property('blob');
+ $save_data = array($pkey => $factors);
+ $update_index = false;
+
+ // remove entry
+ if ($value === null) {
+ unset($factors[$key]);
+ $update_index = true;
+ }
+ // remove non-active entries
+ else if (!empty($value['active'])) {
+ $factors = array_filter($factors, function($prop) {
+ return !empty($prop['active']);
+ });
+ $update_index = true;
+ }
+
+ // update the index of active factors
+ if ($update_index) {
+ $save_data[$this->key2property('factors')] = array_keys(
+ array_filter($factors, function($prop) {
+ return !empty($prop['active']);
+ })
+ );
+ }
+
+ return $user->save_prefs($save_data, true);
}
return false;
@@ -112,6 +153,19 @@ class RcubeUser extends Base
return $this->user;
}
+ /**
+ *
+ */
+ private function get_factors()
+ {
+ if ($user = $this->get_user($this->username)) {
+ $prefs = $user->get_prefs();
+ return (array)$prefs[$this->key2property('blob')];
+ }
+
+ return null;
+ }
+
/**
*
*/
@@ -123,7 +177,7 @@ class RcubeUser extends Base
}
// default
- return 'kolab_2fa_props_' . $key;
+ return 'kolab_2fa_' . $key;
}
}
diff --git a/plugins/kolab_2fa/localization/en_US.inc b/plugins/kolab_2fa/localization/en_US.inc
index 81dea1eb..9f0202b9 100644
--- a/plugins/kolab_2fa/localization/en_US.inc
+++ b/plugins/kolab_2fa/localization/en_US.inc
@@ -23,8 +23,9 @@ $labels['yubikey'] = 'Yubikey';
$labels['or'] = 'or';
$labels['yes'] = 'yes';
-$labels['now'] = 'no';
+$labels['no'] = 'no';
+$labels['label'] = 'Name';
$labels['qrcode'] = 'QR Code';
$labels['showqrcode'] = 'Show QR Code';
$labels['qrcodeexplaintotp'] = 'Download an authenticator app on your phone. Two apps which work well are FreeOTP and Google Authenticator, but any other TOTP app should also work.
|