Refactor session initialization and host validation logic
This commit is contained in:
parent
738f8a455c
commit
c380556255
5 changed files with 138 additions and 44 deletions
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Socialbox\Classes;
|
namespace Socialbox\Classes;
|
||||||
|
|
||||||
|
use Socialbox\Enums\Options\ClientOptions;
|
||||||
use Socialbox\Enums\StandardHeaders;
|
use Socialbox\Enums\StandardHeaders;
|
||||||
use Socialbox\Enums\Types\RequestType;
|
use Socialbox\Enums\Types\RequestType;
|
||||||
use Socialbox\Exceptions\CryptographyException;
|
use Socialbox\Exceptions\CryptographyException;
|
||||||
|
@ -60,7 +61,19 @@
|
||||||
|
|
||||||
// Set the initial properties
|
// Set the initial properties
|
||||||
$this->peerAddress = $peerAddress;
|
$this->peerAddress = $peerAddress;
|
||||||
|
|
||||||
|
// If the username is `host` and the domain is the same as this server's domain, we use our keypair
|
||||||
|
// Essentially this is a special case for the server to contact another server
|
||||||
|
if($this->peerAddress->isHost())
|
||||||
|
{
|
||||||
|
$this->keyPair = new KeyPair(Configuration::getInstanceConfiguration()->getPublicKey(), Configuration::getInstanceConfiguration()->getPrivateKey());
|
||||||
|
}
|
||||||
|
// Otherwise we generate a random keypair
|
||||||
|
else
|
||||||
|
{
|
||||||
$this->keyPair = Cryptography::generateKeyPair();
|
$this->keyPair = Cryptography::generateKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
$this->encryptionKey = Cryptography::generateEncryptionKey();
|
$this->encryptionKey = Cryptography::generateEncryptionKey();
|
||||||
|
|
||||||
// Resolve the domain and get the server's Public Key & RPC Endpoint
|
// Resolve the domain and get the server's Public Key & RPC Endpoint
|
||||||
|
@ -97,16 +110,25 @@
|
||||||
{
|
{
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
|
|
||||||
curl_setopt($ch, CURLOPT_URL, $this->rpcEndpoint);
|
// Basic session details
|
||||||
curl_setopt($ch, CURLOPT_HTTPGET, true);
|
$headers = [
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
||||||
StandardHeaders::REQUEST_TYPE->value . ': ' . RequestType::INITIATE_SESSION->value,
|
StandardHeaders::REQUEST_TYPE->value . ': ' . RequestType::INITIATE_SESSION->value,
|
||||||
StandardHeaders::CLIENT_NAME->value . ': ' . self::CLIENT_NAME,
|
StandardHeaders::CLIENT_NAME->value . ': ' . self::CLIENT_NAME,
|
||||||
StandardHeaders::CLIENT_VERSION->value . ': ' . self::CLIENT_VERSION,
|
StandardHeaders::CLIENT_VERSION->value . ': ' . self::CLIENT_VERSION,
|
||||||
StandardHeaders::PUBLIC_KEY->value . ': ' . $this->keyPair->getPublicKey(),
|
|
||||||
StandardHeaders::IDENTIFY_AS->value . ': ' . $this->peerAddress->getAddress(),
|
StandardHeaders::IDENTIFY_AS->value . ': ' . $this->peerAddress->getAddress(),
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
// If we're not connecting as the host, we need to provide our public key
|
||||||
|
// Otherwise, the server will obtain the public key itself from DNS records rather than trusting the client
|
||||||
|
if(!$this->peerAddress->isHost())
|
||||||
|
{
|
||||||
|
$headers[] = StandardHeaders::PUBLIC_KEY->value . ': ' . $this->keyPair->getPublicKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $this->rpcEndpoint);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPGET, true);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
|
||||||
$response = curl_exec($ch);
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
|
8
src/Socialbox/Enums/Options/ClientOptions.php
Normal file
8
src/Socialbox/Enums/Options/ClientOptions.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Socialbox\Enums\Options;
|
||||||
|
|
||||||
|
enum ClientOptions
|
||||||
|
{
|
||||||
|
case EXTERNAL_CONNECTION;
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Socialbox\Objects;
|
namespace Socialbox\Objects;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use Socialbox\Classes\Configuration;
|
||||||
use Socialbox\Classes\Validator;
|
use Socialbox\Classes\Validator;
|
||||||
use Socialbox\Enums\ReservedUsernames;
|
use Socialbox\Enums\ReservedUsernames;
|
||||||
|
|
||||||
|
@ -67,7 +68,7 @@
|
||||||
*/
|
*/
|
||||||
public function isHost(): bool
|
public function isHost(): bool
|
||||||
{
|
{
|
||||||
return $this->username === ReservedUsernames::HOST->value;
|
return $this->username === ReservedUsernames::HOST->value && $this->domain === Configuration::getInstanceConfiguration()->getDomain();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
*
|
*
|
||||||
* @param string|PeerAddress $peerAddress The address of the peer to connect to.
|
* @param string|PeerAddress $peerAddress The address of the peer to connect to.
|
||||||
* @param ExportedSession|null $exportedSession Optional. The exported session to use for communication.
|
* @param ExportedSession|null $exportedSession Optional. The exported session to use for communication.
|
||||||
|
* @param array $options Optional. Additional options to pass to the client.
|
||||||
* @throws CryptographyException If the public key is invalid.
|
* @throws CryptographyException If the public key is invalid.
|
||||||
* @throws ResolutionException If the domain cannot be resolved.
|
* @throws ResolutionException If the domain cannot be resolved.
|
||||||
* @throws RpcException If the RPC request fails.
|
* @throws RpcException If the RPC request fails.
|
||||||
|
|
|
@ -7,8 +7,10 @@
|
||||||
use Socialbox\Classes\Configuration;
|
use Socialbox\Classes\Configuration;
|
||||||
use Socialbox\Classes\Cryptography;
|
use Socialbox\Classes\Cryptography;
|
||||||
use Socialbox\Classes\Logger;
|
use Socialbox\Classes\Logger;
|
||||||
|
use Socialbox\Classes\ServerResolver;
|
||||||
use Socialbox\Classes\Utilities;
|
use Socialbox\Classes\Utilities;
|
||||||
use Socialbox\Classes\Validator;
|
use Socialbox\Classes\Validator;
|
||||||
|
use Socialbox\Enums\ReservedUsernames;
|
||||||
use Socialbox\Enums\SessionState;
|
use Socialbox\Enums\SessionState;
|
||||||
use Socialbox\Enums\StandardError;
|
use Socialbox\Enums\StandardError;
|
||||||
use Socialbox\Enums\StandardHeaders;
|
use Socialbox\Enums\StandardHeaders;
|
||||||
|
@ -68,6 +70,58 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the headers in an initialization request to ensure that all
|
||||||
|
* required information is present and properly formatted. This includes
|
||||||
|
* checking for headers such as Client Name, Client Version, Public Key,
|
||||||
|
* and Identify-As, as well as validating the Identify-As header value.
|
||||||
|
* If any validation fails, a corresponding HTTP response code and message
|
||||||
|
* are returned.
|
||||||
|
*
|
||||||
|
* @param ClientRequest $clientRequest The client request containing headers to validate.
|
||||||
|
*
|
||||||
|
* @return bool Returns true if all required headers are valid, otherwise false.
|
||||||
|
*/
|
||||||
|
private static function validateInitHeaders(ClientRequest $clientRequest): bool
|
||||||
|
{
|
||||||
|
if(!$clientRequest->getClientName())
|
||||||
|
{
|
||||||
|
http_response_code(400);
|
||||||
|
print('Missing required header: ' . StandardHeaders::CLIENT_NAME->value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$clientRequest->getClientVersion())
|
||||||
|
{
|
||||||
|
http_response_code(400);
|
||||||
|
print('Missing required header: ' . StandardHeaders::CLIENT_VERSION->value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$clientRequest->headerExists(StandardHeaders::PUBLIC_KEY))
|
||||||
|
{
|
||||||
|
http_response_code(400);
|
||||||
|
print('Missing required header: ' . StandardHeaders::PUBLIC_KEY->value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$clientRequest->headerExists(StandardHeaders::IDENTIFY_AS))
|
||||||
|
{
|
||||||
|
http_response_code(400);
|
||||||
|
print('Missing required header: ' . StandardHeaders::IDENTIFY_AS->value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Validator::validatePeerAddress($clientRequest->getHeader(StandardHeaders::IDENTIFY_AS)))
|
||||||
|
{
|
||||||
|
http_response_code(400);
|
||||||
|
print('Invalid Identify-As header: ' . $clientRequest->getHeader(StandardHeaders::IDENTIFY_AS));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes a client request to initiate a session. Validates required headers,
|
* Processes a client request to initiate a session. Validates required headers,
|
||||||
* ensures the peer is authorized and enabled, and creates a new session UUID
|
* ensures the peer is authorized and enabled, and creates a new session UUID
|
||||||
|
@ -80,60 +134,68 @@
|
||||||
*/
|
*/
|
||||||
private static function handleInitiateSession(ClientRequest $clientRequest): void
|
private static function handleInitiateSession(ClientRequest $clientRequest): void
|
||||||
{
|
{
|
||||||
|
if(!self::validateInitHeaders($clientRequest))
|
||||||
if(!$clientRequest->getClientName())
|
|
||||||
{
|
{
|
||||||
http_response_code(400);
|
|
||||||
print('Missing required header: ' . StandardHeaders::CLIENT_NAME->value);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$clientRequest->getClientVersion())
|
// We always accept the client's public key at first
|
||||||
{
|
$publicKey = $clientRequest->getHeader(StandardHeaders::PUBLIC_KEY);
|
||||||
http_response_code(400);
|
|
||||||
print('Missing required header: ' . StandardHeaders::CLIENT_VERSION->value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!$clientRequest->headerExists(StandardHeaders::PUBLIC_KEY))
|
|
||||||
{
|
|
||||||
http_response_code(400);
|
|
||||||
print('Missing required header: ' . StandardHeaders::PUBLIC_KEY->value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!$clientRequest->headerExists(StandardHeaders::IDENTIFY_AS))
|
|
||||||
{
|
|
||||||
http_response_code(400);
|
|
||||||
print('Missing required header: ' . StandardHeaders::IDENTIFY_AS->value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!Validator::validatePeerAddress($clientRequest->getHeader(StandardHeaders::IDENTIFY_AS)))
|
|
||||||
{
|
|
||||||
http_response_code(400);
|
|
||||||
print('Invalid Identify-As header: ' . $clientRequest->getHeader(StandardHeaders::IDENTIFY_AS));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the peer is identifying as the same domain
|
// If the peer is identifying as the same domain
|
||||||
if($clientRequest->getIdentifyAs()->getDomain() === Configuration::getInstanceConfiguration()->getDomain())
|
if($clientRequest->getIdentifyAs()->getDomain() === Configuration::getInstanceConfiguration()->getDomain())
|
||||||
{
|
{
|
||||||
// Prevent the peer from identifying as the host unless it's coming from an external domain
|
// Prevent the peer from identifying as the host unless it's coming from an external domain
|
||||||
if($clientRequest->getIdentifyAs()->getUsername() === 'host')
|
if($clientRequest->getIdentifyAs()->getUsername() === ReservedUsernames::HOST->value)
|
||||||
{
|
{
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
print('Unauthorized: The requested peer is not allowed to identify as the host');
|
print('Unauthorized: The requested peer is not allowed to identify as the host');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If the peer is identifying as an external domain
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
http_response_code(400);
|
// Only allow the host to identify as an external peer
|
||||||
print('External domains are not supported yet');
|
if($clientRequest->getIdentifyAs()->getUsername() !== ReservedUsernames::HOST->value)
|
||||||
|
{
|
||||||
|
http_response_code(403);
|
||||||
|
print('Unauthorized: The requested peer is not allowed to identify as an external peer');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// We need to obtain the public key of the host, since we can't trust the client
|
||||||
|
$resolvedServer = ServerResolver::resolveDomain($clientRequest->getIdentifyAs()->getDomain());
|
||||||
|
|
||||||
|
// Override the public key with the resolved server's public key
|
||||||
|
$publicKey = $resolvedServer->getPublicKey();
|
||||||
|
}
|
||||||
|
catch (Exceptions\ResolutionException $e)
|
||||||
|
{
|
||||||
|
Logger::getLogger()->error('Failed to resolve the host domain', $e);
|
||||||
|
http_response_code(409);
|
||||||
|
print('Conflict: Failed to resolve the host domain: ' . $e->getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception $e)
|
||||||
|
{
|
||||||
|
Logger::getLogger()->error('An internal error occurred while resolving the host domain', $e);
|
||||||
|
http_response_code(500);
|
||||||
|
if(Configuration::getSecurityConfiguration()->isDisplayInternalExceptions())
|
||||||
|
{
|
||||||
|
print(Utilities::throwableToString($e));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
print('An internal error occurred');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$registeredPeer = RegisteredPeerManager::getPeerByAddress($clientRequest->getIdentifyAs());
|
$registeredPeer = RegisteredPeerManager::getPeerByAddress($clientRequest->getIdentifyAs());
|
||||||
|
@ -165,7 +227,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the session UUID
|
// Create the session UUID
|
||||||
$sessionUuid = SessionManager::createSession($clientRequest->getHeader(StandardHeaders::PUBLIC_KEY), $registeredPeer, $clientRequest->getClientName(), $clientRequest->getClientVersion());
|
$sessionUuid = SessionManager::createSession($publicKey, $registeredPeer, $clientRequest->getClientName(), $clientRequest->getClientVersion());
|
||||||
http_response_code(201); // Created
|
http_response_code(201); // Created
|
||||||
print($sessionUuid); // Return the session UUID
|
print($sessionUuid); // Return the session UUID
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue