jonnybarnes.uk/app/Http/Controllers/MicropubController.php
Jonny Barnes 8772db973b Squashed commit of the following:
commit fcebc08f6d89437ba84288a25498ae094fd4f16d
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Jan 10 21:59:33 2018 +0000

    update changelog

commit 74491698857cb2e111006efb349e1f10c2e3cf1d
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Jan 10 21:58:25 2018 +0000

    Modify the micropub controller to look for the token in the right palce (as set by the token middleware

commit 0fd11ff8391062fbe70f3a28d6a98694dc25b36b
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Jan 10 21:57:40 2018 +0000

    If the access token is sent as a bearer token in the http headers, merge it into the request data so the controllers only have one place to look

commit 9e154ec4bc17be3071280409a3f6bb7f02dad816
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Jan 10 21:56:33 2018 +0000

    Add a test with the access token being form encoded
2018-01-10 22:00:03 +00:00

303 lines
9.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers;
use Monolog\Logger;
use Ramsey\Uuid\Uuid;
use App\Jobs\ProcessMedia;
use App\Services\TokenService;
use Illuminate\Http\UploadedFile;
use Monolog\Handler\StreamHandler;
use Intervention\Image\ImageManager;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\{Request, Response};
use App\Exceptions\InvalidTokenException;
use App\Models\{Like, Media, Note, Place};
use Phaza\LaravelPostgis\Geometries\Point;
use Intervention\Image\Exception\NotReadableException;
use App\Services\Micropub\{HCardService, HEntryService, UpdateService};
class MicropubController extends Controller
{
protected $tokenService;
protected $hentryService;
protected $hcardService;
protected $updateService;
public function __construct(
TokenService $tokenService,
HEntryService $hentryService,
HCardService $hcardService,
UpdateService $updateService
) {
$this->tokenService = $tokenService;
$this->hentryService = $hentryService;
$this->hcardService = $hcardService;
$this->updateService = $updateService;
}
/**
* This function receives an API request, verifies the authenticity
* then passes over the info to the relavent Service class.
*
* @return \Illuminate\Http\Response
*/
public function post()
{
try {
info(request()->input('access_token'));
$tokenData = $this->tokenService->validateToken(request()->input('access_token'));
} catch (InvalidTokenException $e) {
return $this->invalidTokenResponse();
}
if ($tokenData->hasClaim('scope') === false) {
return $this->tokenHasNoScopeResponse();
}
$this->logMicropubRequest(request()->all());
if ((request()->input('h') == 'entry') || (request()->input('type.0') == 'h-entry')) {
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
return $this->insufficientScopeResponse();
}
$location = $this->hentryService->process(request()->all(), $this->getCLientId());
return response()->json([
'response' => 'created',
'location' => $location,
], 201)->header('Location', $location);
}
if (request()->input('h') == 'card' || request()->input('type')[0] == 'h-card') {
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
return $this->insufficientScopeResponse();
}
$location = $this->hcardService->process(request()->all());
return response()->json([
'response' => 'created',
'location' => $location,
], 201)->header('Location', $location);
}
if (request()->input('action') == 'update') {
if (stristr($tokenData->getClaim('scope'), 'update') === false) {
return $this->insufficientScopeResponse();
}
return $this->updateService->process(request()->all());
}
return response()->json([
'response' => 'error',
'error_description' => 'unsupported_request_type',
], 500);
}
/**
* A GET request has been made to `api/post` with an accompanying
* token, here we check wether the token is valid and respond
* appropriately. Further if the request has the query parameter
* synidicate-to we respond with the known syndication endpoints.
*
* @return \Illuminate\Http\Response
*/
public function get()
{
try {
$tokenData = $this->tokenService->validateToken(request()->bearerToken());
} catch (InvalidTokenException $e) {
return $this->invalidTokenResponse();
}
if (request()->input('q') === 'syndicate-to') {
return response()->json([
'syndicate-to' => config('syndication.targets'),
]);
}
if (request()->input('q') == 'config') {
return response()->json([
'syndicate-to' => config('syndication.targets'),
'media-endpoint' => route('media-endpoint'),
]);
}
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(new Point($matches[0][0], $matches[0][1]))->get();
return response()->json([
'response' => 'places',
'places' => $places,
]);
}
// default response is just to return the token data
return response()->json([
'response' => 'token',
'token' => [
'me' => $tokenData->getClaim('me'),
'scope' => $tokenData->getClaim('scope'),
'client_id' => $tokenData->getClaim('client_id'),
],
]);
}
/**
* Process a media item posted to the media endpoint.
*
* @return Illuminate\Http\Response
*/
public function media()
{
try {
$tokenData = $this->tokenService->validateToken(request()->bearerToken());
} catch (InvalidTokenException $e) {
return $this->invalidTokenResponse();
}
if ($tokenData->hasClaim('scope') === false) {
return $this->tokenHasNoScopeResponse();
}
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
return $this->insufficientScopeResponse();
}
if ((request()->hasFile('file') && request()->file('file')->isValid()) === false) {
return response()->json([
'response' => 'error',
'error' => 'invalid_request',
'error_description' => 'The uploaded file failed validation',
], 400);
}
$this->logMicropubRequest(request()->all());
$filename = $this->saveFile(request()->file('file'));
$manager = resolve(ImageManager::class);
try {
$image = $manager->make(request()->file('file'));
$width = $image->width();
} catch (NotReadableException $exception) {
// not an image
$width = null;
}
$media = Media::create([
'token' => request()->bearerToken(),
'path' => 'media/' . $filename,
'type' => $this->getFileTypeFromMimeType(request()->file('file')->getMimeType()),
'image_widths' => $width,
]);
ProcessMedia::dispatch($filename);
return response()->json([
'response' => 'created',
'location' => $media->url,
], 201)->header('Location', $media->url);
}
/**
* Get the file type from the mimetype of the uploaded file.
*
* @param string The mimetype
* @return string The type
*/
private function getFileTypeFromMimeType($mimetype)
{
//try known images
$imageMimeTypes = [
'image/gif',
'image/jpeg',
'image/png',
'image/svg+xml',
'image/tiff',
'image/webp',
];
if (in_array($mimetype, $imageMimeTypes)) {
return 'image';
}
//try known video
$videoMimeTypes = [
'video/mp4',
'video/mpeg',
'video/ogg',
'video/quicktime',
'video/webm',
];
if (in_array($mimetype, $videoMimeTypes)) {
return 'video';
}
//try known audio types
$audioMimeTypes = [
'audio/midi',
'audio/mpeg',
'audio/ogg',
'audio/x-m4a',
];
if (in_array($mimetype, $audioMimeTypes)) {
return 'audio';
}
return 'download';
}
private function getClientId(): string
{
return resolve(TokenService::class)
->validateToken(request()->input('access_token'))
->getClaim('client_id');
}
private function logMicropubRequest(array $request)
{
$logger = new Logger('micropub');
$logger->pushHandler(new StreamHandler(storage_path('logs/micropub.log')), Logger::DEBUG);
$logger->debug('MicropubLog', $request);
}
private function saveFile(UploadedFile $file)
{
$filename = Uuid::uuid4() . '.' . $file->extension();
Storage::disk('local')->put($filename, $file);
return $filename;
}
private function insufficientScopeResponse()
{
return response()->json([
'response' => 'error',
'error' => 'insufficient_scope',
'error_description' => 'The tokens scope does not have the necessary requirements.',
], 401);
}
private function invalidTokenResponse()
{
return response()->json([
'response' => 'error',
'error' => 'invalid_token',
'error_description' => 'The provided token did not pass validation',
], 400);
}
private function tokenHasNoScopeResponse()
{
return response()->json([
'response' => 'error',
'error' => 'invalid_request',
'error_description' => 'The provided token has no scopes',
], 400);
}
}