feat(cloudron): add tirreno package artifacts

- Add CloudronStack/output/CloudronPackages-Artifacts/tirreno/ directory and its contents
- Includes package manifest, Dockerfile, source code, documentation, and build artifacts
- Add tirreno-1761840148.tar.gz as a build artifact
- Add tirreno-cloudron-package-1761841304.tar.gz as the Cloudron package
- Include all necessary files for the tirreno Cloudron package

This adds the complete tirreno Cloudron package artifacts to the repository.
This commit is contained in:
2025-10-30 11:43:06 -05:00
parent 0ce353ea9d
commit 91d52d2de5
1692 changed files with 202851 additions and 0 deletions

View File

@@ -0,0 +1,353 @@
<?php
/**
* Tirreno ~ Open source user analytics
* Copyright (c) Tirreno Technologies Sàrl (https://www.tirreno.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Tirreno Technologies Sàrl (https://www.tirreno.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.tirreno.com Tirreno(tm)
*/
namespace Models\Context;
class Data {
private \Models\Context\User $userModel;
private \Models\Context\Ip $ipModel;
private \Models\Context\Device $deviceModel;
private \Models\Context\Email $emailModel;
private \Models\Context\Phone $phoneModel;
private \Models\Context\Event $eventModel;
private \Models\Context\Session $sessionModel;
private \Models\ApiKeys $keyModel;
private array $suspiciousWordsUrl;
private array $suspiciousWordsUserAgent;
private array $suspiciousWordsEmail;
public function __construct() {
$this->userModel = new User();
$this->ipModel = new Ip();
$this->deviceModel = new Device();
$this->emailModel = new Email();
$this->phoneModel = new Phone();
$this->eventModel = new Event();
$this->sessionModel = new Session();
$this->keyModel = new \Models\ApiKeys();
$this->suspiciousWordsUrl = \Utils\WordsLists\Url::getWords();
$this->suspiciousWordsUserAgent = \Utils\WordsLists\UserAgent::getWords();
$this->suspiciousWordsEmail = \Utils\WordsLists\Email::getWords();
}
public function getContext(array $accountIds, int $apiKey): array {
$userDetails = $this->userModel->getContext($accountIds, $apiKey);
$ipDetails = $this->ipModel->getContext($accountIds, $apiKey);
$deviceDetails = $this->deviceModel->getContext($accountIds, $apiKey);
$emailDetails = $this->emailModel->getContext($accountIds, $apiKey);
$phoneDetails = $this->phoneModel->getContext($accountIds, $apiKey);
$eventDetails = $this->eventModel->getContext($accountIds, $apiKey);
//$domainDetails = $this->domainModel->getContext($accountIds, $apiKey);
$timezoneName = $this->keyModel->getTimezoneByKeyId($apiKey);
$utcTime = new \DateTime('now', new \DateTimeZone('UTC'));
$timezone = new \DateTimeZone($timezoneName);
$offsetInSeconds = $timezone->getOffset($utcTime);
// get only suspicious sessions
$sessionDetails = $this->sessionModel->getContext($accountIds, $apiKey, $offsetInSeconds);
//Extend user details context
foreach ($userDetails as $userId => $user) {
$user['le_exists'] = ($user['le_email'] ?? null) !== null;
$user['le_email'] = $user['le_email'] ?? '';
$user['le_local_part'] = explode('@', $user['le_email'])[0] ?? '';
$user['le_domain_part'] = explode('@', $user['le_email'])[1] ?? '';
$userId = $user['ea_id'];
$ip = $ipDetails[$userId] ?? [];
$device = $deviceDetails[$userId] ?? [];
$email = $emailDetails[$userId] ?? [];
$phone = $phoneDetails[$userId] ?? [];
$events = $eventDetails[$userId] ?? [];
$session = $sessionDetails[$userId] ?? [];
$user['eip_ip_id'] = $ip['eip_ip_id'] ?? [];
$user['eip_ip'] = $ip['eip_ip'] ?? [];
$user['eip_cidr'] = $ip['eip_cidr'] ?? [];
$user['eip_country_id'] = $ip['eip_country_id'] ?? [];
$user['eip_data_center'] = $ip['eip_data_center'] ?? [];
$user['eip_tor'] = $ip['eip_tor'] ?? [];
$user['eip_vpn'] = $ip['eip_vpn'] ?? [];
$user['eip_relay'] = $ip['eip_relay'] ?? [];
$user['eip_starlink'] = $ip['eip_starlink'] ?? [];
$user['eip_total_visit'] = $ip['eip_total_visit'] ?? [];
$user['eip_blocklist'] = $ip['eip_blocklist'] ?? [];
$user['eip_shared'] = $ip['eip_shared'] ?? [];
//$user['eip_domains'] = $ip['eip_domains'] ?? [];
$user['eip_country_id'] = $ip['eip_country_id'] ?? [];
$user['eip_fraud_detected'] = $ip['eip_fraud_detected'] ?? [];
$user['eip_alert_list'] = $ip['eip_alert_list'] ?? [];
$user['eip_domains_count_len'] = $ip['eip_domains_count_len'] ?? [];
$user['eup_device'] = $device['eup_device'] ?? [];
$user['eup_device_id'] = $device['eup_device_id'] ?? [];
$user['eup_browser_name'] = $device['eup_browser_name'] ?? [];
$user['eup_browser_version'] = $device['eup_browser_version'] ?? [];
$user['eup_os_name'] = $device['eup_os_name'] ?? [];
$user['eup_lang'] = $device['eup_lang'] ?? [];
$user['eup_ua'] = $device['eup_ua'] ?? [];
// $user['eup_lastseen'] = $device['eup_lastseen'] ?? [];
// $user['eup_created'] = $device['eup_created'] ?? [];
$user['ee_email'] = $email['ee_email'] ?? [];
$user['ee_earliest_breach'] = $email['ee_earliest_breach'] ?? [];
$user['ep_phone_number'] = $phone['ep_phone_number'] ?? [];
$user['ep_shared'] = $phone['ep_shared'] ?? [];
$user['ep_type'] = $phone['ep_type'] ?? [];
$user['event_ip'] = $events['event_ip'] ?? [];
$user['event_url_string'] = $events['event_url_string'] ?? [];
$user['event_empty_referer'] = $events['event_empty_referer'] ?? [];
$user['event_device'] = $events['event_device'] ?? [];
$user['event_type'] = $events['event_type'] ?? [];
$user['event_http_method'] = $events['event_http_method'] ?? [];
$user['event_http_code'] = $events['event_http_code'] ?? [];
$user['event_device_created'] = $events['event_device_created'] ?? [];
$user['event_device_lastseen'] = $events['event_device_lastseen'] ?? [];
$user['event_session_multiple_country'] = $session[0]['event_session_multiple_country'] ?? false;
$user['event_session_multiple_ip'] = $session[0]['event_session_multiple_ip'] ?? false;
$user['event_session_multiple_device'] = $session[0]['event_session_multiple_device'] ?? false;
$user['event_session_night_time'] = $session[0]['event_session_night_time'] ?? false;
//Extra params for rules
$user = $this->extendParams($user);
$userDetails[$userId] = $this->extendEventParams($user);
}
return $userDetails;
}
private function extendParams(array $record): array {
//$record['timezone']
$localPartLen = strlen($record['le_local_part']);
$domainPartLen = strlen($record['le_domain_part']);
$fullName = $this->getUserFullName($record);
$record['le_local_part_len'] = $localPartLen;
$record['ea_fullname_has_numbers'] = preg_match('~[0-9]+~', $fullName) > 0;
$record['ea_fullname_has_spaces_hyphens'] = preg_match('~[\-\s]~', $fullName) > 0;
$record['ea_days_since_account_creation'] = $this->getDaysSinceAccountCreation($record);
$record['ea_days_since_last_visit'] = $this->getDaysSinceLastVisit($record);
//$record['le_has_no_profiles'] = $record['le_profiles'] === 0;
$record['le_has_no_data_breaches'] = $record['le_data_breach'] === false;
$record['le_has_suspicious_str'] = $this->checkEmailForSuspiciousString($record);
$record['le_has_numeric_only_local_part'] = preg_match('/^[0-9]+$/', $record['le_local_part']) > 0;
$record['le_email_has_consec_s_chars'] = preg_match('/[^a-zA-Z0-9]{2,}/', $record['le_local_part']) > 0;
$record['le_email_has_consec_nums'] = preg_match('/\d{2}/', $record['le_local_part']) > 0;
$record['le_email_has_no_digits'] = !preg_match('/\d/', $record['le_local_part']);
$record['le_email_has_vowels'] = preg_match('/[aeoui]/i', $record['le_local_part']) > 0;
$record['le_email_has_consonants'] = preg_match('/[bcdfghjklmnpqrstvwxyz]/i', $record['le_local_part']) > 0;
$record['le_with_long_local_part_length'] = $localPartLen > \Utils\Constants::get('RULE_EMAIL_MAXIMUM_LOCAL_PART_LENGTH');
$record['le_with_long_domain_length'] = $domainPartLen > \Utils\Constants::get('RULE_EMAIL_MAXIMUM_DOMAIN_LENGTH');
$record['le_email_in_blockemails'] = $record['le_blockemails'] ?? false;
$record['le_is_invalid'] = $record['le_exists'] && filter_var($record['le_email'], FILTER_VALIDATE_EMAIL) === false;
$record['le_appears_on_alert_list'] = $record['le_alert_list'] ?? false;
$record['ld_is_disposable'] = $record['ld_disposable_domains'] ?? false;
$record['ld_days_since_domain_creation'] = $this->getDaysSinceDomainCreation($record);
$record['ld_domain_free_email_provider'] = $record['ld_free_email_provider'] ?? false;
$record['ld_from_blockdomains'] = $record['ld_blockdomains'] ?? false;
$record['ld_domain_without_mx_record'] = $record['ld_mx_record'] === false;
$record['ld_website_is_disabled'] = $record['ld_disabled'] ?? false;
$record['ld_tranco_rank'] = $record['ld_tranco_rank'] ?? -1;
$record['lp_invalid_phone'] = $record['lp_invalid'] === true;
$record['ep_shared_phone'] = (bool) count(array_filter($record['ep_shared'], static function ($item) {
return $item !== null && $item > 1;
}));
$daysSinceBreaches = array_map(function ($item) {
return $this->getDaysTillToday($item);
}, $record['ee_earliest_breach']);
$record['ee_days_since_first_breach'] = count($daysSinceBreaches) ? max($daysSinceBreaches) : -1;
$onlyNonResidentialParams = !(bool) count(array_filter(array_merge(
$record['eip_fraud_detected'],
$record['eip_blocklist'],
$record['eip_tor'],
$record['eip_starlink'],
$record['eip_relay'],
$record['eip_vpn'],
$record['eip_data_center'],
), static function ($value): bool {
return $value === true;
}));
$record['eip_only_residential'] = $onlyNonResidentialParams && !in_array(0, $record['eip_country_id']);
$record['eip_has_fraud'] = in_array(true, $record['eip_fraud_detected']);
$record['eip_unique_cidrs'] = count(array_unique($record['eip_cidr']));
$record['lp_fraud_detected'] = $record['lp_fraud_detected'] ?? false;
$record['le_fraud_detected'] = $record['le_fraud_detected'] ?? false;
$record['eup_has_rare_browser'] = (bool) count(array_diff($record['eup_browser_name'], array_keys(\Utils\Constants::get('RULE_REGULAR_BROWSER_NAMES'))));
$record['eup_has_rare_os'] = (bool) count(array_diff($record['eup_os_name'], \Utils\Constants::get('RULE_REGULAR_OS_NAMES')));
$record['eup_device_count'] = count($record['eup_device']);
$record['eup_vulnerable_ua'] = false;
if (count($this->suspiciousWordsUserAgent)) {
foreach ($record['eup_ua'] as $url) {
foreach ($this->suspiciousWordsUserAgent as $sub) {
if (stripos($url, $sub) !== false) {
$record['eup_vulnerable_ua'] = true;
break 2;
}
}
}
}
return $record;
}
private function extendEventParams(array $record): array {
// Remove null values specifically
$eventTypeFiltered = $this->filterStringNum($record['event_type']);
$eventHttpCodeFiltered = $this->filterStringNum($record['event_http_code']);
$eventTypeCount = array_count_values($eventTypeFiltered);
//$accountLoginFailId = \Utils\Constants::get('ACCOUNT_LOGIN_FAIL_EVENT_TYPE_ID');
$accountEmailChangeId = \Utils\Constants::get('ACCOUNT_EMAIL_CHANGE_EVENT_TYPE_ID');
$accountPwdChangeId = \Utils\Constants::get('ACCOUNT_PASSWORD_CHANGE_EVENT_TYPE_ID');
//$record['event_failed_login_attempts'] = $eventTypeCount[$accountLoginFailId] ?? 0;
$record['event_email_changed'] = array_key_exists($accountEmailChangeId, $eventTypeCount);
$record['event_password_changed'] = array_key_exists($accountPwdChangeId, $eventTypeCount);
$record['event_http_method_head'] = in_array(\Utils\Constants::get('EVENT_REQUEST_TYPE_HEAD'), $record['event_http_method']);
$record['event_empty_referer'] = in_array(true, $record['event_empty_referer'], true);
$clientErrors = 0;
$serverErrors = 0;
$successEvents = 0;
foreach ($eventHttpCodeFiltered as $code) {
if (is_int($code) && $code >= 400 && $code < 500) {
++$clientErrors;
} elseif (is_int($code) && $code >= 500 && $code < 600) {
++$serverErrors;
} elseif (is_int($code) && $code >= 200 && $code < 300) {
++$successEvents;
}
}
$record['event_multiple_5xx_http'] = $serverErrors;
$record['event_multiple_4xx_http'] = $clientErrors;
$record['event_2xx_http'] = (bool) $successEvents;
$record['event_vulnerable_url'] = false;
if (count($this->suspiciousWordsUrl)) {
foreach ($record['event_url_string'] as $url) {
foreach ($this->suspiciousWordsUrl as $sub) {
if (stripos($url, $sub) !== false) {
$record['event_vulnerable_url'] = true;
break 2;
}
}
}
}
return $record;
}
private function getDaysSinceDomainCreation(array $params): int {
$dt1 = date('Y-m-d');
$dt2 = $params['ld_creation_date'];
return $this->getDaysDiff($dt1, $dt2);
}
private function getDaysSinceAccountCreation(array $params): int {
$dt1 = date('Y-m-d');
$dt2 = $params['ea_created'] ?? null;
return $this->getDaysDiff($dt1, $dt2);
}
private function getDaysSinceLastVisit(array $params): int {
$dt1 = date('Y-m-d');
$dt2 = $params['ea_lastseen'] ?? null;
return $this->getDaysDiff($dt1, $dt2);
}
private function getDaysTillToday(?string $dt2): int {
$diff = -1;
if ($dt2 !== null) {
$dt1 = date('Y-m-d');
$dt1 = new \DateTime($dt1);
$dt2 = new \DateTime($dt2);
$diff = $dt1->diff($dt2)->format('%a');
}
return $diff;
}
private function getDaysDiff(?string $dt1, ?string $dt2): int {
$diff = -1;
if ($dt2) {
$dt1 = new \DateTime($dt1);
$dt2 = new \DateTime($dt2);
$diff = $dt1->diff($dt2)->format('%a');
}
return $diff;
}
private function getUserFullName(array $record): string {
$name = [];
$fName = $record['ea_firstname'] ?? '';
if ($fName) {
$name[] = $fName;
}
$lName = $record['ea_lastname'] ?? '';
if ($lName) {
$name[] = $lName;
}
return trim(join(' ', $name));
}
private function checkEmailForSuspiciousString(array $record): bool {
foreach ($this->suspiciousWordsEmail as $sub) {
if (stripos($record['le_email'], $sub) !== false) {
return true;
}
}
return false;
}
private function filterStringNum(array $record): array {
return array_filter($record, static function ($value): bool {
return is_string($value) || is_int($value);
});
}
}