Implemented peer syncing (sort-of)
This commit is contained in:
parent
2e1a7a612c
commit
c93568b8f1
10 changed files with 365 additions and 67 deletions
|
@ -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<entity>[a-zA-Z0-9_-]+):(?P<uid>[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;
|
||||
|
|
|
@ -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.
|
||||
}
|
|
@ -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,
|
||||
];
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Enums\Standard\ErrorCodes;
|
||||
use Throwable;
|
||||
|
||||
class BadRequestException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, ErrorCodes::BAD_REQUEST, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Enums\Standard\ErrorCodes;
|
||||
use Throwable;
|
||||
|
||||
class InvalidDataException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, ErrorCodes::INVALID_DATA, $previous);
|
||||
}
|
||||
}
|
|
@ -12,11 +12,17 @@
|
|||
use FederationLib\Exceptions\Standard\InternalServerException;
|
||||
use FederationLib\Exceptions\Standard\InvalidClientDescriptionException;
|
||||
use FederationLib\Exceptions\Standard\InvalidClientNameException;
|
||||
use FederationLib\Exceptions\Standard\InvalidFederatedAddressException;
|
||||
use FederationLib\Exceptions\Standard\InvalidPeerAssociationTypeException;
|
||||
use FederationLib\Exceptions\Standard\InvalidPeerMetadataException;
|
||||
use FederationLib\Exceptions\Standard\UnsupportedPeerType;
|
||||
use FederationLib\Managers\AssociationManager;
|
||||
use FederationLib\Managers\ClientManager;
|
||||
use FederationLib\Managers\PeerManager;
|
||||
use FederationLib\Objects\ClientRecord;
|
||||
use FederationLib\Objects\ResolvedIdentity;
|
||||
use FederationLib\Objects\Standard\ClientIdentity;
|
||||
use FederationLib\Objects\Standard\PeerUpdate;
|
||||
use TamerLib\Enums\TamerMode;
|
||||
use TamerLib\tm;
|
||||
use Throwable;
|
||||
|
@ -33,6 +39,11 @@
|
|||
*/
|
||||
private PeerManager $peer_manager;
|
||||
|
||||
/**
|
||||
* @var AssociationManager
|
||||
*/
|
||||
private AssociationManager $association_manager;
|
||||
|
||||
/**
|
||||
* FederationLib constructor.
|
||||
*/
|
||||
|
@ -40,6 +51,7 @@
|
|||
{
|
||||
$this->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,9 +388,19 @@
|
|||
throw new Exceptions\Standard\AccessDeniedException('You must be authenticated to sync a peer');
|
||||
}
|
||||
|
||||
if($peer_update->getMetadata() === null)
|
||||
{
|
||||
throw new Exceptions\Standard\InvalidPeerMetadataException('You must provide metadata to sync a peer');
|
||||
}
|
||||
|
||||
// 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(), $federated_address, $metadata);
|
||||
$this->peer_manager->syncPeer($identity->getClientUuid(), $peer_update->getAddress(), $peer_update->getMetadata());
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
|
@ -383,7 +411,19 @@
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
13
src/FederationLib/Interfaces/ValidateInterface.php
Normal file
13
src/FederationLib/Interfaces/ValidateInterface.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Interfaces;
|
||||
|
||||
interface ValidateInterface
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function validate(): void;
|
||||
|
||||
// TODO: There could be a better way to do this
|
||||
}
|
|
@ -46,7 +46,7 @@
|
|||
* @throws DatabaseException
|
||||
* @throws InvalidPeerAssociationTypeException
|
||||
*/
|
||||
public function associate(ClientRecord|string $client_uuid, ParsedFederatedAddress|string $parent, ParsedFederatedAddress|string $child, string $type): void
|
||||
public function associate(ClientRecord|string $client_uuid, ParsedFederatedAddress|string $child, ParsedFederatedAddress|string $parent, string $type): void
|
||||
{
|
||||
if(!Validate::peerAssociationType($type))
|
||||
{
|
||||
|
|
140
src/FederationLib/Objects/Standard/PeerUpdate.php
Normal file
140
src/FederationLib/Objects/Standard/PeerUpdate.php
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Objects\Standard;
|
||||
|
||||
use FederationLib\Classes\Security;
|
||||
use FederationLib\Classes\Validate;
|
||||
use FederationLib\Exceptions\Standard\InvalidDataException;
|
||||
use FederationLib\Interfaces\SerializableObjectInterface;
|
||||
use FederationLib\Interfaces\ValidateInterface;
|
||||
use FederationLib\Objects\ParsedFederatedAddress;
|
||||
use FederationLib\Objects\Standard\PeerUpdate\Association;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class PeerUpdate implements SerializableObjectInterface, ValidateInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $address;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private $metadata;
|
||||
|
||||
/**
|
||||
* @var Association|null
|
||||
*/
|
||||
private $association;
|
||||
|
||||
/**
|
||||
* Validates the given object data, throws an exception if invalid.
|
||||
*
|
||||
* @return void
|
||||
* @throws InvalidDataException
|
||||
*/
|
||||
public function validate(): void
|
||||
{
|
||||
if(!is_string($this->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;
|
||||
}
|
||||
}
|
108
src/FederationLib/Objects/Standard/PeerUpdate/Association.php
Normal file
108
src/FederationLib/Objects/Standard/PeerUpdate/Association.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Objects\Standard\PeerUpdate;
|
||||
|
||||
use FederationLib\Classes\Security;
|
||||
use FederationLib\Classes\Validate;
|
||||
use FederationLib\Exceptions\Standard\InvalidDataException;
|
||||
use FederationLib\Interfaces\SerializableObjectInterface;
|
||||
use FederationLib\Interfaces\ValidateInterface;
|
||||
use FederationLib\Objects\ParsedFederatedAddress;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class Association implements SerializableObjectInterface, ValidateInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $parent;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* Validates the given object data, throws an exception if invalid.
|
||||
*
|
||||
* @return void
|
||||
* @throws InvalidDataException
|
||||
*/
|
||||
public function validate(): void
|
||||
{
|
||||
if(!is_string($this->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;
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue