Viewing File: /usr/local/cpanel/whostmgr/docroot/cgi/ncssl/source/src/Service/NcGatewayApi/NcGatewayApi.php

<?php

namespace App\Service\NcGatewayApi;

use App\Entity\Certificate;
use App\Service\Certificate\CertificateTransfer;
use App\Service\NcGatewayApi\Exceptions\NcGatewayApiException;
use App\Service\State\StateUser;
use Exception;
use \ArrayObject;
use GuzzleHttp\Client;
use Psr\Log\LoggerInterface;
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

class NcGatewayApi extends Caller
{

    public const METHOD_HANDLE_GET_INFO = 'handleGetInfo';
    public const METHOD_HANDLE_GET_LIST = 'handleGetList';
    public const METHOD_GET_FREE_CERTIFICATES_INFO = 'getFreeCertificatesInfo';
    public const METHOD_GET_CERTIFICATE = 'getCertificate';
    public const METHOD_ACTIVATE_CERTIFICATE = 'activateCertificate';
    public const METHOD_REISSUE_CERTIFICATE = 'reissueCertificate';

    public const ERR_CODE_WRONG_USERNAME = 4010;
    public const ERR_CANT_GET_NC_USER_ERROR = 2200;

    public const CONST_GET_LIST_PAGE = 1;
    public const CONST_GET_LIST_MAX_PAGE_SIZE = 99;
    public const CONST_GET_LIST_MAX_ITERATIONS = 100;

    public const GATEWAY_DELAY = 10;
    public const GATEWAY_RETRY_LIMIT = 3;
    public function __construct(
        #[Autowire(env: 'NC_GATEWAY_API_URL')]
        private readonly string $apiUrl,
        #[Autowire(env: 'NC_GATEWAY_API_ACCESS_TOKEN')]
        private readonly string $gatewayAccessToken,
        #[Autowire(service: 'guzzle_client')]
        private readonly Client $client,
        private readonly StateUser $stateUser,
        private readonly LoggerInterface $gatewayApiLogger,
    ) {
        parent::__construct($this->apiUrl, $this->gatewayAccessToken, $this->client);
    }

    /**
     * @param array $parameters
     *
     * @return array
     * @throws NcGatewayApiException
     * @throws Exception
     */
    public function getCertificate(array $parameters): array
    {
        $scheme = $this->createSchemeGetCertificate();
        $this->checkValidation($parameters, $scheme);

        return $this->makeCall(self::METHOD_GET_CERTIFICATE, $parameters);
    }

    /**
     * @param CertificateTransfer $certificateTransfer
     * @param string $csr
     * @param bool $isAsyncExternalCall
     *
     * @return array
     * @throws NcGatewayApiException
     * @throws Exception
     */
    public function activate(CertificateTransfer $certificateTransfer, string $csr, bool $isAsyncExternalCall): array
    {
        $parameters = [
            'certificateId' => (string) $certificateTransfer->getCertificateId(),
            'csr' => $csr,
            'username' => $certificateTransfer->getNCUser(),
        ];

        if ($isAsyncExternalCall) {
            $parameters['async'] = true;
        }

        $adminEmailAddress = $certificateTransfer->getAdminEmail();
        if ($adminEmailAddress) {
            $parameters['adminEmailAddress'] = $adminEmailAddress;
        }

        $scheme = $this->createSchemeForActivate();
        $this->checkValidation($parameters, $scheme);

        return $this->makeCall(self::METHOD_ACTIVATE_CERTIFICATE, $parameters);
    }

    /**
     * @param string $certificateId
     * @param string $csr
     * @param string $userName
     * @param string $adminEmailAddress
     *
     * @return array
     * @throws NcGatewayApiException
     * @throws Exception
     */
    public function reissue(string $certificateId, string $csr, string $userName, string $adminEmailAddress): array
    {
        $parameters = [
            'certificateId' => $certificateId,
            'csr' => $csr,
            'username' => $userName
        ];

        if ($adminEmailAddress) {
            $parameters['adminEmailAddress'] = $adminEmailAddress;
        }

        $scheme = $this->createSchemeForReissue();
        $this->checkValidation($parameters, $scheme);

        return $this->makeCall(self::METHOD_REISSUE_CERTIFICATE, $parameters);
    }

