Made message signing in Cryptography use SHA512 as the message content for... #1

Closed
netkas wants to merge 421 commits from master into dev
22 changed files with 795 additions and 138 deletions
Showing only changes of commit c866e2f696 - Show all commits

15
.idea/php.xml generated
View file

@ -10,6 +10,11 @@
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpCodeSniffer">
<phpcs_settings>
<phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="347c1ee9-eeae-4334-9e17-b7e47c2bca71" timeout="30000" />
</phpcs_settings>
</component>
<component name="PhpIncludePathManager">
<include_path>
<path value="/var/ncc/packages/net.nosial.loglib=2.0.0" />
@ -121,6 +126,11 @@
<extension name="zmq" enabled="false" />
</extensions>
</component>
<component name="PhpStan">
<PhpStan_settings>
<phpstan_by_interpreter asDefaultInterpreter="true" interpreter_id="347c1ee9-eeae-4334-9e17-b7e47c2bca71" timeout="60000" />
</PhpStan_settings>
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
@ -129,6 +139,11 @@
<PhpUnitSettings load_method="PHPUNIT_PHAR" bootstrap_file_path="$PROJECT_DIR$/bootstrap.php" custom_loader_path="" phpunit_phar_path="$USER_HOME$/phpunit.phar" use_bootstrap_file="true" />
</phpunit_settings>
</component>
<component name="Psalm">
<Psalm_settings>
<psalm_fixer_by_interpreter asDefaultInterpreter="true" interpreter_id="347c1ee9-eeae-4334-9e17-b7e47c2bca71" timeout="60000" />
</Psalm_settings>
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>

1
.idea/sqldialects.xml generated
View file

@ -7,6 +7,7 @@
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/variables.sql" dialect="MariaDB" />
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/CaptchaManager.php" dialect="MariaDB" />
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/EncryptionRecordsManager.php" dialect="MariaDB" />
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/PasswordManager.php" dialect="MariaDB" />
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/RegisteredPeerManager.php" dialect="MariaDB" />
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/ResolvedServersManager.php" dialect="MariaDB" />
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/SessionManager.php" dialect="MariaDB" />

View file

@ -1,15 +1,67 @@
<?php
namespace Socialbox\Classes;
namespace Socialbox\Classes;
use InvalidArgumentException;
use Socialbox\Enums\DatabaseObjects;
use InvalidArgumentException;
use Socialbox\Enums\DatabaseObjects;
class Resources
{
class Resources
{
/**
* Retrieves the full path to a database resource based on the provided DatabaseObjects instance.
*
* @param DatabaseObjects $object An instance of DatabaseObjects containing the resource value.
* @return string The full file path to the specified database resource.
*/
public static function getDatabaseResource(DatabaseObjects $object): string
{
$tables_directory = __DIR__ . DIRECTORY_SEPARATOR . 'Resources' . DIRECTORY_SEPARATOR . 'database';
return $tables_directory . DIRECTORY_SEPARATOR . $object->value;
}
}
/**
* Retrieves the file path of a document resource based on the provided name.
*
* @param string $name The name of the document resource to retrieve.
* @return string The file path of the specified document resource.
*/
public static function getDocumentResource(String $name): string
{
$documents_directory = __DIR__ . DIRECTORY_SEPARATOR . 'Resources' . DIRECTORY_SEPARATOR . 'documents';
return $documents_directory . DIRECTORY_SEPARATOR . $name . '.html';
}
/**
* Retrieves the content of the privacy policy document.
*
* @return string The content of the privacy policy document. Attempts to fetch the document
* from a configured location if available and valid; otherwise, retrieves it from a default resource.
*/
public static function getPrivacyPolicy(): string
{
$configuredLocation = Configuration::getRegistrationConfiguration()->getPrivacyPolicyDocument();
if($configuredLocation !== null && file_exists($configuredLocation))
{
return file_get_contents($configuredLocation);
}
return file_get_contents(self::getDocumentResource('privacy'));
}
/**
* Retrieves the content of the Terms of Service document.
*
* @return string The content of the Terms of Service file. The method checks a configured location first,
* and falls back to a default resource if the configured file is unavailable.
*/
public static function getTermsOfService(): string
{
$configuredLocation = Configuration::getRegistrationConfiguration()->getTermsOfServiceDocument();
if($configuredLocation !== null && file_exists($configuredLocation))
{
return file_get_contents($configuredLocation);
}
return file_get_contents(self::getDocumentResource('tos'));
}
}

View file

@ -0,0 +1,2 @@
<h1>Privacy Policy Document</h1>
<p>This is where the privacy policy document would reside in</p>

View file

@ -0,0 +1,2 @@
<h1>Terms of Service Document</h1>
<p>This is where the Terms of Service document would reside</p>

View file

