Initial Commit

This commit is contained in:
Netkas 2023-06-04 14:23:51 -04:00
parent 93a0b9be02
commit 6e599b2c0c
No known key found for this signature in database
GPG key ID: 5DAF58535614062B
99 changed files with 10836 additions and 4 deletions

3
.gitignore vendored Normal file → Executable file
View file

@ -1 +1,2 @@
build/
build/
/scratch/

0
.idea/.gitignore generated vendored Normal file → Executable file
View file

1
.idea/.name generated Normal file
View file

@ -0,0 +1 @@
FederationLib

View file

@ -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
View 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>

0
.idea/inspectionProfiles/Project_Default.xml generated Normal file → Executable file
View file

2
.idea/modules.xml generated
View file

@ -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>

114
.idea/php.xml generated
View file

@ -9,6 +9,120 @@
<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" />
</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="memcached" 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="msgpack" enabled="true" />
<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
View file

0
.idea/runConfigurations/Clean.xml generated Normal file → Executable file
View file

0
.idea/runConfigurations/Install.xml generated Normal file → Executable file
View file

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

0
.idea/vcs.xml generated Normal file → Executable file
View file

14
.idea/webResources.xml generated Normal file
View 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>

0
Makefile Normal file → Executable file
View file

4
README.md Normal file
View file

@ -0,0 +1,4 @@
# FederationLib
Please note that this project is currently in development and is not ready for use.
See the [STANDARD.md](STANDARD.md) file for more information.

242
STANDARD.md Executable file
View file

@ -0,0 +1,242 @@
# 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)
* [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
## 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.
![Federated Telegram User](assets/federated_telegram_user.png)
![Federated Email Address](assets/federated_email_address.png)
![Federated IPV4](assets/federated_ipv4.png)
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
![RECON Example](assets/recon_example.png)
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
assets/federated_ipv4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
assets/recon_example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View 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);

29
database/associations.sql Normal file
View file

@ -0,0 +1,29 @@
create table associations
(
child_peer varchar(256) not null comment 'The child peer',
parent_peer varchar(256) not null comment 'The parent peer',
type varchar(32) not null comment 'The association type between the parent peer to the child peer',
client_uuid varchar(36) not null comment 'The client UUID that made the association between the two peers',
timestamp bigint default unix_timestamp() not null comment 'The Unix Timestamp for when this assoication was made',
constraint associations_child_peer_parent_peer_uindex
unique (child_peer, parent_peer),
constraint associations_child_peer_type_parent_peer_uindex
unique (child_peer, type, parent_peer),
constraint associations_clients_uuid_fk
foreign key (client_uuid) references clients (uuid),
constraint associations_discovered_peers_federated_address_fk
foreign key (child_peer) references peers (federated_address),
constraint associations_discovered_peers_federated_address_fk2
foreign key (parent_peer) references peers (federated_address)
)
comment 'A table used for housing associations detected between peers';
create index associations_child_peer_index
on associations (child_peer);
create index associations_client_uuid_index
on associations (client_uuid);
create index associations_parent_peer_index
on associations (parent_peer);

34
database/clients.sql Normal file
View 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';

35
database/events.sql Normal file
View 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
View 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',
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);

View file

@ -0,0 +1,23 @@
create table peers_telegram_chat
(
federated_address varchar(255) not null comment 'A federated internet standard ID for this Telegram chat'
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 varchar(32) not null comment 'The type of chat, Type of chat, can be either “group”, “supergroup” or "channel". "private" is not accepted',
title varchar(255) collate utf8mb4_unicode_ci 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',
is_forum tinyint(1) default 0 not null comment 'Optional. True, if the supergroup chat is a forum (has topics enabled)',
constraint telegram_chat_federated_address_uindex
unique (federated_address),
constraint telegram_chat_id_uindex
unique (id),
constraint telegram_chat_pk2
unique (id),
constraint telegram_chat_discovered_peers_federated_address_fk
foreign key (federated_address) references peers (federated_address)
)
comment 'A table for housing telegram chats for channel references';
create index telegram_chat_username_index
on peers_telegram_chat (username);

View file

@ -0,0 +1,23 @@
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 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',
constraint telegram_user_federated_id_uindex
unique (federated_address),
constraint telegram_user_pk2
unique (id),
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);

View file

@ -0,0 +1,47 @@
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',
channel_peer varchar(256) null comment 'Optional. The channel that the content was sent on',
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',
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)
)
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_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);

45
database/reports.sql Normal file
View file

