Verified Commit d1b54bd5 authored by Elias Häußler's avatar Elias Häußler 🐛
Browse files

Initial commit

parents
Pipeline #147 passed with stage
in 22 seconds
* text=auto
/composer.lock export-ignore
/tests export-ignore
/phpunit.xml export-ignore
/.gitlab-ci.yml export-ignore
/vendor/
/temp/
/.build/
variables:
COMPOSER_CACHE_DIR: '/cache/composer'
COMPOSER_ALLOW_SUPERUSER: '1'
COMPOSER_NO_INTERACTION: '1'
stages:
- build
- test
build:test:
stage: build
image: webdevops/php:7.4
before_script:
- composer self-update
- composer global require hirak/prestissimo --no-progress
script:
- composer install --no-progress
artifacts:
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
paths:
- vendor/
expire_in: 1h
.test: &test-template
stage: test
image: webdevops/php-dev:$DOCKER_TAG
dependencies:
- build:test
script:
- composer test -- --coverage-text --colors=never --testdox
coverage: '/^\s*Lines:\s*\d+.\d+\%/'
only:
- branches
test:7.3:
<<: *test-template
variables:
DOCKER_TAG: '7.3'
test:7.4:
<<: *test-template
variables:
DOCKER_TAG: '7.4'
This diff is collapsed.
[![Pipeline](https://gitlab.elias-haeussler.de/eliashaeussler/cpanel-requests/badges/master/pipeline.svg)](https://gitlab.elias-haeussler.de/eliashaeussler/cpanel-requests/-/pipelines)
[![Coverage](https://gitlab.elias-haeussler.de/eliashaeussler/cpanel-requests/badges/master/coverage.svg)](https://gitlab.elias-haeussler.de/eliashaeussler/cpanel-requests/)
# cPanel Requests
A simple PHP project to make API requests on your [cPanel](https://cpanel.com/) installation. This allows you to
call modules inside the installation and interact with them to add, show or list data such as domains, e-mail accounts,
databases and so on.
**The project makes use of [UAPI](https://documentation.cpanel.net/display/DD/Guide+to+UAPI). Therefore, it is required
to have a cPanel installation with at least version 42 running**.
## Installation
Install the project using Composer:
```bash
composer require eliashaeussler/cpanel-requests
```
## Usage
Connect to your cPanel instance first:
```php
$cPanel = new \EliasHaeussler\CpanelRequests\Application\CPanel('example.com', 'admin', 'password');
$cPanel->authorize();
```
Now you're able to make API requests:
```php
$response = $cPanel->api('<module>', '<function>', ['optional' => 'parameters']);
if ($response->isValid()) {
// Do anything...
// Returned data can be fetched using $response->getData()
}
```
**Note that currently only GET requests are supported.**
Visit the [official documentation](https://documentation.cpanel.net/display/DD/Guide+to+UAPI)
to get an overview about available API modules and functions.
### Example API request
```php
$cPanel = new \EliasHaeussler\CpanelRequests\Application\CPanel('cpanel.example.com', 'user', 'password');
$authorized = $cPanel->authorize();
if (!$authorized) {
throw new \RuntimeException('Could not authorize at cPanel application.');
}
// Fetch domains from cPanel API
$response = $cPanel->api('DomainInfo', 'list_domains');
if (!$response->isValid()) {
throw new \RuntimeException('Got invalid response from cPanel application.');
}
$domains = $response->getData()->data;
echo 'My main domain is: ' . $domains->main_domain;
```
## Command-line usage
The project provides a console which can be used to execute several functions from the command line.
```bash
# General usage
vendor/bin/cpanel-requests
# Clear expired request cookie files
vendor/bin/cpanel-requests clear:cookie
vendor/bin/cpanel-requests clear:cookie --lifetime 604800
# Clear log files
vendor/bin/cpanel-requests clear:logfile
```
## License
[GPL 3.0 or later](LICENSE)
#!/usr/bin/env php
<?php
declare(strict_types=1);
/*
* This file is part of the Composer package "eliashaeussler/cpanel-requests".
*
* Copyright (C) 2020 Elias Häußler <elias@haeussler.dev>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../../vendor/autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) {
if (file_exists($file)) {
/** @noinspection PhpIncludeInspection */
require_once $file;
break;
}
}
if (!class_exists('Composer\\Autoload\\ClassLoader')) {
throw new \RuntimeException('Could not locate Composer autoloader.', 1593550498);
}
use EliasHaeussler\CpanelRequests\Command\ClearCookieCommand;
use EliasHaeussler\CpanelRequests\Command\ClearLogFileCommand;
use Symfony\Component\Console\Application;
// Create application
$app = new Application('cPanel Requests console');
// Register commands
$app->add(new ClearCookieCommand());
$app->add(new ClearLogFileCommand());
// Run application
/** @noinspection PhpUnhandledExceptionInspection */
$app->run();
{
"name": "eliashaeussler/cpanel-requests",
"description": "Small PHP library enabling requests to cPanel instances",
"type": "library",
"license": "GPL-3.0-or-later",
"authors": [
{
"name": "Elias Häußler",
"email": "elias@haeussler.dev",
"homepage": "https://haeussler.dev",
"role": "Maintainer"
}
],
"require": {
"php": ">= 7.1 < 7.5",
"ext-json": "*",
"guzzlehttp/guzzle": "^6.5",
"monolog/monolog": "^2.0",
"spomky-labs/otphp": "^9.1",
"symfony/console": "^4.2",
"symfony/finder": "^5.1"
},
"require-dev": {
"phpunit/phpunit": "^9.2",
"phpspec/prophecy-phpunit": "^2.0",
"donatj/mock-webserver": "^2.1"
},
"autoload": {
"psr-4": {
"EliasHaeussler\\CpanelRequests\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"EliasHaeussler\\CpanelRequests\\Tests\\": "tests"
}
},
"bin": "bin/cpanel-requests",
"scripts": {
"test": "phpunit -c phpunit.xml"
},
"config": {
"sort-packages": true
}
}
This diff is collapsed.
<phpunit
backupGlobals="true"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertWarningsToExceptions="true"
forceCoversAnnotation="false"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
verbose="false"
beStrictAboutTestsThatDoNotTestAnything="true"
>
<testsuites>
<testsuite name="Unit Tests">
<directory>tests/Unit</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-html" target=".build/log/coverage" showUncoveredFiles="true" />
</logging>
</phpunit>
<?php
declare(strict_types=1);
namespace EliasHaeussler\CpanelRequests\Application;
/*
* This file is part of the Composer package "eliashaeussler/cpanel-requests".
*
* Copyright (C) 2020 Elias Häußler <elias@haeussler.dev>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use EliasHaeussler\CpanelRequests\Application\Session\SessionInterface;
use EliasHaeussler\CpanelRequests\Exception\AuthenticationFailedException;
use EliasHaeussler\CpanelRequests\Exception\NotAuthenticatedException;
use EliasHaeussler\CpanelRequests\Exception\RequestFailedException;
use EliasHaeussler\CpanelRequests\Http\Response\ResponseInterface;
/**
Interface describing a single application.
*
* @author Elias Häußler <elias@haeussler.dev>
* @license GPL-3.0-or-later
*/
interface ApplicationInterface
{
/**
* Authorize selected user at this application and start session.
*
* Starts authorization for selected user at currently used application. The
* resulting session should be stored in this application instance in order
* to send further API requests.
*
* @throws AuthenticationFailedException if authorization failed with selected user
* @throws RequestFailedException if result of authorization request is invalid
* @return self This cPanel instance
*/
public function authorize(): self;
/**
* Quit currently active application session.
*
* Starts the logout process of the current session at this application. The result
* should be that no more active sessions are attached to this application instance.
*
* @throws RequestFailedException if exception occurs during logout request
*/
public function logout(): self;
/**
* Get currently active session.
*
* Returns the currently active session, if any session has been started, or NULL.
*
* @return SessionInterface|null Currently active session, if available, `NULL` otherwise
*/
public function getSession(): ?SessionInterface;
/**
* Check whether application has active session.
*
* Returns `true` if the currently used application has an opened session
* which is currently active.
*
* @return bool `true` if application has active session, `false` otherwise
*/
public function hasActiveSession(): bool;
/**
* Make API request at this application and return the API response.
*
* Sends an API request to the currently used application and returns the
* resulting API response. Note that for this method to work an active
* session is required. This can be achieved by calling {@see authorize}
* prior to sending API requests.
*
* @param string $module The application module to be requested
* @param string $function The module function to be requested
* @param array $parameters Optional parameters to be passed to the application module
* @return ResponseInterface API response object
* @throws NotAuthenticatedException if authentication progress is not done yet
* @throws RequestFailedException if result of API request is invalid
*/
public function api(string $module, string $function, array $parameters): ResponseInterface;
/**
* Send an request to the current application.
*
* Sends a request to the current application and returns the response.
*
* @param string $path Application request path
* @param array $options Additional request options, will be merged with default options
* @return ResponseInterface Response from application
* @throws RequestFailedException if application request fails
*/
public function request(string $path, array $options = []): ResponseInterface;
}
<?php
declare(strict_types=1);
namespace EliasHaeussler\CpanelRequests\Application;
/*
* This file is part of the Composer package "eliashaeussler/cpanel-requests".
*
* Copyright (C) 2020 Elias Häußler <elias@haeussler.dev>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use EliasHaeussler\CpanelRequests\Application\Session\SessionInterface;
use EliasHaeussler\CpanelRequests\Application\Session\WebSession;
use EliasHaeussler\CpanelRequests\Application\Traits\CookieAwareTrait;
use EliasHaeussler\CpanelRequests\Application\Traits\LogAwareTrait;
use EliasHaeussler\CpanelRequests\Exception\AuthenticationFailedException;
use EliasHaeussler\CpanelRequests\Exception\InactiveSessionException;
use EliasHaeussler\CpanelRequests\Exception\InvalidResponseDataException;
use EliasHaeussler\CpanelRequests\Exception\NotAuthenticatedException;
use EliasHaeussler\CpanelRequests\Exception\RequestFailedException;
use EliasHaeussler\CpanelRequests\Http\Protocol;
use EliasHaeussler\CpanelRequests\Http\Response\JsonResponse;
use EliasHaeussler\CpanelRequests\Http\Response\ResponseFactory;
use EliasHaeussler\CpanelRequests\Http\Response\ResponseInterface;
use EliasHaeussler\CpanelRequests\Http\UriBuilder;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\FileCookieJar;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Uri;
use GuzzleHttp\RequestOptions;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use OTPHP\TOTP;
use Psr\Http\Message\UriInterface;
/**
* General cPanel class.
*
* @author Elias Häußler <elias@haeussler.dev>
* @license GPL-3.0-or-later
*/
class CPanel implements ApplicationInterface
{
use CookieAwareTrait;
use LogAwareTrait;
/**
* @var string Session token parameter in API responses
*/
public const SESSION_TOKEN_PARAMETER = 'security_token';
/**
* @var string Currently used protocol in request URIs
*/
private $protocol;
/**
* @var string cPanel host
*/
private $host = Protocol::HTTPS;
/**
* @var string cPanel user
*/
private $username;
/**
* @var string cPanel password
*/
private $password;
/**
* @var int cPanel port
*/
private $port;
/**
* @var WebSession Authenticated cPanel session
*/
private $session;
/**
* @var string|null TOTP secret for user authentication
*/
private $otpSecret;
/**
* @var UriBuilder Uri builder used to generate URIs for this application
*/
private $uriBuilder;
/**
* Initialize cPanel API communication.
*
* @param string $host cPanel host
* @param string $username cPanel user
* @param string $password cPanel password
* @param int $port cPanel port
* @param string $protocol Currently used protocol in request URIs
* @param string|null $otpSecret OTP secret for user authentication
*/
public function __construct(
string $host,
string $username,
string $password,
int $port = 2083,
string $protocol = Protocol::HTTPS,
string $otpSecret = null
) {
// Store connection data
$this->host = $host;
$this->username = $username;
$this->password = $password;
$this->port = $port;
$this->protocol = $protocol;
// Store OTP secret
if (is_string($otpSecret) && trim($otpSecret) !== '') {
$this->otpSecret = $otpSecret;
}
// Initialize URI builder
$this->uriBuilder = new UriBuilder($this->buildBaseUri());
// Generate cookie file and log file
$this->generateCookieIdentifier();
$this->generateLogFile();
}
/**
* @inheritDoc
* @throws InactiveSessionException if session identifier does not belong to an active session
* @throws InvalidResponseDataException if response data cannot be parsed as JSON
*/
public function authorize(): ApplicationInterface
{
// Build authentication uri
$path = 'login';
$uriParams = [
'login_only' => 1,
'user' => $this->username,
'pass' => $this->password,
];
if ($this->isTwoFactorAuthenticationEnabled()) {
$uriParams['tfa_token'] = $this->getTOTP();
}
$queryParams = http_build_query($uriParams);
// Send authentication request
$response = $this->request($path . '?' . $queryParams);
// Throw exception if response is no JSON response
if (!($response instanceof JsonResponse)) {
throw new RequestFailedException(
sprintf('Expected JSON response, got "%s" instead.', get_class($response)),
1592850467
);
}
// Throw exception if API response is not valid
if (!$response->isValid(static::SESSION_TOKEN_PARAMETER)) {
throw new AuthenticationFailedException(
'Authentication failed. Please check your login credentials and try again.',
1544739118
);
}
// Store session
$sessionIdentifier = (string)$response->getData()->{static::SESSION_TOKEN_PARAMETER};
$this->session = new WebSession($sessionIdentifier, $this);
return $this;
}
/**
* @inheritDoc
* @see https://documentation.cpanel.net/display/DD/Guide+to+UAPI
*/
public function api(string $module, string $function, array $parameters = []): ResponseInterface
{
if (!$this->hasActiveSession()) {
throw new NotAuthenticatedException(
'Please authenticate first before sending API requests.',
1544738659
);
}
$uri = $this->buildApiRequestPath($module, $function, $parameters);
return $this->request($uri);
}
public function getSession(): ?SessionInterface
{
return $this->session;
}
public function hasActiveSession(): bool
{
return $this->getSession() !== null && $this->session->isActive();
}
public function logout(): ApplicationInterface
{
if (!$this->hasActiveSession()) {
return $this;
}
// Start logout process
$response = $this->request('logout', [], false);
// Throw exception if logout was not successful
if (!$response->isValid()) {
throw new RequestFailedException(
'Error during logout. Make sure your session is still running.',
1593257010
);
}
// Reset session and cookies
$this->session->setActive(false);
$this->removeCookieFile();
return $this;
}
public function request(string $path, array $options = [], bool $json = true): ResponseInterface