kolab_2fa: Bump spomky-labs/otphp to version 10

Fixes various PHP8 warnings
This commit is contained in:
Aleksander Machniak 2023-06-15 12:26:53 +02:00
parent 84f10a366b
commit 70810e1f88
9 changed files with 84 additions and 274 deletions

View file

@ -26,7 +26,7 @@
"require": {
"php": ">=5.3.0",
"roundcube/plugin-installer": ">=0.1.3",
"spomky-labs/otphp": "~5.0.0",
"spomky-labs/otphp": "~10.0.3",
"endroid/qr-code": "~1.6.5",
"enygma/yubikey": "~3.2"
}

View file

@ -193,7 +193,7 @@ class kolab_2fa extends rcube_plugin
$_POST['_host'] = $_SESSION['host'];
$_POST['_pass'] = $rcmail->decrypt($_SESSION['password']);
if ($_SESSION['kolab_auth_admin']) {
if (!empty($_SESSION['kolab_auth_admin'])) {
$_POST['_user'] = $_SESSION['kolab_auth_admin'];
$_POST['_loginas'] = $_SESSION['username'];
}
@ -553,10 +553,11 @@ class kolab_2fa extends rcube_plugin
// add row for displaying the QR code
if (method_exists($driver, 'get_provisioning_uri')) {
$gif = 'data:image/gif;base64,R0lGODlhDwAPAIAAAMDAwAAAACH5BAEAAAAALAAAAAAPAA8AQAINhI+py+0Po5y02otnAQA7';
$table->add('title', $this->gettext('qrcode'));
$table->add(null,
$table->add('pl-3 pr-3',
html::div('explain form-text', $this->gettext("qrcodeexplain$method"))
. html::tag('img', array('src' => 'data:image/gif;base64,R0lGODlhDwAPAIAAAMDAwAAAACH5BAEAAAAALAAAAAAPAA8AQAINhI+py+0Po5y02otnAQA7', 'class' => 'qrcode', 'rel' => $method))
. html::tag('img', array('src' => $gif, 'class' => 'qrcode mt-2', 'rel' => $method))
);
// add row for testing the factor
@ -566,7 +567,6 @@ class kolab_2fa extends rcube_plugin
html::tag('input', array('type' => 'text', 'name' => '_verify_code', 'id' => $field_id, 'class' => 'k2fa-verify', 'size' => 20, 'required' => true)) .
html::div('explain form-text', $this->gettext("verifycodeexplain$method"))
);
}
$input_id = new html_hiddenfield(array('name' => '_prop[id]', 'value' => ''));

View file

@ -112,9 +112,10 @@ abstract class Base
/**
* Verify the submitted authentication code
*
* @param string $code The 2nd authentication factor to verify
* @param int $timestamp Timestamp of authentication process (window start)
* @return boolean True if valid, false otherwise
* @param string $code The 2nd authentication factor to verify
* @param int $timestamp Timestamp of authentication process (window start)
*
* @return bool True if valid, false otherwise
*/
abstract function verify($code, $timestamp = null);
@ -160,7 +161,7 @@ abstract class Base
}
/**
* Implement this method if the driver can be prpvisioned via QR code
* Implement this method if the driver can be provisioned via QR code
*/
/* abstract function get_provisioning_uri(); */
@ -181,6 +182,7 @@ abstract class Base
for ($i = 0; $i < $length; $i++) {
$secret .= $chars[array_rand($chars)];
}
return $secret;
}
@ -291,6 +293,14 @@ abstract class Base
return false;
}
/**
* Checks that a string contains a semicolon
*/
protected function hasSemicolon($value)
{
return preg_match('/(:|%3A)/i', (string) $value) > 0;
}
/**
* Getter for per-user properties for this method
*/

View file

