toRfc4122(); $flags = []; // TODO: Update this to support `host` peers if($peer->isExternal()) { $flags[] = SessionFlags::AUTHENTICATION_REQUIRED; $flags[] = SessionFlags::VER_AUTHENTICATION; } else if($peer->isEnabled()) { $flags[] = SessionFlags::AUTHENTICATION_REQUIRED; if(PasswordManager::usesPassword($peer->getUuid())) { $flags[] = SessionFlags::VER_PASSWORD; } if(Configuration::getRegistrationConfiguration()->isImageCaptchaVerificationRequired()) { $flags[] = SessionFlags::VER_IMAGE_CAPTCHA; } } else { $flags[] = SessionFlags::REGISTRATION_REQUIRED; if(Configuration::getRegistrationConfiguration()->isDisplayNameRequired()) { $flags[] = SessionFlags::SET_DISPLAY_NAME; } if(Configuration::getRegistrationConfiguration()->isDisplayPictureRequired()) { $flags[] = SessionFlags::SET_DISPLAY_PICTURE; } 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; } if(Configuration::getRegistrationConfiguration()->isAcceptPrivacyPolicyRequired()) { $flags[] = SessionFlags::VER_PRIVACY_POLICY; } if(Configuration::getRegistrationConfiguration()->isAcceptTermsOfServiceRequired()) { $flags[] = SessionFlags::VER_TERMS_OF_SERVICE; } if(Configuration::getRegistrationConfiguration()->isAcceptCommunityGuidelinesRequired()) { $flags[] = SessionFlags::VER_COMMUNITY_GUIDELINES; } } if(count($flags) > 0) { $implodedFlags = SessionFlags::toString($flags); } else { $implodedFlags = null; } $peerUuid = $peer->getUuid(); try { $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); $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. * @throws StandardException */ public static function getSession(string $uuid): SessionRecord { Logger::getLogger()->verbose(sprintf("Retrieving session %s from the database", $uuid)); 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) { throw new StandardException(sprintf("The requested session '%s' does not exist", $uuid), StandardError::SESSION_NOT_FOUND); } // Convert the timestamp fields to DateTime objects $data['created'] = new DateTime($data['created']); if(isset($data['last_request']) && $data['last_request'] !== null) { $data['last_request'] = new DateTime($data['last_request']); } else { $data['last_request'] = null; } return SessionRecord::fromArray($data); } 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 * @throws DatabaseOperationException */ public static function updateLastRequest(string $uuid): void { Logger::getLogger()->verbose(sprintf("Updating last request timestamp for session %s", $uuid)); 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. * @throws DatabaseOperationException */ public static function updateState(string $uuid, SessionState $state): void { Logger::getLogger()->verbose(sprintf("Updating state of session %s to %s", $uuid, $state->value)); try { $state_value = $state->value; $statement = Database::getConnection()->prepare('UPDATE sessions SET state=? WHERE uuid=?'); $statement->bindParam(1, $state_value); $statement->bindParam(2, $uuid); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to update session state', $e); } } /** * Updates the encryption keys and session state for a specific session UUID in the database. * * @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. * @return void * @throws DatabaseOperationException If an error occurs during the database operation. */ public static function setEncryptionKeys(string $uuid, string $privateSharedSecret, string $clientEncryptionKey, string $serverEncryptionKey): void { Logger::getLogger()->verbose(sprintf('Setting the encryption key for %s', $uuid)); try { $state_value = SessionState::ACTIVE->value; $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); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to set the encryption key', $e); } } /** * Retrieves the flags associated with a specific session. * * @param string $uuid The UUID of the session to retrieve flags for. * @return SessionFlags[] An array of flags associated with the specified session. * @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); } return SessionFlags::fromString($data['flags']); } 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=?"); $statement->bindValue(1, SessionFlags::toString($flags)); $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. * @param SessionFlags[] $flags An array of flags to be removed from the session. * @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)); $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)); try { // Update the session flags in the database $statement = Database::getConnection()->prepare("UPDATE sessions SET flags=? WHERE uuid=?"); $statement->bindValue(1, $flagString); // Use the stringified updated flags $statement->bindParam(2, $uuid); $statement->execute(); } catch (PDOException $e) { throw new DatabaseOperationException('Failed to remove flags from session', $e); } } /** * 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); } } /** * Marks the session as complete if all necessary conditions are met. * * @param SessionRecord $session The session record to evaluate and potentially mark as complete. * @param array $flagsToRemove An array of flags to remove from the session if it is marked as complete. * @return void * @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. */ public static function updateFlow(SessionRecord $session, array $flagsToRemove=[]): void { // Don't do anything if the session is already authenticated if (!$session->flagExists(SessionFlags::AUTHENTICATION_REQUIRED) && !$session->flagExists(SessionFlags::REGISTRATION_REQUIRED)) { return; } // Don't do anything if the flags to remove are not present if(!$session->flagExists($flagsToRemove)) { return; } // Remove & update the session flags self::removeFlags($session->getUuid(), $flagsToRemove); $session = self::getSession($session->getUuid()); // Check if all registration/authentication requirements are met if(SessionFlags::isComplete($session->getFlags())) { 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 RegisteredPeerManager::enablePeer($session->getPeerUuid()); // Enable the peer } } }