@ -0,0 +1,48 @@
<?php
namespace Socialbox\Classes\StandardMethods;
use Socialbox\Abstracts\Method;
use Socialbox\Enums\Flags\SessionFlags;
use Socialbox\Enums\StandardError;
use Socialbox\Exceptions\DatabaseOperationException;
use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Managers\SessionManager;
use Socialbox\Objects\ClientRequest;
use Socialbox\Objects\RpcRequest;
class AcceptPrivacyPolicy extends Method
{
/**
* @inheritDoc
*/
public static function execute(ClientRequest $request, RpcRequest $rpcRequest): ?SerializableInterface
{
try
{
SessionManager::removeFlags($request->getSessionUuid(), [SessionFlags::VER_PRIVACY_POLICY]);
}
catch (DatabaseOperationException $e)
{
return $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR, $e);
}
// Check if all registration flags are removed
if(SessionFlags::isComplete($request->getSession()->getFlags()))
{
// Set the session as authenticated
try
{
SessionManager::setAuthenticated($request->getSessionUuid(), true);
SessionManager::removeFlags($request->getSessionUuid(), [SessionFlags::REGISTRATION_REQUIRED, SessionFlags::AUTHENTICATION_REQUIRED]);
}
catch (DatabaseOperationException $e)
{
return $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR, $e);
}
}
return $rpcRequest->produceResponse(true);
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Socialbox\Classes\StandardMethods;
use Socialbox\Abstracts\Method;
use Socialbox\Enums\Flags\SessionFlags;
use Socialbox\Enums\StandardError;
use Socialbox\Exceptions\DatabaseOperationException;
use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Managers\SessionManager;
use Socialbox\Objects\ClientRequest;
use Socialbox\Objects\RpcRequest;
class AcceptTermsOfService extends Method
{
/**
* @inheritDoc
*/
public static function execute(ClientRequest $request, RpcRequest $rpcRequest): ?SerializableInterface
{
try
{
SessionManager::removeFlags($request->getSessionUuid(), [SessionFlags::VER_TERMS_OF_SERVICE]);
}
catch (DatabaseOperationException $e)
{
return $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR, $e);
}
// Check if all registration flags are removed
if(SessionFlags::isComplete($request->getSession()->getFlags()))
{
// Set the session as authenticated
try
{
SessionManager::setAuthenticated($request->getSessionUuid(), true);
SessionManager::removeFlags($request->getSessionUuid(), [SessionFlags::REGISTRATION_REQUIRED, SessionFlags::AUTHENTICATION_REQUIRED]);
}
catch (DatabaseOperationException $e)
{
return $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR, $e);
}
}
return $rpcRequest->produceResponse(true);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Socialbox\Classes\StandardMethods;
use Socialbox\Abstracts\Method;
use Socialbox\Classes\Resources;
use Socialbox\Enums\StandardError;
use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Objects\ClientRequest;
use Socialbox\Objects\RpcRequest;
class GetPrivacyPolicy extends Method
{
/**
* @inheritDoc
*/
public static function execute(ClientRequest $request, RpcRequest $rpcRequest): ?SerializableInterface
{
return $rpcRequest->produceResponse(Resources::getPrivacyPolicy());
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Socialbox\Classes\StandardMethods;
use Socialbox\Abstracts\Method;
use Socialbox\Classes\Resources;
use Socialbox\Enums\StandardError;
use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Objects\ClientRequest;
use Socialbox\Objects\RpcRequest;
class GetTermsOfService extends Method
{
/**
* @inheritDoc
*/
public static function execute(ClientRequest $request, RpcRequest $rpcRequest): ?SerializableInterface
{
return $rpcRequest->produceResponse(Resources::getTermsOfService());
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace Socialbox\Classes\StandardMethods;
use Exception;
use Socialbox\Abstracts\Method;
use Socialbox\Enums\Flags\SessionFlags;
use Socialbox\Enums\StandardError;
use Socialbox\Exceptions\DatabaseOperationException;
use Socialbox\Exceptions\StandardException;
use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Managers\PasswordManager;
use Socialbox\Managers\SessionManager;
use Socialbox\Objects\ClientRequest;
use Socialbox\Objects\RpcRequest;
class SettingsSetPassword extends Method
{
/**
* @inheritDoc
*/
public static function execute(ClientRequest $request, RpcRequest $rpcRequest): ?SerializableInterface
{
if(!$rpcRequest->containsParameter('password'))
{
return $rpcRequest->produceError(StandardError::RPC_INVALID_ARGUMENTS, "Missing 'password' parameter");
}
if(!preg_match('/^[a-f0-9]{128}$/', $rpcRequest->getParameter('password')))
{
return $rpcRequest->produceError(StandardError::RPC_INVALID_ARGUMENTS, "Invalid 'password' parameter, must be sha512 hexadecimal hash");
}
try
{
if (PasswordManager::usesPassword($request->getPeer()->getUuid()))
{
return $rpcRequest->produceError(StandardError::METHOD_NOT_ALLOWED, "Cannot set password when one is already set, use 'settingsChangePassword' instead");
}
}
catch (DatabaseOperationException $e)
{
throw new StandardException('Failed to check password due to an internal exception', StandardError::INTERNAL_SERVER_ERROR, $e);
}
try
{
// Set the password
PasswordManager::setPassword($request->getPeer(), $rpcRequest->getParameter('password'));
// Remove the SET_PASSWORD flag
SessionManager::removeFlags($request->getSessionUuid(), [SessionFlags::SET_PASSWORD]);
}
catch(Exception $e)
{
throw new StandardException('Failed to set password due to an internal exception', StandardError::INTERNAL_SERVER_ERROR, $e);
}
// Check if all registration flags are removed
if(SessionFlags::isComplete($request->getSession()->getFlags()))
{
// Set the session as authenticated
try
{
SessionManager::setAuthenticated($request->getSessionUuid(), true);
SessionManager::removeFlags($request->getSessionUuid(), [SessionFlags::REGISTRATION_REQUIRED, SessionFlags::AUTHENTICATION_REQUIRED]);
}
catch (DatabaseOperationException $e)
{
throw new StandardException('Failed to update session due to an internal exception', StandardError::INTERNAL_SERVER_ERROR, $e);
}
}
return $rpcRequest->produceResponse(true);
}
}

View file

@ -4,6 +4,7 @@ namespace Socialbox\Classes\StandardMethods;
use Socialbox\Abstracts\Method;
use Socialbox\Enums\Flags\PeerFlags;
use Socialbox\Enums\Flags\SessionFlags;
use Socialbox\Enums\StandardError;
use Socialbox\Exceptions\DatabaseOperationException;
use Socialbox\Exceptions\StandardException;
@ -11,6 +12,7 @@ use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Managers\CaptchaManager;
use Socialbox\Managers\RegisteredPeerManager;
use Socialbox\Managers\SessionManager;
use Socialbox\Objects\ClientRequest;
use Socialbox\Objects\ClientRequestOld;
use Socialbox\Objects\RpcRequest;
@ -20,51 +22,15 @@ class VerificationAnswerImageCaptcha extends Method
/**
* @inheritDoc
*/
public static function execute(ClientRequestOld $request, RpcRequest $rpcRequest): ?SerializableInterface
public static function execute(ClientRequest $request, RpcRequest $rpcRequest): ?SerializableInterface
{
// Check if the request has a Session UUID
if($request->getSessionUuid() === null)
{
return $rpcRequest->produceError(StandardError::SESSION_REQUIRED);
}
// Get the session and check if it's already authenticated
try
{
$session = SessionManager::getSession($request->getSessionUuid());
}
catch(DatabaseOperationException $e)
{
throw new StandardException("There was an unexpected error while trying to get the session", StandardError::INTERNAL_SERVER_ERROR, $e);
}
// Check for session conditions
if($session->getPeerUuid() === null)
{
return $rpcRequest->produceError(StandardError::AUTHENTICATION_REQUIRED);
}
// Get the peer
try
{
$peer = RegisteredPeerManager::getPeer($session->getPeerUuid());
}
catch(DatabaseOperationException $e)
{
throw new StandardException("There was unexpected error while trying to get the peer", StandardError::INTERNAL_SERVER_ERROR, $e);
}
// Check if the VER_SOLVE_IMAGE_CAPTCHA flag exists.
if(!$peer->flagExists(PeerFlags::VER_SOLVE_IMAGE_CAPTCHA))
{
return $rpcRequest->produceError(StandardError::CAPTCHA_NOT_AVAILABLE, 'You are not required to complete a captcha at this time');
}
if(!$rpcRequest->containsParameter('answer'))
{
return $rpcRequest->produceError(StandardError::RPC_INVALID_ARGUMENTS, 'The answer parameter is required');
}
$session = $request->getSession();
try
{
if(CaptchaManager::getCaptcha($session->getPeerUuid())->isExpired())
@ -83,14 +49,29 @@ class VerificationAnswerImageCaptcha extends Method
if($result)
{
RegisteredPeerManager::removeFlag($session->getPeerUuid(), PeerFlags::VER_SOLVE_IMAGE_CAPTCHA);
SessionManager::removeFlags($request->getSessionUuid(), [SessionFlags::VER_IMAGE_CAPTCHA]);
}
return $rpcRequest->produceResponse($result);
}
catch (DatabaseOperationException $e)
{
throw new StandardException("There was an unexpected error while trying to answer the captcha", StandardError::INTERNAL_SERVER_ERROR, $e);
}
// Check if all registration flags are removed
if(SessionFlags::isComplete($request->getSession()->getFlags()))
{
// Set the session as authenticated
try
{
SessionManager::setAuthenticated($request->getSessionUuid(), true);
SessionManager::removeFlags($request->getSessionUuid(), [SessionFlags::REGISTRATION_REQUIRED, SessionFlags::AUTHENTICATION_REQUIRED]);
}
catch (DatabaseOperationException $e)
{
return $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR, $e);
}
}
return $rpcRequest->produceResponse($result);
}
}

View file

@ -1,44 +1,27 @@
<?php
namespace Socialbox\Classes\StandardMethods;
namespace Socialbox\Classes\StandardMethods;
use Gregwar\Captcha\CaptchaBuilder;
use Socialbox\Abstracts\Method;
use Socialbox\Classes\Logger;
use Socialbox\Enums\Flags\PeerFlags;
use Socialbox\Enums\StandardError;
use Socialbox\Exceptions\DatabaseOperationException;
use Socialbox\Exceptions\StandardException;
use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Managers\CaptchaManager;
use Socialbox\Managers\RegisteredPeerManager;
use Socialbox\Managers\SessionManager;
use Socialbox\Objects\ClientRequestOld;
use Socialbox\Objects\RpcRequest;
use Socialbox\Objects\Standard\ImageCaptcha;
use Gregwar\Captcha\CaptchaBuilder;
use Socialbox\Abstracts\Method;
use Socialbox\Classes\Logger;
use Socialbox\Enums\StandardError;
use Socialbox\Exceptions\DatabaseOperationException;
use Socialbox\Exceptions\StandardException;
use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Managers\CaptchaManager;
use Socialbox\Objects\ClientRequest;
use Socialbox\Objects\RpcRequest;
use Socialbox\Objects\Standard\ImageCaptcha;
class VerificationGetImageCaptcha extends Method
{
class VerificationGetImageCaptcha extends Method
{
/**
* @inheritDoc
*/
public static function execute(ClientRequestOld $request, RpcRequest $rpcRequest): ?SerializableInterface
public static function execute(ClientRequest $request, RpcRequest $rpcRequest): ?SerializableInterface
{
// Check if the request has a Session UUID
if($request->getSessionUuid() === null)
{
return $rpcRequest->produceError(StandardError::SESSION_REQUIRED);
}
// Get the session and check if it's already authenticated
try
{
$session = SessionManager::getSession($request->getSessionUuid());
}
catch(DatabaseOperationException $e)
{
throw new StandardException("There was an unexpected error while trying to get the session", StandardError::INTERNAL_SERVER_ERROR, $e);
}
$session = $request->getSession();
// Check for session conditions
if($session->getPeerUuid() === null)
@ -46,37 +29,40 @@ class VerificationGetImageCaptcha extends Method
return $rpcRequest->produceError(StandardError::AUTHENTICATION_REQUIRED);
}
// Get the peer
try
{
$peer = RegisteredPeerManager::getPeer($session->getPeerUuid());
}
catch(DatabaseOperationException $e)
{
throw new StandardException("There was unexpected error while trying to get the peer", StandardError::INTERNAL_SERVER_ERROR, $e);
}
// Check if the VER_SOLVE_IMAGE_CAPTCHA flag exists.
if(!$peer->flagExists(PeerFlags::VER_SOLVE_IMAGE_CAPTCHA))
{
return $rpcRequest->produceError(StandardError::CAPTCHA_NOT_AVAILABLE, 'You are not required to complete a captcha at this time');
}
$peer = $request->getPeer();
try
{
Logger::getLogger()->debug('Creating a new captcha for peer ' . $peer->getUuid());
if(CaptchaManager::captchaExists($peer))
{
$captchaRecord = CaptchaManager::getCaptcha($peer);
if($captchaRecord->isExpired())
{
$answer = CaptchaManager::createCaptcha($peer);
$captchaRecord = CaptchaManager::getCaptcha($peer);
}
else
{
$answer = $captchaRecord->getAnswer();
}
}
else
{
$answer = CaptchaManager::createCaptcha($peer);
$captchaRecord = CaptchaManager::getCaptcha($peer);
}
}
catch (DatabaseOperationException $e)
{
throw new StandardException("There was an unexpected error while trying create the captcha", StandardError::INTERNAL_SERVER_ERROR, $e);
}
// Build the captcha
// Returns HTML base64 encoded image of the captcha
return $rpcRequest->produceResponse(new ImageCaptcha([
'expires' => $captchaRecord->getExpires(),
'image' => (new CaptchaBuilder($answer))->build()->inline()
])); // Returns HTML base64 encoded image of the captcha
'content' => (new CaptchaBuilder($answer))->build()->inline()
]));
}
}
}

