Small refactoring of the 2FA storage layer + also use it for determining the active factors

This commit is contained in:
Thomas Bruederli 2015-06-08 15:58:52 +02:00
parent 96e195d005
commit 8e51918f64
4 changed files with 93 additions and 40 deletions

View file

@ -30,6 +30,7 @@ class kolab_2fa extends rcube_plugin
protected $login_verified = null;
protected $login_factors = array();
protected $drivers = array();
protected $storage;
/**
* Plugin init
@ -47,6 +48,10 @@ class kolab_2fa extends rcube_plugin
{
$rcmail = rcmail::get_instance();
// register library namespace to autoloader
$loader = include(INSTALL_PATH . 'vendor/autoload.php');
$loader->set('Kolab2FA', array($this->home . '/lib'));
if ($args['task'] === 'login' && $this->api->output) {
$this->add_texts('localization/', false);
$this->add_hook('authenticate', array($this, 'authenticate'));
@ -84,7 +89,7 @@ class kolab_2fa extends rcube_plugin
// parse $host URL
$a_host = parse_url($args['host']);
$hostname = $a_host['host'] ?: $args['host'];
$hostname = $_SESSION['hostname'] = $a_host['host'] ?: $args['host'];
// 1. find user record (and its prefs) before IMAP login
if ($user = rcube_user::query($args['user'], $hostname)) {
@ -92,7 +97,7 @@ class kolab_2fa extends rcube_plugin
}
// 2. check if this user/system has 2FA enabled
if (count($factors = (array)$rcmail->config->get('kolab_2fa_factors', array())) > 0) {
if (($storage = $this->get_storage($args['user'])) && count($factors = (array)$storage->read('active')) > 0) {
$args['abort'] = true;
// 3. flag session as temporary (no further actions allowed)
@ -101,7 +106,6 @@ class kolab_2fa extends rcube_plugin
$_SESSION['kolab_2fa_factors'] = $factors;
$_SESSION['username'] = $args['user'];
$_SESSION['hostname'] = $hostname;
$_SESSION['host'] = $args['host'];
$_SESSION['password'] = $rcmail->encrypt($args['pass']);
@ -143,8 +147,6 @@ class kolab_2fa extends rcube_plugin
$expired = $time < time() - $rcmail->config->get('kolab_2fa_timeout', 120);
if (!empty($sign) && !empty($factors) && !empty($nonce) && !$expired) {
console('VERIFY', $sign, $factors);
// TODO: check signature
// try to verify each configured factor
@ -224,7 +226,8 @@ class kolab_2fa extends rcube_plugin
// render input for each configured auth method
foreach ($this->login_factors as $i => $method) {
if ($i > 0) {
$table->add(array('colspan' => 2, 'class' => 'hint'), $this->gettext('or'));
$table->add(array('colspan' => 2, 'class' => 'title hint', 'style' => 'text-align:center'),
$this->gettext('or'));
}
$field_id = "rcmlogin2fa$method";
@ -273,12 +276,6 @@ class kolab_2fa extends rcube_plugin
return $this->drivers[$method];
}
// register library namespace to autoloader
if (!class_exists('\\Kolab3FA\\Driver\\Base', false)) {
$loader = include(INSTALL_PATH . 'vendor/autoload.php');
$loader->set('Kolab2FA', array($this->home . '/lib'));
}
$config = $rcmail->config->get('kolab_2fa_' . $method, array());
// use product name as "issuer""
@ -292,10 +289,7 @@ class kolab_2fa extends rcube_plugin
$driver = \Kolab2FA\Driver\Base::factory($method, $config);
// attach storage
$driver->storage = \Kolab2FA\Storage\Base::factory(
$rcmail->config->get('kolab_2fa_storage', 'roundcube'),
$rcmail->config->get('kolab_2fa_storage_config', array())
);
$driver->storage = $this->get_storage();
// set user properties from active session
if ($rcmail->user->ID) {
@ -320,6 +314,37 @@ class kolab_2fa extends rcube_plugin
return false;
}
/**
* Getter for a storage instance singleton
*/
public function get_storage($for = null)
{
if (!isset($this->storage) || (!empty($for) && $this->storage->username !== $for)) {
$rcmail = rcmail::get_instance();
try {
$this->storage = \Kolab2FA\Storage\Base::factory(
$rcmail->config->get('kolab_2fa_storage', 'roundcube'),
$rcmail->config->get('kolab_2fa_storage_config', array())
);
$this->storage->set_username($for);
}
catch (Exception $e) {
$this->storage = false;
rcube::raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => $error),
true, false);
}
}
return $this->storage;
}
/**
* Handler for 'settings_actions' hook
*/
@ -365,7 +390,8 @@ class kolab_2fa extends rcube_plugin
public function settings_factoradder($attrib)
{
$rcmail = rcmail::get_instance();
$active = (array)$rcmail->config->get('kolab_2fa_factors', array());
$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') . '...', '');
@ -397,8 +423,9 @@ class kolab_2fa extends rcube_plugin
public function settings_form($attrib = array())
{
$rcmail = rcmail::get_instance();
$storage = $this->get_storage($rcmail->get_user_name());
$factors = $storage ? (array)$storage->read('active') : array();
$drivers = (array)$rcmail->config->get('kolab_2fa_drivers', array());
$factors = (array)$rcmail->config->get('kolab_2fa_factors', array());
foreach ($drivers as $j => $method) {
$out .= $this->settings_factor($method, $attrib);
@ -432,10 +459,8 @@ class kolab_2fa extends rcube_plugin
$out = '';
$rcmail = rcmail::get_instance();
$attrib += array('class' => 'propform');
$factors = (array)$rcmail->config->get('kolab_2fa_factors', array());
if ($driver = $this->get_driver($method)) {
$active = in_array($method, $factors);
$table = new html_table(array('cols' => 2, 'class' => $attrib['class']));
foreach ($driver->props() as $field => $prop) {
@ -530,7 +555,8 @@ class kolab_2fa extends rcube_plugin
$data = @json_decode(rcube_utils::get_input_value('_data', rcube_utils::INPUT_POST), true);
$rcmail = rcmail::get_instance();
$active = (array)$rcmail->config->get('kolab_2fa_factors', array());
$storage = $this->get_storage($rcmail->get_user_name());
$active = $storage ? (array)$storage->read('active') : array();
$success = false;
$errors = 0;
$save_data = array();
@ -575,7 +601,7 @@ class kolab_2fa extends rcube_plugin
// update list of active factors for this user
if (!$errors) {
$success = $rcmail->user->save_prefs(array('kolab_2fa_factors' => $active));
$success = $storage && $storage->write('active', $active);
$save_data = $data !== false ? $this->format_props($driver->props()) : array();
}
}

View file

@ -197,7 +197,7 @@ abstract class Base
$setter = 'set_' . $key;
if (method_exists($this, $setter)) {
call_user_method($this, $setter, $value);
call_user_func(array($this, $setter), $value);
}
else if (in_array($key, $this->allowed_props)) {
$this->props[$key] = $value;
@ -205,7 +205,21 @@ abstract class Base
return true;
}
/**
* Dedicated setter for the username property
*/
public function set_username($username)
{
$this->props['username'] = $username;
if ($this->storage) {
$this->storage->set_username($username);
}
return true;
}
/**
* Clear data stored for this driver
*/
@ -222,7 +236,7 @@ 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->username . ':' . $this->method);
$this->user_props = (array)$this->storage->read($this->method);
}
return $this->user_props[$key];
@ -237,10 +251,9 @@ abstract class Base
$this->user_props[$key] = $value;
if ($this->user_settings[$key] && $this->storage) {
$storage_key = $this->username . ':' . $this->method;
$props = (array)$this->storage->read($storage_key);
$props = (array)$this->storage->read($this->method);
$props[$key] = $value;
$success = $this->storage->write($storage_key, $props);
$success = $this->storage->write($this->method, $props);
}
return $success;

View file

@ -25,6 +25,7 @@ namespace Kolab2FA\Storage;
abstract class Base
{
public $username = null;
protected $config = array();
/**
@ -64,6 +65,14 @@ abstract class Base
$this->config = array_merge($this->config, $config);
}
/**
* Set username to store data for
*/
public function set_username($username)
{
$this->username = $username;
}
/**
* Read data for the given key
*/

View file

@ -29,6 +29,7 @@ use \rcube_user;
class RcubeUser extends Base
{
private $cache = array();
private $user;
public function init(array $config)
{
@ -43,11 +44,9 @@ class RcubeUser extends Base
*/
public function read($key)
{
list($username, $method) = $this->split_key($key);
if (!isset($this->cache[$key]) && ($user = $this->get_user($username))) {
if (!isset($this->cache[$key]) && ($user = $this->get_user($this->username))) {
$prefs = $user->get_prefs();
$pkey = 'kolab_2fa_props_' . $method;
$pkey = 'kolab_2fa_props_' . $key;
$this->cache[$key] = $prefs[$pkey];
}
@ -59,11 +58,9 @@ class RcubeUser extends Base
*/
public function write($key, $value)
{
list($username, $method) = $this->split_key($key);
if ($user = $this->get_user($username)) {
if ($user = $this->get_user($this->username)) {
$this->cache[$key] = $value;
$pkey = 'kolab_2fa_props_' . $method;
$pkey = 'kolab_2fa_props_' . $key;
return $user->save_prefs(array($pkey => $value), true);
}
@ -79,11 +76,15 @@ class RcubeUser extends Base
}
/**
* Helper method to split the storage key into username and auth-method
* Set username to store data for
*/
private function split_key($key)
public function set_username($username)
{
return explode(':', $key, 2);
parent::set_username($username);
// reset cached values
$this->cache = array();
$this->user = null;
}
/**
@ -97,7 +98,11 @@ class RcubeUser extends Base
return $rcmail->user;
}
return rcube_user::query($username, $this->config['hostname']);
if (!$this->user) {
$this->user = rcube_user::query($username, $this->config['hostname']);
}
return $this->user;
}
}