diff --git a/Databases/.gitignore b/Databases/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/Databases/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/Safebrowsing/.gitignore b/Safebrowsing/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/composer.json b/composer.json index ee2d80d..79d63c0 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "ext-iconv": "*", "ext-json": "*", "friendsofsymfony/jsrouting-bundle": "^2.3", + "geoip2/geoip2": "~2.0", "league/uri": "^5.3", "league/uri-manipulations": "^1.5", "league/uri-parser": "^1.4", @@ -19,7 +20,7 @@ "symfony/framework-bundle": "^4.0", "symfony/http-foundation": "^4.0", "symfony/maker-bundle": "^1.3", - "symfony/monolog-bundle": "^3.1", + "symfony/monolog-bundle": "^3.3", "symfony/orm-pack": "^1.0", "symfony/process": "^4.0", "symfony/security-bundle": "^4.0", diff --git a/composer.lock b/composer.lock index 2a67dc2..f26cbda 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,64 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2a6a0c06fe6311532feac7b927ce96e4", + "content-hash": "73363cf0b1d7ac1efd7e4ea11dc286e5", "packages": [ + { + "name": "composer/ca-bundle", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d", + "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", + "psr/log": "^1.0", + "symfony/process": "^2.5 || ^3.0 || ^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "time": "2019-01-28T09:30:10+00:00" + }, { "name": "doctrine/annotations", "version": "v1.6.1", @@ -1372,6 +1428,58 @@ ], "time": "2019-02-03T16:01:06+00:00" }, + { + "name": "geoip2/geoip2", + "version": "v2.9.0", + "source": { + "type": "git", + "url": "https://github.com/maxmind/GeoIP2-php.git", + "reference": "a807fbf65212eef5d8d2db1a1b31082b53633d77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/a807fbf65212eef5d8d2db1a1b31082b53633d77", + "reference": "a807fbf65212eef5d8d2db1a1b31082b53633d77", + "shasum": "" + }, + "require": { + "maxmind-db/reader": "~1.0", + "maxmind/web-service-common": "~0.5", + "php": ">=5.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "2.*", + "phpunit/phpunit": "4.*", + "squizlabs/php_codesniffer": "3.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "GeoIp2\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Gregory J. Oschwald", + "email": "goschwald@maxmind.com", + "homepage": "http://www.maxmind.com/" + } + ], + "description": "MaxMind GeoIP2 PHP API", + "homepage": "https://github.com/maxmind/GeoIP2-php", + "keywords": [ + "IP", + "geoip", + "geoip2", + "geolocation", + "maxmind" + ], + "time": "2018-04-10T15:32:59+00:00" + }, { "name": "jdorn/sql-formatter", "version": "v1.2.17", @@ -1897,6 +2005,108 @@ ], "time": "2018-11-26T08:09:30+00:00" }, + { + "name": "maxmind-db/reader", + "version": "v1.4.1", + "source": { + "type": "git", + "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git", + "reference": "eb83d0ee1c1f9b8a340206302136bc81ee02ae74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/eb83d0ee1c1f9b8a340206302136bc81ee02ae74", + "reference": "eb83d0ee1c1f9b8a340206302136bc81ee02ae74", + "shasum": "" + }, + "require": { + "php": ">=5.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "2.*", + "phpunit/phpunit": "4.* || 5.*", + "satooshi/php-coveralls": "1.0.*", + "squizlabs/php_codesniffer": "3.*" + }, + "suggest": { + "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", + "ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", + "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups" + }, + "type": "library", + "autoload": { + "psr-4": { + "MaxMind\\Db\\": "src/MaxMind/Db" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Gregory J. Oschwald", + "email": "goschwald@maxmind.com", + "homepage": "http://www.maxmind.com/" + } + ], + "description": "MaxMind DB Reader API", + "homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php", + "keywords": [ + "database", + "geoip", + "geoip2", + "geolocation", + "maxmind" + ], + "time": "2019-01-04T19:55:56+00:00" + }, + { + "name": "maxmind/web-service-common", + "version": "v0.5.0", + "source": { + "type": "git", + "url": "https://github.com/maxmind/web-service-common-php.git", + "reference": "61a9836fa3bb1743ab89752bae5005d71e78c73b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/61a9836fa3bb1743ab89752bae5005d71e78c73b", + "reference": "61a9836fa3bb1743ab89752bae5005d71e78c73b", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0.3", + "ext-curl": "*", + "ext-json": "*", + "php": ">=5.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "2.*", + "phpunit/phpunit": "4.*", + "squizlabs/php_codesniffer": "3.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "MaxMind\\Exception\\": "src/Exception", + "MaxMind\\WebService\\": "src/WebService" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Gregory Oschwald", + "email": "goschwald@maxmind.com" + } + ], + "description": "Internal MaxMind Web Service API", + "homepage": "https://github.com/maxmind/web-service-common-php", + "time": "2018-02-12T22:31:54+00:00" + }, { "name": "monolog/monolog", "version": "1.24.0", diff --git a/config/packages/dev/monolog.yaml b/config/packages/dev/monolog.yaml index 3662592..5a41f12 100644 --- a/config/packages/dev/monolog.yaml +++ b/config/packages/dev/monolog.yaml @@ -1,4 +1,5 @@ monolog: + channels: ['linksecurity', 'linkVisitDb', 'geo2ip'] handlers: main: type: stream @@ -17,3 +18,20 @@ monolog: type: console process_psr_3_messages: false channels: ["!event", "!doctrine", "!console"] + + linksecurity: + level: debug + type: rotating_file + path: '%kernel.logs_dir%/linksecurity.log' + channels: [linksecurity] + max_files: 5 + geo2ip: + level: debug + type: rotating_file + path: '%kernel.logs_dir%/geo2ip.log' + channels: [geo2ip] + max_files: 5 + linkVisitDb: + type: service + id: App\Util\MonologHandler\MonologLinkVisitHandler + channels: [linkVisitDb] \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index d0237e8..9e10cce 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -12,7 +12,8 @@ services: public: false # Allows optimizing the container by removing unused services; this also means # fetching services directly from the container via $container->get() won't work. # The best practice is to be explicit about your dependencies anyway. - + bind: + $rootDir: '%kernel.project_dir%' # makes classes in src/ available to be used as services # this creates a service per class whose id is the fully-qualified class name App\: @@ -28,9 +29,6 @@ services: # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones - App\Command\UpdateBannedLinksCommand: - $rootDir: '%kernel.project_dir%' - App\Command\SafeBrowsingUpdateCommand: $rootDir: '%kernel.project_dir%' @@ -39,3 +37,21 @@ services: App\Service\UserManager: $locales: '%app.locales%' + + App\Service\LinkManager: + $linkSecLogger: '@monolog.logger.linksecurity' + + App\Service\Commands\SafebrowsingCmdManager: + $linkSecLogger: '@monolog.logger.linksecurity' + + App\Repository\SBLinkRepository: + $linkSecLogger: '@monolog.logger.linksecurity' + + App\Controller\LinkController: + $linkLogger: '@monolog.logger.linkVisitDb' + + App\Command\Geo2ApiCommand: + $geo2IpLogger: '@monolog.logger.geo2ip' + + App\Service\Geolocalization\Geo2IpManager: + $geo2IpLogger: '@monolog.logger.geo2ip' \ No newline at end of file diff --git a/src/Command/Geo2ApiCommand.php b/src/Command/Geo2ApiCommand.php new file mode 100644 index 0000000..ac0d01c --- /dev/null +++ b/src/Command/Geo2ApiCommand.php @@ -0,0 +1,113 @@ +saveDir = $rootDir.'/Databases'; + $this->geo2IpLogger = $geo2IpLogger; + parent::__construct(); + } + + public function configure() + { + $this->setName(self::$defaultName); + $this->setDescription('Create or update country/ip database.'); + $this->setHelp('Create or update country/ip database'); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return int|void|null + * @throws \Doctrine\DBAL\DBALException + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln('LESSn - Geo2Api database installer'); + $output->writeln('Downloading database...'); + + try { + $this->geo2IpLogger->info('Starting update of geo2ip db'); + + file_put_contents( + $this->saveDir.'/'.self::DB_FILENAME.'.tar.gz', + fopen('https://geolite.maxmind.com/download/geoip/database/'.self::DB_FILENAME.'.tar.gz', 'r') + ); + } catch (\Exception $e) { + $this->geo2IpLogger->critical('Unable to download database'); + return; + } + + $output->writeln('Extracting...'); + + try { + $gz = new \PharData($this->saveDir.'/'.self::DB_FILENAME.'.tar.gz'); + $gz->decompress(); + unset($gz); + + $tar = new \PharData($this->saveDir.'/'.self::DB_FILENAME.'.tar'); + + $rootFoldder = $this->getTarRootFolder($tar); + + $tar->extractTo($this->saveDir, $rootFoldder.'/'.self::DB_FILENAME.'.mmdb', true); + } catch (\Exception $e) { + $this->geo2IpLogger->critical('Unable to extract database'); + return; + } + + $output->writeln('Purging ald files...'); + + try { + rename( + $this->saveDir.'/'.$rootFoldder.'/'.self::DB_FILENAME.'.mmdb', + $this->saveDir.'/'.self::DB_FILENAME.'.mmdb' + ); + unlink($this->saveDir.'/'.self::DB_FILENAME.'.tar.gz'); + unlink($this->saveDir.'/'.self::DB_FILENAME.'.tar'); + rmdir($this->saveDir.'/'.$rootFoldder); + } catch (\Exception $e) { + $this->geo2IpLogger->critical('Unable to purge old files'); + return; + } + + $this->geo2IpLogger->info('Update of geo2ip db Done!'); + + $output->writeln('Done!'); + } + + private function getTarRootFolder(\PharData $tar) + { + /** @var \PharFileInfo $file */ + foreach ($tar as $file) { + return $file->getFilename(); + } + } +} diff --git a/src/Command/UpdateBannedLinksCommand.php b/src/Command/UpdateBannedLinksCommand.php deleted file mode 100644 index cad4a4d..0000000 --- a/src/Command/UpdateBannedLinksCommand.php +++ /dev/null @@ -1,148 +0,0 @@ -rootDir = $rootDir; - $this->uriManager = $uriManager; - $this->em = $em; - - if (!($this->phishTankKey = getenv('PHISHTANK_KEY'))) { - throw new \Exception('Unable to get PhishTank key.'); - } - - parent::__construct(); - } - - public function configure() - { - $this->setName(self::$defaultName); - $this->setDescription('Update list of banned links.'); - $this->setHelp('Update list of banned links.'); - } - - /** - * @param InputInterface $input - * @param OutputInterface $output - * @return int|void|null - * @throws \Doctrine\DBAL\DBALException - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $output->writeln([ - '', - 'LESSn - Banned URL Updater. ><(((°>', - '============', - ]); - - $output->writeln([ - '', - 'Removing old file', - ]); - - $files = glob($this->rootDir.'/fishtank/online-valid.csv'); - foreach ($files as $file) { // iterate files - if (is_file($file)) { - unlink($file); - } - } - - $output->writeln([ - 'Old file removed.', - 'Downloading new file.', - ]); - - $url = "http://data.phishtank.com/data/$this->phishTankKey/online-valid.csv"; - file_put_contents($this->rootDir.'/fishtank/online-valid.csv', fopen($url, 'r')); - - $output->writeln([ - 'New file downloaded.', - 'Purging table and putting new values.' - ]); - - $connexion = $this->em->getConnection(); - $platform = $connexion->getDatabasePlatform(); - $connexion->executeUpdate($platform->getTruncateTableSQL('bannedlink', true)); - - $csvFile = fopen($this->rootDir.'/fishtank/online-valid.csv', 'r'); - fgetcsv($csvFile); - - $linkNb = 0; - while (($line = fgetcsv($csvFile))) { - $phishLinks = $this->uriManager->explodeUrl($line[1]); - - foreach ($phishLinks as $key => $phishLink) { - $host = $this->uriManager->getHost($phishLink); - $domain = $this->uriManager->getDomainName($host); - - $this->putInBd($line, $domain, $key); - $linkNb++; - } - } - - $this->em->flush(); - fclose($csvFile); - - $output->writeln([ - "$linkNb link(s) inserted in database.", - 'Checking links already in DB.' - ]); - - $output->writeln([ - "Removed ".$this->removePhishingLinks().' links.', - ]); - } - - /** - * @param $line - * @param $formattedHost - * @param $key - */ - private function putInBd($line, $formattedHost, $key) - { - if (strlen($line[1]) < 4096 && sizeof(array_filter(explode('.', $formattedHost))) >= 2) { - $this->em->persist( - new BannedLink( - $line[0]."_$key", - $line[1], - $line[3], - $formattedHost - ) - ); - } - } - - private function removePhishingLinks() - { - return $this->em->getRepository(Link::class)->removeBannedLinks(); - } -} diff --git a/src/Controller/LinkController.php b/src/Controller/LinkController.php index 7a6eadb..b30e6e2 100644 --- a/src/Controller/LinkController.php +++ b/src/Controller/LinkController.php @@ -13,6 +13,7 @@ use App\Form\LinkReviewType; use App\Service\LinkManager; use App\Service\UriManager; +use Monolog\Logger; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -27,6 +28,7 @@ class LinkController extends AbstractController private $linkManager; private $validator; private $translator; + private $linkLogger; /** * LinkController constructor. @@ -39,12 +41,14 @@ public function __construct( UriManager $uriManager, LinkManager $linkManager, ValidatorInterface $validator, - TranslatorInterface $translator + TranslatorInterface $translator, + Logger $linkLogger ) { $this->uriManager = $uriManager; $this->linkManager = $linkManager; $this->validator = $validator; $this->translator = $translator; + $this->linkLogger = $linkLogger; } /** @@ -72,10 +76,13 @@ public function linkHandler(Request $request, $uuid) return $this->redirectToRoute('app_main_route'); } - $log = new LogLink($request, $link); - - $em->persist($log); - $em->flush(); + $this->linkLogger->info( + LogLink::MESSAGE['VISITED'], + [ + 'ip' => $request->getClientIp(), + 'link' => $link + ] + ); return $this->redirect($link->getURL()); } diff --git a/src/Entity/LogLink.php b/src/Entity/LogLink.php index df8f495..34225b2 100644 --- a/src/Entity/LogLink.php +++ b/src/Entity/LogLink.php @@ -10,9 +10,14 @@ /** * @ORM\Entity(repositoryClass="App\Repository\LogLinkRepository") * @ORM\Table(name="loglink") + * @ORM\HasLifecycleCallbacks */ class LogLink { + public const MESSAGE = + [ + 'VISITED' => 'VISITED' + ]; /** * @ORM\Id() @@ -40,10 +45,172 @@ class LogLink */ private $link; - public function __construct($request, $link) + + /** + * @ORM\Column(name="message", type="text", options={"default" : ""}) + */ + private $message; + + /** + * @ORM\Column(name="level", type="string", length=8, options={"default" : "INFO"}) + */ + private $level; + + /** + * @var string + * @ORM\Column(name="country", type="string", length=3, nullable=true) + */ + private $country; + + /** + * @return mixed + */ + public function getId() + { + return $this->id; + } + + /** + * @param mixed $id + * @return LogLink + */ + public function setId($id) + { + $this->id = $id; + return $this; + } + + /** + * @return \DateTime + */ + public function getDate(): \DateTime + { + return $this->date; + } + + /** + * @param \DateTime $date + * @return LogLink + */ + public function setDate(\DateTime $date): LogLink + { + $this->date = $date; + return $this; + } + + /** + * @return mixed + */ + public function getIp() + { + return $this->ip; + } + + /** + * @param mixed $ip + * @return LogLink + */ + public function setIp($ip) + { + $this->ip = $ip; + return $this; + } + + /** + * @return Link + */ + public function getLink(): Link + { + return $this->link; + } + + /** + * @param Link $link + * @return LogLink + */ + public function setLink(Link $link): LogLink { - $this->ip = hash('sha512', $request->getClientIp()); - $this->date = new \DateTime(); $this->link = $link; + return $this; + } + + /** + * @return mixed + */ + public function getMessage() + { + return $this->message; + } + + /** + * @param mixed $message + * @return LogLink + */ + public function setMessage($message) + { + $this->message = $message; + return $this; + } + + /** + * @return mixed + */ + public function getContext() + { + return $this->context; + } + + /** + * @param mixed $context + * @return LogLink + */ + public function setContext($context) + { + $this->context = $context; + return $this; + } + + /** + * @return mixed + */ + public function getLevel() + { + return $this->level; + } + + /** + * @param mixed $level + * @return LogLink + */ + public function setLevel($level) + { + $this->level = $level; + return $this; + } + + /** + * @return string + */ + public function getCountry(): string + { + return $this->country; + } + + /** + * @param string $country + * @return LogLink + */ + public function setCountry($country): LogLink + { + $this->country = $country; + return $this; + } + + /** + * @ORM\PrePersist + */ + public function onPrePersist() + { + $this->date = new \DateTime(); } } diff --git a/src/Entity/User.php b/src/Entity/User.php index 1df30b5..6a92750 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -69,7 +69,7 @@ class User implements UserInterface, \Serializable /** * @var string * - * @ORM\Column(type="string", length=2) + * @ORM\Column(type="string", length=2, options={"default" : "en"}) */ public $locale; diff --git a/src/Repository/SBLinkRepository.php b/src/Repository/SBLinkRepository.php index 6751d2a..62540f7 100644 --- a/src/Repository/SBLinkRepository.php +++ b/src/Repository/SBLinkRepository.php @@ -13,6 +13,7 @@ use App\Service\Commands\SafebrowsingFileManager; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\DBAL\Connection; +use Monolog\Logger; use Symfony\Bridge\Doctrine\RegistryInterface; use Symfony\Component\Console\Helper\ProgressBar; @@ -23,15 +24,18 @@ class SBLinkRepository extends ServiceEntityRepository private $safebrowsingCmdManager; private $sbFileManager; + private $linkSecLogger; public function __construct( RegistryInterface $registry, SafebrowsingCmdManager $safebrowsingCmdManager, - SafebrowsingFileManager $sbFileManager + SafebrowsingFileManager $sbFileManager, + Logger $linkSecLogger ) { parent::__construct($registry, SBLink::class); $this->safebrowsingCmdManager = $safebrowsingCmdManager; $this->sbFileManager = $sbFileManager; + $this->linkSecLogger = $linkSecLogger; } private function truncate() @@ -41,6 +45,7 @@ private function truncate() $connection->beginTransaction(); try { $connection->query('truncate table '.$cmd->getTableName()); + $connection->query('ALTER SEQUENCE sblink_id_seq RESTART with 1;'); $connection->commit(); } catch (\Exception $e) { $connection->rollback(); @@ -87,6 +92,7 @@ public function setupHashes($rawHashes, $prefixSize, $output, $update = false) } $connection->commit(); $progressBar->finish(); + $this->linkSecLogger->info('SB : Inserted hashes in db', ['nb' => $len]); $output->writeln(''); return $this->getChecksum(); } @@ -100,6 +106,8 @@ public function addAndDel(string $additions, array $deletions, $prefixSize) $stmt = $this->getEntityManager()->getConnection()->prepare($sql); $stmt->execute(); + $this->linkSecLogger->info('SB : Inserted hashes in db', ['nb' => count($additions)]); + $this->linkSecLogger->info('SB : Deleted hashes in db', ['nb' => count($deletions)]); return $stmt->fetch()['applySbUpdate']; } @@ -133,6 +141,11 @@ public function areBanned($hashArray) 'hash' => $hashArray ] ); - return count($result) > 0; + + if (count($result) > 0) { + $this->linkSecLogger->warn('SB : protection triggered', [$hashArray]); + return true; + } + return false; } } diff --git a/src/Service/Commands/SafebrowsingApiManager.php b/src/Service/Commands/SafebrowsingApiManager.php index 6d43325..0d65150 100644 --- a/src/Service/Commands/SafebrowsingApiManager.php +++ b/src/Service/Commands/SafebrowsingApiManager.php @@ -5,7 +5,6 @@ use App\Entity\SBLinkMeta; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\HttpKernel\Exception\HttpException; class SafebrowsingApiManager { @@ -68,7 +67,7 @@ public function curlExecAndSave($curlInstance, $path, $filename = 'SBresponse.tx $result = curl_exec($curlInstance); if (empty($result)) { - throw new HttpException('Unable to access the api.'); + throw new \Exception('Unable to access the api.'); } file_put_contents($path.'/'.$filename, $result); diff --git a/src/Service/Commands/SafebrowsingCmdManager.php b/src/Service/Commands/SafebrowsingCmdManager.php index 192821c..f6c9af0 100644 --- a/src/Service/Commands/SafebrowsingCmdManager.php +++ b/src/Service/Commands/SafebrowsingCmdManager.php @@ -6,6 +6,7 @@ use App\Entity\SBLink; use App\Entity\SBLinkMeta; use Doctrine\ORM\EntityManagerInterface; +use Monolog\Logger; use function PHPSTORM_META\type; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Input\ArrayInput; @@ -24,17 +25,20 @@ class SafebrowsingCmdManager private $em; private $kernel; private $sbFileManager; + private $linkSecLogger; public function __construct( SerializerInterface $serializer, EntityManagerInterface $em, KernelInterface $kernel, - SafebrowsingFileManager $sbFileManager + SafebrowsingFileManager $sbFileManager, + Logger $linkSecLogger ) { $this->serializer = $serializer; $this->em = $em; $this->kernel = $kernel; $this->sbFileManager = $sbFileManager; + $this->linkSecLogger = $linkSecLogger; } public function process(OutputInterface $output, $jsonA) @@ -49,6 +53,7 @@ public function process(OutputInterface $output, $jsonA) private function update(OutputInterface $output, $jsonA, $status) { $output->writeln("This is a ".$status); + $this->linkSecLogger->info("SB : $status]"); $rawHashes = $jsonA['listUpdateResponses'][0]['additions'][0]['rawHashes']['rawHashes']; unset($jsonA['listUpdateResponses'][0]['additions'][0]['rawHashes']['rawHashes']); @@ -105,9 +110,11 @@ private function validateDatabase($expected, $actual, OutputInterface $output) $output->writeln('Checking database for corruption...'); if ($expected === $actual) { $output->writeln('Database in sync.'); + $this->linkSecLogger->info('SB : Database in sync'); return true; } $output->writeln("Database corrupted. Performing SETUP command.\n"); + $this->linkSecLogger->warn('SB : Database corrupted'); return false; } } diff --git a/src/Service/Commands/SafebrowsingWrapper.php b/src/Service/Commands/SafebrowsingWrapper.php index cac1b0c..42951d2 100644 --- a/src/Service/Commands/SafebrowsingWrapper.php +++ b/src/Service/Commands/SafebrowsingWrapper.php @@ -5,6 +5,7 @@ use App\Service\UriManager; use Doctrine\ORM\EntityManagerInterface; +use Monolog\Logger; use Symfony\Component\Console\Output\OutputInterface; class SafebrowsingWrapper @@ -38,7 +39,7 @@ public function __construct( SafebrowsingApiManager $sbApiManager, SafebrowsingFileManager $sbFileManager ) { - $this->safeBrowsingDir = $rootDir.'/Safebrowsing'; + $this->safeBrowsingDir = $rootDir.'/Databases'; $this->uriManager = $uriManager; $this->em = $em; $this->apiKey = getenv('SAFE_BROWSING_KEY'); diff --git a/src/Service/Geolocalization/Geo2IpManager.php b/src/Service/Geolocalization/Geo2IpManager.php new file mode 100644 index 0000000..b5a8c5b --- /dev/null +++ b/src/Service/Geolocalization/Geo2IpManager.php @@ -0,0 +1,44 @@ +geo2IpLogger = $geo2IpLogger; + try { + $this->dbReader = new Reader($rootDir.'/Databases/'.Geo2ApiCommand::DB_FILENAME.'.mmdb'); + } catch (\Exception $e) { + return; + } + } + + public function getCountryIsoCode($ip) + { + if (is_null($this->dbReader)) { + $this->geo2IpLogger->critical('Unable to create dbReader class'); + return null; + } + + try { + return $this->dbReader->country($ip)->country->isoCode; + } catch (\Exception $e) { + $this->geo2IpLogger->info('Unable to get IP from database'); + return null; + } + } +} diff --git a/src/Service/LinkManager.php b/src/Service/LinkManager.php index 9e6856b..d003c05 100644 --- a/src/Service/LinkManager.php +++ b/src/Service/LinkManager.php @@ -10,6 +10,7 @@ use App\Entity\Link; use Doctrine\ORM\EntityManagerInterface; +use Monolog\Logger; use Symfony\Component\Validator\Validator\ValidatorInterface; class LinkManager @@ -19,17 +20,20 @@ class LinkManager private $em; private $validator; + private $linkSecLogger; /** * LinkManager constructor. * * @param EntityManagerInterface $em * @param ValidatorInterface $validator + * @param Logger $linkSecLogger */ - public function __construct(EntityManagerInterface $em, ValidatorInterface $validator) + public function __construct(EntityManagerInterface $em, ValidatorInterface $validator, Logger $linkSecLogger) { $this->em = $em; $this->validator = $validator; + $this->linkSecLogger = $linkSecLogger; } /** @@ -74,7 +78,12 @@ public function spamProtection($ip, $ua, $em) /** @var $em EntityManagerInterface */ $count = $em->getRepository(Link::class)->countLastByIpUa($ip, $ua); - return $count > self::MAX_SPAM; + if ($count > self::MAX_SPAM) { + $this->linkSecLogger->warn('Spam protection triggered'); + return true; + } + + return false; } public function createOrUpdate($linkArray, $request, $user) @@ -82,7 +91,6 @@ public function createOrUpdate($linkArray, $request, $user) $repo = $this->em->getRepository(Link::class); /** @var Link $link */ if (key_exists('id', $linkArray)) { - dump($repo); $link = $repo->find($linkArray['id']); } else { $link = null; diff --git a/src/Util/MonologHandler/MonologLinkVisitHandler.php b/src/Util/MonologHandler/MonologLinkVisitHandler.php new file mode 100644 index 0000000..9d1c890 --- /dev/null +++ b/src/Util/MonologHandler/MonologLinkVisitHandler.php @@ -0,0 +1,47 @@ +em = $em; + $this->geo2IpManager = $geo2IpManager; + } + + /** + * Called when writing to our database + * @param array $record + */ + protected function write(array $record) + { + $logEntry = new LogLink(); + $logEntry->setMessage($record['message']); + $logEntry->setLevel($record['level_name']); + $logEntry->setIp(hash('sha512', $record['context']['ip'])); + $logEntry->setLink($record['context']['link']); + $logEntry->setCountry($this->geo2IpManager->getCountryIsoCode($record['context']['ip'])); + + $this->em->persist($logEntry); + $this->em->flush(); + } +} diff --git a/src/Validator/Constraints/SafebrowsingValidator.php b/src/Validator/Constraints/SafebrowsingValidator.php index 6656f02..7e4000b 100644 --- a/src/Validator/Constraints/SafebrowsingValidator.php +++ b/src/Validator/Constraints/SafebrowsingValidator.php @@ -18,6 +18,7 @@ use App\Service\UriManager; use Doctrine\ORM\EntityManagerInterface; use League\Uri\Parser; +use Monolog\Logger; use function PhpParser\canonicalize; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; diff --git a/symfony.lock b/symfony.lock index ecb5964..4b9ffaa 100644 --- a/symfony.lock +++ b/symfony.lock @@ -1,4 +1,7 @@ { + "composer/ca-bundle": { + "version": "1.1.4" + }, "doctrine/annotations": { "version": "1.0", "recipe": { @@ -104,6 +107,9 @@ "config/routes/fos_js_routing.yaml" ] }, + "geoip2/geoip2": { + "version": "v2.9.0" + }, "jdorn/sql-formatter": { "version": "v1.2.17" }, @@ -128,6 +134,12 @@ "league/uri-schemes": { "version": "1.2.1" }, + "maxmind-db/reader": { + "version": "v1.4.1" + }, + "maxmind/web-service-common": { + "version": "v0.5.0" + }, "monolog/monolog": { "version": "1.23.0" },