@ -57,13 +57,28 @@ class HOTP extends Base
),
);
if (!in_array($this->config['digest'], array('md5', 'sha1', 'sha256', 'sha512'))) {
throw new \Exception("'{$this->config['digest']}' digest is not supported.");
}
if (!is_numeric($this->config['digits']) || $this->config['digits'] < 1) {
throw new \Exception('Digits must be at least 1.');
}
if ($this->hasSemicolon($this->config['issuer'])) {
throw new \Exception('Issuer must not contain a semi-colon.');
}
// copy config options
$this->backend = new \Kolab2FA\OTP\HOTP();
$this->backend
->setDigits($this->config['digits'])
->setDigest($this->config['digest'])
->setIssuer($this->config['issuer'])
->setIssuerIncludedAsParameter(true);
$this->backend = \OTPHP\HOTP::create(
null, // secret
0, // counter
$this->config['digest'], // digest
$this->config['digits'] // digits
);
$this->backend->setIssuer($this->config['issuer']);
$this->backend->setIssuerIncludedAsParameter(true);
}
/**
@ -82,8 +97,11 @@ class HOTP extends Base
}
try {
$this->backend->setLabel($this->username)->setSecret($secret)->setCounter(intval($this->get('counter')));
$pass = $this->backend->verify($code, $counter, $this->config['window']);
$this->backend->setLabel($this->username);
$this->backend->setSecret($secret);
$this->backend->setCounter(intval($this->get('counter')));
$pass = $this->backend->verify($code, $counter, (int) $this->config['window']);
// store incremented counter value
$this->set('counter', $this->backend->getCounter());
@ -100,7 +118,7 @@ class HOTP extends Base
}
/**
*
* Get the provisioning URI.
*/
public function get_provisioning_uri()
{
@ -114,7 +132,10 @@ class HOTP extends Base
// TODO: deny call if already active?
$this->backend->setLabel($this->username)->setSecret($this->secret)->setCounter(intval($this->get('counter')));
$this->backend->setLabel($this->username);
$this->backend->setSecret($this->secret);
$this->backend->setCounter(intval($this->get('counter')));
return $this->backend->getProvisioningUri();
}

View file

@ -51,14 +51,32 @@ class TOTP extends Base
),
);
if (!in_array($this->config['digest'], array('md5', 'sha1', 'sha256', 'sha512'))) {
throw new \Exception("'{$this->config['digest']}' digest is not supported.");
}
if (!is_numeric($this->config['digits']) || $this->config['digits'] < 1) {
throw new \Exception('Digits must be at least 1.');
}
if (!is_numeric($this->config['interval']) || $this->config['interval'] < 1) {
throw new \Exception('Interval must be at least 1.');
}
if ($this->hasSemicolon($this->config['issuer'])) {
throw new \Exception('Issuer must not contain a semi-colon.');
}
// copy config options
$this->backend = new \Kolab2FA\OTP\TOTP();
$this->backend
->setDigits($this->config['digits'])
->setInterval($this->config['interval'])
->setDigest($this->config['digest'])
->setIssuer($this->config['issuer'])
->setIssuerIncludedAsParameter(true);
$this->backend = \OTPHP\TOTP::create(
null, //secret
$this->config['interval'], // period
$this->config['digest'], // digest
$this->config['digits'] // digits
);
$this->backend->setIssuer($this->config['issuer']);
$this->backend->setIssuerIncludedAsParameter(true);
}
/**
@ -75,11 +93,12 @@ class TOTP extends Base
return false;
}
$this->backend->setLabel($this->username)->setSecret($secret);
$this->backend->setLabel($this->username);
$this->backend->setSecret($secret);
// Pass a window to indicate the maximum timeslip between client (mobile
// device) and server.
$pass = $this->backend->verify($code, $timestamp, 150);
$pass = $this->backend->verify($code, (int) $timestamp, 150);
// try all codes from $timestamp till now
if (!$pass && $timestamp) {
@ -95,7 +114,7 @@ class TOTP extends Base
}
/**
*
* Get the provisioning URI.
*/
public function get_provisioning_uri()
{
@ -110,8 +129,9 @@ class TOTP extends Base
// TODO: deny call if already active?
$this->backend->setLabel($this->username)->setSecret($this->secret);
$this->backend->setLabel($this->username);
$this->backend->setSecret($this->secret);
return $this->backend->getProvisioningUri();
}
}

View file

