04 October 2023  —  Marina Smolyanaya

Fully functional Acronis Cyber Platform API PHP sample – WHMCS provisioning module

Acronis
Build your application quickly and easily with Acronis

The Acronis Cyber Cloud WHMCS provisioning module is a production-quality fully functional WHMCS integration developed by Acronis’ engineering team. The module is also available at the WHMCS Marketplace. It’s a valuable example of Acronis Cyber Platform API usage with PHP and a great quick starter for companies who want to build their own WHMCS module for Acronis Cyber Cloud. A developer can just reuse the code and business logic, or change what they need to change, as we publish it under the MIT license.

Acronis Cyber Platform PHP Client and Authorization Routine

The client for Acronis Cyber Platform provides rich functionality. The library located at src\includes\acroniscloud\vendor\acronis\ci-lib-php-cloud-client\lib\Api and contains ready-to-reuse advanced functions to work with Tenants, Users, Reports etc. All these advanced workloads use the base implementation of low-level API interactions located at src\includes\acroniscloud\lib\CloudApi.

The basis for API calls is implemented in ApiClient class, while all authorization routines are implemented in AuthorizedApi class. The client implementation is quite straightforward, it wraps HTTP calls into the callApi function which is used to call specific REST API. Let's see how authorization is implemented.

The process starts with the obtainAccessToken function in AuthorizedApi class. It supports 2 types of grant types: password and client credentials. For the password grant type, we directly request IDP API to receive an authorization token. For the client credentials grant type we need to make some additional preparation, see below:

/**

* @param bool $force

* @return string

*/

protected function obtainAccessToken($force = false)

{

return LocalApi::decryptPassword($this->fromCache(function () {

if ($this->getGrantType() === static::GRANT_TYPE_CLIENT_CREDENTIALS) {

$idpToken = $this->fetchClientAccessToken();

} else {

/** @var Token $idpToken */

$idpToken = $this->unauthorizedCall(function () {

return $this->getIdpApi()->postIdpToken(

'password', null, $this->getLogin(), $this->getPassword()

);

});

}

return LocalApi::encryptPassword($idpToken->getAccessToken());

}, $this->getAccessTokenTtl(), true, $this->getCredentialsHash(), $force));

}

And then we call IDP API as well.

private function fetchClientAccessToken()

{

$clientId = $this->getLogin();

$clientSecret = $this->getPassword();

$authCredentials = base64_encode("{$clientId}:$clientSecret");

$this->getApiClient()->getConfig()->addDefaultHeader(

'Authorization',

"Basic {$authCredentials}"

);

$idpToken = $this->unauthorizedCall(function () {

return $this->getIdpApi()->postIdpToken('client_credentials');

});

$this->getApiClient()->getConfig()->deleteDefaultHeader('Authorization');

return $idpToken;

}

Let's check what’s happened inside postIdpToken. You can see that it's just a HTTP POST call to the specific endpoint with all required parameters set.

public function postIdpToken($grant_type, $client_id = null, $username = null, $password = null, $refresh_token = null, $code = null, $scope = null, $assertion = null, $device_code = null, $totp_code = null)

{

// parameters check is removed here for code clearness

....

// parse inputs

$resourcePath = "/idp/token";

$httpBody = '';

$queryParams = [];

$headerParams = [];

$formParams = [];

$_header_accept = $this->apiClient->selectHeaderAccept(['application/json']);

if (!is_null($_header_accept)) {

$headerParams['Accept'] = $_header_accept;

}

$headerParams['Content-Type'] = $this->apiClient->selectHeaderContentType(['application/x-www-form-urlencoded']);

// params handling is removed here for code clearness

.....

// for model (json/xml)

if (isset($_tempBody)) {

$httpBody = $_tempBody; // $_tempBody is the method argument, if present

} elseif (count($formParams) > 0) {

$httpBody = $formParams; // for HTTP post (form)

}

// make the API Call

try {

list($response, $statusCode, $httpHeader) = $this->apiClient->callApi(

$resourcePath,

'POST',

$queryParams,

$httpBody,

$headerParams,

'\Acronis\Cloud\Client\Model\Idp\Token',

'/idp/token'

);

return new HttpResponse($statusCode, $httpHeader, $this->apiClient->getSerializer()->deserialize($response, '\Acronis\Cloud\Client\Model\Idp\Token', $httpHeader));

} catch (HttpException $e) {

// exceptopn handling is removed here for code clearness

......

}

throw $e;

}

}

Now we understand what class should be used for API calls and how to get an authorization token. Let's look into another API usage example.

Working with API

All API calls can be found in Api class located in src\includes\acroniscloud\lib\CloudApi\Api.php. It's a wrapper of specific classes from src\includes\acroniscloud\vendor\acronis\ci-lib-php-cloud-client\lib\Api.

For example, for a new tenant creation, the postTenants functions from TenantsApi class should be used.

public function createTenant(TenantPost $body)