View file

@ -51,4 +51,43 @@
return array_map(fn(string $value) => SessionFlags::from(trim($value)), explode(',', $flagString));
}
/**
* Determines if all required session flags for completion are satisfied based on the given array of flags.
*
* @param array $flags An array of session flags to evaluate. Accepts both enum values (strings) and enum objects.
* @return bool True if all required flags for completion are satisfied, false otherwise.
*/
public static function isComplete(array $flags): bool
{
$flags = array_map(function ($flag) {
return is_string($flag) ? SessionFlags::from($flag) : $flag;
}, $flags);
$flags = array_map(fn(SessionFlags $flag) => $flag->value, $flags);
if (in_array(SessionFlags::REGISTRATION_REQUIRED->value, $flags)) {
$flagsToComplete = [
SessionFlags::SET_PASSWORD->value,
SessionFlags::SET_OTP->value,
SessionFlags::SET_DISPLAY_NAME->value,
SessionFlags::VER_PRIVACY_POLICY->value,
SessionFlags::VER_TERMS_OF_SERVICE->value,
SessionFlags::VER_EMAIL->value,
SessionFlags::VER_SMS->value,
SessionFlags::VER_PHONE_CALL->value,
SessionFlags::VER_IMAGE_CAPTCHA->value
];
return !array_intersect($flagsToComplete, $flags); // Check if the intersection is empty
}
if (in_array(SessionFlags::AUTHENTICATION_REQUIRED->value, $flags)) {
$flagsToComplete = [
SessionFlags::VER_PASSWORD->value,
SessionFlags::VER_OTP->value
];
return !array_intersect($flagsToComplete, $flags); // Check if the intersection is empty
}
return true;
}
}

