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,53 @@
<?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;
abstract class Base extends \Models\BaseSql {
protected $DB_TABLE_NAME = 'event';
abstract public function getContext(array $accountIds, int $apiKey): array;
abstract protected function getDetails(array $accountIds, int $apiKey): array;
protected function getRequestParams(array $accountIds, int $apiKey): array {
[$params, $placeHolders] = $this->getArrayPlaceholders($accountIds);
$params[':api_key'] = $apiKey;
return [$params, $placeHolders];
}
protected function groupRecordsByAccount(array $records): array {
$recordsByAccount = [];
$iters = count($records);
for ($i = 0; $i < $iters; ++$i) {
$item = $records[$i];
$accountId = $item['accountid'];
if (!isset($recordsByAccount[$accountId])) {
$recordsByAccount[$accountId] = [];
}
$recordsByAccount[$accountId][] = $item;
}
return $recordsByAccount;
}
protected function getUniqueArray(array $array): array {
return array_values(array_unique($array));
}
}

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

View File

@@ -0,0 +1,70 @@
<?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 Device extends Base {
public function getContext(array $accountIds, int $apiKey): array {
$record = $this->getDetails($accountIds, $apiKey);
$recordByAccount = $this->groupRecordsByAccount($record);
foreach ($recordByAccount as $key => $value) {
$recordByAccount[$key] = [
'eup_device' => array_column($value, 'eup_device'),
'eup_device_id' => array_column($value, 'eup_device_id'),
'eup_browser_name' => array_column($value, 'eup_browser_name'),
'eup_browser_version' => array_column($value, 'eup_browser_version'),
'eup_os_name' => array_column($value, 'eup_os_name'),
'eup_lang' => array_column($value, 'eup_lang'),
'eup_ua' => array_column($value, 'eup_ua'),
// 'eup_lastseen' => array_column($value, 'eup_lastseen'),
// 'eup_created' => array_column($value, 'eup_created'),
];
}
return $recordByAccount;
}
protected function getDetails(array $accountIds, int $apiKey): array {
[$params, $placeHolders] = $this->getRequestParams($accountIds, $apiKey);
$query = (
"SELECT
event_device.account_id AS accountid,
event_device.id AS eup_device_id,
event_ua_parsed.device AS eup_device,
event_ua_parsed.browser_name AS eup_browser_name,
event_ua_parsed.browser_version AS eup_browser_version,
event_ua_parsed.os_name AS eup_os_name,
event_ua_parsed.ua AS eup_ua,
-- event_device.lastseen AS eup_lastseen,
-- event_device.created AS eup_created,
event_device.lang AS eup_lang
FROM
event_device
INNER JOIN event_ua_parsed
ON(event_device.user_agent=event_ua_parsed.id)
WHERE
event_device.key = :api_key
AND event_ua_parsed.checked = true
AND event_device.account_id IN ({$placeHolders})"
);
return $this->execQuery($query, $params);
}
}

View File

@@ -0,0 +1,65 @@
<?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 Domain extends Base {
public function getContext(array $accountIds, int $apiKey): array {
$records = $this->getDetails($accountIds, $apiKey);
$recordsByAccount = $this->groupRecordsByAccount($records);
foreach ($recordsByAccount as $key => $value) {
$recordsByAccount[$key] = [
'ed_domain' => $this->getUniqueArray(array_column($value, 'ed_domain')),
'ed_blockdomains' => $this->getUniqueArray(array_column($value, 'ed_blockdomains')),
'ed_disposable_domains' => $this->getUniqueArray(array_column($value, 'ed_disposable_domains')),
'ed_free_email_provider' => $this->getUniqueArray(array_column($value, 'ed_free_email_provider')),
'ed_creation_date' => $this->getUniqueArray(array_column($value, 'ed_creation_date')),
'ed_disabled' => $this->getUniqueArray(array_column($value, 'ed_disabled')),
'ed_mx_record' => $this->getUniqueArray(array_column($value, 'ed_mx_record')),
];
}
return $recordsByAccount;
}
protected function getDetails(array $accountIds, int $apiKey): array {
[$params, $placeHolders] = $this->getRequestParams($accountIds, $apiKey);
$query = (
"SELECT
event_email.account_id AS accountid,
event_domain.domain AS ed_domain,
event_domain.blockdomains AS ed_blockdomains,
event_domain.disposable_domains AS ed_disposable_domains,
event_domain.free_email_provider AS ed_free_email_provider,
event_domain.creation_date AS ed_creation_date,
event_domain.disabled AS ed_disabled,
event_domain.mx_record AS ed_mx_record
FROM
event_domain
INNER JOIN event_email
ON event_domain.id = event_email.domain
WHERE
event_email.key = :api_key
AND event_email.account_id IN ({$placeHolders})"
);
return $this->execQuery($query, $params);
}
}

View File

@@ -0,0 +1,52 @@
<?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 Email extends Base {
public function getContext(array $accountIds, int $apiKey): array {
$records = $this->getDetails($accountIds, $apiKey);
$recordsByAccount = $this->groupRecordsByAccount($records);
foreach ($recordsByAccount as $key => $value) {
$recordsByAccount[$key] = [
'ee_email' => $this->getUniqueArray(array_column($value, 'ee_email')),
'ee_earliest_breach' => $this->getUniqueArray(array_column($value, 'ee_earliest_breach')),
];
}
return $recordsByAccount;
}
protected function getDetails(array $accountIds, int $apiKey): array {
[$params, $placeHolders] = $this->getRequestParams($accountIds, $apiKey);
$query = (
"SELECT
event_email.account_id AS accountid,
event_email.email AS ee_email,
event_email.earliest_breach AS ee_earliest_breach
FROM
event_email
WHERE
event_email.key = :api_key
AND event_email.checked = 'True'
AND event_email.account_id IN ({$placeHolders})"
);
return $this->execQuery($query, $params);
}
}

View File

@@ -0,0 +1,84 @@
<?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 Event extends Base {
public function getContext(array $accountIds, int $apiKey): array {
$records = $this->getDetails($accountIds, $apiKey);
$recordsByAccount = $this->groupRecordsByAccount($records);
foreach ($recordsByAccount as $key => $value) {
$recordsByAccount[$key] = [
'event_ip' => array_column($value, 'event_ip'),
'event_url_string' => array_column($value, 'event_url_string'),
'event_empty_referer' => array_column($value, 'event_empty_referer'),
'event_device' => array_column($value, 'event_device'),
'event_type' => array_column($value, 'event_type'),
'event_http_code' => array_column($value, 'event_http_code'),
'event_device_created' => array_column($value, 'event_device_created'),
'event_device_lastseen' => array_column($value, 'event_device_lastseen'),
'event_http_method' => array_column($value, 'event_http_method'),
];
}
return $recordsByAccount;
}
protected function getDetails(array $accountIds, int $apiKey): array {
[$params, $placeHolders] = $this->getRequestParams($accountIds, $apiKey);
$contextLimit = \Utils\Constants::get('RULE_EVENT_CONTEXT_LIMIT');
$query = (
"WITH ranked_events AS (
SELECT
event.account AS accountid,
event.id AS event_id,
event.ip AS event_ip,
event_url.url AS event_url_string,
event_referer.referer AS event_referer_string,
event.device AS event_device,
event.time AS event_time,
event.type AS event_type,
event.http_code AS event_http_code,
event.http_method AS event_http_method,
ROW_NUMBER() OVER (PARTITION BY event.account ORDER BY event.time DESC) AS rn
FROM event
LEFT JOIN event_url ON event_url.id = event.url
LEFT JOIN event_referer ON event_referer.id = event.referer
WHERE event.key = :api_key
AND event.account IN ({$placeHolders})
)
SELECT
accountid,
event_ip,
event_url_string,
(event_referer_string IS NULL OR event_referer_string = '') AS event_empty_referer,
event_device,
ed.created AS event_device_created,
ed.lastseen AS event_device_lastseen,
event_type,
event_http_code,
event_http_method
FROM ranked_events
LEFT JOIN event_device AS ed
ON ranked_events.event_device = ed.id
WHERE rn <= {$contextLimit}
ORDER BY event_time DESC;"
);
return $this->execQuery($query, $params);
}
}

View File

@@ -0,0 +1,91 @@
<?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 Ip extends Base {
public function getContext(array $accountIds, int $apiKey): array {
$records = $this->getDetails($accountIds, $apiKey);
$recordsByAccount = $this->groupRecordsByAccount($records);
foreach ($recordsByAccount as $key => $value) {
$recordsByAccount[$key] = [
'eip_ip_id' => array_column($value, 'eip_ip_id'),
'eip_ip' => array_column($value, 'eip_ip'),
'eip_cidr' => array_column($value, 'eip_cidr'),
'eip_data_center' => array_column($value, 'eip_data_center'),
'eip_tor' => array_column($value, 'eip_tor'),
'eip_vpn' => array_column($value, 'eip_vpn'),
'eip_relay' => array_column($value, 'eip_relay'),
'eip_starlink' => array_column($value, 'eip_starlink'),
'eip_total_visit' => array_column($value, 'eip_total_visit'),
'eip_blocklist' => array_column($value, 'eip_blocklist'),
'eip_shared' => array_column($value, 'eip_shared'),
//'eip_domains' => $this->getUniqueArray(array_column($value, 'eip_domains')),
'eip_domains_count_len' => array_column($value, 'eip_domains_count_len'),
'eip_country_id' => array_column($value, 'eip_country_id'),
'eip_fraud_detected' => array_column($value, 'eip_fraud_detected'),
'eip_alert_list' => array_column($value, 'eip_alert_list'),
];
}
return $recordsByAccount;
}
protected function getDetails(array $accountIds, int $apiKey): array {
[$params, $placeHolders] = $this->getRequestParams($accountIds, $apiKey);
$query = (
"SELECT DISTINCT
event.account AS accountid,
event_ip.id AS eip_ip_id,
event_ip.ip AS eip_ip,
event_ip.cidr::text AS eip_cidr,
event_ip.country AS eip_country_id,
event_ip.data_center AS eip_data_center,
event_ip.tor AS eip_tor,
event_ip.vpn AS eip_vpn,
event_ip.relay AS eip_relay,
event_ip.starlink AS eip_starlink,
event_ip.total_visit AS eip_total_visit,
event_ip.blocklist AS eip_blocklist,
event_ip.shared AS eip_shared,
-- event_ip.domains_count AS eip_domains,
json_array_length(event_ip.domains_count::json) AS eip_domains_count_len,
event_ip.fraud_detected AS eip_fraud_detected,
event_ip.alert_list AS eip_alert_list
FROM
event_ip
INNER JOIN event
ON (event_ip.id = event.ip)
WHERE
event_ip.key = :api_key
AND event_ip.checked = 'True'
AND event.account IN ({$placeHolders})
-- ORDER BY event_ip.id DESC"
);
if (count($accountIds) === 1) {
$query .= ' LIMIT 100 OFFSET 0';
}
return $this->execQuery($query, $params);
}
}

View File

@@ -0,0 +1,79 @@
<?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 Phone extends Base {
public function getContext(array $accountIds, int $apiKey): array {
$records = $this->getDetails($accountIds, $apiKey);
$recordsByAccount = $this->groupRecordsByAccount($records);
foreach ($recordsByAccount as $key => $value) {
$recordsByAccount[$key] = [
//'ep_calling_country_code' => $this->getUniqueArray(array_column($value, 'ep_calling_country_code')),
//'ep_carrier_name' => $this->getUniqueArray(array_column($value, 'ep_carrier_name')),
//'ep_checked' => $this->getUniqueArray(array_column($value, 'ep_checked')),
//'ep_country_code' => $this->getUniqueArray(array_column($value, 'ep_country_code')),
//'ep_created' => $this->getUniqueArray(array_column($value, 'ep_created')),
//'ep_lastseen' => $this->getUniqueArray(array_column($value, 'ep_lastseen')),
//'ep_mobile_country_code' => $this->getUniqueArray(array_column($value, 'ep_mobile_country_code')),
//'ep_mobile_network_code' => $this->getUniqueArray(array_column($value, 'ep_mobile_network_code')),
//'ep_national_format' => $this->getUniqueArray(array_column($value, 'ep_national_format')),
'ep_phone_number' => $this->getUniqueArray(array_column($value, 'ep_phone_number')),
'ep_shared' => $this->getUniqueArray(array_column($value, 'ep_shared')),
'ep_type' => $this->getUniqueArray(array_column($value, 'ep_type')),
//'ep_invalid' => $this->getUniqueArray(array_column($value, 'ep_invalid')),
//'ep_validation_errors' => $this->getUniqueArray(array_column($value, 'ep_validation_errors')),
//'ep_alert_list' => $this->getUniqueArray(array_column($value, 'ep_alert_list')),
];
}
return $recordsByAccount;
}
protected function getDetails(array $accountIds, int $apiKey): array {
[$params, $placeHolders] = $this->getRequestParams($accountIds, $apiKey);
$query = (
"SELECT
event_phone.account_id AS accountid,
-- event_phone.calling_country_code AS ep_calling_country_code,
-- event_phone.carrier_name AS ep_carrier_name,
-- event_phone.checked AS ep_checked,
-- event_phone.country_code AS ep_country_code,
-- event_phone.created AS ep_created,
-- event_phone.lastseen AS ep_lastseen,
-- event_phone.mobile_country_code AS ep_mobile_country_code,
-- event_phone.mobile_network_code AS ep_mobile_network_code,
-- event_phone.national_format AS ep_national_format,
event_phone.phone_number AS ep_phone_number,
event_phone.shared AS ep_shared,
event_phone.type AS ep_type
-- event_phone.invalid AS ep_invalid,
-- event_phone.validation_errors AS ep_validation_errors,
-- event_phone.alert_list AS ep_alert_list
FROM
event_phone
WHERE
event_phone.key = :api_key
AND event_phone.account_id IN ({$placeHolders})"
);
return $this->execQuery($query, $params);
}
}

View File

@@ -0,0 +1,71 @@
<?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 Session extends Base {
public function getContext(array $accountIds, int $apiKey, int $timezoneOffset = 0): array {
$records = $this->getDetails($accountIds, $apiKey, $timezoneOffset);
// one record per account
$recordsByAccount = $this->groupRecordsByAccount($records);
return $recordsByAccount;
}
protected function getDetails(array $accountIds, int $apiKey, int $timezoneOffset = 0): array {
[$params, $placeHolders] = $this->getRequestParams($accountIds, $apiKey);
$params[':night_start'] = gmdate('H:i:s', \Utils\Constants::get('NIGHT_RANGE_SECONDS_START') - $timezoneOffset);
$params[':night_end'] = gmdate('H:i:s', \Utils\Constants::get('NIGHT_RANGE_SECONDS_END') - $timezoneOffset);
// boolean logic for defining time ranges overlap
$query = (
"SELECT
event_session.account_id AS accountid,
BOOL_OR(event_session.total_country > 1) AS event_session_multiple_country,
BOOL_OR(event_session.total_ip > 1) AS event_session_multiple_ip,
BOOL_OR(event_session.total_device > 1) AS event_session_multiple_device,
BOOL_OR(
(event_session.lastseen - event_session.created) > INTERVAL '1 day' OR
(
CASE WHEN :night_start::time < :night_end::time
THEN
(event_session.lastseen::time >= :night_start::time AND event_session.lastseen::time <= :night_end::time) OR
(event_session.created::time >= :night_start::time AND event_session.created::time <= :night_end::time) OR
(
CASE WHEN event_session.lastseen::time > event_session.created::time
THEN
event_session.total_visit > 1 AND :night_start::time >= event_session.created::time AND :night_start::time <= event_session.lastseen::time
ELSE
event_session.total_visit > 1 AND (:night_start::time >= event_session.created::time OR :night_start::time <= event_session.lastseen::time)
END
)
ELSE
event_session.lastseen::time >= :night_start::time OR event_session.lastseen::time <= :night_end::time OR
event_session.created::time >= :night_start::time OR event_session.created::time <= :night_end::time OR
event_session.lastseen::time < event_session.created::time
END
)) AS event_session_night_time
FROM
event_session
WHERE
event_session.key = :api_key AND
event_session.account_id IN ({$placeHolders})
GROUP BY event_session.account_id"
);
return $this->execQuery($query, $params);
}
}

