first commit

This commit is contained in:
2024-07-15 11:28:08 +02:00
commit f52d538ea5
21891 changed files with 6161164 additions and 0 deletions

View File

@@ -0,0 +1,133 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Application;
use Awf\Encrypt\Totp;
class UserAuthenticationGoogle extends UserAuthenticationOtep
{
/**
* Is this user authenticated by this object? The $params array contains at least one key, 'password'.
*
* @param array $params The parameters used in the authentication process
*
* @return boolean True if the user is authenticated (or this plugin doesn't apply), false otherwise
*/
public function onAuthentication($params = array())
{
$result = true;
$userParams = $this->user->getParameters();
if ($userParams->get('tfa.method', 'none') == 'google')
{
$result = false;
$secret = isset($params['secret']) ? $params['secret'] : '';
if (!empty($secret))
{
$result = $this->validateGoogleOTP($secret);
if (!$result)
{
$result = $this->validateOtep($secret);
}
}
}
return $result;
}
public function onTfaSave($tfaParams)
{
$tfaMethod = isset($tfaParams['method']) ? $tfaParams['method'] : '';
if ($tfaMethod == 'google')
{
// The Google Authenticator key set by the user in the form
$newKey = isset($tfaParams['google']) ? $tfaParams['google'] : '';
// The Google Authenticator key in the user object
$oldKey = $this->user->getParameters()->get('tfa.google', '');
// The Google Authenticator generated secret code given in the form
$secret = isset($tfaParams['secret']) ? $tfaParams['secret'] : '';
// What was the old TFA method?
$oldTfaMethod = $this->user->getParameters()->get('tfa.method');
if (($oldTfaMethod == 'google') && ($newKey == $oldKey))
{
// We had already set up Google Authenticator and the code is unchanged. No change performed here.
return true;
}
else
{
// Safe fallback until we can verify the new yubikey
$this->user->getParameters()->set('tfa', null);
$this->user->getParameters()->set('tfa.method', 'none');
if (!empty($secret) && $this->validateGoogleOTP($secret, $newKey))
{
$this->user->getParameters()->set('tfa.method', 'google');
$this->user->getParameters()->set('tfa.google', $newKey);
}
}
}
return true;
}
/**
* Validates a Google Authenticator key
*
* @param string $otp The OTP generated by Google Authenticator
* @param string $key The TOTP key (base32 encoded)
*
* @return boolean True if it's a valid OTP
*/
public function validateGoogleOTP($otp, $key = null)
{
// Create a new TOTP class with Google Authenticator compatible settings
$totp = new Totp(30, 6, 10);
// Get the key if none is defined
if (empty($key))
{
$key = $this->user->getParameters()->get('tfa.google', '');
}
// Check the code
$code = $totp->getCode($key);
$check = $code == $otp;
/*
* If the check fails, test the previous 30 second slot. This allow the
* user to enter the security code when it's becoming red in Google
* Authenticator app (reaching the end of its 30 second lifetime)
*/
if (!$check)
{
$time = time() - 30;
$code = $totp->getCode($key, $time);
$check = $code == $otp;
}
/*
* If the check fails, test the next 30 second slot. This allows some
* time drift between the authentication device and the server
*/
if (!$check)
{
$time = time() + 30;
$code = $totp->getCode($key, $time);
$check = $code == $otp;
}
return $check;
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Application;
use Awf\User\Authentication;
abstract class UserAuthenticationOtep extends Authentication
{
/**
* Validates an OTEP. If the OTEP is valid it will be removed from the list of OTEPs and the user account will be
* saved with the updated list of OTEPs.
*
* @param string $otp The OTP generated by Google Authenticator
*
* @return boolean True if it's a valid OTEP.
*/
public function validateOtep($otp)
{
// Get the OTEPs
$oteps = $this->user->getParameters()->get('tfa.otep', array());
// If there is no OTEP we can't authenticate
if (empty($oteps))
{
return false;
}
$oteps = (array)$oteps;
// Does this OTEP exist in the list?
$tempOtp = preg_filter('/\D/', '', $otp);
$otp = is_null($tempOtp) ? $otp : $tempOtp;
// No. Can't authenticate.
if (!in_array($otp, $oteps))
{
return false;
}
// Remove the OTEP from the list
$array_pos = array_search($otp, $oteps);
$temp = array();
// Ugly as heck, but PHP freaks out with the number-as-string array indexes it produces.
foreach ($oteps as $foo)
{
if ($foo == $otp)
{
continue;
}
$temp[] = $foo;
}
// Save the modified user
$this->user->getParameters()->set('tfa.otep', $temp);
$userManager = \Awf\Application\Application::getInstance()->getContainer()->userManager;
$userManager->saveUser($this->user);
// OK, we can authenticate
return true;
}
}

View File

@@ -0,0 +1,108 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Application;
use Awf\User\Authentication;
class UserAuthenticationPassword extends Authentication
{
/**
* Is this user authenticated by this object? The $params array contains at least one key, 'password'.
*
* @param array $params The parameters used in the authentication process
*
* @return boolean True if the user is authenticated (or this plugin doesn't apply), false otherwise
*/
public function onAuthentication($params = array())
{
$password = isset($params['password']) ? $params['password'] : '';
$hashedPassword = $this->user->getPassword();
if (substr($hashedPassword, 0, 4) == '$2y$')
{
return password_verify($password, $hashedPassword);
}
else
{
$parts = explode(':', $hashedPassword, 3);
switch ($parts[0])
{
case 'SHA512':
return $this->timingSafeEquals($parts[1], hash('sha512', $password . $parts[2], false));
break;
case 'SHA256':
return $this->timingSafeEquals($parts[1], hash('sha256', $password . $parts[2], false));
break;
case 'SHA1':
return $this->timingSafeEquals($parts[1], sha1($password . $parts[2]));
break;
case 'MD5':
return $this->timingSafeEquals($parts[1], md5($password . $parts[2]));
break;
}
}
// If all else fails, we assume we can't verify this password
return false;
}
public function onTfaSave($tfaParams)
{
$tfaMethod = isset($tfaParams['method']) ? $tfaParams['method'] : '';
if ($tfaMethod == 'none')
{
// Reset other TFA options
$this->user->getParameters()->set('tfa', null);
// Set the TFA method to "none"
$this->user->getParameters()->set('tfa.method', 'none');
}
return true;
}
/**
* A timing safe equals comparison
*
* To prevent leaking length information, it is important
* that user input is always used as the second parameter.
*
* @param string $safe The internal (safe) value to be checked
* @param string $user The user submitted (unsafe) value
*
* @return boolean True if the two strings are identical.
*/
protected function timingSafeEquals($safe, $user)
{
// Prevent issues if string length is 0
$safe .= chr(0);
$user .= chr(0);
$safeLen = strlen($safe);
$userLen = strlen($user);
// Set the result to the difference between the lengths
$result = $safeLen - $userLen;
// Note that we ALWAYS iterate over the user-supplied length
// This is to prevent leaking length information
for ($i = 0; $i < $userLen; $i++) {
// Using % here is a trick to prevent notices
// It's safe, since if the lengths are different
// $result is already non-0
$result |= (ord($safe[$i % $safeLen]) ^ ord($user[$i]));
}
// They are only identical strings if $result is exactly 0...
return $result === 0;
}
}

View File

@@ -0,0 +1,217 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Application;
use Akeeba\Engine\Platform;
use Awf\Application\Application;
use Awf\Download\Download;
use Awf\Uri\Uri;
class UserAuthenticationYubikey extends UserAuthenticationOtep
{
/**
* Is this user authenticated by this object? The $params array contains at least one key, 'password'.
*
* @param array $params The parameters used in the authentication process
*
* @return boolean True if the user is authenticated (or this plugin doesn't apply), false otherwise
*/
public function onAuthentication($params = array())
{
$result = true;
$userParams = $this->user->getParameters();
if ($userParams->get('tfa.method', 'none') == 'yubikey')
{
$result = false;
$secret = isset($params['secret']) ? $params['secret'] : '';
if (!empty($secret))
{
$result = $this->validateYubikeyOTP($secret);
if (!$result)
{
$result = $this->validateOtep($secret);
}
}
}
return $result;
}
public function onTfaSave($tfaParams)
{
$tfaMethod = isset($tfaParams['method']) ? $tfaParams['method'] : '';
if ($tfaMethod == 'yubikey')
{
// The YubiKey code set by the user in the form
$newCode = isset($tfaParams['yubikey']) ? $tfaParams['yubikey'] : '';
// The YubiKey code in the user object
$oldCode = $this->user->getParameters()->get('tfa.yubikey', '');
// What was the old TFA method?
$oldTfaMethod = $this->user->getParameters()->get('tfa.method');
if (($oldTfaMethod == 'yubikey') && ($newCode == $oldCode))
{
// We had already set up YubiKey and the code is unchanged. No change performed here.
return true;
}
else
{
// Safe fallback until we can verify the new yubikey
$this->user->getParameters()->set('tfa', null);
$this->user->getParameters()->set('tfa.method', 'none');
if (!empty($newCode) && $this->validateYubikeyOTP($newCode))
{
$this->user->getParameters()->set('tfa.method', 'yubikey');
$this->user->getParameters()->set('tfa.yubikey', $newCode);
}
}
}
return true;
}
/**
* Validates a Yubikey OTP against the Yubikey servers
*
* @param string $otp The OTP generated by your Yubikey
*
* @return boolean True if it's a valid OTP
*/
public function validateYubikeyOTP($otp)
{
$server_queue = array(
'api.yubico.com', 'api2.yubico.com', 'api3.yubico.com',
'api4.yubico.com', 'api5.yubico.com'
);
shuffle($server_queue);
$gotResponse = false;
$check = false;
$options = [];
$proxyParams = Platform::getInstance()->getProxySettings();
if ($proxyParams['enabled'])
{
$options['proxy'] = [
'host' => $proxyParams['host'],
'port' => $proxyParams['port'],
'user' => $proxyParams['user'],
'pass' => $proxyParams['pass'],
];
}
$http = new Download();
$http->setAdapterOptions($options);
$token = Application::getInstance()->getContainer()->session->getCsrfToken()->getValue();
$nonce = md5($token . uniqid(rand()));
$response = '';
while (!$gotResponse && !empty($server_queue))
{
$server = array_shift($server_queue);
$uri = new Uri('https://' . $server . '/wsapi/2.0/verify');
// I don't see where this ID is used?
$uri->setVar('id', 1);
// The OTP we read from the user
$uri->setVar('otp', $otp);
// This prevents a REPLAYED_OTP status of the token doesn't change
// after a user submits an invalid OTP
$uri->setVar('nonce', $nonce);
// Minimum service level required: 50% (at least 50% of the YubiCloud
// servers must reply positively for the OTP to validate)
$uri->setVar('sl', 50);
// Timeou waiting for YubiCloud servers to reply: 5 seconds.
$uri->setVar('timeout', 5);
try
{
$response = $http->getFromURL($uri->toString());
if (!empty($response))
{
$gotResponse = true;
}
else
{
continue;
}
}
catch (\Exception $exc)
{
// No response, continue with the next server
continue;
}
}
// No server replied; we can't validate this OTP
if (!$gotResponse)
{
return false;
}
// Parse response
$lines = explode("\n", $response);
$data = array();
foreach ($lines as $line)
{
$line = trim($line);
$parts = explode('=', $line, 2);
if (count($parts) < 2)
{
continue;
}
$data[$parts[0]] = $parts[1];
}
// Validate the response - We need an OK message reply
if ($data['status'] != 'OK')
{
return false;
}
// Validate the response - We need a confidence level over 50%
if ($data['sl'] < 50)
{
return false;
}
// Validate the response - The OTP must match
if ($data['otp'] != $otp)
{
return false;
}
// Validate the response - The token must match
if ($data['nonce'] != $nonce)
{
return false;
}
return true;
}
}

View File

@@ -0,0 +1,24 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Application;
use Awf\User\Privilege;
class UserPrivileges extends Privilege
{
public function __construct()
{
$this->name = 'akeeba';
// Set up the privilege names and their default values
$this->privileges = array(
'backup' => false,
'configure' => false,
'download' => false,
);
}
}