Squashed commit of the following:

commit 1eecb177b6d5aed8fd7ced14ba5a3fb8d528521b
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 16:19:29 2017 +0100

    Fix some Style issues

commit 490ca7b8e90b08ad1e59714e09ba49a853cb753e
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 16:06:13 2017 +0100

    Run check on POST as well GET methods

commit fb588798ce4e6548a0e0be8ec00673b24a41fe40
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 16:04:47 2017 +0100

    Check for exceptions when validating token

commit da2ce1a2893c03ae058c07361d860f4e509ac0ee
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 15:57:27 2017 +0100

    Go back to doing all dusk tests

commit c34fbf519a0cc528c49e8d5d9628a8891540c294
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 15:53:17 2017 +0100

    Don’t check for error message now

commit bd48859f7faf279296c0a15f2f6b18f83a951ebd
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 15:49:23 2017 +0100

    Escape the facade call with backslash

commit fcb405d8caf0d23cb70abb67da35cc68e2e86e4f
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 15:43:19 2017 +0100

    Regenerate the token within dusk

commit 8732f100a075742802447e65a2ba56b7cc69149a
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 15:36:39 2017 +0100

    Slight refactor of token verification

commit cb0ec39bba5765d55ad024fe3d73ad81877034da
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 15:29:46 2017 +0100

    Stupid forgetting to root the namespace

commit 1f2b309d7e77ff195ac8a4aa2f755044d15b4b92
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 15:22:54 2017 +0100

    Try throwing my own exception

commit 41dab30389cd04746051b8552e605eb3693ad0f1
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 15:12:18 2017 +0100

    Checked headers

commit 5eb12543adf999c4887a1e63b8d174fb7321202b
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 15:08:37 2017 +0100

    Don’t catch exceptions

commit 71dbf8d08a7eed9f3795f06811431b7a3c2ddd6c
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 15:00:22 2017 +0100

    Log headers for request

commit 1109a394456dd07375ccfab5a8436a902f3b27ce
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 14:54:19 2017 +0100

    Add laravels own logs to the artifacts

commit f6cd9ee81385bf4366e0e59fc6c34c9b56b6e52c
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 14:38:18 2017 +0100

    Log error

commit dadf3b3150c74d2199a87a529151d55fc1e67394
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 14:33:14 2017 +0100

    Look for different error message

commit 9df2d1b831e6c65283fb5438967f4517752c31ac
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 14:24:15 2017 +0100

    Look for different error message

commit 090736735fe3cc5fb4076b77730f7e39bc92d80c
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 14:14:14 2017 +0100

    Just checking error message

commit 6a766b1bc4af3dd0e335b3f57287086a6e528340
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 14:08:16 2017 +0100

    Look for error message

commit a399af496a440e30a0c0a1d4050121b3e491e968
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 14:00:29 2017 +0100

    Simpler setup of php error log

commit 3472c48ed50466c2d150e727cb6c1e43b1298d4c
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 13:45:11 2017 +0100

    Set the php error log

commit 978c17f33e2d9c3dd5166744aef924bf21483737
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 13:37:04 2017 +0100

    Set the php-fpm error log

commit 0e3407a4781f83bf3c516578a4594eff92b9d4d8
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 13:18:45 2017 +0100

    Save a token for the user

commit 951fe7966e18ac9e38526b6dfdc4a75fd4d68a0c
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 12:56:49 2017 +0100

    Use the right phrase

commit faedc802a46c35b3b9264348ebbd895f4d2b3895
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 12:41:03 2017 +0100

    Easier phrase to find in log

commit 71b3c905bb4be0943254e51604af1293cc63cd0f
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 12:35:07 2017 +0100

    Check we are logged in

commit fd6e3c79f13b9aada395a53b367e316936787298
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 12:20:14 2017 +0100

    Cleanup output

commit 231ac5868070f232f5bd455f0d6070621da9ae91
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 21 12:00:36 2017 +0100

    Try pausing test before running second check

commit 754cbafacdb8dd46db2eee699b8e23009c7d0bf5
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Apr 20 13:15:10 2017 +0100

    Temporary fix for TLS issues on travis-ci and sensiolab’s security checker

commit 8e03bba917ca9ba951bac5197e0008ae7188e19a
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Apr 19 21:24:51 2017 +0100

    Use less strict check on no auth endpoint being found

commit 91ee495ae1302f269febdd0e14ecbe81222b1014
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Apr 19 21:23:44 2017 +0100

    Use newer scope values in token

commit 989a242b4ff271f4ac6ee0873dc2924506c0efcf
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Apr 18 22:30:53 2017 +0100

    Missed some array syntax

commit 92d11c9a62c2a122b4fef62da8af6bf4be23288d
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Apr 18 22:21:43 2017 +0100

    Fix some styleci issues

commit 3a3aa8daca981561594cf3fb542c3e240b174013
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Apr 18 21:50:31 2017 +0100

    Allow client to send request with JSON syntax, cleanup a but of other code

commit dcc3baf2dc554175fdd1de408cb4f7170a6540d4
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Apr 18 21:47:51 2017 +0100

    composer update

commit b89dbb6b7a4940b5eed30727f0bd67bc382d98bc
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Apr 14 09:50:39 2017 +0100

    wip

commit 87eb63dc9570f6822324d665bc1658cfcb446554
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Apr 13 18:44:15 2017 +0100

    Allow the syntax for the endpoint to be set in the config page

commit 9595a0b68e4b51d77c18e63f9940026aa1325475
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Apr 13 17:17:34 2017 +0100

    Use better scope values in command

commit 08e2223a8d56984df826c1cd876f58b00cce8faf
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Apr 12 21:53:44 2017 +0100

    Get media uploads working again

commit 4c0d6861324c23afe4145c4ff9a46d8a1ecc10e9
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Apr 12 21:09:16 2017 +0100

    Show syndication targets properlu in client form and config page

commit 63939316a0818c8dfa8cba9919b231c2fdad5821
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Apr 12 21:08:40 2017 +0100

    Update prism, recompress assets

commit 74902237cb227e05386135fb0e2d5330b65e1c4e
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Apr 12 17:58:41 2017 +0100

    Better presentation of syndication targets

commit 345cda342051af406d6616a9162a75af625a64e0
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Apr 12 17:47:12 2017 +0100

    yarn upgrade

commit 17de68cc8f26cc472b009bf42942778fac75c890
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Apr 12 17:42:49 2017 +0100

    composer update

commit 7da78294dfdda8c68fc411ab03db65f101c7fc4c
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Apr 10 18:34:56 2017 +0100

    Move to using indieauth-client-php client directly, add code to get new tokens

commit 0020596b52c1590936d12ec9c458b7e70a06bc8e
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Apr 10 18:31:40 2017 +0100

    Add routes for retreiving a new token

commit ee0a6763f037629e2a97fa8536c84cbffbbbd7e2
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Apr 10 18:30:33 2017 +0100

    Show syndication targets as an unformated json string

commit 765d032fa883db834a005f61dc553b3b0ef9ee8e
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Apr 10 18:29:21 2017 +0100

    Add a migratin for the indieweb users

commit 3a5c458f132cf6c308c2e83eb57c61ae64cc2b48
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Apr 10 18:28:51 2017 +0100

    Add a normlize_url helper function

commit fb71bd6418e7903b7d50a90dd600a82a8af03d38
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Apr 10 18:27:41 2017 +0100

    Add a token to the users table

commit 56df9e8aa453394f8b83e8f6a28cc41c66d172f1
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Apr 3 09:18:06 2017 +0100

    Set default values for config screen

commit 0df8217a82cac91c3129acc492b73d6e8cb69b44
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Apr 3 09:17:14 2017 +0100

    Use the helper function to normalize URLs

commit d5f882972ec43e766ae7b9288b1ab3645dd471cf
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Apr 3 09:15:41 2017 +0100

    In a dev environment automatically log in as the pre-created user

commit 2c3379d0e560226a4db181e7d45ce02b575990ae
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Apr 3 09:14:13 2017 +0100

    Add a default user of me

commit 5c955803a8218c477e2f2b126811e6bed5f20379
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Apr 3 09:13:08 2017 +0100

    Add a helpers file, currently only has a function to normalize URLs

commit ae052d305c835952c83602d305cfdb08d5be975e
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Mar 31 16:08:30 2017 +0100

    Allow a user to register/login with his domain

commit 638ab8085f18c1bdf9c036c0272a8e88079013f5
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Mar 28 16:51:42 2017 +0100

    Work on allowing people to “register” for the client
This commit is contained in:
Jonny Barnes 2017-04-21 16:38:39 +01:00
parent 75a2ba74b3
commit f9a133e727
32 changed files with 1259 additions and 753 deletions

View file