@ -0,0 +1,45 @@
create table reports
(
report_id varchar(36) default uuid() not null comment 'The UUID V4 for the report''s ID'
primary key,
type varchar(32) default 'OTHER' not null comment 'The standard report type',
client_uuid varchar(36) not null comment 'The Client UUID responsible for submitting the report to the database',
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',
automated tinyint default 0 not null comment 'Indicates if the report was automated or not',
confidence_score float null comment 'The confidence score of the scanner if the report was automated',
reference_document varchar(36) not null comment 'The reference document to which this report came from',
scan_mode varchar(32) null comment 'The scan mode that was used to conduct the automated report',
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_clients_uuid_fk
foreign key (client_uuid) references clients (uuid),
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_client_uuid_index
on reports (client_uuid);
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
View 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
View file

@ -0,0 +1,6 @@
<?php
require 'ncc';
import('net.nosial.federationlib');
\FederationCLI\Program::main(\OptsLib\Parse::getArguments());

54
project.json Normal file → Executable file
View file

@ -5,17 +5,69 @@
"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.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",

View file

@ -0,0 +1,149 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace FederationCLI;
use FederationLib\FederationLib;
class InteractiveMode
{
/**
* The current menu the user is in
*
* @var string
*/
private static $current_menu = 'Main';
/**
* An array of menu pointers to functions
*
* @var string[]
*/
private static $menu_pointers = [
'ClientManager' => 'FederationCLI\InteractiveMode\ClientManager::processCommand',
'ConfigManager' => 'FederationCLI\InteractiveMode\ConfigurationManager::processCommand'
];
private static $help_pointers =[
'ClientManager' => 'FederationCLI\InteractiveMode\ClientManager::help',
'ConfigManager' => 'FederationCLI\InteractiveMode\ConfigurationManager::help'
];
/**
* @var FederationLib|null
*/
private static $federation_lib = null;
/**
* Main entry point for the interactive mode
*
* @param array $args
* @return void
*/
public static function main(array $args=[]): void
{
while(true)
{
print(sprintf('federation@%s:~$ ', self::$current_menu));
$input = trim(fgets(STDIN));
self::processCommand($input);
}
}
/**
* Processes a command from the user
*
* @param string $input
* @return void
*/
private static function processCommand(string $input): void
{
$parsed_input = Utilities::parseShellInput($input);
switch(strtolower($parsed_input['command']))
{
case 'help':
print('Available commands:' . PHP_EOL);
print(' help - displays the help menu for the current menu and global commands' . PHP_EOL);
print(' client_manager - enter client manager mode' . PHP_EOL);
print(' config_manager - enter config manager mode' . PHP_EOL);
print(' clear - clears the screen' . PHP_EOL);
print(' exit - exits the current menu, if on the main menu, exits the program' . PHP_EOL);
if(isset(self::$help_pointers[self::$current_menu]))
{
call_user_func(self::$help_pointers[self::$current_menu]);
}
break;
case 'client_manager':
self::$current_menu = 'ClientManager';
break;
case 'config_manager':
self::$current_menu = 'ConfigManager';
break;
case 'clear':
print(chr(27) . "[2J" . chr(27) . "[;H");
break;
case 'exit':
if(self::$current_menu != 'Main')
{
self::$current_menu = 'Main';
break;
}
exit(0);
default:
if(!isset(self::$menu_pointers[self::$current_menu]))
{
print(sprintf('Unknown command: %s', $parsed_input['command']) . PHP_EOL);
break;
}
call_user_func(self::$menu_pointers[self::$current_menu], $input);
break;
}
}
/**
* Returns the current menu
*
* @return string
*/
public static function getCurrentMenu(): string
{
return self::$current_menu;
}
/**
* Sets the current menu to the specified value
*
* @param string $current_menu
*/
public static function setCurrentMenu(string $current_menu): void
{
self::$current_menu = $current_menu;
}
/**
* Returns the FederationLib instance
*
* @return FederationLib
*/
public static function getFederationLib(): FederationLib
{
if(self::$federation_lib == null)
{
self::$federation_lib = new FederationLib();
}
return self::$federation_lib;
}
}

View file

@ -0,0 +1,190 @@
<?php
namespace FederationCLI\InteractiveMode;
use AsciiTable\Builder;
use Exception;
use FederationCLI\InteractiveMode;
use FederationCLI\Utilities;
use FederationLib\Enums\FilterOrder;
use FederationLib\Enums\Filters\ListClientsFilter;
use FederationLib\Exceptions\DatabaseException;
use FederationLib\Objects\Client;
class ClientManager
{
/**
* @param string $input
* @return void
*/
public static function processCommand(string $input): void
{
$parsed_input = Utilities::parseShellInput($input);
try
{
switch(strtolower($parsed_input['command']))
{
case 'register':
self::registerClient();
break;
case 'list':
// list [optional: page] [optional: filter] [optional: order] [optional: max_items]
self::listClients($parsed_input['args']);
break;
case 'total':
self::totalClients();
break;
case 'total_pages':
self::totalPages($parsed_input['args']);
break;
case 'get':
self::getClient($parsed_input['args']);
break;
default:
print(sprintf('Unknown command: %s', $parsed_input['command']) . PHP_EOL);
break;
}
}
catch(Exception $e)
{
Utilities::printExceptionStack($e);
}
}
/**
* Displays the help message for the client manager
*
* @return void
*/
public static function help(): void
{
print('Client manager commands:' . PHP_EOL);
print(' register - registers a new client with the federation' . PHP_EOL);
print(' list [optional: page (default 1)] [optional: filter (default created_timestamp)] [optional: order (default asc)] [optional: max_items (default 100)] - lists clients' . PHP_EOL);
print(' total - gets the total number of clients' . PHP_EOL);
print(' total_pages [optional: max_items (default 100)] - gets the total number of pages of clients' . PHP_EOL);
print(' get [client uuid] - gets a client by UUID' . PHP_EOL);
}
/**
* Registers a new client with the federation. prints the UUID of the client if successful.
*
* @return void
*/
private static function registerClient(): void
{
$client = new Client();
$client->setName(Utilities::promptInput('Client name (default: Random): '));
$client->setDescription(Utilities::promptInput('Client description (default: N/A): '));
$client->setQueryPermission((int)Utilities::parseBoolean(Utilities::promptInput('Query permission (default: 1): ')));
$client->setUpdatePermission((int)Utilities::parseBoolean(Utilities::promptInput('Update permission (default: 0): ')));
try
{
$client_uuid = InteractiveMode::getFederationLib()->getClientManager()->registerClient($client);
}
catch(Exception $e)
{
print('Failed to register client: ' . $e->getMessage() . PHP_EOL);
Utilities::printExceptionStack($e);
return;
}
print(sprintf('Client registered successfully, UUID: %s', $client_uuid) . PHP_EOL);
}
/**
* @param array $args
* @return void
* @throws DatabaseException
* @throws \Doctrine\DBAL\Exception
* @throws \RedisException
*/
private static function listClients(array $args): void
{
$page = $args[0] ?? 1;
$filter = $args[1] ?? ListClientsFilter::CREATED_TIMESTAMP;
$order = $args[2] ?? FilterOrder::ASCENDING;
$max_items = $args[3] ?? 100;
$clients = InteractiveMode::getFederationLib()->getClientManager()->listClients($page, $filter, $order, $max_items);
if(count($clients) === 0)
{
print('No clients found' . PHP_EOL);
}
else
{
$builder = new Builder();
foreach($clients as $client)
{
$builder->addRow($client->toArray());
}
$builder->setTitle(sprintf('Clients (page %d, filter %s, order %s, max items %d)', $page, $filter, $order, $max_items));
print($builder->renderTable() . PHP_EOL);
}
}
private static function getClient(mixed $args)
{
$client_uuid = $args[0] ?? null;
if(is_null($client_uuid))
{
print('Client UUID required' . PHP_EOL);
return;
}
try
{
$client = InteractiveMode::getFederationLib()->getClientManager()->getClient($client_uuid);
}
catch(Exception $e)
{
print('Failed to get client: ' . $e->getMessage() . PHP_EOL);
Utilities::printExceptionStack($e);
return;
}
foreach($client->toArray() as $key => $value)
{
print match ($key)
{
'id' => (sprintf(' UUID: %s', $value) . PHP_EOL),
'enabled' => (sprintf(' Enabled: %s', (Utilities::parseBoolean($value) ? 'Yes' : 'No')) . PHP_EOL),
'name' => (sprintf(' Name: %s', $value ?? 'N/A') . PHP_EOL),
'description' => (sprintf(' Description: %s', $value ?? 'N/A') . PHP_EOL),
'secret_totp' => (sprintf(' Secret TOTP: %s', $value ?? 'N/A') . PHP_EOL),
'query_permission' => (sprintf(' Query permission Level: %s', $value) . PHP_EOL),
'update_permission' => (sprintf(' Update permission Level: %s', $value) . PHP_EOL),
'flags' => (sprintf(' Flags: %s', $value) . PHP_EOL),
'created_timestamp' => (sprintf(' Created: %s', date('Y-m-d H:i:s', $value)) . PHP_EOL),
'updated_timestamp' => (sprintf(' Updated: %s', date('Y-m-d H:i:s', $value)) . PHP_EOL),
'seen_timestamp' => (sprintf(' Last seen: %s', date('Y-m-d H:i:s', $value)) . PHP_EOL),
default => (sprintf(' %s: %s', $key, $value) . PHP_EOL),
};
}
}
private static function totalClients()
{
print(sprintf('Total clients: %d', InteractiveMode::getFederationLib()->getClientManager()->getTotalClients()) . PHP_EOL);
}
private static function totalPages(mixed $args)
{
$max_items = $args[0] ?? 100;
print(sprintf('Total pages: %d', InteractiveMode::getFederationLib()->getClientManager()->getTotalPages($max_items)) . PHP_EOL);
}
}

View file

@ -0,0 +1,113 @@
<?php
namespace FederationCLI\InteractiveMode;
use Exception;
use FederationCLI\Utilities;
use FederationLib\Classes\Configuration;
class ConfigurationManager
{
/**
* @param string $input
* @return void
*/
public static function processCommand(string $input): void
{
$parsed_input = Utilities::parseShellInput($input);
switch(strtolower($parsed_input['command']))
{
case 'read':
self::read($parsed_input['args'][0] ?? null);
break;
case 'write':
self::write($parsed_input['args'][0] ?? null, $parsed_input['args'][1] ?? null);
break;
case 'save':
self::save();
break;
default:
print(sprintf('Unknown command: %s', $parsed_input['command']) . PHP_EOL);
break;
}
}
/**
* Displays the help message for the client manager
*
* @return void
*/
public static function help(): void
{
print('Configuration manager commands:' . PHP_EOL);
print(' read - reads the current configuration' . PHP_EOL);
print(' read <key> - reads the value of the specified configuration key' . PHP_EOL);
print(' write <key> <value> - writes the value of the specified configuration key' . PHP_EOL);
print(' save - saves the current configuration to disk' . PHP_EOL);
}
/**
* Reads the current configuration or the value of a specific key
*
* @param string|null $key
* @return void
*/
private static function read(?string $key=null): void
{
if($key === null)
{
$value = Configuration::getConfiguration();
}
else
{
$value = Configuration::getConfigurationObject()->get($key);
}
if(is_null($value))
{
print('No value found for key: ' . $key . PHP_EOL);
}
elseif(is_array($value))
{
print(json_encode($value, JSON_PRETTY_PRINT) . PHP_EOL);
}
else
{
print($value . PHP_EOL);
}
}
/**
* Writes the value of a specific configuration key
*
* @param string $key
* @param string $value
* @return void
*/
private static function write(string $key, string $value): void
{
Configuration::getConfigurationObject()->set($key, $value);
}
/**
* Writes the current configuration to disk
*
* @return void
*/
private static function save(): void
{
try
{
Configuration::getConfigurationObject()->save();
}
catch(Exception $e)
{
print('Failed to save configuration: ' . $e->getMessage() . PHP_EOL);
Utilities::printExceptionStack($e);
}
}
}

View 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);
}
}

