Viewing File: /usr/local/cpanel/whostmgr/docroot/cgi/ncssl/source/src/Service/Certificate/SyncCertificate.php
<?php
namespace App\Service\Certificate;
use App\Repository\CertificateRepository;
use App\Service\CpanelHelper;
use App\Service\Manager\HttpsRedirectManager;
use App\Service\Message\EventCoreNotifierInterface;
use App\Service\NcGatewayApi\NcGatewayApi;
use App\Service\NcPlugin\PluginException;
use App\Service\PluginGateway\NcCoreApi;
use App\Service\State\StateUser;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use App\Entity\Certificate as CertificateEntity;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class SyncCertificate
{
public function __construct(
private readonly StateUser $stateUser,
private readonly EntityManagerInterface $entityManager,
private readonly CertificateRepository $certificateRepository,
private readonly NcGatewayApi $ncGatewayApi,
private readonly NcCoreApi $ncApi,
private readonly CpanelHelper $cpanelHelper,
private readonly Install $install,
private readonly EventCoreNotifierInterface $eventCoreNotifier,
private readonly LoggerInterface $syncLogger,
private readonly HttpsRedirectManager $httpsRedirectManager,
private readonly Certificate $certificateService,
#[Autowire(param: 'maxCertificateInstallationAttempts')]
private readonly int $maxCertificateInstallationAttempts,
) {
}
public function syncForLocalDbUpdate(): void
{
$this->syncLogger->info('Start sync for local db update');
$localDbCertificates = $this->certificateService->getCertificatesForLocalDbUpdate();
if (!count($localDbCertificates)) {
return;
}
$pendingCertificatesList = array_filter($localDbCertificates, function ($certificate) {
return !$this->isCertificatePendingInstallation($certificate);
});
if (count($pendingCertificatesList)) {
$pendingUserCertificates = [];
foreach ($pendingCertificatesList as $certificate) {
$pendingUserCertificates[$certificate['ncId']] = $certificate;
}
$this->syncAndUpdateLocalDb($pendingUserCertificates);
}
$this->syncLogger->info('Finish sync for local db update');
}
/**
* @throws Exception
*/
public function synchronizeAndInstall(): void
{
$certificates = $this->certificateRepository->getAllPendingInstallation(
$this->stateUser->getUser(),
$this->maxCertificateInstallationAttempts
);
if (!$certificates) {
return;
}
$pendingInstallationCertificates = $this->filterPendingInstallationByNamecheap($certificates);
if (!$pendingInstallationCertificates) {
return;
}
$syncedCerts = $this->syncAndInstall($pendingInstallationCertificates);
$this->switchOnHttps($syncedCerts, $certificates);
}
/**
* @return void
* @throws PluginException
*/
public function syncAll(): void
{
$this->syncLogger->info('Start sync all certificates');
$userLocalDbCertificates = $this->certificateService->getCertificatesForLocalDbUpdate();
if (count($userLocalDbCertificates) === 0) {
return;
}
$userCertificates = [];
foreach ($userLocalDbCertificates as $certificate) {
$userCertificates[$certificate['ncId']] = $certificate;
}
$certificatesForInstallationIds = $this->updateNcStatus($userCertificates);
$certificatesForInstallation = [];
foreach ($certificatesForInstallationIds as $certificateId) {
$certificatesForInstallation[] = $this->certificateRepository->findOneBy(['id' => $certificateId]);
}
$syncedCerts = $this->syncAndInstall($certificatesForInstallation);
try {
$this->switchOnHttps($syncedCerts, $certificatesForInstallation);
} catch (Exception $e) {
$this->syncLogger->error('Can\'t switch on https', $e->getTrace());
}
$this->syncLogger->info('Finish sync all certificates');
}
/**
* @throws Exception
*/
private function filterPendingInstallationByNamecheap(array $certificates): array
{
$namecheapCertificates = $this->ncGatewayApi->getSslList();
return array_filter($certificates, function (CertificateEntity $cPanelCertificate) use ($namecheapCertificates) {
$namecheapCertificate = $this->findByKeyInArray((string) $cPanelCertificate->getNcId(), 'id', $namecheapCertificates);
if ($namecheapCertificate && $this->isCertificateActiveInNC($cPanelCertificate, $namecheapCertificate)) {
return true;
}
$cPanelCertificate->increaseFailedInstallationAttempts();
$this->entityManager->flush();
return false;
}, ARRAY_FILTER_USE_BOTH);
}
private function isCertificateActiveInNC(CertificateEntity $certificate, array $namecheapCertificate): bool
{
return (($certificate->isInProgress() && $namecheapCertificate['status'] === CertificateEntity::STATUS_ACTIVE) ||
($certificate->isActive() && empty($certificate->getCpanelId())));
}
/**
*
* Example:
* $array = [
* ['id' => 1, 'name' => 'Melony'],
* ['id' => 2, 'name' => 'Henry']
* ]
*
* findByKeyInArray('Melony', 'name', $array);
*
* Result: ['id' => 1, 'name' => 'Melony'
*
* @param string|int $searchValue
* @param string|int $searchKey
* @param array $array
* @return array|null
* @throws InvalidArgumentException
*/
private function findByKeyInArray(string|int $searchValue, string|int $searchKey, array $array): ?array
{
if (!$searchKey) {
throw new \InvalidArgumentException('Please provide searchKey parameter');
}
$filtered = array_filter($array, static function($value) use ($searchValue, $searchKey){
return $value[$searchKey] === $searchValue;
},ARRAY_FILTER_USE_BOTH);
return count($filtered) ? array_shift($filtered) : null;
}
private function isCertificatePendingInstallation(array $certificate): bool
{
return ($certificate['status'] == CertificateEntity::STATUS_INPROGRESS)
|| ($certificate['status'] == CertificateEntity::STATUS_ACTIVE && empty($certificate['cpanel_id']));
}
/**
* @param CertificateEntity[] $certificates
* @return array
* @throws PluginException
*/
private function syncAndInstall(array $certificates): array
{
$syncedCerts = [];
$domainList = $this->cpanelHelper->getDomainsList();
$filteredCertificates = array_filter($certificates, function($certificate) use ($domainList) {
$certificateHost = $certificate->getHost();
if ($certificateHost === null || !in_array($certificateHost, $domainList, true)) {
$this->syncLogger->warning("The certificate cannot be installed because the certificate's domain is not present on the hosting server.", context: [
'certificate' => [
'ncId' => $certificate->getNcId(),
'host' => $certificateHost,
'ncUser' => $certificate->getNcUser(),
],
'userDomainList' => $domainList,
]);
$certificate->increaseFailedInstallationAttempts();
$this->entityManager->flush();
return false;
}
return true;
});
foreach ($filteredCertificates as $certificate) {
try {
$namecheapCertificate = $this->ncGatewayApi->getInfo((string) $certificate->getNcId(), true, $certificate->getUser()->getNcLogin());
if ($this->isCertificateActiveInNC($certificate, $namecheapCertificate)) {
try {
$this->install->install($certificate->getId(), $namecheapCertificate['cert_body'], $namecheapCertificate['ca_certs']);
} catch (\Throwable $throwable) {
$certificate->increaseFailedInstallationAttempts();
$this->syncLogger->error("The certificate installation failed.", context: [
'error' => $throwable->getMessage(),
'certificate' => [
'ncId' => $certificate->getNcId(),
'host' => $certificate->getHost(),
'ncUser' => $certificate->getNcUser(),
'status' => $certificate->getStatus(),
'ncStatus' => $certificate->getNcStatus(),
'failedInstallationAttempts' => $certificate->getFailedInstallationAttempts(),
]
]);
$this->entityManager->flush();
continue;
}
$syncedCerts[] = $certificate->getNcId();
$certificate
->setNcStatus($namecheapCertificate['status'])
->setExpires($namecheapCertificate['expires'])
->setInstalledAt((new \DateTime())->getTimestamp());
$this->entityManager->flush();
$this->eventCoreNotifier->sendEvent(EventCoreNotifierInterface::SUCCESS_INSTALLATION_EVENT_CODE, $certificate->getNcId(), $certificate->getHost());
} else if ($this->isCertificateReadyToInstall($certificate)) {
$this->syncLogger->error('Can\'t install certificate. Wrong Status.', ['certificateNcId' => $certificate->getNcId()]);
}
} catch (\Exception $exception) {
$this->syncLogger->error('Can\'t install certificate', [
'certificateNcId' => $certificate->getNcId(),
'errorMessage' => $exception->getMessage(),
'errorCode' => $exception->getCode(),
]);
}
}
return $syncedCerts;
}
private function isCertificateReadyToInstall(CertificateEntity $certificate): bool
{
return $certificate->isActive() && empty($certificate->getCpanelId()) && empty($certificate->getPrivatekeyId());
}
/**
* @param CertificateEntity[] $certificates
* @param $syncedIds
* @throws \JsonException
*/
private function switchOnHttps(array $syncedIds, array $certificates = []): void
{
foreach ($certificates as $certificate) {
if (!in_array($certificate->getNcId(), $syncedIds, true)) {
continue;
}
if (!$certificate->isAutoRedirect()) {
$this->httpsRedirectManager->toggleHttpRedirect($certificate->getHost(), false);
continue;
}
$result = $this->httpsRedirectManager->toggleHttpRedirect($certificate->getHost(), true);
if ($result['status'] !== HttpsRedirectManager::STATUS_OK) {
continue;
}
//@TODO Success message
}
}
/**
* @param array $certificates
*
* @return void
*/
private function syncAndUpdateLocalDb(array $certificates): void
{
try {
$nc_certs = $this->ncApi->getSslList();
} catch (Exception $e) {
$this->syncLogger->error('Can\'t get certificates from Namecheap', $e->getTrace());
return;
}
// sync cetificates from db with data from namecheap
// certs in statuses ['REPLACED', 'CANCELLED', 'REVOKED'] should be deleted from db
array_walk($certificates, function ($cPanelCertificate, $cPanelCertificateId) use ($nc_certs) {
$namecheapCertificate = $this->findMatchingNamecheapCertificate($nc_certs, $cPanelCertificateId);
if ($namecheapCertificate !== null) {
if ($namecheapCertificate['status'] == CertificateEntity::NCSTATUS_NEWPURCHASE && $cPanelCertificate['status'] != CertificateEntity::STATUS_ACTIVE) {
$this->syncLogger->info('Delete certificate', $cPanelCertificate);
$this->certificateService->deleteById($cPanelCertificate['id']);
} elseif (in_array(
$namecheapCertificate['status'],
[CertificateEntity::NCSTATUS_REPLACED, CertificateEntity::NCSTATUS_CANCELLED, CertificateEntity::NCSTATUS_REVOKED], true)
) {
$this->syncLogger->info('Delete certificate', $cPanelCertificate);
$this->certificateService->deleteById($cPanelCertificate['id']);
} else {
if ($cPanelCertificate['status'] !== $namecheapCertificate['status']) {
$this->syncLogger->info('Update NC status of certificate', ['cpanelCertificate' => $cPanelCertificate, 'ncStatus' => $namecheapCertificate['status']]);
$this->certificateService->updateNcStatus($cPanelCertificate['id'], $namecheapCertificate['status']);
}
}
}
});
}
/**
* @param array $namecheapCertificates
* @param $certificateId
*
* @return mixed|null
*/
private function findMatchingNamecheapCertificate(array $namecheapCertificates, $certificateId): mixed
{
$matchingNamecheapCertificates = array_filter($namecheapCertificates, function ($namecheapCertificate) use ($certificateId) {
return intval($namecheapCertificate['id']) === $certificateId;
});
return array_shift($matchingNamecheapCertificates);
}
/**
* @param array $userCertificates
*
* @return array
*/
private function updateNcStatus(array $userCertificates): array
{
$certsForInstallationIds = [];
try {
$ncCertificates = $this->ncApi->getSslList();
} catch (Exception $e) {
$this->syncLogger->error('Can\'t get certificates from Namecheap', $e->getTrace());
return [];
}
foreach ($ncCertificates as $ncCertificate) {
if (!array_key_exists($ncCertificate['id'], $userCertificates)) {
continue;
}
$userCertificate = $userCertificates[$ncCertificate['id']];
if ($userCertificate['status'] == CertificateEntity::STATUS_INPROGRESS &&
$ncCertificate['status'] == CertificateEntity::NCSTATUS_ACTIVE) {
$certsForInstallationIds[] = $userCertificate['id'];
} elseif ($userCertificate['status'] == CertificateEntity::STATUS_ACTIVE &&
empty($userCertificate['cpanelId'])) {
$certsForInstallationIds[] = $userCertificate['id'];
} elseif ($userCertificate['status'] != CertificateEntity::STATUS_ACTIVE &&
$ncCertificate['status'] == CertificateEntity::NCSTATUS_NEWPURCHASE) {
$this->syncLogger->info('Delete certificate', $userCertificate);
$this->certificateService->deleteById($userCertificate['id']);
} elseif (in_array($ncCertificate['status'], [CertificateEntity::NCSTATUS_REPLACED, CertificateEntity::NCSTATUS_CANCELLED, CertificateEntity::NCSTATUS_REVOKED], true)) {
$this->syncLogger->info('Delete certificate', $userCertificate);
$this->certificateService->deleteById($userCertificate['id']);
try {
$this->cpanelHelper->deleteCertificate($userCertificate['cpanelId']);
} catch (PluginException $e) {
$this->syncLogger->error('Can\'t delete certificate from cPanel', $e->getTrace());
}
} else {
if ($userCertificate['status'] !== $ncCertificate['status']) {
$this->syncLogger->info('Update NC status of certificate', ['userCertificate' => $userCertificate, 'ncStatus' => $ncCertificate['status']]);
$this->certificateService->updateNcStatus($userCertificate['id'], $ncCertificate['status']);
}
}
}
return $certsForInstallationIds;
}
}
Back to Directory
File Manager