@ -19,6 +19,7 @@ addons:
paths: paths:
- $(ls tests/Browser/screenshots/*.png | tr "\n" ":") - $(ls tests/Browser/screenshots/*.png | tr "\n" ":")
- $(ls tests/Browser/console/*.log | tr "\n" ":") - $(ls tests/Browser/console/*.log | tr "\n" ":")
- $(ls storage/logs/*.log | tr "\n" ":")
- $(ls /tmp/*.log | tr "\n" ":") - $(ls /tmp/*.log | tr "\n" ":")
services: services:
@ -49,16 +50,18 @@ install:
- travis/install-nginx.sh - travis/install-nginx.sh
before_script: before_script:
- echo 'error_log = "/tmp/php.error.log"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- psql -U travis -c 'create database travis_ci_test' - psql -U travis -c 'create database travis_ci_test'
- psql -U travis -d travis_ci_test -c 'create extension postgis' - psql -U travis -d travis_ci_test -c 'create extension postgis'
- cp .env.travis .env - cp .env.travis .env
- php artisan key:generate - php artisan key:generate
- php artisan migrate - php artisan migrate
- php artisan db:seed - php artisan db:seed
- php artisan token:generate
- phantomjs --webdriver=127.0.0.1:9515 --webdriver-loglevel=DEBUG & - phantomjs --webdriver=127.0.0.1:9515 --webdriver-loglevel=DEBUG &
- sleep 5 # Give artisan some time to start serving - sleep 5 # Give artisan some time to start serving
script: script:
- php vendor/bin/phpunit --coverage-text - php vendor/bin/phpunit --coverage-text
- php artisan dusk - php artisan dusk
- php artisan security:check - php vendor/bin/security-checker security:check ./composer.lock --end-point=http://security.sensiolabs.org/check_lock

View file

@ -2,9 +2,9 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\IndieWebUser;
use App\Services\TokenService; use App\Services\TokenService;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class GenerateToken extends Command class GenerateToken extends Command
{ {
@ -49,10 +49,12 @@ class GenerateToken extends Command
$data = [ $data = [
'me' => config('app.url'), 'me' => config('app.url'),
'client_id' => route('micropub-client'), 'client_id' => route('micropub-client'),
'scope' => 'post', 'scope' => 'create update',
]; ];
$token = $tokenService->getNewToken($data); $token = $tokenService->getNewToken($data);
Storage::disk('local')->put('dev-token', $token); $user = IndieWebUser::where('me', config('app.url'))->first();
$user->token = $token;
$user->save();
$this->info('Set token'); $this->info('Set token');
} }

View file

@ -2,16 +2,17 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\IndieWebUser;
use IndieAuth\Client;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Services\TokenService; use App\Services\TokenService;
use App\Services\IndieAuthService;
class IndieAuthController extends Controller class IndieAuthController extends Controller
{ {
/** /**
* This service isolates the IndieAuth Client code. * The IndieAuth Client.
*/ */
protected $indieAuthService; protected $client;
/** /**
* The Token handling service. * The Token handling service.
@ -21,15 +22,15 @@ class IndieAuthController extends Controller
/** /**
* Inject the dependencies. * Inject the dependencies.
* *
* @param \App\Services\IndieAuthService $indieAuthService * @param \IndieAuth\Client $client
* @param \App\Services\TokenService $tokenService * @param \App\Services\TokenService $tokenService
* @return void * @return void
*/ */
public function __construct( public function __construct(
IndieAuthService $indieAuthService = null, Client $client = null,
TokenService $tokenService = null TokenService $tokenService = null
) { ) {
$this->indieAuthService = $indieAuthService ?? new IndieAuthService(); $this->client = $client ?? new Client();
$this->tokenService = $tokenService ?? new TokenService(); $this->tokenService = $tokenService ?? new TokenService();
} }
@ -44,25 +45,31 @@ class IndieAuthController extends Controller
*/ */
public function start(Request $request) public function start(Request $request)
{ {
$authorizationEndpoint = $this->indieAuthService->getAuthorizationEndpoint( $url = normalize_url($request->input('me'));
$request->input('me') $authorizationEndpoint = $this->client->discoverAuthorizationEndpoint($url);
); if ($authorizationEndpoint != null) {
if ($authorizationEndpoint !== null) { $state = bin2hex(openssl_random_pseudo_bytes(16));
$authorizationURL = $this->indieAuthService->buildAuthorizationURL( session(['state' => $state]);
$authorizationURL = $this->client->buildAuthorizationURL(
$authorizationEndpoint, $authorizationEndpoint,
$request->input('me') $url,
route('indieauth-callback'), //redirect_uri
route('micropub-client'), //client_id
$state
); );
if ($authorizationURL) { if ($authorizationURL) {
return redirect($authorizationURL); return redirect($authorizationURL);
} }
return redirect(route('micropub-client'))->with('error', 'Error building authorization URL');
} }
return redirect(route('micropub-client'))->with('error', 'Unable to determine authorisation endpoint'); return redirect(route('micropub-client'))->with('error', 'Unable to determine authorisation endpoint');
} }
/** /**
* Once they have verified themselves through the authorisation endpint * Once they have verified themselves through the authorisation endpoint
* the next step is retreiveing a token from the token endpoint. * the next step is register/login the user.
* *
* @param \Illuminate\Http\Rrequest $request * @param \Illuminate\Http\Rrequest $request
* @return \Illuminate\Routing\RedirectResponse redirect * @return \Illuminate\Routing\RedirectResponse redirect
@ -75,6 +82,15 @@ class IndieAuthController extends Controller
'Invalid <code>state</code> value returned from indieauth server' 'Invalid <code>state</code> value returned from indieauth server'
); );
} }
$url = normalize_url($request->input('me'));
$indiewebUser = IndieWebUser::firstOrCreate(['me' => $url]);
$request->session()->put(['me' => $url]);
return redirect(route('micropub-client'));
}
/*
$tokenEndpoint = $this->indieAuthService->getTokenEndpoint($request->input('me')); $tokenEndpoint = $this->indieAuthService->getTokenEndpoint($request->input('me'));
if ($tokenEndpoint === false) { if ($tokenEndpoint === false) {
return redirect(route('micropub-client'))->with( return redirect(route('micropub-client'))->with(
@ -103,10 +119,10 @@ class IndieAuthController extends Controller
'error', 'error',
'Unable to get a token from the endpoint' 'Unable to get a token from the endpoint'
); );
} */
/** /**
* Log out the user, flush an session data, and overwrite any cookie data. * Log out the user, flush the session data.
* *
* @return \Illuminate\Routing\RedirectResponse redirect * @return \Illuminate\Routing\RedirectResponse redirect
*/ */
@ -114,7 +130,7 @@ class IndieAuthController extends Controller
{ {
$request->session()->flush(); $request->session()->flush();
return redirect(route('micropub-client'))->cookie('me', 'loggedout', 1); return redirect(route('micropub-client'));
} }
/** /**

View file

@ -2,7 +2,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Services\IndieAuthService; use App\IndieWebUser;
use IndieAuth\Client as IndieClient; use IndieAuth\Client as IndieClient;
use GuzzleHttp\Client as GuzzleClient; use GuzzleHttp\Client as GuzzleClient;
use Illuminate\Http\{Request, Response}; use Illuminate\Http\{Request, Response};
@ -10,20 +10,13 @@ use GuzzleHttp\Exception\{ClientException, ServerException};
class MicropubClientController extends Controller class MicropubClientController extends Controller
{ {
/**
* The IndieAuth service container.
*/
protected $indieAuthService;
/** /**
* Inject the dependencies. * Inject the dependencies.
*/ */
public function __construct( public function __construct(
IndieAuthService $indieAuthService = null,
IndieClient $indieClient = null, IndieClient $indieClient = null,
GuzzleClient $guzzleClient = null GuzzleClient $guzzleClient = null
) { ) {
$this->indieAuthService = $indieAuthService ?? new IndieAuthService();
$this->guzzleClient = $guzzleClient ?? new GuzzleClient(); $this->guzzleClient = $guzzleClient ?? new GuzzleClient();
$this->indieClient = $indieClient ?? new IndieClient(); $this->indieClient = $indieClient ?? new IndieClient();
} }
@ -37,8 +30,11 @@ class MicropubClientController extends Controller
public function create(Request $request) public function create(Request $request)
{ {
$url = $request->session()->get('me'); $url = $request->session()->get('me');
$syndication = $request->session()->get('syndication'); if ($url) {
$mediaEndpoint = $request->session()->get('media-endpoint'); $indiewebUser = IndieWebUser::where('me', $url)->first();
}
$syndication = $this->parseSyndicationTargets($indiewebUser->syndication);
$mediaEndpoint = $indiewebUser->mediaEndpoint ?? null;
$mediaURLs = $request->session()->get('media-links'); $mediaURLs = $request->session()->get('media-links');
return view('micropub.create', compact('url', 'syndication', 'mediaEndpoint', 'mediaURLs')); return view('micropub.create', compact('url', 'syndication', 'mediaEndpoint', 'mediaURLs'));
@ -56,19 +52,17 @@ class MicropubClientController extends Controller
return back(); return back();
} }
$mediaEndpoint = $request->session()->get('media-endpoint'); $user = IndieWebUser::where('me', $request->session()->get('me'))->firstOrFail();
if ($mediaEndpoint == null) { if ($user->mediaEndpoint == null || $user->token == null) {
return back(); return back();
} }
$token = $request->session()->get('token');
$mediaURLs = []; $mediaURLs = [];
foreach ($request->file('file') as $file) { foreach ($request->file('file') as $file) {
try { try {
$response = $this->guzzleClient->request('POST', $mediaEndpoint, [ $response = $this->guzzleClient->request('POST', $user->mediaEndpoint, [
'headers' => [ 'headers' => [
'Authorization' => 'Bearer ' . $token, 'Authorization' => 'Bearer ' . $user->token,
], ],
'multipart' => [ 'multipart' => [
[ [
@ -109,93 +103,23 @@ class MicropubClientController extends Controller
*/ */
public function store(Request $request) public function store(Request $request)
{ {
$domain = $request->session()->get('me'); $url = normalize_url($request->session()->get('me'));
$token = $request->session()->get('token'); $user = IndieWebUser::where('me', $url)->firstOrFail();
$micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint( if ($user->token == null) {
$domain, return redirect(route('micropub-client'))->with('error', 'You havent requested a token yet');
$this->indieClient }
);
$micropubEndpoint = $this->indieClient->discoverMicropubEndpoint($url);
if (! $micropubEndpoint) { if (! $micropubEndpoint) {
return redirect(route('micropub-client'))->with('error', 'Unable to determine micropub API endpoint'); return redirect(route('micropub-client'))->with('error', 'Unable to determine micropub API endpoint');
} }
$response = $this->postNoteRequest($request, $micropubEndpoint, $token); $headers = [
'Authorization' => 'Bearer ' . $user->token,
];
if ($response->getStatusCode() == 201) { if ($user->syntax == 'html') {
$request->session()->forget('media-links');
$location = $response->getHeader('Location');
if (is_array($location)) {
return redirect($location[0]);
}
return redirect($location);
}
return redirect(route('micropub-client'))->with('error', 'Endpoint didnt create the note.');
}
/**
* Show currently stored configuration values.
*
* @param Illuminate\Http\Request $request
* @return view
*/
public function config(Request $request)
{
$data['me'] = $request->session()->get('me');
$data['token'] = $request->session()->get('token');
$data['syndication'] = $request->session()->get('syndication') ?? 'none defined';
$data['media-endpoint'] = $request->session()->get('media-endpoint') ?? 'none defined';
return view('micropub.config', compact('data'));
}
/**
* Query the micropub endpoint and store response in the session.
*
* @param Illuminate\Http\Request $request
* @return redirect
*/
public function queryEndpoint(Request $request)
{
$domain = $request->session()->get('me');
$token = $request->session()->get('token');
$micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain);
if ($micropubEndpoint !== null) {
try {
$response = $this->guzzleClient->get($micropubEndpoint, [
'headers' => ['Authorization' => 'Bearer ' . $token],
'query' => 'q=config',
]);
} catch (ClientException | ServerException $e) {
return back();
}
$body = (string) $response->getBody();
$syndication = $this->parseSyndicationTargets($body);
$request->session()->put('syndication', $syndication);
$mediaEndpoint = $this->parseMediaEndpoint($body);
$request->session()->put('media-endpoint', $mediaEndpoint);
return back();
}
}
/**
* This method performs the actual POST request.
*
* @param \Illuminate\Http\Request $request
* @param string The Micropub endpoint to post to
* @param string The token to authenticate the request with
* @return \GuzzleHttp\Response $response | \Illuminate\RedirectFactory redirect
*/
private function postNoteRequest(
Request $request,
$micropubEndpoint,
$token
) {
$multipart = [ $multipart = [
[ [
'name' => 'h', 'name' => 'h',
@ -246,9 +170,6 @@ class MicropubClientController extends Controller
]; ];
} }
} }
$headers = [
'Authorization' => 'Bearer ' . $token,
];
try { try {
$response = $this->guzzleClient->post($micropubEndpoint, [ $response = $this->guzzleClient->post($micropubEndpoint, [
'multipart' => $multipart, 'multipart' => $multipart,
@ -261,7 +182,208 @@ class MicropubClientController extends Controller
); );
} }
return $response; if ($response->getStatusCode() == 201) {
$request->session()->forget('media-links');
$location = $response->getHeader('Location');
if (is_array($location)) {
return redirect($location[0]);
}
return redirect($location);
}
}
if ($user->syntax == 'json') {
$json = [];
$json['type'] = ['h-entry'];
$json['properties'] = ['content' => [$request->input('content')]];
if ($request->input('in-reply-to') != '') {
$json['properties']['in-reply-to'] = [$request->input('in-reply-to')];
}
if ($request->input('mp-syndicate-to')) {
foreach ($request->input('mp-syndicate-to') as $syn) {
$json['properties']['mp-syndicate-to'] = [$syn];
}
}
if ($request->input('location')) {
if ($request->input('location') !== 'no-location') {
$json['properties']['location'] = [$request->input('location')];
}
}
if ($request->input('media')) {
$json['properties']['photo'] = [];
foreach ($request->input('media') as $media) {
$json['properties']['photo'][] = $media;
}
}
try {
$response = $this->guzzleClient->post($micropubEndpoint, [
'json' => $json,
'headers' => $headers,
]);
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
return redirect(route('micropub-client'))->with(
'error',
'There was a bad response from the micropub endpoint.'
);
}
if ($response->getStatusCode() == 201) {
$request->session()->forget('media-links');
$location = $response->getHeader('Location');
if (is_array($location)) {
return redirect($location[0]);
}
return redirect($location);
}
}
return redirect(route('micropub-client'))->with('error', 'Endpoint didnt create the note.');
}
/**
* Show currently stored configuration values.
*
* @param Illuminate\Http\Request $request
* @return view
*/
public function config(Request $request)
{
//default values
$data = [
'me' => '',
'token' => 'none',
'syndication' => 'none defined',
'media-endpoint' => 'none defined',
'syntax' => 'html',
];
if ($request->session()->has('me')) {
$data['me'] = normalize_url($request->session()->get('me'));
$user = IndieWebUser::where('me', $request->session()->get('me'))->first();
$data['token'] = $user->token ?? 'none defined';
$data['syndication'] = $user->syndication ?? 'none defined';
$data['media-endpoint'] = $user->mediaEndpoint ?? 'none defined';
$data['syntax'] = $user->syntax;
}
return view('micropub.config', compact('data'));
}
/**
* Get a new token.
*
* @param Illuminate\Http\Request $request
* @return view
*/
public function getNewToken(Request $request)
{
if ($request->session()->has('me')) {
$url = normalize_url($request->session()->get('me'));
$authozationEndpoint = $this->indieClient->discoverAuthorizationEndpoint($url);
if ($authozationEndpoint) {
$state = bin2hex(random_bytes(16));
$request->session()->put('state', $state);
$authorizationURL = $this->indieClient->buildAuthorizationURL(
$authozationEndpoint,
$url,
route('micropub-client-get-new-token-callback'), // redirect_uri
route('micropub-client'), //client_id
$state,
'create update' // scope needs to be a setting
);
return redirect($authorizationURL);
}
return back();
}
return back();
}
/**
* The callback for getting a token.
*/
public function getNewTokenCallback(Request $request)
{
if ($request->input('state') !== $request->session()->get('state')) {
return route('micropub-client')->with('error', 'The <code>state</code> didnt match.');
}
$tokenEndpoint = $this->indieClient->discoverTokenEndpoint(normalize_url($request->input('me')));
if ($tokenEndpoint) {
$token = $this->indieClient->getAccessToken(
$tokenEndpoint,
$request->input('code'),
$request->input('me'),
route('micropub-client-get-new-token-callback'), // redirect_uri
route('micropub-client'), // client_id
$request->input('state')
);
if (array_key_exists('access_token', $token)) {
$url = normalize_url($token['me']);
$user = IndieWebUser::where('me', $url)->firstOrFail();
$user->token = $token['access_token'];
$user->save();
return redirect('micropub-config');
}
}
}
/**
* Query the micropub endpoint and store response.
*
* @param Illuminate\Http\Request $request
* @return redirect
*/
public function queryEndpoint(Request $request)
{
$url = normalize_url($request->session()->get('me'));
$user = IndieWebUser::where('me', $url)->firstOrFail();
$token = $user->token;
$micropubEndpoint = $this->indieClient->discoverMicropubEndpoint($url);
if ($micropubEndpoint) {
try {
$response = $this->guzzleClient->get($micropubEndpoint, [
'headers' => ['Authorization' => 'Bearer ' . $token],
'query' => 'q=config',
]);
} catch (ClientException | ServerException $e) {
return back();
}
$body = (string) $response->getBody();
$data = json_decode($body, true);
if (array_key_exists('syndicate-to', $data)) {
$user->syndication = json_encode($data['syndicate-to']);
}
if (array_key_exists('media-endpoint', $data)) {
$user->mediaEndpoint = $data['media-endpoint'];
}
$user->save();
return back();
}
}
/**
* Update the syntax setting.
*
* @param Illuminate\Http\Request $request
* @return Illuminate\Http\RedirectResponse
* @todo validate input
*/
public function updateSyntax(Request $request)
{
$user = IndieWebUser::where('me', $request->session()->get('me'))->firstOrFail();
$user->syntax = $request->syntax;
$user->save();
return redirect(route('micropub-config'));
} }
/** /**
@ -272,16 +394,17 @@ class MicropubClientController extends Controller
*/ */
public function newPlace(Request $request) public function newPlace(Request $request)
{ {
if ($request->session()->has('token') === false) { $url = normalize_url($request->session()->get('me'));
$user = IndieWebUser::where('me', $url)->firstOrFail();
if ($user->token === null) {
return response()->json([ return response()->json([
'error' => true, 'error' => true,
'error_description' => 'No known token', 'error_description' => 'No known token',
], 400); ], 400);
} }
$domain = $request->session()->get('me');
$token = $request->session()->get('token');
$micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain, $this->indieClient); $micropubEndpoint = $this->indieClient->discoverMicropubEndpoint($url);
if (! $micropubEndpoint) { if (! $micropubEndpoint) {
return response()->json([ return response()->json([
'error' => true, 'error' => true,
@ -289,13 +412,27 @@ class MicropubClientController extends Controller
], 400); ], 400);
} }
$place = $this->postPlaceRequest($request, $micropubEndpoint, $token); $formParams = [
if ($place === false) { 'h' => 'card',
'name' => $request->input('place-name'),
'description' => $request->input('place-description'),
'geo' => 'geo:' . $request->input('place-latitude') . ',' . $request->input('place-longitude'),
];
$headers = [
'Authorization' => 'Bearer ' . $user->token,
];
try {
$response = $this->guzzleClient->request('POST', $micropubEndpoint, [
'form_params' => $formParams,
'headers' => $headers,
]);
} catch (ClientException $e) {
return response()->json([ return response()->json([
'error' => true, 'error' => true,
'error_description' => 'Unable to create the new place', 'error_description' => 'Unable to create the new place',
], 400); ], 400);
} }
$place = $response->getHeader('Location')[0];
return response()->json([ return response()->json([
'uri' => $place, 'uri' => $place,
@ -305,44 +442,6 @@ class MicropubClientController extends Controller
]); ]);
} }
/**
* Actually make a micropub request to make a new place.
*
* @param \Illuminate\Http\Request $request
* @param string The Micropub endpoint to post to
* @param string The token to authenticate the request with
* @param \GuzzleHttp\Client $client
* @return \GuzzleHttp\Response $response | \Illuminate\RedirectFactory redirect
*/
private function postPlaceRequest(
Request $request,
$micropubEndpoint,
$token
) {
$formParams = [
'h' => 'card',
'name' => $request->input('place-name'),
'description' => $request->input('place-description'),
'geo' => 'geo:' . $request->input('place-latitude') . ',' . $request->input('place-longitude'),
];
$headers = [
'Authorization' => 'Bearer ' . $token,
];
try {
$response = $this->guzzleClient->request('POST', $micropubEndpoint, [
'form_params' => $formParams,
'headers' => $headers,
]);
} catch (ClientException $e) {
return false;
}
if ($response->getStatusCode() == 201) {
return $response->getHeader('Location')[0];
}
return false;
}
/** /**
* Make a request to the micropub endpoint requesting any nearby places. * Make a request to the micropub endpoint requesting any nearby places.
* *
@ -351,16 +450,17 @@ class MicropubClientController extends Controller
*/ */
public function nearbyPlaces(Request $request) public function nearbyPlaces(Request $request)
{ {
if ($request->session()->has('token') === false) { $url = normalize_url($request->session()->get('me'));
$user = IndieWebUser::where('me', $url)->firstOrFail();
if ($user->token === null) {
return response()->json([ return response()->json([
'error' => true, 'error' => true,
'error_description' => 'No known token', 'error_description' => 'No known token',
], 400); ], 400);
} }
$domain = $request->session()->get('me');
$token = $request->session()->get('token');
$micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain, $this->indieClient); $micropubEndpoint = $this->indieClient->discoverMicropubEndpoint($url);
if (! $micropubEndpoint) { if (! $micropubEndpoint) {
return response()->json([ return response()->json([
@ -375,7 +475,7 @@ class MicropubClientController extends Controller
$query .= ';u=' . $request->input('u'); $query .= ';u=' . $request->input('u');
} }
$response = $this->guzzleClient->get($micropubEndpoint, [ $response = $this->guzzleClient->get($micropubEndpoint, [
'headers' => ['Authorization' => 'Bearer ' . $token], 'headers' => ['Authorization' => 'Bearer ' . $user->token],
'query' => ['q' => $query], 'query' => ['q' => $query],
]); ]);
} catch (\GuzzleHttp\Exception\BadResponseException $e) { } catch (\GuzzleHttp\Exception\BadResponseException $e) {
@ -390,31 +490,35 @@ class MicropubClientController extends Controller
} }
/** /**
* Parse the syndication targets retreived from a cookie, to a form that can * Parse the syndication targets JSON into a an array.
* be used in a view.
* *
* @param string $syndicationTargets * @param string|null
* @return array|null * @return array|null
*/ */
private function parseSyndicationTargets($syndicationTargets = null) private function parseSyndicationTargets($syndicationTargets = null)
{ {
if ($syndicationTargets === null) { if ($syndicationTargets === null || $syndicationTargets === '') {
return; return;
} }
$syndicateTo = []; $syndicateTo = [];
$data = json_decode($syndicationTargets, true); $data = json_decode($syndicationTargets, true);
if (array_key_exists('syndicate-to', $data)) { if (array_key_exists('uid', $data)) {
foreach ($data['syndicate-to'] as $syn) { $syndicateTo[] = [
'target' => $data['uid'],
'name' => $data['name'],
];
}
foreach ($data as $syn) {
if (array_key_exists('uid', $syn)) {
$syndicateTo[] = [ $syndicateTo[] = [
'target' => $syn['uid'], 'target' => $syn['uid'],
'name' => $syn['name'], 'name' => $syn['name'],
]; ];
} }
} }
if (count($syndicateTo) > 0) {
return $syndicateTo; return $syndicateTo;
} }
}
/** /**
* Parse the media-endpoint retrieved from querying a micropub endpoint. * Parse the media-endpoint retrieved from querying a micropub endpoint.

View file

@ -47,10 +47,18 @@ class MicropubController extends Controller
*/ */
public function post(Request $request) public function post(Request $request)
{ {
try {
$tokenData = $this->tokenService->validateToken($request->bearerToken()); $tokenData = $this->tokenService->validateToken($request->bearerToken());
} catch (\Exception $e) {
return response()->json([
'response' => 'error',
'error' => 'invalid_token',
'error_description' => 'The provided token did not pass validation',
], 400);
}
if ($tokenData->hasClaim('scope')) { if ($tokenData->hasClaim('scope')) {
$scopes = explode(' ', $tokenData->getClaim('scope')); $scopes = explode(' ', $tokenData->getClaim('scope'));
if (array_search('post', $scopes) !== false) { if (array_search('create', $scopes) !== false) {
$clientId = $tokenData->getClaim('client_id'); $clientId = $tokenData->getClaim('client_id');
if (($request->input('h') == 'entry') || ($request->input('type')[0] == 'h-entry')) { if (($request->input('h') == 'entry') || ($request->input('type')[0] == 'h-entry')) {
$data = []; $data = [];
@ -162,8 +170,9 @@ class MicropubController extends Controller
*/ */
public function get(Request $request) public function get(Request $request)
{ {
try {
$tokenData = $this->tokenService->validateToken($request->bearerToken()); $tokenData = $this->tokenService->validateToken($request->bearerToken());
if ($tokenData === null) { } catch (\Exception $e) {
return response()->json([ return response()->json([
'response' => 'error', 'response' => 'error',
'error' => 'invalid_token', 'error' => 'invalid_token',
@ -258,7 +267,7 @@ class MicropubController extends Controller
], 503); ], 503);
} }
$media = new Media(); $media = new Media();
$media->token = $token; $media->token = $request->bearerToken();
$media->path = $path; $media->path = $path;
$media->type = $type; $media->type = $type;
$media->save(); $media->save();

View file

@ -34,7 +34,8 @@ class Kernel extends HttpKernel
\App\Http\Middleware\VerifyCsrfToken::class, \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\LinkHeadersMiddleware::class, \App\Http\Middleware\LinkHeadersMiddleware::class,
\App\Http\Middleware\DevTokenMiddleware::class, //\App\Http\Middleware\DevTokenMiddleware::class,
\App\Http\Middleware\LocalhostSessionMiddleware::class,
], ],
'api' => [ 'api' => [

View file

@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use Closure;
class LocalhostSessionMiddleware
{
/**
* Whilst we are developing locally, automatically log in as
* `['me' => config('app.url')]` as I cant manually log in as
* a .localhost domain.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (config('app.env') !== 'production') {
session(['me' => config('app.url')]);
}
return $next($request);
}
}

15
app/IndieWebUser.php Normal file
View file

@ -0,0 +1,15 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class IndieWebUser extends Model
{
/**
* Mass assignment protection.
*
* @var array
*/
protected $fillable = ['me'];
}

View file

@ -45,14 +45,12 @@ class IndieAuthService
session(['state' => $state]); session(['state' => $state]);
$redirectURL = route('indieauth-callback'); $redirectURL = route('indieauth-callback');
$clientId = route('micropub-client'); $clientId = route('micropub-client');
$scope = 'post';
$authorizationURL = $this->client->buildAuthorizationURL( $authorizationURL = $this->client->buildAuthorizationURL(
$authEndpoint, $authEndpoint,
$this->client->normalizeMeURL($domain), $this->client->normalizeMeURL($domain),
$redirectURL, $redirectURL,
$clientId, $clientId,
$state, $state
$scope
); );
return $authorizationURL; return $authorizationURL;

View file

@ -4,11 +4,9 @@ declare(strict_types=1);
namespace App\Services; namespace App\Services;
use RuntimeException;
use Lcobucci\JWT\Token; use Lcobucci\JWT\Token;
use Lcobucci\JWT\Parser; use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Builder; use Lcobucci\JWT\Builder;
use InvalidArgumentException;
use Lcobucci\JWT\Signer\Hmac\Sha256; use Lcobucci\JWT\Signer\Hmac\Sha256;
class TokenService class TokenService
@ -39,17 +37,14 @@ class TokenService
* @param string The token * @param string The token
* @return mixed * @return mixed
*/ */
public function validateToken(string $token): ?Token public function validateToken(string $bearerToken): ?Token
{ {
$signer = new Sha256(); $signer = new Sha256();
try { $token = (new Parser())->parse((string) $bearerToken);
$token = (new Parser())->parse((string) $token); if (! $token->verify($signer, config('app.key'))) {
} catch (InvalidArgumentException | RuntimeException $e) { throw new \Exception('Token not verified');
return null;
} }
if ($token->verify($signer, config('app.key'))) {
//signuture valid
return $token; return $token;
} }
} }
}

191
app/helpers.php Normal file
View file

@ -0,0 +1,191 @@
<?php
declare(strict_types=1);
/*
helpers.php
*/
// sourced from https://github.com/flattr/normalize-url/blob/master/normalize_url.php
function normalize_url(?string $url): ?string
{
if ($url === null) {
return null;
}
$newUrl = '';
$url = parse_url($url);
$defaultSchemes = ['http' => 80, 'https' => 443];
if (isset($url['scheme'])) {
$url['scheme'] = strtolower($url['scheme']);
// Strip scheme default ports
if (
isset($defaultSchemes[$url['scheme']]) &&
isset($url['port']) &&
$defaultSchemes[$url['scheme']] == $url['port']
) {
unset($url['port']);
}
$newUrl .= "{$url['scheme']}://";
}
if (isset($url['host'])) {
$url['host'] = mb_strtolower($url['host']);
$newUrl .= $url['host'];
}
if (isset($url['port'])) {
$newUrl .= ":{$url['port']}";
}
// here we only want to drop a slash for the root domain
// e.g. http://example.com/ -> http://example.com
// but http://example.com/path/ -/-> http://example.com/path
if (isset($url['path']) && $url['path'] == '/') {
unset($url['path']);
}
if (isset($url['path'])) {
// Case normalization
$url['path'] = normalizer_normalize($url['path'], Normalizer::FORM_C);
// Strip duplicate slashes
while (preg_match("/\/\//", $url['path'])) {
$url['path'] = preg_replace('/\/\//', '/', $url['path']);
}
/*
* Decode unreserved characters, http://www.apps.ietf.org/rfc/rfc3986.html#sec-2.3
* Heavily rewritten version of urlDecodeUnreservedChars() in Glen Scott's url-normalizer.
*/
$u = [];
for ($o = 65; $o <= 90; $o++) {
$u[] = dechex($o);
}
for ($o = 97; $o <= 122; $o++) {
$u[] = dechex($o);
}
for ($o = 48; $o <= 57; $o++) {
$u[] = dechex($o);
}
$chrs = ['-', '.', '_', '~'];
foreach ($chrs as $chr) {
$u[] = dechex(ord($chr));
}
$url['path'] = preg_replace_callback(
array_map(
create_function('$str', 'return "/%" . strtoupper($str) . "/x";'),
$u
),
create_function('$matches', 'return chr(hexdec($matches[0]));'),
$url['path']
);
// Remove directory index
$defaultIndexes = ["/default\.aspx/" => 'default.aspx', "/default\.asp/" => 'default.asp',
"/index\.html/" => 'index.html', "/index\.htm/" => 'index.htm',
"/default\.html/" => 'default.html', "/default\.htm/" => 'default.htm',
"/index\.php/" => 'index.php', "/index\.jsp/" => 'index.jsp', ];
foreach ($defaultIndexes as $index => $strip) {
if (preg_match($index, $url['path'])) {
$url['path'] = str_replace($strip, '', $url['path']);
}
}
/**
* Path segment normalization, http://www.apps.ietf.org/rfc/rfc3986.html#sec-5.2.4
* Heavily rewritten version of removeDotSegments() in Glen Scott's url-normalizer.
*/
$new_path = '';
while (! empty($url['path'])) {
if (preg_match('!^(\.\./|\./)!x', $url['path'])) {
$url['path'] = preg_replace('!^(\.\./|\./)!x', '', $url['path']);
} elseif (preg_match('!^(/\./)!x', $url['path'], $matches) || preg_match('!^(/\.)$!x', $url['path'], $matches)) {
$url['path'] = preg_replace('!^' . $matches[1] . '!', '/', $url['path']);
} elseif (preg_match('!^(/\.\./|/\.\.)!x', $url['path'], $matches)) {
$url['path'] = preg_replace('!^' . preg_quote($matches[1], '!') . '!x', '/', $url['path']);
$new_path = preg_replace('!/([^/]+)$!x', '', $new_path);
} elseif (preg_match('!^(\.|\.\.)$!x', $url['path'])) {
$url['path'] = preg_replace('!^(\.|\.\.)$!x', '', $url['path']);
} else {
if (preg_match('!(/*[^/]*)!x', $url['path'], $matches)) {
$first_path_segment = $matches[1];
$url['path'] = preg_replace('/^' . preg_quote($first_path_segment, '/') . '/', '', $url['path'], 1);
$new_path .= $first_path_segment;
}
}
}
$newUrl .= $new_path;
}
if (isset($url['fragment'])) {
unset($url['fragment']);
}
// Sort GET params alphabetically, not because the RFC requires it but because it's cool!
if (isset($url['query'])) {
if (preg_match('/&/', $url['query'])) {
$s = explode('&', $url['query']);
$url['query'] = '';
sort($s);
foreach ($s as $z) {
$url['query'] .= "{$z}&";
}
$url['query'] = preg_replace('/&\Z/', '', $url['query']);
}
$newUrl .= "?{$url['query']}";
}
return $newUrl;
}
// sourced from https://stackoverflow.com/a/9776726
function prettyPrintJson(string $json): string
{
$result = '';
$level = 0;
$in_quotes = false;
$in_escape = false;
$ends_line_level = null;
$json_length = strlen($json);
for ($i = 0; $i < $json_length; $i++) {
$char = $json[$i];
$new_line_level = null;
$post = '';
if ($ends_line_level !== null) {
$new_line_level = $ends_line_level;
$ends_line_level = null;
}
if ($in_escape) {
$in_escape = false;
} elseif ($char === '"') {
$in_quotes = ! $in_quotes;
} elseif (! $in_quotes) {
switch ($char) {
case '}': case ']':
$level--;
$ends_line_level = null;
$new_line_level = $level;
break;
case '{': case '[':
$level++;
case ',':
$ends_line_level = $level;
break;
case ':':
$post = ' ';
break;
case ' ': case "\t": case "\n": case "\r":
$char = '';
$ends_line_level = $new_line_level;
$new_line_level = null;
break;
}
} elseif ($char === '\\') {
$in_escape = true;
}
if ($new_line_level !== null) {
$result .= "\n".str_repeat("\t", $new_line_level);
}
$result .= $char.$post;
}
return str_replace("\t", ' ', $result);
}

View file

@ -42,7 +42,10 @@
], ],
"psr-4": { "psr-4": {
"App\\": "app/" "App\\": "app/"
} },
"files": [
"app/helpers.php"
]
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {

264
composer.lock generated
View file

@ -8,16 +8,16 @@
"packages": [ "packages": [
{ {
"name": "aws/aws-sdk-php", "name": "aws/aws-sdk-php",
"version": "3.24.7", "version": "3.25.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/aws/aws-sdk-php.git", "url": "https://github.com/aws/aws-sdk-php.git",
"reference": "f062d7ea2123fe2aefef91da855c10ef8ff3af1c" "reference": "d4f1104f5ac9c755875c5e6e9bade2c70708219a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f062d7ea2123fe2aefef91da855c10ef8ff3af1c", "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d4f1104f5ac9c755875c5e6e9bade2c70708219a",
"reference": "f062d7ea2123fe2aefef91da855c10ef8ff3af1c", "reference": "d4f1104f5ac9c755875c5e6e9bade2c70708219a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -84,7 +84,7 @@
"s3", "s3",
"sdk" "sdk"
], ],
"time": "2017-03-23T22:17:20+00:00" "time": "2017-04-11T22:31:27+00:00"
}, },
{ {
"name": "barnabywalters/mf-cleaner", "name": "barnabywalters/mf-cleaner",
@ -180,6 +180,65 @@
], ],
"time": "2016-08-19T16:43:44+00:00" "time": "2016-08-19T16:43:44+00:00"
}, },
{
"name": "composer/ca-bundle",
"version": "1.0.7",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
"reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/b17e6153cb7f33c7e44eb59578dc12eee5dc8e12",
"reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"ext-pcre": "*",
"php": "^5.3.2 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.5",
"psr/log": "^1.0",
"symfony/process": "^2.5 || ^3.0"
},
"suggest": {
"symfony/process": "This is necessary to reliably check whether openssl_x509_parse is vulnerable on older php versions, but can be ignored on PHP 5.5.6+"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\CaBundle\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
"keywords": [
"cabundle",
"cacert",
"certificate",
"ssl",
"tls"
],
"time": "2017-03-06T11:59:08+00:00"
},
{ {
"name": "dnoegel/php-xdg-base-dir", "name": "dnoegel/php-xdg-base-dir",
"version": "0.1", "version": "0.1",
@ -685,16 +744,16 @@
}, },
{ {
"name": "erusev/parsedown", "name": "erusev/parsedown",
"version": "1.6.1", "version": "1.6.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/erusev/parsedown.git", "url": "https://github.com/erusev/parsedown.git",
"reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb" "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/20ff8bbb57205368b4b42d094642a3e52dac85fb", "url": "https://api.github.com/repos/erusev/parsedown/zipball/1bf24f7334fe16c88bf9d467863309ceaf285b01",
"reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb", "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -723,7 +782,7 @@
"markdown", "markdown",
"parser" "parser"
], ],
"time": "2016-11-02T15:56:58+00:00" "time": "2017-03-29T16:04:15+00:00"
}, },
{ {
"name": "ezyang/htmlpurifier", "name": "ezyang/htmlpurifier",
@ -1405,16 +1464,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v5.4.16", "version": "v5.4.19",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "6cf379ec34d08bcdc9c7183e369a8fdf04ade80d" "reference": "02444b7450350db17a7607c8a52f7268ebdb0dad"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/6cf379ec34d08bcdc9c7183e369a8fdf04ade80d", "url": "https://api.github.com/repos/laravel/framework/zipball/02444b7450350db17a7607c8a52f7268ebdb0dad",
"reference": "6cf379ec34d08bcdc9c7183e369a8fdf04ade80d", "reference": "02444b7450350db17a7607c8a52f7268ebdb0dad",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1530,20 +1589,20 @@
"framework", "framework",
"laravel" "laravel"
], ],
"time": "2017-03-21T19:34:41+00:00" "time": "2017-04-16T13:33:34+00:00"
}, },
{ {
"name": "laravel/scout", "name": "laravel/scout",
"version": "v3.0.2", "version": "v3.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/scout.git", "url": "https://github.com/laravel/scout.git",
"reference": "1ddb0fa6f165bf6a69864960102062e7cf3f989d" "reference": "64d28db58a054174eadf1d4df38dad81ff7e68dd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/scout/zipball/1ddb0fa6f165bf6a69864960102062e7cf3f989d", "url": "https://api.github.com/repos/laravel/scout/zipball/64d28db58a054174eadf1d4df38dad81ff7e68dd",
"reference": "1ddb0fa6f165bf6a69864960102062e7cf3f989d", "reference": "64d28db58a054174eadf1d4df38dad81ff7e68dd",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1590,7 +1649,7 @@
"laravel", "laravel",
"search" "search"
], ],
"time": "2017-03-01T14:37:40+00:00" "time": "2017-04-09T00:54:26+00:00"
}, },
{ {
"name": "laravel/tinker", "name": "laravel/tinker",
@ -2740,19 +2799,20 @@
}, },
{ {
"name": "sensiolabs/security-checker", "name": "sensiolabs/security-checker",
"version": "v4.0.2", "version": "v4.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sensiolabs/security-checker.git", "url": "https://github.com/sensiolabs/security-checker.git",
"reference": "56bded66985e22f6eac2cf86735fd21c625bff2f" "reference": "9e69eddf3bc49d1ee5c7908564da3141796d4bbc"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/56bded66985e22f6eac2cf86735fd21c625bff2f", "url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/9e69eddf3bc49d1ee5c7908564da3141796d4bbc",
"reference": "56bded66985e22f6eac2cf86735fd21c625bff2f", "reference": "9e69eddf3bc49d1ee5c7908564da3141796d4bbc",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"composer/ca-bundle": "^1.0",
"symfony/console": "~2.7|~3.0" "symfony/console": "~2.7|~3.0"
}, },
"bin": [ "bin": [
@ -2780,7 +2840,7 @@
} }
], ],
"description": "A security checker for your composer.lock", "description": "A security checker for your composer.lock",
"time": "2017-03-09T17:33:20+00:00" "time": "2017-03-31T14:50:32+00:00"
}, },
{ {
"name": "swiftmailer/swiftmailer", "name": "swiftmailer/swiftmailer",
@ -2838,16 +2898,16 @@
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v3.2.6", "version": "v3.2.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "28fb243a2b5727774ca309ec2d92da240f1af0dd" "reference": "c30243cc51f726812be3551316b109a2f5deaf8d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/28fb243a2b5727774ca309ec2d92da240f1af0dd", "url": "https://api.github.com/repos/symfony/console/zipball/c30243cc51f726812be3551316b109a2f5deaf8d",
"reference": "28fb243a2b5727774ca309ec2d92da240f1af0dd", "reference": "c30243cc51f726812be3551316b109a2f5deaf8d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2897,11 +2957,11 @@
], ],
"description": "Symfony Console Component", "description": "Symfony Console Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-03-06T19:30:27+00:00" "time": "2017-04-04T14:33:42+00:00"
}, },
{ {
"name": "symfony/css-selector", "name": "symfony/css-selector",
"version": "v3.2.6", "version": "v3.2.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/css-selector.git", "url": "https://github.com/symfony/css-selector.git",
@ -2954,16 +3014,16 @@
}, },
{ {
"name": "symfony/debug", "name": "symfony/debug",
"version": "v3.2.6", "version": "v3.2.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/debug.git", "url": "https://github.com/symfony/debug.git",
"reference": "b90c9f91ad8ac37d9f114e369042d3226b34dc1a" "reference": "56f613406446a4a0a031475cfd0a01751de22659"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/debug/zipball/b90c9f91ad8ac37d9f114e369042d3226b34dc1a", "url": "https://api.github.com/repos/symfony/debug/zipball/56f613406446a4a0a031475cfd0a01751de22659",
"reference": "b90c9f91ad8ac37d9f114e369042d3226b34dc1a", "reference": "56f613406446a4a0a031475cfd0a01751de22659",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3007,20 +3067,20 @@
], ],
"description": "Symfony Debug Component", "description": "Symfony Debug Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-02-18T17:28:00+00:00" "time": "2017-03-28T21:38:24+00:00"
}, },
{ {
"name": "symfony/event-dispatcher", "name": "symfony/event-dispatcher",
"version": "v3.2.6", "version": "v3.2.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/event-dispatcher.git", "url": "https://github.com/symfony/event-dispatcher.git",
"reference": "b7a1b9e0a0f623ce43b4c8d775eb138f190c9d8d" "reference": "154bb1ef7b0e42ccc792bd53edbce18ed73440ca"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7a1b9e0a0f623ce43b4c8d775eb138f190c9d8d", "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/154bb1ef7b0e42ccc792bd53edbce18ed73440ca",
"reference": "b7a1b9e0a0f623ce43b4c8d775eb138f190c9d8d", "reference": "154bb1ef7b0e42ccc792bd53edbce18ed73440ca",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3067,20 +3127,20 @@
], ],
"description": "Symfony EventDispatcher Component", "description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-02-21T09:12:04+00:00" "time": "2017-04-04T07:26:27+00:00"
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v3.2.6", "version": "v3.2.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/finder.git", "url": "https://github.com/symfony/finder.git",
"reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10" "reference": "b20900ce5ea164cd9314af52725b0bb5a758217a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/92d7476d2df60cd851a3e13e078664b1deb8ce10", "url": "https://api.github.com/repos/symfony/finder/zipball/b20900ce5ea164cd9314af52725b0bb5a758217a",
"reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10", "reference": "b20900ce5ea164cd9314af52725b0bb5a758217a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3116,20 +3176,20 @@
], ],
"description": "Symfony Finder Component", "description": "Symfony Finder Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-02-21T09:12:04+00:00" "time": "2017-03-20T09:32:19+00:00"
}, },
{ {
"name": "symfony/http-foundation", "name": "symfony/http-foundation",
"version": "v3.2.6", "version": "v3.2.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/http-foundation.git", "url": "https://github.com/symfony/http-foundation.git",
"reference": "c57009887010eb4e58bfca2970314a5b820b24b9" "reference": "cb0b6418f588952c9290b3df4ca650f1b7ab570a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/c57009887010eb4e58bfca2970314a5b820b24b9", "url": "https://api.github.com/repos/symfony/http-foundation/zipball/cb0b6418f588952c9290b3df4ca650f1b7ab570a",
"reference": "c57009887010eb4e58bfca2970314a5b820b24b9", "reference": "cb0b6418f588952c9290b3df4ca650f1b7ab570a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3169,20 +3229,20 @@
], ],
"description": "Symfony HttpFoundation Component", "description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-03-04T12:23:14+00:00" "time": "2017-04-04T15:30:56+00:00"
}, },
{ {
"name": "symfony/http-kernel", "name": "symfony/http-kernel",
"version": "v3.2.6", "version": "v3.2.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/http-kernel.git", "url": "https://github.com/symfony/http-kernel.git",
"reference": "bc909e85b8585c9edf043d0fca871308c41bb9b4" "reference": "8285ab5faf1306b1a5ebcf287fe91c231a6de88e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/bc909e85b8585c9edf043d0fca871308c41bb9b4", "url": "https://api.github.com/repos/symfony/http-kernel/zipball/8285ab5faf1306b1a5ebcf287fe91c231a6de88e",
"reference": "bc909e85b8585c9edf043d0fca871308c41bb9b4", "reference": "8285ab5faf1306b1a5ebcf287fe91c231a6de88e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3251,7 +3311,7 @@
], ],
"description": "Symfony HttpKernel Component", "description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-03-10T18:35:31+00:00" "time": "2017-04-05T12:52:03+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
@ -3314,16 +3374,16 @@
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v3.2.6", "version": "v3.2.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "68bfa8c83f24c0ac04ea7193bcdcda4519f41892" "reference": "57fdaa55827ae14d617550ebe71a820f0a5e2282"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/68bfa8c83f24c0ac04ea7193bcdcda4519f41892", "url": "https://api.github.com/repos/symfony/process/zipball/57fdaa55827ae14d617550ebe71a820f0a5e2282",
"reference": "68bfa8c83f24c0ac04ea7193bcdcda4519f41892", "reference": "57fdaa55827ae14d617550ebe71a820f0a5e2282",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3359,11 +3419,11 @@
], ],
"description": "Symfony Process Component", "description": "Symfony Process Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-03-04T12:23:14+00:00" "time": "2017-03-27T18:07:02+00:00"
}, },
{ {
"name": "symfony/routing", "name": "symfony/routing",
"version": "v3.2.6", "version": "v3.2.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/routing.git", "url": "https://github.com/symfony/routing.git",
@ -3438,16 +3498,16 @@
}, },
{ {
"name": "symfony/translation", "name": "symfony/translation",
"version": "v3.2.6", "version": "v3.2.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/translation.git", "url": "https://github.com/symfony/translation.git",
"reference": "0e1b15ce8fbf3890f4ccdac430ed5e07fdfe0690" "reference": "c740eee70783d2af4d3d6b70d5146f209e6b4d13"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/0e1b15ce8fbf3890f4ccdac430ed5e07fdfe0690", "url": "https://api.github.com/repos/symfony/translation/zipball/c740eee70783d2af4d3d6b70d5146f209e6b4d13",
"reference": "0e1b15ce8fbf3890f4ccdac430ed5e07fdfe0690", "reference": "c740eee70783d2af4d3d6b70d5146f209e6b4d13",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3498,20 +3558,20 @@
], ],
"description": "Symfony Translation Component", "description": "Symfony Translation Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-03-04T12:23:14+00:00" "time": "2017-03-21T21:44:32+00:00"
}, },
{ {
"name": "symfony/var-dumper", "name": "symfony/var-dumper",
"version": "v3.2.6", "version": "v3.2.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/var-dumper.git", "url": "https://github.com/symfony/var-dumper.git",
"reference": "4100f347aff890bc16b0b4b42843b599db257b2d" "reference": "81dce20f69a8b40427e1f4e6462178df87cafc03"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/4100f347aff890bc16b0b4b42843b599db257b2d", "url": "https://api.github.com/repos/symfony/var-dumper/zipball/81dce20f69a8b40427e1f4e6462178df87cafc03",
"reference": "4100f347aff890bc16b0b4b42843b599db257b2d", "reference": "81dce20f69a8b40427e1f4e6462178df87cafc03",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3564,7 +3624,7 @@
"debug", "debug",
"dump" "dump"
], ],
"time": "2017-02-20T13:45:48+00:00" "time": "2017-03-12T16:07:05+00:00"
}, },
{ {
"name": "themattharris/tmhoauth", "name": "themattharris/tmhoauth",
@ -4053,16 +4113,16 @@
}, },
{ {
"name": "laravel/dusk", "name": "laravel/dusk",
"version": "v1.0.10", "version": "v1.0.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/dusk.git", "url": "https://github.com/laravel/dusk.git",
"reference": "11537ac1a939a2194e9e3cdc2536e6e34eff9ea6" "reference": "9a150bedc3ae6566d05f0a8d1657c658ce98dcad"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/dusk/zipball/11537ac1a939a2194e9e3cdc2536e6e34eff9ea6", "url": "https://api.github.com/repos/laravel/dusk/zipball/9a150bedc3ae6566d05f0a8d1657c658ce98dcad",
"reference": "11537ac1a939a2194e9e3cdc2536e6e34eff9ea6", "reference": "9a150bedc3ae6566d05f0a8d1657c658ce98dcad",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4104,7 +4164,7 @@
"testing", "testing",
"webdriver" "webdriver"
], ],
"time": "2017-03-03T14:36:19+00:00" "time": "2017-04-07T21:47:06+00:00"
}, },
{ {
"name": "maximebf/debugbar", "name": "maximebf/debugbar",
@ -4234,16 +4294,16 @@
}, },
{ {
"name": "myclabs/deep-copy", "name": "myclabs/deep-copy",
"version": "1.6.0", "version": "1.6.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/myclabs/DeepCopy.git", "url": "https://github.com/myclabs/DeepCopy.git",
"reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe" "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe", "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102",
"reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe", "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4272,7 +4332,7 @@
"object", "object",
"object graph" "object graph"
], ],
"time": "2017-01-26T22:05:40+00:00" "time": "2017-04-12T18:52:22+00:00"
}, },
{ {
"name": "phpdocumentor/reflection-common", "name": "phpdocumentor/reflection-common",
@ -4485,16 +4545,16 @@
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "4.0.7", "version": "4.0.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "09e2277d14ea467e5a984010f501343ef29ffc69" "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/09e2277d14ea467e5a984010f501343ef29ffc69", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
"reference": "09e2277d14ea467e5a984010f501343ef29ffc69", "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4544,7 +4604,7 @@
"testing", "testing",
"xunit" "xunit"
], ],
"time": "2017-03-01T09:12:17+00:00" "time": "2017-04-02T07:44:40+00:00"
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
@ -4734,16 +4794,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "5.7.17", "version": "5.7.19",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "68752b665d3875f9a38a357e3ecb35c79f8673bf" "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/68752b665d3875f9a38a357e3ecb35c79f8673bf", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/69c4f49ff376af2692bad9cebd883d17ebaa98a1",
"reference": "68752b665d3875f9a38a357e3ecb35c79f8673bf", "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4812,7 +4872,7 @@
"testing", "testing",
"xunit" "xunit"
], ],
"time": "2017-03-19T16:52:12+00:00" "time": "2017-04-03T02:22:27+00:00"
}, },
{ {
"name": "phpunit/phpunit-mock-objects", "name": "phpunit/phpunit-mock-objects",
@ -5477,16 +5537,16 @@
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v3.2.6", "version": "v3.2.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/yaml.git", "url": "https://github.com/symfony/yaml.git",
"reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a" "reference": "62b4cdb99d52cb1ff253c465eb1532a80cebb621"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/093e416ad096355149e265ea2e4cc1f9ee40ab1a", "url": "https://api.github.com/repos/symfony/yaml/zipball/62b4cdb99d52cb1ff253c465eb1532a80cebb621",
"reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a", "reference": "62b4cdb99d52cb1ff253c465eb1532a80cebb621",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5528,20 +5588,20 @@
], ],
"description": "Symfony Yaml Component", "description": "Symfony Yaml Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-03-07T16:47:02+00:00" "time": "2017-03-20T09:45:15+00:00"
}, },
{ {
"name": "theseer/fdomdocument", "name": "theseer/fdomdocument",
"version": "1.6.1", "version": "1.6.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/theseer/fDOMDocument.git", "url": "https://github.com/theseer/fDOMDocument.git",
"reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684" "reference": "cf219ede922fb47956726f35e2127277ebd302ca"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/d9ad139d6c2e8edf5e313ffbe37ff13344cf0684", "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/cf219ede922fb47956726f35e2127277ebd302ca",
"reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684", "reference": "cf219ede922fb47956726f35e2127277ebd302ca",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5568,7 +5628,7 @@
], ],
"description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.",
"homepage": "https://github.com/theseer/fDOMDocument", "homepage": "https://github.com/theseer/fDOMDocument",
"time": "2015-05-27T22:58:02+00:00" "time": "2017-04-17T09:08:13+00:00"
}, },
{ {
"name": "webmozart/assert", "name": "webmozart/assert",

View file

@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateIndieWebUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('indie_web_users', function (Blueprint $table) {
$table->increments('id');
$table->string('me')->unique();
$table->text('token')->nullable();
$table->string('syntax')->default('json');
$table->jsonb('syndication')->nullable();
$table->string('mediaEndpoint')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('indie_web_users');
}
}

View file

@ -17,5 +17,6 @@ class DatabaseSeeder extends Seeder
$this->call(PlacesTableSeeder::class); $this->call(PlacesTableSeeder::class);
$this->call(NotesTableSeeder::class); $this->call(NotesTableSeeder::class);
$this->call(WebMentionsTableSeeder::class); $this->call(WebMentionsTableSeeder::class);
$this->call(IndieWebUserTableSeeder::class);
} }
} }

