From 756297671f8c4cbf951b99555538268975473911 Mon Sep 17 00:00:00 2001 From: netkas Date: Thu, 12 Dec 2024 14:55:44 -0500 Subject: [PATCH] Add client metadata to session creation and enforce TOS/PP --- src/Socialbox/Classes/Configuration.php | 4 + .../RegistrationConfiguration.php | 49 +++ .../StandardMethods/GetSessionState.php | 8 +- src/Socialbox/Enums/Flags/SessionFlags.php | 6 + src/Socialbox/Enums/StandardMethods.php | 54 ++- src/Socialbox/Managers/SessionManager.php | 27 +- .../Objects/Database/SessionRecord.php | 37 ++ src/Socialbox/Objects/RpcRequest.php | 332 +++++++++--------- .../Objects/Standard/SessionState.php | 100 +++++- src/Socialbox/Socialbox.php | 3 +- 10 files changed, 414 insertions(+), 206 deletions(-) diff --git a/src/Socialbox/Classes/Configuration.php b/src/Socialbox/Classes/Configuration.php index 2b0d315..07fc492 100644 --- a/src/Socialbox/Classes/Configuration.php +++ b/src/Socialbox/Classes/Configuration.php @@ -72,6 +72,10 @@ class Configuration // Registration configuration $config->setDefault('registration.enabled', true); + $config->setDefault('registration.privacy_policy_document', null); + $config->setDefault('registration.accept_privacy_policy', true); + $config->setDefault('registration.terms_of_service_document', null); + $config->setDefault('registration.accept_terms_of_service', null); $config->setDefault('registration.password_required', true); $config->setDefault('registration.otp_required', false); $config->setDefault('registration.display_name_required', false); diff --git a/src/Socialbox/Classes/Configuration/RegistrationConfiguration.php b/src/Socialbox/Classes/Configuration/RegistrationConfiguration.php index 269124c..29ebcea 100644 --- a/src/Socialbox/Classes/Configuration/RegistrationConfiguration.php +++ b/src/Socialbox/Classes/Configuration/RegistrationConfiguration.php @@ -9,6 +9,10 @@ namespace Socialbox\Classes\Configuration; class RegistrationConfiguration { private bool $registrationEnabled; + private ?string $privacyPolicyDocument; + private bool $acceptPrivacyPolicy; + private ?string $termsOfServiceDocument; + private bool $acceptTermsOfService; private bool $passwordRequired; private bool $otpRequired; private bool $displayNameRequired; @@ -31,6 +35,10 @@ class RegistrationConfiguration public function __construct(array $data) { $this->registrationEnabled = (bool)$data['enabled']; + $this->privacyPolicyDocument = $data['privacy_policy_document'] ?? null; + $this->acceptPrivacyPolicy = $data['accept_privacy_policy'] ?? true; + $this->termsOfServiceDocument = $data['terms_of_service_document'] ?? null; + $this->acceptTermsOfService = $data['accept_terms_of_service'] ?? true; $this->passwordRequired = (bool)$data['password_required']; $this->otpRequired = (bool)$data['otp_required']; $this->displayNameRequired = (bool)$data['display_name_required']; @@ -50,6 +58,47 @@ class RegistrationConfiguration return $this->registrationEnabled; } + + /** + * Retrieves the privacy policy document. + * + * @return ?string Returns the privacy policy document or null if not set. + */ + public function getPrivacyPolicyDocument(): ?string + { + return $this->privacyPolicyDocument; + } + + /** + * Checks if accepting the privacy policy is required. + * + * @return bool Returns true if the privacy policy must be accepted, false otherwise. + */ + public function isAcceptPrivacyPolicyRequired(): bool + { + return $this->acceptPrivacyPolicy; + } + + /** + * Retrieves the terms of service document. + * + * @return ?string Returns the terms of service document or null if not set. + */ + public function getTermsOfServiceDocument(): ?string + { + return $this->termsOfServiceDocument; + } + + /** + * Checks if accepting the terms of service is required. + * + * @return bool Returns true if the terms of service must be accepted, false otherwise. + */ + public function isAcceptTermsOfServiceRequired(): bool + { + return $this->acceptTermsOfService; + } + /** * Determines if a password is required. * diff --git a/src/Socialbox/Classes/StandardMethods/GetSessionState.php b/src/Socialbox/Classes/StandardMethods/GetSessionState.php index ebe20f1..11f4d2d 100644 --- a/src/Socialbox/Classes/StandardMethods/GetSessionState.php +++ b/src/Socialbox/Classes/StandardMethods/GetSessionState.php @@ -3,6 +3,7 @@ namespace Socialbox\Classes\StandardMethods; use Socialbox\Abstracts\Method; + use Socialbox\Enums\StandardError; use Socialbox\Interfaces\SerializableInterface; use Socialbox\Objects\ClientRequest; use Socialbox\Objects\RpcRequest; @@ -15,6 +16,11 @@ */ public static function execute(ClientRequest $request, RpcRequest $rpcRequest): ?SerializableInterface { - // TODO: Implement execute() method. + if($request->getSessionUuid() === null) + { + return $rpcRequest->produceError(StandardError::SESSION_REQUIRED); + } + + return $rpcRequest->produceResponse($request->getSession()->toStandardSessionState()); } } \ No newline at end of file diff --git a/src/Socialbox/Enums/Flags/SessionFlags.php b/src/Socialbox/Enums/Flags/SessionFlags.php index 15be9a7..bf97b95 100644 --- a/src/Socialbox/Enums/Flags/SessionFlags.php +++ b/src/Socialbox/Enums/Flags/SessionFlags.php @@ -4,12 +4,18 @@ enum SessionFlags : string { + // Session states + case REGISTRATION_REQUIRED = 'REGISTRATION_REQUIRED'; // Peer has to register + case AUTHENTICATION_REQUIRED = 'AUTHENTICATION_REQUIRED'; // Peer has to authenticate + // Verification, require fields case SET_PASSWORD = 'SET_PASSWORD'; // Peer has to set a password case SET_OTP = 'SET_OTP'; // Peer has to set an OTP case SET_DISPLAY_NAME = 'SET_DISPLAY_NAME'; // Peer has to set a display name // Verification, verification requirements + case VER_PRIVACY_POLICY = 'VER_PRIVACY_POLICY'; // Peer has to accept the privacy policy + case VER_TERMS_OF_SERVICE = 'VER_TERMS_OF_SERVICE'; // Peer has to accept the terms of service case VER_EMAIL = 'VER_EMAIL'; // Peer has to verify their email case VER_SMS = 'VER_SMS'; // Peer has to verify their phone number case VER_PHONE_CALL = 'VER_PHONE_CALL'; // Peer has to verify their phone number via a phone call diff --git a/src/Socialbox/Enums/StandardMethods.php b/src/Socialbox/Enums/StandardMethods.php index a606815..65c83bd 100644 --- a/src/Socialbox/Enums/StandardMethods.php +++ b/src/Socialbox/Enums/StandardMethods.php @@ -1,35 +1,31 @@ Ping::execute($request, $rpcRequest), - }; - } -} \ No newline at end of file + return match ($this) + { + self::PING => Ping::execute($request, $rpcRequest), + self::GET_SESSION_STATE => GetSessionState::execute($request, $rpcRequest), + }; + } + } \ No newline at end of file diff --git a/src/Socialbox/Managers/SessionManager.php b/src/Socialbox/Managers/SessionManager.php index d2c2c23..374ff5d 100644 --- a/src/Socialbox/Managers/SessionManager.php +++ b/src/Socialbox/Managers/SessionManager.php @@ -27,13 +27,12 @@ * Creates a new session with the given public key. * * @param string $publicKey The public key to associate with the new session. - * - * @return string The UUID of the newly created session. + * @param RegisteredPeerRecord $peer * * @throws InvalidArgumentException If the public key is empty or invalid. * @throws DatabaseOperationException If there is an error while creating the session in the database. */ - public static function createSession(string $publicKey, RegisteredPeerRecord $peer): string + public static function createSession(string $publicKey, RegisteredPeerRecord $peer, string $clientName, string $clientVersion): string { if($publicKey === '') { @@ -50,6 +49,8 @@ if($peer->isEnabled()) { + $flags[] = SessionFlags::AUTHENTICATION_REQUIRED; + if(RegisteredPeerManager::getPasswordAuthentication($peer)) { $flags[] = SessionFlags::VER_PASSWORD; @@ -62,6 +63,8 @@ } else { + $flags[] = SessionFlags::REGISTRATION_REQUIRED; + if(Configuration::getRegistrationConfiguration()->isDisplayNameRequired()) { $flags[] = SessionFlags::SET_DISPLAY_NAME; @@ -96,6 +99,16 @@ { $flags[] = SessionFlags::SET_OTP; } + + if(Configuration::getRegistrationConfiguration()->isAcceptPrivacyPolicyRequired()) + { + $flags[] = SessionFlags::VER_PRIVACY_POLICY; + } + + if(Configuration::getRegistrationConfiguration()->isAcceptTermsOfServiceRequired()) + { + $flags[] = SessionFlags::VER_TERMS_OF_SERVICE; + } } if(count($flags) > 0) @@ -111,11 +124,13 @@ try { - $statement = Database::getConnection()->prepare("INSERT INTO sessions (uuid, peer_uuid, public_key, flags) VALUES (?, ?, ?, ?)"); + $statement = Database::getConnection()->prepare("INSERT INTO sessions (uuid, peer_uuid, client_name, client_version, public_key, flags) VALUES (?, ?, ?, ?, ?, ?)"); $statement->bindParam(1, $uuid); $statement->bindParam(2, $peerUuid); - $statement->bindParam(3, $publicKey); - $statement->bindParam(4, $implodedFlags); + $statement->bindParam(3, $clientName); + $statement->bindParam(4, $clientVersion); + $statement->bindParam(5, $publicKey); + $statement->bindParam(6, $implodedFlags); $statement->execute(); } catch(PDOException $e) diff --git a/src/Socialbox/Objects/Database/SessionRecord.php b/src/Socialbox/Objects/Database/SessionRecord.php index 6f654a5..03dea46 100644 --- a/src/Socialbox/Objects/Database/SessionRecord.php +++ b/src/Socialbox/Objects/Database/SessionRecord.php @@ -6,11 +6,14 @@ use Socialbox\Enums\Flags\SessionFlags; use Socialbox\Enums\SessionState; use Socialbox\Interfaces\SerializableInterface; + use Socialbox\Managers\RegisteredPeerManager; class SessionRecord implements SerializableInterface { private string $uuid; private ?string $peerUuid; + private string $clientName; + private string $clientVersion; private bool $authenticated; private string $publicKey; private SessionState $state; @@ -36,6 +39,8 @@ { $this->uuid = $data['uuid']; $this->peerUuid = $data['peer_uuid'] ?? null; + $this->clientName = $data['client_name']; + $this->clientVersion = $data['client_version']; $this->authenticated = $data['authenticated'] ?? false; $this->publicKey = $data['public_key']; $this->created = $data['created']; @@ -149,6 +154,38 @@ return $this->lastRequest; } + /** + * Retrieves the client name. + * + * @return string Returns the client name. + */ + public function getClientName(): string + { + return $this->clientName; + } + + /** + * Retrieves the client version. + * + * @return string Returns the client version. + */ + public function getClientVersion(): string + { + return $this->clientVersion; + } + + public function toStandardSessionState(): \Socialbox\Objects\Standard\SessionState + { + return new \Socialbox\Objects\Standard\SessionState([ + 'uuid' => $this->uuid, + 'identified_as' => RegisteredPeerManager::getPeer($this->peerUuid)->getAddress(), + 'authenticated' => $this->authenticated, + 'flags' => $this->flags, + 'created' => $this->created + ]); + } + + /** * Creates a new instance of the class using the provided array data. * diff --git a/src/Socialbox/Objects/RpcRequest.php b/src/Socialbox/Objects/RpcRequest.php index db85a8f..b44089c 100644 --- a/src/Socialbox/Objects/RpcRequest.php +++ b/src/Socialbox/Objects/RpcRequest.php @@ -1,193 +1,193 @@ method = $method; - $this->parameters = $parameters; - $this->id = $id; - } + private ?string $id; + private string $method; + private ?array $parameters; - /** - * Returns the ID of the request. - * - * @return string|null The ID of the request. - */ - public function getId(): ?string - { - return $this->id; - } - - /** - * Returns the method of the request. - * - * @return string The method of the request. - */ - public function getMethod(): string - { - return $this->method; - } - - /** - * Returns the parameters of the request. - * - * @return array|null The parameters of the request, null if the request is a notification. - */ - public function getParameters(): ?array - { - return $this->parameters; - } - - /** - * Checks if the parameter exists within the RPC request - * - * @param string $parameter The parameter to check - * @return bool True if the parameter exists, False otherwise. - */ - public function containsParameter(string $parameter): bool - { - return isset($this->parameters[$parameter]); - } - - /** - * Returns the parameter value from the RPC request - * - * @param string $parameter The parameter name to get - * @return mixed The parameter value, null if the parameter value is null or not found. - */ - public function getParameter(string $parameter): mixed - { - if(!$this->containsParameter($parameter)) + /** + * Constructs the object from an array of data. + * + * @param string $method The method of the request. + * @param string|null $id The ID of the request. + * @param array|null $parameters The parameters of the request. + */ + public function __construct(string $method, ?string $id, ?array $parameters) { - return null; + $this->method = $method; + $this->parameters = $parameters; + $this->id = $id; } - return $this->parameters[$parameter]; - } - - /** - * Produces a response based off the request, null if the request is a notification - * - * @param mixed|null $result - * @return RpcResponse|null - */ - public function produceResponse(mixed $result=null): ?RpcResponse - { - if($this->id == null) + /** + * Returns the ID of the request. + * + * @return string|null The ID of the request. + */ + public function getId(): ?string { - return null; + return $this->id; } - $valid = false; - if(is_array($result)) + /** + * Returns the method of the request. + * + * @return string The method of the request. + */ + public function getMethod(): string { - $valid = true; - } - elseif($result instanceof SerializableInterface) - { - $valid = true; - } - elseif(is_string($result)) - { - $valid = true; - } - elseif(is_bool($result)) - { - $valid = true; - } - elseif(is_int($result)) - { - $valid = true; - } - elseif(is_null($result)) - { - $valid = true; + return $this->method; } - if(!$valid) + /** + * Returns the parameters of the request. + * + * @return array|null The parameters of the request, null if the request is a notification. + */ + public function getParameters(): ?array { - throw new InvalidArgumentException('The \'$result\' property must either be string, boolean, integer, array, null or SerializableInterface'); + return $this->parameters; } - Logger::getLogger()->verbose(sprintf('Producing response for request %s', $this->id)); - return new RpcResponse($this->id, $result); - } - - /** - * Produces an error response based off the request, null if the request is a notification - * - * @param StandardError $error - * @param string|null $message - * @return RpcError|null - */ - public function produceError(StandardError $error, ?string $message=null): ?RpcError - { - if($this->id == null) + /** + * Checks if the parameter exists within the RPC request + * + * @param string $parameter The parameter to check + * @return bool True if the parameter exists, False otherwise. + */ + public function containsParameter(string $parameter): bool { - return null; + return isset($this->parameters[$parameter]); } - if($message == null) + /** + * Returns the parameter value from the RPC request + * + * @param string $parameter The parameter name to get + * @return mixed The parameter value, null if the parameter value is null or not found. + */ + public function getParameter(string $parameter): mixed { - $message = $error->getMessage(); + if(!$this->containsParameter($parameter)) + { + return null; + } + + return $this->parameters[$parameter]; } - return new RpcError($this->id, $error, $message); - } + /** + * Produces a response based off the request, null if the request is a notification + * + * @param mixed|null $result + * @return RpcResponse|null + */ + public function produceResponse(mixed $result=null): ?RpcResponse + { + if($this->id == null) + { + return null; + } - /** - * @param StandardException $e - * @return RpcError|null - */ - public function handleStandardException(StandardException $e): ?RpcError - { - return $this->produceError($e->getStandardError(), $e->getMessage()); - } + $valid = false; + if(is_array($result)) + { + $valid = true; + } + elseif($result instanceof SerializableInterface) + { + $valid = true; + } + elseif(is_string($result)) + { + $valid = true; + } + elseif(is_bool($result)) + { + $valid = true; + } + elseif(is_int($result)) + { + $valid = true; + } + elseif(is_null($result)) + { + $valid = true; + } - /** - * Returns an array representation of the object. - * - * @return array - */ - public function toArray(): array - { - return [ - 'id' => $this->id, - 'method' => $this->method, - 'parameters' => $this->parameters - ]; - } + if(!$valid) + { + throw new InvalidArgumentException('The \'$result\' property must either be string, boolean, integer, array, null or SerializableInterface'); + } - /** - * Returns the request object from an array of data. - * - * @param array $data The data to construct the object from. - * @return RpcRequest The request object. - */ - public static function fromArray(array $data): RpcRequest - { - return new RpcRequest($data['method'], $data['id'] ?? null, $data['parameters'] ?? null); - } -} \ No newline at end of file + Logger::getLogger()->verbose(sprintf('Producing response for request %s', $this->id)); + return new RpcResponse($this->id, $result); + } + + /** + * Produces an error response based off the request, null if the request is a notification + * + * @param StandardError $error + * @param string|null $message + * @return RpcError|null + */ + public function produceError(StandardError $error, ?string $message=null): ?RpcError + { + if($this->id == null) + { + return null; + } + + if($message == null) + { + $message = $error->getMessage(); + } + + return new RpcError($this->id, $error, $message); + } + + /** + * @param StandardException $e + * @return RpcError|null + */ + public function handleStandardException(StandardException $e): ?RpcError + { + return $this->produceError($e->getStandardError(), $e->getMessage()); + } + + /** + * Returns an array representation of the object. + * + * @return array + */ + public function toArray(): array + { + return [ + 'id' => $this->id, + 'method' => $this->method, + 'parameters' => $this->parameters + ]; + } + + /** + * Returns the request object from an array of data. + * + * @param array $data The data to construct the object from. + * @return RpcRequest The request object. + */ + public static function fromArray(array $data): RpcRequest + { + return new RpcRequest($data['method'], $data['id'] ?? null, $data['parameters'] ?? null); + } + } \ No newline at end of file diff --git a/src/Socialbox/Objects/Standard/SessionState.php b/src/Socialbox/Objects/Standard/SessionState.php index 67819a5..5c81dd4 100644 --- a/src/Socialbox/Objects/Standard/SessionState.php +++ b/src/Socialbox/Objects/Standard/SessionState.php @@ -2,7 +2,103 @@ namespace Socialbox\Objects\Standard; - class SessionState - { + use DateTime; + use Socialbox\Enums\Flags\SessionFlags; + use Socialbox\Interfaces\SerializableInterface; + class SessionState implements SerializableInterface + { + private string $uuid; + private string $identifiedAs; + private bool $authenticated; + /** + * @var SessionFlags[]|null + */ + private ?array $flags; + private DateTime $created; + + /** + * Constructor for initializing the object with the provided data. + * + * @param array $data An associative array containing the values for initializing the object. + * - 'uuid': string, Unique identifier. + * - 'identified_as': mixed, The identity information. + * - 'authenticated': bool, Whether the object is authenticated. + * - 'flags': string|null, Optional flags in + * @throws \DateMalformedStringException + */ + public function __construct(array $data) + { + $this->uuid = $data['uuid']; + $this->identifiedAs = $data['identified_as']; + $this->authenticated = $data['authenticated']; + + if(is_string($data['flags'])) + { + $this->flags = SessionFlags::fromString($data['flags']); + } + elseif(is_array($data['flags'])) + { + $this->flags = $data['flags']; + } + else + { + $this->flags = null; + } + + if(is_int($data['created'])) + { + $this->created = new DateTime(); + $this->created->setTimestamp($data['created']); + } + elseif($data['created'] instanceof DateTime) + { + $this->created = $data['created']; + } + else + { + $this->created = new DateTime($data['created']); + } + } + + public function getUuid(): string + { + return $this->uuid; + } + + public function getIdentifiedAs(): string + { + return $this->identifiedAs; + } + + public function isAuthenticated(): bool + { + return $this->authenticated; + } + + public function getFlags(): ?array + { + return $this->flags; + } + + public function getCreated(): DateTime + { + return $this->created; + } + + public static function fromArray(array $data): SessionState + { + return new self($data); + } + + public function toArray(): array + { + return [ + 'uuid' => $this->uuid, + 'identified_as' => $this->identifiedAs, + 'authenticated' => $this->authenticated, + 'flags' => $this->flags, + 'created' => $this->created->getTimestamp() + ]; + } } \ No newline at end of file diff --git a/src/Socialbox/Socialbox.php b/src/Socialbox/Socialbox.php index 3b5c175..cee7473 100644 --- a/src/Socialbox/Socialbox.php +++ b/src/Socialbox/Socialbox.php @@ -149,8 +149,7 @@ } // Create the session UUID - // TODO: Save client name and version to the database - $sessionUuid = SessionManager::createSession($clientRequest->getHeader(StandardHeaders::PUBLIC_KEY), $registeredPeer); + $sessionUuid = SessionManager::createSession($clientRequest->getHeader(StandardHeaders::PUBLIC_KEY), $registeredPeer, $clientRequest->getClientName(), $clientRequest->getClientVersion()); http_response_code(201); // Created print($sessionUuid); // Return the session UUID }