    /**
     * @param $id
     * @param $returnCert
     * @param $userName
     *
     * @return array
     * @throws NcGatewayApiException
     */
    public function getInfo($id, $returnCert, $userName): array
    {
        $parameters = [
            'CertificateID' => $id,
            'ncUserName' => $userName
        ];

        if ($returnCert) {
            $parameters['returncertificate'] = 'true';
            $parameters['returntype'] = 'individual';
        }

        $scheme = $this->createSchemeForGetInfo();
        $this->checkValidation($parameters, $scheme);

        $certificate = $this->makeCall(self::METHOD_HANDLE_GET_INFO, $parameters);

        if ($certificate['status'] === Certificate::NCSTATUS_NEWPURCHASE) {
            $certificate['common_name'] = '';
        }

        $certificate['expires'] = $certificate['expires'] != '' ? strtotime($certificate['expires']) : 0;
        $certificate['activation_expire_date'] = $certificate['activation_expire_date'] != '' ? strtotime($certificate['activation_expire_date']) : 0;
        $certificate['issued_on'] = $certificate['issued_on'] != '' ? strtotime($certificate['issued_on']) : 0;

        return $certificate;
    }

    /**
     * @return array
     * @throws Exception
     */
    public function getFreeCertificatesInfo(): array
    {
        return $this->makeCall(self::METHOD_GET_FREE_CERTIFICATES_INFO, [
            'userName' => $this->stateUser->getUser()->getName(),
            'cPanelServer' => gethostname()
        ]);
    }

    /**
     * @param string $list_type
     * @param string $search_query
     * @param string $sort_by
     *
     * @return array
     * @throws Exception
     */
    public function getSslList(string $list_type = 'ALL', string $search_query = '', string $sort_by = 'PURCHASEDATE_DESC'): array
    {
        $certs = [];
        $parameters = [
            'ncUserName' => $this->stateUser->getUser()->getNcLogin(),
            'PageSize' => self::CONST_GET_LIST_MAX_PAGE_SIZE,
            'Page' => self::CONST_GET_LIST_PAGE,
            'ListType' => $list_type,
            'SortBy' => $sort_by,
        ];

        if ($search_query !== '') {
            $parameters['SearchTerm'] = $search_query;
        }

        for ($i = 0; $i < self::CONST_GET_LIST_MAX_ITERATIONS; $i++) {
            $response = $this->makeCall(self::METHOD_HANDLE_GET_LIST, $parameters);

            foreach ($response['certificatesList'] as $certInfo) {
                $certInfo['status'] = strtoupper($certInfo['status']);
                if ($certInfo['status'] === Certificate::NCSTATUS_NEWPURCHASE) {
                    $certInfo['common_name'] = '';
                }

                $certs[] = $certInfo;
            }

            $total_items = (int)$response['meta']['totalItems'];
            if (($parameters['Page'] * $parameters['PageSize']) >= $total_items || $total_items == 0) {
                break;
            }

            ++$parameters['Page'];
        }

        return $certs;
    }

    /**
     * @return Assert\Collection
     */
    protected function createSchemeForReissue(): Assert\Collection
    {
        return new Assert\Collection([
            'certificateId' => new Assert\Required([
                new Assert\NotBlank(),
                new Assert\Type('string'),
            ]),
            'csr' => new Assert\Required([
                new Assert\NotBlank(),
                new Assert\Type('string'),
            ]),
            'username' => new Assert\Required([
                new Assert\NotBlank(),
                new Assert\Type('string'),
            ]),
        ], allowExtraFields: true);
    }

    /**
     * @return Assert\Collection
     */
    protected function createSchemeForActivate(): Assert\Collection
    {
        return new Assert\Collection([
            'certificateId' => new Assert\Required([
                new Assert\NotBlank(),
                new Assert\Type('string'),
            ]),
            'username' => new Assert\Required([
                new Assert\NotBlank(),
                new Assert\Type('string'),
            ]),
            'csr' => new Assert\Required([
                new Assert\NotBlank(),
                new Assert\Type('string'),
            ]),
        ], allowExtraFields: true);
    }

