Compare commits
31 commits
Author | SHA1 | Date | |
---|---|---|---|
5bdf330d83 | |||
2408ee9350 | |||
3301e01065 | |||
ba9cbf6a60 | |||
f9fd442891 | |||
c93568b8f1 | |||
2e1a7a612c | |||
34e1f27878 | |||
1be08be54d | |||
8f3be08f57 | |||
42d331408c | |||
a2f4b2b685 | |||
e6a869c216 | |||
8c5237ff7f | |||
1728d607d7 | |||
0a8f7589b9 | |||
18fcee5863 | |||
80f6650eaf | |||
143e50c1ed | |||
dc15ce5bb9 | |||
8e2e1eaba1 | |||
a322c54a69 | |||
f329cd34c4 | |||
e726ad42e8 | |||
6fa23b98d3 | |||
40426c293e | |||
6bbf9c3dab | |||
d346c4d23d | |||
26f0f31cc6 | |||
6e599b2c0c | |||
![]() |
93a0b9be02 |
122 changed files with 14801 additions and 10 deletions
3
.gitignore
vendored
Normal file → Executable file
3
.gitignore
vendored
Normal file → Executable file
|
@ -1 +1,2 @@
|
|||
build/
|
||||
build/
|
||||
/scratch/
|
||||
|
|
0
.idea/.gitignore
generated
vendored
Normal file → Executable file
0
.idea/.gitignore
generated
vendored
Normal file → Executable file
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
|
@ -0,0 +1 @@
|
|||
FederationLib
|
|
@ -3,7 +3,9 @@
|
|||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/scratch" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
21
.idea/dataSources.xml
generated
Normal file
21
.idea/dataSources.xml
generated
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="MariaDB" uuid="b231469c-f074-47d0-8f1e-58831eba6163">
|
||||
<driver-ref>mariadb</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<remarks>A local test database</remarks>
|
||||
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mariadb://localhost:3306/federation</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="Redis" uuid="5b4aac6d-0507-4697-8f39-5e7d34072817">
|
||||
<driver-ref>redis</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<remarks>A local test Redis Server</remarks>
|
||||
<jdbc-driver>jdbc.RedisDriver</jdbc-driver>
|
||||
<jdbc-url>jdbc:redis://127.0.0.1:6379/0</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
131
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file → Executable file
131
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file → Executable file
|
@ -1,6 +1,78 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<Languages>
|
||||
<language minSize="111" name="PHP" />
|
||||
</Languages>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ForgottenDebugOutputInspection" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="configuration">
|
||||
<list>
|
||||
<option value="\Codeception\Util\Debug::debug" />
|
||||
<option value="\Codeception\Util\Debug::pause" />
|
||||
<option value="\Doctrine\Common\Util\Debug::dump" />
|
||||
<option value="\Doctrine\Common\Util\Debug::export" />
|
||||
<option value="\Illuminate\Support\Debug\Dumper::dump" />
|
||||
<option value="\Symfony\Component\Debug\Debug::enable" />
|
||||
<option value="\Symfony\Component\Debug\DebugClassLoader::enable" />
|
||||
<option value="\Symfony\Component\Debug\ErrorHandler::register" />
|
||||
<option value="\Symfony\Component\Debug\ExceptionHandler::register" />
|
||||
<option value="\TYPO3\CMS\Core\Utility\DebugUtility::debug" />
|
||||
<option value="\Zend\Debug\Debug::dump" />
|
||||
<option value="\Zend\Di\Display\Console::export" />
|
||||
<option value="dd" />
|
||||
<option value="debug_print_backtrace" />
|
||||
<option value="debug_zval_dump" />
|
||||
<option value="dpm" />
|
||||
<option value="dpq" />
|
||||
<option value="dsm" />
|
||||
<option value="dump" />
|
||||
<option value="dvm" />
|
||||
<option value="error_log" />
|
||||
<option value="kpr" />
|
||||
<option value="phpinfo" />
|
||||
<option value="print_r" />
|
||||
<option value="var_dump" />
|
||||
<option value="var_export" />
|
||||
<option value="wp_die" />
|
||||
<option value="xdebug_break" />
|
||||
<option value="xdebug_call_class" />
|
||||
<option value="xdebug_call_file" />
|
||||
<option value="xdebug_call_function" />
|
||||
<option value="xdebug_call_line" />
|
||||
<option value="xdebug_code_coverage_started" />
|
||||
<option value="xdebug_debug_zval" />
|
||||
<option value="xdebug_debug_zval_stdout" />
|
||||
<option value="xdebug_dump_superglobals" />
|
||||
<option value="xdebug_enable" />
|
||||
<option value="xdebug_get_code_coverage" />
|
||||
<option value="xdebug_get_collected_errors" />
|
||||
<option value="xdebug_get_declared_vars" />
|
||||
<option value="xdebug_get_function_stack" />
|
||||
<option value="xdebug_get_headers" />
|
||||
<option value="xdebug_get_monitored_functions" />
|
||||
<option value="xdebug_get_profiler_filename" />
|
||||
<option value="xdebug_get_stack_depth" />
|
||||
<option value="xdebug_get_tracefile_name" />
|
||||
<option value="xdebug_is_enabled" />
|
||||
<option value="xdebug_memory_usage" />
|
||||
<option value="xdebug_peak_memory_usage" />
|
||||
<option value="xdebug_print_function_stack" />
|
||||
<option value="xdebug_start_code_coverage" />
|
||||
<option value="xdebug_start_error_collection" />
|
||||
<option value="xdebug_start_function_monitor" />
|
||||
<option value="xdebug_start_trace" />
|
||||
<option value="xdebug_stop_code_coverage" />
|
||||
<option value="xdebug_stop_error_collection" />
|
||||
<option value="xdebug_stop_function_monitor" />
|
||||
<option value="xdebug_stop_trace" />
|
||||
<option value="xdebug_time_index" />
|
||||
<option value="xdebug_var_dump" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="migratedIntoUserSpace" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IncorrectHttpHeaderInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="customHeaders">
|
||||
<set>
|
||||
|
@ -19,8 +91,67 @@
|
|||
<option value="X-Args-3" />
|
||||
<option value="X-Args-4" />
|
||||
<option value="X-Args-5" />
|
||||
<option value="X-Temperature" />
|
||||
<option value="X-Model" />
|
||||
<option value="X-OPENAI-API-KEY" />
|
||||
</set>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JsonStandardCompliance" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="optionConfiguration">
|
||||
<list>
|
||||
<option value="barryvdh/laravel-debugbar" />
|
||||
<option value="behat/behat" />
|
||||
<option value="brianium/paratest" />
|
||||
<option value="codeception/codeception" />
|
||||
<option value="codedungeon/phpunit-result-printer" />
|
||||
<option value="composer/composer" />
|
||||
<option value="doctrine/coding-standard" />
|
||||
<option value="filp/whoops" />
|
||||
<option value="friendsofphp/php-cs-fixer" />
|
||||
<option value="humbug/humbug" />
|
||||
<option value="infection/infection" />
|
||||
<option value="jakub-onderka/php-parallel-lint" />
|
||||
<option value="johnkary/phpunit-speedtrap" />
|
||||
<option value="kalessil/production-dependencies-guard" />
|
||||
<option value="mikey179/vfsStream" />
|
||||
<option value="mockery/mockery" />
|
||||
<option value="mybuilder/phpunit-accelerator" />
|
||||
<option value="orchestra/testbench" />
|
||||
<option value="pdepend/pdepend" />
|
||||
<option value="phan/phan" />
|
||||
<option value="phing/phing" />
|
||||
<option value="phpcompatibility/php-compatibility" />
|
||||
<option value="phpmd/phpmd" />
|
||||
<option value="phpro/grumphp" />
|
||||
<option value="phpspec/phpspec" />
|
||||
<option value="phpspec/prophecy" />
|
||||
<option value="phpstan/phpstan" />
|
||||
<option value="phpunit/phpunit" />
|
||||
<option value="povils/phpmnd" />
|
||||
<option value="roave/security-advisories" />
|
||||
<option value="satooshi/php-coveralls" />
|
||||
<option value="sebastian/phpcpd" />
|
||||
<option value="slevomat/coding-standard" />
|
||||
<option value="spatie/phpunit-watcher" />
|
||||
<option value="squizlabs/php_codesniffer" />
|
||||
<option value="sstalle/php7cc" />
|
||||
<option value="symfony/debug" />
|
||||
<option value="symfony/maker-bundle" />
|
||||
<option value="symfony/phpunit-bridge" />
|
||||
<option value="symfony/var-dumper" />
|
||||
<option value="vimeo/psalm" />
|
||||
<option value="wimg/php-compatibility" />
|
||||
<option value="wp-coding-standards/wpcs" />
|
||||
<option value="yiisoft/yii2-coding-standards" />
|
||||
<option value="yiisoft/yii2-debug" />
|
||||
<option value="yiisoft/yii2-gii" />
|
||||
<option value="zendframework/zend-coding-standard" />
|
||||
<option value="zendframework/zend-debug" />
|
||||
<option value="zendframework/zend-test" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
2
.idea/modules.xml
generated
2
.idea/modules.xml
generated
|
@ -2,7 +2,7 @@
|
|||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/federationlib.iml" filepath="$PROJECT_DIR$/.idea/federationlib.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/FederationLib.iml" filepath="$PROJECT_DIR$/.idea/FederationLib.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
113
.idea/php.xml
generated
113
.idea/php.xml
generated
|
@ -9,6 +9,119 @@
|
|||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpIncludePathManager">
|
||||
<include_path>
|
||||
<path value="/usr/share/php" />
|
||||
<path value="/etc/ncc" />
|
||||
<path value="/var/ncc/packages/com.doctrine.dbal=3.6.2" />
|
||||
<path value="/var/ncc/packages/com.symfony.uid=6.2.7" />
|
||||
<path value="/var/ncc/packages/net.nosial.configlib=1.0.0" />
|
||||
<path value="/var/ncc/packages/net.nosial.loglib=1.0.1" />
|
||||
<path value="/var/ncc/packages/net.nosial.optslib=1.0.0" />
|
||||
<path value="/var/ncc/packages/com.doctrine.cache=2.2.0" />
|
||||
<path value="/var/ncc/packages/com.doctrine.deprecations=1.0.0" />
|
||||
<path value="/var/ncc/packages/com.doctrine.event_manager=2.0.0" />
|
||||
<path value="/var/ncc/packages/com.php_school.cli_menu=4.3.0" />
|
||||
<path value="/var/ncc/packages/com.php_school.terminal=0.2.1" />
|
||||
<path value="/var/ncc/packages/com.malios.php_to_ascii_table=3.0.0" />
|
||||
<path value="/var/ncc/packages/net.nosial.tamerlib=2.0.0" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="8.1" />
|
||||
<component name="PhpRuntimeConfiguration">
|
||||
<extensions>
|
||||
<extension name="Ev" enabled="false" />
|
||||
<extension name="FFI" enabled="true" />
|
||||
<extension name="SQLite" enabled="false" />
|
||||
<extension name="SimpleXML" enabled="false" />
|
||||
<extension name="SplType" enabled="false" />
|
||||
<extension name="ZendDebugger" enabled="false" />
|
||||
<extension name="ZendUtils" enabled="false" />
|
||||
<extension name="amqp" enabled="false" />
|
||||
<extension name="apache" enabled="false" />
|
||||
<extension name="apcu" enabled="false" />
|
||||
<extension name="bcmath" enabled="false" />
|
||||
<extension name="bz2" enabled="false" />
|
||||
<extension name="cassandra" enabled="false" />
|
||||
<extension name="couchbase" enabled="false" />
|
||||
<extension name="cubrid" enabled="false" />
|
||||
<extension name="dba" enabled="false" />
|
||||
<extension name="decimal" enabled="false" />
|
||||
<extension name="dio" enabled="false" />
|
||||
<extension name="dom" enabled="false" />
|
||||
<extension name="elastic_apm" enabled="false" />
|
||||
<extension name="enchant" enabled="false" />
|
||||
<extension name="fann" enabled="false" />
|
||||
<extension name="ffmpeg" enabled="false" />
|
||||
<extension name="geoip" enabled="false" />
|
||||
<extension name="geos" enabled="false" />
|
||||
<extension name="gmagick" enabled="false" />
|
||||
<extension name="gmp" enabled="false" />
|
||||
<extension name="gnupg" enabled="false" />
|
||||
<extension name="grpc" enabled="false" />
|
||||
<extension name="http" enabled="false" />
|
||||
<extension name="ibm_db2" enabled="false" />
|
||||
<extension name="imagick" enabled="false" />
|
||||
<extension name="imap" enabled="false" />
|
||||
<extension name="inotify" enabled="false" />
|
||||
<extension name="interbase" enabled="false" />
|
||||
<extension name="intl" enabled="false" />
|
||||
<extension name="judy" enabled="false" />
|
||||
<extension name="ldap" enabled="false" />
|
||||
<extension name="libevent" enabled="false" />
|
||||
<extension name="libsodium" enabled="false" />
|
||||
<extension name="mailparse" enabled="false" />
|
||||
<extension name="mbstring" enabled="false" />
|
||||
<extension name="mcrypt" enabled="false" />
|
||||
<extension name="memcache" enabled="false" />
|
||||
<extension name="ming" enabled="false" />
|
||||
<extension name="mongo" enabled="false" />
|
||||
<extension name="mosquitto-php" enabled="false" />
|
||||
<extension name="mqseries" enabled="false" />
|
||||
<extension name="mssql" enabled="false" />
|
||||
<extension name="mysql" enabled="false" />
|
||||
<extension name="mysql_xdevapi" enabled="false" />
|
||||
<extension name="ncurses" enabled="false" />
|
||||
<extension name="newrelic" enabled="false" />
|
||||
<extension name="oauth" enabled="false" />
|
||||
<extension name="oci8" enabled="false" />
|
||||
<extension name="odbc" enabled="false" />
|
||||
<extension name="pdflib" enabled="false" />
|
||||
<extension name="pdo_ibm" enabled="false" />
|
||||
<extension name="pdo_sqlite" enabled="false" />
|
||||
<extension name="pspell" enabled="false" />
|
||||
<extension name="pthreads" enabled="false" />
|
||||
<extension name="rar" enabled="false" />
|
||||
<extension name="recode" enabled="false" />
|
||||
<extension name="relay" enabled="false" />
|
||||
<extension name="rrd" enabled="false" />
|
||||
<extension name="snappy" enabled="false" />
|
||||
<extension name="snmp" enabled="false" />
|
||||
<extension name="soap" enabled="false" />
|
||||
<extension name="sqlite3" enabled="false" />
|
||||
<extension name="sqlsrv" enabled="false" />
|
||||
<extension name="ssh2" enabled="false" />
|
||||
<extension name="suhosin" enabled="false" />
|
||||
<extension name="svn" enabled="false" />
|
||||
<extension name="sybase" enabled="false" />
|
||||
<extension name="tidy" enabled="false" />
|
||||
<extension name="v8js" enabled="false" />
|
||||
<extension name="wddx" enabled="false" />
|
||||
<extension name="win32service" enabled="false" />
|
||||
<extension name="wincache" enabled="false" />
|
||||
<extension name="xdebug" enabled="false" />
|
||||
<extension name="xhprof" enabled="false" />
|
||||
<extension name="xlswriter" enabled="false" />
|
||||
<extension name="xml" enabled="false" />
|
||||
<extension name="xmlreader" enabled="false" />
|
||||
<extension name="xmlrpc" enabled="false" />
|
||||
<extension name="xmlwriter" enabled="false" />
|
||||
<extension name="xsl" enabled="false" />
|
||||
<extension name="yaml" enabled="false" />
|
||||
<extension name="zend" enabled="false" />
|
||||
<extension name="zmq" enabled="false" />
|
||||
</extensions>
|
||||
</component>
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
|
|
0
.idea/runConfigurations/Build.xml
generated
Normal file → Executable file
0
.idea/runConfigurations/Build.xml
generated
Normal file → Executable file
0
.idea/runConfigurations/Clean.xml
generated
Normal file → Executable file
0
.idea/runConfigurations/Clean.xml
generated
Normal file → Executable file
0
.idea/runConfigurations/Install.xml
generated
Normal file → Executable file
0
.idea/runConfigurations/Install.xml
generated
Normal file → Executable file
7
.idea/runConfigurations/base32_test_php.xml
generated
Normal file
7
.idea/runConfigurations/base32_test_php.xml
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="base32_test.php" type="PhpLocalRunConfigurationType" factoryName="PHP Console" folderName="Tests" path="$PROJECT_DIR$/tests/base32_test.php" scriptParameters="--log-level debug">
|
||||
<method v="2">
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Install" run_configuration_type="MAKEFILE_TARGET_RUN_CONFIGURATION" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
7
.idea/runConfigurations/cache_update_test_php.xml
generated
Normal file
7
.idea/runConfigurations/cache_update_test_php.xml
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="cache_update_test.php" type="PhpLocalRunConfigurationType" factoryName="PHP Console" folderName="Client Tests" path="$PROJECT_DIR$/tests/client_manager/cache_update_test.php" scriptParameters="--log-level debug">
|
||||
<method v="2">
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Install" run_configuration_type="MAKEFILE_TARGET_RUN_CONFIGURATION" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
7
.idea/runConfigurations/create_clients_php.xml
generated
Normal file
7
.idea/runConfigurations/create_clients_php.xml
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="create_clients.php" type="PhpLocalRunConfigurationType" factoryName="PHP Console" folderName="Client Tests" path="$PROJECT_DIR$/tests/client_manager/create_client.php" scriptParameters="--log-level debug">
|
||||
<method v="2">
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Install" run_configuration_type="MAKEFILE_TARGET_RUN_CONFIGURATION" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
7
.idea/runConfigurations/create_secured_client_php.xml
generated
Normal file
7
.idea/runConfigurations/create_secured_client_php.xml
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="create_secured_client.php" type="PhpLocalRunConfigurationType" factoryName="PHP Console" folderName="Client Tests" path="$PROJECT_DIR$/tests/client_manager/create_secured_client.php" scriptParameters="--log-level debug">
|
||||
<method v="2">
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Install" run_configuration_type="MAKEFILE_TARGET_RUN_CONFIGURATION" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
7
.idea/runConfigurations/list_clients_php.xml
generated
Normal file
7
.idea/runConfigurations/list_clients_php.xml
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="list_clients.php" type="PhpLocalRunConfigurationType" factoryName="PHP Console" folderName="Client Tests" path="$PROJECT_DIR$/tests/client_manager/list_clients.php" scriptParameters="--log-level debug">
|
||||
<method v="2">
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Install" run_configuration_type="MAKEFILE_TARGET_RUN_CONFIGURATION" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
7
.idea/runConfigurations/totp_test_php.xml
generated
Normal file
7
.idea/runConfigurations/totp_test_php.xml
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="totp_test.php" type="PhpLocalRunConfigurationType" factoryName="PHP Console" folderName="Tests" path="$PROJECT_DIR$/tests/totp_test.php" scriptParameters="--log-level debug">
|
||||
<method v="2">
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Install" run_configuration_type="MAKEFILE_TARGET_RUN_CONFIGURATION" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
13
.idea/sqldialects.xml
generated
Normal file
13
.idea/sqldialects.xml
generated
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$/database/anomaly_tracking.sql" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/database/clients.sql" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/database/content.sql" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/database/events.sql" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/database/peers.sql" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/database/peers_associations.sql" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/database/peers_telegram_user.sql" dialect="MariaDB" />
|
||||
<file url="PROJECT" dialect="MariaDB" />
|
||||
</component>
|
||||
</project>
|
0
.idea/vcs.xml
generated
Normal file → Executable file
0
.idea/vcs.xml
generated
Normal file → Executable file
14
.idea/webResources.xml
generated
Normal file
14
.idea/webResources.xml
generated
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="WebResourcesPaths">
|
||||
<contentEntries>
|
||||
<entry url="file://$PROJECT_DIR$">
|
||||
<entryData>
|
||||
<resourceRoots>
|
||||
<path value="file://$PROJECT_DIR$/bin" />
|
||||
</resourceRoots>
|
||||
</entryData>
|
||||
</entry>
|
||||
</contentEntries>
|
||||
</component>
|
||||
</project>
|
14
LICENSE
Normal file
14
LICENSE
Normal file
|
@ -0,0 +1,14 @@
|
|||
Copyright 2022-2023 Nosial
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
0
Makefile
Normal file → Executable file
0
Makefile
Normal file → Executable file
17
README.md
Executable file
17
README.md
Executable file
|
@ -0,0 +1,17 @@
|
|||
# FederationLib
|
||||
|
||||
[](https://wakatime.com/badge/user/bc15cc8e-c9b9-4c11-bad9-3e3cfacf01e4/project/738fe066-6fc0-45f7-b046-1c8bbf00ffee)
|
||||
|
||||
Spam is a persistent problem on the internet, affecting various communication channels such as email, social media,
|
||||
messaging apps, and more. Spammers use different techniques to distribute spam, such as creating fake accounts, using
|
||||
automated bots or exploiting vulnerabilities in the system, this extends towards bad actors who may use these techniques
|
||||
to harm children, spread misinformation, or even commit financial fraud. In order to combat these issues, while different
|
||||
systems have developed methods for identifying and classifying spam, there is no standard or centralized way to share
|
||||
this information. This results in duplication of effort and inconsistencies in spam classification across different
|
||||
systems.
|
||||
|
||||
The objective of this project is to develop a decentralized, open-source, and privacy-preserving spam detection and
|
||||
classification system. This system will act as a decentralized authority for internet spam classification. The server
|
||||
will allow different organizations and individuals to contribute to the common database, to which the public may query
|
||||
the common database to check if the given content or entity could be classified as spam and provide a confidence score
|
||||
alongside with evidence to support the classification.
|
367
STANDARD.md
Executable file
367
STANDARD.md
Executable file
|
@ -0,0 +1,367 @@
|
|||
# Internet Federation Database Standard (IFDS) Version 1
|
||||
|
||||
IFDS (Internet Federation Database Standard) is a standard database system to which allows internet clients both
|
||||
authorized and public users to query the database to check if the given content or entities involved in the
|
||||
communication could be classified as spam or malicious, allowing a client to take place protections against
|
||||
spam and malicious content on various communication channels such as email, social media, messaging apps, and more.
|
||||
|
||||
# Introduction
|
||||
|
||||
# Table of Contents
|
||||
|
||||
<!-- TOC -->
|
||||
* [Internet Federation Database Standard (IFDS) Version 1](#internet-federation-database-standard-ifds-version-1)
|
||||
* [Introduction](#introduction)
|
||||
* [Table of Contents](#table-of-contents)
|
||||
* [Definitions](#definitions)
|
||||
* [Clients](#clients)
|
||||
* [Client TOTP Signature](#client-totp-signature)
|
||||
* [Client Object](#client-object)
|
||||
* [Federation Standard](#federation-standard)
|
||||
* [Available Types](#available-types)
|
||||
* [ClientIdentity Object](#clientidentity-object)
|
||||
* [Invokable Methods](#invokable-methods)
|
||||
* [ping Method](#ping-method)
|
||||
* [whoami Method](#whoami-method)
|
||||
* [create_client Method](#createclient-method)
|
||||
* [Standard Federated Addresses](#standard-federated-addresses)
|
||||
* [Query Document](#query-document)
|
||||
* [QueryDocument Versioning](#querydocument-versioning)
|
||||
* [QueryDocument Subject Types](#querydocument-subject-types)
|
||||
* [RECON](#recon)
|
||||
* [ANALYZE](#analyze)
|
||||
* [REPORT](#report)
|
||||
* [QueryDocument Event Types](#querydocument-event-types)
|
||||
* [QueryDocument Object](#querydocument-object)
|
||||
* [QueryDocument.Peer Object](#querydocumentpeer-object)
|
||||
* [QueryDocument.Content Object](#querydocumentcontent-object)
|
||||
* [QueryDocument.Attachment Object](#querydocumentattachment-object)
|
||||
* [QueryDocument.Report Object](#querydocumentreport-object)
|
||||
* [Platforms](#platforms)
|
||||
* [Telegram](#telegram)
|
||||
<!-- TOC -->
|
||||
|
||||
# Definitions
|
||||
|
||||
Some definitions used in this document, these definitions are not official and are only used to provide a better
|
||||
understanding of the document and the purposes behind features and fields.
|
||||
|
||||
- **Peer** - Everything from users, bots, channels, groups, and more is considered a peer, a peer is represented by
|
||||
a standard federated address, see [Standard Federated Addresses](#standard-federated-addresses) for more information,
|
||||
a peer could be a parent or child of another peer, for example, a user could be a child of a group or channel with
|
||||
an association type of "member", a peer could also be a parent of another peer, for example, a group or channel
|
||||
could be a parent of a user with an association type of "owner" or "admin", these associations are only made known
|
||||
by trusted clients if specified in the QueryDocument, see [QueryDocument](#querydocument) for more information.
|
||||
|
||||
- **Discovery** - The process of using the provided information in a QueryDocument to discover & collect information
|
||||
about the content or entities involved in the communication, allowing the database to keep a record of or to
|
||||
have a better understanding of peer associations, content, and more. Discovery is not required for a QueryDocument
|
||||
to be valid, however, it is recommended to provide as much information as possible to allow the database to
|
||||
have a better understanding of the content or entities involved in the communication.
|
||||
|
||||
|
||||
# Clients
|
||||
|
||||
TODO: Write this section
|
||||
|
||||
|
||||
## Client TOTP Signature
|
||||
|
||||
TODO: Write this section
|
||||
|
||||
|
||||
## Client Object
|
||||
|
||||
TODO: Write this section
|
||||
|
||||
|
||||
# Federation Standard
|
||||
|
||||
The federation standard is a standard which defines how clients should communicate with servers, the standard
|
||||
|
||||
## Available Types
|
||||
|
||||
|
||||
### ClientIdentity Object
|
||||
|
||||
The ClientIdentity object is used to identify the client, this standard object contains information about what client
|
||||
is invoking the method, this object is required to be provided by all clients when invoking a method, this object
|
||||
is used by the server to determine if the client is authorized to invoke the method and to determine what permissions
|
||||
the client has.
|
||||
|
||||
In such cases where a peer is attempting to invoke a method through a client such as, the server will identify the
|
||||
invoker as the peer on behalf of the client, this is done by providing the peer's standard federated address in the
|
||||
`peer` field of the ClientIdentity object
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-------------------------|----------|----------|--------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `client_uuid` | `string` | Yes | The UUID of the client that is invoking the request |
|
||||
| `client_totp_signature` | `string` | No | Optional. The client's TOTP signature that proves the request came from the client, only applicable if the client uses authorization |
|
||||
| `peer` | `string` | No | Optional. The peer that's invoking the command on behalf of the client |
|
||||
|
||||
> Note: If a server has strict permissions enabled, in such cases where the peer has higher permissions than the
|
||||
client, the server will default to maximum permissions as the client rather than the peer, this is to
|
||||
prevent clients without high permissions from abusing the system by using a peer with higher permissions.
|
||||
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
## Invokable Methods
|
||||
|
||||
Invokable Methods are used by clients, peers or the server to invoke a method on the server, the server will use the
|
||||
provided information to determine who's invoking the method and if the method is allowed to be invoked by the peer.
|
||||
|
||||
All these methods are standard and required to be implemented by all servers, however, approach would allow for servers
|
||||
to implement additional methods to allow for additional functionality that may not be available in the standard,
|
||||
however, this would require the client to be aware of the additional methods and the server to be aware of the
|
||||
additional methods.
|
||||
|
||||
These methods are supposed to invokable by a virtual shell or API endpoint, however, this means while the client does
|
||||
not need to implement or use the shell to invoke these methods but can use the API to achieve the same result as the
|
||||
shell.
|
||||
|
||||
|
||||
### ping Method
|
||||
|
||||
The ping method is used to check if the server is online and to check the server's version, the ping method is
|
||||
available to all peers. Returns True if the execution was successful, otherwise the client should assume the server
|
||||
is offline or the server is temporarily unreachable.
|
||||
|
||||
JSON-RPC Example:
|
||||
|
||||
```jsonl
|
||||
> {"jsonrpc": "2.0", "method": "ping", "id": 1}
|
||||
< {"jsonrpc": "2.0", "result": true, "id": 1}
|
||||
```
|
||||
|
||||
Shell Example:
|
||||
|
||||
```shell
|
||||
$ ping
|
||||
true
|
||||
```
|
||||
|
||||
Default Permission Table:
|
||||
|
||||
| root | admin | operator | agent | client | guest |
|
||||
|:----:|:-----:|:--------:|:-----:|:------:|:-----:|
|
||||
| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
### whoami Method
|
||||
|
||||
The whoami method is used to check the client's permissions and other information, the whoami method is available
|
||||
to all peers. Returns the identified "*uid*" of whoever is invoking the method, this can either be `root` if the
|
||||
function is being invoked by the server or from the root shell, or the client's UID if the function is being invoked
|
||||
by a client or finally the peer's Standard Federated Address if the function is being invoked by a peer.
|
||||
|
||||
JSON-RPC Example:
|
||||
|
||||
```jsonl
|
||||
> {"jsonrpc": "2.0", "method": "whoami", "id": 1}
|
||||
< {"jsonrpc": "2.0", "result": "root", "id": 1}
|
||||
```
|
||||
|
||||
Shell Example:
|
||||
|
||||
```shell
|
||||
$ whoami
|
||||
root
|
||||
```
|
||||
|
||||
Default Permission Table:
|
||||
|
||||
| root | admin | operator | agent | client | guest |
|
||||
|:----:|:-----:|:--------:|:-----:|:------:|:-----:|
|
||||
| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
### create_client Method
|
||||
|
||||
The create_client method is used to create a new client, the create_client method is only available to the root & admin
|
||||
user by default, however, this can be changed by the server's configuration. Returns the UID of the newly created client
|
||||
if the execution was successful
|
||||
|
||||
Parameters:
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|------------------|----------|----------|:-------------------------------------------|
|
||||
| `uid` | `string` | Yes | The user ID of that is invoking the method |
|
||||
| `totp_signature` | `string` | Yes | The TOTP signature for authentication |
|
||||
| `name` | `string` | Yes | The username of the client |
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
## Standard Federated Addresses
|
||||
|
||||
A standard federated address is a universally unique identifier which can represent multiple types of peers from
|
||||
different platforms, federated address formats are standardized so only one format is required to be used by clients
|
||||
and allows servers to reject requests to platforms that are not supported by the server.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
The format of a standard federated address is as follows:
|
||||
|
||||
`source`.`type`:`id`
|
||||
|
||||
- `source` - The platform/source where the peer is from, must be a supported platform, see [Platform](#platform)
|
||||
- `type` - The type of peer, peer types are unique to the platform, see the platform's documentation for more information
|
||||
- `id` - The unique identifier of the peer, the identifier must be unique to the platform, see the platform's documentation for more information
|
||||
|
||||
Even though the value types are defined by the platform's specifications for the federated address, the end result
|
||||
will always be a parsable address that can be used by the server to identify the peer.
|
||||
|
||||
When coming up with a federated address standard for a platform, it is important to keep in mind that `id` must be
|
||||
unique to the peer on the platform, for example, if a platform has a username system it is best to avoid using something
|
||||
that can be changed by the user such as a username, instead, use something that is unique to the peer such as an ID or
|
||||
UUID provided by the platform, this will ensure that the federated address will always be valid and will always point
|
||||
to the correct peer even if the peer changes their username or other information that isn't unique to the peer.
|
||||
|
||||
For a regex pattern to match a standard federated address, see the following:
|
||||
|
||||
```regex
|
||||
(?<source>[a-z0-9]+)\.(?<type>[a-z0-9]+):(?<id>.+)
|
||||
```
|
||||
|
||||
# Query Document
|
||||
|
||||
QueryDocument is a query document constructed by the client which presents the contents of the message or event to
|
||||
either be put into discovery, analyzed for spam or malicious content, or report an event where a report has been
|
||||
produced either by a client's automated system or by manually by a user.
|
||||
|
||||
## QueryDocument Versioning
|
||||
|
||||
The version of the QueryDocument is represented by the `version` field, the version must always be "1" for now, future
|
||||
versions of the QueryDocument structure may be introduced in the future, to ensure backwards compatibility, the
|
||||
version field must be checked to ensure the server can parse the document correctly. If the server does not support
|
||||
the version of the document, the server must reject the document and return an error to the client.
|
||||
|
||||
## QueryDocument Subject Types
|
||||
|
||||
The subject type of the QueryDocument is represented by the `subject_type` field, the subject type must be one of the
|
||||
following values:
|
||||
|
||||
TODO: Write this section
|
||||
|
||||
|
||||
### RECON
|
||||
|
||||

|
||||
|
||||
TODO: Write this section
|
||||
|
||||
|
||||
### ANALYZE
|
||||
|
||||
TODO: Write this section
|
||||
|
||||
|
||||
### REPORT
|
||||
|
||||
TODO: Write this section
|
||||
|
||||
|
||||
## QueryDocument Event Types
|
||||
|
||||
The event type of the QueryDocument is represented by the `event_type` field, the event type must be one of the
|
||||
following values:
|
||||
|
||||
- **GENERAL** - The event is a general event that does not fit into any other event type, the `GENERAL` event type is
|
||||
used for events such as a general update to a peer's activity or status that could be used to keep the database
|
||||
up to date with the peer's activity or status.
|
||||
- **INCOMING** - The event is an incoming message/request from a peer, the `INCOMING` event type is used for events such
|
||||
as a message/request from a peer that was captured by the client.
|
||||
- **OUTGOING** - The event is an outgoing message/request to a peer, the `OUTGOING` event type is used for events such
|
||||
as a message/request to a peer that was captured by the client.
|
||||
- **PEER_JOIN** - The event is a peer join event, the `PEER_JOIN` event type is used for events such when a peer joins
|
||||
or connects to the `channel_peer`
|
||||
- **PEER_LEAVE** - The event is a peer leave event, the `PEER_LEAVE` event type is used for events such when a peer
|
||||
leaves or disconnects from the `channel_peer`
|
||||
- **PEER_BAN** - The event is a peer ban event, the `PEER_BAN` event type is used for events such when a peer is banned
|
||||
from the `channel_peer`, in such cases the `from_peer` is the peer that banned the `to_peer` and the `to_peer`
|
||||
is the peer that was banned.
|
||||
- **PEER_UNBAN** - The event is a peer unban event, the `PEER_UNBAN` event type is used for events such when a peer is
|
||||
unbanned from the `channel_peer`, in such cases the `from_peer` is the peer that unbanned the `to_peer` and the
|
||||
`to_peer` is the peer that was unbanned.
|
||||
- **PEER_KICK** - The event is a peer kick event, the `PEER_KICK` event type is used for events such when a peer is
|
||||
kicked from the `channel_peer`, in such cases the `from_peer` is the peer that kicked the `to_peer` and the
|
||||
`to_peer` is the peer that was kicked.
|
||||
- **PEER_RESTRICT** - The event is a peer restrict event, the `PEER_RESTRICT` event type is used for events such when a
|
||||
peer is restricted from the `channel_peer`, in such cases the `from_peer` is the peer that restricted the `to_peer`
|
||||
and the `to_peer` is the peer that was restricted.
|
||||
- **ANNOUNCEMENT** - The event is an announcement event, the `ANNOUNCEMENT` event type is used for events such as an
|
||||
announcement from the `channel_peer` that was captured by the client, optionally, the `from_peer` field can be
|
||||
used to specify the peer that made the announcement, if the `from_peer` field is not specified, the announcement
|
||||
is assumed to be from the `channel_peer
|
||||
|
||||
Events are used to give the database context about what's happening in the channel, for example, a server with
|
||||
anomaly detection may use the events to determine if a peer is sending too many messages in a short period of time
|
||||
or a channel is being raided by a group of peers.
|
||||
|
||||
In all other cases `GENERAL` may be used if the client is simply doing RECON requests to keep the database up to date
|
||||
with the peer's activity or status.
|
||||
|
||||
## QueryDocument Object
|
||||
|
||||
The QueryDocument is a JSON object which contains the following fields:
|
||||
|
||||
| Name | Type | Required | Example Value(s) | Description |
|
||||
|-------------------------|--------------------------------------------|----------|-----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `version` | `string` | Yes | `1` | The version of the QueryDocument, must always be "1" |
|
||||
| `subject_type` | `string` | Yes | `RECON`, `ANALYZE` or `REPORT` | The subject type, must be "**RECON**", "**ANALYZE**" or "**REPORT**" |
|
||||
| `client_id` | `string` | Yes | `00000000-0000-0000-0000-000000000000` | The client's unique UUIDv4 identifier to identify who the document is from |
|
||||
| `client_totp_signature` | `string` | No | `a94a8fe5ccb19ba61c4c0873d391e987982fbbd3` | The client's TOTP signature to verify the client's identity with the document's reported timestamp, if the client does not use authentication, this field is not required, see [Client TOTP Signature](#client-totp-signature) |
|
||||
| `timestamp` | `int` | No | `1614556800` | The timestamp of the document, if the client does not use authentication, this field is not required instead the server will use the server's timestamp. |
|
||||
| `platform` | `string` | Yes | `telegram.org`, `email`, `discord.com`, etc. | The platform of the message or event, must be a supported service, see [Platform](#platform) |
|
||||
| `event_type` | `string` | Yes | `incoming_message`, `peer_join`, `peer_leave`, etc. | The event type of the message or event that is represented by the document, this provides the server with context to detect anomalies in the document, see [Event Type](#event-type) |
|
||||
| `channel_peer` | `string` | No | `telegram.chat:-1001301191379` | The channel peer represented as a Standard Federated Address in which the content was sent in, this could be a communication channel or chat room where one or more peers may use to broadcast messages on, see [Standard Federated Addresses](#standard-federated-addresses) |
|
||||
| `resent_from_peer` | `string` | No | `telegram.user:123456789` | The resent from peer represented as a Standard Federated Address in which the content was resent/forwarded from, this could be a peer who has resent the content from another peer, see [Standard Federated Addresses](#standard-federated-addresses) |
|
||||
| `from_peer` | `string` | No | `telegram.user:123456789` | The from peer represented as a Standard Federated Address in which the content was sent from, this could be a peer who has sent the content, see [Standard Federated Addresses](#standard-federated-addresses) |
|
||||
| `to_peer` | `string` | No | `telegram.user:123456789` | The to peer represented as a Standard Federated Address in which the content was sent to, this could be the intended recipient of the content, see [Standard Federated Addresses](#standard-federated-addresses) |
|
||||
| `proxy_peer` | `string` | No | `telegram.user:123456789` | The proxy peer represented as a Standard Federated Address in which the content was sent through a proxy, if identified as a proxy that could be mean `from_peer` is not the original sender of the content but instead the proxy peer is, see [Standard Federated Addresses](#standard-federated-addresses) |
|
||||
| `peers` | [`Peer[]`](#querydocumentpeer) | No | N/A | The peer definitions of the document, see [Peer](#querydocumentpeer) |
|
||||
| `content` | [`Content`](#querydocumentcontent) | No | N/A | The content of the document if applicable to the event type, see [Content](#querydocumentcontent) |
|
||||
| `attachments` | [`Attachment[]`](#querydocumentattachment) | No | N/A | Optional attachments if the content contains any, see [Attachment](#querydocumentattachment) |
|
||||
| `reports` | [`Report[]`](#querydocumentreport) | No | N/A | Optional reports if the content contains any, see [Report](#querydocumentreport) |
|
||||
|
||||
> **Note**: The `timestamp` field is not required if the client uses authentication, instead the server will use the server's timestamp to verify the document's timestamp, if the client does not use authentication, the `timestamp` field is required to verify the document's timestamp.
|
||||
|
||||
> **Note**: `peers` uses the Standard Federated Address as the key
|
||||
|
||||
> **Note**: `attachments` uses the File Name as the key
|
||||
|
||||
### QueryDocument.Peer Object
|
||||
|
||||
TODO: Write this section
|
||||
|
||||
|
||||
### QueryDocument.Content Object
|
||||
|
||||
TODO: Write this section
|
||||
|
||||
|
||||
### QueryDocument.Attachment Object
|
||||
|
||||
TODO: Write this section
|
||||
|
||||
|
||||
### QueryDocument.Report Object
|
||||
|
||||
TODO: Write this section
|
||||
|
||||
|
||||
# Platforms
|
||||
|
||||
TODO: Write this section
|
||||
|
||||
## Telegram
|
||||
|
||||
TODO: Write this section
|
BIN
assets/federated_email_address.png
Normal file
BIN
assets/federated_email_address.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
BIN
assets/federated_ipv4.png
Normal file
BIN
assets/federated_ipv4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
assets/federated_telegram_user.png
Normal file
BIN
assets/federated_telegram_user.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
assets/recon_example.png
Normal file
BIN
assets/recon_example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
23
database/anomaly_tracking.sql
Normal file
23
database/anomaly_tracking.sql
Normal file
|
@ -0,0 +1,23 @@
|
|||
create table anomaly_tracking
|
||||
(
|
||||
tracking_id varchar(36) default uuid() not null comment 'The UUID V4 Tracking ID for the anomaly'
|
||||
primary key,
|
||||
target_peer varchar(256) not null comment 'The target peer to which this anomaly is being tracked to',
|
||||
event_type varchar(32) not null comment 'The event type that''s being tracked for the target peer',
|
||||
usage_data mediumblob null comment 'The usage data of the event from the past 48 hours',
|
||||
last_updated bigint default unix_timestamp() not null comment 'The Unix Timestamp for when this record was last updated',
|
||||
created_timestamp bigint default unix_timestamp() not null comment 'The Unix Timestamp for when this record was first created',
|
||||
constraint anomaly_tracking_event_type_target_peer_uindex
|
||||
unique (event_type, target_peer),
|
||||
constraint anomaly_tracking_target_peer_uindex
|
||||
unique (target_peer),
|
||||
constraint anomaly_tracking_tracking_id_uindex
|
||||
unique (tracking_id),
|
||||
constraint anomaly_tracking_peers_federated_address_fk
|
||||
foreign key (target_peer) references peers (federated_address)
|
||||
)
|
||||
comment 'This table is used for tracking anomalies for events';
|
||||
|
||||
create index anomaly_tracking_event_type_index
|
||||
on anomaly_tracking (event_type);
|
||||
|
34
database/clients.sql
Normal file
34
database/clients.sql
Normal file
|
@ -0,0 +1,34 @@
|
|||
create table clients
|
||||
(
|
||||
uuid varchar(36) default uuid() not null comment 'The UUID v4 ID for the client, null for anonymous users.',
|
||||
enabled tinyint(1) default 1 not null comment 'Indicates if this client is enabled or not',
|
||||
name text collate utf8mb4_unicode_ci not null comment 'Optional. Name of the client',
|
||||
description text collate utf8mb4_unicode_ci null comment 'The optional description of the client',
|
||||
secret_totp varchar(32) null comment 'The secret TOTP key used to generated authorization tokens for each request, if null then the client is not required to authenticate via providing generated authorization tokens',
|
||||
query_permission tinyint(1) default 1 null comment 'The permission level set for the query permission
|
||||
0 = Cannot query the database for content analysis
|
||||
1 = Can query the database for content analysis
|
||||
2 = Can query for additional sensitive information
|
||||
3 = Can query and upate metadata',
|
||||
update_permission tinyint(1) default 0 null comment 'The permission level set for the update permission
|
||||
0 = Cannot update the database in anyway whatsoever
|
||||
1 = Can update entity metadata
|
||||
2 = Can submit evidence
|
||||
3 = Can change entity flags & change entity association id',
|
||||
flags varchar(255) null comment 'Comma separated flags associated with the client',
|
||||
created_timestamp bigint default unix_timestamp() not null comment 'The Unix Timestamp for when this client was first registered into the database',
|
||||
updated_timestamp bigint default unix_timestamp() not null comment 'The Unix Timestamp for when this client record was last updated in the database',
|
||||
seen_timestamp bigint default unix_timestamp() not null comment 'The Unix Timestamp for when this client was last seen by the database',
|
||||
constraint clients_uuid_uindex
|
||||
unique (uuid)
|
||||
)
|
||||
comment 'A table for housing registered users/clients that can access the database';
|
||||
|
||||
create index clients_created_timestamp_seen_timestamp_updated_timestamp_index
|
||||
on clients (created_timestamp, seen_timestamp, updated_timestamp)
|
||||
comment 'Index for the client''s timestamps indicators';
|
||||
|
||||
create index clients_enabled_index
|
||||
on clients (enabled)
|
||||
comment 'Index for the client''s enabled state';
|
||||
|
13
database/content.sql
Normal file
13
database/content.sql
Normal file
|
@ -0,0 +1,13 @@
|
|||
create table content
|
||||
(
|
||||
reference_document varchar(36) not null comment 'The primary reference document that the content is from'
|
||||
primary key,
|
||||
body mediumtext not null comment 'UTF-8 encoding body content, max content length 16MB (16,777,216 bytes)',
|
||||
content_type tinytext not null comment 'The content type of the body',
|
||||
priority tinyint(1) default 0 not null comment 'Indicates if the message was sent as a priority',
|
||||
constraint content_reference_document_uindex
|
||||
unique (reference_document),
|
||||
constraint content_query_documents_document_id_fk
|
||||
foreign key (reference_document) references query_documents (document_id)
|
||||
)
|
||||
comment 'The table for housing the content of the messages';
|
35
database/events.sql
Normal file
35
database/events.sql
Normal file
|
@ -0,0 +1,35 @@
|
|||
create table events
|
||||
(
|
||||
uuid varchar(36) default uuid() not null comment 'The unique universal identifier for the event'
|
||||
primary key,
|
||||
code varchar(36) default 'GENERIC' not null comment 'A short-code event name that represents the event that has occured, the message field will contain more details in regards to the event code used.',
|
||||
type varchar(36) default 'INFO' not null comment 'The event type, can be INFO, WARN, or ERR',
|
||||
client_uuid varchar(36) null comment 'The client that created/caused the event',
|
||||
peer varchar(256) null comment 'Optional. The peer that is associated with this event',
|
||||
message text null comment 'The event details',
|
||||
exception blob null comment 'The optional exeception details in a serialized format (MSGPACK)',
|
||||
timestamp bigint default unix_timestamp() not null comment 'The Unix Timestamp for the record',
|
||||
constraint events_uuid_uindex
|
||||
unique (uuid),
|
||||
constraint events_clients_uuid_fk
|
||||
foreign key (client_uuid) references clients (uuid),
|
||||
constraint events_peers_federated_address_fk
|
||||
foreign key (peer) references peers (federated_address)
|
||||
)
|
||||
comment 'A table for housing event logs created by FederationLib in regards to Database changes.';
|
||||
|
||||
create index events_client_uuid_index
|
||||
on events (client_uuid);
|
||||
|
||||
create index events_code_index
|
||||
on events (code);
|
||||
|
||||
create index events_peer_index
|
||||
on events (peer);
|
||||
|
||||
create index events_timestamp_index
|
||||
on events (timestamp);
|
||||
|
||||
create index events_type_index
|
||||
on events (type);
|
||||
|
29
database/peers.sql
Normal file
29
database/peers.sql
Normal file
|
@ -0,0 +1,29 @@
|
|||
create table peers
|
||||
(
|
||||
federated_address varchar(256) not null comment 'The standard federated address of the peer'
|
||||
primary key,
|
||||
client_first_seen varchar(36) not null comment 'The UUID of the client that first discovered the peer',
|
||||
client_last_seen varchar(36) not null comment 'The UUID of the client that last saw this peer',
|
||||
active_restriction varchar(36) null comment 'Optional. The UUID of the restriction that is currently active on the peer',
|
||||
permission_role tinyint default 5 not null comment 'The permission role assigned to the peer',
|
||||
discovered_timestamp bigint default unix_timestamp() not null comment 'The Unix Timestamp for when this peer was first discovered',
|
||||
seen_timestamp bigint default unix_timestamp() not null comment 'The Unix Timestamp for when this peer was last seen',
|
||||
constraint discovered_peers_federated_address_uindex
|
||||
unique (federated_address),
|
||||
constraint discovered_peers_clients_uuid_fk
|
||||
foreign key (client_first_seen) references clients (uuid),
|
||||
constraint discovered_peers_clients_uuid_fk2
|
||||
foreign key (client_last_seen) references clients (uuid),
|
||||
constraint peers_restrictions_uuid_fk
|
||||
foreign key (active_restriction) references restrictions (uuid)
|
||||
)
|
||||
comment 'The table for housing all the discovered peers';
|
||||
|
||||
create index discovered_peers_client_first_seen_index
|
||||
on peers (client_first_seen);
|
||||
|
||||
create index discovered_peers_client_last_seen_index
|
||||
on peers (client_last_seen);
|
||||
|
||||
create index peers_active_restriction_index
|
||||
on peers (active_restriction);
|
16
database/peers_associations.sql
Normal file
16
database/peers_associations.sql
Normal file
|
@ -0,0 +1,16 @@
|
|||
create table peers_associations
|
||||
(
|
||||
child_peer varchar(255) not null comment 'The peer that is used to declare the association to the parent peer',
|
||||
parent_peer varchar(255) not null comment 'The parent peer that the child peer is associated with',
|
||||
association_type tinytext not null comment 'The association type between the child peer and the parent peer',
|
||||
client_uuid varchar(36) not null comment 'The client UUID that made the association between the two peers',
|
||||
timestamp int default unix_timestamp() not null comment 'The Unix Timestamp for when the client made the association',
|
||||
constraint peers_associations_child_peer_parent_peer_uindex
|
||||
unique (child_peer, parent_peer),
|
||||
constraint peers_associations_clients_uuid_fk
|
||||
foreign key (client_uuid) references clients (uuid),
|
||||
constraint peers_associations_peers_federated_address_fk
|
||||
foreign key (child_peer) references peers (federated_address),
|
||||
constraint peers_associations_peers_federated_address_fk2
|
||||
foreign key (parent_peer) references peers (federated_address)
|
||||
);
|
26
database/peers_telegram_chat.sql
Normal file
26
database/peers_telegram_chat.sql
Normal file
|
@ -0,0 +1,26 @@
|
|||
create table peers_telegram_chat
|
||||
(
|
||||
federated_address varchar(255) not null comment 'The primary standard federated address to which this record is associated to what peer'
|
||||
primary key,
|
||||
id bigint not null comment ' Unique identifier for this chat. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this identifier.',
|
||||
type tinytext not null comment 'Type of chat, can be either “private”, “group”, “supergroup” or “channel”',
|
||||
title varchar(255) null comment 'Optional. Title, for supergroups, channels and group chats',
|
||||
username varchar(255) null comment 'Optional. Username, for private chats, supergroups and channels if available',
|
||||
first_name varchar(255) null comment 'Optional. First name of the other party in a private chat',
|
||||
last_name varchar(255) null comment 'Optional. Last name of the other party in a private chat',
|
||||
is_forum tinyint(1) default 0 null comment 'Optional. True, if the supergroup chat is a forum (has topics enabled)',
|
||||
updated_timestamp bigint default unix_timestamp() not null comment 'The Unix Timestamp for when this record was last updated',
|
||||
updated_client varchar(36) not null comment 'The Client UUID that last updated the peer''s metadata',
|
||||
constraint peers_telegram_chat_federated_address_uindex
|
||||
unique (federated_address),
|
||||
constraint peers_telegram_chat_id_uindex
|
||||
unique (id),
|
||||
constraint peers_telegram_chat_clients_uuid_fk
|
||||
foreign key (updated_client) references clients (uuid),
|
||||
constraint peers_telegram_chat_peers_federated_address_fk
|
||||
foreign key (federated_address) references peers (federated_address)
|
||||
)
|
||||
comment 'Table for housing metadata information for Telegram Chats';
|
||||
|
||||
create index peers_telegram_chat_username_index
|
||||
on peers_telegram_chat (username);
|
26
database/peers_telegram_user.sql
Normal file
26
database/peers_telegram_user.sql
Normal file
|
@ -0,0 +1,26 @@
|
|||
create table peers_telegram_user
|
||||
(
|
||||
federated_address varchar(255) default concat(`id`, 'u@telegram.org') not null comment 'A federated Internet Standard ID for this entity'
|
||||
primary key,
|
||||
id bigint not null comment 'Unique identifier for this user or bot. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a 64-bit integer or double-precision float type are safe for storing this identifier.',
|
||||
is_bot tinyint(1) default 0 not null comment 'True, if this user is a bot',
|
||||
first_name varchar(255) collate utf8mb4_unicode_ci not null comment 'Optional. User''s or bot''s last name',
|
||||
last_name varchar(255) collate utf8mb4_unicode_ci null comment 'Optional. User''s or bot''s last name',
|
||||
username varchar(255) collate utf8mb4_unicode_ci null comment 'Optional. User''s or bot''s username',
|
||||
language_code tinyint(2) null comment 'Optional. IETF language tag of the user''s language',
|
||||
is_premium tinyint(1) default 0 not null comment 'Optional. True, if this user is a Telegram Premium user',
|
||||
updated_timestamp bigint default unix_timestamp() not null comment 'The Unix Timestamp for when this record was last updated',
|
||||
updated_client varchar(36) not null comment 'The client UUID that last updated the peer''s metadata',
|
||||
constraint telegram_user_federated_id_uindex
|
||||
unique (federated_address),
|
||||
constraint telegram_user_pk2
|
||||
unique (id),
|
||||
constraint peers_telegram_user_clients_uuid_fk
|
||||
foreign key (updated_client) references clients (uuid),
|
||||
constraint telegram_user_discovered_peers_federated_address_fk
|
||||
foreign key (federated_address) references peers (federated_address)
|
||||
)
|
||||
comment 'The table for housing Telegram user entity references';
|
||||
|
||||
create index telegram_user_username_index
|
||||
on peers_telegram_user (username);
|
59
database/query_documents.sql
Normal file
59
database/query_documents.sql
Normal file
|
@ -0,0 +1,59 @@
|
|||
create table query_documents
|
||||
(
|
||||
document_id varchar(26) not null comment 'The unique reference ID of the document (UUID v4)'
|
||||
primary key,
|
||||
subject_type varchar(32) default 'RECON' not null comment 'The subject type of the query document',
|
||||
client_id varchar(36) not null comment 'The ID of the client that submitted the document',
|
||||
client_signature varchar(36) null comment 'The authorization token signed with the document by the client',
|
||||
event_type tinytext not null comment 'The event type that occured',
|
||||
channel_peer varchar(256) null comment 'Optional. The channel that the content was sent on',
|
||||
resent_from_peer varchar(256) null comment 'Optional. The original peer that constructed the message, this indicates "from_peer" is resending the content',
|
||||
from_peer varchar(256) null comment 'Optional. The peer that sent the content',
|
||||
to_peer varchar(256) null comment 'Optional. The peer that is the intended reciever of the content',
|
||||
reply_to_peer varchar(256) null comment 'Optional. This indicates the message is a reply to the peer',
|
||||
proxy_peer varchar(256) null comment 'Optional. The proxy peer used to send the content, may indicates "from_peer" is the original sender.',
|
||||
retain tinyint default 0 not null comment 'Indicates if this record should be retained or not',
|
||||
timestamp bigint default unix_timestamp() not null comment 'The Unix Timestamp of the document''s submission (based off the authorization token)',
|
||||
constraint query_documents_document_id_uindex
|
||||
unique (document_id),
|
||||
constraint query_documents_discovered_peers_federated_address_fk
|
||||
foreign key (channel_peer) references peers (federated_address),
|
||||
constraint query_documents_discovered_peers_federated_address_fk2
|
||||
foreign key (from_peer) references peers (federated_address),
|
||||
constraint query_documents_discovered_peers_federated_address_fk3
|
||||
foreign key (to_peer) references peers (federated_address),
|
||||
constraint query_documents_peers_federated_address_fk
|
||||
foreign key (proxy_peer) references peers (federated_address),
|
||||
constraint query_documents_peers_federated_address_fk2
|
||||
foreign key (resent_from_peer) references peers (federated_address),
|
||||
constraint query_documents_peers_federated_address_fk3
|
||||
foreign key (reply_to_peer) references peers (federated_address)
|
||||
)
|
||||
comment 'An archive of all the submitted query documents the database received';
|
||||
|
||||
create index query_documents_channel_peer_index
|
||||
on query_documents (channel_peer);
|
||||
|
||||
create index query_documents_client_id_index
|
||||
on query_documents (client_id);
|
||||
|
||||
create index query_documents_from_peer_index
|
||||
on query_documents (from_peer);
|
||||
|
||||
create index query_documents_proxy_peer_index
|
||||
on query_documents (proxy_peer);
|
||||
|
||||
create index query_documents_reply_to_peer_index
|
||||
on query_documents (reply_to_peer);
|
||||
|
||||
create index query_documents_resent_from_peer_index
|
||||
on query_documents (resent_from_peer);
|
||||
|
||||
create index query_documents_retain_index
|
||||
on query_documents (retain);
|
||||
|
||||
create index query_documents_subject_type_index
|
||||
on query_documents (subject_type);
|
||||
|
||||
create index query_documents_to_peer_index
|
||||
on query_documents (to_peer);
|
37
database/reports.sql
Normal file
37
database/reports.sql
Normal file
|
@ -0,0 +1,37 @@
|
|||
create table reports
|
||||
(
|
||||
report_id varchar(36) default uuid() not null comment 'The UUID V4 for the report''s ID'
|
||||
primary key,
|
||||
reference_document varchar(36) not null comment 'The reference document to which this report came from',
|
||||
type varchar(32) default 'OTHER' not null comment 'The standard report type',
|
||||
reporting_peer varchar(256) null comment 'The peer that submitted the report, if empty will assume the client is the reporter.',
|
||||
target_peer varchar(36) not null comment 'The target peer that the report is against at',
|
||||
scan_mode varchar(32) null comment 'The scan mode that was used to conduct the automated report',
|
||||
confidence_score float null comment 'The confidence score of the scanner if the report was automated',
|
||||
reason text null comment 'The reason for the report',
|
||||
submission_timestamp bigint default unix_timestamp() null comment 'The Unix Timestamp for when this report was submitted to the database',
|
||||
constraint reports_report_id_uindex
|
||||
unique (report_id),
|
||||
constraint reports_discovered_peers_federated_address_fk
|
||||
foreign key (reporting_peer) references peers (federated_address),
|
||||
constraint reports_discovered_peers_federated_address_fk2
|
||||
foreign key (target_peer) references peers (federated_address),
|
||||
constraint reports_query_documents_document_id_fk
|
||||
foreign key (reference_document) references query_documents (document_id)
|
||||
)
|
||||
comment 'The table for storing reports made against peers';
|
||||
|
||||
create index reports_reference_document_index
|
||||
on reports (reference_document);
|
||||
|
||||
create index reports_reporting_peer_index
|
||||
on reports (reporting_peer);
|
||||
|
||||
create index reports_submission_timestamp_index
|
||||
on reports (submission_timestamp);
|
||||
|
||||
create index reports_target_peer_index
|
||||
on reports (target_peer);
|
||||
|
||||
create index reports_type_index
|
||||
on reports (type);
|
28
database/restrictions.sql
Normal file
28
database/restrictions.sql
Normal file
|
@ -0,0 +1,28 @@
|
|||
create table restrictions
|
||||
(
|
||||
uuid varchar(36) default uuid() not null comment 'The Unique UUID V4 for the restriction record'
|
||||
primary key,
|
||||
peer varchar(256) not null comment 'The standard federated address of the peer',
|
||||
client_uuid varchar(36) null comment 'Optional. The Client UUID that placed the restriction on the peer, if empty then it was an automated restriction placed by the system.',
|
||||
reason varchar(64) not null comment 'The reason for the restriction that was applied to the peer',
|
||||
automated tinyint default 0 not null comment 'Indicates if this restriction was automated',
|
||||
type varchar(32) null comment 'The restriction type that was applied to the peer',
|
||||
created bigint default unix_timestamp() not null comment 'The Unix Timestamp for when the restriction was created',
|
||||
expires bigint not null comment 'The Unix Timestamp for when the restriction expires',
|
||||
constraint restrictions_peer_uindex
|
||||
unique (peer) comment 'The main unique index for the primary key "peer"',
|
||||
constraint restrictions_uuid_uindex
|
||||
unique (uuid),
|
||||
constraint restrictions_clients_uuid_fk
|
||||
foreign key (client_uuid) references clients (uuid),
|
||||
constraint restrictions_peers_federated_address_fk
|
||||
foreign key (peer) references peers (federated_address)
|
||||
)
|
||||
comment 'Archive of past restrictions placed on peers';
|
||||
|
||||
create index restrictions_automated_index
|
||||
on restrictions (automated);
|
||||
|
||||
create index restrictions_client_uuid_index
|
||||
on restrictions (client_uuid);
|
||||
|
6
main
Normal file
6
main
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
require 'ncc';
|
||||
import('net.nosial.federationlib');
|
||||
|
||||
\FederationCLI\Program::main(\OptsLib\Parse::getArguments());
|
66
project.json
Normal file → Executable file
66
project.json
Normal file → Executable file
|
@ -5,17 +5,81 @@
|
|||
"minimum_version": "8.0",
|
||||
"maximum_version": "8.2"
|
||||
},
|
||||
"options": []
|
||||
"options": {
|
||||
"create_symlink": true
|
||||
}
|
||||
},
|
||||
"assembly": {
|
||||
"name": "FederationLib",
|
||||
"package": "net.nosial.federationlib",
|
||||
"company": "Nosial",
|
||||
"copyright": "Copyright (c) 2022-2023 Nosial",
|
||||
"description": "A Standard Federation Database Library written in PHP",
|
||||
"version": "1.0.0",
|
||||
"uuid": "2fb017d4-cf2f-11ed-8cee-b7943a7c7304"
|
||||
},
|
||||
"execution_policies": [
|
||||
{
|
||||
"name": "main",
|
||||
"runner": "php",
|
||||
"execute": {
|
||||
"target": "main",
|
||||
"working_directory": "%CWD%",
|
||||
"tty": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"build": {
|
||||
"source_path": "src",
|
||||
"default_configuration": "release",
|
||||
"main": "main",
|
||||
"define_constants": {
|
||||
"version": "%ASSEMBLY.VERSION%"
|
||||
},
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "net.nosial.configlib",
|
||||
"version": "latest",
|
||||
"source_type": "remote",
|
||||
"source": "nosial/libs.config=latest@n64"
|
||||
},
|
||||
{
|
||||
"name": "net.nosial.optslib",
|
||||
"version": "latest",
|
||||
"source_type": "remote",
|
||||
"source": "nosial/libs.opts=latest@n64"
|
||||
},
|
||||
{
|
||||
"name": "net.nosial.tamerlib",
|
||||
"version": "latest",
|
||||
"source_type": "remote",
|
||||
"source": "nosial/libs.tamer=latest@n64"
|
||||
},
|
||||
{
|
||||
"name": "net.nosial.loglib",
|
||||
"version": "latest",
|
||||
"source_type": "remote",
|
||||
"source": "nosial/libs.log=latest@n64"
|
||||
},
|
||||
{
|
||||
"name": "com.doctrine.dbal",
|
||||
"version": "latest",
|
||||
"source_type": "remote",
|
||||
"source": "doctrine/dbal=latest@composer"
|
||||
},
|
||||
{
|
||||
"name": "com.symfony.uid",
|
||||
"version": "latest",
|
||||
"source_type": "remote",
|
||||
"source": "symfony/uid=latest@composer"
|
||||
},
|
||||
{
|
||||
"name": "com.malios.php_to_ascii_table",
|
||||
"version": "latest",
|
||||
"source_type": "remote",
|
||||
"source": "malios/php-to-ascii-table=latest@composer"
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
{
|
||||
"name": "release",
|
||||
|
|
223
src/FederationCLI/InteractiveMode.php
Normal file
223
src/FederationCLI/InteractiveMode.php
Normal file
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationCLI;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Classes\Configuration;
|
||||
use FederationLib\Enums\Standard\Methods;
|
||||
use FederationLib\Exceptions\DatabaseException;
|
||||
use FederationLib\Exceptions\Standard\AccessDeniedException;
|
||||
use FederationLib\Exceptions\Standard\ClientNotFoundException;
|
||||
use FederationLib\Exceptions\Standard\InternalServerException;
|
||||
use FederationLib\Exceptions\Standard\InvalidClientDescriptionException;
|
||||
use FederationLib\Exceptions\Standard\InvalidClientNameException;
|
||||
use FederationLib\FederationLib;
|
||||
use JsonException;
|
||||
use OptsLib\Parse;
|
||||
use TamerLib\Enums\TamerMode;
|
||||
use TamerLib\Exceptions\ServerException;
|
||||
use TamerLib\Exceptions\WorkerFailedException;
|
||||
use TamerLib\tm;
|
||||
|
||||
class InteractiveMode
|
||||
{
|
||||
/**
|
||||
* @var FederationLib
|
||||
*/
|
||||
private static $federation_lib;
|
||||
|
||||
/**
|
||||
* Main entry point for the interactive mode
|
||||
*
|
||||
* @param array $args
|
||||
* @return void
|
||||
* @throws ServerException
|
||||
* @throws WorkerFailedException
|
||||
*/
|
||||
public static function main(array $args=[]): void
|
||||
{
|
||||
tm::initialize(TamerMode::CLIENT);
|
||||
tm::createWorker(Configuration::getTamerLibConfiguration()->getCliWorkers(), FederationLib::getSubprocessPath());
|
||||
|
||||
self::$federation_lib = new FederationLib();
|
||||
|
||||
while(true)
|
||||
{
|
||||
print(sprintf('%s@%s:~$ ', 'root', Configuration::getHostName()));
|
||||
$input = trim(fgets(STDIN));
|
||||
|
||||
try
|
||||
{
|
||||
switch(strtolower(explode(' ', $input)[0]))
|
||||
{
|
||||
case Methods::PING:
|
||||
self::ping();
|
||||
break;
|
||||
case Methods::WHOAMI:
|
||||
self::whoami();
|
||||
break;
|
||||
|
||||
case Methods::CREATE_CLIENT:
|
||||
self::createClient($input);
|
||||
break;
|
||||
case Methods::GET_CLIENT:
|
||||
self::getClient($input);
|
||||
break;
|
||||
case Methods::UPDATE_CLIENT_NAME:
|
||||
self::changeClientName($input);
|
||||
break;
|
||||
|
||||
default:
|
||||
print(sprintf('Command \'%s\' not found' . PHP_EOL, $input));
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
print(sprintf('Error: %s' . PHP_EOL, $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the ping method
|
||||
*
|
||||
* @return void
|
||||
* @throws AccessDeniedException
|
||||
* @throws ClientNotFoundException
|
||||
* @throws InternalServerException
|
||||
*/
|
||||
private static function ping(): void
|
||||
{
|
||||
if(self::$federation_lib->ping(null))
|
||||
{
|
||||
print('OK' . PHP_EOL);
|
||||
return;
|
||||
}
|
||||
|
||||
print('ERROR' . PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the whoami method and prints the result
|
||||
*
|
||||
* @return void
|
||||
* @throws AccessDeniedException
|
||||
* @throws ClientNotFoundException
|
||||
* @throws InternalServerException
|
||||
*/
|
||||
private static function whoami(): void
|
||||
{
|
||||
print(self::$federation_lib->whoami(null) . PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
* @return void
|
||||
* @throws AccessDeniedException
|
||||
* @throws ClientNotFoundException
|
||||
* @throws DatabaseException
|
||||
* @throws InternalServerException
|
||||
* @throws InvalidClientDescriptionException
|
||||
* @throws InvalidClientNameException
|
||||
*/
|
||||
private static function createClient(string $input): void
|
||||
{
|
||||
$parsed_arguments = Parse::parseArgument($input);
|
||||
|
||||
$name = $parsed_arguments['name'] ?? $parsed_arguments['n'] ?? null;
|
||||
$description = $parsed_arguments['description'] ?? $parsed_arguments['d'] ?? null;
|
||||
|
||||
print(self::$federation_lib->createClient(null, $name, $description) . PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
* @return void
|
||||
* @throws AccessDeniedException
|
||||
* @throws DatabaseException
|
||||
* @throws InternalServerException
|
||||
* @throws JsonException
|
||||
* @noinspection PhpMultipleClassDeclarationsInspection
|
||||
*/
|
||||
private static function getClient(string $input): void
|
||||
{
|
||||
$parsed_arguments = Parse::parseArgument($input);
|
||||
|
||||
$uuid = $parsed_arguments['uuid'] ?? $parsed_arguments['u'] ?? null;
|
||||
$raw = $parsed_arguments['raw'] ?? $parsed_arguments['r'] ?? false;
|
||||
|
||||
if($uuid === null | $uuid === '')
|
||||
{
|
||||
print('Missing required argument \'uuid\'' . PHP_EOL);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$client = self::$federation_lib->getClient(null, $uuid);
|
||||
|
||||
if($raw)
|
||||
{
|
||||
print(json_encode($client->toArray(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT) . PHP_EOL);
|
||||
return;
|
||||
}
|
||||
|
||||
Utilities::printData('CLIENT LOOKUP RESULTS', [
|
||||
'UUID' => $client->getUuid(),
|
||||
'NAME' => $client->getName(),
|
||||
'DESCRIPTION' => $client->getDescription() ?? 'N/A',
|
||||
'PERMISSION_ROLE' => $client->getPermissionRole(),
|
||||
'CREATED_AT' => date('Y-m-d H:i:s', $client->getCreatedTimestamp()),
|
||||
'UPDATED_AT' => date('Y-m-d H:i:s', $client->getUpdatedTimestamp()),
|
||||
'SEEN_AT' => date('Y-m-d H:i:s', $client->getSeenTimestamp())
|
||||
]);
|
||||
}
|
||||
catch(ClientNotFoundException)
|
||||
{
|
||||
print(sprintf('Client with UUID \'%s\' not found' . PHP_EOL, $uuid));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the name of a client with the given UUID
|
||||
*
|
||||
* @param string $input
|
||||
* @return void
|
||||
* @throws AccessDeniedException
|
||||
* @throws DatabaseException
|
||||
* @throws InternalServerException
|
||||
* @throws InvalidClientNameException
|
||||
*/
|
||||
private static function changeClientName(string $input): void
|
||||
{
|
||||
$parsed_arguments = Parse::parseArgument($input);
|
||||
|
||||
$uuid = $parsed_arguments['uuid'] ?? $parsed_arguments['u'] ?? null;
|
||||
$name = $parsed_arguments['name'] ?? $parsed_arguments['n'] ?? null;
|
||||
|
||||
if($uuid === null | $uuid === '')
|
||||
{
|
||||
print('Missing required argument \'uuid\'' . PHP_EOL);
|
||||
return;
|
||||
}
|
||||
|
||||
if($name === null | $name === '')
|
||||
{
|
||||
print('Missing required argument \'name\'' . PHP_EOL);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
self::$federation_lib->updateClientName(null, $uuid, $name);
|
||||
print('OK' . PHP_EOL);
|
||||
}
|
||||
catch(ClientNotFoundException)
|
||||
{
|
||||
print(sprintf('Client with UUID \'%s\' not found' . PHP_EOL, $uuid));
|
||||
}
|
||||
}
|
||||
}
|
41
src/FederationCLI/Program.php
Normal file
41
src/FederationCLI/Program.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace FederationCLI;
|
||||
|
||||
use ncc\Runtime;
|
||||
|
||||
class Program
|
||||
{
|
||||
/**
|
||||
* Main entry point for the CLI
|
||||
*
|
||||
* @param array $args
|
||||
* @return void
|
||||
*/
|
||||
public static function main(array $args=[]): void
|
||||
{
|
||||
if (isset($args['shell']))
|
||||
{
|
||||
InteractiveMode::main($args);
|
||||
}
|
||||
|
||||
self::help();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the help message
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function help(): void
|
||||
{
|
||||
print('FederationLib v' . Runtime::getConstant('net.nosial.federationlib', 'version') . PHP_EOL . PHP_EOL);
|
||||
|
||||
print('Usage: federationlib [command] [options]' . PHP_EOL);
|
||||
print('Commands:' . PHP_EOL);
|
||||
print(' help - show this help' . PHP_EOL);
|
||||
print(' shell - enter interactive mode' . PHP_EOL);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
}
|
162
src/FederationCLI/Utilities.php
Normal file
162
src/FederationCLI/Utilities.php
Normal file
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
namespace FederationCLI;
|
||||
|
||||
class Utilities
|
||||
{
|
||||
/**
|
||||
* Parses the shell input into a command and arguments array
|
||||
*
|
||||
* @param string $input
|
||||
* @return array
|
||||
*/
|
||||
public static function parseShellInput(string $input): array
|
||||
{
|
||||
$parsed = explode(' ', $input);
|
||||
$command = array_shift($parsed);
|
||||
$args = $parsed;
|
||||
|
||||
return [
|
||||
'command' => $command,
|
||||
'args' => $args
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a boolean value from a string
|
||||
*
|
||||
* @param $input
|
||||
* @return bool
|
||||
*/
|
||||
public static function parseBoolean($input): bool
|
||||
{
|
||||
if(is_null($input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(is_bool($input))
|
||||
{
|
||||
return $input;
|
||||
}
|
||||
|
||||
if(is_numeric($input))
|
||||
{
|
||||
return (bool) $input;
|
||||
}
|
||||
|
||||
if(is_string($input))
|
||||
{
|
||||
$input = trim($input);
|
||||
}
|
||||
|
||||
switch(strtolower($input))
|
||||
{
|
||||
case 'true':
|
||||
case 'yes':
|
||||
case 'y':
|
||||
case '1':
|
||||
return true;
|
||||
|
||||
default:
|
||||
case 'false':
|
||||
case 'no':
|
||||
case 'n':
|
||||
case '0':
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user for an input value
|
||||
*
|
||||
* @param string|null $prompt
|
||||
* @param string|null $default_value
|
||||
* @return string|null
|
||||
*/
|
||||
public static function promptInput(?string $prompt=null, ?string $default_value=null): ?string
|
||||
{
|
||||
if($prompt)
|
||||
{
|
||||
print($prompt . ' ');
|
||||
}
|
||||
|
||||
$input = trim(fgets(STDIN));
|
||||
|
||||
if(!$input && $default_value)
|
||||
{
|
||||
$input = $default_value;
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user for a boolean value
|
||||
*
|
||||
* @param string|null $prompt
|
||||
* @param bool $default_value
|
||||
* @return bool
|
||||
*/
|
||||
public static function promptYesNo(?string $prompt=null, bool $default_value=false): bool
|
||||
{
|
||||
if($prompt)
|
||||
{
|
||||
print($prompt . ' ');
|
||||
}
|
||||
|
||||
$input = trim(fgets(STDIN));
|
||||
|
||||
if(!$input && $default_value)
|
||||
{
|
||||
$input = $default_value;
|
||||
}
|
||||
|
||||
return self::parseBoolean($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints data in a formatted manner
|
||||
*
|
||||
* @param $banner_text
|
||||
* @param $data
|
||||
* @param int $body_width
|
||||
* @return void
|
||||
*/
|
||||
public static function printData($banner_text, $data, int $body_width = 70): void
|
||||
{
|
||||
// Padding and wrap for the longest key
|
||||
$max_key_len = max(array_map('strlen', array_keys($data)));
|
||||
|
||||
// Adjust padding for body_width
|
||||
$padding = $body_width - ($max_key_len + 4); // Additional 2 spaces for initial padding
|
||||
|
||||
// Banner
|
||||
$banner_width = $body_width + 2;
|
||||
echo str_repeat("*", $banner_width) . PHP_EOL;
|
||||
echo "* " . str_pad($banner_text, $banner_width - 4, ' ', STR_PAD_RIGHT) . " *" . PHP_EOL;
|
||||
echo str_repeat("*", $banner_width) . PHP_EOL;
|
||||
|
||||
// Print data
|
||||
foreach ($data as $key => $value)
|
||||
{
|
||||
// Split value into lines if it's too long
|
||||
$lines = str_split($value, $padding);
|
||||
// Print lines
|
||||
foreach ($lines as $i => $line)
|
||||
{
|
||||
if ($i === 0)
|
||||
{
|
||||
// First line - print key and value
|
||||
print(' ' . str_pad(strtoupper($key), $max_key_len) . ' ' . $line . PHP_EOL);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Additional lines - only value
|
||||
print(str_repeat(' ', $max_key_len + 4) . $line . PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
84
src/FederationLib/Classes/Base32.php
Normal file
84
src/FederationLib/Classes/Base32.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
class Base32
|
||||
{
|
||||
/**
|
||||
* The base32 alphabet. (RFC 4648)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
|
||||
/**
|
||||
* Encodes a string to base32.
|
||||
*
|
||||
* @param $string
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($string): string
|
||||
{
|
||||
$binary = '';
|
||||
$output = '';
|
||||
foreach (str_split($string) as $char)
|
||||
{
|
||||
$binary .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
$padding = 5 - (strlen($binary) % 5);
|
||||
|
||||
if ($padding !== 5)
|
||||
{
|
||||
$binary .= str_repeat('0', $padding);
|
||||
}
|
||||
|
||||
$chunks = str_split($binary, 5);
|
||||
foreach ($chunks as $chunk)
|
||||
{
|
||||
$output .= self::$alphabet[bindec($chunk)];
|
||||
}
|
||||
|
||||
$padding = (strlen($output) % 8 === 0) ? 0 : (8 - (strlen($output) % 8));
|
||||
if ($padding !== 0)
|
||||
{
|
||||
$output .= str_repeat('=', $padding);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a base32 encoded string.
|
||||
*
|
||||
* @param $string
|
||||
* @return string
|
||||
*/
|
||||
public static function decode($string): string
|
||||
{
|
||||
$binary = '';
|
||||
$output = '';
|
||||
|
||||
foreach (str_split($string) as $char)
|
||||
{
|
||||
if ($char === '=')
|
||||
break;
|
||||
$binary .= str_pad(decbin(strpos(self::$alphabet, $char)), 5, '0', STR_PAD_LEFT);
|
||||
}
|
||||
$padding = 8 - (strlen($binary) % 8);
|
||||
|
||||
if ($padding !== 8)
|
||||
{
|
||||
$binary = substr($binary, 0, -$padding);
|
||||
}
|
||||
|
||||
$chunks = str_split($binary, 8);
|
||||
|
||||
foreach ($chunks as $chunk)
|
||||
{
|
||||
$output .= chr(bindec($chunk));
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
7
src/FederationLib/Classes/CacheSystem.php
Normal file
7
src/FederationLib/Classes/CacheSystem.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
class CacheSystem
|
||||
{
|
||||
}
|
362
src/FederationLib/Classes/Configuration.php
Normal file
362
src/FederationLib/Classes/Configuration.php
Normal file
|
@ -0,0 +1,362 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Classes\Configuration\CacheServerConfiguration;
|
||||
use FederationLib\Classes\Configuration\TamerLibConfiguration;
|
||||
use FederationLib\Enums\Standard\Methods;
|
||||
use FederationLib\Enums\Standard\PermissionRole;
|
||||
use RuntimeException;
|
||||
|
||||
class Configuration
|
||||
{
|
||||
/**
|
||||
* @var \ConfigLib\Configuration|null
|
||||
*/
|
||||
private static $configuration;
|
||||
|
||||
/**
|
||||
* @var TamerLibConfiguration|null
|
||||
*/
|
||||
private static $tamerlib_configuration;
|
||||
|
||||
/**
|
||||
* Returns the full raw configuration array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getConfiguration(): array
|
||||
{
|
||||
if(self::$configuration === null)
|
||||
{
|
||||
self::$configuration = new \ConfigLib\Configuration('federation');
|
||||
|
||||
/** Database Configuration */
|
||||
self::$configuration->setDefault('database.driver', 'mysqli');
|
||||
self::$configuration->setDefault('database.host', 'localhost');
|
||||
self::$configuration->setDefault('database.port', 3306);
|
||||
self::$configuration->setDefault('database.name', 'federation');
|
||||
self::$configuration->setDefault('database.username', 'root');
|
||||
self::$configuration->setDefault('database.password', 'root');
|
||||
self::$configuration->setDefault('database.charset', 'utf8mb4');
|
||||
self::$configuration->setDefault('database.reconnect_interval', 1800);
|
||||
|
||||
/** Multi-Cache Configuration */
|
||||
// Cache System Configuration
|
||||
self::$configuration->setDefault('cache_system.enabled', true);
|
||||
self::$configuration->setDefault('cache_system.opened_connection_priority', 20); // Higher is better
|
||||
self::$configuration->setDefault('cache_system.error_connection_priority', -30); // Lower is better
|
||||
// Client Objects
|
||||
self::$configuration->setDefault('cache_system.cache.client_objects_enabled', true);
|
||||
self::$configuration->setDefault('cache_system.cache.client_objects_ttl', 200);
|
||||
self::$configuration->setDefault('cache_system.cache.client_objects_server_preference', 'redis_master');
|
||||
self::$configuration->setDefault('cache_system.cache.client_objects_server_fallback', 'redis_slave');
|
||||
// PeerRecord Objects
|
||||
self::$configuration->setDefault('cache_system.cache.peer_objects_enabled', true);
|
||||
self::$configuration->setDefault('cache_system.cache.peer_objects_ttl', 200);
|
||||
self::$configuration->setDefault('cache_system.cache.peer_objects_server_preference', 'redis_master');
|
||||
self::$configuration->setDefault('cache_system.cache.peer_objects_server_fallback', 'redis_slave');
|
||||
// PeerAssociationRecord Objects
|
||||
self::$configuration->setDefault('cache_system.cache.peer_association_objects_enabled', true);
|
||||
self::$configuration->setDefault('cache_system.cache.peer_association_objects_ttl', 200);
|
||||
self::$configuration->setDefault('cache_system.cache.peer_association_objects_server_preference', 'redis_master');
|
||||
self::$configuration->setDefault('cache_system.cache.peer_association_objects_server_fallback', 'redis_slave');
|
||||
/** Multi-Cache Server Configuration */
|
||||
// Redis Master Configuration
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.enabled', true);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.host', 'localhost');
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.port', 6379);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.priority', 100);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.password', null);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.database', null);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.reconnect_interval', 1800);
|
||||
// Redis Slave Configuration
|
||||
self::$configuration->setDefault('cache_system.servers.redis_slave.enabled', false);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_slave.host', 'localhost');
|
||||
self::$configuration->setDefault('cache_system.servers.redis_slave.port', 11211);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_slave.priority', 50);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_slave.password', null);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_slave.database', null);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_slave.reconnect_interval', 1800);
|
||||
|
||||
/** Federation Configuration */
|
||||
self::$configuration->setDefault('federation.hostname', 'FederationLib'); // Server Hostname
|
||||
self::$configuration->setDefault('federation.events_retention', 1209600); // Two Weeks
|
||||
self::$configuration->setDefault('federation.security.strict_permissions', true); // Security Feature, prevents clients & peers from elevating their permissions
|
||||
self::$configuration->setDefault('federation.security.method_permissions.ping', 5); // Guest or above
|
||||
self::$configuration->setDefault('federation.security.method_permissions.whoami', 5); // Guest or above
|
||||
self::$configuration->setDefault('federation.security.method_permissions.create_client', 2); // Admin or above
|
||||
self::$configuration->setDefault('federation.security.method_permissions.get_client', 2); // Admin or above
|
||||
self::$configuration->setDefault('federation.security.method_permissions.update_client_name', 2); // Admin or above
|
||||
|
||||
/** TamerLib Configuration */
|
||||
self::$configuration->setDefault('federation.tamer_lib.cli_workers', 8);
|
||||
self::$configuration->setDefault('federation.tamer_lib.node_workers', 20);
|
||||
self::$configuration->setDefault('federation.tamer_lib.server.host', '127.0.0.1');
|
||||
self::$configuration->setDefault('federation.tamer_lib.server.port', 6379);
|
||||
self::$configuration->setDefault('federation.tamer_lib.server.password', null);
|
||||
self::$configuration->setDefault('federation.tamer_lib.server.database', null);
|
||||
|
||||
/** Save the configuration's default values if they don't exist */
|
||||
try
|
||||
{
|
||||
self::$configuration->save();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new RuntimeException('Failed to save configuration: ' . $e->getMessage(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$configuration->getConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration object.
|
||||
*
|
||||
* @return \ConfigLib\Configuration
|
||||
*/
|
||||
public static function getConfigurationObject(): \ConfigLib\Configuration
|
||||
{
|
||||
if(self::$configuration === null)
|
||||
{
|
||||
self::getConfiguration();
|
||||
}
|
||||
|
||||
return self::$configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TamerLibConfiguration
|
||||
*/
|
||||
public static function getTamerLibConfiguration(): TamerLibConfiguration
|
||||
{
|
||||
if(self::$tamerlib_configuration === null)
|
||||
{
|
||||
self::$tamerlib_configuration = new TamerLibConfiguration(self::getConfiguration());
|
||||
}
|
||||
|
||||
return self::$tamerlib_configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns driver of the database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDatabaseDriver(): string
|
||||
{
|
||||
return self::getConfiguration()['database']['driver'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host of the database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDatabaseHost(): string
|
||||
{
|
||||
return self::getConfiguration()['database']['host'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the port of the database.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getDatabasePort(): int
|
||||
{
|
||||
return (int)self::getConfiguration()['database']['port'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDatabaseName(): string
|
||||
{
|
||||
return self::getConfiguration()['database']['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username of the database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDatabaseUsername(): string
|
||||
{
|
||||
return self::getConfiguration()['database']['username'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the password of the database.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getDatabasePassword(): ?string
|
||||
{
|
||||
$password = self::getConfiguration()['database']['password'];
|
||||
|
||||
if($password === '')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the charset of the database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDatabaseCharset(): string
|
||||
{
|
||||
return self::getConfiguration()['database']['charset'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the interval in seconds to reconnect to the database.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getDatabaseReconnectInterval(): int
|
||||
{
|
||||
return (int)self::getConfiguration()['database']['reconnect_interval'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hostname of the server.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getHostName(): string
|
||||
{
|
||||
return self::getConfiguration()['federation']['hostname'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns True if the strict permission system is enabled and False if not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function strictPermissionEnabled(): bool
|
||||
{
|
||||
return (bool)self::getConfiguration()['federation']['security']['strict_permissions'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the permission level required to execute a command.
|
||||
*
|
||||
* @param string $command
|
||||
* @return int
|
||||
*/
|
||||
public static function getMethodPermission(string $command): int
|
||||
{
|
||||
if(isset(self::getConfiguration()['federation']['security']['method_permissions'][$command]))
|
||||
{
|
||||
return (int)self::getConfiguration()['federation']['security']['method_permissions'][$command];
|
||||
}
|
||||
|
||||
return match ($command)
|
||||
{
|
||||
Methods::CREATE_CLIENT => PermissionRole::ADMIN,
|
||||
default => PermissionRole::GUEST,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns True if the cache system is enabled for FederationLib
|
||||
* and False if not, based on the configuration.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isCacheSystemEnabled(): bool
|
||||
{
|
||||
return (bool)self::getConfiguration()['cache_system']['enabled'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration for all the cache servers
|
||||
*
|
||||
* @return CacheServerConfiguration[]
|
||||
*/
|
||||
public static function getCacheServers(): array
|
||||
{
|
||||
$results = [];
|
||||
foreach(self::getConfiguration()['cache_system']['servers'] as $server_name => $server)
|
||||
{
|
||||
if($server['enabled'] === true)
|
||||
{
|
||||
$results[] = new CacheServerConfiguration($server_name, $server);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the additional priority to add/remove if the connection is opened.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getCacheOpenedConnectionPriority(): int
|
||||
{
|
||||
return (int)self::getConfiguration()['cache_system']['opened_connection_priority'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns additional priority to add/remove if the connection is closed.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getCacheErrorConnectionPriority(): int
|
||||
{
|
||||
return (int)self::getConfiguration()['cache_system']['error_connection_priority'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public static function getObjectCacheEnabled(string $name): bool
|
||||
{
|
||||
if(self::isCacheSystemEnabled() === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool)self::getConfiguration()['cache_system']['cache'][sprintf('%s_enabled', $name)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return int
|
||||
*/
|
||||
public static function getObjectCacheTtl(string $name): int
|
||||
{
|
||||
return (int)self::getConfiguration()['cache_system']['cache'][sprintf('%s_ttl', $name)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
public static function getObjectCacheServerPreference(string $name): string
|
||||
{
|
||||
return self::getConfiguration()['cache_system']['cache'][sprintf('%s_server_preference', $name)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
public static function getObjectCacheServerFallback(string $name): string
|
||||
{
|
||||
return self::getConfiguration()['cache_system']['cache'][sprintf('%s_server_fallback', $name)];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Classes\Configuration;
|
||||
|
||||
class CacheServerConfiguration
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private bool $enabled;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $host;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private ?int $port;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private ?int $priority;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $password;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $database;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private ?int $reconnect_interval;
|
||||
|
||||
/**
|
||||
* CacheServerConfiguration constructor.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $configuration
|
||||
*/
|
||||
public function __construct(string $name, array $configuration)
|
||||
{
|
||||
$this->name = $configuration['name'] ?? $name;
|
||||
$this->enabled = $configuration['enabled'] ?? false;
|
||||
$this->host = $configuration['host'] ?? null;
|
||||
$this->port = $configuration['port'] ?? null;
|
||||
$this->priority = $configuration['priority'] ?? null;
|
||||
$this->password = $configuration['password'] ?? null;
|
||||
$this->database = $configuration['database'] ?? null;
|
||||
$this->reconnect_interval = $configuration['reconnect_interval'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the cache server
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the cache server is enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->enabled ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host of the cache server
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getHost(): ?string
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getPort(): ?int
|
||||
{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getPriority(): ?int
|
||||
{
|
||||
return $this->priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPassword(): ?string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDatabase(): ?string
|
||||
{
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getReconnectInterval(): ?int
|
||||
{
|
||||
return $this->reconnect_interval;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Classes\Configuration;
|
||||
|
||||
class CacheSystemConfiguration
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $client_objects_enabled;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $client_objects_ttl;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $client_objects_server_preference;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $client_objects_server_fallback;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $peer_objects_enabled;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $peer_objects_ttl;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $peer_objects_server_preference;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $peer_objects_server_fallback;
|
||||
|
||||
/**
|
||||
* CacheSystemConfiguration constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
*/
|
||||
public function __construct(array $configuration)
|
||||
{
|
||||
$this->client_objects_enabled = $configuration['cache_system.cache.client_objects_enabled'] ?? false;
|
||||
$this->client_objects_ttl = $configuration['cache_system.cache.client_objects_ttl'] ?? 200;
|
||||
$this->client_objects_server_preference = $configuration['cache_system.cache.client_objects_server_preference'] ?? 'any';
|
||||
$this->client_objects_server_fallback = $configuration['cache_system.cache.client_objects_server_fallback'] ?? 'any';
|
||||
|
||||
$this->peer_objects_enabled = $configuration['cache_system.cache.peer_objects_enabled'] ?? false;
|
||||
$this->peer_objects_ttl = $configuration['cache_system.cache.peer_objects_ttl'] ?? 200;
|
||||
$this->peer_objects_server_preference = $configuration['cache_system.cache.peer_objects_server_preference'] ?? 'any';
|
||||
$this->peer_objects_server_fallback = $configuration['cache_system.cache.peer_objects_server_fallback'] ?? 'any';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns True if client cache objects are enabled, false otherwise
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getClientObjectsEnabled(): bool
|
||||
{
|
||||
return $this->client_objects_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TTL for client cache objects
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getClientObjectsTtl(): int
|
||||
{
|
||||
return $this->client_objects_ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server preference to where client cache objects should be stored
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getClientObjectsServerPreference(): string
|
||||
{
|
||||
return $this->client_objects_server_preference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server fallback to where client cache objects should be stored
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getClientObjectsServerFallback(): string
|
||||
{
|
||||
return $this->client_objects_server_fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns True if peer cache objects are enabled, false otherwise
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getPeerObjectsEnabled(): bool
|
||||
{
|
||||
return $this->peer_objects_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TTL for peer cache objects
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPeerObjectsTtl(): int
|
||||
{
|
||||
return $this->peer_objects_ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server preference to where peer cache objects should be stored
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPeerObjectsServerPreference(): string
|
||||
{
|
||||
return $this->peer_objects_server_preference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server fallback to where peer cache objects should be stored
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPeerObjectsServerFallback(): string
|
||||
{
|
||||
return $this->peer_objects_server_fallback;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Classes\Configuration;
|
||||
|
||||
use TamerLib\Objects\ServerConfiguration;
|
||||
|
||||
class TamerLibConfiguration
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $cli_workers;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $node_workers;
|
||||
|
||||
/**
|
||||
* @var ServerConfiguration
|
||||
*/
|
||||
private $server_configuration;
|
||||
|
||||
/**
|
||||
* TamerLibConfiguration constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
*/
|
||||
public function __construct(array $configuration)
|
||||
{
|
||||
$this->cli_workers = $configuration['federation.tamer_lib.cli_workers'] ?? 8;
|
||||
$this->node_workers = $configuration['federation.tamer_lib.node_workers'] ?? 20;
|
||||
|
||||
$this->server_configuration = new ServerConfiguration(
|
||||
$configuration['federation.tamer_lib.server.host'] ?? '127.0.0.1',
|
||||
$configuration['federation.tamer_lib.server.port'] ?? 6379,
|
||||
$configuration['federation.tamer_lib.server.password'] ?? null,
|
||||
$configuration['federation.tamer_lib.server.database'] ?? 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of CLI workers to use.
|
||||
*
|
||||
* @return int|mixed
|
||||
*/
|
||||
public function getCliWorkers(): mixed
|
||||
{
|
||||
return $this->cli_workers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of Node workers to use.
|
||||
*
|
||||
* @return int|mixed
|
||||
*/
|
||||
public function getNodeWorkers(): mixed
|
||||
{
|
||||
return $this->node_workers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ServerConfiguration object for TamerLib.
|
||||
*
|
||||
* @return ServerConfiguration
|
||||
*/
|
||||
public function getServerConfiguration(): ServerConfiguration
|
||||
{
|
||||
return $this->server_configuration;
|
||||
}
|
||||
}
|
6082
src/FederationLib/Classes/Data/wordlist.txt
Normal file
6082
src/FederationLib/Classes/Data/wordlist.txt
Normal file
File diff suppressed because it is too large
Load diff
88
src/FederationLib/Classes/Database.php
Normal file
88
src/FederationLib/Classes/Database.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\Logging\Middleware;
|
||||
use Exception;
|
||||
use FederationLib\Exceptions\DatabaseException;
|
||||
use LogLib\Log;
|
||||
use LogLib\Psr;
|
||||
|
||||
class Database
|
||||
{
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private static $sql_last_connection_time;
|
||||
|
||||
/**
|
||||
* @var Connection|null
|
||||
*/
|
||||
private static $sql_connection;
|
||||
|
||||
/**
|
||||
* Returns/Establishes a connection to the database.
|
||||
*
|
||||
* @return Connection
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public static function getConnection(): Connection
|
||||
{
|
||||
if(self::$sql_connection === null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log::debug('net.nosial.federationlib', sprintf('Connecting to the database: %s://%s@%s:%s/%s', Configuration::getDatabaseDriver(), Configuration::getDatabaseUsername(), Configuration::getDatabaseHost(), Configuration::getDatabasePort(), Configuration::getDatabaseName()));
|
||||
$connection = DriverManager::getConnection([
|
||||
'driver' => Configuration::getDatabaseDriver(),
|
||||
'host' => Configuration::getDatabaseHost(),
|
||||
'port' => Configuration::getDatabasePort(),
|
||||
'dbname' => Configuration::getDatabaseName(),
|
||||
'user' => Configuration::getDatabaseUsername(),
|
||||
'password' => Configuration::getDatabasePassword(),
|
||||
'charset' => Configuration::getDatabaseCharset()
|
||||
]);
|
||||
|
||||
$connection->getConfiguration()->setMiddlewares([new Middleware(new Psr('com.dbal.doctrine'))]);
|
||||
$connection->connect();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to connect to the database: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
self::$sql_connection = $connection;
|
||||
self::$sql_last_connection_time = time();
|
||||
}
|
||||
else
|
||||
{
|
||||
/** @noinspection NestedPositiveIfStatementsInspection */
|
||||
if(time() - self::$sql_last_connection_time > Configuration::getDatabaseReconnectInterval())
|
||||
{
|
||||
Log::debug('net.nosial.federationlib', sprintf('Interval to reconnect to the %s server has been reached, reconnecting...', Configuration::getDatabaseDriver()));
|
||||
|
||||
// Reconnect to the database.
|
||||
self::$sql_connection->close();
|
||||
self::$sql_connection = null;
|
||||
|
||||
return self::getConnection();
|
||||
}
|
||||
}
|
||||
|
||||
return self::$sql_connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time of the last connection to the database. (null if no connection has been made)
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public static function getSqlLastConnectionTime(): ?int
|
||||
{
|
||||
return self::$sql_last_connection_time;
|
||||
}
|
||||
}
|
229
src/FederationLib/Classes/Redis.php
Normal file
229
src/FederationLib/Classes/Redis.php
Normal file
|
@ -0,0 +1,229 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Classes\Configuration\CacheServerConfiguration;
|
||||
use FederationLib\Enums\Misc;
|
||||
use FederationLib\Exceptions\CacheConnectionException;
|
||||
use FederationLib\Exceptions\CacheDriverException;
|
||||
use LogLib\Log;
|
||||
use RedisException;
|
||||
|
||||
class Redis
|
||||
{
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $redis_last_connection_time;
|
||||
|
||||
/**
|
||||
* @var \Redis|null
|
||||
*/
|
||||
private $redis_connection;
|
||||
|
||||
/**
|
||||
* @var CacheServerConfiguration
|
||||
*/
|
||||
private $configuration;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $connection_error;
|
||||
|
||||
/**
|
||||
* Redis constructor.
|
||||
*
|
||||
* @param CacheServerConfiguration $configuration
|
||||
*/
|
||||
public function __construct(CacheServerConfiguration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
$this->connection_error = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the redis server is available to connect to
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
if(!$this->configuration->isEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the redis server is connected.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isConnected(): bool
|
||||
{
|
||||
if(!$this->configuration->isEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->redis_connection !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isConnectionError(): bool
|
||||
{
|
||||
return $this->connection_error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the redis server if it's connected.
|
||||
*
|
||||
* @param bool $reset_error
|
||||
* @return void
|
||||
*/
|
||||
public function disconnect(bool $reset_error=true): void
|
||||
{
|
||||
if(!$this->isConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$this->redis_connection->close();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to disconnect from redis server: %s', $e->getMessage()));
|
||||
}
|
||||
|
||||
if($reset_error)
|
||||
{
|
||||
$this->connection_error = false;
|
||||
}
|
||||
|
||||
$this->redis_connection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a connection to the redis server.
|
||||
*
|
||||
* @param bool $throw_exception
|
||||
* @return void
|
||||
* @throws CacheConnectionException
|
||||
* @throws CacheDriverException
|
||||
*/
|
||||
public function connect(bool $throw_exception=true): void
|
||||
{
|
||||
if(!$this->configuration->isEnabled())
|
||||
{
|
||||
if($throw_exception)
|
||||
{
|
||||
throw new CacheDriverException(sprintf('Failed to connect to the redis server \'%s\' because it\'s disabled.', $this->configuration->getName()));
|
||||
}
|
||||
}
|
||||
|
||||
if($this->redis_connection !== null)
|
||||
{
|
||||
if($this->redis_last_connection_time === null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if($this->redis_last_connection_time < (time() - $this->configuration->getReconnectInterval()))
|
||||
{
|
||||
Log::verbose(Misc::FEDERATIONLIB, sprintf('Interval limit of %s seconds to reconnect to the redis server \'%s\' has been reached, reconnecting...', $this->configuration->getReconnectInterval(), $this->configuration->getName()));
|
||||
$this->disconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Log::info(Misc::FEDERATIONLIB, sprintf('Connecting to the redis server \'%s\': %s:%s', $this->configuration->getName(), $this->configuration->getHost(), $this->configuration->getPort()));
|
||||
$redis = new \Redis();
|
||||
$redis->connect($this->configuration->getHost(), $this->configuration->getPort());
|
||||
if($this->configuration->getPassword() !== null)
|
||||
{
|
||||
$redis->auth($this->configuration->getPassword());
|
||||
}
|
||||
$redis->select($this->configuration->getDatabase());
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
$this->connection_error = true;
|
||||
|
||||
if($throw_exception)
|
||||
{
|
||||
throw new CacheConnectionException(sprintf('Failed to connect to the redis server \'%s\': %s', $this->configuration->getName(), $e->getMessage()), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to connect to the redis server \'%s\': %s', $this->configuration->getName(), $e->getMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->redis_connection = $redis;
|
||||
$this->redis_last_connection_time = time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns/Establishes a connection to the redis server.
|
||||
* Returns null if redis is disabled.
|
||||
*
|
||||
* @param bool $throw_exception
|
||||
* @return \Redis|null
|
||||
* @throws CacheConnectionException
|
||||
* @throws CacheDriverException
|
||||
*/
|
||||
public function getConnection(bool $throw_exception=true): ?\Redis
|
||||
{
|
||||
if(!$this->configuration->isEnabled())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!$this->isConnected())
|
||||
{
|
||||
$this->connect($throw_exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
if($this->redis_last_connection_time < (time() - $this->configuration->getReconnectInterval()))
|
||||
{
|
||||
$this->connect($throw_exception);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->redis_connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last time the redis server was connected to.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getRedisLastConnectionTime(): ?int
|
||||
{
|
||||
return $this->redis_last_connection_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CacheServerConfiguration
|
||||
*/
|
||||
public function getConfiguration(): CacheServerConfiguration
|
||||
{
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
}
|
121
src/FederationLib/Classes/Security.php
Normal file
121
src/FederationLib/Classes/Security.php
Normal file
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
||||
class Security
|
||||
{
|
||||
/**
|
||||
* The number of digits in the generated code.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $digits = 6;
|
||||
|
||||
/**
|
||||
* The number of seconds a code is valid.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $time_step = 30;
|
||||
|
||||
/**
|
||||
* The number of seconds the clock is allowed to drift.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $time_offset = 0;
|
||||
|
||||
/**
|
||||
* The hash algorithm used to generate the code.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $hash_algorithm = 'sha1';
|
||||
|
||||
/**
|
||||
* Generates a secret TOTP key.
|
||||
*
|
||||
* @param $length
|
||||
* @return string
|
||||
*/
|
||||
public static function generateSecret($length = 20): string
|
||||
{
|
||||
try
|
||||
{
|
||||
return Base32::encode(random_bytes($length));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new RuntimeException('Failed to generate secret: ' . $e->getMessage(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a TOTP code based on a secret and timestamp.
|
||||
*
|
||||
* @param $secret
|
||||
* @param $timestamp
|
||||
* @return string
|
||||
*/
|
||||
public static function generateCode($secret, $timestamp=null): string
|
||||
{
|
||||
if (!$timestamp)
|
||||
{
|
||||
$timestamp = time();
|
||||
}
|
||||
|
||||
$hash = hash_hmac(self::$hash_algorithm, pack('N*', 0, $timestamp / self::$time_step + self::$time_offset), Base32::decode($secret), true);
|
||||
$offset = ord(substr($hash, -1)) & 0x0F;
|
||||
$value = unpack('N', substr($hash, $offset, 4))[1] & 0x7FFFFFFF;
|
||||
$modulo = 10 ** self::$digits;
|
||||
return str_pad($value % $modulo, self::$digits, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a TOTP code.
|
||||
*
|
||||
* @param $secret
|
||||
* @param $code
|
||||
* @param $window
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateCode($secret, $code, $window = 1): bool
|
||||
{
|
||||
$timestamp = time();
|
||||
|
||||
for ($i = -$window; $i <= $window; $i++)
|
||||
{
|
||||
$generatedCode = self::generateCode($secret, $timestamp + $i * self::$time_step);
|
||||
if ($code === $generatedCode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as gettype() but returns only what the user should see.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
public static function gettype(mixed $value): string
|
||||
{
|
||||
return match (strtolower(gettype($value)))
|
||||
{
|
||||
'boolean' => 'boolean',
|
||||
'integer' => 'integer',
|
||||
'double' => 'float',
|
||||
'string' => 'string',
|
||||
'array' => 'array',
|
||||
default => 'null',
|
||||
};
|
||||
}
|
||||
}
|
174
src/FederationLib/Classes/Utilities.php
Normal file
174
src/FederationLib/Classes/Utilities.php
Normal file
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
use FederationLib\Interfaces\PeerMetadataInterface;
|
||||
use InvalidArgumentException;
|
||||
use Throwable;
|
||||
|
||||
class Utilities
|
||||
{
|
||||
/**
|
||||
* @var string[]|null
|
||||
*/
|
||||
private static $wordlist;
|
||||
|
||||
/**
|
||||
* Returns an array of words from the wordlist.
|
||||
*
|
||||
* @param bool $cache True to cache the wordlist in memory, false to not cache the wordlist
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getWordlist(bool $cache=true): array
|
||||
{
|
||||
if(self::$wordlist !== null)
|
||||
{
|
||||
return self::$wordlist;
|
||||
}
|
||||
|
||||
$wordlist = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'Data' . DIRECTORY_SEPARATOR . 'wordlist.txt');
|
||||
$wordlist = array_filter(array_map('trim', explode("\n", $wordlist)));
|
||||
|
||||
if($cache)
|
||||
{
|
||||
self::$wordlist = $wordlist;
|
||||
}
|
||||
|
||||
return $wordlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random name.
|
||||
*
|
||||
* @param int $word_count
|
||||
* @param string $separator
|
||||
* @param bool $capitalize
|
||||
* @return string
|
||||
*/
|
||||
public static function generateName(int $word_count=3, string $separator=' ', bool $capitalize=true): string
|
||||
{
|
||||
$wordlist = self::getWordlist();
|
||||
$name = '';
|
||||
|
||||
for($i = 0; $i < $word_count; $i++)
|
||||
{
|
||||
$name .= $wordlist[array_rand($wordlist)] . $separator;
|
||||
}
|
||||
|
||||
$name = substr($name, 0, -1);
|
||||
|
||||
if($capitalize)
|
||||
{
|
||||
$name = ucwords($name);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* The value of the returned array is the probability of the number being an anomaly.
|
||||
* Negative values are anomalies, positive values are not.
|
||||
* The higher the absolute value, the more likely it is to be an anomaly.
|
||||
*
|
||||
* @param array $data An array of numbers to check for anomalies
|
||||
* @param int $threshold The threshold to use for detecting anomalies
|
||||
* @param bool $filter True to only return anomalies, false to return all values
|
||||
* @return array An array of probabilities
|
||||
*/
|
||||
public static function detectAnomalies(array $data, int $threshold = 2, bool $filter=true): array
|
||||
{
|
||||
$mean = array_sum($data) / count($data);
|
||||
$squares = array_map(static function($x) use ($mean) { return ($x - $mean) ** 2; }, $data);
|
||||
$standard_deviation = sqrt(array_sum($squares) / count($data));
|
||||
$outliers = [];
|
||||
foreach ($data as $key => $value)
|
||||
{
|
||||
$z_score = ($value - $mean) / $standard_deviation;
|
||||
$probability = exp(-$z_score ** 2 / 2) / (sqrt(2 * M_PI) * $standard_deviation);
|
||||
|
||||
if($filter)
|
||||
{
|
||||
if ($z_score >= $threshold)
|
||||
{
|
||||
$outliers[$key] = -$probability;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$outliers[$key] = $probability;
|
||||
}
|
||||
|
||||
}
|
||||
return $outliers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public static function weightedRandomPick(array $data): string
|
||||
{
|
||||
$totalWeight = array_sum($data);
|
||||
if($totalWeight === 0)
|
||||
{
|
||||
throw new InvalidArgumentException('Total weight cannot be 0');
|
||||
}
|
||||
|
||||
// Normalize weights to 0-1
|
||||
foreach ($data as $item => $weight)
|
||||
{
|
||||
$data[$item] = $weight / $totalWeight;
|
||||
}
|
||||
|
||||
// Generate a random number between 0 and 1
|
||||
$rand = mt_rand() / mt_getrandmax();
|
||||
|
||||
// Select an item
|
||||
$cumulativeWeight = 0.0;
|
||||
foreach ($data as $item => $weight)
|
||||
{
|
||||
$cumulativeWeight += $weight;
|
||||
|
||||
if ($rand < $cumulativeWeight)
|
||||
{
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
||||
// Select a random item if the cumulative weight is 0
|
||||
return array_rand($data);
|
||||
}
|
||||
/**
|
||||
* This function returns an array showing the changed values comparing from $a to $b
|
||||
* if $b contains changes that is different from $a, it will be returned in the array
|
||||
*
|
||||
* @param PeerMetadataInterface $a
|
||||
* @param PeerMetadataInterface $b
|
||||
* @return array
|
||||
*/
|
||||
public static function metadataDifferences(PeerMetadataInterface $a, PeerMetadataInterface $b, array $filter=[]): array
|
||||
{
|
||||
$differences = [];
|
||||
$a_array = $a->toArray();
|
||||
$b_array = $b->toArray();
|
||||
|
||||
foreach ($a_array as $key => $value)
|
||||
{
|
||||
if(in_array($key, $filter, true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if($value !== $b_array[$key])
|
||||
{
|
||||
$differences[$key] = $b_array[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return $differences;
|
||||
}
|
||||
}
|
152
src/FederationLib/Classes/Validate.php
Normal file
152
src/FederationLib/Classes/Validate.php
Normal file
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
use FederationLib\Enums\Standard\PeerType;
|
||||
use FederationLib\Enums\Standard\InternetPeerType;
|
||||
use FederationLib\Enums\Standard\PeerAssociationType;
|
||||
use FederationLib\Enums\Standard\UserPeerType;
|
||||
use FederationLib\Exceptions\Standard\InvalidPeerMetadataException;
|
||||
|
||||
class Validate
|
||||
{
|
||||
/**
|
||||
* Determines the entity type based on the entity type string.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* @return string
|
||||
*/
|
||||
public static function getEntityType(string $entity_type): string
|
||||
{
|
||||
if(in_array($entity_type, InternetPeerType::ALL))
|
||||
{
|
||||
return PeerType::INTERNET;
|
||||
}
|
||||
|
||||
if(in_array($entity_type, UserPeerType::ALL))
|
||||
{
|
||||
return PeerType::USER;
|
||||
}
|
||||
|
||||
return PeerType::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the entity type is valid and supported.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateEntityType(string $entity_type): bool
|
||||
{
|
||||
return self::getEntityType($entity_type) !== PeerType::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the peer association type.
|
||||
*
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
public static function peerAssociationType(string $type): bool
|
||||
{
|
||||
if (in_array(strtolower($type), PeerAssociationType::ALL))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a client name based on certain criteria.
|
||||
*
|
||||
* The client name must be alphanumeric, allowing spaces, periods, dashes, and underscores,
|
||||
* with a minimum length of 3 characters and a maximum length of 42 characters.
|
||||
*
|
||||
* @param string $name The client name to validate
|
||||
* @return bool Returns true if the client name is valid, false otherwise
|
||||
*/
|
||||
public static function clientName(string $name): bool
|
||||
{
|
||||
if (!preg_match('/^[a-zA-Z0-9\s\.\-_]+$/', $name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$length = strlen($name);
|
||||
return !($length < 3 || $length > 42);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a client description based on certain criteria.
|
||||
*
|
||||
* @param string $description The client description to validate
|
||||
* @return bool Returns true if the client description is valid, false otherwise
|
||||
*/
|
||||
public static function clientDescription(string $description): bool
|
||||
{
|
||||
$length = strlen($description);
|
||||
return !($length < 3 || $length > 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the given permission role is valid.
|
||||
*
|
||||
* @param string|int $role
|
||||
* @return bool
|
||||
*/
|
||||
public static function permissionRole(string|int $role): bool
|
||||
{
|
||||
return (int)$role >= 0 && (int)$role <= 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given metadata for a peer.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $required
|
||||
* @param array $optional
|
||||
* @return void
|
||||
* @throws InvalidPeerMetadataException
|
||||
*/
|
||||
public static function metadata(array $data, array $required, array $optional): void
|
||||
{
|
||||
foreach($required as $property => $type)
|
||||
{
|
||||
if(!isset($data[$property]))
|
||||
{
|
||||
throw new InvalidPeerMetadataException(sprintf('The property "%s" is required in the metadata', $property));
|
||||
}
|
||||
|
||||
if(gettype($data[$property]) !== $type)
|
||||
{
|
||||
throw new InvalidPeerMetadataException(sprintf('The property "%s" must be a %s in metadata, got %s', $property, $type, Security::gettype($data[$property])));
|
||||
}
|
||||
}
|
||||
|
||||
foreach($optional as $property => $type)
|
||||
{
|
||||
if(isset($data[$property]) && gettype($data[$property]) !== $type)
|
||||
{
|
||||
throw new InvalidPeerMetadataException(sprintf('The property "%s" must be a %s in metadata, got %s', $property, $type, Security::gettype($data[$property])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the given association type is valid.
|
||||
*
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
public static function associationType(string $type): bool
|
||||
{
|
||||
if(in_array($type, PeerAssociationType::ALL))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
30
src/FederationLib/Enums/DatabaseTables.php
Normal file
30
src/FederationLib/Enums/DatabaseTables.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums;
|
||||
|
||||
final class DatabaseTables
|
||||
{
|
||||
public const ANOMALY_TRACKING = 'anomaly_tracking';
|
||||
public const CLIENTS = 'clients';
|
||||
public const EVENTS = 'events';
|
||||
public const PEERS = 'peers';
|
||||
public const PEERS_ASSOCIATIONS = 'peers_associations';
|
||||
public const PEERS_TELEGRAM_CHAT = 'peers_telegram_chat';
|
||||
public const PEERS_TELEGRAM_USER = 'peers_telegram_user';
|
||||
public const QUERY_DOCUMENTS = 'query_documents';
|
||||
public const REPORTS = 'reports';
|
||||
public const RESTRICTIONS = 'restrictions';
|
||||
|
||||
public const ALL = [
|
||||
self::ANOMALY_TRACKING,
|
||||
self::CLIENTS,
|
||||
self::EVENTS,
|
||||
self::PEERS,
|
||||
self::PEERS_ASSOCIATIONS,
|
||||
self::PEERS_TELEGRAM_CHAT,
|
||||
self::PEERS_TELEGRAM_USER,
|
||||
self::QUERY_DOCUMENTS,
|
||||
self::REPORTS,
|
||||
self::RESTRICTIONS,
|
||||
];
|
||||
}
|
11
src/FederationLib/Enums/EventPriority.php
Normal file
11
src/FederationLib/Enums/EventPriority.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums;
|
||||
|
||||
final class EventPriority
|
||||
{
|
||||
public const NONE = 0;
|
||||
public const LOW = 1;
|
||||
public const MEDIUM = 2;
|
||||
public const HIGH = 3;
|
||||
}
|
9
src/FederationLib/Enums/FilterOrder.php
Normal file
9
src/FederationLib/Enums/FilterOrder.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums;
|
||||
|
||||
final class FilterOrder
|
||||
{
|
||||
public const ASCENDING = 'asc';
|
||||
public const DESCENDING = 'desc';
|
||||
}
|
18
src/FederationLib/Enums/Filters/ListClientsFilter.php
Normal file
18
src/FederationLib/Enums/Filters/ListClientsFilter.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Filters;
|
||||
|
||||
final class ListClientsFilter
|
||||
{
|
||||
public const CREATED_TIMESTAMP = 'created_timestamp';
|
||||
public const UPDATED_TIMESTAMP = 'updated_timestamp';
|
||||
public const SEEN_TIMESTAMP = 'seen_timestamp';
|
||||
public const ENABLED = 'enabled';
|
||||
|
||||
public const ALL = [
|
||||
self::CREATED_TIMESTAMP,
|
||||
self::UPDATED_TIMESTAMP,
|
||||
self::SEEN_TIMESTAMP,
|
||||
self::ENABLED
|
||||
];
|
||||
}
|
8
src/FederationLib/Enums/Misc.php
Normal file
8
src/FederationLib/Enums/Misc.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums;
|
||||
|
||||
final class Misc
|
||||
{
|
||||
public const FEDERATIONLIB = 'net.nosial.federationlib';
|
||||
}
|
80
src/FederationLib/Enums/Standard/ErrorCodes.php
Normal file
80
src/FederationLib/Enums/Standard/ErrorCodes.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php // TODO: Fix the error-codes once it's been decided what they should be.
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class ErrorCodes
|
||||
{
|
||||
/**
|
||||
* An internal server error occurred.
|
||||
*/
|
||||
public const INTERNAL_SERVER_ERROR = -1000;
|
||||
|
||||
/**
|
||||
* The invoker does not have permission to perform the requested action.
|
||||
*/
|
||||
public const ACCESS_DENIED = -1001;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The requested client was not found.
|
||||
*/
|
||||
public const CLIENT_NOT_FOUND = 1000;
|
||||
|
||||
/**
|
||||
* The requested client name is invalid.
|
||||
*/
|
||||
public const INVALID_CLIENT_NAME = 1001;
|
||||
|
||||
/**
|
||||
* The requested client description is invalid.
|
||||
*/
|
||||
public const INVALID_CLIENT_DESCRIPTION = 1002;
|
||||
|
||||
/**
|
||||
* The requested client signature verification failed.
|
||||
*/
|
||||
public const SIGNATURE_VERIFICATION_FAILED = 1003;
|
||||
|
||||
/**
|
||||
* The requested client is disabled.
|
||||
*/
|
||||
public const CLIENT_DISABLED = 1004;
|
||||
|
||||
|
||||
/**
|
||||
* The given peer metadata is invalid or malformed.
|
||||
*/
|
||||
public const INVALID_PEER_METADATA = 2000;
|
||||
|
||||
public const PEER_METADATA_NOT_FOUND = 2001;
|
||||
|
||||
public const INVALID_FEDERATED_ADDRESS = 2002;
|
||||
|
||||
public const INVALID_PEER_ASSOCIATION_TYPE = 2003;
|
||||
|
||||
public const INVALID_DATA = 2004;
|
||||
|
||||
public const BAD_REQUEST = 2005;
|
||||
|
||||
|
||||
public const ALL = [
|
||||
self::INTERNAL_SERVER_ERROR,
|
||||
self::ACCESS_DENIED,
|
||||
|
||||
self::CLIENT_NOT_FOUND,
|
||||
self::INVALID_CLIENT_NAME,
|
||||
self::INVALID_CLIENT_DESCRIPTION,
|
||||
self::SIGNATURE_VERIFICATION_FAILED,
|
||||
self::CLIENT_DISABLED,
|
||||
|
||||
self::INVALID_PEER_METADATA,
|
||||
self::PEER_METADATA_NOT_FOUND,
|
||||
self::INVALID_FEDERATED_ADDRESS,
|
||||
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.
|
||||
}
|
116
src/FederationLib/Enums/Standard/EventCode.php
Normal file
116
src/FederationLib/Enums/Standard/EventCode.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class EventCode
|
||||
{
|
||||
/**
|
||||
* A generic event code indicates the event is not specific to a particular event type.
|
||||
* This could apply to server events such as cleaning up old data or general system
|
||||
* messages.
|
||||
*/
|
||||
public const GENERIC = 'GENERIC';
|
||||
|
||||
/**
|
||||
* Indicates a client was created.
|
||||
*/
|
||||
public const CLIENT_CREATED = 'CLIENT_CREATED';
|
||||
|
||||
/**
|
||||
* Indicates a client was deleted.
|
||||
*/
|
||||
public const CLIENT_DELETED = 'CLIENT_DELETED';
|
||||
|
||||
/**
|
||||
* Indicates a client was enabled.
|
||||
*/
|
||||
public const CLIENT_ENABLED = 'CLIENT_ENABLED';
|
||||
|
||||
/**
|
||||
* Indicates a client was disabled.
|
||||
*/
|
||||
public const CLIENT_DISABLED = 'CLIENT_DISABLED';
|
||||
|
||||
/**
|
||||
* Indicates a client's authentication was enabled.
|
||||
*/
|
||||
public const CLIENT_AUTHENTICATION_ENABLED = 'CLIENT_AUTHENTICATION_ENABLED';
|
||||
|
||||
/**
|
||||
* Indicates a client's authentication was disabled.
|
||||
*/
|
||||
public const CLIENT_AUTHENTICATION_DISABLED = 'CLIENT_AUTHENTICATION_DISABLED';
|
||||
|
||||
/**
|
||||
* Indicates a client's query document was rejected
|
||||
* (e.g. the query document couldn't be processed by the server, see the message for details).
|
||||
*/
|
||||
public const QUERY_DOCUMENT_REJECTED = 'QUERY_DOCUMENT_REJECTED';
|
||||
|
||||
/**
|
||||
* Indicates an anomaly was detected in INCOMING events
|
||||
* (e.g. a client is receiving too many requests/messages).
|
||||
*/
|
||||
public const ANOMALY_INCOMING = 'ANOMALY_INCOMING';
|
||||
|
||||
/**
|
||||
* Indicates an anomaly was detected in OUTGOING events
|
||||
* (e.g. a client is sending too many requests/messages).
|
||||
*/
|
||||
public const ANOMALY_OUTGOING = 'ANOMALY_OUTGOING';
|
||||
|
||||
/**
|
||||
* Indicates an anomaly was detected in PEER JOIN events
|
||||
* (e.g. a channel is receiving too many join requests/events).
|
||||
*/
|
||||
public const ANOMALY_PEER_JOIN = 'ANOMALY_PEER_JOIN';
|
||||
|
||||
/**
|
||||
* Indicates a service cleanup event.
|
||||
* This is a special event code that is not intended to be used by clients.
|
||||
* (e.g. the server is cleaning up old data, results are explained in the message).
|
||||
*/
|
||||
public const SERVICE_CLEANUP = 'SERVICE_CLEANUP';
|
||||
|
||||
/**
|
||||
* Indicates a service message event.
|
||||
* This is a special event code that is not intended to be used by clients.
|
||||
* (e.g. a message from the server).
|
||||
*/
|
||||
public const SERVICE_MESSAGE = 'SERVICE_MESSAGE';
|
||||
|
||||
/**
|
||||
* Indicates a service error event.
|
||||
* This is a special event code that is not intended to be used by clients.
|
||||
* (e.g. an error occurred on the server, results are explained in the message).
|
||||
*/
|
||||
public const SERVICE_ERROR = 'SERVICE_ERROR';
|
||||
|
||||
/**
|
||||
* Indicates a peer was restricted by the server.
|
||||
* This is a special event code that is not intended to be used by clients.
|
||||
* (e.g. a peer was restricted by the server, results are explained in the message).
|
||||
*/
|
||||
public const PEER_RESTRICTED = 'PEER_RESTRICTED';
|
||||
|
||||
/**
|
||||
* An array of all event codes.
|
||||
*/
|
||||
public const ALL = [
|
||||
self::GENERIC,
|
||||
self::CLIENT_CREATED,
|
||||
self::CLIENT_DELETED,
|
||||
self::CLIENT_ENABLED,
|
||||
self::CLIENT_DISABLED,
|
||||
self::CLIENT_AUTHENTICATION_ENABLED,
|
||||
self::CLIENT_AUTHENTICATION_DISABLED,
|
||||
self::QUERY_DOCUMENT_REJECTED,
|
||||
self::ANOMALY_INCOMING,
|
||||
self::ANOMALY_OUTGOING,
|
||||
self::ANOMALY_PEER_JOIN,
|
||||
self::SERVICE_CLEANUP,
|
||||
self::SERVICE_MESSAGE,
|
||||
self::SERVICE_ERROR,
|
||||
self::PEER_RESTRICTED
|
||||
];
|
||||
}
|
16
src/FederationLib/Enums/Standard/EventType.php
Normal file
16
src/FederationLib/Enums/Standard/EventType.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class EventType
|
||||
{
|
||||
public const INFORMATION = 'INF';
|
||||
public const WARNING = 'WRN';
|
||||
public const ERROR = 'ERR';
|
||||
|
||||
public const ALL = [
|
||||
self::INFORMATION,
|
||||
self::WARNING,
|
||||
self::ERROR
|
||||
];
|
||||
}
|
12
src/FederationLib/Enums/Standard/InternetPeerType.php
Normal file
12
src/FederationLib/Enums/Standard/InternetPeerType.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class InternetPeerType
|
||||
{
|
||||
public const TELEGRAM_CHAT = 'telegram.chat';
|
||||
|
||||
public const ALL = [
|
||||
self::TELEGRAM_CHAT,
|
||||
];
|
||||
}
|
27
src/FederationLib/Enums/Standard/Methods.php
Normal file
27
src/FederationLib/Enums/Standard/Methods.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class Methods
|
||||
{
|
||||
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,
|
||||
];
|
||||
}
|
54
src/FederationLib/Enums/Standard/PeerAssociationType.php
Normal file
54
src/FederationLib/Enums/Standard/PeerAssociationType.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class PeerAssociationType
|
||||
{
|
||||
/**
|
||||
* Indicates the parent peer is the owner of the child peer.
|
||||
*/
|
||||
public const OWNER = 'owner';
|
||||
|
||||
/**
|
||||
* Indicates the parent peer is the administrator of the child peer.
|
||||
*/
|
||||
public const ADMIN = 'admin';
|
||||
|
||||
/**
|
||||
* Indicates the parent peer is a moderator of the child peer.
|
||||
*/
|
||||
public const MODERATOR = 'moderator';
|
||||
|
||||
/**
|
||||
* Indicates the parent peer is a member of the child peer.
|
||||
*/
|
||||
public const MEMBER = 'member';
|
||||
|
||||
/**
|
||||
* Indicates the parent peer is banned from the child peer.
|
||||
*/
|
||||
public const BANNED = 'banned';
|
||||
|
||||
/**
|
||||
* Indicates the parent peer is an alternative address for the child peer.
|
||||
*/
|
||||
public const ALTERNATIVE = 'alternative';
|
||||
|
||||
/**
|
||||
* Indicates the parent peer is a subscriber of the child peer.
|
||||
*/
|
||||
public const SUBSCRIBER = 'subscriber';
|
||||
|
||||
/**
|
||||
* An array of all peer association types.
|
||||
*/
|
||||
const ALL = [
|
||||
self::OWNER,
|
||||
self::ADMIN,
|
||||
self::MODERATOR,
|
||||
self::MEMBER,
|
||||
self::BANNED,
|
||||
self::ALTERNATIVE,
|
||||
self::SUBSCRIBER
|
||||
];
|
||||
}
|
18
src/FederationLib/Enums/Standard/PeerType.php
Normal file
18
src/FederationLib/Enums/Standard/PeerType.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class PeerType
|
||||
{
|
||||
public const USER = 'user';
|
||||
|
||||
public const INTERNET = 'internet';
|
||||
|
||||
public const UNKNOWN = 'unknown';
|
||||
|
||||
public const ALL = [
|
||||
self::USER,
|
||||
self::INTERNET,
|
||||
self::UNKNOWN
|
||||
];
|
||||
}
|
22
src/FederationLib/Enums/Standard/PermissionRole.php
Normal file
22
src/FederationLib/Enums/Standard/PermissionRole.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class PermissionRole
|
||||
{
|
||||
public const ROOT = 0;
|
||||
public const ADMIN = 1;
|
||||
public const OPERATOR = 2;
|
||||
public const AGENT = 3;
|
||||
public const CLIENT = 4;
|
||||
public const GUEST = 5;
|
||||
|
||||
public const ALL = [
|
||||
self::ROOT => 'root',
|
||||
self::ADMIN => 'admin',
|
||||
self::OPERATOR => 'operator',
|
||||
self::AGENT => 'agent',
|
||||
self::CLIENT => 'client',
|
||||
self::GUEST => 'guest'
|
||||
];
|
||||
}
|
76
src/FederationLib/Enums/Standard/ReportType.php
Normal file
76
src/FederationLib/Enums/Standard/ReportType.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class ReportType
|
||||
{
|
||||
/**
|
||||
* This category can be used for general reports submitted by users regarding any issues they encounter on the platform.
|
||||
*/
|
||||
public const USER_REPORT = 'user_report';
|
||||
|
||||
/**
|
||||
* This category can be used when reports are received from external sources, such as third-party organizations or security researchers.
|
||||
*/
|
||||
public const EXTERNAL_REPORT = 'external_report';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting unsolicited and unwanted commercial or promotional content.
|
||||
*/
|
||||
public const SPAM = 'spam';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting fraudulent activities, schemes, or attempts to deceive users for personal gain.
|
||||
*/
|
||||
public const SCAM = 'scam';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting attempts to acquire sensitive user information, such as passwords or financial details, through deceptive means.
|
||||
*/
|
||||
public const PHISHING = 'phishing';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting the presence or distribution of malicious software or code.
|
||||
*/
|
||||
public const MALWARE = 'malware';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting instances of unauthorized distribution or infringement of copyrighted material.
|
||||
*/
|
||||
public const PIRACY = 'piracy';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting the presence or activities of botnets, which are networks of compromised computers used for malicious purposes.
|
||||
*/
|
||||
public const BOTNET = 'botnet';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting the presence of explicit adult content that violates the platform's guidelines or policies.
|
||||
*/
|
||||
public const PORNOGRAPHY = 'pornography';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting the presence of graphic and violent content that may be disturbing or offensive.
|
||||
*/
|
||||
public const GORE = 'gore';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting any abuse or misuse of the platform's services or features.
|
||||
*/
|
||||
public const SERVICE_ABUSE = 'service_abuse';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting instances of child abuse, child pornography, or any content that exploits minors.
|
||||
*/
|
||||
public const CHILD_EXPLOITATION = 'child_exploitation';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting fraudulent activities conducted online, such as scams, fake websites, or misleading online marketplaces.
|
||||
*/
|
||||
public const FRAUD = 'fraud';
|
||||
|
||||
/**
|
||||
* This category can be used for any reports that do not fit into the predefined categories but still require attention.
|
||||
*/
|
||||
public const OTHER = 'other';
|
||||
}
|
12
src/FederationLib/Enums/Standard/UserPeerType.php
Normal file
12
src/FederationLib/Enums/Standard/UserPeerType.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class UserPeerType
|
||||
{
|
||||
public const TELEGRAM_USER = 'telegram.user';
|
||||
|
||||
public const ALL = [
|
||||
self::TELEGRAM_USER,
|
||||
];
|
||||
}
|
19
src/FederationLib/Exceptions/CacheConnectionException.php
Normal file
19
src/FederationLib/Exceptions/CacheConnectionException.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class CacheConnectionException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
20
src/FederationLib/Exceptions/CacheDriverException.php
Normal file
20
src/FederationLib/Exceptions/CacheDriverException.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
|
||||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class CacheDriverException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $previous);
|
||||
}
|
||||
}
|
26
src/FederationLib/Exceptions/DatabaseException.php
Normal file
26
src/FederationLib/Exceptions/DatabaseException.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class DatabaseException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
* @noinspection SenselessProxyMethodInspection
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
if (!empty($previous))
|
||||
{
|
||||
parent::__construct($message, $previous->getCode(), $previous);
|
||||
}
|
||||
else
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class PeerAssociationNotFoundException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
20
src/FederationLib/Exceptions/RedisException.php
Normal file
20
src/FederationLib/Exceptions/RedisException.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class RedisException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @param Throwable|null $previous
|
||||
* @noinspection SenselessProxyMethodInspection
|
||||
*/
|
||||
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Enums\Standard\ErrorCodes;
|
||||
use Throwable;
|
||||
|
||||
class AccessDeniedException extends Exception
|
||||
{
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, ErrorCodes::ACCESS_DENIED, $previous);
|
||||
}
|
||||
}
|
|
@ -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 ClientNotFoundException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, ErrorCodes::CLIENT_NOT_FOUND, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Enums\Standard\ErrorCodes;
|
||||
use Throwable;
|
||||
|
||||
class InternalServerException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, ErrorCodes::INTERNAL_SERVER_ERROR, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Enums\Standard\ErrorCodes;
|
||||
use Throwable;
|
||||
|
||||
class InvalidClientDescriptionException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, ErrorCodes::INVALID_CLIENT_DESCRIPTION, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Enums\Standard\ErrorCodes;
|
||||
use Throwable;
|
||||
|
||||
class InvalidClientNameException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, ErrorCodes::INVALID_CLIENT_NAME, $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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Enums\Standard\ErrorCodes;
|
||||
use Throwable;
|
||||
|
||||
class InvalidFederatedAddressException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, ErrorCodes::INVALID_FEDERATED_ADDRESS, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Enums\Standard\ErrorCodes;
|
||||
use Throwable;
|
||||
|
||||
class InvalidPeerAssociationTypeException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, ErrorCodes::INVALID_PEER_ASSOCIATION_TYPE, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Enums\Standard\ErrorCodes;
|
||||
use Throwable;
|
||||
|
||||
class InvalidPeerMetadataException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, ErrorCodes::INVALID_PEER_METADATA, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class InvalidPermissionRoleException extends Exception
|
||||
{
|
||||
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, 0, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
//TODO: This is more of a internal error
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Enums\Standard\ErrorCodes;
|
||||
use Throwable;
|
||||
|
||||
class PeerMetadataNotFoundException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, ErrorCodes::PEER_METADATA_NOT_FOUND, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class PeerNotFoundException extends Exception
|
||||
{
|
||||
/**
|
||||
* PeerNotFoundException constructor.
|
||||
*
|
||||
* @param string $peer
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $peer, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct("The peer '{$peer}' was not found", 0, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class UnsupportedPeerType extends Exception
|
||||
{
|
||||
/**
|
||||
* UnsupportedPeerType constructor.
|
||||
*
|
||||
* @param string $peer_type
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $peer_type, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct("The peer type '{$peer_type}' is not supported by this server", 0, $previous);
|
||||
}
|
||||
}
|
19
src/FederationLib/Exceptions/UnsupportedEntityType.php
Normal file
19
src/FederationLib/Exceptions/UnsupportedEntityType.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class UnsupportedEntityType extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
429
src/FederationLib/FederationLib.php
Normal file → Executable file
429
src/FederationLib/FederationLib.php
Normal file → Executable file
|
@ -1,8 +1,429 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib;
|
||||
namespace FederationLib;
|
||||
|
||||
class FederationLib
|
||||
{
|
||||
use Exception;
|
||||
use FederationLib\Classes\Configuration;
|
||||
use FederationLib\Enums\Standard\ErrorCodes;
|
||||
use FederationLib\Enums\Standard\Methods;
|
||||
use FederationLib\Exceptions\DatabaseException;
|
||||
use FederationLib\Exceptions\Standard\AccessDeniedException;
|
||||
use FederationLib\Exceptions\Standard\ClientNotFoundException;
|
||||
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;
|
||||
|
||||
}
|
||||
class FederationLib
|
||||
{
|
||||
/**
|
||||
* @var ClientManager
|
||||
*/
|
||||
private ClientManager $client_manager;
|
||||
|
||||
/**
|
||||
* @var PeerManager
|
||||
*/
|
||||
private PeerManager $peer_manager;
|
||||
|
||||
/**
|
||||
* @var AssociationManager
|
||||
*/
|
||||
private AssociationManager $association_manager;
|
||||
|
||||
/**
|
||||
* FederationLib constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->client_manager = new ClientManager($this);
|
||||
$this->peer_manager = new PeerManager($this);
|
||||
$this->association_manager = new AssociationManager($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers functions to the TamerLib instance, if applicable
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function registerFunctions(): void
|
||||
{
|
||||
if(tm::getMode() !== TamerMode::WORKER)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->client_manager->registerFunctions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getSubprocessPath(): string
|
||||
{
|
||||
return __DIR__ . DIRECTORY_SEPARATOR . 'subproc';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the permission role from the given identity and attempts to check if the identity has the
|
||||
* required permission to perform the given method
|
||||
*
|
||||
* @param ClientIdentity|null $identity
|
||||
* @throws AccessDeniedException
|
||||
* @throws ClientNotFoundException
|
||||
* @throws InternalServerException
|
||||
* @return ResolvedIdentity
|
||||
*/
|
||||
private function resolveIdentity(?ClientIdentity $identity): ResolvedIdentity
|
||||
{
|
||||
if($identity === null)
|
||||
{
|
||||
return new ResolvedIdentity(null, null, true);
|
||||
}
|
||||
|
||||
$get_client = tm::do('client_getClient', [$identity->getClientUuid()]);
|
||||
$peer = null;
|
||||
|
||||
try
|
||||
{
|
||||
$client = tm::waitFor($get_client);
|
||||
tm::dof('client_updateLastSeen');
|
||||
}
|
||||
catch(ClientNotFoundException $e)
|
||||
{
|
||||
tm::clear();
|
||||
throw new ClientNotFoundException('Invalid client UUID or client does not exist', $e);
|
||||
}
|
||||
catch(Exception|Throwable $e)
|
||||
{
|
||||
tm::clear();
|
||||
throw new InternalServerException('There was an error while trying to access the client', $e);
|
||||
}
|
||||
|
||||
return new ResolvedIdentity($client, $peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given identity has the required permission to perform the given method
|
||||
*
|
||||
* @param string $method
|
||||
* @param ResolvedIdentity $resolved_identity
|
||||
* @return bool
|
||||
*/
|
||||
private function checkPermission(string $method, ResolvedIdentity $resolved_identity): bool
|
||||
{
|
||||
return $resolved_identity->getPermissionRole() <= Configuration::getMethodPermission($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pings the client
|
||||
*
|
||||
* @param ClientIdentity|null $identity
|
||||
* @return bool
|
||||
* @throws AccessDeniedException
|
||||
* @throws ClientNotFoundException
|
||||
* @throws InternalServerException
|
||||
*/
|
||||
public function ping(?ClientIdentity $identity): bool
|
||||
{
|
||||
if(!$this->checkPermission(Methods::PING, $this->resolveIdentity($identity)))
|
||||
{
|
||||
throw new Exceptions\Standard\AccessDeniedException('You do not have permission to perform this action');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClientIdentity|null $identity
|
||||
* @return string
|
||||
* @throws AccessDeniedException
|
||||
* @throws ClientNotFoundException
|
||||
* @throws InternalServerException
|
||||
*/
|
||||
public function whoami(?ClientIdentity $identity): string
|
||||
{
|
||||
$resolved_identity = $this->resolveIdentity($identity);
|
||||
|
||||
if(!$this->checkPermission(Methods::WHOAMI, $resolved_identity))
|
||||
{
|
||||
throw new Exceptions\Standard\AccessDeniedException('You do not have permission to perform this action');
|
||||
}
|
||||
|
||||
if($resolved_identity->getPeer() !== null)
|
||||
{
|
||||
return $resolved_identity->getPeer()->getAddress();
|
||||
}
|
||||
|
||||
if($resolved_identity->getClient() !== null)
|
||||
{
|
||||
return $resolved_identity->getClient()->getUuid();
|
||||
}
|
||||
|
||||
return 'root';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new client into the database
|
||||
*
|
||||
* @param ClientIdentity|null $identity
|
||||
* @param string|null $name
|
||||
* @param string|null $description
|
||||
* @return string
|
||||
* @throws AccessDeniedException
|
||||
* @throws ClientNotFoundException
|
||||
* @throws DatabaseException
|
||||
* @throws InternalServerException
|
||||
* @throws InvalidClientDescriptionException
|
||||
* @throws InvalidClientNameException
|
||||
*/
|
||||
public function createClient(?ClientIdentity $identity, ?string $name=null, ?string $description=null): string
|
||||
{
|
||||
if(!$this->checkPermission(Methods::CREATE_CLIENT, $this->resolveIdentity($identity)))
|
||||
{
|
||||
throw new Exceptions\Standard\AccessDeniedException('You do not have sufficient permission to create a client');
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return $this->client_manager->registerClient($name, $description);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
if(in_array($e->getCode(), ErrorCodes::ALL, true))
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
|
||||
throw new Exceptions\Standard\InternalServerException('There was an error while creating the client', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an existing client from the database
|
||||
*
|
||||
* @param ClientIdentity|null $identity
|
||||
* @param string|ClientRecord $client_uuid
|
||||
* @return Objects\Standard\Client
|
||||
* @throws ClientNotFoundException
|
||||
* @throws DatabaseException
|
||||
* @throws InternalServerException
|
||||
* @throws AccessDeniedException
|
||||
*/
|
||||
public function getClient(?ClientIdentity $identity, string|ClientRecord $client_uuid): Objects\Standard\Client
|
||||
{
|
||||
if(!$this->checkPermission(Methods::GET_CLIENT, $this->resolveIdentity($identity)))
|
||||
{
|
||||
throw new Exceptions\Standard\AccessDeniedException('You do not have sufficient permission to fetch a client from the database');
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Return the standard client object
|
||||
return new Objects\Standard\Client($this->client_manager->getClient($client_uuid));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
if(in_array($e->getCode(), ErrorCodes::ALL, true))
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
|
||||
throw new Exceptions\Standard\InternalServerException('There was an error while getting the client', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the name of an existing client, return True if successful
|
||||
*
|
||||
* @param ClientIdentity|null $identity
|
||||
* @param string $client_uuid
|
||||
* @param string $new_name
|
||||
* @return bool
|
||||
* @throws AccessDeniedException
|
||||
* @throws ClientNotFoundException
|
||||
* @throws DatabaseException
|
||||
* @throws InternalServerException
|
||||
* @throws InvalidClientNameException
|
||||
*/
|
||||
public function updateClientName(?ClientIdentity $identity, string $client_uuid, ?string $new_name): bool
|
||||
{
|
||||
if(!$this->checkPermission(Methods::UPDATE_CLIENT_NAME, $this->resolveIdentity($identity)))
|
||||
{
|
||||
throw new Exceptions\Standard\AccessDeniedException('You do not have sufficient permission to update the name of a client');
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$this->client_manager->changeClientName($client_uuid, $new_name);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
if(in_array($e->getCode(), ErrorCodes::ALL, true))
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
|
||||
throw new Exceptions\Standard\InternalServerException('There was an error while updating the client name', $e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the client's description, return True if successful
|
||||
*
|
||||
* @param ClientIdentity|null $identity
|
||||
* @param string $client_uuid
|
||||
* @param string|null $new_description
|
||||
* @return bool
|
||||
* @throws AccessDeniedException
|
||||
* @throws ClientNotFoundException
|
||||
* @throws DatabaseException // TODO: DatabaseException should not be returning from FederationLib, it's not standard
|
||||
* @throws InternalServerException
|
||||
* @throws InvalidClientDescriptionException
|
||||
*/
|
||||
public function updateClientDescription(?ClientIdentity $identity, string $client_uuid, ?string $new_description): bool
|
||||
{
|
||||
if(!$this->checkPermission(Methods::UPDATE_CLIENT_DESCRIPTION, $this->resolveIdentity($identity)))
|
||||
{
|
||||
throw new Exceptions\Standard\AccessDeniedException('You do not have sufficient permission to update the description of a client');
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$this->client_manager->updateClientDescription($client_uuid, $new_description);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
if(in_array($e->getCode(), ErrorCodes::ALL, true))
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
|
||||
throw new Exceptions\Standard\InternalServerException('There was an error while updating the client description', $e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the client's permission role, return True if successful
|
||||
*
|
||||
* @param ClientIdentity|null $identity
|
||||
* @param string $client_uuid
|
||||
* @param int $new_permission_role
|
||||
* @return bool
|
||||
* @throws AccessDeniedException
|
||||
* @throws ClientNotFoundException
|
||||
* @throws DatabaseException
|
||||
* @throws Exceptions\Standard\InvalidPermissionRoleException
|
||||
* @throws InternalServerException
|
||||
*/
|
||||
public function updateClientPermissionRole(?ClientIdentity $identity, string $client_uuid, int $new_permission_role): bool
|
||||
{
|
||||
if(!$this->checkPermission(Methods::UPDATE_CLIENT_PERMISSION_ROLE, $this->resolveIdentity($identity)))
|
||||
{
|
||||
throw new Exceptions\Standard\AccessDeniedException('You do not have sufficient permission to update the permission role of a client');
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$this->client_manager->updateClientPermissionRole($client_uuid, $new_permission_role);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
if(in_array($e->getCode(), ErrorCodes::ALL, true))
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
|
||||
throw new Exceptions\Standard\InternalServerException('There was an error while updating the client permission role', $e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 PeerUpdate $peer_update
|
||||
* @return bool
|
||||
* @throws AccessDeniedException
|
||||
* @throws ClientNotFoundException
|
||||
* @throws DatabaseException
|
||||
* @throws InvalidFederatedAddressException
|
||||
* @throws InvalidPeerAssociationTypeException
|
||||
* @throws InvalidPeerMetadataException
|
||||
* @throws UnsupportedPeerType
|
||||
* @throws InternalServerException
|
||||
*/
|
||||
public function syncPeer(?ClientIdentity $identity, PeerUpdate $peer_update): bool
|
||||
{
|
||||
if(!$this->checkPermission(Methods::SYNC_PEER, $this->resolveIdentity($identity)))
|
||||
{
|
||||
throw new Exceptions\Standard\AccessDeniedException('You do not have sufficient permission to sync a peer');
|
||||
}
|
||||
|
||||
if($identity === null)
|
||||
{
|
||||
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(), $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;
|
||||
}
|
||||
|
||||
}
|
92
src/FederationLib/Interfaces/PeerMetadataInterface.php
Normal file
92
src/FederationLib/Interfaces/PeerMetadataInterface.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Interfaces;
|
||||
|
||||
use FederationLib\Exceptions\Standard\InvalidPeerMetadataException;
|
||||
use FederationLib\Objects\ParsedFederatedAddress;
|
||||
|
||||
interface PeerMetadataInterface extends SerializableObjectInterface
|
||||
{
|
||||
/**
|
||||
* Validates if the metadata is valid or not
|
||||
* throws InvalidPeerMetadataException if the metadata is invalid
|
||||
*
|
||||
* @throws InvalidPeerMetadataException
|
||||
*/
|
||||
public function validate(): void;
|
||||
|
||||
/**
|
||||
* Returns True if the metadata indicates that the peer is a bot
|
||||
* Returns False if there's no indication that the peer is a bot
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAutomated(): bool;
|
||||
|
||||
/**
|
||||
* Returns the platform-specific ID of the peer (e.g. Telegram Chat ID)
|
||||
* If no ID is available, the method will try to find a unique ID in the metadata
|
||||
* that can uniquely identify the peer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPlatformId(): string;
|
||||
|
||||
/**
|
||||
* Returns the calculated standard federated address of the peer
|
||||
*
|
||||
* @return ParsedFederatedAddress
|
||||
*/
|
||||
public function getFederatedAddress(): ParsedFederatedAddress;
|
||||
|
||||
/**
|
||||
* Sets the timestamp of when the peer record was last updated (this is usually used internally)
|
||||
*
|
||||
* @param int|null $timestamp
|
||||
* @return void
|
||||
*/
|
||||
public function setLastUpdatedTimestamp(?int $timestamp): void;
|
||||
|
||||
/**
|
||||
* Returns the timestamp of when the peer record was last updated
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getLastUpdatedTimestamp(): ?int;
|
||||
|
||||
/**
|
||||
* Sets the Client's UUID that last updated the peer record (this is usually used internally)
|
||||
*
|
||||
* @param string|null $client_uuid
|
||||
* @return void
|
||||
*/
|
||||
public function setUpdatedClient(?string $client_uuid): void;
|
||||
|
||||
/**
|
||||
* Returns the Client's UUID that last updated the peer record
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getUpdatedClient(): ?string;
|
||||
|
||||
/**
|
||||
* Returns an array representation of the object, if $raw is true, the array will contain
|
||||
* additional information that is not part of the standard metadata such as the client UUID
|
||||
* and the last updated timestamp
|
||||
*
|
||||
* @param bool $raw
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(bool $raw=false): array;
|
||||
|
||||
/**
|
||||
* Constructs object from an array representation, if $raw is enabled it will accept
|
||||
* raw data that is not part of the standard metadata, this should be disabled if
|
||||
* the data is coming from an untrusted source such as a user
|
||||
*
|
||||
* @param array $array
|
||||
* @param bool $raw
|
||||
* @return PeerMetadataInterface
|
||||
*/
|
||||
public static function fromArray(array $array, bool $raw=false): PeerMetadataInterface;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Interfaces;
|
||||
|
||||
use FederationLib\Objects\ClientRecord;
|
||||
use FederationLib\Objects\ParsedFederatedAddress;
|
||||
use FederationLib\Objects\Standard\PeerMetadata\TelegramUserMetadata;
|
||||
|
||||
interface PeerMetadataManagerInterface
|
||||
{
|
||||
/**
|
||||
* Intelligently syncs the metadata of the given federated address, this will ignore raw metadata & use
|
||||
* data from the given $client_uuid
|
||||
*
|
||||
* @param ClientRecord|string $client_uuid
|
||||
* @param PeerMetadataInterface $peer_metadata
|
||||
* @return string
|
||||
*/
|
||||
public function syncMetadata(ClientRecord|string $client_uuid, PeerMetadataInterface $peer_metadata): string;
|
||||
|
||||
/**
|
||||
* Registers the metadata of the given federated address, this will ignore raw metadata & use data from the
|
||||
* given $client_uuid
|
||||
*
|
||||
* @param ClientRecord|string $client_uuid
|
||||
* @param PeerMetadataInterface $peer_metadata
|
||||
* @return string
|
||||
*/
|
||||
public function registerMetadata(ClientRecord|string $client_uuid, PeerMetadataInterface $peer_metadata): string;
|
||||
|
||||
/**
|
||||
* Returns the metadata of the given federated address, this returns the full raw metadata
|
||||
*
|
||||
* @param ParsedFederatedAddress|string $federated_address
|
||||
* @return PeerMetadataInterface
|
||||
*/
|
||||
public function getMetadata(ParsedFederatedAddress|string $federated_address): PeerMetadataInterface;
|
||||
}
|
21
src/FederationLib/Interfaces/SerializableObjectInterface.php
Normal file
21
src/FederationLib/Interfaces/SerializableObjectInterface.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Interfaces;
|
||||
|
||||
interface SerializableObjectInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array representation of the object.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array;
|
||||
|
||||
/**
|
||||
* Constructs object from an array representation.
|
||||
*
|
||||
* @param array $array
|
||||
* @return SerializableObjectInterface
|
||||
*/
|
||||
public static function fromArray(array $array): SerializableObjectInterface;
|
||||
}
|
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
|
||||
}
|
307
src/FederationLib/Managers/AssociationManager.php
Normal file
307
src/FederationLib/Managers/AssociationManager.php
Normal file
|
@ -0,0 +1,307 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Managers;
|
||||
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Exception;
|
||||
use FederationLib\Classes\Configuration;
|
||||
use FederationLib\Classes\Database;
|
||||
use FederationLib\Classes\Validate;
|
||||
use FederationLib\Enums\DatabaseTables;
|
||||
use FederationLib\Enums\Misc;
|
||||
use FederationLib\Exceptions\DatabaseException;
|
||||
use FederationLib\Exceptions\PeerAssociationNotFoundException;
|
||||
use FederationLib\Exceptions\Standard\InvalidPeerAssociationTypeException;
|
||||
use FederationLib\FederationLib;
|
||||
use FederationLib\Objects\ClientRecord;
|
||||
use FederationLib\Objects\ParsedFederatedAddress;
|
||||
use FederationLib\Objects\PeerAssociationRecord;
|
||||
use LogLib\Log;
|
||||
|
||||
class AssociationManager
|
||||
{
|
||||
/**
|
||||
* @var FederationLib
|
||||
*/
|
||||
private FederationLib $federationLib;
|
||||
|
||||
/**
|
||||
* AssociationManager constructor.
|
||||
*
|
||||
* @param FederationLib $federationLib
|
||||
*/
|
||||
public function __construct(FederationLib $federationLib)
|
||||
{
|
||||
$this->federationLib = $federationLib;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a child peer with a parent peer.
|
||||
*
|
||||
* @param ClientRecord|string $client_uuid
|
||||
* @param ParsedFederatedAddress|string $parent
|
||||
* @param ParsedFederatedAddress|string $child
|
||||
* @param string $type
|
||||
* @return void
|
||||
* @throws DatabaseException
|
||||
* @throws InvalidPeerAssociationTypeException
|
||||
*/
|
||||
public function associate(ClientRecord|string $client_uuid, ParsedFederatedAddress|string $child, ParsedFederatedAddress|string $parent, string $type): void
|
||||
{
|
||||
if(!Validate::peerAssociationType($type))
|
||||
{
|
||||
throw new InvalidPeerAssociationTypeException(sprintf('Invalid peer association type: %s', $type));
|
||||
}
|
||||
|
||||
if($client_uuid instanceof ClientRecord)
|
||||
{
|
||||
$client_uuid = $client_uuid->getUuid();
|
||||
}
|
||||
|
||||
if(is_string($parent))
|
||||
{
|
||||
$parent = new ParsedFederatedAddress($parent);
|
||||
}
|
||||
|
||||
if(is_string($child))
|
||||
{
|
||||
$child = new ParsedFederatedAddress($child);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Try updating the association if it already exists, but only if the association type is different
|
||||
$peer_association = $this->getAssociation($parent, $child);
|
||||
if($peer_association->getAssociationType() === $type)
|
||||
{
|
||||
return;
|
||||
}
|
||||
$this->updateAssociation($client_uuid, $parent, $child, $type);
|
||||
return;
|
||||
}
|
||||
catch(PeerAssociationNotFoundException $e)
|
||||
{
|
||||
// This is fine, we'll just create a new association
|
||||
unset($e);
|
||||
}
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
|
||||
$qb->insert(DatabaseTables::PEERS_ASSOCIATIONS);
|
||||
$qb->setValue('child_peer', $qb->createNamedParameter($child->getAddress()));
|
||||
$qb->setValue('parent_peer', $qb->createNamedParameter($parent->getAddress()));
|
||||
$qb->setValue('association_type', $qb->createNamedParameter($type));
|
||||
$qb->setValue('client_uuid', $qb->createNamedParameter($client_uuid));
|
||||
$qb->setValue('timestamp', $qb->createNamedParameter(time(), ParameterType::INTEGER));
|
||||
|
||||
try
|
||||
{
|
||||
$qb->executeStatement();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to register peer association: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
Log::info(Misc::FEDERATIONLIB, sprintf('Associated %s with %s as %s', $child->getAddress(), $parent->getAddress(), $type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the association record for the child peer and parent peer.
|
||||
*
|
||||
* @param ParsedFederatedAddress|string $parent
|
||||
* @param ParsedFederatedAddress|string $child
|
||||
* @return PeerAssociationRecord
|
||||
* @throws DatabaseException
|
||||
* @throws PeerAssociationNotFoundException
|
||||
*/
|
||||
public function getAssociation(ParsedFederatedAddress|string $parent, ParsedFederatedAddress|string $child): PeerAssociationRecord
|
||||
{
|
||||
if(is_string($parent))
|
||||
{
|
||||
$parent = new ParsedFederatedAddress($parent);
|
||||
}
|
||||
|
||||
if(is_string($child))
|
||||
{
|
||||
$child = new ParsedFederatedAddress($child);
|
||||
}
|
||||
|
||||
$cache_key = sprintf('peer_association_%s_%s', $parent->getAddress(), $child->getAddress());
|
||||
if(Configuration::isCacheSystemEnabled() && Configuration::getObjectCacheEnabled('peer_association_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('peer_association_objects');
|
||||
|
||||
if($redis->exists($cache_key))
|
||||
{
|
||||
$peer_association = PeerAssociationRecord::fromArray($redis->hGetAll($cache_key));
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Loaded peer association object %s from cache', $cache_key));
|
||||
return $peer_association;
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to load peer association %s from cache: %s', $cache_key, $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->select(
|
||||
'child_peer',
|
||||
'parent_peer',
|
||||
'association_type',
|
||||
'client_uuid',
|
||||
'timestamp'
|
||||
);
|
||||
|
||||
$qb->from(DatabaseTables::PEERS_ASSOCIATIONS);
|
||||
$qb->where('child_peer = :child_peer');
|
||||
$qb->andWhere('parent_peer = :parent_peer');
|
||||
$qb->setParameter('child_peer', $child->getAddress());
|
||||
$qb->setParameter('parent_peer', $parent->getAddress());
|
||||
$qb->setMaxResults(1);
|
||||
|
||||
try
|
||||
{
|
||||
$result = $qb->executeQuery();
|
||||
|
||||
if($result->rowCount() === 0)
|
||||
{
|
||||
throw new PeerAssociationNotFoundException(sprintf('Peer association not found: %s -> %s', $child->getAddress(), $parent->getAddress()));
|
||||
}
|
||||
|
||||
$peer_association = PeerAssociationRecord::fromArray($result->fetchAssociative());
|
||||
}
|
||||
catch(PeerAssociationNotFoundException $e)
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to get peer association: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
if(Configuration::isCacheSystemEnabled() && Configuration::getObjectCacheEnabled('peer_association_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
if(!isset($redis))
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('peer_association_objects');
|
||||
}
|
||||
|
||||
$redis->hMSet($cache_key, $peer_association->toArray());
|
||||
|
||||
if(Configuration::getObjectCacheTtl('peer_association_objects') > 0)
|
||||
{
|
||||
$redis->expire($cache_key, Configuration::getObjectCacheTtl('peer_association_objects'));
|
||||
}
|
||||
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Cached peer association object %s', $cache_key));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to cache peer association %s: %s', $cache_key, $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
return $peer_association;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the association record for the child peer and parent peer.
|
||||
* If the type remains the same, nothing will be done.
|
||||
*
|
||||
* @param ClientRecord|string $client_uuid
|
||||
* @param ParsedFederatedAddress|string $parent
|
||||
* @param ParsedFederatedAddress|string $child
|
||||
* @param string $type
|
||||
* @return void
|
||||
* @throws DatabaseException
|
||||
* @throws InvalidPeerAssociationTypeException
|
||||
* @throws PeerAssociationNotFoundException
|
||||
*/
|
||||
public function updateAssociation(ClientRecord|string $client_uuid, ParsedFederatedAddress|string $parent, ParsedFederatedAddress|string $child, string $type): void
|
||||
{
|
||||
if(!Validate::peerAssociationType($type))
|
||||
{
|
||||
throw new InvalidPeerAssociationTypeException(sprintf('Invalid peer association type: %s', $type));
|
||||
}
|
||||
|
||||
if($client_uuid instanceof ClientRecord)
|
||||
{
|
||||
$client_uuid = $client_uuid->getUuid();
|
||||
}
|
||||
|
||||
if(is_string($parent))
|
||||
{
|
||||
$parent = new ParsedFederatedAddress($parent);
|
||||
}
|
||||
|
||||
if(is_string($child))
|
||||
{
|
||||
$child = new ParsedFederatedAddress($child);
|
||||
}
|
||||
|
||||
$peer_association = $this->getAssociation($parent, $child);
|
||||
|
||||
if($peer_association->getAssociationType() === $type)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
|
||||
$qb->update(DatabaseTables::PEERS_ASSOCIATIONS);
|
||||
$qb->set('association_type', $qb->createNamedParameter($type));
|
||||
$qb->set('client_uuid', $qb->createNamedParameter($client_uuid));
|
||||
$qb->set('timestamp', $qb->createNamedParameter(time(), ParameterType::INTEGER));
|
||||
$qb->where('child_peer = :child_peer');
|
||||
$qb->andWhere('parent_peer = :parent_peer');
|
||||
$qb->setParameter('child_peer', $child->getAddress());
|
||||
$qb->setParameter('parent_peer', $parent->getAddress());
|
||||
|
||||
try
|
||||
{
|
||||
$qb->executeStatement();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to update peer association: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
$cache_key = sprintf('peer_association_%s_%s', $parent->getAddress(), $child->getAddress());
|
||||
|
||||
if(Configuration::isCacheSystemEnabled() && Configuration::getObjectCacheEnabled('peer_association_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('peer_association_objects');
|
||||
|
||||
if($redis->exists($cache_key))
|
||||
{
|
||||
$redis->hMSet($cache_key, [
|
||||
'association_type' => $type,
|
||||
'client_uuid' => $client_uuid,
|
||||
'timestamp' => time()
|
||||
]);
|
||||
|
||||
if(Configuration::getObjectCacheTtl('peer_association_objects') > 0)
|
||||
{
|
||||
$redis->expire($cache_key, Configuration::getObjectCacheTtl('peer_association_objects'));
|
||||
}
|
||||
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Updated cached peer association object %s', $cache_key));
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to update cached peer association %s: %s', $cache_key, $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Updated peer association %s -> %s: %s', $child->getAddress(), $parent->getAddress(), $type));
|
||||
}
|
||||
}
|
685
src/FederationLib/Managers/ClientManager.php
Normal file
685
src/FederationLib/Managers/ClientManager.php
Normal file
|
@ -0,0 +1,685 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Managers;
|
||||
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Exception;
|
||||
use FederationLib\Classes\Configuration;
|
||||
use FederationLib\Classes\Database;
|
||||
use FederationLib\Classes\Security;
|
||||
use FederationLib\Classes\Utilities;
|
||||
use FederationLib\Classes\Validate;
|
||||
use FederationLib\Enums\DatabaseTables;
|
||||
use FederationLib\Enums\FilterOrder;
|
||||
use FederationLib\Enums\Filters\ListClientsFilter;
|
||||
use FederationLib\Enums\Misc;
|
||||
use FederationLib\Exceptions\DatabaseException;
|
||||
use FederationLib\Exceptions\Standard\ClientNotFoundException;
|
||||
use FederationLib\Exceptions\Standard\InvalidClientDescriptionException;
|
||||
use FederationLib\Exceptions\Standard\InvalidClientNameException;
|
||||
use FederationLib\Exceptions\Standard\InvalidPermissionRoleException;
|
||||
use FederationLib\FederationLib;
|
||||
use FederationLib\Objects\ClientRecord;
|
||||
use LogLib\Log;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use TamerLib\Enums\TamerMode;
|
||||
use TamerLib\tm;
|
||||
|
||||
class ClientManager
|
||||
{
|
||||
/**
|
||||
* @var FederationLib
|
||||
*/
|
||||
private FederationLib $federationLib;
|
||||
|
||||
/**
|
||||
* ClientManager constructor.
|
||||
*
|
||||
* @param FederationLib $federationLib
|
||||
*/
|
||||
public function __construct(FederationLib $federationLib)
|
||||
{
|
||||
$this->federationLib = $federationLib;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers functions to the TamerLib instance, if applicable
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function registerFunctions(): void
|
||||
{
|
||||
if(tm::getMode() !== TamerMode::WORKER)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tm::addFunction('client_registerClient', [$this, 'registerClient']);
|
||||
tm::addFunction('client_getClient', [$this, 'getClient']);
|
||||
tm::addFunction('client_changeClientName', [$this, 'changeClientName']);
|
||||
tm::addFunction('client_changeClientDescription', [$this, 'updateClientDescription']);
|
||||
tm::addFunction('client_changeClientPermissionRole', [$this, 'updateClientPermissionRole']);
|
||||
tm::addFunction('client_updateLastSeen', [$this, 'updateLastSeen']);
|
||||
tm::addFunction('client_listClients', [$this, 'listClients']);
|
||||
tm::addFunction('client_getTotalClients', [$this, 'getTotalClients']);
|
||||
tm::addFunction('client_getTotalPages', [$this, 'getTotalPages']);
|
||||
tm::addFunction('client_deleteClient', [$this, 'deleteClient']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a client into the database, returns the UUID that was generated for the client.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @param string|null $description
|
||||
* @return string
|
||||
* @throws DatabaseException
|
||||
* @throws InvalidClientDescriptionException
|
||||
* @throws InvalidClientNameException
|
||||
*/
|
||||
public function registerClient(?string $name=null, ?string $description=null): string
|
||||
{
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->insert(DatabaseTables::CLIENTS);
|
||||
$uuid = Uuid::v4()->toRfc4122();
|
||||
|
||||
if($name === null)
|
||||
{
|
||||
$name = Utilities::generateName(4);
|
||||
}
|
||||
|
||||
if(!Validate::clientName($name))
|
||||
{
|
||||
throw new InvalidClientNameException(sprintf('Invalid client name: %s', $name));
|
||||
}
|
||||
|
||||
if($description !== null && strlen($description) > 128)
|
||||
{
|
||||
throw new InvalidClientDescriptionException(sprintf('Invalid client description: %s', $description));
|
||||
}
|
||||
|
||||
$qb->setValue('uuid', $qb->createNamedParameter($uuid));
|
||||
$qb->setValue('name', $qb->createNamedParameter($name));
|
||||
$qb->setValue('description', $qb->createNamedParameter($description, (is_null($description) ? ParameterType::NULL : ParameterType::STRING)));
|
||||
$qb->setValue('secret_totp', $qb->createNamedParameter(Security::generateSecret()));
|
||||
$qb->setValue('flags', $qb->createNamedParameter(null, ParameterType::NULL));
|
||||
|
||||
try
|
||||
{
|
||||
$qb->executeStatement();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to register client: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
Log::info(Misc::FEDERATIONLIB, sprintf('Registered client with UUID %s', $uuid));
|
||||
return $uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an existing client from the database.
|
||||
*
|
||||
* @param string|ClientRecord $client_uuid
|
||||
* @return ClientRecord
|
||||
* @throws ClientNotFoundException
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function getClient(string|ClientRecord $client_uuid): ClientRecord
|
||||
{
|
||||
if($client_uuid instanceof ClientRecord)
|
||||
{
|
||||
$client_uuid = $client_uuid->getUuid();
|
||||
}
|
||||
|
||||
// Use the cache first if it's enabled
|
||||
if(Configuration::isCacheSystemEnabled() && Configuration::getObjectCacheEnabled('client_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('client_objects');
|
||||
|
||||
if($redis->exists($client_uuid))
|
||||
{
|
||||
$client = ClientRecord::fromArray($redis->hGetAll($client_uuid));
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Loaded client object %s from cache', $client_uuid));
|
||||
return $client;
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to load client object %s from cache: %s', $client_uuid, $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->select('*');
|
||||
$qb->from(DatabaseTables::CLIENTS);
|
||||
$qb->where('uuid = :uuid');
|
||||
$qb->setParameter('uuid', $client_uuid);
|
||||
$qb->setMaxResults(1);
|
||||
|
||||
try
|
||||
{
|
||||
$result = $qb->executeQuery();
|
||||
|
||||
if($result->rowCount() === 0)
|
||||
{
|
||||
throw new ClientNotFoundException($client_uuid);
|
||||
}
|
||||
|
||||
$client = ClientRecord::fromArray($result->fetchAssociative());
|
||||
}
|
||||
catch(ClientNotFoundException $e)
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to get Client: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
// Store the record in the cache if caching is enabled.
|
||||
if(Configuration::isCacheSystemEnabled() && Configuration::getObjectCacheEnabled('client_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
if(!isset($redis))
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('client_objects');
|
||||
}
|
||||
|
||||
$redis->hMSet($client->getUuid(), $client->toArray());
|
||||
if(Configuration::getObjectCacheTTL('client_objects') > 0)
|
||||
{
|
||||
$redis->expire($client->getUuid(), Configuration::getObjectCacheTTL('client_objects'));
|
||||
}
|
||||
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Cached client object %s', $client->getUuid()));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to cache client object %s: %s', $client->getUuid(), $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the name of a client.
|
||||
*
|
||||
* @param string|ClientRecord $client_uuid
|
||||
* @param string|null $name
|
||||
* @return void
|
||||
* @throws ClientNotFoundException
|
||||
* @throws DatabaseException
|
||||
* @throws InvalidClientNameException
|
||||
*/
|
||||
public function changeClientName(string|ClientRecord $client_uuid, ?string $name=null): void
|
||||
{
|
||||
if($client_uuid instanceof ClientRecord)
|
||||
{
|
||||
$client_uuid = $client_uuid->getUuid();
|
||||
}
|
||||
|
||||
if($name === null)
|
||||
{
|
||||
$name = Utilities::generateName(4);
|
||||
}
|
||||
|
||||
if(!Validate::clientName($name))
|
||||
{
|
||||
throw new InvalidClientNameException(sprintf('Invalid client name: %s', $name));
|
||||
}
|
||||
|
||||
$update_timestamp = time();
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->update(DatabaseTables::CLIENTS);
|
||||
$qb->set('name', ':name');
|
||||
$qb->setParameter('name', $name);
|
||||
$qb->set('updated_timestamp', ':updated_timestamp');
|
||||
$qb->setParameter('updated_timestamp', $update_timestamp, ParameterType::INTEGER);
|
||||
$qb->where('uuid = :uuid');
|
||||
$qb->setParameter('uuid', $client_uuid);
|
||||
$qb->setMaxResults(1);
|
||||
|
||||
try
|
||||
{
|
||||
$affected_rows = $qb->executeStatement();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to change client name: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
if($affected_rows === 0)
|
||||
{
|
||||
throw new ClientNotFoundException($client_uuid);
|
||||
}
|
||||
|
||||
Log::verbose(Misc::FEDERATIONLIB, sprintf('Changed client name for client %s to %s', $client_uuid, $name));
|
||||
|
||||
// Update the record in redis if caching is enabled
|
||||
if(Configuration::isCacheSystemEnabled() && Configuration::getObjectCacheEnabled('client_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('client_objects');
|
||||
|
||||
if(!$redis->exists($client_uuid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$redis->hSet($client_uuid, 'name', $name);
|
||||
$redis->hSet($client_uuid, 'updated_timestamp', $update_timestamp);
|
||||
if(Configuration::getObjectCacheTTL('client_objects') > 0)
|
||||
{
|
||||
$redis->expire($client_uuid, Configuration::getObjectCacheTTL('client_objects'));
|
||||
}
|
||||
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Updated client object %s <%s> in cache', $client_uuid, 'name'));
|
||||
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to update client object %s in cache: %s', $client_uuid, $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the description of a client
|
||||
*
|
||||
* @param string|ClientRecord $client_uuid
|
||||
* @param string|null $description
|
||||
* @return void
|
||||
* @throws ClientNotFoundException
|
||||
* @throws DatabaseException
|
||||
* @throws InvalidClientDescriptionException
|
||||
*/
|
||||
public function updateClientDescription(string|ClientRecord $client_uuid, ?string $description=null): void
|
||||
{
|
||||
if($client_uuid instanceof ClientRecord)
|
||||
{
|
||||
$client_uuid = $client_uuid->getUuid();
|
||||
}
|
||||
|
||||
if($description !== null && strlen($description) > 128)
|
||||
{
|
||||
throw new InvalidClientDescriptionException(sprintf('Invalid client description: %s', $description));
|
||||
}
|
||||
|
||||
$updated_timestamp = time();
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->update(DatabaseTables::CLIENTS);
|
||||
$qb->set('description', ':description');
|
||||
$qb->setParameter('description', $description, (is_null($description) ? ParameterType::NULL : ParameterType::STRING));
|
||||
$qb->set('updated_timestamp', ':updated_timestamp');
|
||||
$qb->setParameter('updated_timestamp', $updated_timestamp, ParameterType::INTEGER);
|
||||
$qb->where('uuid = :uuid');
|
||||
$qb->setParameter('uuid', $client_uuid);
|
||||
$qb->setMaxResults(1);
|
||||
|
||||
try
|
||||
{
|
||||
$affected_rows = $qb->executeStatement();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to change client description: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
if($affected_rows === 0)
|
||||
{
|
||||
throw new ClientNotFoundException($client_uuid);
|
||||
}
|
||||
|
||||
Log::verbose(Misc::FEDERATIONLIB, sprintf('Changed client description for client %s to %s', $client_uuid, $description));
|
||||
|
||||
if(Configuration::isCacheSystemEnabled() && Configuration::getObjectCacheEnabled('client_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('client_objects');
|
||||
|
||||
if(!$redis->exists($client_uuid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$redis->hSet($client_uuid, 'description', $description);
|
||||
$redis->hSet($client_uuid, 'updated_timestamp', $updated_timestamp);
|
||||
|
||||
if(Configuration::getObjectCacheTTL('client_objects') > 0)
|
||||
{
|
||||
$redis->expire($client_uuid, Configuration::getObjectCacheTTL('client_objects'));
|
||||
}
|
||||
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Updated client object %s <%s> in cache', $client_uuid, 'description'));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to update client object %s in cache: %s', $client_uuid, $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the permission role of a client.
|
||||
*
|
||||
* @param string|ClientRecord $client_uuid
|
||||
* @param int $permission_role
|
||||
* @return void
|
||||
* @throws ClientNotFoundException
|
||||
* @throws DatabaseException
|
||||
* @throws InvalidPermissionRoleException
|
||||
*/
|
||||
public function updateClientPermissionRole(string|ClientRecord $client_uuid, int $permission_role): void
|
||||
{
|
||||
if($client_uuid instanceof ClientRecord)
|
||||
{
|
||||
$client_uuid = $client_uuid->getUuid();
|
||||
}
|
||||
|
||||
if(!Validate::permissionRole($permission_role))
|
||||
{
|
||||
throw new InvalidPermissionRoleException(sprintf('Invalid permission role: %s', $permission_role));
|
||||
}
|
||||
|
||||
$updated_timestamp = time();
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->update(DatabaseTables::CLIENTS);
|
||||
$qb->set('permission_role', ':permission_role');
|
||||
$qb->setParameter('permission_role', $permission_role, ParameterType::INTEGER);
|
||||
$qb->set('updated_timestamp', ':updated_timestamp');
|
||||
$qb->setParameter('updated_timestamp', $updated_timestamp, ParameterType::INTEGER);
|
||||
$qb->where('uuid = :uuid');
|
||||
$qb->setParameter('uuid', $client_uuid);
|
||||
$qb->setMaxResults(1);
|
||||
|
||||
try
|
||||
{
|
||||
$affected_rows = $qb->executeStatement();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to change client permission role: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
if($affected_rows === 0)
|
||||
{
|
||||
throw new ClientNotFoundException($client_uuid);
|
||||
}
|
||||
|
||||
Log::verbose(Misc::FEDERATIONLIB, sprintf('Changed client permission role for client %s to %s', $client_uuid, $permission_role));
|
||||
|
||||
if(Configuration::isCacheSystemEnabled() && Configuration::getObjectCacheEnabled('client_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('client_objects');
|
||||
|
||||
if(!$redis->exists($client_uuid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$redis->hSet($client_uuid, 'permission_role', $permission_role);
|
||||
$redis->hSet($client_uuid, 'updated_timestamp', $updated_timestamp);
|
||||
|
||||
if(Configuration::getObjectCacheTTL('client_objects') > 0)
|
||||
{
|
||||
$redis->expire($client_uuid, Configuration::getObjectCacheTTL('client_objects'));
|
||||
}
|
||||
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Updated client object %s <%s> in cache', $client_uuid, 'permission_role'));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to update client object %s in cache: %s', $client_uuid, $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a client's last seen timestamp.
|
||||
*
|
||||
* @param string|ClientRecord $uuid
|
||||
* @return void
|
||||
* @throws DatabaseException
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function updateLastSeen(string|ClientRecord $uuid): void
|
||||
{
|
||||
if($uuid instanceof ClientRecord)
|
||||
{
|
||||
$uuid = $uuid->getUuid();
|
||||
}
|
||||
|
||||
$timestamp = time();
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->update(DatabaseTables::CLIENTS);
|
||||
$qb->set('seen_timestamp', ':seen_timestamp');
|
||||
$qb->setParameter('seen_timestamp', $timestamp);
|
||||
$qb->where('uuid = :uuid');
|
||||
$qb->setParameter('uuid', $uuid);
|
||||
$qb->setMaxResults(1);
|
||||
|
||||
try
|
||||
{
|
||||
$qb->executeStatement();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to update last seen timestamp: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
Log::verbose(Misc::FEDERATIONLIB, sprintf('Updated last seen timestamp for client %s to %s', $uuid, $timestamp));
|
||||
|
||||
if(Configuration::isCacheSystemEnabled() && Configuration::getObjectCacheEnabled('client_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('client_objects');
|
||||
|
||||
if(!$redis->exists($uuid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$redis->hSet($uuid, 'seen_timestamp', $timestamp);
|
||||
|
||||
if(Configuration::getObjectCacheTTL('client_objects') > 0)
|
||||
{
|
||||
$redis->expire($uuid, Configuration::getObjectCacheTTL('client_objects'));
|
||||
}
|
||||
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Updated client object %s <%s> in cache', $uuid, 'seen_timestamp'));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to update client object %s in cache: %s', $uuid, $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of clients based on the filter, order, and page.
|
||||
*
|
||||
* @param int $page
|
||||
* @param string $filter
|
||||
* @param string $order
|
||||
* @param int $max_items
|
||||
* @return array
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function listClients(int $page, string $filter=ListClientsFilter::CREATED_TIMESTAMP, string $order=FilterOrder::ASCENDING, int $max_items=100): array
|
||||
{
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->select(
|
||||
'uuid', 'enabled', 'name', 'description', 'secret_totp', 'permission_role',
|
||||
'flags', 'created_timestamp', 'updated_timestamp', 'seen_timestamp'
|
||||
);
|
||||
|
||||
$qb->from(DatabaseTables::CLIENTS);
|
||||
$qb->setFirstResult(($page - 1) * $max_items);
|
||||
$qb->setMaxResults($max_items);
|
||||
|
||||
if($order !== FilterOrder::ASCENDING && $order !== FilterOrder::DESCENDING)
|
||||
{
|
||||
throw new DatabaseException('Invalid order: ' . $order);
|
||||
}
|
||||
|
||||
$redis_client = null;
|
||||
|
||||
if(Configuration::isCacheSystemEnabled() && Configuration::getObjectCacheEnabled('client_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis_client = RedisConnectionManager::getConnectionFromConfig('client_objects');
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to connect to Redis server: %s', $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
|
||||
switch($filter)
|
||||
{
|
||||
case ListClientsFilter::CREATED_TIMESTAMP:
|
||||
$qb->orderBy('created_timestamp', strtoupper($order));
|
||||
break;
|
||||
|
||||
case ListClientsFilter::UPDATED_TIMESTAMP:
|
||||
$qb->orderBy('updated_timestamp', strtoupper($order));
|
||||
break;
|
||||
|
||||
case ListClientsFilter::SEEN_TIMESTAMP:
|
||||
$qb->orderBy('seen_timestamp', strtoupper($order));
|
||||
break;
|
||||
|
||||
case ListClientsFilter::ENABLED:
|
||||
$qb->orderBy('enabled', strtoupper($order));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new DatabaseException('Invalid filter: ' . $filter);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$result = $qb->executeQuery();
|
||||
$clients = [];
|
||||
|
||||
while($row = $result->fetchAssociative())
|
||||
{
|
||||
$client_object = ClientRecord::fromArray($row);
|
||||
|
||||
if($redis_client !== null)
|
||||
{
|
||||
$redis_client->hMSet($client_object->getUuid(), $client_object->toArray());
|
||||
|
||||
if(Configuration::getObjectCacheTTL('client_objects') > 0)
|
||||
{
|
||||
$redis_client->expire($row['uuid'], Configuration::getObjectCacheTTL('client_objects'));
|
||||
}
|
||||
}
|
||||
|
||||
$clients[] = $client_object;
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to list clients: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
unset($client);
|
||||
return $clients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of clients.
|
||||
*
|
||||
* @return int
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function getTotalClients(): int
|
||||
{
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->select('COUNT(uuid)');
|
||||
$qb->from(DatabaseTables::CLIENTS);
|
||||
|
||||
try
|
||||
{
|
||||
$result = $qb->executeQuery();
|
||||
$row = $result->fetchAssociative();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to get total clients: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
return (int)$row['COUNT(uuid)'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns teh total number of pages.
|
||||
*
|
||||
* @param int $max_items
|
||||
* @return int
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function getTotalPages(int $max_items=100): int
|
||||
{
|
||||
return (int)ceil($this->getTotalClients() / $max_items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an existing client from the database.
|
||||
*
|
||||
* @param string|ClientRecord $uuid
|
||||
* @return void
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function deleteClient(string|ClientRecord $uuid): void
|
||||
{
|
||||
if($uuid instanceof ClientRecord)
|
||||
{
|
||||
$uuid = $uuid->getUuid();
|
||||
}
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->delete(DatabaseTables::CLIENTS);
|
||||
$qb->where('uuid = :uuid');
|
||||
$qb->setParameter('uuid', $uuid);
|
||||
$qb->setMaxResults(1);
|
||||
|
||||
try
|
||||
{
|
||||
$qb->executeStatement();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to delete client: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
if(Configuration::isCacheSystemEnabled() && Configuration::getObjectCacheEnabled('client_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('client_objects');
|
||||
|
||||
if($redis->exists($uuid))
|
||||
{
|
||||
$redis->del($uuid);
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to delete client object %s from cache: %s', $uuid, $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,281 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Managers\MetadataManagers;
|
||||
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Exception;
|
||||
use FederationLib\Classes\Configuration;
|
||||
use FederationLib\Classes\Database;
|
||||
use FederationLib\Classes\Utilities;
|
||||
use FederationLib\Enums\DatabaseTables;
|
||||
use FederationLib\Enums\Misc;
|
||||
use FederationLib\Exceptions\DatabaseException;
|
||||
use FederationLib\Exceptions\Standard\InvalidPeerMetadataException;
|
||||
use FederationLib\Exceptions\Standard\PeerMetadataNotFoundException;
|
||||
use FederationLib\Interfaces\PeerMetadataInterface;
|
||||
use FederationLib\Interfaces\PeerMetadataManagerInterface;
|
||||
use FederationLib\Managers\RedisConnectionManager;
|
||||
use FederationLib\Objects\ClientRecord;
|
||||
use FederationLib\Objects\ParsedFederatedAddress;
|
||||
use FederationLib\Objects\Standard\PeerMetadata\TelegramChatMetadata;
|
||||
use LogLib\Log;
|
||||
|
||||
class TelegramChatManager implements PeerMetadataManagerInterface
|
||||
{
|
||||
/**
|
||||
* Syncs the metadata into the database
|
||||
*
|
||||
* @param ClientRecord|string $client_uuid
|
||||
* @param PeerMetadataInterface $peer_metadata
|
||||
* @return string
|
||||
* @throws DatabaseException
|
||||
* @throws InvalidPeerMetadataException
|
||||
*/
|
||||
public function syncMetadata(ClientRecord|string $client_uuid, PeerMetadataInterface $peer_metadata): string
|
||||
{
|
||||
if($client_uuid instanceof ClientRecord)
|
||||
{
|
||||
$client_uuid = $client_uuid->getUuid();
|
||||
}
|
||||
|
||||
$peer_metadata->validate();
|
||||
$cache_key = sprintf('telegram_chat_metadata:%s', $peer_metadata->getFederatedAddress());
|
||||
$redis_client = null;
|
||||
|
||||
if(Configuration::getObjectCacheEnabled('peer_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis_client = RedisConnectionManager::getConnectionFromConfig('peer_objects');
|
||||
|
||||
if($redis_client->exists($cache_key))
|
||||
{
|
||||
$redis_client->del($cache_key);
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('There was an error with the cache system while tyring to sync peer metadata with the Redis server: %s', $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$old_metadata = $this->getMetadata($peer_metadata->getFederatedAddress());
|
||||
}
|
||||
catch(PeerMetadataNotFoundException $e)
|
||||
{
|
||||
unset($e);
|
||||
|
||||
// If it doesn't exist, we create it instead of updating it
|
||||
return $this->registerMetadata($client_uuid, $peer_metadata);
|
||||
}
|
||||
|
||||
// Take the old metadata and get the differences
|
||||
$differences = Utilities::metadataDifferences($old_metadata, $peer_metadata);
|
||||
|
||||
try
|
||||
{
|
||||
// If there are no differences, then we don't need to update the database
|
||||
if(count($differences) === 0)
|
||||
{
|
||||
if($redis_client !== null && Configuration::getObjectCacheTtl('peer_objects') > 0)
|
||||
{
|
||||
// Reset the TTL since we just accessed it
|
||||
$redis_client->expire($cache_key, Configuration::getObjectCacheTtl('peer_objects'));
|
||||
}
|
||||
|
||||
return $peer_metadata->getFederatedAddress();
|
||||
}
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->update(DatabaseTables::PEERS_TELEGRAM_CHAT);
|
||||
$qb->where('federated_address = :federated_address');
|
||||
$qb->setParameter('federated_address', $peer_metadata->getFederatedAddress());
|
||||
$qb->set('updated_timestamp', $qb->createNamedParameter(time(), ParameterType::INTEGER));
|
||||
$qb->set('updated_client', $qb->createNamedParameter($client_uuid));
|
||||
$qb->setMaxResults(1);
|
||||
|
||||
foreach($differences as $key => $value)
|
||||
{
|
||||
$qb->set($key, $qb->createNamedParameter($value));
|
||||
}
|
||||
|
||||
$qb->executeQuery();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException(sprintf('There was a database error while trying to sync peer metadata: %s', $e->getMessage()), $e);
|
||||
}
|
||||
|
||||
if($redis_client !== null && count($differences) > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach($differences as $key => $value)
|
||||
{
|
||||
$redis_client->hSet($cache_key, $key, $value);
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('There was an error with the cache system while tyring to sync peer metadata with the Redis server: %s', $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
|
||||
return $peer_metadata->getFederatedAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the metadata into the database
|
||||
*
|
||||
* @param ClientRecord|string $client_uuid
|
||||
* @param PeerMetadataInterface $peer_metadata
|
||||
* @return string
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function registerMetadata(ClientRecord|string $client_uuid, PeerMetadataInterface $peer_metadata): string
|
||||
{
|
||||
if($client_uuid instanceof ClientRecord)
|
||||
{
|
||||
$client_uuid = $client_uuid->getUuid();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$peer_metadata->validate();
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->insert(DatabaseTables::PEERS_TELEGRAM_CHAT);
|
||||
|
||||
$qb->setValue('federated_address', $qb->createNamedParameter($peer_metadata->getFederatedAddress()));
|
||||
$qb->setValue('updated_timestamp', $qb->createNamedParameter(time(), ParameterType::INTEGER));
|
||||
$qb->setValue('updated_client', $qb->createNamedParameter($client_uuid));
|
||||
|
||||
foreach($peer_metadata->toArray() as $key => $value)
|
||||
{
|
||||
switch(gettype($value))
|
||||
{
|
||||
case 'array':
|
||||
$qb->setValue($key, $qb->createNamedParameter(implode(',', $value)));
|
||||
break;
|
||||
case 'NULL':
|
||||
$qb->setValue($key, $qb->createNamedParameter(null, ParameterType::NULL));
|
||||
break;
|
||||
case 'boolean':
|
||||
$qb->setValue($key, $qb->createNamedParameter((int)$value, ParameterType::INTEGER));
|
||||
break;
|
||||
case 'integer':
|
||||
$qb->setValue($key, $qb->createNamedParameter($value, ParameterType::INTEGER));
|
||||
break;
|
||||
|
||||
default:
|
||||
$qb->setValue($key, $qb->createNamedParameter($value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$qb->executeQuery();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException(sprintf('There was a database error while trying to register peer metadata: %s', $e->getMessage()), $e);
|
||||
}
|
||||
|
||||
return $peer_metadata->getFederatedAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ParsedFederatedAddress $federated_address
|
||||
* @return PeerMetadataInterface
|
||||
* @throws DatabaseException
|
||||
* @throws PeerMetadataNotFoundException
|
||||
*/
|
||||
public function getMetadata(string|ParsedFederatedAddress $federated_address): PeerMetadataInterface
|
||||
{
|
||||
$cache_key = sprintf('telegram_chat_metadata:%s', $federated_address->getAddress());
|
||||
|
||||
if(Configuration::getObjectCacheEnabled('peer_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('peer_objects');
|
||||
|
||||
if($redis->exists($cache_key))
|
||||
{
|
||||
$telegram_chat_metadata = TelegramChatMetadata::fromArray($redis->hGetAll($cache_key), true);
|
||||
|
||||
if(Configuration::getObjectCacheTTL('peer_objects') > 0)
|
||||
{
|
||||
$redis->expire($cache_key, Configuration::getObjectCacheTTL('peer_objects'));
|
||||
}
|
||||
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Loaded peer metadata object %s from cache', $federated_address->getAddress()));
|
||||
return $telegram_chat_metadata;
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to load peer metadata object %s from cache: %s', $federated_address->getAddress(), $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->select(
|
||||
'id',
|
||||
'type',
|
||||
'title',
|
||||
'username',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'is_forum',
|
||||
'updated_timestamp',
|
||||
'updated_client'
|
||||
);
|
||||
$qb->from(DatabaseTables::PEERS_TELEGRAM_CHAT);
|
||||
$qb->where('federated_address = :federated_address');
|
||||
$qb->setParameter('federated_address', $federated_address->getAddress());
|
||||
$qb->setMaxResults(1);
|
||||
|
||||
try
|
||||
{
|
||||
$result = $qb->executeQuery();
|
||||
|
||||
if($result->rowCount() === 0)
|
||||
{
|
||||
throw new PeerMetadataNotFoundException(sprintf('PeerRecord metadata not found for federated address %s', $federated_address->getAddress()));
|
||||
}
|
||||
|
||||
$telegram_chat_metadata = TelegramChatMetadata::fromArray($result->fetchAssociative(), true);
|
||||
}
|
||||
catch(PeerMetadataNotFoundException $e)
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException(sprintf('Failed to get peer metadata for federated address %s', $federated_address->getAddress()), $e);
|
||||
}
|
||||
|
||||
if(Configuration::isCacheSystemEnabled() && Configuration::getObjectCacheEnabled('peer_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('peer_objects');
|
||||
$redis->hMSet($cache_key, $telegram_chat_metadata->toArray());
|
||||
|
||||
if(Configuration::getObjectCacheTTL('peer_objects') > 0)
|
||||
{
|
||||
$redis->expire($cache_key, Configuration::getObjectCacheTTL('peer_objects'));
|
||||
}
|
||||
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Cached peer metadata object %s', $federated_address->getAddress()));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to cache peer metadata object %s: %s', $federated_address->getAddress(), $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
return $telegram_chat_metadata;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,280 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Managers\MetadataManagers;
|
||||
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Exception;
|
||||
use FederationLib\Classes\Configuration;
|
||||
use FederationLib\Classes\Database;
|
||||
use FederationLib\Classes\Utilities;
|
||||
use FederationLib\Enums\DatabaseTables;
|
||||
use FederationLib\Enums\Misc;
|
||||
use FederationLib\Exceptions\DatabaseException;
|
||||
use FederationLib\Exceptions\Standard\InvalidPeerMetadataException;
|
||||
use FederationLib\Exceptions\Standard\PeerMetadataNotFoundException;
|
||||
use FederationLib\Interfaces\PeerMetadataInterface;
|
||||
use FederationLib\Interfaces\PeerMetadataManagerInterface;
|
||||
use FederationLib\Managers\RedisConnectionManager;
|
||||
use FederationLib\Objects\ClientRecord;
|
||||
use FederationLib\Objects\ParsedFederatedAddress;
|
||||
use FederationLib\Objects\Standard\PeerMetadata\TelegramUserMetadata;
|
||||
use LogLib\Log;
|
||||
|
||||
class TelegramUserManager implements PeerMetadataManagerInterface
|
||||
{
|
||||
/**
|
||||
* @param ClientRecord|string $client_uuid
|
||||
* @param TelegramUserMetadata $peer_metadata
|
||||
* @return string
|
||||
* @throws DatabaseException
|
||||
* @throws InvalidPeerMetadataException
|
||||
*/
|
||||
public function syncMetadata(ClientRecord|string $client_uuid, PeerMetadataInterface $peer_metadata): string
|
||||
{
|
||||
if($client_uuid instanceof ClientRecord)
|
||||
{
|
||||
$client_uuid = $client_uuid->getUuid();
|
||||
}
|
||||
|
||||
$peer_metadata->validate();
|
||||
$cache_key = sprintf('telegram_user_metadata:%s', $peer_metadata->getFederatedAddress());
|
||||
$redis_client = null;
|
||||
|
||||
if(Configuration::getObjectCacheEnabled('peer_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis_client = RedisConnectionManager::getConnectionFromConfig('peer_objects');
|
||||
|
||||
if($redis_client->exists($cache_key))
|
||||
{
|
||||
$redis_client->del($cache_key);
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('There was an error with the cache system while tyring to sync peer metadata with the Redis server: %s', $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$old_metadata = $this->getMetadata($peer_metadata->getFederatedAddress());
|
||||
}
|
||||
catch(PeerMetadataNotFoundException $e)
|
||||
{
|
||||
unset($e);
|
||||
|
||||
// If it doesn't exist, we create it instead of updating it
|
||||
return $this->registerMetadata($client_uuid, $peer_metadata);
|
||||
}
|
||||
|
||||
// Take the old metadata and get the differences
|
||||
$differences = Utilities::metadataDifferences($old_metadata, $peer_metadata);
|
||||
|
||||
try
|
||||
{
|
||||
// If there are no differences, then we don't need to update the database
|
||||
if(count($differences) === 0)
|
||||
{
|
||||
if($redis_client !== null && Configuration::getObjectCacheTtl('peer_objects') > 0)
|
||||
{
|
||||
// Reset the TTL since we just accessed it
|
||||
$redis_client->expire($cache_key, Configuration::getObjectCacheTtl('peer_objects'));
|
||||
}
|
||||
|
||||
return $peer_metadata->getFederatedAddress();
|
||||
}
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->update(DatabaseTables::PEERS_TELEGRAM_USER);
|
||||
$qb->where('federated_address = :federated_address');
|
||||
$qb->setParameter('federated_address', $peer_metadata->getFederatedAddress());
|
||||
$qb->set('updated_timestamp', $qb->createNamedParameter(time(), ParameterType::INTEGER));
|
||||
$qb->set('updated_client', $qb->createNamedParameter($client_uuid));
|
||||
$qb->setMaxResults(1);
|
||||
|
||||
foreach($differences as $key => $value)
|
||||
{
|
||||
$qb->set($key, $qb->createNamedParameter($value));
|
||||
}
|
||||
|
||||
$qb->executeQuery();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException(sprintf('There was a database error while trying to sync peer metadata: %s', $e->getMessage()), $e);
|
||||
}
|
||||
|
||||
if($redis_client !== null && count($differences) > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach($differences as $key => $value)
|
||||
{
|
||||
$redis_client->hSet($cache_key, $key, $value);
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('There was an error with the cache system while tyring to sync peer metadata with the Redis server: %s', $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
|
||||
return $peer_metadata->getFederatedAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the client's metadata with the database
|
||||
*
|
||||
* @param ClientRecord|string $client_uuid
|
||||
* @param TelegramUserMetadata $peer_metadata
|
||||
* @return string
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function registerMetadata(ClientRecord|string $client_uuid, PeerMetadataInterface $peer_metadata): string
|
||||
{
|
||||
if($client_uuid instanceof ClientRecord)
|
||||
{
|
||||
$client_uuid = $client_uuid->getUuid();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$peer_metadata->validate();
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->insert(DatabaseTables::PEERS_TELEGRAM_USER);
|
||||
|
||||
$qb->setValue('federated_address', $qb->createNamedParameter($peer_metadata->getFederatedAddress()));
|
||||
$qb->setValue('updated_timestamp', $qb->createNamedParameter(time(), ParameterType::INTEGER));
|
||||
$qb->setValue('updated_client', $qb->createNamedParameter($client_uuid));
|
||||
|
||||
foreach($peer_metadata->toArray() as $key => $value)
|
||||
{
|
||||
switch(gettype($value))
|
||||
{
|
||||
case 'array':
|
||||
$qb->setValue($key, $qb->createNamedParameter(implode(',', $value)));
|
||||
break;
|
||||
case 'NULL':
|
||||
$qb->setValue($key, $qb->createNamedParameter(null, ParameterType::NULL));
|
||||
break;
|
||||
case 'boolean':
|
||||
$qb->setValue($key, $qb->createNamedParameter((int)$value, ParameterType::INTEGER));
|
||||
break;
|
||||
case 'integer':
|
||||
$qb->setValue($key, $qb->createNamedParameter($value, ParameterType::INTEGER));
|
||||
break;
|
||||
|
||||
default:
|
||||
$qb->setValue($key, $qb->createNamedParameter($value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$qb->executeQuery();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException(sprintf('There was a database error while trying to register peer metadata: %s', $e->getMessage()), $e);
|
||||
}
|
||||
|
||||
return $peer_metadata->getFederatedAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ParsedFederatedAddress|string $federated_address
|
||||
* @return TelegramUserMetadata
|
||||
* @throws DatabaseException
|
||||
* @throws PeerMetadataNotFoundException
|
||||
*/
|
||||
public function getMetadata(ParsedFederatedAddress|string $federated_address): TelegramUserMetadata
|
||||
{
|
||||
if(Configuration::getObjectCacheEnabled('peer_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('peer_objects');
|
||||
$key = sprintf('telegram_user_metadata:%s', $federated_address->getAddress());
|
||||
|
||||
if($redis->exists($key))
|
||||
{
|
||||
$telegram_user_metadata = TelegramUserMetadata::fromArray($redis->hGetAll($key), true);
|
||||
|
||||
if(Configuration::getObjectCacheTTL('peer_objects') > 0)
|
||||
{
|
||||
$redis->expire($key, Configuration::getObjectCacheTTL('peer_objects'));
|
||||
}
|
||||
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Loaded peer metadata object %s from cache', $federated_address->getAddress()));
|
||||
return $telegram_user_metadata;
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to load peer metadata object %s from cache: %s', $federated_address->getAddress(), $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->select(
|
||||
'id',
|
||||
'type',
|
||||
'title',
|
||||
'username',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'is_forum',
|
||||
'updated_timestamp',
|
||||
'updated_client'
|
||||
);
|
||||
$qb->from(DatabaseTables::PEERS_TELEGRAM_USER);
|
||||
$qb->where('federated_address = :federated_address');
|
||||
$qb->setParameter('federated_address', $federated_address->getAddress());
|
||||
$qb->setMaxResults(1);
|
||||
|
||||
try
|
||||
{
|
||||
$result = $qb->executeQuery();
|
||||
|
||||
if($result->rowCount() === 0)
|
||||
{
|
||||
throw new PeerMetadataNotFoundException(sprintf('PeerRecord metadata not found for federated address %s', $federated_address->getAddress()));
|
||||
}
|
||||
|
||||
$telegram_user_metadata = TelegramUserMetadata::fromArray($result->fetchAssociative(), true);
|
||||
}
|
||||
catch(PeerMetadataNotFoundException $e)
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException(sprintf('Failed to get peer metadata for federated address %s', $federated_address->getAddress()), $e);
|
||||
}
|
||||
|
||||
if(Configuration::isCacheSystemEnabled() && Configuration::getObjectCacheEnabled('peer_objects'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$redis = RedisConnectionManager::getConnectionFromConfig('peer_objects');
|
||||
|
||||
$key = sprintf('telegram_user_metadata:%s', $federated_address);
|
||||
$redis->hMSet($key, $telegram_user_metadata->toArray());
|
||||
|
||||
if(Configuration::getObjectCacheTTL('peer_objects') > 0)
|
||||
{
|
||||
$redis->expire($key, Configuration::getObjectCacheTTL('peer_objects'));
|
||||
}
|
||||
|
||||
Log::debug(Misc::FEDERATIONLIB, sprintf('Cached peer metadata object %s', $federated_address->getAddress()));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to cache peer metadata object %s: %s', $federated_address->getAddress(), $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
return $telegram_user_metadata;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue