diff --git a/app/Http/Controllers/MicropubController.php b/app/Http/Controllers/MicropubController.php index e414da3d..ef561efc 100644 --- a/app/Http/Controllers/MicropubController.php +++ b/app/Http/Controllers/MicropubController.php @@ -47,117 +47,107 @@ class MicropubController extends Controller */ public function post(Request $request) { - $httpAuth = $request->header('Authorization'); - if (preg_match('/Bearer (.+)/', $httpAuth, $match)) { - $token = $match[1]; - $tokenData = $this->tokenService->validateToken($token); - if ($tokenData->hasClaim('scope')) { - $scopes = explode(' ', $tokenData->getClaim('scope')); - if (array_search('post', $scopes) !== false) { - $clientId = $tokenData->getClaim('client_id'); - if (($request->input('h') == 'entry') || ($request->input('type')[0] == 'h-entry')) { - $data = []; - $data['client-id'] = $clientId; - if ($request->header('Content-Type') == 'application/json') { - $data['content'] = $request->input('properties.content')[0]; - $data['in-reply-to'] = $request->input('properties.in-reply-to')[0]; - $data['location'] = $request->input('properties.location'); - //flatten location if array - if (is_array($data['location'])) { - $data['location'] = $data['location'][0]; - } - } else { - $data['content'] = $request->input('content'); - $data['in-reply-to'] = $request->input('in-reply-to'); - $data['location'] = $request->input('location'); + $tokenData = $this->tokenService->validateToken($request->bearerToken()); + if ($tokenData->hasClaim('scope')) { + $scopes = explode(' ', $tokenData->getClaim('scope')); + if (array_search('post', $scopes) !== false) { + $clientId = $tokenData->getClaim('client_id'); + if (($request->input('h') == 'entry') || ($request->input('type')[0] == 'h-entry')) { + $data = []; + $data['client-id'] = $clientId; + if ($request->header('Content-Type') == 'application/json') { + $data['content'] = $request->input('properties.content')[0]; + $data['in-reply-to'] = $request->input('properties.in-reply-to')[0]; + $data['location'] = $request->input('properties.location'); + //flatten location if array + if (is_array($data['location'])) { + $data['location'] = $data['location'][0]; } - $data['syndicate'] = []; - $targets = array_pluck(config('syndication.targets'), 'uid', 'service.name'); - if (is_string($request->input('mp-syndicate-to'))) { - $service = array_search($request->input('mp-syndicate-to')); - if ($service == 'Twitter') { - $data['syndicate'][] = 'twitter'; - } - if ($service == 'Facebook') { - $data['syndicate'][] = 'facebook'; - } + } else { + $data['content'] = $request->input('content'); + $data['in-reply-to'] = $request->input('in-reply-to'); + $data['location'] = $request->input('location'); + } + $data['syndicate'] = []; + $targets = array_pluck(config('syndication.targets'), 'uid', 'service.name'); + if (is_string($request->input('mp-syndicate-to'))) { + $service = array_search($request->input('mp-syndicate-to')); + if ($service == 'Twitter') { + $data['syndicate'][] = 'twitter'; } - if (is_array($request->input('mp-syndicate-to'))) { - foreach ($targets as $service => $target) { - if (in_array($target, $request->input('mp-syndicate-to'))) { - if ($service == 'Twitter') { - $data['syndicate'][] = 'twitter'; - } - if ($service == 'Facebook') { - $data['syndicate'][] = 'facebook'; - } + if ($service == 'Facebook') { + $data['syndicate'][] = 'facebook'; + } + } + if (is_array($request->input('mp-syndicate-to'))) { + foreach ($targets as $service => $target) { + if (in_array($target, $request->input('mp-syndicate-to'))) { + if ($service == 'Twitter') { + $data['syndicate'][] = 'twitter'; + } + if ($service == 'Facebook') { + $data['syndicate'][] = 'facebook'; } } } - $data['photo'] = []; - if (is_array($request->input('photo'))) { - foreach ($request->input('photo') as $photo) { - if (is_string($photo)) { - //only supporting media URLs for now - $data['photo'][] = $photo; - } - } - } - try { - $note = $this->noteService->createNote($data); - } catch (Exception $exception) { - return response()->json(['error' => true], 400); - } - - return response()->json([ - 'response' => 'created', - 'location' => $note->longurl, - ], 201)->header('Location', $note->longurl); } - if ($request->input('h') == 'card' || $request->input('type')[0] == 'h-card') { - $data = []; - if ($request->header('Content-Type') == 'application/json') { - $data['name'] = $request->input('properties.name'); - $data['description'] = $request->input('properties.description') ?? null; - if ($request->has('properties.geo')) { - $data['geo'] = $request->input('properties.geo'); - } - } else { - $data['name'] = $request->input('name'); - $data['description'] = $request->input('description'); - if ($request->has('geo')) { - $data['geo'] = $request->input('geo'); - } - if ($request->has('latitude')) { - $data['latitude'] = $request->input('latitude'); - $data['longitude'] = $request->input('longitude'); + $data['photo'] = []; + if (is_array($request->input('photo'))) { + foreach ($request->input('photo') as $photo) { + if (is_string($photo)) { + //only supporting media URLs for now + $data['photo'][] = $photo; } } - try { - $place = $this->placeService->createPlace($data); - } catch (Exception $exception) { - return response()->json(['error' => true], 400); - } - - return response()->json([ - 'response' => 'created', - 'location' => $place->longurl, - ], 201)->header('Location', $place->longurl); } + try { + $note = $this->noteService->createNote($data); + } catch (Exception $exception) { + return response()->json(['error' => true], 400); + } + + return response()->json([ + 'response' => 'created', + 'location' => $note->longurl, + ], 201)->header('Location', $note->longurl); + } + if ($request->input('h') == 'card' || $request->input('type')[0] == 'h-card') { + $data = []; + if ($request->header('Content-Type') == 'application/json') { + $data['name'] = $request->input('properties.name'); + $data['description'] = $request->input('properties.description') ?? null; + if ($request->has('properties.geo')) { + $data['geo'] = $request->input('properties.geo'); + } + } else { + $data['name'] = $request->input('name'); + $data['description'] = $request->input('description'); + if ($request->has('geo')) { + $data['geo'] = $request->input('geo'); + } + if ($request->has('latitude')) { + $data['latitude'] = $request->input('latitude'); + $data['longitude'] = $request->input('longitude'); + } + } + try { + $place = $this->placeService->createPlace($data); + } catch (Exception $exception) { + return response()->json(['error' => true], 400); + } + + return response()->json([ + 'response' => 'created', + 'location' => $place->longurl, + ], 201)->header('Location', $place->longurl); } } - - return response()->json([ - 'response' => 'error', - 'error' => 'invalid_token', - 'error_description' => 'The token provided is not valid or does not have the necessary scope', - ], 400); } return response()->json([ 'response' => 'error', - 'error' => 'no_token', - 'error_description' => 'No OAuth token sent with request', + 'error' => 'invalid_token', + 'error_description' => 'The token provided is not valid or does not have the necessary scope', ], 400); } @@ -172,68 +162,57 @@ class MicropubController extends Controller */ public function get(Request $request) { - $httpAuth = $request->header('Authorization'); - if (preg_match('/Bearer (.+)/', $httpAuth, $match)) { - $token = $match[1]; - $valid = $this->tokenService->validateToken($token); - - if ($valid === null) { - return response()->json([ - 'response' => 'error', - 'error' => 'invalid_token', - 'error_description' => 'The provided token did not pass validation', - ], 400); - } - //we have a valid token, is `syndicate-to` set? - if ($request->input('q') === 'syndicate-to') { - return response()->json([ - 'syndicate-to' => config('syndication.targets'), - ]); - } - - //nope, how about a config query? - if ($request->input('q') == 'config') { - return response()->json([ - 'syndicate-to' => config('syndication.targets'), - 'media-endpoint' => route('media-endpoint'), - ]); - } - - //nope, how about a geo URL? - if (substr($request->input('q'), 0, 4) === 'geo:') { - preg_match_all( - '/([0-9\.\-]+)/', - $request->input('q'), - $matches - ); - $distance = (count($matches[0]) == 3) ? 100 * $matches[0][2] : 1000; - $places = Place::near($matches[0][0], $matches[0][1], $distance); - foreach ($places as $place) { - $place->uri = config('app.url') . '/places/' . $place->slug; - } - - return response()->json([ - 'response' => 'places', - 'places' => $places, - ]); - } - - //nope, just return the token + $tokenData = $this->tokenService->validateToken($request->bearerToken()); + if ($valid === null) { return response()->json([ - 'response' => 'token', - 'token' => [ - 'me' => $valid->getClaim('me'), - 'scope' => $valid->getClaim('scope'), - 'client_id' => $valid->getClaim('client_id'), - ], + 'response' => 'error', + 'error' => 'invalid_token', + 'error_description' => 'The provided token did not pass validation', + ], 400); + } + //we have a valid token, is `syndicate-to` set? + if ($request->input('q') === 'syndicate-to') { + return response()->json([ + 'syndicate-to' => config('syndication.targets'), ]); } + //nope, how about a config query? + if ($request->input('q') == 'config') { + return response()->json([ + 'syndicate-to' => config('syndication.targets'), + 'media-endpoint' => route('media-endpoint'), + ]); + } + + //nope, how about a geo URL? + if (substr($request->input('q'), 0, 4) === 'geo:') { + preg_match_all( + '/([0-9\.\-]+)/', + $request->input('q'), + $matches + ); + $distance = (count($matches[0]) == 3) ? 100 * $matches[0][2] : 1000; + $places = Place::near($matches[0][0], $matches[0][1], $distance); + foreach ($places as $place) { + $place->uri = config('app.url') . '/places/' . $place->slug; + } + + return response()->json([ + 'response' => 'places', + 'places' => $places, + ]); + } + + //nope, just return the token return response()->json([ - 'response' => 'error', - 'error' => 'no_token', - 'error_description' => 'No token provided with request', - ], 400); + 'response' => 'token', + 'token' => [ + 'me' => $valid->getClaim('me'), + 'scope' => $valid->getClaim('scope'), + 'client_id' => $valid->getClaim('client_id'), + ], + ]); } /** @@ -244,83 +223,64 @@ class MicropubController extends Controller */ public function media(Request $request) { - //can this go in middleware - $httpAuth = $request->header('Authorization'); - if (preg_match('/Bearer (.+)/', $httpAuth, $match)) { - $token = $match[1]; - $tokenData = $this->tokenService->validateToken($token); - - if ($tokenData === null) { - return response()->json([ - 'response' => 'error', - 'error' => 'invalid_token', - 'error_description' => 'The provided token did not pass validation', - ], 400); - } - - //check post scope - if ($tokenData->hasClaim('scope')) { - $scopes = explode(' ', $tokenData->getClaim('scope')); - if (array_search('post', $scopes) !== false) { - //check media valid - if ($request->hasFile('file') && $request->file('file')->isValid()) { - $type = $this->getFileTypeFromMimeType($request->file('file')->getMimeType()); - try { - $filename = Uuid::uuid4() . '.' . $request->file('file')->extension(); - } catch (UnsatisfiedDependencyException $e) { - return response()->json([ - 'response' => 'error', - 'error' => 'internal_server_error', - 'error_description' => 'A problem occured handling your request', - ], 500); - } - try { - $path = $request->file('file')->storeAs('media', $filename, 's3'); - } catch (Exception $e) { // which exception? - return response()->json([ - 'response' => 'error', - 'error' => 'service_unavailable', - 'error_description' => 'Unable to save media to S3', - ], 503); - } - $media = new Media(); - $media->token = $token; - $media->path = $path; - $media->type = $type; - $media->save(); + $tokenData = $this->tokenService->validateToken($request->bearerToken()); + if ($tokenData === null) { + return response()->json([ + 'response' => 'error', + 'error' => 'invalid_token', + 'error_description' => 'The provided token did not pass validation', + ], 400); + } + //check post scope + if ($tokenData->hasClaim('scope')) { + $scopes = explode(' ', $tokenData->getClaim('scope')); + if (array_search('post', $scopes) !== false) { + //check media valid + if ($request->hasFile('file') && $request->file('file')->isValid()) { + $type = $this->getFileTypeFromMimeType($request->file('file')->getMimeType()); + try { + $filename = Uuid::uuid4() . '.' . $request->file('file')->extension(); + } catch (UnsatisfiedDependencyException $e) { return response()->json([ - 'response' => 'created', - 'location' => $media->url, - ], 201)->header('Location', $media->url); + 'response' => 'error', + 'error' => 'internal_server_error', + 'error_description' => 'A problem occured handling your request', + ], 500); } + try { + $path = $request->file('file')->storeAs('media', $filename, 's3'); + } catch (Exception $e) { // which exception? + return response()->json([ + 'response' => 'error', + 'error' => 'service_unavailable', + 'error_description' => 'Unable to save media to S3', + ], 503); + } + $media = new Media(); + $media->token = $token; + $media->path = $path; + $media->type = $type; + $media->save(); return response()->json([ - 'response' => 'error', - 'error' => 'invalid_request', - 'error_description' => 'The uploaded file failed validation', - ], 400); + 'response' => 'created', + 'location' => $media->url, + ], 201)->header('Location', $media->url); } return response()->json([ 'response' => 'error', - 'error' => 'insufficient_scope', - 'error_description' => 'The provided token has insufficient scopes', - ], 401); + 'error' => 'invalid_request', + 'error_description' => 'The uploaded file failed validation', + ], 400); } - return response()->json([ - 'response' => 'error', - 'error' => 'unauthorized', - 'error_description' => 'No token provided with request', - ], 401); - } - return response()->json([ 'response' => 'error', - 'error' => 'no_token', - 'error_description' => 'There was no token provided with the request', - ], 400); + 'error' => 'insufficient_scope', + 'error_description' => 'The provided token has insufficient scopes', + ], 401); } /** diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 45473220..63f01def 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -58,5 +58,6 @@ class Kernel extends HttpKernel 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'myauth' => \App\Http\Middleware\MyAuthMiddleware::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'micropub.token' => \App\Http\Middleware\VerifyMicropubToken::class, ]; } diff --git a/app/Http/Middleware/VerifyMicropubToken.php b/app/Http/Middleware/VerifyMicropubToken.php new file mode 100644 index 00000000..b9973e10 --- /dev/null +++ b/app/Http/Middleware/VerifyMicropubToken.php @@ -0,0 +1,24 @@ +bearerToken() === null) { + abort(401); + } + + return $next($request); + } +} diff --git a/routes/web.php b/routes/web.php index 5d9c572d..f9db688d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -116,9 +116,9 @@ Route::group(['domain' => config('url.longurl')], function () { Route::get('micropub/media/clearlinks', 'MicropubClientController@clearLinks'); // Micropub Endpoint - Route::get('api/post', 'MicropubController@get'); - Route::post('api/post', 'MicropubController@post'); - Route::post('api/media', 'MicropubController@media')->name('media-endpoint'); + Route::get('api/post', 'MicropubController@get')->middleware('micropub.token'); + Route::post('api/post', 'MicropubController@post')->middleware('micropub.token'); + Route::post('api/media', 'MicropubController@media')->middleware('micropub.token')->name('media-endpoint'); //webmention Route::get('webmention', 'WebMentionsController@get');