@ -1,58 +0,0 @@
<?php
/**
* Kolab HOTP implementation based on Spomky-Labs/otphp
*
* This basically follows the exmaple implementation from
* https://github.com/Spomky-Labs/otphp/tree/master/examples
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2015, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Kolab2FA\OTP;
use OTPHP\HOTP as Base;
class HOTP extends Base
{
use OTP;
protected $counter = 0;
public function setCounter($counter)
{
if (!is_integer($counter) || $counter < 0) {
throw new \Exception('Counter must be at least 0.');
}
$this->counter = $counter;
return $this;
}
public function getCounter()
{
return $this->counter;
}
public function updateCounter($counter)
{
$this->counter = $counter;
return $this;
}
}

View file

@ -1,133 +0,0 @@
<?php
/**
* Kolab OTP trait based on Spomky-Labs/otphp
*
* This basically follows the exmaple implementation from
* https://github.com/Spomky-Labs/otphp/tree/master/examples
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2015, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Kolab2FA\OTP;
trait OTP
{
protected $secret = null;
protected $issuer = null;
protected $issuer_included_as_parameter = false;
protected $label = null;
protected $digest = 'sha1';
protected $digits = 6;
public function setSecret($secret)
{
$this->secret = $secret;
return $this;
}
public function getSecret()
{
return $this->secret;
}
public function setLabel($label)
{
if ($this->hasSemicolon($label)) {
throw new \Exception('Label must not contain a semi-colon.');
}
$this->label = $label;
return $this;
}
public function getLabel()
{
return $this->label;
}
public function setIssuer($issuer)
{
if ($this->hasSemicolon($issuer)) {
throw new \Exception('Issuer must not contain a semi-colon.');
}
$this->issuer = $issuer;
return $this;
}
public function getIssuer()
{
return $this->issuer;
}
public function isIssuerIncludedAsParameter()
{
return $this->issuer_included_as_parameter;
}
public function setIssuerIncludedAsParameter($issuer_included_as_parameter)
{
$this->issuer_included_as_parameter = $issuer_included_as_parameter;
return $this;
}
public function setDigits($digits)
{
if (!is_numeric($digits) || $digits < 1) {
throw new \Exception('Digits must be at least 1.');
}
$this->digits = $digits;
return $this;
}
public function getDigits()
{
return $this->digits;
}
public function setDigest($digest)
{
if (!in_array($digest, array('md5', 'sha1', 'sha256', 'sha512'))) {
throw new \Exception("'$digest' digest is not supported.");
}
$this->digest = $digest;
return $this;
}
public function getDigest()
{
return $this->digest;
}
private function hasSemicolon($value)
{
$semicolons = array(':', '%3A', '%3a');
foreach ($semicolons as $semicolon) {
if (false !== strpos($value, $semicolon)) {
return true;
}
}
return false;
}
}

View file

@ -1,50 +0,0 @@
<?php
/**
* Kolab TOTP implementation based on Spomky-Labs/otphp
*
* This basically follows the exmaple implementation from
* https://github.com/Spomky-Labs/otphp/tree/master/examples
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2015, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Kolab2FA\OTP;
use OTPHP\TOTP as Base;
class TOTP extends Base
{
use OTP;
protected $interval = 30;
public function setInterval($interval)
{
if (!is_integer($interval) || $interval < 1) {
throw new \Exception('Interval must be at least 1.');
}
$this->interval = $interval;
return $this;
}
public function getInterval()
{
return $this->interval;
}
}

View file

@ -65,7 +65,7 @@ class RcubeUser extends Base
{
if (!isset($this->cache[$key])) {
$factors = $this->get_factors();
$this->log(LOG_DEBUG, 'RcubeUser::read() ' . $key);
$this->log(LOG_DEBUG, "RcubeUser::read({$key})");
$this->cache[$key] = $factors[$key];
}
@ -77,7 +77,7 @@ class RcubeUser extends Base
*/
public function write($key, $value)
{
$this->log(LOG_DEBUG, 'RcubeUser::write() ' . @json_encode($value));
$this->log(LOG_DEBUG, "RcubeUser::write({$key}) " . @json_encode($value));
if ($user = $this->get_user($this->username)) {
$this->cache[$key] = $value;