From 43e163576069172479679cc05625bf4e70631d06 Mon Sep 17 00:00:00 2001 From: Dominik Bohuncak Date: Mon, 26 Jun 2023 07:45:17 +0200 Subject: [PATCH] fix configuration attlasian + fix cross headers --- application/public/index.php | 76 ++++++++++++----- application/src/Handler/AbstractHandler.php | 72 ++++++++++------ .../src/Handler/GetIssueSearchHandler.php | 8 -- application/src/Handler/IssueHandler.php | 82 +++++++++++++++++++ application/src/Handler/SendIdeaHandler.php | 30 +------ .../src/Handler/SendProblemHandler.php | 32 +------- application/src/Handler/TestAuthHandler.php | 26 +++--- application/src/Handler/TestFormHandler.php | 46 +++++++++++ application/src/Model/AtlassianIdeaModel.php | 4 +- .../src/Model/AtlassianProblemModel.php | 2 + .../src/Service/AtlassianApiService.php | 28 +++++++ application/src/Traits/RecaptchaTrait.php | 28 +++++++ 12 files changed, 308 insertions(+), 126 deletions(-) create mode 100644 application/src/Handler/IssueHandler.php create mode 100644 application/src/Handler/TestFormHandler.php create mode 100644 application/src/Traits/RecaptchaTrait.php diff --git a/application/public/index.php b/application/public/index.php index 0d172ce..8cfb1a9 100644 --- a/application/public/index.php +++ b/application/public/index.php @@ -13,6 +13,10 @@ $app = AppFactory::create(); + +// Add Error Handling Middleware +//$app->addErrorMiddleware(true, true, false); + function procesJsonRequest($request): Request { $contentType = $request->getHeaderLine('Content-Type'); @@ -26,21 +30,43 @@ function procesJsonRequest($request): Request return $request; } -function callAtlassian(HandlerInterface $handler, Request $request, Response $response): Response -{ - $headers = [ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'Access-Control-Allow-Origin' => '*', - ]; +function callAtlassian( + HandlerInterface $handler, + Request $request, + Response $response, + bool $isOption = false, + string $accept = 'application/json', + string $contentType = 'application/json' +): Response { + $headers = []; + $headers["Access-Control-Allow-Origin"] = "*"; + $headers['Access-Control-Allow-Methods'] = "*"; + $headers['Access-Control-Allow-Headers'] = 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Recaptcha-Token,Recaptcha-Action'; + $headers['Access-Control-Allow-Credentials'] = "true"; + $headers['Access-Control-Max-Age'] = 86400; + $headers['Content-Type'] = $contentType; + $headers['Accept'] = $accept; + + if ($isOption) { + return new \GuzzleHttp\Psr7\Response(200, $headers, json_encode([])); + } + try { - $request = procesJsonRequest($request); - return $handler->handle($request); + $response = $handler->handle($request); + return new \GuzzleHttp\Psr7\Response( + $response->getStatusCode(), + $headers, + $response->getBody() + ); } catch (\Exception $e) { - $response = $response->withStatus(500); - $response->getBody()->write($e->getMessage()); + return new \GuzzleHttp\Psr7\Response( + 500, + $headers, + json_encode([ + 'error' => $e->getMessage() + ]) + ); } - return $response; } $app->get('/test/ping', function (Request $request, Response $response, $args) { @@ -49,30 +75,40 @@ function callAtlassian(HandlerInterface $handler, Request $request, Response $re return $response; }); -$app->get('/test/auth', function (Request $request, Response $response, $args) { +$app->post('/test/auth', function (Request $request, Response $response, $args) { $handler = new TestAuthHandler(); - return callAtlassian($handler, $request, $response); + return $handler->handle($request); +}); + +$app->options('/test/auth', function (Request $request, Response $response, $args) { + $handler = new TestAuthHandler(); + return $handler->handle($request); }); $app->get('/issue/search', function (Request $request, Response $response, $args) { $handler = new GetIssueSearchHandler(); + $request = procesJsonRequest($request); return callAtlassian($handler, $request, $response); }); $app->post('/issue/idea', function (Request $request, Response $response, $args) { $handler = new SendIdeaHandler(); - return callAtlassian($handler, $request, $response); + return callAtlassian($handler, $request, $response, false, 'multipart/form-data'); +}); + +$app->options('/issue/idea', function (Request $request, Response $response, $args) { + $handler = new SendIdeaHandler(); + return callAtlassian($handler, $request, $response, true, 'multipart/form-data'); }); $app->post('/issue/problem', function (Request $request, Response $response, $args) { $handler = new SendProblemHandler(); - return callAtlassian($handler, $request, $response); + return callAtlassian($handler, $request, $response, false, 'multipart/form-data'); }); -$app->get('/issue', function (Request $request, Response $response, $args) { - $request = procesJsonRequest($request); - $response->getBody()->write("Hello world!"); - return $response; +$app->options('/issue/problem', function (Request $request, Response $response, $args) { + $handler = new SendProblemHandler(); + return callAtlassian($handler, $request, $response, true, 'multipart/form-data'); }); $app->run(); \ No newline at end of file diff --git a/application/src/Handler/AbstractHandler.php b/application/src/Handler/AbstractHandler.php index 1cfb092..f7a7ded 100644 --- a/application/src/Handler/AbstractHandler.php +++ b/application/src/Handler/AbstractHandler.php @@ -4,43 +4,61 @@ namespace App\Handler; +use App\Service\AtlassianApiService; +use App\Traits\RecaptchaTrait; use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\UploadedFile; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; class AbstractHandler implements HandlerInterface { - protected function validRecaptcha(?string $token, ?string $action) { - $secretKey = getenv('recaptcha_secret'); - - if (isset($_POST['email']) && $_POST['email']) { - $email = filter_var($_POST['email'], FILTER_SANITIZE_STRING); - } else { - // set error message and redirect back to form... - header('location: subscribe_newsletter_form.php'); - exit; - } + use RecaptchaTrait; + protected AtlassianApiService $atlassianApi; - // call curl to POST request - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL,"https://www.google.com/recaptcha/api/siteverify"); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array('secret' => $secretKey, 'response' => $token))); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - $response = curl_exec($ch); - curl_close($ch); - $arrResponse = json_decode($response, true); - - // verify the response - if($arrResponse["success"] == '1' && $arrResponse["action"] == $action && $arrResponse["score"] >= 0.5) { - return true; - } else { - return false; - } + public function __construct() + { + $this->atlassianApi = new AtlassianApiService(); } public function handle(ServerRequestInterface $request): ResponseInterface { return new Response(); } -} \ No newline at end of file + + protected function getApiResponseHeaders(): array + { + $headers = []; + + $headers["Access-Control-Allow-Origin"] = "*"; + $headers['Access-Control-Allow-Methods'] = "*"; + $headers['Access-Control-Allow-Headers'] = 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Recaptcha-Token,Recaptcha-Action'; + $headers['Access-Control-Allow-Credentials'] = "true"; + $headers['Access-Control-Max-Age'] = 86400; + $headers['Content-Type'] = 'application/json'; + $headers['Accept'] = 'application/json'; + return $headers; + } + + public function getFilesFromRequest(ServerRequestInterface $request): array + { + $uploadedFiles = $request->getUploadedFiles(); + $files = []; + + /** + * @var UploadedFile $image + */ + foreach ($uploadedFiles as $image) { + + if ($image->getError() === UPLOAD_ERR_OK) { + $file = [ + "name" => 'file', + "contents" => (string)$image->getStream(), + 'filename' => $image->getClientFilename(), + ]; + $files[] = $file; + } + } + return $files; + } +} diff --git a/application/src/Handler/GetIssueSearchHandler.php b/application/src/Handler/GetIssueSearchHandler.php index f786977..666a14c 100644 --- a/application/src/Handler/GetIssueSearchHandler.php +++ b/application/src/Handler/GetIssueSearchHandler.php @@ -4,20 +4,12 @@ namespace App\Handler; -use App\Service\AtlassianApiService; use GuzzleHttp\Exception\GuzzleException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; class GetIssueSearchHandler extends AbstractHandler { - private AtlassianApiService $atlassianApi; - - public function __construct() - { - $this->atlassianApi = new AtlassianApiService(); - } - /** * @throws GuzzleException */ diff --git a/application/src/Handler/IssueHandler.php b/application/src/Handler/IssueHandler.php new file mode 100644 index 0000000..0ee18fa --- /dev/null +++ b/application/src/Handler/IssueHandler.php @@ -0,0 +1,82 @@ +getUploadedFiles(); + $files = []; + + /** + * @var UploadedFile $image + */ + foreach ($uploadedFiles as $image) { + + if ($image->getError() === UPLOAD_ERR_OK) { + $file = [ + "name" => 'file', + "contents" => (string)$image->getStream(), + 'filename' => $image->getClientFilename(), + ]; + $files[] = $file; + } + } + return $files; + } + + public function addIssueAttachments(array $files, string $issueId): ?ResponseInterface + { + if (empty($files)) return null; + /* @var AtlassianApiService $attlasianApi */ + $tempResponse = $this->atlassianApi->CreateAttachment($files); + $tempResponseData = json_decode($tempResponse->getBody()->getContents(), true); + $attachmentIds = []; + foreach ($tempResponseData['temporaryAttachments'] ?? [] as $attachment) { + $attachmentIds[] = $attachment['temporaryAttachmentId']; + } + return $this->atlassianApi->addIssueAttachments($issueId, $attachmentIds); + } + + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $data = $request->getParsedBody(); + $token = $request->getHeader('Recaptcha-Token')[0] ?? null; + $action = $request->getHeader('Recaptcha-Action')[0] ?? null; + + try { + if ($this->validRecaptcha($token, $action)) { + $issueData = new $this->issueClassName(); + $issueData->fromArray($data); + $issueResponse = $this->atlassianApi->sendIssue($issueData); + + $files = $this->getFilesFromRequest($request); + if ($issueResponse->getStatusCode() === 201 && !empty($files)) { + $issue = json_decode($issueResponse->getBody()->getContents(), true); + $this->addIssueAttachments($files, $issue['issueId']); + } + + return $issueResponse; + } + } catch (Exception | GuzzleException $e) { + return new Response(401, [], json_encode([$e->getMessage()])); + } + + return new Response(401, [], json_encode(['error' => 'DATA_INVALID'])); + } +} diff --git a/application/src/Handler/SendIdeaHandler.php b/application/src/Handler/SendIdeaHandler.php index 1c108cb..3eb0595 100644 --- a/application/src/Handler/SendIdeaHandler.php +++ b/application/src/Handler/SendIdeaHandler.php @@ -5,34 +5,8 @@ namespace App\Handler; use App\Model\AtlassianIdeaModel; -use App\Service\AtlassianApiService; -use Exception; -use GuzzleHttp\Exception\GuzzleException; -use GuzzleHttp\Psr7\Response; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -final class SendIdeaHandler extends AbstractHandler +final class SendIdeaHandler extends IssueHandler { - private AtlassianApiService $atlassianApi; - - public function __construct() - { - $this->atlassianApi = new AtlassianApiService(); - } - - /** - * @throws GuzzleException - * @throws Exception - */ - public function handle(ServerRequestInterface $request): ResponseInterface - { - $data = $request->getParsedBody(); - if ($this->validRecaptcha($data['token'] ?? null, $data['action'] ?? null)) { - $issueData = new AtlassianIdeaModel(); - $issueData->fromArray($data); - return $this->atlassianApi->sendIssue($issueData); - } - return new Response(401, [], 'DATA_INVALID'); - } + private string $issueClassName = AtlassianIdeaModel::class; } diff --git a/application/src/Handler/SendProblemHandler.php b/application/src/Handler/SendProblemHandler.php index 38a6e31..0791dd9 100644 --- a/application/src/Handler/SendProblemHandler.php +++ b/application/src/Handler/SendProblemHandler.php @@ -5,34 +5,8 @@ namespace App\Handler; use App\Model\AtlassianProblemModel; -use App\Service\AtlassianApiService; -use Exception; -use GuzzleHttp\Exception\GuzzleException; -use GuzzleHttp\Psr7\Response; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -final class SendProblemHandler extends AbstractHandler +final class SendProblemHandler extends IssueHandler { - private AtlassianApiService $atlassianApi; - - public function __construct() - { - $this->atlassianApi = new AtlassianApiService(); - } - - /** - * @throws GuzzleException - * @throws Exception - */ - public function handle(ServerRequestInterface $request): ResponseInterface - { - $data = $request->getParsedBody(); - if ($this->validRecaptcha($data['token'] ?? null, $data['action'] ?? null)) { - $problemData = new AtlassianProblemModel(); - $problemData->fromArray($data); - return $this->atlassianApi->sendIssue($problemData); - } - return new Response(401, [], 'DATA_INVALID'); - } -} + private string $issueClassName = AtlassianProblemModel::class; +} \ No newline at end of file diff --git a/application/src/Handler/TestAuthHandler.php b/application/src/Handler/TestAuthHandler.php index c508b86..5d565b6 100644 --- a/application/src/Handler/TestAuthHandler.php +++ b/application/src/Handler/TestAuthHandler.php @@ -4,27 +4,29 @@ namespace App\Handler; -use App\Service\AtlassianApiService; use GuzzleHttp\Psr7\Response; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; class TestAuthHandler extends AbstractHandler { - - private AtlassianApiService $atlassianApi; - - public function __construct() - { - $this->atlassianApi = new AtlassianApiService(); - } public function handle(ServerRequestInterface $request): ResponseInterface { - $data = $request->getParsedBody(); - if ($this->validRecaptcha($data['token'] ?? null, $data['action'] ?? null)) { - return new Response(200, [], 'DATA_VALID'); + $headers = $this->getApiResponseHeaders(); + $headers['Accept'] = 'multipart/form-data'; + + $token = $request->getHeader('recaptcha-token')[0] ?? null; + $action = $request->getHeader('recaptcha-action')[0] ?? null; + + try { + if ($this->validRecaptcha($token, $action)) { + return new Response(200, $headers, json_encode([$token,$action,'OK'])); + } + } catch (\Exception $e) { + return new Response(200, $headers, json_encode([$token,$action,$e->getMessage()])); } - return new Response(401, [], 'DATA_INVALID'); + + return new Response(200, $headers, json_encode([$token,$action,'NOK'])); } } \ No newline at end of file diff --git a/application/src/Handler/TestFormHandler.php b/application/src/Handler/TestFormHandler.php new file mode 100644 index 0000000..ddac9e6 --- /dev/null +++ b/application/src/Handler/TestFormHandler.php @@ -0,0 +1,46 @@ +getApiResponseHeaders(); + $headers['Accept'] = 'multipart/form-data'; + + $data = $request->getParsedBody(); + return new Response( 200, $headers, json_encode($request->getHeaders())); + $files = $this->getFilesFromRequest($request); + $attachment = $this->addIssueAttachments($files, '10732'); + + $body = $attachment->getbody()->getContents(); + $out = new \GuzzleHttp\Psr7\Response( + $attachment->getStatusCode(), + $headers, + $body + ); + + return new Response( $attachment->getStatusCode(), $headers, $body); + } + + public function addIssueAttachments(array $files, string $issueId): ?ResponseInterface + { + if (empty($files)) return null; + /* @var \App\Service\AtlassianApiService $attlasianApi */ + $tempResponse = $this->atlassianApi->CreateAttachment($files); + $tempResponseData = json_decode($tempResponse->getBody()->getContents(), true); + $attachmentIds = []; + foreach ($tempResponseData['temporaryAttachments'] ?? [] as $attachment) { + $attachmentIds[] = $attachment['temporaryAttachmentId']; + } + $response = $this->atlassianApi->addIssueAttachments($issueId, $attachmentIds); + return $response; + } +} diff --git a/application/src/Model/AtlassianIdeaModel.php b/application/src/Model/AtlassianIdeaModel.php index 690bd3b..0f61e6b 100644 --- a/application/src/Model/AtlassianIdeaModel.php +++ b/application/src/Model/AtlassianIdeaModel.php @@ -24,8 +24,8 @@ public function __construct( $phone = '' ) { - $this->serviceDeskId = '4'; - $this->requestTypeId = '19'; + $this->serviceDeskId = '5'; + $this->requestTypeId = '26'; $this->summary = $summary; $this->description = $description; $this->name = $name; diff --git a/application/src/Model/AtlassianProblemModel.php b/application/src/Model/AtlassianProblemModel.php index 6ff3fb7..726d77c 100644 --- a/application/src/Model/AtlassianProblemModel.php +++ b/application/src/Model/AtlassianProblemModel.php @@ -7,6 +7,7 @@ class AtlassianProblemModel extends AtlassianIdeaModel { private string $url; + private string $requestTypeId; public function __construct( $summary = '', @@ -26,6 +27,7 @@ public function __construct( $email, $phone ); + $this->requestTypeId = '25'; $this->url = $url; } diff --git a/application/src/Service/AtlassianApiService.php b/application/src/Service/AtlassianApiService.php index 0b00daa..9124628 100644 --- a/application/src/Service/AtlassianApiService.php +++ b/application/src/Service/AtlassianApiService.php @@ -14,6 +14,7 @@ class AtlassianApiService { private Client $client; private ?string $token; + private int $serviceDeskApiID = 5; public function __construct() { @@ -50,4 +51,31 @@ public function getSearchData(array $searchData): ResponseInterface RequestOptions::QUERY => $searchData, ]); } + + public function CreateAttachment(array $files): ResponseInterface + { + return $this->client->post('servicedeskapi/servicedesk/'.$this->serviceDeskApiID.'/attachTemporaryFile', [ + RequestOptions::HEADERS => [ + 'Authorization' => 'Basic '.$this->token, + 'X-Atlassian-Token' => 'nocheck', + ], + RequestOptions::MULTIPART => $files, + ]); + } + + public function addIssueAttachments(string $issueId, array $files): ResponseInterface + { + $data = [ + "public" => true, + "temporaryAttachmentIds" => $files + ]; + return $this->client->post('servicedeskapi/request/'.$issueId.'/attachment', [ + RequestOptions::HEADERS => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + 'Authorization' => 'Basic '.$this->token, + ], + RequestOptions::JSON => $data, + ]); + } } diff --git a/application/src/Traits/RecaptchaTrait.php b/application/src/Traits/RecaptchaTrait.php new file mode 100644 index 0000000..a1c3699 --- /dev/null +++ b/application/src/Traits/RecaptchaTrait.php @@ -0,0 +1,28 @@ + $secretKey, 'response' => $token))); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + curl_close($ch); + $arrResponse = json_decode($response, true); + + // verify the response + if($arrResponse["success"] == '1' && $arrResponse["action"] == $action && $arrResponse["score"] >= 0.5) { + return true; + } else { + return false; + } + } +}