2022-07-19 17:27:23 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
2022-11-21 11:16:43 +01:00
|
|
|
use Illuminate\Support\Collection;
|
2022-07-19 17:27:23 +02:00
|
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
use Illuminate\Support\Facades\Log;
|
2022-11-22 15:15:52 +01:00
|
|
|
use Illuminate\Support\Facades\Storage;
|
2022-07-20 13:31:51 +02:00
|
|
|
use Illuminate\Support\Str;
|
2022-07-19 17:27:23 +02:00
|
|
|
|
|
|
|
class LogoService
|
|
|
|
{
|
|
|
|
/**
|
2022-11-21 11:16:43 +01:00
|
|
|
* @var \Illuminate\Support\Collection<string, string>
|
2022-07-19 17:27:23 +02:00
|
|
|
*/
|
|
|
|
protected $tfas;
|
|
|
|
|
|
|
|
/**
|
2022-09-07 17:54:27 +02:00
|
|
|
* @var string
|
2022-07-19 17:27:23 +02:00
|
|
|
*/
|
|
|
|
const TFA_JSON = 'tfa.json';
|
2022-07-30 11:25:45 +02:00
|
|
|
|
|
|
|
/**
|
2022-09-07 17:54:27 +02:00
|
|
|
* @var string
|
2022-07-30 11:25:45 +02:00
|
|
|
*/
|
2022-07-19 17:27:23 +02:00
|
|
|
const TFA_URL = 'https://2fa.directory/api/v3/tfa.json';
|
|
|
|
|
|
|
|
public function __construct()
|
|
|
|
{
|
|
|
|
$this->setTfaCollection();
|
|
|
|
}
|
|
|
|
|
2022-07-20 13:31:51 +02:00
|
|
|
/**
|
2022-07-30 11:25:45 +02:00
|
|
|
* Fetch a logo for the given service and save it as an icon
|
2022-11-22 15:15:52 +01:00
|
|
|
*
|
2024-10-01 15:36:18 +02:00
|
|
|
* @param string|null $serviceName Name of the service to fetch a logo for
|
2022-07-20 13:31:51 +02:00
|
|
|
* @return string|null The icon filename or null if no logo has been found
|
|
|
|
*/
|
2024-10-01 15:36:18 +02:00
|
|
|
public function getIcon(?string $serviceName)
|
2022-07-20 13:31:51 +02:00
|
|
|
{
|
2022-07-22 14:11:46 +02:00
|
|
|
$logoFilename = $this->getLogo(strval($serviceName));
|
2022-07-20 13:31:51 +02:00
|
|
|
|
|
|
|
if ($logoFilename) {
|
2022-11-22 15:15:52 +01:00
|
|
|
$iconFilename = Str::random(40) . '.svg';
|
|
|
|
|
2022-07-22 16:26:23 +02:00
|
|
|
return $this->copyToIcons($logoFilename, $iconFilename) ? $iconFilename : null;
|
2022-11-22 15:15:52 +01:00
|
|
|
} else {
|
|
|
|
return null;
|
2022-07-20 13:31:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-19 17:27:23 +02:00
|
|
|
/**
|
|
|
|
* Return the logo's filename for a given service
|
2022-11-22 15:15:52 +01:00
|
|
|
*
|
2024-04-20 19:03:44 +02:00
|
|
|
* @param string $serviceName Name of the service to fetch a logo for
|
2022-07-19 17:27:23 +02:00
|
|
|
* @return string|null The logo filename or null if no logo has been found
|
|
|
|
*/
|
2024-10-01 15:36:18 +02:00
|
|
|
protected function getLogo(string $serviceName)
|
2022-07-19 17:27:23 +02:00
|
|
|
{
|
2022-11-22 15:15:52 +01:00
|
|
|
$domain = $this->tfas->get($this->cleanDomain(strval($serviceName)));
|
|
|
|
$logoFilename = $domain . '.svg';
|
2022-07-19 17:27:23 +02:00
|
|
|
|
2022-12-13 12:07:29 +01:00
|
|
|
if ($domain && ! Storage::disk('logos')->exists($logoFilename)) {
|
2022-07-19 17:27:23 +02:00
|
|
|
$this->fetchLogo($logoFilename);
|
|
|
|
}
|
|
|
|
|
2022-07-20 13:31:51 +02:00
|
|
|
return Storage::disk('logos')->exists($logoFilename) ? $logoFilename : null;
|
2022-07-19 17:27:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Build and set the TFA directoy collection
|
|
|
|
*/
|
2022-12-13 12:07:29 +01:00
|
|
|
protected function setTfaCollection() : void
|
2022-07-19 17:27:23 +02:00
|
|
|
{
|
|
|
|
// We fetch a fresh tfaDirectory if necessary to prevent too many API calls
|
|
|
|
if (Storage::disk('logos')->exists(self::TFA_JSON)) {
|
|
|
|
if (time() - Storage::disk('logos')->lastModified(self::TFA_JSON) > 86400) {
|
|
|
|
$this->cacheTfaDirectorySource();
|
|
|
|
}
|
|
|
|
} else {
|
2022-11-22 15:15:52 +01:00
|
|
|
$this->cacheTfaDirectorySource();
|
2022-07-19 17:27:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->tfas = Storage::disk('logos')->exists(self::TFA_JSON)
|
2022-11-21 11:16:43 +01:00
|
|
|
? new Collection(json_decode(Storage::disk('logos')->get(self::TFA_JSON)))
|
|
|
|
: collect([]);
|
2022-07-19 17:27:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetch and cache fresh TFA.Directory data using the https://2fa.directory API
|
|
|
|
*/
|
2022-12-13 12:07:29 +01:00
|
|
|
protected function cacheTfaDirectorySource() : void
|
2022-07-19 17:27:23 +02:00
|
|
|
{
|
|
|
|
try {
|
2023-12-13 16:49:35 +01:00
|
|
|
$response = Http::withOptions([
|
|
|
|
'proxy' => config('2fauth.config.outgoingProxy'),
|
|
|
|
])->retry(3, 100)->get(self::TFA_URL);
|
2022-07-19 17:27:23 +02:00
|
|
|
|
2022-12-13 09:08:22 +01:00
|
|
|
$coll = collect(json_decode(htmlspecialchars_decode($response->body()), true)) /* @phpstan-ignore-line */
|
2022-12-09 10:55:11 +01:00
|
|
|
->mapWithKeys(function ($item, $key) {
|
|
|
|
return [
|
|
|
|
strtolower(head($item)) => $item[1]['domain'],
|
|
|
|
];
|
|
|
|
});
|
2022-07-19 17:27:23 +02:00
|
|
|
|
|
|
|
Storage::disk('logos')->put(self::TFA_JSON, $coll->toJson())
|
|
|
|
? Log::info('Fresh tfa.json saved to logos dir')
|
|
|
|
: Log::notice('Cannot save tfa.json to logos dir');
|
2022-11-22 15:15:52 +01:00
|
|
|
} catch (\Exception $e) {
|
2024-04-26 10:54:54 +02:00
|
|
|
Log::error('Caching of tfa.json failed:' . $e->getMessage());
|
2022-07-19 17:27:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-07-20 13:31:51 +02:00
|
|
|
* Fetch and cache a logo from 2fa.Directory repository
|
2022-11-22 15:15:52 +01:00
|
|
|
*
|
2024-04-20 19:03:44 +02:00
|
|
|
* @param string $logoFile Logo filename to fetch
|
2022-07-19 17:27:23 +02:00
|
|
|
*/
|
2022-12-13 12:07:29 +01:00
|
|
|
protected function fetchLogo(string $logoFile) : void
|
2022-07-19 17:27:23 +02:00
|
|
|
{
|
|
|
|
try {
|
2023-12-13 16:49:35 +01:00
|
|
|
$response = Http::withOptions([
|
|
|
|
'proxy' => config('2fauth.config.outgoingProxy'),
|
|
|
|
])->retry(3, 100)->get('https://raw.githubusercontent.com/2factorauth/twofactorauth/master/img/' . $logoFile[0] . '/' . $logoFile);
|
2022-11-22 15:15:52 +01:00
|
|
|
|
2022-07-19 17:27:23 +02:00
|
|
|
if ($response->successful()) {
|
|
|
|
Storage::disk('logos')->put($logoFile, $response->body())
|
|
|
|
? Log::info(sprintf('Logo "%s" saved to logos dir.', $logoFile))
|
|
|
|
: Log::notice(sprintf('Cannot save logo "%s" to logos dir', $logoFile));
|
|
|
|
}
|
2022-11-22 15:15:52 +01:00
|
|
|
} catch (\Exception $exception) {
|
2022-07-19 17:27:23 +02:00
|
|
|
Log::error(sprintf('Fetching of logo "%s" failed.', $logoFile));
|
|
|
|
}
|
|
|
|
}
|
2022-07-20 13:31:51 +02:00
|
|
|
|
|
|
|
/**
|
2022-07-22 14:11:46 +02:00
|
|
|
* Prepare and make some replacement to optimize logo fetching
|
2022-11-22 15:15:52 +01:00
|
|
|
*
|
2022-07-22 14:11:46 +02:00
|
|
|
* @return string Optimized domain name
|
2022-07-20 13:31:51 +02:00
|
|
|
*/
|
2022-12-13 12:07:29 +01:00
|
|
|
protected function cleanDomain(string $domain) : string
|
2022-07-20 13:31:51 +02:00
|
|
|
{
|
|
|
|
return strtolower(str_replace(['+'], ['plus'], $domain));
|
|
|
|
}
|
2022-07-22 16:26:23 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Copy a logo file to the icons disk with a new name
|
2022-11-22 15:15:52 +01:00
|
|
|
*
|
|
|
|
* @param string $logoFilename
|
|
|
|
* @param string $iconFilename
|
2022-07-22 16:26:23 +02:00
|
|
|
* @return bool Weither the copy succed or not
|
|
|
|
*/
|
2022-12-13 12:07:29 +01:00
|
|
|
protected function copyToIcons($logoFilename, $iconFilename) : bool
|
2022-07-22 16:26:23 +02:00
|
|
|
{
|
|
|
|
return Storage::disk('icons')->put($iconFilename, Storage::disk('logos')->get($logoFilename));
|
|
|
|
}
|
2022-11-22 15:15:52 +01:00
|
|
|
}
|