From 70810e1f88f42421e17483461c77a7d7dcdf360d Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 15 Jun 2023 12:26:53 +0200 Subject: [PATCH] kolab_2fa: Bump spomky-labs/otphp to version 10 Fixes various PHP8 warnings --- plugins/kolab_2fa/composer.json | 2 +- plugins/kolab_2fa/kolab_2fa.php | 8 +- .../kolab_2fa/lib/Kolab2FA/Driver/Base.php | 18 ++- .../kolab_2fa/lib/Kolab2FA/Driver/HOTP.php | 41 ++++-- .../kolab_2fa/lib/Kolab2FA/Driver/TOTP.php | 44 ++++-- plugins/kolab_2fa/lib/Kolab2FA/OTP/HOTP.php | 58 -------- plugins/kolab_2fa/lib/Kolab2FA/OTP/OTP.php | 133 ------------------ plugins/kolab_2fa/lib/Kolab2FA/OTP/TOTP.php | 50 ------- .../lib/Kolab2FA/Storage/RcubeUser.php | 4 +- 9 files changed, 84 insertions(+), 274 deletions(-) delete mode 100644 plugins/kolab_2fa/lib/Kolab2FA/OTP/HOTP.php delete mode 100644 plugins/kolab_2fa/lib/Kolab2FA/OTP/OTP.php delete mode 100644 plugins/kolab_2fa/lib/Kolab2FA/OTP/TOTP.php diff --git a/plugins/kolab_2fa/composer.json b/plugins/kolab_2fa/composer.json index 4198643d..f1c5d964 100644 --- a/plugins/kolab_2fa/composer.json +++ b/plugins/kolab_2fa/composer.json @@ -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" } diff --git a/plugins/kolab_2fa/kolab_2fa.php b/plugins/kolab_2fa/kolab_2fa.php index 2f684bfc..b09d6b3c 100644 --- a/plugins/kolab_2fa/kolab_2fa.php +++ b/plugins/kolab_2fa/kolab_2fa.php @@ -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' => '')); diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Driver/Base.php b/plugins/kolab_2fa/lib/Kolab2FA/Driver/Base.php index 0af9dbd4..e46dfa85 100644 --- a/plugins/kolab_2fa/lib/Kolab2FA/Driver/Base.php +++ b/plugins/kolab_2fa/lib/Kolab2FA/Driver/Base.php @@ -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 */ diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Driver/HOTP.php b/plugins/kolab_2fa/lib/Kolab2FA/Driver/HOTP.php index 8093c11c..a1e3152e 100644 --- a/plugins/kolab_2fa/lib/Kolab2FA/Driver/HOTP.php +++ b/plugins/kolab_2fa/lib/Kolab2FA/Driver/HOTP.php @@ -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(); } diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Driver/TOTP.php b/plugins/kolab_2fa/lib/Kolab2FA/Driver/TOTP.php index 5f294bff..926890ce 100644 --- a/plugins/kolab_2fa/lib/Kolab2FA/Driver/TOTP.php +++ b/plugins/kolab_2fa/lib/Kolab2FA/Driver/TOTP.php @@ -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(); } - } diff --git a/plugins/kolab_2fa/lib/Kolab2FA/OTP/HOTP.php b/plugins/kolab_2fa/lib/Kolab2FA/OTP/HOTP.php deleted file mode 100644 index 8b895b0b..00000000 --- a/plugins/kolab_2fa/lib/Kolab2FA/OTP/HOTP.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * Copyright (C) 2015, Kolab Systems AG - * - * 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 . - */ - - -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; - } -} \ No newline at end of file diff --git a/plugins/kolab_2fa/lib/Kolab2FA/OTP/OTP.php b/plugins/kolab_2fa/lib/Kolab2FA/OTP/OTP.php deleted file mode 100644 index aec816cc..00000000 --- a/plugins/kolab_2fa/lib/Kolab2FA/OTP/OTP.php +++ /dev/null @@ -1,133 +0,0 @@ - - * - * Copyright (C) 2015, Kolab Systems AG - * - * 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 . - */ - -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; - } -} \ No newline at end of file diff --git a/plugins/kolab_2fa/lib/Kolab2FA/OTP/TOTP.php b/plugins/kolab_2fa/lib/Kolab2FA/OTP/TOTP.php deleted file mode 100644 index 1e265831..00000000 --- a/plugins/kolab_2fa/lib/Kolab2FA/OTP/TOTP.php +++ /dev/null @@ -1,50 +0,0 @@ - - * - * Copyright (C) 2015, Kolab Systems AG - * - * 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 . - */ - -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; - } -} \ No newline at end of file diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Storage/RcubeUser.php b/plugins/kolab_2fa/lib/Kolab2FA/Storage/RcubeUser.php index 74c32c92..e8bfcd8e 100644 --- a/plugins/kolab_2fa/lib/Kolab2FA/Storage/RcubeUser.php +++ b/plugins/kolab_2fa/lib/Kolab2FA/Storage/RcubeUser.php @@ -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;