View file

@ -0,0 +1,101 @@
<?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);
}
}

View 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;
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace FederationLib\Classes;
class CacheSystem
{
}

View file

@ -0,0 +1,226 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace FederationLib\Classes;
use Exception;
use FederationLib\Classes\Configuration\CacheServerConfiguration;
use RuntimeException;
class Configuration
{
/**
* @var \ConfigLib\Configuration|null
*/
private static $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 */
self::$configuration->setDefault('cache_system.enabled', true);
// Cache System Configuration
// 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', 'any');
// Peer 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', 'any');
// Redis 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.driver', 'redis');
self::$configuration->setDefault('cache_system.servers.redis_master.priority', 1);
self::$configuration->setDefault('cache_system.servers.redis_master.username', null);
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);
// Memcached Configuration
self::$configuration->setDefault('cache_system.servers.memcached_master.enabled', false);
self::$configuration->setDefault('cache_system.servers.memcached_master.host', 'localhost');
self::$configuration->setDefault('cache_system.servers.memcached_master.port', 11211);
self::$configuration->setDefault('cache_system.servers.memcached_master.driver', 'memcached');
self::$configuration->setDefault('cache_system.servers.memcached_master.priority', 1);
self::$configuration->setDefault('cache_system.servers.memcached_master.username', null);
self::$configuration->setDefault('cache_system.servers.memcached_master.password', null);
self::$configuration->setDefault('cache_system.servers.memcached_master.database', null);
self::$configuration->setDefault('cache_system.servers.memcached_master.reconnect_interval', 1800);
/** Federation Configuration */
self::$configuration->setDefault('federation.events_retention', 1209600); // Two Weeks
self::$configuration->setDefault('federation.events_enabled.generic', true);
self::$configuration->setDefault('federation.events_enabled.client_Created', true);
/** 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;
}
/**
* 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 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;
}
}

View file

@ -0,0 +1,162 @@
<?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 string|null
*/
private ?string $driver;
/**
* @var int|null
*/
private ?int $priority;
/**
* @var string|null
*/
private ?string $username;
/**
* @var string|null
*/
private ?string $password;
/**
* @var string|null
*/
private ?string $database;
/**
* @var int|null
*/
private ?int $reconnect_interval;
/**
* CacheServerConfiguration constructor.
*
* @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->driver = $configuration['driver'] ?? null;
$this->priority = $configuration['priority'] ?? null;
$this->username = $configuration['username'] ?? 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
*/
public function getName(): ?string
{
return $this->name;
}
/**
* Returns whether the cache server is enabled
*
* @return bool
*/
public function getEnabled(): 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 string|null
*/
public function getDriver(): ?string
{
return $this->driver;
}
/**
* @return int|null
*/
public function getPriority(): ?int
{
return $this->priority;
}
/**
* @return string|null
*/
public function getUsername(): ?string
{
return $this->username;
}
/**
* @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;
}
}

View file

@ -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;
}
}

File diff suppressed because it is too large Load diff

View 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::info('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::info('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;
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace FederationLib\Classes;
class Memcached
{
/**
* @var int|null
*/
private static $memcached_last_connection_time;
/**
* @var \Memcached|null
*/
private static $memcached_connection;
}

View file

@ -0,0 +1,91 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace FederationLib\Classes;
use Exception;
use LogLib\Log;
use RedisException;
class Redis
{
/**
* @var int|null
*/
private static $redis_last_connection_time;
/**
* @var \Redis|null
*/
private static $redis_connection;
/**
* Returns/Establishes a connection to the redis server.
* Returns null if redis is disabled.
*
* @return \Redis|null
* @throws RedisException
*/
public static function getConnection(): ?\Redis
{
if(!Configuration::isRedisEnabled())
{
return null;
}
if(self::$redis_connection === null)
{
try
{
Log::info('net.nosial.federationlib', sprintf('Connecting to the redis server: %s:%s', Configuration::getRedisHost(), Configuration::getRedisPort()));
$redis = new \Redis();
$redis->connect(Configuration::getRedisHost(), Configuration::getRedisPort());
if(Configuration::getRedisPassword() !== null)
{
$redis->auth(Configuration::getRedisPassword());
}
$redis->select(Configuration::getRedisDatabase());
}
catch(Exception $e)
{
throw new RedisException('Failed to connect to the redis server: ' . $e->getMessage(), $e->getCode(), $e);
}
self::$redis_connection = $redis;
self::$redis_last_connection_time = time();
}
else
{
if(self::$redis_last_connection_time === null || self::$redis_last_connection_time < (time() - Configuration::getRedisReconnectInterval()))
{
Log::info('net.nosial.federationlib', 'Interval to reconnect to the redis server has been reached, reconnecting...');
try
{
self::$redis_connection->close();
}
catch(Exception $e)
{
// Do nothing
unset($e);
}
self::$redis_connection = null;
return self::getConnection();
}
}
return self::$redis_connection;
}
/**
* Returns the last time the redis server was connected to.
*
* @return int|null
*/
public static function getRedisLastConnectionTime(): ?int
{
return self::$redis_last_connection_time;
}
}

View file

@ -0,0 +1,102 @@
<?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;
}
}

View file

