From 924ba58f7368929ada0a8aef0205fbb62f5e2010 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 26 Aug 2016 11:51:51 +0200 Subject: [PATCH 1/5] Added swagger documentation file --- docs/swagger.yaml | 289 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 docs/swagger.yaml diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 00000000..27037fe5 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,289 @@ +swagger: '2.0' +info: + title: Shlink + description: Shlink, the self-hosted URL shortener + version: "1.2.0" + +schemes: + - https + +basePath: /rest +produces: + - application/json + +paths: + /authenticate: + post: + description: Performs an authentication + parameters: + - name: apiKey + in: formData + description: The API key to authenticate with + required: true + type: string + responses: + 200: + description: The authentication worked. + schema: + type: object + properties: + token: + type: string + description: The authentication token that needs to be sent in the Authorization header + 400: + description: An API key was not provided. + schema: + $ref: '#/definitions/Error' + 401: + description: The API key is incorrect, is disabled or has expired. + schema: + $ref: '#/definitions/Error' + 500: + description: Unexpected error. + schema: + $ref: '#/definitions/Error' + /short-codes: + get: + description: Returns the list of short codes + parameters: + - name: page + in: query + description: The page to be displayed. Defaults to 1 + required: false + type: integer + - name: Authorization + in: header + description: The authorization token with Bearer type + required: true + type: string + responses: + 200: + description: The list of short URLs + schema: + type: object + properties: + shortUrls: + type: object + properties: + data: + type: array + items: + $ref: '#/definitions/ShortUrl' + pagination: + $ref: '#/definitions/Pagination' + 500: + description: Unexpected error. + schema: + $ref: '#/definitions/Error' + post: + description: Creates a new short code + parameters: + - name: longUrl + in: formData + description: The URL to parse + required: true + type: string + - name: tags + in: formData + description: The URL to parse + required: false + type: array + items: + type: string + - name: Authorization + in: header + description: The authorization token with Bearer type + required: true + type: string + responses: + 200: + description: The result of parsing the long URL + schema: + type: object + properties: + longUrl: + type: string + description: The original long URL that has been parsed + shortUrl: + type: string + description: The generated short URL + shortCode: + type: string + description: the short code that is being used in the short URL + 400: + description: The long URL was not provided or is invalid. + schema: + $ref: '#/definitions/Error' + 500: + description: Unexpected error. + schema: + $ref: '#/definitions/Error' + /short-codes/{shortCode}: + get: + description: Get the long URL behind a short code. + parameters: + - name: shortCode + in: path + type: string + description: The short code to resolve. + required: true + - name: Authorization + in: header + description: The authorization token with Bearer type + required: true + type: string + responses: + 200: + description: The long URL behind a short code. + schema: + type: object + properties: + longUrl: + type: string + description: The original long URL behind the short code. + 404: + description: No URL was found for provided short code. + schema: + $ref: '#/definitions/Error' + 400: + description: Provided shortCode does not match the character set currently used by the app to generate short codes. + schema: + $ref: '#/definitions/Error' + 500: + description: Unexpected error. + schema: + $ref: '#/definitions/Error' + /short-codes/{shortCode}/visits: + get: + description: Get the list of visits on provided short code. + parameters: + - name: shortCode + in: path + type: string + description: The shortCode from which we want to get the visits. + required: true + - name: Authorization + in: header + description: The authorization token with Bearer type + required: true + type: string + responses: + 200: + description: List of visits. + schema: + type: object + properties: + visits: + type: object + properties: + data: + type: array + items: + $ref: '#/definitions/Visit' + 404: + description: The short code does not belong to any short URL. + schema: + $ref: '#/definitions/Error' + 500: + description: Unexpected error. + schema: + $ref: '#/definitions/Error' + /short-codes/{shortCode}/tags: + put: + description: Edit the tags on provided short code. + parameters: + - name: shortCode + in: path + type: string + description: The shortCode in which we want to edit tags. + required: true + - name: tags + in: formData + type: array + items: + type: string + description: The list of tags to set to the short URL. + required: true + - name: Authorization + in: header + description: The authorization token with Bearer type + required: true + type: string + responses: + 200: + description: List of tags. + schema: + type: object + properties: + tags: + type: array + items: + type: string + 400: + description: The request body does not contain a "tags" param with array type. + schema: + $ref: '#/definitions/Error' + 404: + description: No short URL was found for provided short code. + schema: + $ref: '#/definitions/Error' + 500: + description: Unexpected error. + schema: + $ref: '#/definitions/Error' + +definitions: + ShortUrl: + type: object + properties: + shortCode: + type: string + description: The short code for this short URL. + originalUrl: + type: string + description: The original long URL. + dateCreated: + type: string + format: date-time + description: The date in which the short URL was created in ISO format. + visitsCount: + type: integer + description: The number of visits that this short URL has recieved. + tags: + type: array + items: + type: string + description: A list of tags applied to this short URL + + Visit: + type: object + properties: + referer: + type: string + date: + type: string + format: date-time + remoteAddr: + type: string + userAgent: + type: string + + Error: + type: object + properties: + code: + type: string + description: A machine unique code + message: + type: string + description: A human-friendly error message + + Pagination: + type: object + properties: + currentPage: + type: integer + description: The number of current page being displayed. + pagesCount: + type: integer + description: The total number of pages that can be displayed. From 4bd67d5f9872e417c5c31a036e6774138412faa6 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 27 Aug 2016 13:00:41 +0200 Subject: [PATCH 2/5] Fixed cross domain middleware not exposing the Authorization header --- module/Rest/src/Middleware/CrossDomainMiddleware.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module/Rest/src/Middleware/CrossDomainMiddleware.php b/module/Rest/src/Middleware/CrossDomainMiddleware.php index 4327df9e..d6b84d5b 100644 --- a/module/Rest/src/Middleware/CrossDomainMiddleware.php +++ b/module/Rest/src/Middleware/CrossDomainMiddleware.php @@ -41,7 +41,8 @@ class CrossDomainMiddleware implements MiddlewareInterface } // Add Allow-Origin header - $response = $response->withHeader('Access-Control-Allow-Origin', $request->getHeader('Origin')); + $response = $response->withHeader('Access-Control-Allow-Origin', $request->getHeader('Origin')) + ->withHeader('Access-Control-Expose-Headers', 'Authorization'); if ($request->getMethod() !== 'OPTIONS') { return $response; } From a9f480ca995196b32a863d6bf04560890d030d86 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 28 Aug 2016 09:46:11 +0200 Subject: [PATCH 3/5] Fixed error while checking an API key that doesn't exist --- module/Rest/src/Action/AuthenticateAction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/Rest/src/Action/AuthenticateAction.php b/module/Rest/src/Action/AuthenticateAction.php index 020a0fb5..a20e5964 100644 --- a/module/Rest/src/Action/AuthenticateAction.php +++ b/module/Rest/src/Action/AuthenticateAction.php @@ -66,7 +66,7 @@ class AuthenticateAction extends AbstractRestAction // Authenticate using provided API key $apiKey = $this->apiKeyService->getByKey($authData['apiKey']); - if (! $apiKey->isValid()) { + if (! isset($apiKey) || ! $apiKey->isValid()) { return new JsonResponse([ 'error' => RestUtils::INVALID_API_KEY_ERROR, 'message' => $this->translator->translate('Provided API key does not exist or is invalid.'), From 15896045f322bf08c1fb735791887fe2f97ab85d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 28 Aug 2016 19:32:07 +0200 Subject: [PATCH 4/5] Removed logic making visits to be returned for 2 days only if no start or end date were provided --- composer.json | 3 +++ module/Core/src/Repository/VisitRepository.php | 6 ------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 7f1ef082..58e9d40d 100644 --- a/composer.json +++ b/composer.json @@ -73,5 +73,8 @@ "serve": "php -S 0.0.0.0:8000 -t public/", "test": "phpunit --coverage-clover build/clover.xml", "pretty-test": "phpunit --coverage-html build/coverage" + }, + "config": { + "process-timeout": 0 } } diff --git a/module/Core/src/Repository/VisitRepository.php b/module/Core/src/Repository/VisitRepository.php index 743427f6..f86017a4 100644 --- a/module/Core/src/Repository/VisitRepository.php +++ b/module/Core/src/Repository/VisitRepository.php @@ -29,12 +29,6 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa $shortUrl = $shortUrl instanceof ShortUrl ? $shortUrl : $this->getEntityManager()->find(ShortUrl::class, $shortUrl); - if (! isset($dateRange) || $dateRange->isEmpty()) { - $startDate = $shortUrl->getDateCreated(); - $endDate = clone $startDate; - $endDate->add(new \DateInterval('P2D')); - $dateRange = new DateRange($startDate, $endDate); - } $qb = $this->createQueryBuilder('v'); $qb->where($qb->expr()->eq('v.shortUrl', ':shortUrl')) From cf604402885a91f9fa7cefa715fcfb0ee573701f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 29 Aug 2016 12:43:02 +0200 Subject: [PATCH 5/5] Fixed possible PHP errors being missed while checking REST auth --- .../src/Middleware/CheckAuthenticationMiddleware.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php b/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php index 53f6cbe9..5b18ea31 100644 --- a/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php +++ b/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php @@ -13,6 +13,7 @@ use Shlinkio\Shlink\Rest\Util\RestUtils; use Zend\Diactoros\Response\JsonResponse; use Zend\Expressive\Router\RouteResult; use Zend\I18n\Translator\TranslatorInterface; +use Zend\Stdlib\ErrorHandler; use Zend\Stratigility\MiddlewareInterface; class CheckAuthenticationMiddleware implements MiddlewareInterface @@ -117,9 +118,11 @@ class CheckAuthenticationMiddleware implements MiddlewareInterface } try { + ErrorHandler::start(); if (! $this->jwtService->verify($jwt)) { return $this->createTokenErrorResponse(); } + ErrorHandler::stop(true); // Update the token expiration and continue to next middleware $jwt = $this->jwtService->refresh($jwt); @@ -131,6 +134,14 @@ class CheckAuthenticationMiddleware implements MiddlewareInterface } catch (AuthenticationException $e) { $this->logger->warning('Tried to access API with an invalid JWT.' . PHP_EOL . $e); return $this->createTokenErrorResponse(); + } catch (\Exception $e) { + $this->logger->warning('Unexpected error occurred.' . PHP_EOL . $e); + return $this->createTokenErrorResponse(); + } catch (\Throwable $e) { + $this->logger->warning('Unexpected error occurred.' . PHP_EOL . $e); + return $this->createTokenErrorResponse(); + } finally { + ErrorHandler::clean(); } }