Viewing File: /usr/local/cpanel/whostmgr/docroot/cgi/ncssl/source/src/Service/Certificate/AbstractActivate.php
<?php
namespace App\Service\Certificate;
use App\Entity\User;
use App\Exception\ActivateException;
use App\Entity\Certificate as CertificateEntity;
use App\Service\CpanelHelper;
use App\Service\NcGatewayApi\Exceptions\NcGatewayApiException;
use App\Service\NcGatewayApi\NcGatewayApi;
use App\Service\NcPlugin\PluginException;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
abstract class AbstractActivate
{
public const CSR_COUNTRY_NAME = 'US';
public const CSR_LOCALITY = 'Phoenix';
public const CSR_STATE = 'Arizona';
public const MESSAGE_START_ACTIVATION = 'Start activation for certId: %s';
public const MESSAGE_START_REISSUE = 'Start reissue for certId: %s';
public const MESSAGE_GET_ROOT_FOLDER = 'Get Root folder for domain: %s';
public const MESSAGE_GENERATE_PRIVATE_KEY = 'Generate private key';
public const MESSAGE_GENERATE_CSR = 'Generate CSR';
public const MESSAGE_RENAME_PRIVATE_KEY = 'Rename private key';
public const MESSAGE_EXTERNAL_CALL = 'Make external call';
public const MESSAGE_SAVE_CERTIFICATE = 'Save certificate: %s';
public const MESSAGE_SAVE_HTTP_DCV_FILE = 'Save HttpDcvFile for certificateId: %s';
public const START_ASYNC_ACTIVATION_MESSAGE = 'Start async activation for certId: %s';
public const CHANGE_CERTIFICATE_STATUS_TO_NEW_MESSAGE = 'Certificate status has been changed to new, for certId: %s';
public const MESSAGE_UNABLE_COMPLETE_DCV = 'SSL installation failed due to %s being missing from cPanel or a server access issue. Please <a href="https://support.namecheap.com/index.php?/Tickets/Submit" target="_blank" class="btn-link">contact support</a>';
public const MESSAGE_INVALID_DCV_FILE_VALIDATION = 'Invalid HttpDcvFile validation: %s';
public const MESSAGE_CANT_DEFINE_CPANEL_USER = 'Can\'t define cPanel user';
public const MESSAGE_NC_USER_MISSING = 'Nc user name missing';
public const MESSAGE_USER_NOT_FOUND = 'User not found in database for activate certificate';
private string $privateKey;
private string $csr;
private string $cpanelUser;
abstract protected function makeExternalCall(CertificateTransfer $certificateTransfer, string $csr, bool $isAsyncExternalCall): void;
public function __construct(protected CpanelHelper $cpanelHelper, protected NcGatewayApi $ncGatewayApi, protected EntityManagerInterface $entityManager, protected LoggerInterface $logger, protected ProductManager $productManager) { }
/**
* @throws ActivateException
*/
protected function makeAction(CertificateTransfer $data, bool $isAsyncExternalCall = false): void
{
try {
$this->generateCsr($data->getCertificateId(), $this->performCsrData($data->getCommonName()));
$this->logger->notice(self::MESSAGE_EXTERNAL_CALL);
$this->makeExternalCall($data, $this->getCsrRequest(), $isAsyncExternalCall);
$this->logger->notice(self::MESSAGE_RENAME_PRIVATE_KEY);
$this->cpanelHelper->renamePrivateKey($this->privateKey, $data->getCertificateId());
$this->logger->notice(sprintf(self::MESSAGE_SAVE_CERTIFICATE, $data->getCertificateId()), (array) $data);
if (!$data->getNCUser()) {
$this->logger->error(self::MESSAGE_NC_USER_MISSING, [
'ncLogin' => $data->getNCUser(),
'certificateId' => $data->getCertificateId(),
]);
throw new PluginException(self::MESSAGE_NC_USER_MISSING);
}
$user = $this->getUserFromRepository($data->getCPanelUser(), $data->getNCUser());
if (!$user) {
// if user have never logged to plugin he doesn't have a ncLogin in db
$user = $this->getUserFromRepository($data->getCPanelUser());
if (!$user) {
$this->logger->error(self::MESSAGE_USER_NOT_FOUND, [
'ncLogin' => $data->getNCUser(),
'certificateId' => $data->getCertificateId(),
]);
throw new PluginException(self::MESSAGE_USER_NOT_FOUND);
}
$user->setNcLogin($data->getNCUser());
$this->logger->notice(sprintf('Set ncLogin for user: %s', $user->getName()), [
'ncLogin' => $data->getNCUser(),
]);
}
$certificate = new CertificateEntity();
$certificate->setUser($user);
$certificate->setNcId($data->getCertificateId());
$certificate->setNcStatus($data->getNCStatus());
$certificate->setNcUser($data->getNCUser());
$certificate->setHost($this->getHostName($data->getDomainName()));
$certificate->setCommonName($data->getCommonName());
$certificate->setType($data->getCertType());
$certificate->setYears($data->getYears());
$certificate->setVendor($data->getVendor());
$certificate->setStatus(CertificateEntity::STATUS_INPROGRESS);
$certificate->setPrivatekeyId($this->getPrivateKey());
$certificate->setValidationData($this->prepareValidationData($data));
$certificate->setAutoRedirect((bool)$user->getAutoRedirect());
$this->entityManager->persist($certificate);
$this->entityManager->flush();
if ($this->checkValidationFile($data)) {
$this->saveHttpDcvFile($data);
}
} catch (\Throwable $exception) {
if ($exception instanceof NcGatewayApiException) {
$message = sprintf('Plugin Exception error: %s, code: %s', $exception->getMessage(), $exception->getCode());
} else {
$message = sprintf('Plugin Exception error: %s', $exception->getMessage());
}
$this->logger->error($message, [$data->getDomainName()]);
throw new ActivateException($exception->getMessage(), $exception->getCode(), $exception);
}
}
/**
* @param CertificateTransfer $certificateTransfer
*
* @throws PluginException
*/
public function saveHttpDcvFile(CertificateTransfer $certificateTransfer): void
{
$this->logger->notice(sprintf(self::MESSAGE_SAVE_HTTP_DCV_FILE, $certificateTransfer->getCertificateId()));
$this->cpanelUser = $certificateTransfer->getCPanelUser();
try {
$domainDocRoot = $this->getDomainDocRoot($certificateTransfer->getDomainName());
} catch (\Exception $e) {
$this->logger->error(sprintf('GetDomainDocRoot exception: %s', $e->getMessage()));
throw new PluginException(sprintf(self::MESSAGE_UNABLE_COMPLETE_DCV, $certificateTransfer->getDomainName()));
}
$filePath = $this->getDcvFilePath($domainDocRoot) . '/' . $certificateTransfer->getFileName();
$this->logger->notice(sprintf('saveHttpDcvFile path: %s', $filePath));
$write_res = file_put_contents($filePath, $certificateTransfer->getFileContent());
if ($write_res === false) {
throw new PluginException(sprintf(self::MESSAGE_UNABLE_COMPLETE_DCV, $certificateTransfer->getDomainName()));
}
$scanDirResults = print_r(scandir($this->getDcvFilePath($domainDocRoot)), true);
$this->logger->notice(sprintf('saveHttpDcvFile [scan dir]: %s', $scanDirResults));
if ($this->cpanelUser) {
chown($filePath, $this->cpanelUser);
chgrp($filePath, $this->cpanelUser);
}
}
/**
* @param string $rootPath
* @throws PluginException
*/
protected function validateDcvDataPath(string $rootPath): void
{
$this->getDcvFilePath($rootPath);
if (!$this->checkDirWritable($rootPath)) {
throw new PluginException('Can\'t create validation file in host docroot folder. [' . $rootPath . ']');
}
}
/**
* @param string $domainName
*
* @return mixed|null
*
* @throws PluginException
*/
public function getDomainDocRoot(string $domainName): mixed
{
$this->logger->notice(sprintf(self::MESSAGE_GET_ROOT_FOLDER, $domainName));
$domain = $this->getHostName($domainName);
$domainDocRoot = $this->cpanelHelper->getDomainDocRoot($domain);
$this->validateDcvDataPath($domainDocRoot);
return $domainDocRoot;
}
protected function getDcvFilePath(string $docRoot): string
{
$dcvDataPath = '/.well-known/pki-validation';
$destinationPath = $docRoot . $dcvDataPath;
if (!is_dir($destinationPath)) {
if (!mkdir($destinationPath, 0755, true) && !is_dir($destinationPath)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $destinationPath));
}
}
$this->changeOwner($docRoot, $dcvDataPath);
return $destinationPath;
}
/**
* @param string $root
* @param string $path
*/
protected function changeOwner(string $root, string $path): void
{
if (!$this->cpanelUser) {
return;
}
$path = trim($path, '/');
$chunks = explode('/', $path);
$curPath = $root . '/' . array_shift($chunks);
chown($curPath, $this->cpanelUser);
chgrp($curPath, $this->cpanelUser);
if (count($chunks)) {
$this->changeOwner($curPath, implode('/', $chunks));
}
}
/**
* @param string $dir
* @return bool
*/
protected function checkDirWritable(string $dir): bool
{
$tst_filename = md5(time());
$write_res = file_put_contents($dir . '/' . $tst_filename, $tst_filename);
if ($write_res === false) {
return false;
}
unlink($dir . '/' . $tst_filename);
return true;
}
/**
* @param string $host
* @return string
*/
public function getHostName(string $host = ''): string
{
if (str_starts_with($host, 'www.')) {
$host = substr($host, 4);
}
return $host;
}
/**
* @param string $certificateId
* @param array $csrDetails
* @throws PluginException
*/
public function generateCsr(string $certificateId, array $csrDetails = []): void
{
$this->logger->notice(self::MESSAGE_GENERATE_PRIVATE_KEY);
$this->privateKey = $this->cpanelHelper->generatePrivateKey($certificateId);
$this->logger->notice(self::MESSAGE_GENERATE_CSR);
$this->csr = $this->cpanelHelper->generateCsr($this->privateKey, $csrDetails);
}
public function getCsrRequest(): ?string
{
return $this->csr;
}
public function getPrivateKey(): string
{
return $this->privateKey;
}
/**
* @param string $commonName
* @return array
*/
public function performCsrData(string $commonName = ''): array
{
return [
'domains' => $commonName,
'countryName' => self::CSR_COUNTRY_NAME,
'stateOrProvinceName' => self::CSR_STATE,
'localityName' => self::CSR_LOCALITY,
'organizationName' => $commonName,
];
}
public static function getFormPrefilling(array $userInfo): array
{
return [
'email' => $userInfo['default_address']['email'],
];
}
/**
* @throws \JsonException
*/
protected function prepareValidationData(CertificateTransfer $certificateTransfer): string
{
if (!$this->checkValidationFile($certificateTransfer)) {
return '';
}
return json_encode([
'filename' => $certificateTransfer->getFileName(),
'content' => $certificateTransfer->getFileContent(),
], JSON_THROW_ON_ERROR);
}
protected function checkValidationFile(CertificateTransfer $certificateTransfer): bool
{
return $certificateTransfer->getFileContent() && $certificateTransfer->getFileName();
}
protected function getUserFromRepository(string $cPanelUser, string $ncUser = null): ?User
{
return $this->entityManager->getRepository(User::class)->findOneBy([
'name' => $cPanelUser,
'ncLogin' => $ncUser,
]);
}
}
Back to Directory
File Manager