Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/cydrobolt/polr.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorChaoyi Zha <summermontreal@gmail.com>2017-03-18 00:00:13 +0300
committerChaoyi Zha <summermontreal@gmail.com>2017-03-18 00:00:13 +0300
commit65794f83d72161bf6330ba04107035692d9ad261 (patch)
tree36b9d780614dcdce9a4b712bd34a682baa9bf093 /app
parent8f6d9762e98a418c7f3cfc416199b654c39319b0 (diff)
Use ApiException to handle API errors and use ApiMiddleware to handle API authentication
Diffstat (limited to 'app')
-rw-r--r--app/Exceptions/Api/ApiException.php42
-rw-r--r--app/Exceptions/Handler.php18
-rw-r--r--app/Http/Controllers/Api/ApiAnalyticsController.php11
-rw-r--r--app/Http/Controllers/Api/ApiController.php41
-rw-r--r--app/Http/Controllers/Api/ApiLinkController.php19
-rw-r--r--app/Http/Middleware/ApiMiddleware.php62
-rw-r--r--app/Http/Middleware/VerifyCsrfToken.php2
-rw-r--r--app/Http/routes.php15
8 files changed, 148 insertions, 62 deletions
diff --git a/app/Exceptions/Api/ApiException.php b/app/Exceptions/Api/ApiException.php
new file mode 100644
index 0000000..d2f36a6
--- /dev/null
+++ b/app/Exceptions/Api/ApiException.php
@@ -0,0 +1,42 @@
+<?php
+namespace App\Exceptions\Api;
+
+class ApiException extends \Exception {
+ /**
+ * Catch an API exception.
+ *
+ * @param string $text_code
+ * @param string $message
+ * @param integer $status_code
+ * @param string $response_type
+ * @param \Exception $previous
+ *
+ * @return mixed
+ */
+ public function __construct($text_code='SERVER_ERROR', $message, $status_code = 0, $response_type='plain_text', Exception $previous = null) {
+ // TODO special Polr error codes for JSON
+
+ $this->response_type = $response_type;
+ $this->text_code = $text_code;
+ parent::__construct($message, $status_code, $previous);
+ }
+
+ private function encodeJsonResponse($status_code, $message, $text_code) {
+ $response = [
+ 'status_code' => $status_code,
+ 'error_code' => $text_code,
+ 'error' => $message
+ ];
+
+ return json_encode($response);
+ }
+
+ public function getEncodedErrorMessage() {
+ if ($this->response_type == 'json') {
+ return $this->encodeJsonResponse($this->code, $this->message, $this->text_code);
+ }
+ else {
+ return $this->code . ' ' . $this->message;
+ }
+ }
+}
diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
index c0e1136..2afb883 100644
--- a/app/Exceptions/Handler.php
+++ b/app/Exceptions/Handler.php
@@ -8,6 +8,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Laravel\Lumen\Exceptions\Handler as ExceptionHandler;
use Illuminate\Support\Facades\Response;
+use App\Exceptions\Api\ApiException;
+
class Handler extends ExceptionHandler {
/**
* A list of the exception types that should not be reported.
@@ -43,6 +45,7 @@ class Handler extends ExceptionHandler {
if (env('APP_DEBUG') != true) {
// Render nice error pages if debug is off
if ($e instanceof NotFoundHttpException) {
+ // Handle 404 exceptions
if (env('SETTING_REDIRECT_404')) {
// Redirect 404s to SETTING_INDEX_REDIRECT
return redirect()->to(env('SETTING_INDEX_REDIRECT'));
@@ -51,6 +54,7 @@ class Handler extends ExceptionHandler {
return view('errors.404');
}
if ($e instanceof HttpException) {
+ // Handle HTTP exceptions thrown by public-facing controllers
$status_code = $e->getStatusCode();
$status_message = $e->getMessage();
@@ -63,6 +67,20 @@ class Handler extends ExceptionHandler {
return response(view('errors.generic', ['status_code' => $status_code, 'status_message' => $status_message]), $status_code);
}
}
+ if ($e instanceof ApiException) {
+ // Handle HTTP exceptions thrown by API controllers
+ $status_code = $e->getCode();
+ $encoded_status_message = $e->getEncodedErrorMessage();
+ if ($e->response_type == 'json') {
+ return response($encoded_status_message, $status_code)
+ ->header('Content-Type', 'application/json')
+ ->header('Access-Control-Allow-Origin', '*');
+ }
+
+ return response($encoded_status_message, $status_code)
+ ->header('Content-Type', 'text/plain')
+ ->header('Access-Control-Allow-Origin', '*');
+ }
}
return parent::render($request, $e);
diff --git a/app/Http/Controllers/Api/ApiAnalyticsController.php b/app/Http/Controllers/Api/ApiAnalyticsController.php
index 692316d..abb2d86 100644
--- a/app/Http/Controllers/Api/ApiAnalyticsController.php
+++ b/app/Http/Controllers/Api/ApiAnalyticsController.php
@@ -5,13 +5,14 @@ use Illuminate\Http\Request;
use App\Helpers\LinkHelper;
use App\Helpers\UserHelper;
use App\Helpers\StatsHelper;
+use App\Exceptions\Api\ApiException;
class ApiAnalyticsController extends ApiController {
public function lookupLinkStats (Request $request, $stats_type=false) {
$response_type = $request->input('response_type') ?: 'json';
if ($response_type != 'json') {
- abort(401, 'Only JSON-encoded data is available for this endpoint.');
+ throw new ApiException('JSON_ONLY', 'Only JSON-encoded data is available for this endpoint.', 401, $response_type);
}
$user = self::getApiUserInfo($request);
@@ -24,7 +25,7 @@ class ApiAnalyticsController extends ApiController {
]);
if ($validator->fails()) {
- return abort(400, 'Invalid or missing parameters.');
+ throw new ApiException('MISSING_PARAMETERS', 'Invalid or missing parameters.', 400, $response_type);
}
$url_ending = $request->input('url_ending');
@@ -37,13 +38,13 @@ class ApiAnalyticsController extends ApiController {
$link = LinkHelper::linkExists($url_ending);
if ($link === false) {
- abort(404, 'Link not found.');
+ throw new ApiException('NOT_FOUND', 'Link not found.', 404, $response_type);
}
if (($link->creator != $user->username) &&
!(UserHelper::userIsAdmin($user->username))){
// If user does not own link and is not an admin
- abort(401, 'Unauthorized.');
+ throw new ApiException('ACCESS_DENIED', 'Unauthorized.', 401, $response_type);
}
$stats = new StatsHelper($link->id, $left_bound, $right_bound);
@@ -58,7 +59,7 @@ class ApiAnalyticsController extends ApiController {
$fetched_stats = $stats->getRefererStats();
}
else {
- abort(400, 'Invalid analytics type requested.');
+ throw new ApiException('INVALID_ANALYTICS_TYPE', 'Invalid analytics type requested.', 400, $response_type);
}
return self::encodeResponse([
diff --git a/app/Http/Controllers/Api/ApiController.php b/app/Http/Controllers/Api/ApiController.php
index 27d102b..ea3b96c 100644
--- a/app/Http/Controllers/Api/ApiController.php
+++ b/app/Http/Controllers/Api/ApiController.php
@@ -2,48 +2,8 @@
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
-use Illuminate\Http\Request;
-
-use App\Models\User;
-use App\Helpers\ApiHelper;
class ApiController extends Controller {
- protected static function getApiUserInfo(Request $request) {
- $api_key = $request->input('key');
-
- if (!$api_key) {
- // no API key provided -- check whether anonymous API is on
-
- if (env('SETTING_ANON_API')) {
- $username = 'ANONIP-' . $request->ip();
- }
- else {
- abort(401, "Authentication token required.");
- }
- $user = (object) [
- 'username' => $username
- ];
- }
- else {
- $user = User::where('active', 1)
- ->where('api_key', $api_key)
- ->where('api_active', 1)
- ->first();
-
- if (!$user) {
- abort(401, "Invalid authentication token.");
- }
- $username = $user->username;
- }
-
- $api_limit_reached = ApiHelper::checkUserApiQuota($username);
-
- if ($api_limit_reached) {
- abort(403, "Quota exceeded.");
- }
- return $user;
- }
-
protected static function encodeResponse($result, $action, $response_type='json', $plain_text_response=false) {
$response = [
"action" => $action,
@@ -64,7 +24,6 @@ class ApiController extends Controller {
return response($result)
->header('Content-Type', 'text/plain')
->header('Access-Control-Allow-Origin', '*');
-
}
}
}
diff --git a/app/Http/Controllers/Api/ApiLinkController.php b/app/Http/Controllers/Api/ApiLinkController.php
index 0880df2..d4c72f2 100644
--- a/app/Http/Controllers/Api/ApiLinkController.php
+++ b/app/Http/Controllers/Api/ApiLinkController.php
@@ -4,11 +4,13 @@ use Illuminate\Http\Request;
use App\Factories\LinkFactory;
use App\Helpers\LinkHelper;
+use App\Exceptions\Api\ApiException;
class ApiLinkController extends ApiController {
public function shortenLink(Request $request) {
$response_type = $request->input('response_type');
- $user = self::getApiUserInfo($request);
+ // $user = self::getApiUserInfo($request);
+ $user = $request->user;
// Validate parameters
// Encode spaces as %20 to avoid validator conflicts
@@ -19,7 +21,7 @@ class ApiLinkController extends ApiController {
]);
if ($validator->fails()) {
- return abort(400, 'Invalid or missing parameters.');
+ throw new ApiException('MISSING_PARAMETERS', 'Invalid or missing parameters.', 400, $response_type);
}
$long_url = $request->input('url'); // * required
@@ -32,15 +34,17 @@ class ApiLinkController extends ApiController {
$formatted_link = LinkFactory::createLink($long_url, $is_secret, $custom_ending, $link_ip, $user->username, false, true);
}
catch (\Exception $e) {
- abort(400, $e->getMessage());
+ throw new ApiException('CREATE_ERROR', $e->getMessage(), 400, $response_type);
}
return self::encodeResponse($formatted_link, 'shorten', $response_type);
}
public function lookupLink(Request $request) {
+ $user = $request->user;
+
$response_type = $request->input('response_type');
- $user = self::getApiUserInfo($request);
+ // $user = self::getApiUserInfo($request);
// Validate URL form data
$validator = \Validator::make($request->all(), [
@@ -48,7 +52,7 @@ class ApiLinkController extends ApiController {
]);
if ($validator->fails()) {
- return abort(400, 'Invalid or missing parameters.');
+ throw new ApiException('MISSING_PARAMETERS', 'Invalid or missing parameters.', 400, $response_type);
}
$url_ending = $request->input('url_ending');
@@ -60,7 +64,7 @@ class ApiLinkController extends ApiController {
if ($link['secret_key']) {
if ($url_key != $link['secret_key']) {
- abort(401, "Invalid URL code for secret URL.");
+ throw new ApiException('ACCESS_DENIED', 'Invalid URL code for secret URL.', 401, $response_type);
}
}
@@ -74,8 +78,7 @@ class ApiLinkController extends ApiController {
], 'lookup', $response_type, $link['long_url']);
}
else {
- abort(404, "Link not found.");
+ throw new ApiException('NOT_FOUND', 'Link not found.', 404, $response_type);
}
-
}
}
diff --git a/app/Http/Middleware/ApiMiddleware.php b/app/Http/Middleware/ApiMiddleware.php
new file mode 100644
index 0000000..6c4e5eb
--- /dev/null
+++ b/app/Http/Middleware/ApiMiddleware.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+use App\Models\User;
+use App\Helpers\ApiHelper;
+use App\Exceptions\Api\ApiException;
+
+class ApiMiddleware {
+ protected static function getApiUserInfo(Request $request) {
+ $api_key = $request->input('key');
+ $response_type = $request->input('response_type');
+
+ if (!$api_key) {
+ // no API key provided; check whether anonymous API is enabled
+
+ if (env('SETTING_ANON_API')) {
+ $username = 'ANONIP-' . $request->ip();
+ }
+ else {
+ throw new ApiException('AUTH_ERROR', 'Authentication token required.', 401, $response_type);
+ }
+ $user = (object) [
+ 'username' => $username
+ ];
+ }
+ else {
+ $user = User::where('active', 1)
+ ->where('api_key', $api_key)
+ ->where('api_active', 1)
+ ->first();
+
+ if (!$user) {
+ abort(401, "Invalid authentication token.");
+ }
+ $username = $user->username;
+ }
+
+ $api_limit_reached = ApiHelper::checkUserApiQuota($username);
+
+ if ($api_limit_reached) {
+ throw new ApiException('QUOTA_EXCEEDED', 'Quota exceeded.', 429, $response_type);
+ }
+ return $user;
+ }
+
+ /**
+ * Handle an incoming request.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \Closure $next
+ * @return mixed
+ */
+
+ public function handle($request, Closure $next) {
+ $request->user = $this->getApiUserInfo($request);
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php
index a08af42..d1ee178 100644
--- a/app/Http/Middleware/VerifyCsrfToken.php
+++ b/app/Http/Middleware/VerifyCsrfToken.php
@@ -11,7 +11,7 @@ class VerifyCsrfToken extends BaseVerifier
* @var array
*/
public function handle($request, \Closure $next) {
- if ($request->is('api/v*/action/*')) {
+ if ($request->is('api/v*/action/*') || $request->is('api/v*/data/*')) {
// Exclude public API from CSRF protection
// but do not exclude private API endpoints
return $next($request);
diff --git a/app/Http/routes.php b/app/Http/routes.php
index bd85dce..7ec879d 100644
--- a/app/Http/routes.php
+++ b/app/Http/routes.php
@@ -59,17 +59,18 @@ $app->group(['prefix' => '/api/v2', 'namespace' => 'App\Http\Controllers'], func
$app->get('admin/get_admin_users', ['as' => 'api_get_admin_users', 'uses' => 'AdminPaginationController@paginateAdminUsers']);
$app->get('admin/get_admin_links', ['as' => 'api_get_admin_links', 'uses' => 'AdminPaginationController@paginateAdminLinks']);
$app->get('admin/get_user_links', ['as' => 'api_get_user_links', 'uses' => 'AdminPaginationController@paginateUserLinks']);
+});
-
+$app->group(['prefix' => '/api/v2', 'namespace' => 'App\Http\Controllers\Api', 'middleware' => 'api'], function ($app) {
/* API shorten endpoints */
- $app->post('action/shorten', ['as' => 'api_shorten_url', 'uses' => 'Api\ApiLinkController@shortenLink']);
- $app->get('action/shorten', ['as' => 'api_shorten_url', 'uses' => 'Api\ApiLinkController@shortenLink']);
+ $app->post('action/shorten', ['as' => 'api_shorten_url', 'uses' => 'ApiLinkController@shortenLink']);
+ $app->get('action/shorten', ['as' => 'api_shorten_url', 'uses' => 'ApiLinkController@shortenLink']);
/* API lookup endpoints */
- $app->post('action/lookup', ['as' => 'api_lookup_url', 'uses' => 'Api\ApiLinkController@lookupLink']);
- $app->get('action/lookup', ['as' => 'api_lookup_url', 'uses' => 'Api\ApiLinkController@lookupLink']);
+ $app->post('action/lookup', ['as' => 'api_lookup_url', 'uses' => 'ApiLinkController@lookupLink']);
+ $app->get('action/lookup', ['as' => 'api_lookup_url', 'uses' => 'ApiLinkController@lookupLink']);
/* API data endpoints */
- $app->get('data/link', ['as' => 'api_link_analytics', 'uses' => 'Api\ApiAnalyticsController@lookupLinkStats']);
- $app->post('data/link', ['as' => 'api_link_analytics', 'uses' => 'Api\ApiAnalyticsController@lookupLinkStats']);
+ $app->get('data/link', ['as' => 'api_link_analytics', 'uses' => 'ApiAnalyticsController@lookupLinkStats']);
+ $app->post('data/link', ['as' => 'api_link_analytics', 'uses' => 'ApiAnalyticsController@lookupLinkStats']);
});