| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- <?php
- declare(strict_types=1);
- namespace Opentalent\OtAdmin\Http;
- use Doctrine\DBAL\Driver\Exception;
- use Opentalent\OtAdmin\Controller\SiteController;
- use Opentalent\OtCore\Exception\InvalidWebsiteConfigurationException;
- use Opentalent\OtCore\Exception\NoSuchOrganizationException;
- use Opentalent\OtCore\Exception\NoSuchRecordException;
- use Opentalent\OtCore\Exception\NoSuchWebsiteException;
- use Psr\Log\LoggerAwareInterface;
- use Psr\Log\LoggerAwareTrait;
- use Psr\Log\LoggerInterface;
- use TYPO3\CMS\Core\Http\JsonResponse;
- use TYPO3\CMS\Core\Http\ServerRequest;
- use TYPO3\CMS\Core\Utility\GeneralUtility;
- /**
- * Actions for Http API calls
- *
- * @package Opentalent\OtAdmin\Http
- */
- class ApiController implements LoggerAwareInterface
- {
- use LoggerAwareTrait;
- const PROD_FRONT_IP = "172.16.0.68";
- const PROD_BACK_IP = "172.16.0.70";
- const PROD_V2_IP = "172.16.0.35";
- const PUBLIC_PRODFRONT_IP = "141.94.117.38";
- const PUBLIC_PROD_BACK_IP = "141.94.117.40";
- const PUBLIC_PROD_V2_IP = "141.94.117.35";
- const array ALLOWED_IPS = [
- '/^127\.0\.0\.[0-1]$/', // Localhost
- '/^localhost$/', // Localhost
- '/^10\.8\.0\.\d{1,3}$/', // 10.8.0.[0-255] - VPN
- '/^141\.94\.117\.((3[3-9])|(4\d)|(5\d)|(6[0-1]))$/', // 141.94.117.[33-61] - Opentalent hosts public ips
- '/^172\.16\.0.\d{1,3}$/', // 172.16.0.[0-255] - Opentalent hosts private ips
- '/^172\.20\.\d{1,3}\.\d{1,3}$/', // 172.20.[0-255].[0-255] - Docker
- ];
- private readonly SiteController $siteController;
- public function __construct() {
- $this->siteController = GeneralUtility::makeInstance(SiteController::class);
- }
- /**
- * Returns true if the client Ip is allowed
- *
- * @param string $clientIp
- * @return bool
- */
- public static function isIpAllowed(string $clientIp): bool
- {
- foreach (self::ALLOWED_IPS as $ipRule) {
- if (preg_match($ipRule, $clientIp)) {
- return true;
- }
- }
- return false;
- }
- /**
- * Check that the client Ip is allowed, else throw a Runtime error
- *
- * @return bool
- */
- private function assertIpAllowed(): bool
- {
- $clientIp = $_SERVER['REMOTE_ADDR'];
- if (!self::isIpAllowed($clientIp)){
- $route = $_REQUEST['route'];
- $this->logger->error(sprintf(
- "OtAdmin API: an attempt was made to call the route " .
- $route . " from an non-allowed IP (" . $clientIp . ")"));
- throw new \RuntimeException("Not allowed");
- }
- return true;
- }
- /**
- * Lève une erreur si l'environnement est la prod et que la requête provient d'un autre environnement, car
- * cette requête a probablement été envoyée à la prod par erreur.
- *
- * Permet de sécuriser certaines opérations destructives, comme la suppression d'organisation.
- *
- * @return void
- */
- private function preventIfIsDubious(): void
- {
- if (
- $_SERVER &&
- (
- (
- $_SERVER['SERVER_ADDR'] === self::PROD_FRONT_IP
- && $_SERVER['REMOTE_ADDR'] !== self::PROD_V2_IP
- && $_SERVER['REMOTE_ADDR'] !== self::PROD_BACK_IP
- ) || (
- $_SERVER['SERVER_ADDR'] === self::PUBLIC_PRODFRONT_IP
- && $_SERVER['REMOTE_ADDR'] !== self::PUBLIC_PROD_V2_IP
- && $_SERVER['REMOTE_ADDR'] !== self::PUBLIC_PROD_BACK_IP
- )
- )
- ) {
- throw new \RuntimeException("Invalid client ip : " . $_SERVER['REMOTE_ADDR']);
- }
- }
- /**
- * Lève une erreur si le token de confirmation n'a pas était ajouté, ou si sa valeur est invalide.
- *
- * Permet de sécuriser certaines opérations destructives, comme la suppression d'organisation.
- *
- * @param int $organizationId
- * @return void
- */
- private function preventOnMissingConfirmationToken(int $organizationId): void
- {
- $headers = getallheaders();
- if (
- !isset($headers['Confirmation-Token']) ||
- $headers['Confirmation-Token'] !== 'DEL-'.$organizationId.'-'.date('Ymd')
- ) {
- throw new \RuntimeException("Missing or invalid confirmation token");
- }
- }
- /**
- * Retrieve the organization's id from the given request parameters
- *
- * @param ServerRequest $request
- * @return int
- */
- private function getOrganizationId(ServerRequest $request): int
- {
- $params = $request->getQueryParams();
- $organizationId = $params['organization-id'];
- if (!$organizationId) {
- throw new \RuntimeException("Missing parameter: 'organization-id'");
- }
- return (int)$organizationId;
- }
- /**
- * -- Target of the route 'site_infos' --
- *
- * Return the main information about the organization's website
- *
- * @param ServerRequest $request
- * @return JsonResponse
- * @throws \Exception
- */
- public function getSiteInfosAction(
- ServerRequest $request,
- SiteController $siteController
- ): JsonResponse
- {
- $this->assertIpAllowed();
- $organizationId = $this->getOrganizationId($request);
- $infos = $siteController->getSiteInfosAction($organizationId);
- return new JsonResponse($infos);
- }
- /**
- * -- Target of the route 'site_create' --
- * >> Requires a query param named 'organization-id' (int)
- *
- * Create the organization's website
- *
- * @param ServerRequest $request
- * @return JsonResponse
- * @throws \Exception
- */
- public function createSiteAction(ServerRequest $request): JsonResponse
- {
- $this->assertIpAllowed();
- $organizationId = $this->getOrganizationId($request);
- $rootUid = $this->siteController->createSiteAction($organizationId);
- $this->logger->info(sprintf(
- "OtAdmin API: A new website has been created with root page uid=" . $rootUid .
- " for the organization " . $organizationId));
- return new JsonResponse(
- [
- 'organization_id' => $organizationId,
- 'msg' => "A new website has been created with root page uid=" . $rootUid,
- 'root_uid' => $rootUid
- ]
- );
- }
- /**
- * -- Target of the route 'site_update' --
- * >> Requires a query param named 'organization-id' (int)
- *
- * Update the settings of the organization's website
- *
- * @param ServerRequest $request
- * @return JsonResponse
- * @throws \Exception
- */
- public function updateSiteConstantsAction(ServerRequest $request): JsonResponse
- {
- $this->assertIpAllowed();
- $organizationId = $this->getOrganizationId($request);
- $deep = (isset($queryParams['deep']) && $queryParams['deep']);
- $rootUid = $this->siteController->updateSiteAction($organizationId, $deep);
- $this->logger->info(sprintf(
- "OtAdmin API: The website with root uid " . $rootUid . " has been updated " .
- " (organization: " . $organizationId . ")"));
- return new JsonResponse(
- [
- 'organization_id' => $organizationId,
- 'msg' => "The website with root uid " . $rootUid . " has been updated",
- 'root_uid' => $rootUid
- ]
- );
- }
- /**
- * -- Target of the route 'redirect_add' --
- * >> Requires query params named 'from-domain' (string) and 'to-domain' (string)
- *
- * Add or update a redirection from 'from-domain' to 'to-domain'
- *
- * @param ServerRequest $request
- * @return JsonResponse
- * @throws \Exception
- */
- public function addRedirectionAction(ServerRequest $request): JsonResponse
- {
- $this->assertIpAllowed();
- $fromDomain = (isset($queryParams['from-domain']) && $queryParams['from-domain']);
- $toDomain = (isset($queryParams['to-domain']) && $queryParams['to-domain']);
- $res = $this->siteController->addRedirection($fromDomain, $toDomain);
- if ($res === SiteController::REDIRECTION_UPDATED) {
- $msg = "An existing redirection has been updated ";
- } elseif ($res === SiteController::REDIRECTION_CREATED) {
- $msg = "A redirection has been added ";
- }
- $this->logger->info(sprintf(
- "OtAdmin API: " . $msg . " from " . $fromDomain . " to " . $toDomain
- ));
- return new JsonResponse(
- [
- 'msg' => $msg . " from " . $fromDomain . " to " . $toDomain,
- ]
- );
- }
- /**
- * -- Target of the route 'site_delete' --
- * >> Requires a query param named 'organization-id' (int)
- *
- * Proceeds to a soft-deletion of the organization's website
- *
- * In the case of a hard deletion, a special header is requested as a confirmation token. The header
- * shall be named 'Confirmation-Token' and its value shall be DEL-XXXX-YYYYMMDD, where XXXX is the id of
- * the organization owning the website, and YYYYMMDD is the date of the current day.
- *
- * /!\ Warning: this is a destructive operation
- *
- * @param ServerRequest $request
- * @return JsonResponse
- * @throws \Exception
- */
- public function deleteSiteAction(ServerRequest $request): JsonResponse
- {
- $this->assertIpAllowed();
- $organizationId = $this->getOrganizationId($request);
- $params = $request->getQueryParams();
- $hard = (isset($params['hard']) && $params['hard']);
- if ($hard) {
- $this->preventIfIsDubious();
- $this->preventOnMissingConfirmationToken($organizationId);
- }
- $rootUid = $this->siteController->deleteSiteAction($organizationId, $hard, true, true);
- $this->logger->info(sprintf(
- "OtAdmin API: The website with root uid " . $rootUid . " has been soft-deleted " .
- " (organization: " . $organizationId . ")"));
- $msg = $hard ?
- "The website with root uid " . $rootUid . " has been hard-deleted." :
- "The website with root uid " . $rootUid . " has been soft-deleted. Use the /site/undelete route to restore it.";
- return new JsonResponse(
- [
- 'organization_id' => $organizationId,
- 'msg' => $msg,
- 'root_uid' => $rootUid
- ]
- );
- }
- /**
- * -- Target of the route 'site_undelete' --
- * >> Requires a query param named 'organization-id' (int)
- *
- * Restore a soft-deleted organization's website
- *
- * @param ServerRequest $request
- * @return JsonResponse
- * @throws \Exception
- */
- public function undeleteSiteAction(ServerRequest $request): JsonResponse
- {
- $this->assertIpAllowed();
- $organizationId = $this->getOrganizationId($request);
- $rootUid = $this->siteController->undeleteSiteAction($organizationId);
- $this->logger->info(sprintf(
- "OtAdmin API: The website with root uid " . $rootUid . " has been restored " .
- " (organization: " . $organizationId . ")"));
- return new JsonResponse(
- [
- 'organization_id' => $organizationId,
- 'msg' => "The website with root uid " . $rootUid . " has been restored",
- 'root_uid' => $rootUid
- ]
- );
- }
- /**
- * -- Target of the route 'site_clearcache' --
- * >> Requires a query param named 'organization-id' (int)
- *
- * Clear the cache of the organization's website
- *
- * @param ServerRequest $request
- * @return JsonResponse
- * @throws \Exception
- */
- public function clearSiteCacheAction(ServerRequest $request): JsonResponse
- {
- $this->assertIpAllowed();
- $organizationId = $this->getOrganizationId($request);
- $queryParams = $request->getQueryParams();
- $clearAll = (isset($queryParams['all']) && $queryParams['all']);;
- $rootUid = $this->siteController->clearSiteCacheAction($organizationId, $clearAll);
- return new JsonResponse(
- [
- 'organization_id' => $organizationId,
- 'msg' => "The cache has been cleared for the website with root uid " . $rootUid . "",
- 'root_uid' => $rootUid
- ]
- );
- }
- /**
- * -- Target of the route 'site_setdomain' --
- * >> Requires a query param named 'organization-id' (int)
- * and a parameter named 'domain' (string)
- *
- * Set a new domain for the organization website
- *
- * @param ServerRequest $request
- * @return JsonResponse
- * @throws \Exception
- */
- public function setSiteCustomDomainAction(ServerRequest $request): JsonResponse
- {
- $this->assertIpAllowed();
- $organizationId = $this->getOrganizationId($request);
- $queryParams = $request->getQueryParams();
- $domain = $queryParams['domain'];
- if (!$domain) {
- throw new \RuntimeException("Missing 'domain' parameter");
- }
- $redirect = (isset($queryParams['redirect']) && $queryParams['redirect']);
- $rootUid = $this->siteController->setSiteCustomDomainAction($organizationId, $domain, $redirect);
- return new JsonResponse(
- [
- 'organization_id' => $organizationId,
- 'msg' => "The cache has been cleared for the website with root uid " . $rootUid . "",
- 'root_uid' => $rootUid
- ]
- );
- }
- /**
- * -- Target of the route 'site_resetperms' --
- * >> Requires a query param named 'organization-id' (int)
- *
- * Reset the permissions of the website be users (admin, editors...)
- *
- * @param ServerRequest $request
- * @return JsonResponse
- * @throws \Exception
- */
- public function resetBeUserPermsAction(ServerRequest $request): JsonResponse
- {
- $this->assertIpAllowed();
- $organizationId = $this->getOrganizationId($request);
- $rootUid = $this->siteController->resetBeUserPermsAction($organizationId);
- return new JsonResponse(
- [
- 'organization_id' => $organizationId,
- 'msg' => "The website with root uid " . $rootUid . " had its be users permissions reset",
- 'root_uid' => $rootUid
- ]
- );
- }
- /**
- * -- Target of the route 'site_status' --
- * >> Requires a query param named 'organization-id' (int)
- *
- * Returns the current status of the website
- *
- * @param ServerRequest $request
- * @param SiteController $siteController
- * @return JsonResponse
- * @throws Exception
- * @throws InvalidWebsiteConfigurationException
- * @throws NoSuchOrganizationException
- * @throws NoSuchRecordException
- * @throws NoSuchWebsiteException
- */
- public function getSiteStatusAction(
- ServerRequest $request
- ): JsonResponse
- {
- $this->assertIpAllowed();
- $organizationId = $this->getOrganizationId($request);
- $queryParams = $request->getQueryParams();
- $full = (isset($queryParams['full']) && $queryParams['full']);
- $status = $this->siteController->getSiteStatusAction($organizationId, $full);
- return new JsonResponse($status->toArray());
- }
- /**
- * -- Target of the route 'scan' --
- *
- * Scan the whole Typo3 database and return the results
- *
- * @param ServerRequest $request
- * @return JsonResponse
- * @throws \Exception
- */
- public function scanAllAction(ServerRequest $request): JsonResponse
- {
- $this->assertIpAllowed();
- $queryParams = $request->getQueryParams();
- $full = (isset($queryParams['full']) && $queryParams['full']);
- $results = $this->siteController->scanAllAction($full);
- return new JsonResponse($results);
- }
- /**
- * -- Target of the route 'delete-user-created-pages' --
- * >> Requires a query param named 'organization-id' (int)
- *
- * Delete all user-created pages for the organization's website
- *
- * /!\ Warning: this is a destructive operation
- *
- * @param ServerRequest $request
- * @return JsonResponse
- * @throws \Exception
- */
- public function deleteUserCreatedPagesAction(ServerRequest $request): JsonResponse
- {
- $this->assertIpAllowed();
- $organizationId = $this->getOrganizationId($request);
- $this->preventIfIsDubious();
- $this->preventOnMissingConfirmationToken($organizationId);
- $rootUid = $this->siteController->deleteUserCreatedPagesAction($organizationId);
- return new JsonResponse(
- [
- 'organization_id' => $organizationId,
- 'msg' => "The website with root uid " . $rootUid . " had its user-created pages deleted.",
- 'root_uid' => $rootUid
- ]
- );
- }
- }
|