2024-09-13 13:52:38 -04:00
< ? php
namespace Socialbox\Managers ;
use DateMalformedStringException ;
use DateTime ;
use InvalidArgumentException ;
use PDO ;
use PDOException ;
2024-12-12 04:33:10 -05:00
use Socialbox\Classes\Configuration ;
2024-09-13 13:52:38 -04:00
use Socialbox\Classes\Cryptography ;
use Socialbox\Classes\Database ;
2024-10-30 15:28:36 -04:00
use Socialbox\Classes\Logger ;
2024-12-12 04:33:10 -05:00
use Socialbox\Enums\Flags\SessionFlags ;
2024-09-13 13:52:38 -04:00
use Socialbox\Enums\SessionState ;
use Socialbox\Enums\StandardError ;
use Socialbox\Exceptions\DatabaseOperationException ;
2024-09-30 03:00:02 -04:00
use Socialbox\Exceptions\StandardException ;
2025-01-24 15:10:20 -05:00
use Socialbox\Objects\Database\PeerRecord ;
2024-10-24 13:55:21 -04:00
use Socialbox\Objects\Database\SessionRecord ;
2025-01-03 12:27:04 -05:00
use Socialbox\Objects\KeyPair ;
2024-09-13 13:52:38 -04:00
use Symfony\Component\Uid\Uuid ;
class SessionManager
{
2025-01-04 15:32:25 -05:00
/**
* Creates a new session for a given peer and client details , and stores it in the database .
*
2025-01-24 15:10:20 -05:00
* @ param PeerRecord $peer The peer record for which the session is being created .
2025-01-04 15:32:25 -05:00
* @ param string $clientName The name of the client application .
* @ param string $clientVersion The version of the client application .
* @ param string $clientPublicSigningKey The client ' s public signing key , which must be a valid Ed25519 key .
* @ param string $clientPublicEncryptionKey The client ' s public encryption key , which must be a valid X25519 key .
* @ param KeyPair $serverEncryptionKeyPair The server ' s key pair for encryption , including both public and private keys .
* @ return string The UUID of the newly created session .
* @ throws InvalidArgumentException If the provided public signing key or encryption key is invalid .
* @ throws DatabaseOperationException If there is an error during the session creation in the database .
*/
2025-01-24 15:10:20 -05:00
public static function createSession ( PeerRecord $peer , string $clientName , string $clientVersion , string $clientPublicSigningKey , string $clientPublicEncryptionKey , KeyPair $serverEncryptionKeyPair ) : string
2024-09-13 13:52:38 -04:00
{
2025-01-03 12:27:04 -05:00
if ( $clientPublicSigningKey === '' || Cryptography :: validatePublicSigningKey ( $clientPublicSigningKey ) === false )
2024-09-13 13:52:38 -04:00
{
2025-01-03 12:27:04 -05:00
throw new InvalidArgumentException ( 'The public key is not a valid Ed25519 public key' );
2024-09-13 13:52:38 -04:00
}
2025-01-03 12:27:04 -05:00
if ( $clientPublicEncryptionKey === '' || Cryptography :: validatePublicEncryptionKey ( $clientPublicEncryptionKey ) === false )
2024-09-13 13:52:38 -04:00
{
2025-01-03 12:27:04 -05:00
throw new InvalidArgumentException ( 'The public key is not a valid X25519 public key' );
2024-09-13 13:52:38 -04:00
}
$uuid = Uuid :: v4 () -> toRfc4122 ();
2024-12-12 04:33:10 -05:00
$flags = [];
2025-01-03 12:27:04 -05:00
// TODO: Update this to support `host` peers
2025-01-10 18:09:47 -05:00
if ( $peer -> isExternal ())
{
$flags [] = SessionFlags :: AUTHENTICATION_REQUIRED ;
2025-01-10 18:17:28 -05:00
$flags [] = SessionFlags :: VER_AUTHENTICATION ;
2025-01-10 18:09:47 -05:00
}
else if ( $peer -> isEnabled ())
2024-12-12 04:33:10 -05:00
{
2024-12-12 14:55:44 -05:00
$flags [] = SessionFlags :: AUTHENTICATION_REQUIRED ;
2025-01-04 15:32:25 -05:00
if ( PasswordManager :: usesPassword ( $peer -> getUuid ()))
2024-12-12 04:33:10 -05:00
{
$flags [] = SessionFlags :: VER_PASSWORD ;
}
if ( Configuration :: getRegistrationConfiguration () -> isImageCaptchaVerificationRequired ())
{
$flags [] = SessionFlags :: VER_IMAGE_CAPTCHA ;
}
}
else
{
2024-12-12 14:55:44 -05:00
$flags [] = SessionFlags :: REGISTRATION_REQUIRED ;
2024-12-12 04:33:10 -05:00
if ( Configuration :: getRegistrationConfiguration () -> isDisplayNameRequired ())
{
$flags [] = SessionFlags :: SET_DISPLAY_NAME ;
}
2024-12-23 19:25:21 -05:00
if ( Configuration :: getRegistrationConfiguration () -> isDisplayPictureRequired ())
2024-12-12 04:33:10 -05:00
{
2024-12-23 19:25:21 -05:00
$flags [] = SessionFlags :: SET_DISPLAY_PICTURE ;
2024-12-12 04:33:10 -05:00
}
if ( Configuration :: getRegistrationConfiguration () -> isImageCaptchaVerificationRequired ())
{
$flags [] = SessionFlags :: VER_IMAGE_CAPTCHA ;
}
if ( Configuration :: getRegistrationConfiguration () -> isPasswordRequired ())
{
$flags [] = SessionFlags :: SET_PASSWORD ;
}
if ( Configuration :: getRegistrationConfiguration () -> isOtpRequired ())
{
$flags [] = SessionFlags :: SET_OTP ;
}
2024-12-12 14:55:44 -05:00
if ( Configuration :: getRegistrationConfiguration () -> isAcceptPrivacyPolicyRequired ())
{
$flags [] = SessionFlags :: VER_PRIVACY_POLICY ;
}
if ( Configuration :: getRegistrationConfiguration () -> isAcceptTermsOfServiceRequired ())
{
$flags [] = SessionFlags :: VER_TERMS_OF_SERVICE ;
}
2024-12-23 19:25:21 -05:00
if ( Configuration :: getRegistrationConfiguration () -> isAcceptCommunityGuidelinesRequired ())
{
$flags [] = SessionFlags :: VER_COMMUNITY_GUIDELINES ;
}
2024-12-12 04:33:10 -05:00
}
if ( count ( $flags ) > 0 )
{
$implodedFlags = SessionFlags :: toString ( $flags );
}
else
{
$implodedFlags = null ;
}
$peerUuid = $peer -> getUuid ();
2024-09-13 13:52:38 -04:00
try
{
2025-01-03 12:27:04 -05:00
$statement = Database :: getConnection () -> prepare ( " INSERT INTO sessions (uuid, peer_uuid, client_name, client_version, client_public_signing_key, client_public_encryption_key, server_public_encryption_key, server_private_encryption_key, flags) VALUES (:uuid, :peer_uuid, :client_name, :client_version, :client_public_signing_key, :client_public_encryption_key, :server_public_encryption_key, :server_private_encryption_key, :flags) " );
$statement -> bindParam ( ':uuid' , $uuid );
$statement -> bindParam ( ':peer_uuid' , $peerUuid );
$statement -> bindParam ( ':client_name' , $clientName );
$statement -> bindParam ( ':client_version' , $clientVersion );
$statement -> bindParam ( ':client_public_signing_key' , $clientPublicSigningKey );
$statement -> bindParam ( ':client_public_encryption_key' , $clientPublicEncryptionKey );
$serverPublicEncryptionKey = $serverEncryptionKeyPair -> getPublicKey ();
$statement -> bindParam ( ':server_public_encryption_key' , $serverPublicEncryptionKey );
$serverPrivateEncryptionKey = $serverEncryptionKeyPair -> getPrivateKey ();
$statement -> bindParam ( ':server_private_encryption_key' , $serverPrivateEncryptionKey );
$statement -> bindParam ( ':flags' , $implodedFlags );
2024-09-13 13:52:38 -04:00
$statement -> execute ();
}
catch ( PDOException $e )
{
throw new DatabaseOperationException ( 'Failed to create a session on the database' , $e );
}
return $uuid ;
}
/**
* Checks if a session with the given UUID exists in the database .
*
* @ param string $uuid The UUID of the session to check .
* @ return bool True if the session exists , false otherwise .
* @ throws DatabaseOperationException If there is an error executing the database query .
*/
public static function sessionExists ( string $uuid ) : bool
{
try
{
$statement = Database :: getConnection () -> prepare ( " SELECT COUNT(*) FROM sessions WHERE uuid=? " );
$statement -> bindParam ( 1 , $uuid );
$statement -> execute ();
$result = $statement -> fetchColumn ();
return $result > 0 ;
}
catch ( PDOException $e )
{
throw new DatabaseOperationException ( 'Failed to check if the session exists' , $e );
}
}
/**
* Retrieves a session record by its unique identifier .
*
* @ param string $uuid The unique identifier of the session .
* @ return SessionRecord The session record corresponding to the given UUID .
* @ throws DatabaseOperationException If the session record cannot be found or if there is an error during retrieval .
2024-09-30 03:00:02 -04:00
* @ throws StandardException
2024-09-13 13:52:38 -04:00
*/
public static function getSession ( string $uuid ) : SessionRecord
{
2024-10-30 15:28:36 -04:00
Logger :: getLogger () -> verbose ( sprintf ( " Retrieving session %s from the database " , $uuid ));
2024-09-13 13:52:38 -04:00
try
{
$statement = Database :: getConnection () -> prepare ( " SELECT * FROM sessions WHERE uuid=? " );
$statement -> bindParam ( 1 , $uuid );
$statement -> execute ();
$data = $statement -> fetch ( PDO :: FETCH_ASSOC );
if ( $data === false )
{
2024-12-10 12:54:02 -05:00
throw new StandardException ( sprintf ( " The requested session '%s' does not exist " , $uuid ), StandardError :: SESSION_NOT_FOUND );
2024-09-13 13:52:38 -04:00
}
// Convert the timestamp fields to DateTime objects
$data [ 'created' ] = new DateTime ( $data [ 'created' ]);
2024-10-30 15:28:36 -04:00
if ( isset ( $data [ 'last_request' ]) && $data [ 'last_request' ] !== null )
{
$data [ 'last_request' ] = new DateTime ( $data [ 'last_request' ]);
}
else
{
$data [ 'last_request' ] = null ;
}
2024-09-13 13:52:38 -04:00
2024-10-25 13:39:33 -04:00
return SessionRecord :: fromArray ( $data );
2024-10-24 15:15:14 -04:00
2024-09-13 13:52:38 -04:00
}
catch ( PDOException | DateMalformedStringException $e )
{
throw new DatabaseOperationException ( sprintf ( 'Failed to retrieve session record %s' , $uuid ), $e );
}
}
/**
* Updates the last request timestamp for a given session by its UUID .
*
* @ param string $uuid The UUID of the session to be updated .
* @ return void
2024-10-24 15:15:14 -04:00
* @ throws DatabaseOperationException
2024-09-13 13:52:38 -04:00
*/
public static function updateLastRequest ( string $uuid ) : void
{
2024-10-30 15:28:36 -04:00
Logger :: getLogger () -> verbose ( sprintf ( " Updating last request timestamp for session %s " , $uuid ));
2024-09-13 13:52:38 -04:00
try
{
$formattedTime = ( new DateTime ( '@' . time ())) -> format ( 'Y-m-d H:i:s' );
$statement = Database :: getConnection () -> prepare ( " UPDATE sessions SET last_request=? WHERE uuid=? " );
$statement -> bindValue ( 1 , $formattedTime , PDO :: PARAM_STR );
$statement -> bindParam ( 2 , $uuid );
$statement -> execute ();
}
catch ( PDOException | DateMalformedStringException $e )
{
throw new DatabaseOperationException ( 'Failed to update last request' , $e );
}
}
/**
* Updates the state of a session given its UUID .
*
* @ param string $uuid The unique identifier of the session to update .
* @ param SessionState $state The new state to be set for the session .
* @ return void No return value .
2024-10-24 15:15:14 -04:00
* @ throws DatabaseOperationException
2024-09-13 13:52:38 -04:00
*/
public static function updateState ( string $uuid , SessionState $state ) : void
{
2024-10-30 15:28:36 -04:00
Logger :: getLogger () -> verbose ( sprintf ( " Updating state of session %s to %s " , $uuid , $state -> value ));
2024-09-13 13:52:38 -04:00
try
{
$state_value = $state -> value ;
$statement = Database :: getConnection () -> prepare ( 'UPDATE sessions SET state=? WHERE uuid=?' );
$statement -> bindParam ( 1 , $state_value );
$statement -> bindParam ( 2 , $uuid );
2024-12-12 04:33:10 -05:00
$statement -> execute ();
2024-09-13 13:52:38 -04:00
}
catch ( PDOException $e )
{
throw new DatabaseOperationException ( 'Failed to update session state' , $e );
}
}
2024-12-10 13:30:08 -05:00
2024-12-12 04:33:10 -05:00
/**
2025-01-03 12:27:04 -05:00
* Updates the encryption keys and session state for a specific session UUID in the database .
2024-12-12 04:33:10 -05:00
*
2025-01-03 12:27:04 -05:00
* @ param string $uuid The unique identifier for the session to update .
* @ param string $privateSharedSecret The private shared secret to secure communication .
* @ param string $clientEncryptionKey The client ' s encryption key used for transport security .
* @ param string $serverEncryptionKey The server ' s encryption key used for transport security .
2024-12-12 04:33:10 -05:00
* @ return void
2025-01-03 12:27:04 -05:00
* @ throws DatabaseOperationException If an error occurs during the database operation .
2024-12-12 04:33:10 -05:00
*/
2025-01-03 12:27:04 -05:00
public static function setEncryptionKeys ( string $uuid , string $privateSharedSecret , string $clientEncryptionKey , string $serverEncryptionKey ) : void
2024-12-12 04:33:10 -05:00
{
Logger :: getLogger () -> verbose ( sprintf ( 'Setting the encryption key for %s' , $uuid ));
try
{
$state_value = SessionState :: ACTIVE -> value ;
2025-01-03 12:27:04 -05:00
$statement = Database :: getConnection () -> prepare ( 'UPDATE sessions SET state=:state, private_shared_secret=:private_shared_secret, client_transport_encryption_key=:client_transport_encryption_key, server_transport_encryption_key=:server_transport_encryption_key WHERE uuid=:uuid' );
$statement -> bindParam ( ':state' , $state_value );
$statement -> bindParam ( ':private_shared_secret' , $privateSharedSecret );
$statement -> bindParam ( ':client_transport_encryption_key' , $clientEncryptionKey );
$statement -> bindParam ( ':server_transport_encryption_key' , $serverEncryptionKey );
$statement -> bindParam ( ':uuid' , $uuid );
2024-12-12 04:33:10 -05:00
$statement -> execute ();
}
catch ( PDOException $e )
{
throw new DatabaseOperationException ( 'Failed to set the encryption key' , $e );
}
}
2024-12-10 13:30:08 -05:00
/**
* Retrieves the flags associated with a specific session .
*
* @ param string $uuid The UUID of the session to retrieve flags for .
2025-01-14 14:53:47 -05:00
* @ return SessionFlags [] An array of flags associated with the specified session .
2024-12-10 13:30:08 -05:00
* @ throws StandardException If the specified session does not exist .
* @ throws DatabaseOperationException If there
*/
private static function getFlags ( string $uuid ) : array
{
Logger :: getLogger () -> verbose ( sprintf ( " Retrieving flags for session %s " , $uuid ));
try
{
$statement = Database :: getConnection () -> prepare ( " SELECT flags FROM sessions WHERE uuid=? " );
$statement -> bindParam ( 1 , $uuid );
$statement -> execute ();
$data = $statement -> fetch ( PDO :: FETCH_ASSOC );
if ( $data === false )
{
throw new StandardException ( sprintf ( " The requested session '%s' does not exist " , $uuid ), StandardError :: SESSION_NOT_FOUND );
}
2024-12-14 00:43:19 -05:00
return SessionFlags :: fromString ( $data [ 'flags' ]);
2024-12-10 13:30:08 -05:00
}
catch ( PDOException $e )
{
throw new DatabaseOperationException ( sprintf ( 'Failed to retrieve flags for session %s' , $uuid ), $e );
}
}
/**
* Adds the specified flags to the session identified by the given UUID .
*
* @ param string $uuid The unique identifier of the session to which the flags will be added .
* @ param array $flags The flags to add to the session .
* @ return void
* @ throws DatabaseOperationException | StandardException If there is an error while updating the session in the database .
*/
public static function addFlags ( string $uuid , array $flags ) : void
{
Logger :: getLogger () -> verbose ( sprintf ( " Adding flags to session %s " , $uuid ));
// First get the existing flags
$existingFlags = self :: getFlags ( $uuid );
// Merge the new flags with the existing ones
$flags = array_unique ( array_merge ( $existingFlags , $flags ));
try
{
$statement = Database :: getConnection () -> prepare ( " UPDATE sessions SET flags=? WHERE uuid=? " );
2025-01-14 14:53:47 -05:00
$statement -> bindValue ( 1 , SessionFlags :: toString ( $flags ));
2024-12-10 13:30:08 -05:00
$statement -> bindParam ( 2 , $uuid );
$statement -> execute ();
}
catch ( PDOException $e )
{
throw new DatabaseOperationException ( 'Failed to add flags to session' , $e );
}
}
/**
* Removes specified flags from the session associated with the given UUID .
*
* @ param string $uuid The UUID of the session from which the flags will be removed .
2024-12-14 00:43:19 -05:00
* @ param SessionFlags [] $flags An array of flags to be removed from the session .
2024-12-10 13:30:08 -05:00
* @ return void
* @ throws DatabaseOperationException | StandardException If there is an error while updating the session in the database .
*/
public static function removeFlags ( string $uuid , array $flags ) : void
{
Logger :: getLogger () -> verbose ( sprintf ( " Removing flags from session %s " , $uuid ));
2025-01-14 14:53:47 -05:00
$existingFlags = array_map ( fn ( SessionFlags $flag ) => $flag -> value , self :: getFlags ( $uuid ));
$flagsToRemove = array_map ( fn ( SessionFlags $flag ) => $flag -> value , $flags );
$updatedFlags = array_diff ( $existingFlags , $flagsToRemove );
$flagString = SessionFlags :: toString ( array_map ( fn ( string $value ) => SessionFlags :: from ( $value ), $updatedFlags ));
2024-12-10 13:30:08 -05:00
try
{
2025-01-14 14:53:47 -05:00
// Update the session flags in the database
2024-12-10 13:30:08 -05:00
$statement = Database :: getConnection () -> prepare ( " UPDATE sessions SET flags=? WHERE uuid=? " );
2025-01-14 14:53:47 -05:00
$statement -> bindValue ( 1 , $flagString ); // Use the stringified updated flags
2024-12-10 13:30:08 -05:00
$statement -> bindParam ( 2 , $uuid );
$statement -> execute ();
}
catch ( PDOException $e )
{
throw new DatabaseOperationException ( 'Failed to remove flags from session' , $e );
}
}
2024-12-14 00:43:19 -05:00
/**
* Updates the authentication status for the specified session .
*
* @ param string $uuid The unique identifier of the session to be updated .
* @ param bool $authenticated The authentication status to set for the session .
* @ return void
* @ throws DatabaseOperationException If the database operation fails .
*/
public static function setAuthenticated ( string $uuid , bool $authenticated ) : void
{
Logger :: getLogger () -> verbose ( sprintf ( " Setting session %s as authenticated: %s " , $uuid , $authenticated ? 'true' : 'false' ));
try
{
$statement = Database :: getConnection () -> prepare ( " UPDATE sessions SET authenticated=? WHERE uuid=? " );
$statement -> bindParam ( 1 , $authenticated );
$statement -> bindParam ( 2 , $uuid );
$statement -> execute ();
}
catch ( PDOException $e )
{
throw new DatabaseOperationException ( 'Failed to update authenticated peer' , $e );
}
}
2024-12-23 19:02:37 -05:00
/**
* Marks the session as complete if all necessary conditions are met .
*
* @ param SessionRecord $session The session record to evaluate and potentially mark as complete .
2025-01-05 01:23:43 -05:00
* @ param array $flagsToRemove An array of flags to remove from the session if it is marked as complete .
* @ return void
2024-12-23 19:02:37 -05:00
* @ throws DatabaseOperationException If there is an error while updating the session in the database .
* @ throws StandardException If the session record cannot be found or if there is an error during retrieval .
*/
2025-01-05 01:23:43 -05:00
public static function updateFlow ( SessionRecord $session , array $flagsToRemove = []) : void
2024-12-23 19:02:37 -05:00
{
2024-12-24 00:51:13 -05:00
// Don't do anything if the session is already authenticated
2025-01-14 14:53:47 -05:00
if ( ! $session -> flagExists ( SessionFlags :: AUTHENTICATION_REQUIRED ) && ! $session -> flagExists ( SessionFlags :: REGISTRATION_REQUIRED ))
2024-12-24 00:51:13 -05:00
{
return ;
}
2025-01-06 04:44:12 -05:00
// Don't do anything if the flags to remove are not present
if ( ! $session -> flagExists ( $flagsToRemove ))
{
return ;
}
// Remove & update the session flags
2025-01-05 01:23:43 -05:00
self :: removeFlags ( $session -> getUuid (), $flagsToRemove );
$session = self :: getSession ( $session -> getUuid ());
2024-12-24 00:51:13 -05:00
// Check if all registration/authentication requirements are met
2024-12-23 19:02:37 -05:00
if ( SessionFlags :: isComplete ( $session -> getFlags ()))
{
2025-01-06 04:44:12 -05:00
SessionManager :: removeFlags ( $session -> getUuid (), [ SessionFlags :: REGISTRATION_REQUIRED , SessionFlags :: AUTHENTICATION_REQUIRED ]); // Remove the registration/authentication flags
SessionManager :: setAuthenticated ( $session -> getUuid (), true ); // Mark the session as authenticated
2025-01-14 14:53:47 -05:00
RegisteredPeerManager :: enablePeer ( $session -> getPeerUuid ()); // Enable the peer
2024-12-23 19:02:37 -05:00
}
}
2024-09-13 13:52:38 -04:00
}