View File

@@ -0,0 +1,118 @@
<?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 User extends Base {
use \Traits\Enrichment\Emails;
public function getContext(array $accountIds, int $apiKey): array {
$results = $this->getDetails($accountIds, $apiKey);
$this->calculateEmailReputationForContext($results);
$recordsByAccount = [];
foreach ($results as $item) {
$recordsByAccount[$item['ea_id']] = $item;
}
return $recordsByAccount;
}
protected function getDetails(array $accountIds, int $apiKey): array {
[$params, $placeHolders] = $this->getRequestParams($accountIds, $apiKey);
$query = (
"SELECT
event_account.id AS ea_id,
event_account.userid AS ea_userid,
event_account.created AS ea_created,
event_account.lastseen AS ea_lastseen,
event_account.total_visit AS ea_total_visit,
event_account.total_country AS ea_total_country,
event_account.total_ip AS ea_total_ip,
event_account.total_device AS ea_total_device,
event_account.firstname AS ea_firstname,
event_account.lastname AS ea_lastname,
event_email.email AS ee_email,
event_email.blockemails AS ee_blockemails,
event_email.data_breach AS ee_data_breach,
-- event_email.profiles AS ee_profiles,
event_email.checked AS ee_checked,
event_domain.discovery_date AS ed_discovery_date,
event_domain.blockdomains AS ed_blockdomains,
event_domain.disposable_domains AS ed_disposable_domains,
-- event_domain.total_account AS ed_total_account,
event_domain.free_email_provider AS ed_free_provider,
event_domain.tranco_rank AS ed_tranco_rank,
event_domain.creation_date AS ed_creation_date,
event_domain.expiration_date AS ed_expiration_date,
event_domain.return_code AS ed_return_code,
event_domain.closest_snapshot AS ed_closest_snapshot,
event_domain.mx_record AS ed_mx_record,
lastemail_record.email AS le_email,
lastemail_record.blockemails AS le_blockemails,
lastemail_record.data_breach AS le_data_breach,
-- lastemail_record.profiles AS le_profiles,
lastemail_record.checked AS le_checked,
lastemail_record.fraud_detected AS le_fraud_detected,
lastemail_record.alert_list AS le_alert_list,
lastdomain_record.disposable_domains AS ld_disposable_domains,
lastdomain_record.free_email_provider AS ld_free_email_provider,
lastdomain_record.blockdomains AS ld_blockdomains,
lastdomain_record.mx_record AS ld_mx_record,
lastdomain_record.disabled AS ld_disabled,
lastdomain_record.creation_date AS ld_creation_date,
lastdomain_record.tranco_rank AS ld_tranco_rank,
lastphone_record.phone_number AS lp_phone_number,
lastphone_record.country_code AS lp_country_code,
lastphone_record.invalid AS lp_invalid,
lastphone_record.fraud_detected AS lp_fraud_detected,
lastphone_record.alert_list AS lp_alert_list
FROM
event_account
LEFT JOIN event_phone
ON (event_account.id = event_phone.account_id)
LEFT JOIN event_email
ON event_account.id = event_email.account_id
LEFT JOIN event_domain
ON event_email.domain = event_domain.id
LEFT JOIN event_email AS lastemail_record
ON event_account.lastemail = lastemail_record.id
LEFT JOIN event_phone AS lastphone_record
ON event_account.lastphone = lastphone_record.id
LEFT JOIN event_domain AS lastdomain_record
ON lastemail_record.domain = lastdomain_record.id
WHERE
event_account.key = :api_key
AND event_account.id IN ({$placeHolders})"
);
return $this->execQuery($query, $params);
}
}