{

return $this->authorizedCall(function () use ($body) {

return $this->getTenantsApi()->postTenants($body);

});

}

And the same way as it is implemented for IDP API calls, it's a wrapped POST call to /tenants endpoint with required parameters.

/**

* Operation postTenants

*

* Tenants

*

* @param \Acronis\Cloud\Client\Model\Tenants\TenantPost $body (required)

* @param string $_issues (optional)

* @throws \Acronis\Cloud\Client\ApiException on non-2xx response

* @return HttpResponse

*/

public function postTenants($body, $_issues = null)

{

// parameters check is removed here for code clearness

....

// parse inputs

$resourcePath = "/tenants";

$httpBody = '';

$queryParams = [];

$headerParams = [];

$formParams = [];

$_header_accept = $this->apiClient->selectHeaderAccept(['application/json']);

if (!is_null($_header_accept)) {

$headerParams['Accept'] = $_header_accept;

}

$headerParams['Content-Type'] = $this->apiClient->selectHeaderContentType(['application/json']);

/// params handling is removed here for code clearness

.....

// for model (json/xml)

if (isset($_tempBody)) {

$httpBody = $_tempBody; // $_tempBody is the method argument, if present

} elseif (count($formParams) > 0) {

$httpBody = $formParams; // for HTTP post (form)

}

// this endpoint requires OAuth (access token)

if (strlen($this->apiClient->getConfig()->getAccessToken()) !== 0) {

$headerParams['Authorization'] = 'Bearer ' . $this->apiClient->getConfig()->getAccessToken();

}

// make the API Call

try {

list($response, $statusCode, $httpHeader) = $this->apiClient->callApi(

$resourcePath,

'POST',

$queryParams,

$httpBody,

$headerParams,

'\Acronis\Cloud\Client\Model\Tenants\Tenant',

'/tenants'

);

return new HttpResponse($statusCode, $httpHeader, $this->apiClient->getSerializer()->deserialize($response, '\Acronis\Cloud\Client\Model\Tenants\Tenant', $httpHeader));

} catch (HttpException $e) {

// exceptopn handling is removed here for code clearness

......

}

throw $e;

}

}

Now we understand Acronis Cyber Cloud WHMCS provisioning module code structure for the Acronis Cyber Platform integration and can find, learn and reuse valuable code pieces.

If you have any question regarding Acronis Cyber Platform API samples or usage, please check the documentations at Acronis Developer Network portal or use Acronis Cyber Platform Forum.

Acronis Cyber Cloud WHMCS Integration and Hooks

Acronis Cyber Cloud WHMCS provisioning module uses WHMCS hooks model to integrate. You can find registered hooks in includes\hooks\acroniscloud.php.

$hooks = [

'ServerAdd',

'ServerDelete',

'ServerEdit',

'ClientEdit',

'ProductEdit',

'ServiceDelete',

'AdminAreaHeaderOutput',

'ClientAreaHeaderOutput',

'OrderProductUpgradeOverride',

];

$outputHooks = [

'AdminAreaHeaderOutput',

'ClientAreaHeaderOutput',

];

foreach ($hooks as $hook) {

add_hook($hook, 1, function ($parameters) use ($hook, $outputHooks) {

/** @var Dispatcher $dispatcher */

$response = '';

try {

$dispatcher = Locator::getInstance()->get(DispatcherFactory::NAME);

$response = $dispatcher->dispatch(

__FILE__,

ACRONIS_CLOUD_SERVICE_NAME . RequestInterface::ACTION_NAME_DELIMITER . $hook,

$parameters

);

} catch (Exception $e) {

// always suppress exceptions for hooks not to affect other WHMCS modules

}

return in_array($hook, $outputHooks) ? $response : $parameters;

});

}

And in src\includes\acroniscloud\controllers.php you can find hooks mapping.

'hooks' => new ModuleActionRouter([

'ServerAdd' => [Server::class, 'updateServerInfo'],

'ServerEdit' => [Server::class, 'updateServerInfo'],

'ServerDelete' => [Server::class, 'deleteInternalTag'],

'ClientEdit' => [ContactInfo::class, 'updateTenants'],

'ProductEdit' => [Product::class, 'setupCustomFields'],

'ServiceDelete' => [Subscription::class, 'terminate'],

'OrderProductUpgradeOverride' => [Product::class, 'beforeUpgrade'],

'AdminAreaHeaderOutput' => [Server::class, 'adminOutput'],

'ClientAreaHeaderOutput' => [CustomHeaderOutput::class, 'clientOutput'],

])

Additional information regarding WGMCS integrations development can be found at WHMCS portal for developers.

Summary

Now you know how to start coding your own Acronis Cyber Platform solution using PHP and Management API.

About Acronis

A Swiss company founded in Singapore in 2003, Acronis has 15 offices worldwide and employees in 50+ countries. Acronis Cyber Protect Cloud is available in 26 languages in 150 countries and is used by over 20,000 service providers to protect over 750,000 businesses.