@ -0,0 +1,183 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace FederationLib\Classes;
use FederationLib\Enums\SerializationMethod;
use FederationLib\Interfaces\SerializableObjectInterface;
use InvalidArgumentException;
use LogLib\Log;
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;
}
/**
* Parses a federated address into an array of parts.
* Example: entity:uid
*
* @param string $address
* @return array
*/
public static function parseFederatedAddress(string $address): array
{
if (preg_match($address, '/^(?P<entity>[a-zA-Z0-9_-]+):(?P<uid>[a-zA-Z0-9_-]+)$/', $matches, PREG_UNMATCHED_AS_NULL))
{
return [
'entity' => $matches['entity'],
'uid' => $matches['uid']
];
}
throw new InvalidArgumentException(sprintf('Invalid address provided: %s', $address));
}
/**
* Serializes an array into a string.
*
* @param array $data
* @param string $method
* @return string
*/
public static function serialize(array|SerializableObjectInterface $data, string $method): string
{
if($data instanceof SerializableObjectInterface)
{
$data = $data->toArray();
}
switch(strtolower($method))
{
case SerializationMethod::JSON:
return json_encode($data);
case SerializationMethod::MSGPACK:
return msgpack_pack($data);
default:
Log::warning('net.nosial.federationlib', sprintf('Unknown serialization method: %s, defaulting to msgpack', $method));
return msgpack_pack($data);
}
}
/**
* Recursively converts a Throwable into an array representation.
*
* @param Throwable $throwable
* @return array
*/
public static function throwableToArray(Throwable $throwable): array
{
$results = [
'message' => $throwable->getMessage(),
'code' => $throwable->getCode(),
'file' => $throwable->getFile(),
'line' => $throwable->getLine(),
'trace' => $throwable->getTrace(),
'previous' => $throwable->getPrevious()
];
if($results['previous'] instanceof Throwable)
{
$results['previous'] = self::throwableToArray($results['previous']);
}
return $results;
}
/**
* Uses the z-score method to detect anomalies in an array of numbers.
* The key of the returned array is the index of the number in the original array.
* 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;
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace FederationLib\Classes;
use FederationLib\Enums\Standard\PeerType;
use FederationLib\Enums\Standard\InternetPeerType;
use FederationLib\Enums\Standard\PeerAssociationType;
use FederationLib\Enums\Standard\UserPeerType;
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
{
if(self::getEntityType($entity_type) === PeerType::UNKNOWN)
return false;
return true;
}
/**
* 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;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace FederationLib\Enums;
final class CacheDriver
{
public const MEMCACHED = 'memcached';
public const REDIS = 'redis';
}

View file

@ -0,0 +1,30 @@
<?php
namespace FederationLib\Enums;
final class DatabaseTables
{
public const ANOMALY_TRACKING = 'anomaly_tracking';
public const ASSOCIATIONS = 'associations';
public const CLIENTS = 'clients';
public const EVENTS = 'events';
public const PEERS = 'peers';
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::ASSOCIATIONS,
self::CLIENTS,
self::EVENTS,
self::PEERS,
self::PEERS_TELEGRAM_CHAT,
self::PEERS_TELEGRAM_USER,
self::QUERY_DOCUMENTS,
self::REPORTS,
self::RESTRICTIONS,
];
}

View 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;
}

View file

@ -0,0 +1,9 @@
<?php
namespace FederationLib\Enums;
final class FilterOrder
{
public const ASCENDING = 'asc';
public const DESCENDING = 'desc';
}

View 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
];
}

View file

@ -0,0 +1,8 @@
<?php
namespace FederationLib\Enums;
final class Misc
{
public const FEDERATIONLIB = 'net.nosial.federationlib';
}

View file

@ -0,0 +1,9 @@
<?php
namespace FederationLib\Enums;
final class SerializationMethod
{
public const JSON = 'json';
public const MSGPACK = 'msgpack';
}

View file

@ -0,0 +1,12 @@
<?php
namespace FederationLib\Enums\Standard;
final class AttachmentDataType
{
public const RAW = 'raw';
public const BASE64 = 'base64';
public const URL = 'url';
}

View file

@ -0,0 +1,15 @@
<?php
namespace FederationLib\Enums\Standard;
final class ContentType
{
public const NULL = 'null';
public const TEXT = 'text/plain';
public const HTML = 'text/html';
public const JSON = 'application/json';
public const XML = 'application/xml';
public const EMAIL = 'message/rfc822';
public const BASE64 = 'application/base64';
public const HTTP = 'application/http';
}

View file

@ -0,0 +1,12 @@
<?php
namespace FederationLib\Enums\Standard;
final class DocumentSubjectType
{
public const RECON = 'RECON';
public const ALL = [
self::RECON
];
}

View file

@ -0,0 +1,55 @@
<?php
namespace FederationLib\Enums\Standard;
final class ErrorCodes
{
/**
* An internal server error occurred.
*/
public const INTERNAL_SERVER_ERROR = 0;
/**
* 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 requested user entity was not found.
*/
public const PEER_NOT_FOUND = 2000;
/**
* The requested peer association was not found.
*/
public const PEER_ASSOCIATION_NOT_FOUND = 3000;
/**
* The requested peer association type is invalid.
*/
public const INVALID_PEER_ASSOCIATION_TYPE = 3001;
}

View 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
];
}

View 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
];
}

View file

@ -0,0 +1,15 @@
<?php
namespace FederationLib\Enums\Standard;
final class InternetPeerType
{
public const TELEGRAM_CHAT = 'telegram.chat';
public const TELEGRAM_CHANNEL = 'telegram.channel';
public const ALL = [
self::TELEGRAM_CHAT,
self::TELEGRAM_CHANNEL,
];
}

View file

@ -0,0 +1,48 @@
<?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';
/**
* An array of all peer association types.
*/
const ALL = [
self::OWNER,
self::ADMIN,
self::MODERATOR,
self::MEMBER,
self::BANNED,
self::ALTERNATIVE
];
}

View file

@ -0,0 +1,18 @@
<?php
namespace FederationLib\Enums\Standard;
final class PeerType
{
const USER = 'user';
const INTERNET = 'internet';
const UNKNOWN = 'unknown';
const ALL = [
self::USER,
self::INTERNET,
self::UNKNOWN
];
}

View 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';
}

View file

@ -0,0 +1,12 @@
<?php
namespace FederationLib\Enums\Standard;
final class ScanMode
{
/**
* Means that the scanner should only scan the header of the document.
*/
public const DOCUMENT = 'document';
}

View 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,
];
}

View file

@ -0,0 +1,8 @@
<?php
namespace FederationLib\Enums;
final class UserEntityRelationType
{
public const OWNER = 'owner';
}

View 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);
}
}
}

View 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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View 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);
}
}

38
src/FederationLib/FederationLib.php Normal file → Executable file
View file