View file

@ -36,6 +36,7 @@ enum StandardError : int
case INVALID_USERNAME = -4001;
case USERNAME_ALREADY_EXISTS = -4002;
case NOT_REGISTERED = -4003;
case METHOD_NOT_ALLOWED = -4004;
/**
* Returns the default generic message for the error
@ -69,6 +70,9 @@ enum StandardError : int
self::INVALID_USERNAME => 'The given username is invalid, it must be Alphanumeric with a minimum of 3 character but no greater than 255 characters',
self::USERNAME_ALREADY_EXISTS => 'The given username already exists on the network',
self::NOT_REGISTERED => 'The given username is not registered on the server',
self::METHOD_NOT_ALLOWED => 'The requested method is not allowed',
self::SESSION_EXPIRED => 'The session has expired',
self::SESSION_DHE_REQUIRED => 'The session requires DHE to be established',
};
}

View file

@ -2,8 +2,16 @@
namespace Socialbox\Enums;
use Socialbox\Classes\StandardMethods\AcceptPrivacyPolicy;
use Socialbox\Classes\StandardMethods\AcceptTermsOfService;
use Socialbox\Classes\StandardMethods\GetPrivacyPolicy;
use Socialbox\Classes\StandardMethods\GetSessionState;
use Socialbox\Classes\StandardMethods\GetTermsOfService;
use Socialbox\Classes\StandardMethods\Ping;
use Socialbox\Classes\StandardMethods\SettingsSetPassword;
use Socialbox\Classes\StandardMethods\VerificationAnswerImageCaptcha;
use Socialbox\Classes\StandardMethods\VerificationGetImageCaptcha;
use Socialbox\Enums\Flags\SessionFlags;
use Socialbox\Exceptions\StandardException;
use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Objects\ClientRequest;
@ -14,11 +22,23 @@
case PING = 'ping';
case GET_SESSION_STATE = 'getSessionState';
case GET_PRIVACY_POLICY = 'getPrivacyPolicy';
case ACCEPT_PRIVACY_POLICY = 'acceptPrivacyPolicy';
case GET_TERMS_OF_SERVICE = 'getTermsOfService';
case ACCEPT_TERMS_OF_SERVICE = 'acceptTermsOfService';
case VERIFICATION_GET_IMAGE_CAPTCHA = 'verificationGetImageCaptcha';
case VERIFICATION_ANSWER_IMAGE_CAPTCHA = 'verificationAnswerImageCaptcha';
case SETTINGS_SET_PASSWORD = 'settingsSetPassword';
/**
* @param ClientRequest $request
* @param RpcRequest $rpcRequest
* @return SerializableInterface|null
* @throws StandardException
* Executes the appropriate operation based on the current context and requests provided.
*
* @param ClientRequest $request The client request object containing necessary data for execution.
* @param RpcRequest $rpcRequest The RPC request object providing additional parameters for execution.
* @return SerializableInterface|null The result of the operation as a serializable interface or null if no operation matches.
* @throws StandardException If an error occurs during execution
*/
public function execute(ClientRequest $request, RpcRequest $rpcRequest): ?SerializableInterface
{
@ -26,6 +46,74 @@
{
self::PING => Ping::execute($request, $rpcRequest),
self::GET_SESSION_STATE => GetSessionState::execute($request, $rpcRequest),
self::GET_PRIVACY_POLICY => GetPrivacyPolicy::execute($request, $rpcRequest),
self::ACCEPT_PRIVACY_POLICY => AcceptPrivacyPolicy::execute($request, $rpcRequest),
self::GET_TERMS_OF_SERVICE => GetTermsOfService::execute($request, $rpcRequest),
self::ACCEPT_TERMS_OF_SERVICE => AcceptTermsOfService::execute($request, $rpcRequest),
self::VERIFICATION_GET_IMAGE_CAPTCHA => VerificationGetImageCaptcha::execute($request, $rpcRequest),
self::VERIFICATION_ANSWER_IMAGE_CAPTCHA => VerificationAnswerImageCaptcha::execute($request, $rpcRequest),
self::SETTINGS_SET_PASSWORD => SettingsSetPassword::execute($request, $rpcRequest),
};
}
/**
* Checks if the access method is allowed for the given client request.
*
* @param ClientRequest $clientRequest The client request instance to check access against.
* @return void
* @throws StandardException If the method is not allowed for the given client request.
*/
public function checkAccess(ClientRequest $clientRequest): void
{
if(in_array($this, self::getAllowedMethods($clientRequest)))
{
return;
}
throw new StandardException(StandardError::METHOD_NOT_ALLOWED->getMessage(), StandardError::METHOD_NOT_ALLOWED);
}
/**
* Determines the list of allowed methods for a given client request.
*
* @param ClientRequest $clientRequest The client request for which allowed methods are determined.
* @return array Returns an array of allowed methods for the provided client request.
*/
public static function getAllowedMethods(ClientRequest $clientRequest): array
{
$methods = [
self::PING,
self::GET_SESSION_STATE,
self::GET_PRIVACY_POLICY,
self::GET_TERMS_OF_SERVICE,
];
$session = $clientRequest->getSession();
if(in_array(SessionFlags::VER_PRIVACY_POLICY, $session->getFlags()))
{
$methods[] = self::ACCEPT_PRIVACY_POLICY;
}
if(in_array(SessionFlags::VER_TERMS_OF_SERVICE, $session->getFlags()))
{
$methods[] = self::ACCEPT_TERMS_OF_SERVICE;
}
if(in_array(SessionFlags::VER_IMAGE_CAPTCHA, $session->getFlags()))
{
$methods[] = self::VERIFICATION_GET_IMAGE_CAPTCHA;
$methods[] = self::VERIFICATION_ANSWER_IMAGE_CAPTCHA;
}
if(in_array(SessionFlags::SET_PASSWORD, $session->getFlags()))
{
$methods[] = self::SETTINGS_SET_PASSWORD;
}
return $methods;
}
}

