Small refactoring of the 2FA storage layer + also use it for determining the active factors
This commit is contained in:
parent
96e195d005
commit
8e51918f64
4 changed files with 93 additions and 40 deletions
|
@ -30,6 +30,7 @@ class kolab_2fa extends rcube_plugin
|
||||||
protected $login_verified = null;
|
protected $login_verified = null;
|
||||||
protected $login_factors = array();
|
protected $login_factors = array();
|
||||||
protected $drivers = array();
|
protected $drivers = array();
|
||||||
|
protected $storage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin init
|
* Plugin init
|
||||||
|
@ -47,6 +48,10 @@ class kolab_2fa extends rcube_plugin
|
||||||
{
|
{
|
||||||
$rcmail = rcmail::get_instance();
|
$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) {
|
if ($args['task'] === 'login' && $this->api->output) {
|
||||||
$this->add_texts('localization/', false);
|
$this->add_texts('localization/', false);
|
||||||
$this->add_hook('authenticate', array($this, 'authenticate'));
|
$this->add_hook('authenticate', array($this, 'authenticate'));
|
||||||
|
@ -84,7 +89,7 @@ class kolab_2fa extends rcube_plugin
|
||||||
|
|
||||||
// parse $host URL
|
// parse $host URL
|
||||||
$a_host = parse_url($args['host']);
|
$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
|
// 1. find user record (and its prefs) before IMAP login
|
||||||
if ($user = rcube_user::query($args['user'], $hostname)) {
|
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
|
// 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;
|
$args['abort'] = true;
|
||||||
|
|
||||||
// 3. flag session as temporary (no further actions allowed)
|
// 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['kolab_2fa_factors'] = $factors;
|
||||||
|
|
||||||
$_SESSION['username'] = $args['user'];
|
$_SESSION['username'] = $args['user'];
|
||||||
$_SESSION['hostname'] = $hostname;
|
|
||||||
$_SESSION['host'] = $args['host'];
|
$_SESSION['host'] = $args['host'];
|
||||||
$_SESSION['password'] = $rcmail->encrypt($args['pass']);
|
$_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);
|
$expired = $time < time() - $rcmail->config->get('kolab_2fa_timeout', 120);
|
||||||
|
|
||||||
if (!empty($sign) && !empty($factors) && !empty($nonce) && !$expired) {
|
if (!empty($sign) && !empty($factors) && !empty($nonce) && !$expired) {
|
||||||
console('VERIFY', $sign, $factors);
|
|
||||||
|
|
||||||
// TODO: check signature
|
// TODO: check signature
|
||||||
|
|
||||||
// try to verify each configured factor
|
// try to verify each configured factor
|
||||||
|
@ -224,7 +226,8 @@ class kolab_2fa extends rcube_plugin
|
||||||
// render input for each configured auth method
|
// render input for each configured auth method
|
||||||
foreach ($this->login_factors as $i => $method) {
|
foreach ($this->login_factors as $i => $method) {
|
||||||
if ($i > 0) {
|
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";
|
$field_id = "rcmlogin2fa$method";
|
||||||
|
@ -273,12 +276,6 @@ class kolab_2fa extends rcube_plugin
|
||||||
return $this->drivers[$method];
|
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());
|
$config = $rcmail->config->get('kolab_2fa_' . $method, array());
|
||||||
|
|
||||||
// use product name as "issuer""
|
// use product name as "issuer""
|
||||||
|
@ -292,10 +289,7 @@ class kolab_2fa extends rcube_plugin
|
||||||
$driver = \Kolab2FA\Driver\Base::factory($method, $config);
|
$driver = \Kolab2FA\Driver\Base::factory($method, $config);
|
||||||
|
|
||||||
// attach storage
|
// attach storage
|
||||||
$driver->storage = \Kolab2FA\Storage\Base::factory(
|
$driver->storage = $this->get_storage();
|
||||||
$rcmail->config->get('kolab_2fa_storage', 'roundcube'),
|
|
||||||
$rcmail->config->get('kolab_2fa_storage_config', array())
|
|
||||||
);
|
|
||||||
|
|
||||||
// set user properties from active session
|
// set user properties from active session
|
||||||
if ($rcmail->user->ID) {
|
if ($rcmail->user->ID) {
|
||||||
|
@ -320,6 +314,37 @@ class kolab_2fa extends rcube_plugin
|
||||||
return false;
|
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
|
* Handler for 'settings_actions' hook
|
||||||
*/
|
*/
|
||||||
|
@ -365,7 +390,8 @@ class kolab_2fa extends rcube_plugin
|
||||||
public function settings_factoradder($attrib)
|
public function settings_factoradder($attrib)
|
||||||
{
|
{
|
||||||
$rcmail = rcmail::get_instance();
|
$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 = new html_select(array('id' => 'kolab2fa-add'));
|
||||||
$select->add($this->gettext('addfactor') . '...', '');
|
$select->add($this->gettext('addfactor') . '...', '');
|
||||||
|
@ -397,8 +423,9 @@ class kolab_2fa extends rcube_plugin
|
||||||
public function settings_form($attrib = array())
|
public function settings_form($attrib = array())
|
||||||
{
|
{
|
||||||
$rcmail = rcmail::get_instance();
|
$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());
|
$drivers = (array)$rcmail->config->get('kolab_2fa_drivers', array());
|
||||||
$factors = (array)$rcmail->config->get('kolab_2fa_factors', array());
|
|
||||||
|
|
||||||
foreach ($drivers as $j => $method) {
|
foreach ($drivers as $j => $method) {
|
||||||
$out .= $this->settings_factor($method, $attrib);
|
$out .= $this->settings_factor($method, $attrib);
|
||||||
|
@ -432,10 +459,8 @@ class kolab_2fa extends rcube_plugin
|
||||||
$out = '';
|
$out = '';
|
||||||
$rcmail = rcmail::get_instance();
|
$rcmail = rcmail::get_instance();
|
||||||
$attrib += array('class' => 'propform');
|
$attrib += array('class' => 'propform');
|
||||||
$factors = (array)$rcmail->config->get('kolab_2fa_factors', array());
|
|
||||||
|
|
||||||
if ($driver = $this->get_driver($method)) {
|
if ($driver = $this->get_driver($method)) {
|
||||||
$active = in_array($method, $factors);
|
|
||||||
$table = new html_table(array('cols' => 2, 'class' => $attrib['class']));
|
$table = new html_table(array('cols' => 2, 'class' => $attrib['class']));
|
||||||
|
|
||||||
foreach ($driver->props() as $field => $prop) {
|
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);
|
$data = @json_decode(rcube_utils::get_input_value('_data', rcube_utils::INPUT_POST), true);
|
||||||
|
|
||||||
$rcmail = rcmail::get_instance();
|
$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;
|
$success = false;
|
||||||
$errors = 0;
|
$errors = 0;
|
||||||
$save_data = array();
|
$save_data = array();
|
||||||
|
@ -575,7 +601,7 @@ class kolab_2fa extends rcube_plugin
|
||||||
|
|
||||||
// update list of active factors for this user
|
// update list of active factors for this user
|
||||||
if (!$errors) {
|
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();
|
$save_data = $data !== false ? $this->format_props($driver->props()) : array();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,7 +197,7 @@ abstract class Base
|
||||||
|
|
||||||
$setter = 'set_' . $key;
|
$setter = 'set_' . $key;
|
||||||
if (method_exists($this, $setter)) {
|
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)) {
|
else if (in_array($key, $this->allowed_props)) {
|
||||||
$this->props[$key] = $value;
|
$this->props[$key] = $value;
|
||||||
|
@ -205,7 +205,21 @@ abstract class Base
|
||||||
|
|
||||||
return true;
|
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
|
* Clear data stored for this driver
|
||||||
*/
|
*/
|
||||||
|
@ -222,7 +236,7 @@ abstract class Base
|
||||||
protected function get_user_prop($key)
|
protected function get_user_prop($key)
|
||||||
{
|
{
|
||||||
if (!isset($this->user_props[$key]) && $this->storage) {
|
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];
|
return $this->user_props[$key];
|
||||||
|
@ -237,10 +251,9 @@ abstract class Base
|
||||||
$this->user_props[$key] = $value;
|
$this->user_props[$key] = $value;
|
||||||
|
|
||||||
if ($this->user_settings[$key] && $this->storage) {
|
if ($this->user_settings[$key] && $this->storage) {
|
||||||
$storage_key = $this->username . ':' . $this->method;
|
$props = (array)$this->storage->read($this->method);
|
||||||
$props = (array)$this->storage->read($storage_key);
|
|
||||||
$props[$key] = $value;
|
$props[$key] = $value;
|
||||||
$success = $this->storage->write($storage_key, $props);
|
$success = $this->storage->write($this->method, $props);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $success;
|
return $success;
|
||||||
|
|
|
@ -25,6 +25,7 @@ namespace Kolab2FA\Storage;
|
||||||
|
|
||||||
abstract class Base
|
abstract class Base
|
||||||
{
|
{
|
||||||
|
public $username = null;
|
||||||
protected $config = array();
|
protected $config = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,6 +65,14 @@ abstract class Base
|
||||||
$this->config = array_merge($this->config, $config);
|
$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
|
* Read data for the given key
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -29,6 +29,7 @@ use \rcube_user;
|
||||||
class RcubeUser extends Base
|
class RcubeUser extends Base
|
||||||
{
|
{
|
||||||
private $cache = array();
|
private $cache = array();
|
||||||
|
private $user;
|
||||||
|
|
||||||
public function init(array $config)
|
public function init(array $config)
|
||||||
{
|
{
|
||||||
|
@ -43,11 +44,9 @@ class RcubeUser extends Base
|
||||||
*/
|
*/
|
||||||
public function read($key)
|
public function read($key)
|
||||||
{
|
{
|
||||||
list($username, $method) = $this->split_key($key);
|
if (!isset($this->cache[$key]) && ($user = $this->get_user($this->username))) {
|
||||||
|
|
||||||
if (!isset($this->cache[$key]) && ($user = $this->get_user($username))) {
|
|
||||||
$prefs = $user->get_prefs();
|
$prefs = $user->get_prefs();
|
||||||
$pkey = 'kolab_2fa_props_' . $method;
|
$pkey = 'kolab_2fa_props_' . $key;
|
||||||
$this->cache[$key] = $prefs[$pkey];
|
$this->cache[$key] = $prefs[$pkey];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,11 +58,9 @@ class RcubeUser extends Base
|
||||||
*/
|
*/
|
||||||
public function write($key, $value)
|
public function write($key, $value)
|
||||||
{
|
{
|
||||||
list($username, $method) = $this->split_key($key);
|
if ($user = $this->get_user($this->username)) {
|
||||||
|
|
||||||
if ($user = $this->get_user($username)) {
|
|
||||||
$this->cache[$key] = $value;
|
$this->cache[$key] = $value;
|
||||||
$pkey = 'kolab_2fa_props_' . $method;
|
$pkey = 'kolab_2fa_props_' . $key;
|
||||||
return $user->save_prefs(array($pkey => $value), true);
|
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 $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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue