From c93568b8f143670c1f28099553735fb32675b79a Mon Sep 17 00:00:00 2001 From: Netkas Date: Fri, 23 Jun 2023 02:05:48 -0400 Subject: [PATCH] Implemented peer syncing (sort-of) --- src/FederationLib/Classes/Utilities.php | 47 +----- .../Enums/Standard/ErrorCodes.php | 10 +- src/FederationLib/Enums/Standard/Methods.php | 4 - .../Standard/BadRequestException.php | 19 +++ .../Standard/InvalidDataException.php | 19 +++ src/FederationLib/FederationLib.php | 70 +++++++-- .../Interfaces/ValidateInterface.php | 13 ++ .../Managers/AssociationManager.php | 2 +- .../Objects/Standard/PeerUpdate.php | 140 ++++++++++++++++++ .../Standard/PeerUpdate/Association.php | 108 ++++++++++++++ 10 files changed, 365 insertions(+), 67 deletions(-) create mode 100644 src/FederationLib/Exceptions/Standard/BadRequestException.php create mode 100644 src/FederationLib/Exceptions/Standard/InvalidDataException.php create mode 100644 src/FederationLib/Interfaces/ValidateInterface.php create mode 100644 src/FederationLib/Objects/Standard/PeerUpdate.php create mode 100644 src/FederationLib/Objects/Standard/PeerUpdate/Association.php diff --git a/src/FederationLib/Classes/Utilities.php b/src/FederationLib/Classes/Utilities.php index ac03fe8..559a332 100644 --- a/src/FederationLib/Classes/Utilities.php +++ b/src/FederationLib/Classes/Utilities.php @@ -67,51 +67,6 @@ return $name; } - /** - * Parses a federated address into an array of parts. - * Example: entity:uid - * - * @param string $address - * @return array - */ - public static function parseFederatedAddress(string $address): array - { - if (preg_match($address, '/^(?P[a-zA-Z0-9_-]+):(?P[a-zA-Z0-9_-]+)$/', $matches, PREG_UNMATCHED_AS_NULL)) - { - return [ - 'entity' => $matches['entity'], - 'uid' => $matches['uid'] - ]; - } - - throw new InvalidArgumentException(sprintf('Invalid address provided: %s', $address)); - } - - /** - * Recursively converts a Throwable into an array representation. - * - * @param Throwable $throwable - * @return array - */ - public static function throwableToArray(Throwable $throwable): array - { - $results = [ - 'message' => $throwable->getMessage(), - 'code' => $throwable->getCode(), - 'file' => $throwable->getFile(), - 'line' => $throwable->getLine(), - 'trace' => $throwable->getTrace(), - 'previous' => $throwable->getPrevious() - ]; - - if($results['previous'] instanceof Throwable) - { - $results['previous'] = self::throwableToArray($results['previous']); - } - - return $results; - } - /** * Uses the z-score method to detect anomalies in an array of numbers. * The key of the returned array is the index of the number in the original array. @@ -170,7 +125,7 @@ } // Generate a random number between 0 and 1 - $rand = mt_rand() / getrandmax(); + $rand = mt_rand() / mt_getrandmax(); // Select an item $cumulativeWeight = 0.0; diff --git a/src/FederationLib/Enums/Standard/ErrorCodes.php b/src/FederationLib/Enums/Standard/ErrorCodes.php index 2c67447..21db82f 100644 --- a/src/FederationLib/Enums/Standard/ErrorCodes.php +++ b/src/FederationLib/Enums/Standard/ErrorCodes.php @@ -53,6 +53,10 @@ public const INVALID_PEER_ASSOCIATION_TYPE = 2003; + public const INVALID_DATA = 2004; + + public const BAD_REQUEST = 2005; + public const ALL = [ self::INTERNAL_SERVER_ERROR, @@ -67,6 +71,10 @@ self::INVALID_PEER_METADATA, self::PEER_METADATA_NOT_FOUND, self::INVALID_FEDERATED_ADDRESS, - self::INVALID_PEER_ASSOCIATION_TYPE + self::INVALID_PEER_ASSOCIATION_TYPE, + self::INVALID_DATA, + self::BAD_REQUEST ]; + + // TOOD: Re-organize all the error codes to make sense, this is not final. } \ No newline at end of file diff --git a/src/FederationLib/Enums/Standard/Methods.php b/src/FederationLib/Enums/Standard/Methods.php index 75932bd..8fad40b 100644 --- a/src/FederationLib/Enums/Standard/Methods.php +++ b/src/FederationLib/Enums/Standard/Methods.php @@ -6,26 +6,22 @@ { public const PING = 'ping'; public const WHOAMI = 'whoami'; - public const CREATE_CLIENT = 'create_client'; public const GET_CLIENT = 'get_client'; public const UPDATE_CLIENT_NAME = 'update_client_name'; public const UPDATE_CLIENT_DESCRIPTION = 'update_client_description'; public const UPDATE_CLIENT_PERMISSION_ROLE = 'update_client_permission_role'; - public const SYNC_PEER = 'sync_peer'; public const ALL = [ self::PING, self::WHOAMI, - self::CREATE_CLIENT, self::GET_CLIENT, self::UPDATE_CLIENT_NAME, self::UPDATE_CLIENT_DESCRIPTION, self::UPDATE_CLIENT_PERMISSION_ROLE, - self::SYNC_PEER, ]; } \ No newline at end of file diff --git a/src/FederationLib/Exceptions/Standard/BadRequestException.php b/src/FederationLib/Exceptions/Standard/BadRequestException.php new file mode 100644 index 0000000..198aab0 --- /dev/null +++ b/src/FederationLib/Exceptions/Standard/BadRequestException.php @@ -0,0 +1,19 @@ +client_manager = new ClientManager($this); $this->peer_manager = new PeerManager($this); + $this->association_manager = new AssociationManager($this); } /** @@ -346,19 +358,25 @@ } /** + * Syncs the peer to the database, this requires the peer's metadata to be set so that the peer can be + * registered or updated if it already exists, additionally more information about the peer can be set + * such as the peer's association. + * + * Returns True if successful + * * @param ClientIdentity|null $identity - * @param string $federated_address - * @param array $metadata + * @param PeerUpdate $peer_update * @return bool * @throws AccessDeniedException * @throws ClientNotFoundException * @throws DatabaseException - * @throws Exceptions\Standard\InvalidFederatedAddressException - * @throws Exceptions\Standard\InvalidPeerMetadataException - * @throws Exceptions\Standard\UnsupportedPeerType + * @throws InvalidFederatedAddressException + * @throws InvalidPeerAssociationTypeException + * @throws InvalidPeerMetadataException + * @throws UnsupportedPeerType * @throws InternalServerException */ - public function syncPeer(?ClientIdentity $identity, string $federated_address, array $metadata): bool + public function syncPeer(?ClientIdentity $identity, PeerUpdate $peer_update): bool { if(!$this->checkPermission(Methods::SYNC_PEER, $this->resolveIdentity($identity))) { @@ -370,20 +388,42 @@ throw new Exceptions\Standard\AccessDeniedException('You must be authenticated to sync a peer'); } - try + if($peer_update->getMetadata() === null) { - $this->peer_manager->syncPeer($identity->getClientUuid(), $federated_address, $metadata); + throw new Exceptions\Standard\InvalidPeerMetadataException('You must provide metadata to sync a peer'); } - catch(Exception $e) - { - if(in_array($e->getCode(), ErrorCodes::ALL, true)) - { - throw $e; - } - throw new Exceptions\Standard\InternalServerException('There was an error while syncing the peer', $e); + // TODO: Make this run in parallel + + // Update the metadata if there's any + if($peer_update->getMetadata() !== null) + { + try + { + $this->peer_manager->syncPeer($identity->getClientUuid(), $peer_update->getAddress(), $peer_update->getMetadata()); + } + catch(Exception $e) + { + if(in_array($e->getCode(), ErrorCodes::ALL, true)) + { + throw $e; + } + + throw new Exceptions\Standard\InternalServerException('There was an error while syncing the peer', $e); + } + } + + // Update the association if there's one + if($peer_update->getAssociation() !== null) + { + $association = $peer_update->getAssociation(); + /** @noinspection NullPointerExceptionInspection */ + $this->association_manager->associate($identity->getClientUuid(), + $peer_update->getAddress(), $association->getParent(), $association->getType() + ); } return true; } + } \ No newline at end of file diff --git a/src/FederationLib/Interfaces/ValidateInterface.php b/src/FederationLib/Interfaces/ValidateInterface.php new file mode 100644 index 0000000..2ca6f6c --- /dev/null +++ b/src/FederationLib/Interfaces/ValidateInterface.php @@ -0,0 +1,13 @@ +address)) + { + throw new InvalidDataException(sprintf('Peer\'s address must be a string, %s given.', Security::gettype($this->address))); + } + + try + { + $parsed_address = new ParsedFederatedAddress($this->address); + } + catch(InvalidArgumentException $e) + { + throw new InvalidDataException(sprintf('Peer\'s address must be a valid federated address, %s given.', $this->address), $e); + } + + if(!Validate::validateEntityType($parsed_address->getPeerType())) + { + throw new InvalidDataException(sprintf('Peer\'s address must be a valid peer type, %s given.', $parsed_address->getPeerType())); + } + + if(!is_null($this->metadata) && !is_array($this->metadata)) + { + throw new InvalidDataException(sprintf('Peer\'s metadata must be an array or null, %s given.', Security::gettype($this->metadata))); + } + + if(!is_null($this->association)) + { + $this->association->validate(); + } + } + + /** + * Returns the federated address of the peer. + * + * @return string + */ + public function getAddress(): string + { + return $this->address; + } + + /** + * Optional. Returns the metadata of the peer, or null if none. + * + * @return array|null + */ + public function getMetadata(): ?array + { + return $this->metadata; + } + + /** + * Optional. Returns the association of the peer, or null if none. + * + * @return Association|null + */ + public function getAssociation(): ?Association + { + return $this->association; + } + + /** + * Returns an array representation of the string + * + * @return array + */ + public function toArray(): array + { + $association = null; + + if($association instanceof Association) + { + $association = $this->association->toArray(); + } + + return [ + 'address' => $this->address, + 'metadata' => $this->metadata, + 'association' => $association + ]; + } + + /** + * Constructs the object from an array representation + * + * @param array $array + * @return PeerUpdate + */ + public static function fromArray(array $array): PeerUpdate + { + $object = new self(); + + $object->address = $array['address']; + $object->metadata = $array['metadata']; + + if(isset($array['association'])) + { + $object->association = Association::fromArray($array['association']); + } + + return $object; + } + } \ No newline at end of file diff --git a/src/FederationLib/Objects/Standard/PeerUpdate/Association.php b/src/FederationLib/Objects/Standard/PeerUpdate/Association.php new file mode 100644 index 0000000..164f149 --- /dev/null +++ b/src/FederationLib/Objects/Standard/PeerUpdate/Association.php @@ -0,0 +1,108 @@ +parent)) + { + throw new InvalidDataException(sprintf('Peer\'s assocation parent must be a string, %s given.', Security::gettype($this->parent))); + } + + if(!is_string($this->type)) + { + throw new InvalidDataException(sprintf('Peer\'s assocation type must be a string, %s given.', Security::gettype($this->type))); + } + + try + { + $parsed_address = new ParsedFederatedAddress($this->parent); + } + catch(InvalidArgumentException $e) + { + throw new InvalidDataException(sprintf('Peer\'s association parent must be a valid federated address, %s given.', $this->parent), $e); + } + + if(!Validate::validateEntityType($parsed_address->getPeerType())) + { + throw new InvalidDataException(sprintf('Peer\'s association parent must be a valid peer type, %s given.', $parsed_address->getPeerType())); + } + } + + /** + * Returns the parent of the association. + * + * @return string + */ + public function getParent(): string + { + return $this->parent; + } + + /** + * Returns the type of association. + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * Returns an array representation of the object. + * + * @return array + */ + public function toArray(): array + { + return [ + 'parent' => $this->parent, + 'type' => $this->type, + ]; + } + + /** + * Constructs the object from an array representation. + * + * @param array $array + * @return Association + */ + public static function fromArray(array $array): Association + { + $object = new self(); + + $object->parent = $array['parent']; + $object->type = $array['type']; + + return $object; + } + } \ No newline at end of file