requestMethod = strtoupper(string: $requestMethod); $dateFormat = "Y:m:d H:i:s"; $output = "%datetime% %channel%.%level_name% %message%\n"; // %context% %extra% $formatter = new LineFormatter(format: $output, dateFormat: $dateFormat); $stream = new StreamHandler(stream: dirname(path: __DIR__, levels: 2) . '/bindAPI.log'); $stream->setFormatter(formatter: $formatter); $this->log = new Logger(name: 'bindAPI'); $this->log->pushHandler(handler: $stream); $containerBuilder = new ContainerBuilder(); $containerBuilder->addDefinitions([ DatabaseConnection::class => autowire()->constructorParameter(parameter: 'config', value: $this->config), DomainRepository::class => autowire() ->constructorParameter(parameter: 'config', value: $this->config) ->constructorParameter(parameter: 'log', value: $this->log), ]); $this->container = $containerBuilder->build(); $this->domainRepository = $this->container->get(name: DomainRepository::class); $this->apikeyRepository = $this->container->get(name: ApikeyRepository::class); } /** * @return void */ #[OAT\Get( path: '/domains', operationId: 'getAllDomains', description: 'Returns a list of all domains on this server.', summary: 'Listing all domains.', // security: [ // 'Authorization' => [ // // "read:api" // ] // ], servers: [], tags: ['Domains'], responses: [ new OAT\Response( response: 200, description: 'OK' ), new OAT\Response( response: 401, description: 'API key is missing or invalid.' ), new OAT\Response( response: 404, description: 'Domain not found.' )] )] public function handleAllDomainsGetRequest(): void { $domains = $this->domainRepository->findAll(); $resultDomain = []; foreach ($domains as $singleDomain) { $domain = [ 'id' => $singleDomain->getId(), 'name' => $singleDomain->getName(), 'content' => json_decode(json: $singleDomain->getContent()) ]; $resultDomain[] = $domain; } $this->result = $resultDomain; } /** * @OA\Tag(name = "Server") * @OA\Get( * path = "/ping", * summary = "Returning pong.", * description = "Can be used to check API or server availability.", * tags={"Server"}, * @OA\Response(response = "200", description = "OK"), * @OA\Response(response = "401", description = "API key is missing or invalid."), * security={ * {"Authorization":{"read"}} * } * ) * * @OA\Tag(name = "Domains") * @OA\Put( * path="/domains/{name}", * summary="Updates a domain.", * description="Updates a domain. Only supplied fields will be updated, existing won't be affected.", * tags={"Domains"}, * @OA\Response(response="200", description="OK"), * @OA\Response(response = "401", description = "API key is missing or invalid."), * @OA\Response(response="404", description="Domain not found."), * security={ * {"Authorization":{"read":"write"}} * } * ) * @OA\Delete ( * path="/domains/{name}", * summary="Deletes a domain.", * description="Deletes a domain.", * tags={"Domains"}, * @OA\Response(response="200", description="OK"), * @OA\Response(response = "401", description = "API key is missing or invalid."), * @OA\Response(response="404", description="Domain not found."), * security={ * {"Authorization":{"read":"write"}} * } * ) * @return void */ #[OAT\Get( path: '/domains/{name}', operationId: 'getSingleDomain', description: 'Returns information of a single domain specified by its domain name.', summary: 'Returns a single domain.', security: [ ], tags: ['Domains'], parameters: [ new OAT\Parameter(name: 'name', in: 'path', required: true, schema: new OAT\Schema(type: 'string')), ], responses: [ new OAT\Response( response: 200, description: 'OK' ), new OAT\Response( response: 401, description: 'API key is missing or invalid.' ), new OAT\Response( response: 404, description: 'Domain not found.' )] )] public function processRequest() { $command = $this->uri[2]; if (empty($command) || !(($command == 'domains') || ($command == 'ping') || ($command == 'apidoc'))) { $this->header = '404 Not Found'; $this->status = "404 Not Found"; $this->message = "Endpoint not found."; } else { if ($command == 'apidoc') { $openapi = Generator::scan(sources: [__DIR__ . 'RequestController.php']); $this->status = 'openapi'; $this->result[] = $openapi->toJson(); } else { if ($this->checkPassword()) { if ($this->uri[2] == "ping") { $this->header = '200 OK'; $this->status = 'pong'; } else { try { match ($this->requestMethod) { 'GET' => $this->handleDomainGetRequest(), 'POST' => $this->handleDomainPostRequest(), 'PUT' => $this->handleDomainPutRequest(), 'DELETE' => $this->handleDomainDeleteRequest() }; } catch (UnhandledMatchError) { $this->header = '400 Bad Request'; $this->status = '400 Bad Request'; $this->message = "unknown request method: $this->requestMethod"; } } } } if (!empty($this->header)) { header(header: $_SERVER['SERVER_PROTOCOL'] . ' ' . $this->header); } if (!empty($this->result)) { if (!empty($this->status) && $this->status == 'openapi') { header(header: 'Content-Type: application/json'); echo $this->result[0]; } else { echo json_encode(value: $this->result); } } else { if (!empty($this->status) && $this->status == 'pong') { echo json_encode(value: [ 'response' => $this->status ]); } else { echo json_encode(value: [ 'status' => $this->status ?? "Error: No status", 'message' => $this->message ?? "Error: No message." ]); } } } } /** * @return bool */ public function checkPassword(): bool { $headers = array_change_key_case(array: getallheaders(), case: CASE_UPPER); $apiKey = $headers['X-API-KEY'] ?? ''; if (empty($apiKey)) { $this->header = "401 Unauthorized"; $this->status = "401 Unauthorized"; $this->message = "API key is missing."; return false; } else { [$prefix,] = explode(separator: '.', string: $apiKey); if ($apiResult = $this->apikeyRepository->findByPrefix(prefix: $prefix)) { $storedHash = $apiResult->getApiToken(); if (!password_verify(password: $apiKey, hash: $storedHash)) { $this->header = "401 Unauthorized"; $this->status = "401 Unauthorized"; $this->message = "API key mismatch."; return false; } } else { $this->header = "401 Unauthorized"; $this->status = "401 Unauthorized"; $this->message = "Invalid API key."; return false; } } return true; } /** * @return void */ public function handleDomainGetRequest(): void { if ($this->uri[3] == 'name') { if ($result = $this->domainRepository->findByName(name: $this->uri[4])) { $domain = [ 'id' => $result->getId(), 'name' => $result->getName(), 'content' => json_decode(json: $result->getContent()) ]; $this->result = $domain; } else { $this->header = "404 Not Found "; $this->status = "404 Not Found "; $this->message = "The specified domain was not found."; } } else { if (empty($this->uri[3])) { $this->handleAllDomainsGetRequest(); } else { if ($result = $this->domainRepository->findById(id: $this->uri[3])) { $domain = [ 'id' => $result->getId(), 'name' => $result->getName(), 'content' => json_decode(json: $result->getContent()) ]; $this->result = $domain; } else { $this->header = "404 Not Found "; $this->status = "404 Not Found "; $this->message = "The specified domain was not found."; } } } } /** * @return void */ public function handleDomainPostRequest(): void { $name = $_POST['name'] ?? ''; $panelID = intval(value: $_POST['panel_id'] ?? 0); $content = $_POST['content'] ?? ''; if (empty($name)) { $this->header = "400 Bad Request"; $this->status = "400 Bad Request"; $this->message = "A name is required"; } else { if (empty($a) && empty($aaaa) && empty($panelID)) { $this->header = "400 Bad Request"; $this->status = "400 Bad Request"; $this->message = "At least one IP address or panel ID is required."; } else { if ($this->domainRepository->findByName(name: $name)) { $this->header = "400 Bad request"; $this->status = "400 Bad request"; $this->message = "Domain: $name already exists."; } else { $domain = new Domain(name: $name, content: $content); $result = $this->domainRepository->insert(domain: $domain); $this->status = "201 Created"; $this->message = $result; } } } } /** * @return void */ public function handleDomainPutRequest(): void { $putData = fopen(filename: 'php://input', mode: 'r'); $data = fread(stream: $putData, length: 512); $params = explode(separator: '&', string: $data); foreach ($params as $param) { [$key, $value] = explode(separator: '=', string: $param); $put[$key] = $value; } $id = $put['id'] ?? 0; $name = $put['name'] ?? ''; $content = $put['content'] ?? ""; if ($id == 0) { $this->status = "400 Bad Request"; $this->message = "An ID is required"; } else { if (!$this->domainRepository->findByID(id: $id)) { $this->status = "404 Not Found"; $this->message = "Domain with ID : $id doesn't exist."; } else { // TODO not required, as we rely on the ID if (empty($name)) { $this->status = "400 Bad Request"; $this->message = "A name is required"; } else { if (empty($a) && empty($aaaa)) { $this->status = "400 Bad Request"; $this->message = "At least one IP address is required."; } else { $domain = new Domain(name: $name, id: $id, content: $content); $this->domainRepository->update(domain: $domain); $this->header = "201 Updated"; $this->status = "201 Updated"; $this->message = "201 Updated"; } } } } } /** * @return void */ public function handleDomainDeleteRequest(): void { $deleteData = fopen(filename: 'php://input', mode: 'r'); $data = fread(stream: $deleteData, length: 512); $params = explode(separator: '&', string: $data); foreach ($params as $param) { [$key, $value] = explode(separator: '=', string: $param); $delete[$key] = $value; } $id = $delete['id'] ?? 0; if ($id == 0) { $this->header = "400 Bad Request"; $this->status = "400 Bad Request"; $this->message = "You need to supply an ID."; } else { if (!$domain = $this->domainRepository->findByID(id: $id)) { $this->header = "400 Bad Request"; $this->status = "400 Bad Request"; $this->message = "There is no domain with ID $id."; } else { $this->domainRepository->delete(domain: $domain); $this->header = "204 No content."; $this->status = "204 No content."; $this->message = "The domain $id has been deleted."; } } } }