    /**
     * @return Assert\Collection
     */
    protected function createSchemeGetCertificate(): Assert\Collection
    {
        return new Assert\Collection([
            'cPanelServer' => new Assert\Required([
                new Assert\NotBlank(),
                new Assert\Type('string'),
            ]),
            'userName' => new Assert\Required([
                new Assert\NotBlank(),
                new Assert\Type('string'),
            ]),
            'domainName' => new Assert\Required([
                new Assert\NotBlank(),
                new Assert\Type('string'),
            ]),
        ], allowExtraFields: true);
    }

    /**
     * @return Assert\Collection
     */
    protected function createSchemeForGetInfo(): Assert\Collection
    {
        return new Assert\Collection([
            'CertificateID' => new Assert\Required([
                new Assert\NotBlank(),
                new Assert\Type('string'),
            ]),
            'ncUserName' => new Assert\Required([
                new Assert\NotBlank(),
                new Assert\Type('string'),
            ]),
            'returncertificate' => new Assert\Optional([
                new Assert\Type('string'),
            ]),
            'returntype' => new Assert\Optional([
                new Assert\Type('string'),
            ]),
        ], allowExtraFields: true);
    }

    /**
     * @param array $parameters
     * @param mixed $scheme
     *
     * @return void
     * @throws NcGatewayApiException
     */
    protected function checkValidation(array $parameters, mixed $scheme): void
    {
        $validator = Validation::createValidator();
        $errors = $validator->validate($parameters, $scheme);

        if (count($errors) > 0) {
            throw new NcGatewayApiException('Invalid parameters');
        }
    }

    /**
     * @param string $method
     * @param array $parameters
     * @param bool $loggerEnabled
     *
     * @return array
     * @throws Exception
     */
    public function makeCall(string $method = '', array $parameters = [], bool $loggerEnabled = true): array
    {
        $retryCounter = 0;
        do {
            try {
                $response = $this->call($method, $parameters);
                if ($loggerEnabled) {
                    $this->gatewayApiLogger->info('Nc Gateway request & response', [
                        'method' => $method,
                        'url' => $this->apiUrl,
                        'request' => json_encode($parameters, JSON_THROW_ON_ERROR),
                        'response' => $response ? json_encode($this->removeSensitiveInfo($response), JSON_THROW_ON_ERROR) : '[empty response]'
                    ]);
                }

                return $response;
            } catch (Exception $e) {
                $this->handleError($e, $retryCounter++);
            }
        } while (true);
    }

    /**
     * @param Exception $e
     * @param int $attempt
     *
     * @return void
     * @throws Exception
     */
    private function handleError(Exception $e, int $attempt): void
    {
        $message = $e->getMessage();
        if ($e->getCode() === self::ERR_CODE_WRONG_USERNAME) {
            $message = 'Invalid parameters: "userName"';
        }

        if ($e->getCode() === self::ERR_CANT_GET_NC_USER_ERROR) {
            if ($attempt !== self::GATEWAY_RETRY_LIMIT) {
                $logContext['error'] = sprintf('Error. Code (%s), Message: (%s), Attempt: (%s)', $e->getCode(), $message, $attempt);

                $this->gatewayApiLogger->error($message, $logContext);

                sleep(self::GATEWAY_DELAY);
                return;
            }

            $message = 'Can\'t get NC user for: "userName"';
        }

        $logContext['error'] = sprintf('Error. Code (%s), Message: (%s)', $e->getCode(), $message);

        $this->gatewayApiLogger->error($message, $logContext);
        throw $e;
    }

    /**
     * @param array $content
     * @return array
     */
    protected function removeSensitiveInfo(array $content = []): array
    {
        $contentObject = new ArrayObject($content);
        $result = $contentObject->getArrayCopy();

        if (isset($result['ca_certs'])) {
            $result['ca_certs'] = '';
        }

        if (isset($result['cert_body'])) {
            $result['cert_body'] = '';
        }

        if (isset($result['csr'])) {
            $result['csr'] = '';
        }

        return $result;
    }
}
Back to Directory File Manager