@ -2,7 +2,45 @@
namespace FederationLib;
use FederationLib\Managers\ClientManager;
use FederationLib\Managers\EventLogManager;
class FederationLib
{
/**
* @var ClientManager
*/
private ClientManager $client_manager;
/**
* @var EventLogManager
*/
private EventLogManager $event_log_manager;
/**
* FederationLib constructor.
*/
public function __construct()
{
$this->client_manager = new ClientManager($this);
$this->event_log_manager = new EventLogManager($this);
}
/**
* Returns the Client manager instance
*
* @return ClientManager
*/
public function getClientManager(): ClientManager
{
return $this->client_manager;
}
/**
* @return EventLogManager
*/
public function getEventLogManager(): EventLogManager
{
return $this->event_log_manager;
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace FederationLib\Interfaces;
use FederationLib\Classes\Configuration\CacheServerConfiguration;
use FederationLib\Classes\Memcached;
use FederationLib\Classes\Redis;
interface CacheDriverInterface
{
/**
* Constructs a cache server driver
*
* @param CacheServerConfiguration $configuration
*/
public function __construct(CacheServerConfiguration $configuration);
/**
* Returns the configuration for the driver
*
* @return CacheServerConfiguration
*/
public function getConfiguration(): CacheServerConfiguration;
/**
* Returns the driver connection instance.
*
* @return Redis|Memcached
*/
public function getConnection(): Redis|Memcached;
/**
* Connects to the cache server
*
* @return void
*/
public function connect(): void;
/**
* Disconnects from the cache server
*
* @return void
*/
public function disconnect(): void;
}

View file

@ -0,0 +1,20 @@
<?php
namespace FederationLib\Interfaces;
interface EntityObjectInterface extends SerializableObjectInterface
{
/**
* Validates the given object and returns true if it is valid.
*
* @return bool
*/
public function validate(): bool;
/**
* Returns the standard federated address of the entity.
*
* @return string
*/
public function getFederatedAddress(): string;
}

View 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;
}

View file

@ -0,0 +1,503 @@
<?php
namespace FederationLib\Managers;
use Exception;
use FederationLib\Classes\Configuration;
use FederationLib\Classes\Database;
use FederationLib\Classes\Redis;
use FederationLib\Classes\Utilities;
use FederationLib\Enums\DatabaseTables;
use FederationLib\Enums\EventPriority;
use FederationLib\Enums\FilterOrder;
use FederationLib\Enums\Filters\ListClientsFilter;
use FederationLib\Enums\Standard\EventCode;
use FederationLib\Exceptions\DatabaseException;
use FederationLib\Exceptions\Standard\ClientNotFoundException;
use FederationLib\FederationLib;
use FederationLib\Objects\Client;
use LogLib\Log;
use Symfony\Component\Uid\Uuid;
class ClientManager
{
/**
* @var FederationLib
*/
private FederationLib $federationLib;
/**
* ClientManager constructor.
*
* @param FederationLib $federationLib
*/
public function __construct(FederationLib $federationLib)
{
$this->federationLib = $federationLib;
}
/**
* Registers a client into the database, returns the UUID that was generated for the client.
*
* @param Client $client
* @return string
* @throws DatabaseException
*/
public function registerClient(Client $client): string
{
$qb = Database::getConnection()->createQueryBuilder();
$qb->insert(DatabaseTables::CLIENTS);
$uuid = Uuid::v4()->toRfc4122();
foreach($client->toArray() as $key => $value)
{
switch($key)
{
case 'id':
$qb->setValue($key, ':' . $key);
$qb->setParameter($key, $uuid);
break;
case 'name':
if($value === null || strlen($value) === 0 || !preg_match('/^[a-zA-Z0-9_\-]+$/', $value ))
{
$value = Utilities::generateName(4);
Log::debug('net.nosial.federationlib', sprintf('generated name for client: %s', $value));
}
$qb->setValue($key, ':' . $key);
$qb->setParameter($key, substr($value, 0, 64));
break;
case 'description':
if($value !== null)
{
$qb->setValue($key, ':' . $key);
$qb->setParameter($key, substr($value, 0, 255));
}
break;
case 'enabled':
$qb->setValue($key, ':' . $key);
$qb->setParameter($key, $value ? 1 : 0);
break;
case 'seen_timestamp':
case 'updated_timestamp':
case 'created_timestamp':
$qb->setValue($key, ':' . $key);
$qb->setParameter($key, time());
break;
default:
$qb->setValue($key, ':' . $key);
$qb->setParameter($key, $value);
break;
}
}
try
{
$qb->executeStatement();
}
catch(Exception $e)
{
throw new DatabaseException('Failed to register client: ' . $e->getMessage(), $e);
}
$this->federationLib->getEventLogManager()->logEvent(
EventCode::CLIENT_CREATED, EventPriority::LOW, null,
sprintf('Registered client with UUID %s', $uuid)
);
Log::info('net.nosial.federationlib', sprintf('Registered client with UUID %s', $uuid));
return $uuid;
}
/**
* Returns an existing client from the database.
*
* @param string|Client $uuid
* @return Client
* @throws ClientNotFoundException
* @throws DatabaseException
*/
public function getClient(string|Client $uuid): Client
{
if($uuid instanceof Client)
{
$uuid = $uuid->getUuid();
}
if(Configuration::isRedisCacheClientObjectsEnabled())
{
try
{
if(Redis::getConnection()?->exists(sprintf('Client<%s>', $uuid)))
{
return Client::fromArray(Redis::getConnection()?->hGetAll(sprintf('Client<%s>', $uuid)));
}
}
catch(Exception $e)
{
Log::warning('net.nosial.federationlib', sprintf('Failed to get Client from redis: %s', $e->getMessage()));
}
}
$qb = Database::getConnection()->createQueryBuilder();
$qb->select('*');
$qb->from(DatabaseTables::CLIENTS);
$qb->where('uuid = :uuid');
$qb->setParameter('uuid', $uuid);
try
{
$result = $qb->executeQuery();
if($result->rowCount() === 0)
{
throw new ClientNotFoundException($uuid);
}
$client = Client::fromArray($result->fetchAssociative());
}
catch(ClientNotFoundException $e)
{
throw $e;
}
catch(Exception $e)
{
throw new DatabaseException('Failed to get Client: ' . $e->getMessage(), $e);
}
if(Configuration::isRedisCacheClientObjectsEnabled())
{
try
{
Redis::getConnection()?->hMSet((string)$client, $client->toArray());
Redis::getConnection()?->expire((string)$client, Configuration::getRedisCacheClientObjectsTTL());
}
catch(Exception $e)
{
Log::warning('net.nosial.federationlib', sprintf('Failed to cache Client in redis: %s', $e->getMessage()));
}
}
return $client;
}
/**
* Updates a client record in the database, if the client does not exist it will be created.
* This function is cache aware, if the client is cached it will only update the changed values.
*
* @param Client $client
* @return void
* @throws DatabaseException
*/
public function updateClient(Client $client): void
{
$cached_client = null;
if(Configuration::isRedisCacheClientObjectsEnabled())
{
try
{
if(Redis::getConnection()?->exists(sprintf('Client<%s>', $client->getUuid())))
{
$cached_client = Client::fromArray(Redis::getConnection()?->hGetAll(sprintf('Client<%s>', $client->getUuid())));
}
}
catch(Exception $e)
{
Log::warning('net.nosial.federationlib', sprintf('Failed to get Client from redis: %s', $e->getMessage()));
}
}
$qb = Database::getConnection()->createQueryBuilder();
$qb->update(DatabaseTables::CLIENTS);
$qb->set('updated_timestamp', ':updated_timestamp');
$qb->setParameter('updated_timestamp', time());
$qb->where('uuid = :uuid');
$qb->setParameter('uuid', $client->getUuid());
if($cached_client instanceof Client)
{
$data = array_diff($client->toArray(), $cached_client->toArray());
}
else
{
$data = $client->toArray();
}
foreach($data as $key => $value)
{
switch($key)
{
case 'uuid':
case 'created_timestamp':
case 'updated_timestamp':
case 'seen_timestamp':
break;
case 'name':
if($value === null || strlen($value) === 0 || !preg_match('/^[a-zA-Z0-9_\-]+$/', $value ))
{
break;
}
$qb->set($key, ':' . $key);
$qb->setParameter($key, substr($value, 0, 64));
break;
case 'description':
if($value !== null)
{
$qb->set($key, ':' . $key);
$qb->setParameter($key, substr($value, 0, 255));
}
break;
case 'enabled':
$qb->set($key, ':' . $key);
$qb->setParameter($key, $value ? 1 : 0);
break;
default:
$qb->set($key, ':' . $key);
$qb->setParameter($key, $value);
break;
}
}
try
{
$qb->executeStatement();
}
catch(Exception $e)
{
throw new DatabaseException('Failed to update client: ' . $e->getMessage(), $e);
}
if(Configuration::isRedisCacheClientObjectsEnabled())
{
// Update the differences in the cache
if($cached_client instanceof Client)
{
try
{
Redis::getConnection()?->hMSet((string)$client, array_diff($client->toArray(), $cached_client->toArray()));
Redis::getConnection()?->expire((string)$client, Configuration::getRedisCacheClientObjectsTTL());
}
catch(Exception $e)
{
Log::warning('net.nosial.federationlib', sprintf('Failed to cache client in redis: %s', $e->getMessage()));
}
}
else
{
try
{
Redis::getConnection()?->hMSet((string)$client, $client->toArray());
Redis::getConnection()?->expire((string)$client, $client->getUuid(), Configuration::getRedisCacheClientObjectsTTL());
}
catch(Exception $e)
{
Log::warning('net.nosial.federationlib', sprintf('Failed to cache Client in redis: %s', $e->getMessage()));
}
}
}
}
/**
* Updates a client's last seen timestamp.
*
* @param string|Client $uuid
* @return void
* @throws DatabaseException
* @noinspection PhpUnused
*/
public function updateLastSeen(string|Client $uuid): void
{
if($uuid instanceof Client)
{
$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);
try
{
$qb->executeStatement();
}
catch(Exception $e)
{
throw new DatabaseException('Failed to update last seen timestamp: ' . $e->getMessage(), $e);
}
// Update the 'seen_timestamp' only in the hash table in redis
if(Configuration::isRedisCacheClientObjectsEnabled())
{
try
{
Redis::getConnection()?->hSet(sprintf('Client<%s>', $uuid), 'seen_timestamp', $timestamp);
}
catch(Exception $e)
{
Log::warning('net.nosial.federationlib', sprintf('Failed to update last seen timestamp in redis: %s', $e->getMessage()));
}
}
}
/**
* 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', 'query_permission', 'update_permission',
'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);
}
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())
{
$clients[] = Client::fromArray($row);
}
}
catch(Exception $e)
{
throw new DatabaseException('Failed to list clients: ' . $e->getMessage(), $e->getCode(), $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->getCode(), $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|Client $uuid
* @return void
* @throws DatabaseException
*/
public function deleteClient(string|Client $uuid): void
{
if($uuid instanceof Client)
{
$uuid = $uuid->getUuid();
}
$qb = Database::getConnection()->createQueryBuilder();
$qb->delete(DatabaseTables::CLIENTS);
$qb->where('uuid = :uuid');
$qb->setParameter('uuid', $uuid);
try
{
$qb->executeStatement();
}
catch(Exception $e)
{
throw new DatabaseException('Failed to delete client: ' . $e->getMessage(), $e->getCode(), $e);
}
// Invalidate the cache
if(Configuration::isRedisCacheClientObjectsEnabled())
{
try
{
Redis::getConnection()?->del(sprintf('Client<%s>', $uuid));
}
catch(Exception $e)
{
Log::warning('net.nosial.federationlib', sprintf('Failed to invalidate client cache in redis: %s', $e->getMessage()));
}
}
}
}

View file

@ -0,0 +1,172 @@
<?php
namespace FederationLib\Managers;
use Doctrine\DBAL\ParameterType;
use Exception;
use FederationLib\Classes\Configuration;
use FederationLib\Classes\Database;
use FederationLib\Enums\DatabaseTables;
use FederationLib\Enums\Misc;
use FederationLib\Enums\Standard\InternetPeerType;
use FederationLib\Enums\Standard\PeerType;
use FederationLib\Enums\Standard\UserPeerType;
use FederationLib\Exceptions\DatabaseException;
use FederationLib\Exceptions\Standard\PeerNotFoundException;
use FederationLib\Exceptions\Standard\UnsupportedPeerType;
use FederationLib\FederationLib;
use FederationLib\Objects\Client;
use FederationLib\Objects\ParsedFederatedAddress;
use FederationLib\Objects\Peer;
use LogLib\Log;
class PeerManager
{
/**
* @var FederationLib
*/
private $federationLib;
/**
* @param FederationLib $federationLib
*/
public function __construct(FederationLib $federationLib)
{
$this->federationLib = $federationLib;
}
/**
* Returns the Peer Type of the federated address, returns "unknown" if the
* type is not supported by the server
*
* @param string $type
* @return string
*/
private function getPeerType(string $type): string
{
if(in_array(strtolower($type), InternetPeerType::ALL))
return PeerType::INTERNET;
if(in_array(strtolower($type), UserPeerType::ALL))
return PeerType::USER;
return PeerType::UNKNOWN;
}
/**
* Parses a raw federated address and returns a ParsedFederatedAddress object
*
* @param string $federated_address
* @return ParsedFederatedAddress
* @throws UnsupportedPeerType
*/
private function parseAddress(string $federated_address): ParsedFederatedAddress
{
$parsed_address = new ParsedFederatedAddress($federated_address);
if($this->getPeerType($parsed_address->getPeerType()) === PeerType::UNKNOWN)
{
throw new UnsupportedPeerType($parsed_address->getPeerType());
}
return $parsed_address;
}
/**
* Registers a new peer into the database
*
* @param string|Client $client_uuid
* @param string $federated_address
* @return void
* @throws DatabaseException
* @throws UnsupportedPeerType
*/
public function registerPeer(string|Client $client_uuid, string $federated_address): void
{
// If the client_uuid is a Client object, get the UUID from it
if ($client_uuid instanceof Client)
{
$client_uuid = $client_uuid->getUuid();
}
// Check if the peer type is supported by the server
$parsed_address = $this->parseAddress($federated_address);
try
{
// Generate a query to insert the peer into the database
$query_builder = Database::getConnection()->createQueryBuilder();
$query_builder->insert(DatabaseTables::PEERS);
$timestamp = time();
$query_builder->values([
'federated_address' => $query_builder->createNamedParameter($parsed_address->getAddress()),
'client_first_seen' => $query_builder->createNamedParameter($client_uuid),
'client_last_seen' => $query_builder->createNamedParameter($client_uuid),
'active_restriction' => $query_builder->createNamedParameter(null, ParameterType::NULL),
'discovered_timestamp' => $query_builder->createNamedParameter($timestamp, ParameterType::INTEGER),
'seen_timestamp' => $query_builder->createNamedParameter($timestamp, ParameterType::INTEGER),
]);
$query_builder->executeStatement();
}
catch(Exception $e)
{
throw new DatabaseException(sprintf('Failed to register peer %s: %s', $parsed_address->getAddress(), $e->getMessage()), $e);
}
Log::info(Misc::FEDERATIONLIB, sprintf('Registered new peer: %s', $parsed_address->getAddress()));
}
public function cachePeerObject(Peer $peer): void
{
if(!Configuration::isRedisEnabled() && !Configuration::isPeerObjectsCached())
{
return;
}
}
/**
* Fetches a peer from the database by its federated address
*
* @param string $federated_address
* @return Peer
* @throws DatabaseException
* @throws PeerNotFoundException
* @throws UnsupportedPeerType
*/
public function getPeer(string $federated_address): Peer
{
// Check if the peer type is supported by the server
$parsed_address = $this->parseAddress($federated_address);
try
{
$query_builder = Database::getConnection()->createQueryBuilder();
$query_builder->select('*');
$query_builder->from(DatabaseTables::PEERS);
$query_builder->where('federated_address = :federated_address');
$query_builder->setParameter('federated_address', $parsed_address->getAddress());
$query_builder->setMaxResults(1);
$result = $query_builder->executeQuery();
if($result->rowCount() === 0)
{
throw new PeerNotFoundException($parsed_address->getAddress());
}
return Peer::fromArray($result->fetchAssociative());
}
catch(PeerNotFoundException $e)
{
throw $e;
}
catch(Exception $e)
{
throw new DatabaseException(sprintf('Failed to get peer %s: %s', $parsed_address->getAddress(), $e->getMessage()), $e);
}
}
}

View file

@ -0,0 +1,380 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace FederationLib\Objects;
use FederationCLI\Utilities;
use FederationLib\Classes\Security;
use FederationLib\Interfaces\SerializableObjectInterface;
class Client implements SerializableObjectInterface
{
/**
* @var string
*/
private $uuid;
/**
* @var bool
*/
private $enabled;
/**
* @var string|null
*/
private $name;
/**
* @var string|null
*/
private $description;
/**
* @var string|null
*/
private $secret_totp;
/**
* @var int
*/
private $query_permission;
/**
* @var int
*/
private $update_permission;
/**
* @var string[]
*/
private $flags;
/**
* @var int
*/
private $created_timestamp;
/**
* @var int
*/
private $updated_timestamp;
/**
* @var int
*/
private $seen_timestamp;
/**
* Client constructor.
*/
public function __construct()
{
$this->enabled = true;
$this->flags = [];
$this->query_permission = 1;
$this->update_permission = 0;
}
/**
* Returns the client's unique ID.
*
* @return string
*/
public function getUuid(): string
{
return $this->uuid;
}
/**
* @return bool
*/
public function isEnabled(): bool
{
return $this->enabled;
}
/**
* @param bool $enabled
*/
public function setEnabled(bool $enabled): void
{
$this->enabled = $enabled;
}
/**
* @return string|null
*/
public function getName(): string
{
return $this->name;
}
/**
* @param string|null $name
*/
public function setName(?string $name): void
{
$this->name = $name;
}
/**
* @return string|null
*/
public function getDescription(): ?string
{
return $this->description;
}
/**
* @param string|null $description
*/
public function setDescription(?string $description): void
{
$this->description = $description;
}
/**
* Enables authentication for the client, returns the secret.
*
* @return string
*/
public function enableAuthentication(): string
{
$this->secret_totp = Security::generateSecret();
return $this->secret_totp;
}
/**
* Disables authentication for the client, wipes the secret.
*
* @return void
*/
public function disableAuthentication(): void
{
$this->secret_totp = null;
}
/**
* Validates the given authentication code against the client's secret.
*
* @param string|null $authentication_code
* @param int|null $timestamp
* @return bool
*/
public function validateAuthentication(?string $authentication_code=null, ?int $timestamp=null): bool
{
if($this->secret_totp === null)
{
// Always authenticate if authentication is disabled.
return true;
}
if($authentication_code === null)
{
// Authentication code not provided but required.
return false;
}
// Authentication Code: sha1(client_id + totp_code)
return hash('sha1', $this->uuid . Security::generateCode($this->secret_totp, $timestamp)) === $authentication_code;
}
/**
* Returns the client's query permission level.
*
* @return int
*/
public function getQueryPermission(): int
{
return $this->query_permission;
}
/**
* Sets the client's query permission level.
*
* @param int $query_permission
*/
public function setQueryPermission(int $query_permission): void
{
$this->query_permission = $query_permission;
}
/**
* Returns the client's update permission level.
*
* @return int
*/
public function getUpdatePermission(): int
{
return $this->update_permission;
}
/**
* Sets the client's update permission.
*
* @param int $update_permission
*/
public function setUpdatePermission(int $update_permission): void
{
$this->update_permission = $update_permission;
}
/**
* Returns the client's flags.
*
* @return string[]
*/
public function getFlags(): array
{
return $this->flags;
}
/**
* Sets an array of flags for the client.
* This function overrides any existing flags.
*
* @param string[] $flags
*/
public function setFlags(array $flags): void
{
$this->flags = $flags;
}
/**
* Appends a flag to the client's flags.
*
* @param string $flag
* @return void
*/
public function appendFlag(string $flag): void
{
if(!in_array($flag, $this->flags))
{
$this->flags[] = $flag;
}
}
/**
* Removes a flag from the client's flags.
*
* @param string $flag
* @return void
*/
public function removeFlag(string $flag): void
{
$this->flags = array_diff($this->flags, [$flag]);
}
/**
* Returns True if the client has the given flag.
*
* @param string $flag
* @return bool
*/
public function hasFlag(string $flag): bool
{
return in_array($flag, $this->flags);
}
/**
* Returns the Unix Timestamp for when the client was last created on the server
*
* @return int
*/
public function getCreatedTimestamp(): int
{
return $this->created_timestamp;
}
/**
* Returns the Unix Timestamp for when the client was last updated on the server.
*
* @return int
*/
public function getUpdatedTimestamp(): int
{
return $this->updated_timestamp;
}
/**
* Returns the Unix Timestamp for when the client was last seen by the server.
*
* @return int
*/
public function getSeenTimestamp(): int
{
return $this->seen_timestamp;
}
/**
* Returns an array representation of the object
*
* @return array
*/
public function toArray(): array
{
$flags = null;
if($this->flags !== null && count($this->flags) > 0)
{
$flags = implode(',', $this->flags);
}
return [
'uuid' => $this->uuid,
'enabled' => $this->enabled,
'name' => $this->name,
'description' => $this->description,
'secret_totp' => $this->secret_totp,
'query_permission' => $this->query_permission,
'update_permission' => $this->update_permission,
'flags' => $flags,
'created_timestamp' => $this->created_timestamp,
'updated_timestamp' => $this->updated_timestamp,
'seen_timestamp' => $this->seen_timestamp,
];
}
/**
* Constructs object from an array representation.
*
* @param array $array
* @return Client
*/
public static function fromArray(array $array): Client
{
$client = new self();
$client->uuid = $array['uuid'] ?? null;
$client->enabled = Utilities::parseBoolean($array['enabled']);
$client->name = $array['name'] ?? null;
$client->description = $array['description'] ?? null;
$client->secret_totp = $array['secret_totp'] ?? null;
$client->query_permission = $array['query_permission'] ?? 0;
$client->update_permission = $array['update_permission'] ?? 0;
if(isset($array['flags']))
{
$client->flags = explode(',', $array['flags']);
}
else
{
$client->flags = [];
}
$client->created_timestamp = $array['created_timestamp'] ?? 0;
$client->updated_timestamp = $array['updated_timestamp'] ?? 0;
$client->seen_timestamp = $array['seen_timestamp'] ?? 0;
return $client;
}
/**
* Returns the object identifier.
*
* @return string
*/
public function __toString(): string
{
return sprintf('Client<%s>', $this->uuid ?? spl_object_hash($this));
}
}

View file

@ -0,0 +1,78 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace FederationLib\Objects;
class ParsedFederatedAddress
{
/**
* @var string
*/
private $source;
/**
* @var string
*/
private $peer_type;
/**
* @var string
*/
private $unique_identifier;
/**
* Public Constructor, parses the federated address and sets the properties
*
* @param string $federated_address
*/
public function __construct(string $federated_address)
{
preg_match("/(?<source>[a-z0-9]+)\.(?<type>[a-z0-9]+):(?<id>.+)/", $federated_address, $matches);
$this->source = $matches['source'];
$this->peer_type = $matches['type'];
$this->unique_identifier = $matches['id'];
}
/**
* Returns the platform source of the peer
*
* @return string
*/
public function getSource(): string
{
return $this->source;
}
/**
* Returns the peer type of the platform
*
* @return string
*/
public function getPeerType(): string
{
return $this->peer_type;
}
/**
* Returns the Unique Identifier of the peer
*
* @return string
*/
public function getUniqueIdentifier(): string
{
return $this->unique_identifier;
}
/**
* Returns the Standard Federated Address of the peer
*
* @return string
*/
public function getAddress(): string
{
return sprintf('%s.%s:%s', $this->source, $this->peer_type, $this->unique_identifier);
}
}

View file

@ -0,0 +1,138 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace FederationLib\Objects;
use FederationLib\Interfaces\SerializableObjectInterface;
class Peer implements SerializableObjectInterface
{
/**
* @var string
*/
private $federated_address;
/**
* @var string
*/
private $client_first_seen;
/**
* @var string
*/
private $client_last_seen;
/**
* @var string|null
*/
private $active_restriction;
/**
* @var int
*/
private $discovered_timestamp;
/**
* @var int
*/
private $seen_timestamp;
/**
* Returns the Standard Federated Address for the peer
*
* @return string
*/
public function getFederatedAddress(): string
{
return $this->federated_address;
}
/**
* Returns the client UUID that first saw the peer
*
* @return string
*/
public function getClientFirstSeen(): string
{
return $this->client_first_seen;
}
/**
* Returns the client UUID that last saw the peer
*
* @return string
*/
public function getClientLastSeen(): string
{
return $this->client_last_seen;
}
/**
* Optional. Returns the active restriction ID for the peer
* This is only available if the peer is currently restricted
*
* @return string|null
*/
public function getActiveRestriction(): ?string
{
return $this->active_restriction;
}
/**
* Returns the Unix Timestamp for when the peer was first discovered by a client
*
* @return int
*/
public function getDiscoveredTimestamp(): int
{
return $this->discovered_timestamp;
}
/**
* Returns the Unix Timestamp for when the peer was last seen by a client
*
* @return int
*/
public function getSeenTimestamp(): int
{
return $this->seen_timestamp;
}
/**
* Returns an array representation of the object
*
* @return array
*/
public function toArray(): array
{
return [
'federated_address' => $this->federated_address,
'client_first_seen' => $this->client_first_seen,
'client_last_seen' => $this->client_last_seen,
'active_restriction' => $this->active_restriction,
'discovered_timestamp' => $this->discovered_timestamp,
'seen_timestamp' => $this->seen_timestamp,
];
}
/**
* Constructs object from an array representation
*
* @param array $array
* @return Peer
*/
public static function fromArray(array $array): Peer
{
$object = new self();
$object->federated_address = $array['federated_address'] ?? null;
$object->client_first_seen = $array['client_first_seen'] ?? null;
$object->client_last_seen = $array['client_last_seen'] ?? null;
$object->active_restriction = $array['active_restriction'] ?? null;
$object->discovered_timestamp = $array['discovered_timestamp'] ?? null;
$object->seen_timestamp = $array['seen_timestamp'] ?? null;
return $object;
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace FederationLib\Objects;
class QueryDocument
{
private $subject_type;
private $client_id;
private $client_totp_signature;
private $timstamp;
private $platform;
private $event_type;
private $channel_peer;
private $resent_from_peer;
private
}

14
src/README.md Normal file → Executable file
View file

@ -1,3 +1,15 @@
# FederationLib
Coming Soon ...
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.

18
tests/base32_test.php Normal file
View file

@ -0,0 +1,18 @@
<?php
require 'ncc';
import('net.nosial.federationlib');
$data = "Hello World!";
print(sprintf("Original data: %s\n", $data));
$encoded = \FederationLib\Classes\Base32::encode($data);
print(sprintf("Encoded data: %s\n", $encoded));
$decoded = \FederationLib\Classes\Base32::decode($encoded);
print(sprintf("Decoded data: %s\n", $decoded));
print(sprintf("Original data equals decoded data: %s\n", $data === $decoded ? 'true' : 'false'));
exit(0);

View file

@ -0,0 +1,25 @@
<?php
require 'ncc';
import('net.nosial.federationlib');
$federationlib = new \FederationLib\FederationLib();
$client = new \FederationLib\Objects\Client();
$client->setDescription('This is a test client generated by a test script.');
$uuid = $federationlib->getClientManager()->registerClient($client);
print(sprintf('Client UUID: %s', $uuid) . PHP_EOL);
$client = $federationlib->getClientManager()->getClient($uuid);
$client->setName('Test Client');
$client->setDescription('This is a test client generated by a test script. It has been updated.');
$federationlib->getClientManager()->updateClient($client);
$client = $federationlib->getClientManager()->getClient($uuid);
print(sprintf('Client name: %s', $client->getName()) . PHP_EOL);
print(sprintf('Client description: %s', $client->getDescription()) . PHP_EOL);
exit(0);

View file

@ -0,0 +1,14 @@
<?php
require 'ncc';
import('net.nosial.federationlib');
$federationlib = new \FederationLib\FederationLib();
$client = new \FederationLib\Objects\Client();
$client->setDescription('This is a test client generated by a test script.');
$uuid = $federationlib->getClientManager()->registerClient($client);
print(sprintf('Client UUID: %s', $uuid) . PHP_EOL);
exit(0);

View file

@ -0,0 +1,18 @@
<?php
require 'ncc';
import('net.nosial.federationlib');
$federationlib = new \FederationLib\FederationLib();
// loop 50 times to create 50 clients
for($i = 0; $i < 50; $i++)
{
$client = new \FederationLib\Objects\Client();
$client->setDescription('This is a test client generated by a test script.');
$client->enableAuthentication();
$uuid = $federationlib->getClientManager()->registerClient($client);
print(sprintf('Client UUID: %s', $uuid) . PHP_EOL);
}

View file

@ -0,0 +1,8 @@
<?php
require 'ncc';
import('net.nosial.federationlib');
$federationlib = new \FederationLib\FederationLib();
var_dump($federationlib->getClientManager()->listClients(1));

15
tests/totp_test.php Normal file
View file

@ -0,0 +1,15 @@
<?php
require 'ncc';
import('net.nosial.federationlib');
$secret_file = __DIR__ . DIRECTORY_SEPARATOR . 'secret.txt';
if(!file_exists($secret_file))
{
file_put_contents($secret_file, \FederationLib\Classes\Security::generateSecret());
}
$secret = file_get_contents($secret_file);
$code = \FederationLib\Classes\Security::generateCode($secret);
print(sprintf("Code: %s\n", $code));
exit(0);