View file

@ -135,10 +135,10 @@ class CaptchaManager
* Retrieves the captcha record for the given peer UUID.
*
* @param string|RegisteredPeerRecord $peer_uuid The UUID of the peer to retrieve the captcha for.
* @return CaptchaRecord The captcha record.
* @return CaptchaRecord|null The captcha record.
* @throws DatabaseOperationException If the operation fails.
*/
public static function getCaptcha(string|RegisteredPeerRecord $peer_uuid): CaptchaRecord
public static function getCaptcha(string|RegisteredPeerRecord $peer_uuid): ?CaptchaRecord
{
// If the peer_uuid is a RegisteredPeerRecord, get the UUID
if($peer_uuid instanceof RegisteredPeerRecord)
@ -162,7 +162,7 @@ class CaptchaManager
if($result === false)
{
throw new DatabaseOperationException('The requested captcha does not exist');
return null;
}
return CaptchaRecord::fromArray($result);
@ -175,7 +175,7 @@ class CaptchaManager
* @return bool True if a captcha exists, false otherwise.
* @throws DatabaseOperationException If the operation fails.
*/
private static function captchaExists(string|RegisteredPeerRecord $peer_uuid): bool
public static function captchaExists(string|RegisteredPeerRecord $peer_uuid): bool
{
// If the peer_uuid is a RegisteredPeerRecord, get the UUID
if($peer_uuid instanceof RegisteredPeerRecord)

View file

@ -0,0 +1,187 @@
<?php
namespace Socialbox\Managers;
use PDO;
use Socialbox\Classes\Database;
use Socialbox\Classes\SecuredPassword;
use Socialbox\Exceptions\CryptographyException;
use Socialbox\Exceptions\DatabaseOperationException;
use Socialbox\Objects\Database\RegisteredPeerRecord;
use Socialbox\Objects\Database\SecurePasswordRecord;
class PasswordManager
{
/**
* Checks if the given peer UUID is associated with a password in the database.
*
* @param string|RegisteredPeerRecord $peerUuid The UUID of the peer, or an instance of RegisteredPeerRecord from which the UUID will be retrieved.
* @return bool Returns true if the peer UUID is associated with a password, otherwise false.
* @throws DatabaseOperationException If an error occurs while querying the database.
*/
public static function usesPassword(string|RegisteredPeerRecord $peerUuid): bool
{
if($peerUuid instanceof RegisteredPeerRecord)
{
$peerUuid = $peerUuid->getUuid();
}
try
{
$stmt = Database::getConnection()->prepare('SELECT COUNT(*) FROM authentication_passwords WHERE peer_uuid=:uuid');
$stmt->bindParam(':uuid', $peerUuid);
$stmt->execute();
return $stmt->fetchColumn() > 0;
}
catch (\PDOException $e)
{
throw new DatabaseOperationException('An error occurred while checking the password usage in the database', $e);
}
}
/**
* Sets a password for a given user or peer record by securely encrypting it
* and storing it in the authentication_passwords database table.
*
* @param string|RegisteredPeerRecord $peerUuid The UUID of the peer or an instance of RegisteredPeerRecord.
* @param string $password The plaintext password to be securely stored.
* @throws CryptographyException If an error occurs while securing the password.
* @throws DatabaseOperationException If an error occurs while attempting to store the password in the database.
* @throws \DateMalformedStringException If the updated timestamp cannot be formatted.
* @return void
*/
public static function setPassword(string|RegisteredPeerRecord $peerUuid, string $password): void
{
if($peerUuid instanceof RegisteredPeerRecord)
{
$peerUuid = $peerUuid->getUuid();
}
$encryptionRecord = EncryptionRecordsManager::getRandomRecord();
$securedPassword = SecuredPassword::securePassword($peerUuid, $password, $encryptionRecord);
try
{
$stmt = Database::getConnection()->prepare("INSERT INTO authentication_passwords (peer_uuid, iv, encrypted_password, encrypted_tag) VALUES (:peer_uuid, :iv, :encrypted_password, :encrypted_tag)");
$stmt->bindParam(":peer_uuid", $peerUuid);
$iv = $securedPassword->getIv();
$stmt->bindParam(':iv', $iv);
$encryptedPassword = $securedPassword->getEncryptedPassword();
$stmt->bindParam(':encrypted_password', $encryptedPassword);
$encryptedTag = $securedPassword->getEncryptedTag();
$stmt->bindParam(':encrypted_tag', $encryptedTag);
$stmt->execute();
}
catch(\PDOException $e)
{
throw new DatabaseOperationException(sprintf('Failed to set password for user %s', $peerUuid), $e);
}
}
/**
* Updates the password for a given peer identified by their UUID or a RegisteredPeerRecord.
*
* @param string|RegisteredPeerRecord $peerUuid The UUID of the peer or an instance of RegisteredPeerRecord.
* @param string $newPassword The new password to be set for the peer.
* @throws CryptographyException If an error occurs while securing the new password.
* @throws DatabaseOperationException If the update operation fails due to a database error.
* @throws \DateMalformedStringException If the updated timestamp cannot be formatted.
* @returns void
*/
public static function updatePassword(string|RegisteredPeerRecord $peerUuid, string $newPassword): void
{
if($peerUuid instanceof RegisteredPeerRecord)
{
$peerUuid = $peerUuid->getUuid();
}
$encryptionRecord = EncryptionRecordsManager::getRandomRecord();
$securedPassword = SecuredPassword::securePassword($peerUuid, $newPassword, $encryptionRecord);
try
{
$stmt = Database::getConnection()->prepare("UPDATE authentication_passwords SET iv=:iv, encrypted_password=:encrypted_password, encrypted_tag=:encrypted_tag, updated=:updated WHERE peer_uuid=:peer_uuid");
$stmt->bindParam(":peer_uuid", $peerUuid);
$iv = $securedPassword->getIv();
$stmt->bindParam(':iv', $iv);
$encryptedPassword = $securedPassword->getEncryptedPassword();
$stmt->bindParam(':encrypted_password', $encryptedPassword);
$encryptedTag = $securedPassword->getEncryptedTag();
$stmt->bindParam(':encrypted_tag', $encryptedTag);
$updated = $securedPassword->getUpdated()->format('Y-m-d H:i:s');
$stmt->bindParam(':updated', $updated);
$stmt->execute();
}
catch(\PDOException $e)
{
throw new DatabaseOperationException(sprintf('Failed to update password for user %s', $peerUuid), $e);
}
}
/**
* Retrieves the password record associated with the given peer UUID.
*
* @param string|RegisteredPeerRecord $peerUuid The UUID of the peer or an instance of RegisteredPeerRecord.
* @return SecurePasswordRecord|null Returns a SecurePasswordRecord if found, or null if no record is present.
* @throws DatabaseOperationException If a database operation error occurs during the retrieval process.
*/
private static function getPassword(string|RegisteredPeerRecord $peerUuid): ?SecurePasswordRecord
{
if($peerUuid instanceof RegisteredPeerRecord)
{
$peerUuid = $peerUuid->getUuid();
}
try
{
$statement = Database::getConnection()->prepare("SELECT * FROM authentication_passwords WHERE peer_uuid=:peer_uuid LIMIT 1");
$statement->bindParam(':peer_uuid', $peerUuid);
$statement->execute();
$data = $statement->fetch(PDO::FETCH_ASSOC);
if ($data === false)
{
return null;
}
return SecurePasswordRecord::fromArray($data);
}
catch(\PDOException $e)
{
throw new DatabaseOperationException(sprintf('Failed to retrieve password record for user %s', $peerUuid), $e);
}
}
/**
* Verifies if the provided password matches the secured password associated with the given peer UUID.
*
* @param string|RegisteredPeerRecord $peerUuid The unique identifier or registered peer record of the user.
* @param string $password The password to be verified.
* @return bool Returns true if the password is verified successfully; otherwise, false.
* @throws DatabaseOperationException If an error occurs while retrieving the password record from the database.
* @throws CryptographyException If an error occurs while verifying the password.
*/
public static function verifyPassword(string|RegisteredPeerRecord $peerUuid, string $password): bool
{
$securedPassword = self::getPassword($peerUuid);
if($securedPassword === null)
{
return false;
}
$encryptionRecords = EncryptionRecordsManager::getAllRecords();
return SecuredPassword::verifyPassword($password, $securedPassword, $encryptionRecords);
}
}

View file

@ -361,7 +361,7 @@
throw new StandardException(sprintf("The requested session '%s' does not exist", $uuid), StandardError::SESSION_NOT_FOUND);
}
return Utilities::unserializeList($data['flags']);
return SessionFlags::fromString($data['flags']);
}
catch (PDOException $e)
{
@ -404,7 +404,7 @@
* Removes specified flags from the session associated with the given UUID.
*
* @param string $uuid The UUID of the session from which the flags will be removed.
* @param array $flags An array of flags to be removed from the session.
* @param SessionFlags[] $flags An array of flags to be removed from the session.
* @return void
* @throws DatabaseOperationException|StandardException If there is an error while updating the session in the database.
*/
@ -412,16 +412,15 @@
{
Logger::getLogger()->verbose(sprintf("Removing flags from session %s", $uuid));
// First get the existing flags
$existingFlags = self::getFlags($uuid);
// Remove the specified flags
$flags = array_diff($existingFlags, $flags);
$flagsToRemove = array_map(fn($flag) => $flag->value, $flags);
$updatedFlags = array_filter($existingFlags, fn($flag) => !in_array($flag->value, $flagsToRemove));
$flags = SessionFlags::toString($updatedFlags);
try
{
$statement = Database::getConnection()->prepare("UPDATE sessions SET flags=? WHERE uuid=?");
$statement->bindValue(1, Utilities::serializeList($flags));
$statement->bindValue(1, $flags); // Directly use the toString() result
$statement->bindParam(2, $uuid);
$statement->execute();
}
@ -430,4 +429,29 @@
throw new DatabaseOperationException('Failed to remove flags from session', $e);
}
}
/**
* Updates the authentication status for the specified session.
*
* @param string $uuid The unique identifier of the session to be updated.
* @param bool $authenticated The authentication status to set for the session.
* @return void
* @throws DatabaseOperationException If the database operation fails.
*/
public static function setAuthenticated(string $uuid, bool $authenticated): void
{
Logger::getLogger()->verbose(sprintf("Setting session %s as authenticated: %s", $uuid, $authenticated ? 'true' : 'false'));
try
{
$statement = Database::getConnection()->prepare("UPDATE sessions SET authenticated=? WHERE uuid=?");
$statement->bindParam(1, $authenticated);
$statement->bindParam(2, $uuid);
$statement->execute();
}
catch (PDOException $e)
{
throw new DatabaseOperationException('Failed to update authenticated peer', $e);
}
}
}