View file

@ -0,0 +1,16 @@
<?php
use Illuminate\Database\Seeder;
class IndieWebUserTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
App\IndieWebUser::create(['me' => config('app.url')]);
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,20 +1,21 @@
/* http://prismjs.com/download.html?themes=prism-dark&languages=markup+css+clike+javascript+git+http+markdown+php+php-extras+scss+sql&plugins=line-numbers+show-invisibles */ /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+bash+c+csharp+cpp+ruby+css-extras+diff+git+go+http+ini+json+latex+lua+makefile+markdown+nginx+objectivec+php+php-extras+python+rust+sass+scss+sql+swift+vim+wiki+yaml&plugins=line-numbers+autolinker */
/** /**
* prism.js Dark theme for JavaScript, CSS and HTML * okaidia theme for JavaScript, CSS and HTML
* Based on the slides of the talk /Reg(exp){2}lained/ * Loosely based on Monokai textmate theme by http://www.monokai.nl/
* @author Lea Verou * @author ocodia
*/ */
code[class*="language-"], code[class*="language-"],
pre[class*="language-"] { pre[class*="language-"] {
color: white; color: #f8f8f2;
text-shadow: 0 -.1em .2em black; background: none;
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
direction: ltr;
text-align: left; text-align: left;
white-space: pre; white-space: pre;
word-spacing: normal; word-spacing: normal;
word-break: normal; word-break: normal;
word-wrap: normal;
line-height: 1.5; line-height: 1.5;
-moz-tab-size: 4; -moz-tab-size: 4;
@ -27,45 +28,35 @@ pre[class*="language-"] {
hyphens: none; hyphens: none;
} }
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
pre[class*="language-"],
:not(pre) > code[class*="language-"] {
background: hsl(30, 20%, 25%);
}
/* Code blocks */ /* Code blocks */
pre[class*="language-"] { pre[class*="language-"] {
padding: 1em; padding: 1em;
margin: .5em 0; margin: .5em 0;
overflow: auto; overflow: auto;
border: .3em solid hsl(30, 20%, 40%); border-radius: 0.3em;
border-radius: .5em; }
box-shadow: 1px 1px .5em black inset;
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #272822;
} }
/* Inline code */ /* Inline code */
:not(pre) > code[class*="language-"] { :not(pre) > code[class*="language-"] {
padding: .15em .2em .05em; padding: .1em;
border-radius: .3em; border-radius: .3em;
border: .13em solid hsl(30, 20%, 40%); white-space: normal;
box-shadow: 1px 1px .3em -.1em black inset;
} }
.token.comment, .token.comment,
.token.prolog, .token.prolog,
.token.doctype, .token.doctype,
.token.cdata { .token.cdata {
color: hsl(30, 20%, 50%); color: slategray;
} }
.token.punctuation { .token.punctuation {
opacity: .7; color: #f8f8f2;
} }
.namespace { .namespace {
@ -74,11 +65,15 @@ pre[class*="language-"] {
.token.property, .token.property,
.token.tag, .token.tag,
.token.boolean,
.token.number,
.token.constant, .token.constant,
.token.symbol { .token.symbol,
color: hsl(350, 40%, 70%); .token.deleted {
color: #f92672;
}
.token.boolean,
.token.number {
color: #ae81ff;
} }
.token.selector, .token.selector,
@ -87,7 +82,7 @@ pre[class*="language-"] {
.token.char, .token.char,
.token.builtin, .token.builtin,
.token.inserted { .token.inserted {
color: hsl(75, 70%, 60%); color: #a6e22e;
} }
.token.operator, .token.operator,
@ -96,18 +91,22 @@ pre[class*="language-"] {
.language-css .token.string, .language-css .token.string,
.style .token.string, .style .token.string,
.token.variable { .token.variable {
color: hsl(40, 90%, 60%); color: #f8f8f2;
} }
.token.atrule, .token.atrule,
.token.attr-value, .token.attr-value,
.token.function {
color: #e6db74;
}
.token.keyword { .token.keyword {
color: hsl(350, 40%, 70%); color: #66d9ef;
} }
.token.regex, .token.regex,
.token.important { .token.important {
color: #e90; color: #fd971f;
} }
.token.important, .token.important,
@ -122,10 +121,6 @@ pre[class*="language-"] {
cursor: help; cursor: help;
} }
.token.deleted {
color: red;
}
pre.line-numbers { pre.line-numbers {
position: relative; position: relative;
padding-left: 3.8em; padding-left: 3.8em;
@ -166,23 +161,6 @@ pre.line-numbers > code {
padding-right: 0.8em; padding-right: 0.8em;
text-align: right; text-align: right;
} }
.token.tab:not(:empty):before, .token a {
.token.cr:before, color: inherit;
.token.lf:before {
color: hsl(24, 20%, 85%);
}
.token.tab:not(:empty):before {
content: '\21E5';
}
.token.cr:before {
content: '\240D';
}
.token.crlf:before {
content: '\240D\240A';
}
.token.lf:before {
content: '\240A';
} }

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View file

@ -7,11 +7,34 @@ Micropub Config «
@section('content') @section('content')
<p>The values for your micropub endpoint.</p> <p>The values for your micropub endpoint.</p>
<dl> <dl>
<dt>Me (your url)</dt><dd>{{ $data['me'] }}</dd> <dt>Me (your url)</dt><dd><code>{{ $data['me'] }}</code></dd>
<dt>Token</dt><dd>{{ $data['token'] }}</dd> <dt>Token</dt><dd><code>{{ $data['token'] }}</code></dd>
<dt>Syndication Targets</dt><dd>@if(is_array($data['syndication']))<ul>@foreach ($data['syndication'] as $syn)<li>{{ $syn['name'] }} ({{ $syn['target'] }})</li>@endforeach</ul>@else{{ $data['syndication'] }}@endif</dd> <dt>Syndication Targets</dt><dd>
<dt>Media Endpoint</dt><dd>{{ $data['media-endpoint'] }}</dd> @if(is_array($data['syndication']))<ul>@foreach ($data['syndication'] as $syn)
<li>{{ $syn['name'] }} ({{ $syn['target'] }})</li>
@endforeach</ul>@elseif($data['syndication'] == 'none defined')
<code>none defined</code>
@else
<pre><code class="language-json">{{ prettyPrintJson($data['syndication']) }}</code></pre>
@endif</dd>
<dt>Media Endpoint</dt><dd><code>{{ $data['media-endpoint'] }}</code></dd>
</dl> </dl>
<p>Get a <a href="{{ route('micropub-client-get-new-token') }}">new token</a>.</p>
<p><a href="{{ route('micropub-query-action') }}">Re-query</a> the endpoint.</p> <p><a href="{{ route('micropub-query-action') }}">Re-query</a> the endpoint.</p>
<p>Return to <a href="{{ route('micropub-client') }}">client</a>. <p>Return to <a href="{{ route('micropub-client') }}">client</a>.
<form action="{{ route('micropub-update-syntax') }}" method="post">
{{ csrf_field() }}
<fieldset>
<legend>Syntax</legend>
<p><input type="radio" name="syntax" value="html" id="mf2"@if($data['syntax'] == 'html') checked @endif> <label for="html"><code>x-www-form-urlencoded</code> or <code>multipart/form-data</code></label></p>
<p><input type="radio" name="syntax" value="json" id="json"@if($data['syntax'] == 'json') checked @endif> <label for="json"><code>json</code></label></p>
<p><button type="submit">Update syntax</button></p>
</fieldset>
</form>
@stop
@section('scripts')
<script src="/assets/prism/prism.js"></script>
<link rel="stylesheet" href="/assets/prism/prism.css">
@stop @stop

View file

@ -109,7 +109,10 @@ Route::group(['domain' => config('url.longurl')], function () {
Route::get('micropub/create', 'MicropubClientController@create')->name('micropub-client'); Route::get('micropub/create', 'MicropubClientController@create')->name('micropub-client');
Route::post('micropub', 'MicropubClientController@store')->name('micropub-client-post'); Route::post('micropub', 'MicropubClientController@store')->name('micropub-client-post');
Route::get('micropub/config', 'MicropubClientController@config')->name('micropub-config'); Route::get('micropub/config', 'MicropubClientController@config')->name('micropub-config');
Route::get('micropub/get-new-token', 'MicropubClientController@getNewToken')->name('micropub-client-get-new-token');
Route::get('micropub/get-new-token/callback', 'MicropubClientController@getNewTokenCallback')->name('micropub-client-get-new-token-callback');
Route::get('micropub/query-endpoint', 'MicropubClientController@queryEndpoint')->name('micropub-query-action'); Route::get('micropub/query-endpoint', 'MicropubClientController@queryEndpoint')->name('micropub-query-action');
Route::post('micropub/update-syntax', 'MicropubClientController@updateSyntax')->name('micropub-update-syntax');
Route::get('micropub/places', 'MicropubClientController@nearbyPlaces'); Route::get('micropub/places', 'MicropubClientController@nearbyPlaces');
Route::post('micropub/places', 'MicropubClientController@newPlace'); Route::post('micropub/places', 'MicropubClientController@newPlace');
Route::post('micropub/media', 'MicropubClientController@processMedia')->name('process-media'); Route::post('micropub/media', 'MicropubClientController@processMedia')->name('process-media');

View file

@ -22,13 +22,16 @@ class MicropubClientTest extends DuskTestCase
public function test_client_page_creates_new_note() public function test_client_page_creates_new_note()
{ {
\Artisan::call('token:generate');
$faker = \Faker\Factory::create(); $faker = \Faker\Factory::create();
$note = 'Fake note from #LaravelDusk: ' . $faker->text; $note = 'Fake note from #LaravelDusk: ' . $faker->text;
$this->browse(function ($browser) use ($note) { $this->browse(function ($browser) use ($note) {
$browser->visit(route('micropub-client')) $browser->visit(route('micropub-client'))
->assertSeeLink('log out')
->type('content', $note) ->type('content', $note)
->press('Submit'); ->press('Submit');
}); });
sleep(2);
$this->assertDatabaseHas('notes', ['note' => $note]); $this->assertDatabaseHas('notes', ['note' => $note]);
$newNote = \App\Note::where('note', $note)->first(); $newNote = \App\Note::where('note', $note)->first();
$newNote->forceDelete(); $newNote->forceDelete();

View file

@ -287,7 +287,7 @@ class MicropubControllerTest extends TestCase
$token = (new Builder()) $token = (new Builder())
->set('client_id', 'https://quill.p3k.io') ->set('client_id', 'https://quill.p3k.io')
->set('me', 'https://jonnybarnes.localhost') ->set('me', 'https://jonnybarnes.localhost')
->set('scope', 'post') ->set('scope', 'create update')
->set('issued_at', time()) ->set('issued_at', time())
->sign($signer, env('APP_KEY')) ->sign($signer, env('APP_KEY'))
->getToken(); ->getToken();

View file

@ -1,4 +1,5 @@
[global] [global]
error_log = /tmp/php-fpm.error.log
[travis] [travis]
user = {USER} user = {USER}

614
yarn.lock

File diff suppressed because it is too large Load diff