ApiController.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. <?php
  2. declare(strict_types=1);
  3. namespace Opentalent\OtAdmin\Http;
  4. use Doctrine\DBAL\Driver\Exception;
  5. use Opentalent\OtAdmin\Controller\SiteController;
  6. use Opentalent\OtCore\Exception\InvalidWebsiteConfigurationException;
  7. use Opentalent\OtCore\Exception\NoSuchOrganizationException;
  8. use Opentalent\OtCore\Exception\NoSuchRecordException;
  9. use Opentalent\OtCore\Exception\NoSuchWebsiteException;
  10. use Psr\Log\LoggerAwareInterface;
  11. use Psr\Log\LoggerAwareTrait;
  12. use TYPO3\CMS\Core\Http\JsonResponse;
  13. use TYPO3\CMS\Core\Http\ServerRequest;
  14. use TYPO3\CMS\Core\Utility\GeneralUtility;
  15. /**
  16. * Actions for Http API calls
  17. *
  18. * @package Opentalent\OtAdmin\Http
  19. */
  20. class ApiController implements LoggerAwareInterface
  21. {
  22. use LoggerAwareTrait;
  23. const array ALLOWED_IPS = [
  24. '/^127\.0\.0\.[0-1]$/', // Localhost
  25. '/^localhost$/', // Localhost
  26. '/^10\.8\.0\.\d{1,3}$/', // 10.8.0.[0-255] - VPN
  27. '/^141\.94\.117\.((3[3-9])|(4\d)|(5\d)|(6[0-1]))$/', // 141.94.117.[33-61] - Opentalent hosts public ips
  28. '/^172\.16\.0.\d{1,3}$/', // 172.16.0.[0-255] - Opentalent hosts private ips
  29. '/^172\.20\.\d{1,3}\.\d{1,3}$/', // 172.20.[0-255].[0-255] - Docker
  30. ];
  31. private readonly SiteController $siteController;
  32. public function __construct() {
  33. $this->siteController = GeneralUtility::makeInstance(SiteController::class);
  34. }
  35. /**
  36. * Returns true if the client Ip is allowed
  37. *
  38. * @param string $clientIp
  39. * @return bool
  40. */
  41. public static function isIpAllowed(string $clientIp): bool
  42. {
  43. foreach (self::ALLOWED_IPS as $ipRule) {
  44. if (preg_match($ipRule, $clientIp)) {
  45. return true;
  46. }
  47. }
  48. return false;
  49. }
  50. /**
  51. * Check that the client Ip is allowed, else throw a Runtime error
  52. *
  53. * @return bool
  54. */
  55. private function assertIpAllowed(): bool
  56. {
  57. $clientIp = $_SERVER['REMOTE_ADDR'];
  58. if (!self::isIpAllowed($clientIp)){
  59. $route = $_REQUEST['route'];
  60. $this->logger->error(sprintf(
  61. "OtAdmin API: an attempt was made to call the route " .
  62. $route . " from an non-allowed IP (" . $clientIp . ")"));
  63. throw new \RuntimeException("Not allowed");
  64. }
  65. return true;
  66. }
  67. /**
  68. * Retrieve the organization's id from the given request parameters
  69. *
  70. * @param ServerRequest $request
  71. * @return int
  72. */
  73. private function getOrganizationId(ServerRequest $request): int
  74. {
  75. $params = $request->getQueryParams();
  76. $organizationId = $params['organization-id'];
  77. if (!$organizationId) {
  78. throw new \RuntimeException("Missing parameter: 'organization-id'");
  79. }
  80. return (int)$organizationId;
  81. }
  82. /**
  83. * -- Target of the route 'site_infos' --
  84. *
  85. * Return the main information about the organization's website
  86. *
  87. * @param ServerRequest $request
  88. * @return JsonResponse
  89. * @throws \Exception
  90. */
  91. public function getSiteInfosAction(
  92. ServerRequest $request,
  93. SiteController $siteController
  94. ): JsonResponse
  95. {
  96. $this->assertIpAllowed();
  97. $organizationId = $this->getOrganizationId($request);
  98. $infos = $siteController->getSiteInfosAction($organizationId);
  99. return new JsonResponse($infos);
  100. }
  101. /**
  102. * -- Target of the route 'site_create' --
  103. * >> Requires a query param named 'organization-id' (int)
  104. *
  105. * Create the organization's website
  106. *
  107. * @param ServerRequest $request
  108. * @return JsonResponse
  109. * @throws \Exception
  110. */
  111. public function createSiteAction(ServerRequest $request): JsonResponse
  112. {
  113. $this->assertIpAllowed();
  114. $organizationId = $this->getOrganizationId($request);
  115. $rootUid = $this->siteController->createSiteAction($organizationId);
  116. $this->logger->info(sprintf(
  117. "OtAdmin API: A new website has been created with root page uid=" . $rootUid .
  118. " for the organization " . $organizationId));
  119. return new JsonResponse(
  120. [
  121. 'organization_id' => $organizationId,
  122. 'msg' => "A new website has been created with root page uid=" . $rootUid,
  123. 'root_uid' => $rootUid
  124. ]
  125. );
  126. }
  127. /**
  128. * -- Target of the route 'site_update' --
  129. * >> Requires a query param named 'organization-id' (int)
  130. *
  131. * Update the settings of the organization's website
  132. *
  133. * @param ServerRequest $request
  134. * @return JsonResponse
  135. * @throws \Exception
  136. */
  137. public function updateSiteConstantsAction(ServerRequest $request): JsonResponse
  138. {
  139. $this->assertIpAllowed();
  140. $organizationId = $this->getOrganizationId($request);
  141. $deep = (isset($queryParams['deep']) && $queryParams['deep']);
  142. $rootUid = $this->siteController->updateSiteAction($organizationId, $deep);
  143. $this->logger->info(sprintf(
  144. "OtAdmin API: The website with root uid " . $rootUid . " has been updated " .
  145. " (organization: " . $organizationId . ")"));
  146. return new JsonResponse(
  147. [
  148. 'organization_id' => $organizationId,
  149. 'msg' => "The website with root uid " . $rootUid . " has been updated",
  150. 'root_uid' => $rootUid
  151. ]
  152. );
  153. }
  154. /**
  155. * -- Target of the route 'redirect_add' --
  156. * >> Requires query params named 'from-domain' (string) and 'to-domain' (string)
  157. *
  158. * Add or update a redirection from 'from-domain' to 'to-domain'
  159. *
  160. * @param ServerRequest $request
  161. * @return JsonResponse
  162. * @throws \Exception
  163. */
  164. public function addRedirectionAction(ServerRequest $request): JsonResponse
  165. {
  166. $this->assertIpAllowed();
  167. $fromDomain = (isset($queryParams['from-domain']) && $queryParams['from-domain']);
  168. $toDomain = (isset($queryParams['to-domain']) && $queryParams['to-domain']);
  169. $res = $this->siteController->addRedirection($fromDomain, $toDomain);
  170. if ($res === SiteController::REDIRECTION_UPDATED) {
  171. $msg = "An existing redirection has been updated ";
  172. } elseif ($res === SiteController::REDIRECTION_CREATED) {
  173. $msg = "A redirection has been added ";
  174. }
  175. $this->logger->info(sprintf(
  176. "OtAdmin API: " . $msg . " from " . $fromDomain . " to " . $toDomain
  177. ));
  178. return new JsonResponse(
  179. [
  180. 'msg' => $msg . " from " . $fromDomain . " to " . $toDomain,
  181. ]
  182. );
  183. }
  184. /**
  185. * -- Target of the route 'site_delete' --
  186. * >> Requires a query param named 'organization-id' (int)
  187. *
  188. * Proceeds to a soft-deletion of the organization's website
  189. *
  190. * @param ServerRequest $request
  191. * @return JsonResponse
  192. * @throws \Exception
  193. */
  194. public function deleteSiteAction(ServerRequest $request): JsonResponse
  195. {
  196. $this->assertIpAllowed();
  197. $organizationId = $this->getOrganizationId($request);
  198. $params = $request->getQueryParams();
  199. $rootUid = $this->siteController->deleteSiteAction($organizationId);
  200. $this->logger->info(sprintf(
  201. "OtAdmin API: The website with root uid " . $rootUid . " has been soft-deleted " .
  202. " (organization: " . $organizationId . ")"));
  203. return new JsonResponse(
  204. [
  205. 'organization_id' => $organizationId,
  206. 'msg' => "The website with root uid " . $rootUid . " has been soft-deleted. Use the /site/undelete route to restore it.",
  207. 'root_uid' => $rootUid
  208. ]
  209. );
  210. }
  211. /**
  212. * -- Target of the route 'site_undelete' --
  213. * >> Requires a query param named 'organization-id' (int)
  214. *
  215. * Restore a soft-deleted organization's website
  216. *
  217. * @param ServerRequest $request
  218. * @return JsonResponse
  219. * @throws \Exception
  220. */
  221. public function undeleteSiteAction(ServerRequest $request): JsonResponse
  222. {
  223. $this->assertIpAllowed();
  224. $organizationId = $this->getOrganizationId($request);
  225. $rootUid = $this->siteController->undeleteSiteAction($organizationId);
  226. $this->logger->info(sprintf(
  227. "OtAdmin API: The website with root uid " . $rootUid . " has been restored " .
  228. " (organization: " . $organizationId . ")"));
  229. return new JsonResponse(
  230. [
  231. 'organization_id' => $organizationId,
  232. 'msg' => "The website with root uid " . $rootUid . " has been restored",
  233. 'root_uid' => $rootUid
  234. ]
  235. );
  236. }
  237. /**
  238. * -- Target of the route 'site_clearcache' --
  239. * >> Requires a query param named 'organization-id' (int)
  240. *
  241. * Clear the cache of the organization's website
  242. *
  243. * @param ServerRequest $request
  244. * @return JsonResponse
  245. * @throws \Exception
  246. */
  247. public function clearSiteCacheAction(ServerRequest $request): JsonResponse
  248. {
  249. $this->assertIpAllowed();
  250. $organizationId = $this->getOrganizationId($request);
  251. $queryParams = $request->getQueryParams();
  252. $clearAll = (isset($queryParams['all']) && $queryParams['all']);;
  253. $rootUid = $this->siteController->clearSiteCacheAction($organizationId, $clearAll);
  254. return new JsonResponse(
  255. [
  256. 'organization_id' => $organizationId,
  257. 'msg' => "The cache has been cleared for the website with root uid " . $rootUid . "",
  258. 'root_uid' => $rootUid
  259. ]
  260. );
  261. }
  262. /**
  263. * -- Target of the route 'site_setdomain' --
  264. * >> Requires a query param named 'organization-id' (int)
  265. * and a parameter named 'domain' (string)
  266. *
  267. * Set a new domain for the organization website
  268. *
  269. * @param ServerRequest $request
  270. * @return JsonResponse
  271. * @throws \Exception
  272. */
  273. public function setSiteCustomDomainAction(ServerRequest $request): JsonResponse
  274. {
  275. $this->assertIpAllowed();
  276. $organizationId = $this->getOrganizationId($request);
  277. $queryParams = $request->getQueryParams();
  278. $domain = $queryParams['domain'];
  279. if (!$domain) {
  280. throw new \RuntimeException("Missing 'domain' parameter");
  281. }
  282. $redirect = (isset($queryParams['redirect']) && $queryParams['redirect']);
  283. $rootUid = $this->siteController->setSiteCustomDomainAction($organizationId, $domain, $redirect);
  284. return new JsonResponse(
  285. [
  286. 'organization_id' => $organizationId,
  287. 'msg' => "The cache has been cleared for the website with root uid " . $rootUid . "",
  288. 'root_uid' => $rootUid
  289. ]
  290. );
  291. }
  292. /**
  293. * -- Target of the route 'site_resetperms' --
  294. * >> Requires a query param named 'organization-id' (int)
  295. *
  296. * Reset the permissions of the website be users (admin, editors...)
  297. *
  298. * @param ServerRequest $request
  299. * @return JsonResponse
  300. * @throws \Exception
  301. */
  302. public function resetBeUserPermsAction(ServerRequest $request): JsonResponse
  303. {
  304. $this->assertIpAllowed();
  305. $organizationId = $this->getOrganizationId($request);
  306. $rootUid = $this->siteController->resetBeUserPermsAction($organizationId);
  307. return new JsonResponse(
  308. [
  309. 'organization_id' => $organizationId,
  310. 'msg' => "The website with root uid " . $rootUid . " had its be users permissions reset",
  311. 'root_uid' => $rootUid
  312. ]
  313. );
  314. }
  315. /**
  316. * -- Target of the route 'site_status' --
  317. * >> Requires a query param named 'organization-id' (int)
  318. *
  319. * Returns the current status of the website
  320. *
  321. * @param ServerRequest $request
  322. * @param SiteController $siteController
  323. * @return JsonResponse
  324. * @throws Exception
  325. * @throws InvalidWebsiteConfigurationException
  326. * @throws NoSuchOrganizationException
  327. * @throws NoSuchRecordException
  328. * @throws NoSuchWebsiteException
  329. */
  330. public function getSiteStatusAction(
  331. ServerRequest $request
  332. ): JsonResponse
  333. {
  334. $this->assertIpAllowed();
  335. $organizationId = $this->getOrganizationId($request);
  336. $queryParams = $request->getQueryParams();
  337. $full = (isset($queryParams['full']) && $queryParams['full']);
  338. $status = $this->siteController->getSiteStatusAction($organizationId, $full);
  339. return new JsonResponse($status->toArray());
  340. }
  341. /**
  342. * -- Target of the route 'scan' --
  343. *
  344. * Scan the whole Typo3 database and return the results
  345. *
  346. * @param ServerRequest $request
  347. * @return JsonResponse
  348. * @throws \Exception
  349. */
  350. public function scanAllAction(ServerRequest $request): JsonResponse
  351. {
  352. $this->assertIpAllowed();
  353. $queryParams = $request->getQueryParams();
  354. $full = (isset($queryParams['full']) && $queryParams['full']);
  355. $results = $this->siteController->scanAllAction($full);
  356. return new JsonResponse($results);
  357. }
  358. }