View file

@ -10,7 +10,9 @@
use Socialbox\Enums\Types\RequestType;
use Socialbox\Exceptions\CryptographyException;
use Socialbox\Exceptions\RequestException;
use Socialbox\Managers\RegisteredPeerManager;
use Socialbox\Managers\SessionManager;
use Socialbox\Objects\Database\RegisteredPeerRecord;
use Socialbox\Objects\Database\SessionRecord;
class ClientRequest
@ -113,6 +115,18 @@
return SessionManager::getSession($this->sessionUuid);
}
public function getPeer(): ?RegisteredPeerRecord
{
$session = $this->getSession();
if($session === null)
{
return null;
}
return RegisteredPeerManager::getPeer($session->getPeerUuid());
}
public function getSignature(): ?string
{
return $this->signature;

View file

@ -81,4 +81,20 @@
{
return $this->updated;
}
public function toArray(): array
{
return [
'peer_uuid' => $this->peerUuid,
'iv' => $this->iv,
'encrypted_password' => $this->encryptedPassword,
'encrypted_tag' => $this->encryptedTag,
'updated' => $this->updated->format('Y-m-d H:i:s')
];
}
public static function fromArray(array $data): SecurePasswordRecord
{
return new SecurePasswordRecord($data);
}
}

View file

@ -144,6 +144,26 @@
return $this->flags;
}
/**
* Checks if a given flag exists in the list of session flags.
*
* @param string|SessionFlags $flag The flag to check, either as a string or a SessionFlags object.
* @return bool True if the flag exists, false otherwise.
*/
public function flagExists(string|SessionFlags $flag): bool
{
if(is_string($flag))
{
$flag = SessionFlags::tryFrom($flag);
if($flag === null)
{
return false;
}
}
return in_array($flag, $this->flags);
}
/**
* Retrieves the timestamp of the last request made.
*

View file

@ -293,6 +293,17 @@
{
$method = StandardMethods::tryFrom($rpcRequest->getMethod());
try
{
$method->checkAccess($clientRequest);
}
catch (StandardException $e)
{
$response = $e->produceError($rpcRequest);
$results[] = $response->toArray();
continue;
}
if($method === false)
{
Logger::getLogger()->warning('The requested method does not exist');