diff --git a/.idea/socialbox-php.iml b/.idea/socialbox-php.iml index 9ac0b9d..47ac8d8 100644 --- a/.idea/socialbox-php.iml +++ b/.idea/socialbox-php.iml @@ -2,6 +2,8 @@ + + diff --git a/src/Socialbox/Classes/Configuration/SecurityConfiguration.php b/src/Socialbox/Classes/Configuration/SecurityConfiguration.php index 70aa930..805437c 100644 --- a/src/Socialbox/Classes/Configuration/SecurityConfiguration.php +++ b/src/Socialbox/Classes/Configuration/SecurityConfiguration.php @@ -4,7 +4,7 @@ namespace Socialbox\Classes\Configuration; class SecurityConfiguration { - private bool $displayInternalErrors; + private bool $displayInternalExceptions; private int $resolvedServersTtl; private int $captchaTtl; @@ -17,7 +17,7 @@ class SecurityConfiguration */ public function __construct(array $data) { - $this->displayInternalErrors = $data['display_internal_errors']; + $this->displayInternalExceptions = $data['display_internal_exceptions']; $this->resolvedServersTtl = $data['resolved_servers_ttl']; $this->captchaTtl = $data['captcha_ttl']; } @@ -27,9 +27,9 @@ class SecurityConfiguration * * @return bool True if the display of internal errors is enabled, false otherwise. */ - public function isDisplayInternalErrors(): bool + public function isDisplayInternalExceptions(): bool { - return $this->displayInternalErrors; + return $this->displayInternalExceptions; } /** diff --git a/src/Socialbox/Classes/StandardMethods/GetCaptcha.php b/src/Socialbox/Classes/StandardMethods/GetCaptcha.php new file mode 100644 index 0000000..a7bbc78 --- /dev/null +++ b/src/Socialbox/Classes/StandardMethods/GetCaptcha.php @@ -0,0 +1,83 @@ +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'); + } + + try + { + Logger::getLogger()->debug('Creating a new captcha for peer ' . $peer->getUuid()); + $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 + Logger::getLogger()->debug('Building captcha for peer ' . $peer->getUuid()); + return $rpcRequest->produceResponse(new ImageCaptcha([ + 'expires' => $captchaRecord->getExpires()->getTimestamp(), + 'image' => (new CaptchaBuilder($answer))->build()->inline()] // Returns HTML base64 encoded image of the captcha + )); + } +} \ No newline at end of file diff --git a/src/Socialbox/Enums/StandardError.php b/src/Socialbox/Enums/StandardError.php index 22f3599..9ab424f 100644 --- a/src/Socialbox/Enums/StandardError.php +++ b/src/Socialbox/Enums/StandardError.php @@ -23,6 +23,9 @@ enum StandardError : int case SESSION_NOT_FOUND = -3004; case SESSION_REQUIRED = -3005; case REGISTRATION_DISABLED = -3006; + case CAPTCHA_NOT_AVAILABLE = -3007; + case INCORRECT_CAPTCHA_ANSWER = -3008; + case CAPTCHA_EXPIRED = -3009; // General Error Messages case PEER_NOT_FOUND = -4000; @@ -52,6 +55,10 @@ enum StandardError : int self::AUTHENTICATION_REQUIRED => 'Authentication is required to preform this action', self::SESSION_NOT_FOUND => 'The requested session UUID was not found', self::SESSION_REQUIRED => 'A session is required to preform this action', + self::REGISTRATION_DISABLED => 'Registration is disabled on the server', + self::CAPTCHA_NOT_AVAILABLE => 'Captcha is not available', + self::INCORRECT_CAPTCHA_ANSWER => 'The Captcha answer is incorrect', + self::CAPTCHA_EXPIRED => 'The captcha has expired and a new captcha needs to be requested', self::PEER_NOT_FOUND => 'The requested peer was not found', self::INVALID_USERNAME => 'The given username is invalid, it must be Alphanumeric with a minimum of 3 character but no greater than 255 characters', diff --git a/src/Socialbox/Enums/StandardMethods.php b/src/Socialbox/Enums/StandardMethods.php index 3d683e1..e412502 100644 --- a/src/Socialbox/Enums/StandardMethods.php +++ b/src/Socialbox/Enums/StandardMethods.php @@ -3,6 +3,7 @@ namespace Socialbox\Enums; use Socialbox\Classes\StandardMethods\CreateSession; +use Socialbox\Classes\StandardMethods\GetCaptcha; use Socialbox\Classes\StandardMethods\GetMe; use Socialbox\Classes\StandardMethods\Ping; use Socialbox\Classes\StandardMethods\Register; @@ -17,6 +18,7 @@ enum StandardMethods : string case CREATE_SESSION = 'createSession'; case REGISTER = 'register'; case GET_ME = 'getMe'; + case GET_CAPTCHA = 'getCaptcha'; /** * @param ClientRequest $request @@ -32,6 +34,7 @@ enum StandardMethods : string self::CREATE_SESSION => CreateSession::execute($request, $rpcRequest), self::REGISTER => Register::execute($request, $rpcRequest), self::GET_ME => GetMe::execute($request, $rpcRequest), + self::GET_CAPTCHA => GetCaptcha::execute($request, $rpcRequest), }; } } \ No newline at end of file diff --git a/src/Socialbox/Managers/CaptchaManager.php b/src/Socialbox/Managers/CaptchaManager.php index fc75d1c..ddf62ca 100644 --- a/src/Socialbox/Managers/CaptchaManager.php +++ b/src/Socialbox/Managers/CaptchaManager.php @@ -6,6 +6,7 @@ use DateTime; use DateTimeInterface; use PDOException; use Socialbox\Classes\Database; +use Socialbox\Classes\Logger; use Socialbox\Classes\Utilities; use Socialbox\Enums\Status\CaptchaStatus; use Socialbox\Exceptions\DatabaseOperationException; @@ -21,7 +22,7 @@ class CaptchaManager * @return string The answer to the captcha. * @throws DatabaseOperationException If the operation fails. */ - private static function createCaptcha(string|RegisteredPeerRecord $peer_uuid): string + public static function createCaptcha(string|RegisteredPeerRecord $peer_uuid): string { // If the peer_uuid is a RegisteredPeerRecord, get the UUID if($peer_uuid instanceof RegisteredPeerRecord) @@ -33,6 +34,7 @@ class CaptchaManager if(!self::captchaExists($peer_uuid)) { + Logger::getLogger()->debug('Creating a new captcha record for peer ' . $peer_uuid); $statement = Database::getConnection()->prepare("INSERT INTO captcha_images (peer_uuid, answer) VALUES (?, ?)"); $statement->bindParam(1, $peer_uuid); $statement->bindParam(2, $answer); @@ -49,6 +51,7 @@ class CaptchaManager return $answer; } + Logger::getLogger()->debug('Updating an existing captcha record for peer ' . $peer_uuid); $statement = Database::getConnection()->prepare("UPDATE captcha_images SET answer=?, status='UNSOLVED', created=NOW() WHERE peer_uuid=?"); $statement->bindParam(1, $answer); $statement->bindParam(2, $peer_uuid); @@ -83,6 +86,7 @@ class CaptchaManager // Return false if the captcha does not exist if(!self::captchaExists($peer_uuid)) { + Logger::getLogger()->warning(sprintf("The requested captcha does not exist for the peer %s", $peer_uuid)); return false; } @@ -91,21 +95,25 @@ class CaptchaManager // Return false if the captcha has already been solved if($captcha->getStatus() === CaptchaStatus::SOLVED) { + Logger::getLogger()->warning(sprintf("The requested captcha has already been solved for the peer %s", $peer_uuid)); return false; } // Return false if the captcha is older than 5 minutes - if ($captcha->getCreated() instanceof DateTimeInterface && $captcha->getCreated()->diff(new DateTime())->i > 5) + if ($captcha->isExpired()) { + Logger::getLogger()->warning(sprintf("The requested captcha is older than 5 minutes for the peer %s", $peer_uuid)); return false; } // Verify the answer if($captcha->getAnswer() !== $answer) { + Logger::getLogger()->warning(sprintf("The answer to the requested captcha is incorrect for the peer %s", $peer_uuid)); return false; } + Logger::getLogger()->debug(sprintf("The answer to the requested captcha is correct for the peer %s", $peer_uuid)); $statement = Database::getConnection()->prepare("UPDATE captcha_images SET status='SOLVED', answered=NOW() WHERE peer_uuid=?"); $statement->bindParam(1, $peer_uuid); @@ -136,6 +144,8 @@ class CaptchaManager $peer_uuid = $peer_uuid->getUuid(); } + Logger::getLogger()->debug('Getting the captcha record for peer ' . $peer_uuid); + try { $statement = Database::getConnection()->prepare("SELECT * FROM captcha_images WHERE peer_uuid=? LIMIT 1"); @@ -171,6 +181,8 @@ class CaptchaManager $peer_uuid = $peer_uuid->getUuid(); } + Logger::getLogger()->debug('Checking if a captcha exists for peer ' . $peer_uuid); + try { $statement = Database::getConnection()->prepare("SELECT COUNT(*) FROM captcha_images WHERE peer_uuid=?"); diff --git a/src/Socialbox/Objects/Database/CaptchaRecord.php b/src/Socialbox/Objects/Database/CaptchaRecord.php index 4427310..6526e26 100644 --- a/src/Socialbox/Objects/Database/CaptchaRecord.php +++ b/src/Socialbox/Objects/Database/CaptchaRecord.php @@ -3,6 +3,7 @@ namespace Socialbox\Objects\Database; use DateTime; +use Socialbox\Classes\Configuration; use Socialbox\Enums\Status\CaptchaStatus; use Socialbox\Interfaces\SerializableInterface; @@ -55,6 +56,16 @@ class CaptchaRecord implements SerializableInterface return $this->created; } + public function getExpires(): DateTime + { + return $this->created->modify(sprintf("+%s seconds", Configuration::getSecurityConfiguration()->getCaptchaTtl())); + } + + public function isExpired(): bool + { + return $this->getExpires() < new DateTime(); + } + /** * @inheritDoc */ diff --git a/src/Socialbox/Objects/Standard/ImageCaptcha.php b/src/Socialbox/Objects/Standard/ImageCaptcha.php new file mode 100644 index 0000000..ed66b24 --- /dev/null +++ b/src/Socialbox/Objects/Standard/ImageCaptcha.php @@ -0,0 +1,53 @@ +expires = $data['expires']; + $this->image = $data['image']; + } + + /** + * @return int + */ + public function getExpires(): int + { + return $this->expires; + } + + /** + * @return string + */ + public function getImage(): string + { + return $this->image; + } + + /** + * @inheritDoc + */ + public static function fromArray(array $data): object + { + return new self($data); + } + + /** + * @inheritDoc + */ + public function toArray(): array + { + return [ + 'expires' => $this->expires, + 'image' => $this->image + ]; + } +} \ No newline at end of file diff --git a/src/Socialbox/Socialbox.php b/src/Socialbox/Socialbox.php index 713cb7d..8687d3f 100644 --- a/src/Socialbox/Socialbox.php +++ b/src/Socialbox/Socialbox.php @@ -52,6 +52,7 @@ { Logger::getLogger()->debug(sprintf('Processing RPC request for method %s', $rpcRequest->getMethod())); $response = $method->execute($clientRequest, $rpcRequest); + Logger::getLogger()->debug(sprintf('%s method executed successfully', $rpcRequest->getMethod())); } catch(StandardException $e) { diff --git a/src/Socialbox/Socialclient.php b/src/Socialbox/Socialclient.php new file mode 100644 index 0000000..7e33df6 --- /dev/null +++ b/src/Socialbox/Socialclient.php @@ -0,0 +1,11 @@ +