Viewing File: /usr/local/cpanel/whostmgr/docroot/cgi/ncssl/source/src/Service/PluginGateway/NcCoreApi.php
<?php
namespace App\Service\PluginGateway;
use App\Entity\Certificate;
use App\Service\Auth\Auth;
use App\Service\Auth\OAuthException;
use App\Service\NcPlugin\InvalidAccessTokenNcApiException;
use App\Service\NcPlugin\NcApiException;
use App\Service\NcPlugin\PluginException;
use App\Service\State\StateUser;
use DOMDocument;
use DOMElement;
use Monolog\Level;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class NcCoreApi extends AbstractApi
{
/**
* NC API commands
*/
const CMD_NC_GET_INFO = 'namecheap.ssl.getInfo';
const CMD_NC_ACTIVATE = 'namecheap.ssl.activate';
const CMD_NC_REISSUE = 'namecheap.ssl.reissue';
const CMD_NC_GET_SSL_LIST = 'namecheap.ssl.getList';
const CMD_NC_GET_USER_INFO = 'namecheap.users.getInfo';
/**
* Configuration options:
* <pre>
* array with following keys:
*
* - [optional if we use oAuth] API user and key for calling NC API (got from NC user account: http://www.namecheap.com/myaccount/modify-profile-api.asp )
* 'user' => 'API_USER',
* 'key' => 'API_KEY',
*
* - [optional] cPanel related fields
* 'cpAccountName' => 'cPanelAccountName', //Need only for work with RCC
* 'cpServerName' => 'cPanelServerName', //Need only for work with RCC
*
* - [optional] data for technical params (used for activation/reissue)
* 'techs' => array(
* 'TechEmailAddress' => 'TechEmailAddress',
* 'TechFirstName' => 'TechFirstName',
* 'TechLastName' => 'TechLastName',
* 'TechOrganizationName' => 'TechOrganizationName',
* 'TechAddress1' => 'TechAddress1',
* 'TechAddress2' => 'TechAddress2',
* 'TechJobTitle' => 'TechJobTitle',
* 'TechCity' => 'TechCity',
* 'TechStateProvince' => 'TechStateProvince',
* 'TechStateProvinceChoice' => 'TechStateProvinceChoice',
* 'TechPostalCode' => 'TechPostalCode',
* 'TechCountry' => 'TechCountry',
* 'TechPhone' => 'TechPhone',
* 'TechPhoneExt' => 'TechPhoneExt',
* 'TechFax' => 'TechFax',
* ),
*
* - [optional] data for billing params, can be the same as technical params (used for activation/reissue)
* 'bill' => array(
* 'BillingEmailAddress' => 'TechEmailAddress',
* 'BillingFirstName' => 'TechFirstName',
* 'BillingLastName' => 'TechLastName',
* 'BillingOrganizationName' => 'TechOrganizationName',
* 'BillingAddress1' => 'TechAddress1',
* 'BillingAddress2' => 'TechAddress2',
* 'BillingJobTitle' => 'TechJobTitle',
* 'BillingCity' => 'TechCity',
* 'BillingStateProvince' => 'TechStateProvince',
* 'BillingStateProvinceChoice' => 'TechStateProvinceChoice',
* 'BillingPostalCode' => 'TechPostalCode',
* 'BillingCountry' => 'TechCountry',
* 'BillingPhone' => 'TechPhone',
* 'BillingPhoneExt' => 'TechPhoneExt',
* 'BillingFax' => 'TechFax',
* ),
* </pre>
* @param StateUser $stateUser
* @param Auth $auth
* @param string $apiUrl
* @param LoggerInterface $ncCoreApiLogger
*/
public function __construct(
protected StateUser $stateUser,
private readonly Auth $auth,
#[Autowire(env: 'NC_CORE_API_URL')]
private readonly string $apiUrl,
protected LoggerInterface $ncCoreApiLogger
) {
$this->logger = $this->ncCoreApiLogger;
}
/**
* Gets certificates list
*
* @param string $list_type - Possible values are ALL,Processing,EmailSent,TechnicalProblem,InProgress,Completed,Deactivated,Active,Cancelled,NewPurchase,NewRenewal.
* @param string $search_query - Keyword to look for on the SSL list
* @param string $sort_by - Possible values are PURCHASEDATE, PURCHASEDATE_DESC, SSLTYPE, SSLTYPE_DESC, EXPIREDATETIME, EXPIREDATETIME_DESC,Host_Name, Host_Name_DESC.
* @return array - array of ssl certificates
* E.g.:
* [
* [
* 'id' => '123456',
* 'common_name' => '',
* 'type' => 'positivessl free',
* 'purchase_date' => 1521468127,
* 'expires' => 0,
* 'is_expired' => false,
* 'status' => 'NEWPURCHASE',
* 'activation_expire_date' => 0,
* 'years' => '1',
* ],
* [
* 'id' => '123457',
* 'common_name' => 'example.com',
* 'type' => 'positivessl',
* 'purchase_date' => 1521468119,
* 'expires' => 0,
* 'is_expired' => false,
* 'status' => 'NEWRENEWAL',
* 'activation_expire_date' => 0,
* 'years' => '1',
* ],
* ]
* @throws InvalidAccessTokenNcApiException
* @throws NcApiException
*/
public function getSslList($list_type = 'ALL', $search_query = '', $sort_by = 'PURCHASEDATE_DESC'): array
{
$certs = [];
$params = [
'PageSize' => 99,
'Page' => 1,
'ListType' => $list_type,
'SortBy' => $sort_by,
];
if ($search_query != '') {
$params['SearchTerm'] = $search_query;
}
$total_items = 0;
for ($ii = 0; $ii < 100; $ii++) {
$response = $this->call(self::CMD_NC_GET_SSL_LIST, $params);
/** @var $certInfo DOMElement */
foreach ($response->getElementsByTagName('SSL') as $certInfo) {
$buf = [
'id' => $certInfo->hasAttribute('CertificateID')
? $certInfo->getAttribute('CertificateID')
: '',
'common_name' => $certInfo->hasAttribute('HostName')
? $certInfo->getAttribute('HostName')
: '',
'type' => $certInfo->hasAttribute('SSLType')
? $certInfo->getAttribute('SSLType')
: '',
'purchase_date' => $certInfo->hasAttribute('PurchaseDate')
? $certInfo->getAttribute('PurchaseDate')
: '',
'expires' => $certInfo->hasAttribute('ExpireDate')
? $certInfo->getAttribute('ExpireDate')
: '',
'is_expired' => $certInfo->hasAttribute('IsExpiredYN') && strtolower($certInfo->getAttribute('IsExpiredYN')) === 'true',
'status' => $certInfo->hasAttribute('Status')
? strtoupper($certInfo->getAttribute('Status'))
: '',
'activation_expire_date' => $certInfo->hasAttribute('ActivationExpireDate')
? $certInfo->getAttribute('ActivationExpireDate')
: '',
'years' => $certInfo->hasAttribute('Years')
? $certInfo->getAttribute('Years')
: '',
];
$buf['expires'] = $buf['expires'] != ''
? strtotime($buf['expires'])
: 0;
$buf['purchase_date'] = $buf['purchase_date'] != ''
? strtotime($buf['purchase_date'])
: 0;
$buf['activation_expire_date'] = $buf['activation_expire_date'] != ''
? strtotime($buf['activation_expire_date'])
: 0;
if ($buf['status'] === Certificate::NCSTATUS_NEWPURCHASE) {
$buf['common_name'] = null;
}
$certs[] = $buf;
}
if ($total_items === 0) {
$totalItemsElements = $response->getElementsByTagName('TotalItems');
if ($totalItemsElements->length > 0) {
$total_items = (int)$totalItemsElements->item(0)->nodeValue;
}
}
if (($params['Page'] * $params['PageSize']) >= $total_items || $total_items == 0) {
break;
}
$params['Page'] += 1;
}
return $certs;
}
/**
* @param $allowedStatuses
*
* @return array
* @throws InvalidAccessTokenNcApiException
* @throws NcApiException
*/
public function getSslListWithStatuses($allowedStatuses): array
{
$getExpired = false;
if (false !== ($expiredKey = array_search('EXPIRED', $allowedStatuses))) {
$getExpired = true;
unset($allowedStatuses[$expiredKey]);
}
$list = [];
foreach ($allowedStatuses as $status) {
$list = array_merge($list, $this->getSslList(strtoupper($status)));
}
if (!$getExpired) {
return array_filter($list, function($ssl) {
return !$ssl['is_expired'];
});
}
return $list;
}
/**
* @param $id
* @param bool $returnCert
* @param string $userName
* @return array
* @throws NcApiException|InvalidAccessTokenNcApiException
*/
public function getInfo($id, $returnCert = false, $userName = ''): array
{
$params = [
'CertificateID' => $id,
];
if ($returnCert) {
$params['returncertificate'] = 'true';
$params['returntype'] = 'individual';
}
$response = $this->call(self::CMD_NC_GET_INFO, $params);
$ssl_info = $response->getElementsByTagName('SSLGetInfoResult')->item(0);
$ssl_info_doc = new DomDocument;
$ssl_info_doc->appendChild($ssl_info_doc->importNode($ssl_info, true));
$ssl_info_xpath = new \DOMXPath($ssl_info_doc);
$certificate = [
'id' => $id,
'status' => strtoupper($ssl_info?->getAttribute('Status')),
'status_desc' => $ssl_info?->getAttribute('StatusDescription'),
'type' => $ssl_info?->getAttribute('Type'),
'issued_on' => $ssl_info?->getAttribute('IssuedOn'),
'expires' => $ssl_info?->getAttribute('Expires'),
'activation_expire_date' => $ssl_info?->getAttribute('ActivationExpireDate'),
'order_id' => $ssl_info?->getAttribute('OrderId'),
'sans_count' => $ssl_info?->getAttribute('SANSCount'),
'replaced_by' => $ssl_info?->getAttribute('ReplacedBy'),
'approver_email' => $this->getXmlElementValue($ssl_info_doc, 'ApproverEmail'),
'common_name' => $ssl_info_doc?->getElementsByTagName('CommonName')?->item(0)?->nodeValue,
'admin_name' => $ssl_info_doc?->getElementsByTagName('AdministratorName')?->item(0)?->nodeValue,
'admin_email' => $ssl_info_doc?->getElementsByTagName('AdministratorEmail')?->item(0)?->nodeValue,
'csr' => '',
'cert_body' => '',
'ca_certs' => array(),
];
if ($certificate['status'] === Certificate::NCSTATUS_NEWPURCHASE) {
$certificate['common_name'] = null;
}
$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;
$csr = $ssl_info_doc->getElementsByTagName('CSR');
if ($csr->length > 0) {
$certificate['csr'] = $csr->item(0)->nodeValue;
}
$cert = $ssl_info_xpath->query('//CertificateDetails/Certificates/Certificate');
if ($cert->length > 0) {
$certificate['cert_body'] = $cert->item(0)->nodeValue;
}
$ca_certs = $ssl_info_xpath->query('//CertificateDetails/Certificates/CaCertificates/Certificate/Certificate');
foreach ($ca_certs as $ca_cert) {
$certificate['ca_certs'][] = $ca_cert->nodeValue;
}
return $certificate;
}
/**
* @return array
*
* @throws NcApiException
*/
public function getUserInfo(): array
{
$response = $this->call(self::CMD_NC_GET_USER_INFO);
$user_info = $response->getElementsByTagName('UserGetInfoResult')->item(0);
$default_addr = $response->getElementsByTagName('DefaultAddress')->item(0);
$default_addr_doc = new DomDocument;
$default_addr_doc->appendChild($default_addr_doc->importNode($default_addr, true));
$profile_addr = $response->getElementsByTagName('ProfileAddress')->item(0);
$profile_addr_doc = new DomDocument;
$profile_addr_doc->appendChild($profile_addr_doc->importNode($profile_addr, true));
return [
'id' => $user_info->getAttribute('UserID'),
'login' => $user_info->getAttribute('UserName'),
'default_address' => array(
'first_name' => $default_addr_doc->getElementsByTagName('FirstName')->item(0)->nodeValue,
'last_name' => $default_addr_doc->getElementsByTagName('LastName')->item(0)->nodeValue,
'email' => $default_addr_doc->getElementsByTagName('EmailAddress')->item(0)->nodeValue,
'address1' => $default_addr_doc->getElementsByTagName('Address1')->item(0)->nodeValue,
'address2' => $default_addr_doc->getElementsByTagName('Address2')->item(0)->nodeValue,
'city' => $default_addr_doc->getElementsByTagName('City')->item(0)->nodeValue,
'state_province' => $default_addr_doc->getElementsByTagName('StateProvince')->item(0)->nodeValue,
'state_province_choice' => $default_addr_doc->getElementsByTagName('StateProvinceChoice')->item(0)->nodeValue,
'zip' => $default_addr_doc->getElementsByTagName('Zip')->item(0)->nodeValue,
'country' => $default_addr_doc->getElementsByTagName('Country')->item(0)->nodeValue,
'phone' => $default_addr_doc->getElementsByTagName('Phone')->item(0)->nodeValue,
'phone_ext' => $default_addr_doc->getElementsByTagName('PhoneExt')->item(0)->nodeValue,
'fax' => $default_addr_doc->getElementsByTagName('Fax')->item(0)->nodeValue,
'job_title' => $default_addr_doc->getElementsByTagName('JobTitle')->item(0)->nodeValue,
'organization' => $default_addr_doc->getElementsByTagName('Organization')->item(0)->nodeValue,
),
'profile_address' => array(
'first_name' => $this->getXmlElementValue($profile_addr_doc, 'FirstName'),
'last_name' => $this->getXmlElementValue($profile_addr_doc, 'LastName'),
'email' => $this->getXmlElementValue($profile_addr_doc, 'EmailAddress'),
'address1' => $this->getXmlElementValue($profile_addr_doc, 'Address1'),
'address2' => $this->getXmlElementValue($profile_addr_doc, 'Address2'),
'city' => $this->getXmlElementValue($profile_addr_doc, 'City'),
'state_province' => $this->getXmlElementValue($profile_addr_doc, 'StateProvince'),
'state_province_choice' => $this->getXmlElementValue($profile_addr_doc, 'StateProvinceChoice'),
'zip' => $this->getXmlElementValue($profile_addr_doc, 'Zip'),
'country' => $this->getXmlElementValue($profile_addr_doc, 'Country'),
'phone' => $this->getXmlElementValue($profile_addr_doc, 'Phone'),
'phone_ext' => $this->getXmlElementValue($profile_addr_doc, 'PhoneExt'),
'fax' => $this->getXmlElementValue($profile_addr_doc, 'Fax'),
'job_title' => $this->getXmlElementValue($profile_addr_doc, 'JobTitle'),
'organization' => $this->getXmlElementValue($profile_addr_doc, 'Organization'),
),
];
}
protected function getXmlElementValue($xml, $tagName)
{
$nodeSet = $xml->getElementsByTagName($tagName);
if (!empty($nodeSet)) {
$node = $nodeSet->item(0);
if (!empty($node)) {
return $node->nodeValue;
}
}
return null;
}
/**
* Activates certificate
*
* @param int $certId
* @param string $csr
* @param string $webserverType
* @param array $adminDetails
* @param string $sslType
* @return array
* @throws NcApiException
*/
public function activate(int $certId, string $csr, string $webserverType, array $adminDetails, string $sslType = ''): array
{
return $this->makeActivation(self::CMD_NC_ACTIVATE, $certId, $csr, $webserverType, $adminDetails, $sslType);
}
/**
* Reissues certificate
*
* @param int $certId
* @param string $csr
* @param string $webserverType
* @param array $adminDetails
* @param string $sslType
* @return array
* @throws NcApiException
* @throws InvalidAccessTokenNcApiException
*/
public function reissue(int $certId, string $csr, string $webserverType, array $adminDetails, string $sslType = ''): array
{
return $this->makeActivation(self::CMD_NC_REISSUE, $certId, $csr, $webserverType, $adminDetails, $sslType);
}
/**
* @param string $command
* @param array $params
* @param bool $loggerEnabled
*
* @return DOMDocument
*
* @throws NcApiException
* @throws InvalidAccessTokenNcApiException
*/
private function call(string $command, array $params = [], bool $loggerEnabled = true): DOMDocument
{
$response = $this->makeCall($command, $params, $loggerEnabled);
try {
//Hack to remove warning: The default (no prefix) Namespace URI for XPath queries is always '' and it cannot be redefined to 'http://api.namecheap.com/xml.response'.
$response = preg_replace('#xmlns\=\"(.+?)\"#', '', $response);
$dom = new DOMDocument('1.0', 'utf-8');
// Load Xml Data
if (!$dom->loadXML($response)) {
throw new NcApiException('Can\'t get a data from NC probably problem with auth tokens', NcApiException::NC_INVALID_RESPONSE);
}
} catch (NcApiException $e) {
throw new NcApiException(
'Internal error occurred. Please try again. More Info: ' . $e->getMessage(),
$e->getCode()
);
} catch (\Exception $e) {
throw new NcApiException(
'Some error occurred. Please try again. More Info: ' . $e->getMessage(),
$e->getCode(),
$e
);
}
if (str_contains($response, '500-UnhandledException')) {
throw new NcApiException('Can\'t get a data from NC probably problem with auth tokens', NcApiException::NC_INVALID_RESPONSE);
}
/** @var DOMElement $apiError */
$apiError = $dom->getElementsByTagName('Error')->item(0);
if (!empty($apiError->nodeValue)
// exception for CREATE command: it should be ignored
&& strtolower(trim($apiError->nodeValue)) != 'certificate cannot be created as the purchasevalidationid already exists.'
) {
if (!stripos($apiError->nodeValue, 'Invalid access token')){
throw new InvalidAccessTokenNcApiException(
'Invalid access token. More Info: ' . $apiError->nodeValue,
$apiError->hasAttribute('Number') ? $apiError->getAttribute('Number') : -1
);
}
throw new NcApiException(
'Service provider error occurred. More Info: ' . $apiError->nodeValue,
$apiError->hasAttribute('Number') ? $apiError->getAttribute('Number') : -1
);
}
return $dom;
}
/**
* @param string $command
* @param array $params
* @param bool $loggerEnabled
*
* @return string
*
* @throws PluginException|OAuthException
*/
private function makeCall(string $command, array $params, bool $loggerEnabled): string
{
$requestParameters = $this->createRequestParameters($command, $params);
$response = $this->auth->accessResource($this->apiUrl, $requestParameters);
if ($loggerEnabled) {
$this->log(Level::Info, [
'request' => http_build_query($requestParameters),
'response' => $response ?: '[empty response]',
]);
}
return $response;
}
/**
* @param string $command
* @param array $params
*
* @return array
*/
private function createRequestParameters(string $command, array $params): array
{
$username = $this->stateUser->getUser()->getNcLogin();
return array_merge($params, [
'Command' => $command,
'ClientIp' => $this->getClientIp(),
'UserName' => $username,
'ApiUser' => $username,
]);
}
/**
* Performs activation/reissue operations
*
* @param string $command
* @param int $certId
* @param string $csr
* @param string $webserverType
* @param array $adminDetails
* @param string $sslType It's better send it to make sure the CA is Comodo and HTTP-validation must be used for parseCsr NC API command
* @return array
*@throws NcApiException|InvalidAccessTokenNcApiException
*/
private function makeActivation(string $command, int $certId, string $csr, string $webserverType, array $adminDetails, string $sslType = ''): array
{
$request = $this->getActivateParams($certId, $csr, $webserverType, $adminDetails, $sslType);
$response = $this->call($command, $request);
/** @var DOMElement $details */
if ($command == self::CMD_NC_ACTIVATE) {
$details = $response->getElementsByTagName('SSLActivateResult')->item(0);
} else {
$details = $response->getElementsByTagName('SSLReissueResult')->item(0);
}
if (!$details instanceof DOMElement) {
throw new NcApiException('Cannot get activation result data');
}
// we didn't make the parseCsr request or it didn't contain HTTP-validation field
if (empty($fileName) || empty($fileContent)) {
$httpValidation = $response->getElementsByTagName('HttpDCValidation')->item(0);
if (!$httpValidation) {
if (empty($fileName) && empty($fileContent)) {
throw new NcApiException('Cannot get the HTTP validation data');
} else {
throw new NcApiException('HTTP validation data is empty');
}
}
$fileName = $httpValidation->getElementsByTagName('FileName')->length
? trim($httpValidation->getElementsByTagName('FileName')->item(0)->nodeValue)
: '';
$fileContent = $httpValidation->getElementsByTagName('FileContent')->length
? trim($httpValidation->getElementsByTagName('FileContent')->item(0)->nodeValue)
: '';
if (empty($fileName) || empty($fileContent)) {
throw new NcApiException('HTTP validation data is empty');
}
}
return [
'ID' => $details->getAttribute('ID'),
'Status' => 'ORDERCREATED',
'ValidationFileName' => $fileName,
'ValidationFileContent' => $fileContent,
];
}
/**
* @param int $certId
* @param string $csr
* @param string $webServerType
* @param array $adminDetails
* @param string $sslType
*
* @return array
*/
private function getActivateParams(int $certId, string $csr, string $webServerType, array $adminDetails, string $sslType = ''): array
{
$orgCertificates = array('premiumssl wildcard', 'instantssl', 'instantssl pro', 'premiumssl');
$request = array(
'HTTPDCValidation' => 'TRUE',
'CertificateID' => $certId,
'csr' => $csr,
'WebServerType' => $webServerType,
);
if (!empty($sslType) && in_array(strtolower($sslType), $orgCertificates)) {
$orgRepPhone = ($adminDetails['OrganizationRepPhoneno'] != '')
? $adminDetails['OrganizationRepPhoneExt'] . '.' . $adminDetails['OrganizationRepPhoneno']
: '';
$orgRepFax = ($adminDetails['OrganizationRepFaxno'] != '')
? $adminDetails['OrganizationRepFaxExt'] . '.' . $adminDetails['OrganizationRepFaxno']
: '';
$ovData = array(
'OrganizationRepPhone' => $orgRepPhone,
'OrganizationRepFax' => $orgRepFax
);
$request = array_merge($request, $ovData);
if ($adminDetails['OrganizationRepCallbackDestinationSame'] == 'Yes') {
unset($adminDetails['OrganizationRepLegalName']);
unset($adminDetails['OrganizationRepAddress1']);
unset($adminDetails['OrganizationRepAddress2']);
unset($adminDetails['OrganizationRepCity']);
unset($adminDetails['OrganizationRepStateProvince']);
unset($adminDetails['OrganizationRepPostalCode']);
unset($adminDetails['OrganizationRepCountry']);
unset($adminDetails['OrganizationRepDUNS']);
unset($adminDetails['OrganizationRepPostOfficeBox']);
}
unset($adminDetails['OrganizationRepPhoneExt']);
unset($adminDetails['OrganizationRepPhoneno']);
unset($adminDetails['OrganizationRepFaxExt']);
unset($adminDetails['OrganizationRepFaxno']);
}
if (isset($adminDetails['OrganizationPhoneNum'])) {
$adminDetails['OrganizationPhone'] = $adminDetails['OrganizationPhoneCode'] . '.'
. $adminDetails['OrganizationPhoneNum'];
unset($adminDetails['OrganizationPhoneCode']);
unset($adminDetails['OrganizationPhoneNum']);
}
if (isset($adminDetails['OrganizationFaxNum'])) {
$adminDetails['OrganizationFax'] = !empty($adminDetails['OrganizationFaxNum'])
? $adminDetails['OrganizationFaxCode'] . '.' . $adminDetails['OrganizationFaxNum']
: '';
unset($adminDetails['OrganizationFaxCode']);
unset($adminDetails['OrganizationFaxNum']);
}
return array_filter(array_merge($request, $adminDetails), static function($var) {
return $var !== null;
});
}
/**
* @return string
*/
private function getClientIp(): string
{
return $_SERVER['REMOTE_ADDR'] ?? $_SERVER['HTTP_HOST'] ?? '127.0.0.1';
}
}
Back to Directory
File Manager