Merge branch 'release/0.5'
This commit is contained in:
commit
a7117b3972
55 changed files with 2324 additions and 1231 deletions
|
@ -22,7 +22,7 @@ REDIS_PASSWORD=null
|
|||
REDIS_PORT=6379
|
||||
|
||||
MAIL_DRIVER=smtp
|
||||
MAIL_HOST=mailtrap.io
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
|
@ -52,3 +52,5 @@ TWITTER_ACCESS_TOKEN_SECRET=
|
|||
SCOUT_DRIVER=pgsql
|
||||
|
||||
PIWIK=false
|
||||
|
||||
PSYSH_CONFIG=tinker.config.php
|
||||
|
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1,3 +1,4 @@
|
|||
* text=auto
|
||||
*.css linguist-vendored
|
||||
*.scss linguist-vendored
|
||||
*.js linguist-vendored
|
||||
|
|
|
@ -19,6 +19,7 @@ addons:
|
|||
paths:
|
||||
- $(ls tests/Browser/screenshots/*.png | tr "\n" ":")
|
||||
- $(ls tests/Browser/console/*.log | tr "\n" ":")
|
||||
- $(ls storage/logs/*.log | tr "\n" ":")
|
||||
- $(ls /tmp/*.log | tr "\n" ":")
|
||||
|
||||
services:
|
||||
|
@ -49,16 +50,18 @@ install:
|
|||
- travis/install-nginx.sh
|
||||
|
||||
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 -d travis_ci_test -c 'create extension postgis'
|
||||
- cp .env.travis .env
|
||||
- php artisan key:generate
|
||||
- php artisan migrate
|
||||
- php artisan db:seed
|
||||
- php artisan token:generate
|
||||
- phantomjs --webdriver=127.0.0.1:9515 --webdriver-loglevel=DEBUG &
|
||||
- sleep 5 # Give artisan some time to start serving
|
||||
|
||||
script:
|
||||
- php vendor/bin/phpunit --coverage-text
|
||||
- php artisan dusk
|
||||
- php artisan security:check
|
||||
- php vendor/bin/security-checker security:check ./composer.lock --end-point=http://security.sensiolabs.org/check_lock
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\IndieWebUser;
|
||||
use App\Services\TokenService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class GenerateToken extends Command
|
||||
{
|
||||
|
@ -49,10 +49,12 @@ class GenerateToken extends Command
|
|||
$data = [
|
||||
'me' => config('app.url'),
|
||||
'client_id' => route('micropub-client'),
|
||||
'scope' => 'post',
|
||||
'scope' => 'create update',
|
||||
];
|
||||
$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');
|
||||
}
|
||||
|
|
13
app/Exceptions/InvalidTokenException.php
Normal file
13
app/Exceptions/InvalidTokenException.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidTokenException extends Exception
|
||||
{
|
||||
public function __construct($message, $code = 0, Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
|
@ -2,35 +2,26 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\IndieWebUser;
|
||||
use IndieAuth\Client;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\TokenService;
|
||||
use App\Services\IndieAuthService;
|
||||
|
||||
class IndieAuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* This service isolates the IndieAuth Client code.
|
||||
* The IndieAuth Client.
|
||||
*/
|
||||
protected $indieAuthService;
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* The Token handling service.
|
||||
*/
|
||||
protected $tokenService;
|
||||
|
||||
/**
|
||||
* Inject the dependencies.
|
||||
* Inject the dependency.
|
||||
*
|
||||
* @param \App\Services\IndieAuthService $indieAuthService
|
||||
* @param \App\Services\TokenService $tokenService
|
||||
* @param \IndieAuth\Client $client
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
IndieAuthService $indieAuthService = null,
|
||||
TokenService $tokenService = null
|
||||
) {
|
||||
$this->indieAuthService = $indieAuthService ?? new IndieAuthService();
|
||||
$this->tokenService = $tokenService ?? new TokenService();
|
||||
public function __construct(Client $client = null)
|
||||
{
|
||||
$this->client = $client ?? new Client();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,25 +35,31 @@ class IndieAuthController extends Controller
|
|||
*/
|
||||
public function start(Request $request)
|
||||
{
|
||||
$authorizationEndpoint = $this->indieAuthService->getAuthorizationEndpoint(
|
||||
$request->input('me')
|
||||
);
|
||||
if ($authorizationEndpoint !== null) {
|
||||
$authorizationURL = $this->indieAuthService->buildAuthorizationURL(
|
||||
$url = normalize_url($request->input('me'));
|
||||
$authorizationEndpoint = $this->client->discoverAuthorizationEndpoint($url);
|
||||
if ($authorizationEndpoint != null) {
|
||||
$state = bin2hex(openssl_random_pseudo_bytes(16));
|
||||
session(['state' => $state]);
|
||||
$authorizationURL = $this->client->buildAuthorizationURL(
|
||||
$authorizationEndpoint,
|
||||
$request->input('me')
|
||||
$url,
|
||||
route('indieauth-callback'), //redirect_uri
|
||||
route('micropub-client'), //client_id
|
||||
$state
|
||||
);
|
||||
if ($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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Once they have verified themselves through the authorisation endpint
|
||||
* the next step is retreiveing a token from the token endpoint.
|
||||
* Once they have verified themselves through the authorisation endpoint
|
||||
* the next step is register/login the user.
|
||||
*
|
||||
* @param \Illuminate\Http\Rrequest $request
|
||||
* @return \Illuminate\Routing\RedirectResponse redirect
|
||||
|
@ -75,38 +72,16 @@ class IndieAuthController extends Controller
|
|||
'Invalid <code>state</code> value returned from indieauth server'
|
||||
);
|
||||
}
|
||||
$tokenEndpoint = $this->indieAuthService->getTokenEndpoint($request->input('me'));
|
||||
if ($tokenEndpoint === false) {
|
||||
return redirect(route('micropub-client'))->with(
|
||||
'error',
|
||||
'Unable to determine token endpoint'
|
||||
);
|
||||
}
|
||||
$data = [
|
||||
'endpoint' => $tokenEndpoint,
|
||||
'code' => $request->input('code'),
|
||||
'me' => $request->input('me'),
|
||||
'redirect_url' => route('indieauth-callback'),
|
||||
'client_id' => route('micropub-client'),
|
||||
'state' => $request->input('state'),
|
||||
];
|
||||
$token = $this->indieAuthService->getAccessToken($data);
|
||||
|
||||
if (array_key_exists('access_token', $token)) {
|
||||
$request->session()->put('me', $token['me']);
|
||||
$request->session()->put('token', $token['access_token']);
|
||||
$url = normalize_url($request->input('me'));
|
||||
$indiewebUser = IndieWebUser::firstOrCreate(['me' => $url]);
|
||||
$request->session()->put(['me' => $url]);
|
||||
|
||||
return redirect(route('micropub-client'));
|
||||
}
|
||||
|
||||
return redirect(route('micropub-client'))->with(
|
||||
'error',
|
||||
'Unable to get a token from the endpoint'
|
||||
);
|
||||
return redirect(route('micropub-client'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@ -114,44 +89,6 @@ class IndieAuthController extends Controller
|
|||
{
|
||||
$request->session()->flush();
|
||||
|
||||
return redirect(route('micropub-client'))->cookie('me', 'loggedout', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user has auth’d via IndieAuth, issue a valid token.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function tokenEndpoint(Request $request)
|
||||
{
|
||||
$authData = [
|
||||
'code' => $request->input('code'),
|
||||
'me' => $request->input('me'),
|
||||
'redirect_url' => $request->input('redirect_uri'),
|
||||
'client_id' => $request->input('client_id'),
|
||||
'state' => $request->input('state'),
|
||||
];
|
||||
$auth = $this->indieAuthService->verifyIndieAuthCode($authData);
|
||||
if (array_key_exists('me', $auth)) {
|
||||
$scope = $auth['scope'] ?? '';
|
||||
$tokenData = [
|
||||
'me' => $request->input('me'),
|
||||
'client_id' => $request->input('client_id'),
|
||||
'scope' => $auth['scope'],
|
||||
];
|
||||
$token = $this->tokenService->getNewToken($tokenData);
|
||||
$content = http_build_query([
|
||||
'me' => $request->input('me'),
|
||||
'scope' => $scope,
|
||||
'access_token' => $token,
|
||||
]);
|
||||
|
||||
return response($content)
|
||||
->header('Content-Type', 'application/x-www-form-urlencoded');
|
||||
}
|
||||
$content = 'There was an error verifying the authorisation code.';
|
||||
|
||||
return response($content, 400);
|
||||
return redirect(route('micropub-client'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\IndieAuthService;
|
||||
use App\IndieWebUser;
|
||||
use IndieAuth\Client as IndieClient;
|
||||
use GuzzleHttp\Client as GuzzleClient;
|
||||
use Illuminate\Http\{Request, Response};
|
||||
|
@ -10,20 +10,13 @@ use GuzzleHttp\Exception\{ClientException, ServerException};
|
|||
|
||||
class MicropubClientController extends Controller
|
||||
{
|
||||
/**
|
||||
* The IndieAuth service container.
|
||||
*/
|
||||
protected $indieAuthService;
|
||||
|
||||
/**
|
||||
* Inject the dependencies.
|
||||
*/
|
||||
public function __construct(
|
||||
IndieAuthService $indieAuthService = null,
|
||||
IndieClient $indieClient = null,
|
||||
GuzzleClient $guzzleClient = null
|
||||
) {
|
||||
$this->indieAuthService = $indieAuthService ?? new IndieAuthService();
|
||||
$this->guzzleClient = $guzzleClient ?? new GuzzleClient();
|
||||
$this->indieClient = $indieClient ?? new IndieClient();
|
||||
}
|
||||
|
@ -37,8 +30,11 @@ class MicropubClientController extends Controller
|
|||
public function create(Request $request)
|
||||
{
|
||||
$url = $request->session()->get('me');
|
||||
$syndication = $request->session()->get('syndication');
|
||||
$mediaEndpoint = $request->session()->get('media-endpoint');
|
||||
if ($url) {
|
||||
$indiewebUser = IndieWebUser::where('me', $url)->first();
|
||||
}
|
||||
$syndication = $this->parseSyndicationTargets($indiewebUser->syndication);
|
||||
$mediaEndpoint = $indiewebUser->mediaEndpoint ?? null;
|
||||
$mediaURLs = $request->session()->get('media-links');
|
||||
|
||||
return view('micropub.create', compact('url', 'syndication', 'mediaEndpoint', 'mediaURLs'));
|
||||
|
@ -56,19 +52,17 @@ class MicropubClientController extends Controller
|
|||
return back();
|
||||
}
|
||||
|
||||
$mediaEndpoint = $request->session()->get('media-endpoint');
|
||||
if ($mediaEndpoint == null) {
|
||||
$user = IndieWebUser::where('me', $request->session()->get('me'))->firstOrFail();
|
||||
if ($user->mediaEndpoint == null || $user->token == null) {
|
||||
return back();
|
||||
}
|
||||
|
||||
$token = $request->session()->get('token');
|
||||
|
||||
$mediaURLs = [];
|
||||
foreach ($request->file('file') as $file) {
|
||||
try {
|
||||
$response = $this->guzzleClient->request('POST', $mediaEndpoint, [
|
||||
$response = $this->guzzleClient->request('POST', $user->mediaEndpoint, [
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer ' . $token,
|
||||
'Authorization' => 'Bearer ' . $user->token,
|
||||
],
|
||||
'multipart' => [
|
||||
[
|
||||
|
@ -109,27 +103,142 @@ class MicropubClientController extends Controller
|
|||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$domain = $request->session()->get('me');
|
||||
$token = $request->session()->get('token');
|
||||
$url = normalize_url($request->session()->get('me'));
|
||||
$user = IndieWebUser::where('me', $url)->firstOrFail();
|
||||
|
||||
$micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint(
|
||||
$domain,
|
||||
$this->indieClient
|
||||
);
|
||||
if ($user->token == null) {
|
||||
return redirect(route('micropub-client'))->with('error', 'You haven’t requested a token yet');
|
||||
}
|
||||
|
||||
$micropubEndpoint = $this->indieClient->discoverMicropubEndpoint($url);
|
||||
if (! $micropubEndpoint) {
|
||||
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) {
|
||||
$request->session()->forget('media-links');
|
||||
$location = $response->getHeader('Location');
|
||||
if (is_array($location)) {
|
||||
return redirect($location[0]);
|
||||
if ($user->syntax == 'html') {
|
||||
$multipart = [
|
||||
[
|
||||
'name' => 'h',
|
||||
'contents' => 'entry',
|
||||
],
|
||||
[
|
||||
'name' => 'content',
|
||||
'contents' => $request->input('content'),
|
||||
],
|
||||
];
|
||||
if ($request->hasFile('photo')) {
|
||||
$photos = $request->file('photo');
|
||||
foreach ($photos as $photo) {
|
||||
$multipart[] = [
|
||||
'name' => 'photo[]',
|
||||
'contents' => fopen($photo->path(), 'r'),
|
||||
'filename' => $photo->getClientOriginalName(),
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($request->input('in-reply-to') != '') {
|
||||
$multipart[] = [
|
||||
'name' => 'in-reply-to',
|
||||
'contents' => $request->input('in-reply-to'),
|
||||
];
|
||||
}
|
||||
if ($request->input('mp-syndicate-to')) {
|
||||
foreach ($request->input('mp-syndicate-to') as $syn) {
|
||||
$multipart[] = [
|
||||
'name' => 'mp-syndicate-to[]',
|
||||
'contents' => $syn,
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($request->input('location')) {
|
||||
if ($request->input('location') !== 'no-location') {
|
||||
$multipart[] = [
|
||||
'name' => 'location',
|
||||
'contents' => $request->input('location'),
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($request->input('media')) {
|
||||
foreach ($request->input('media') as $media) {
|
||||
$multipart[] = [
|
||||
'name' => 'photo[]',
|
||||
'contents' => $media,
|
||||
];
|
||||
}
|
||||
}
|
||||
try {
|
||||
$response = $this->guzzleClient->post($micropubEndpoint, [
|
||||
'multipart' => $multipart,
|
||||
'headers' => $headers,
|
||||
]);
|
||||
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
|
||||
return redirect(route('micropub-client'))->with(
|
||||
'error',
|
||||
'There was a bad response from the micropub endpoint.'
|
||||
);
|
||||
}
|
||||
|
||||
return redirect($location);
|
||||
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 didn’t create the note.');
|
||||
|
@ -143,26 +252,100 @@ class MicropubClientController extends Controller
|
|||
*/
|
||||
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';
|
||||
//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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the micropub endpoint and store response in the session.
|
||||
* 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> didn’t 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)
|
||||
{
|
||||
$domain = $request->session()->get('me');
|
||||
$token = $request->session()->get('token');
|
||||
$micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain);
|
||||
if ($micropubEndpoint !== null) {
|
||||
$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],
|
||||
|
@ -172,96 +355,35 @@ class MicropubClientController extends Controller
|
|||
return back();
|
||||
}
|
||||
$body = (string) $response->getBody();
|
||||
$data = json_decode($body, true);
|
||||
|
||||
$syndication = $this->parseSyndicationTargets($body);
|
||||
$request->session()->put('syndication', $syndication);
|
||||
if (array_key_exists('syndicate-to', $data)) {
|
||||
$user->syndication = json_encode($data['syndicate-to']);
|
||||
}
|
||||
|
||||
$mediaEndpoint = $this->parseMediaEndpoint($body);
|
||||
$request->session()->put('media-endpoint', $mediaEndpoint);
|
||||
if (array_key_exists('media-endpoint', $data)) {
|
||||
$user->mediaEndpoint = $data['media-endpoint'];
|
||||
}
|
||||
$user->save();
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method performs the actual POST request.
|
||||
* Update the syntax setting.
|
||||
*
|
||||
* @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
|
||||
* @param Illuminate\Http\Request $request
|
||||
* @return Illuminate\Http\RedirectResponse
|
||||
* @todo validate input
|
||||
*/
|
||||
private function postNoteRequest(
|
||||
Request $request,
|
||||
$micropubEndpoint,
|
||||
$token
|
||||
) {
|
||||
$multipart = [
|
||||
[
|
||||
'name' => 'h',
|
||||
'contents' => 'entry',
|
||||
],
|
||||
[
|
||||
'name' => 'content',
|
||||
'contents' => $request->input('content'),
|
||||
],
|
||||
];
|
||||
if ($request->hasFile('photo')) {
|
||||
$photos = $request->file('photo');
|
||||
foreach ($photos as $photo) {
|
||||
$multipart[] = [
|
||||
'name' => 'photo[]',
|
||||
'contents' => fopen($photo->path(), 'r'),
|
||||
'filename' => $photo->getClientOriginalName(),
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($request->input('in-reply-to') != '') {
|
||||
$multipart[] = [
|
||||
'name' => 'in-reply-to',
|
||||
'contents' => $request->input('in-reply-to'),
|
||||
];
|
||||
}
|
||||
if ($request->input('mp-syndicate-to')) {
|
||||
foreach ($request->input('mp-syndicate-to') as $syn) {
|
||||
$multipart[] = [
|
||||
'name' => 'mp-syndicate-to[]',
|
||||
'contents' => $syn,
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($request->input('location')) {
|
||||
if ($request->input('location') !== 'no-location') {
|
||||
$multipart[] = [
|
||||
'name' => 'location',
|
||||
'contents' => $request->input('location'),
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($request->input('media')) {
|
||||
foreach ($request->input('media') as $media) {
|
||||
$multipart[] = [
|
||||
'name' => 'photo[]',
|
||||
'contents' => $media,
|
||||
];
|
||||
}
|
||||
}
|
||||
$headers = [
|
||||
'Authorization' => 'Bearer ' . $token,
|
||||
];
|
||||
try {
|
||||
$response = $this->guzzleClient->post($micropubEndpoint, [
|
||||
'multipart' => $multipart,
|
||||
'headers' => $headers,
|
||||
]);
|
||||
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
|
||||
return redirect(route('micropub-client'))->with(
|
||||
'error',
|
||||
'There was a bad response from the micropub endpoint.'
|
||||
);
|
||||
}
|
||||
public function updateSyntax(Request $request)
|
||||
{
|
||||
$user = IndieWebUser::where('me', $request->session()->get('me'))->firstOrFail();
|
||||
$user->syntax = $request->syntax;
|
||||
$user->save();
|
||||
|
||||
return $response;
|
||||
return redirect(route('micropub-config'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -272,16 +394,17 @@ class MicropubClientController extends Controller
|
|||
*/
|
||||
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([
|
||||
'error' => true,
|
||||
'error_description' => 'No known token',
|
||||
], 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) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
|
@ -289,13 +412,27 @@ class MicropubClientController extends Controller
|
|||
], 400);
|
||||
}
|
||||
|
||||
$place = $this->postPlaceRequest($request, $micropubEndpoint, $token);
|
||||
if ($place === false) {
|
||||
$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 ' . $user->token,
|
||||
];
|
||||
try {
|
||||
$response = $this->guzzleClient->request('POST', $micropubEndpoint, [
|
||||
'form_params' => $formParams,
|
||||
'headers' => $headers,
|
||||
]);
|
||||
} catch (ClientException $e) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'error_description' => 'Unable to create the new place',
|
||||
], 400);
|
||||
}
|
||||
$place = $response->getHeader('Location')[0];
|
||||
|
||||
return response()->json([
|
||||
'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.
|
||||
*
|
||||
|
@ -351,16 +450,17 @@ class MicropubClientController extends Controller
|
|||
*/
|
||||
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([
|
||||
'error' => true,
|
||||
'error_description' => 'No known token',
|
||||
], 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) {
|
||||
return response()->json([
|
||||
|
@ -375,7 +475,7 @@ class MicropubClientController extends Controller
|
|||
$query .= ';u=' . $request->input('u');
|
||||
}
|
||||
$response = $this->guzzleClient->get($micropubEndpoint, [
|
||||
'headers' => ['Authorization' => 'Bearer ' . $token],
|
||||
'headers' => ['Authorization' => 'Bearer ' . $user->token],
|
||||
'query' => ['q' => $query],
|
||||
]);
|
||||
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
|
||||
|
@ -390,30 +490,34 @@ class MicropubClientController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Parse the syndication targets retreived from a cookie, to a form that can
|
||||
* be used in a view.
|
||||
* Parse the syndication targets JSON into a an array.
|
||||
*
|
||||
* @param string $syndicationTargets
|
||||
* @param string|null
|
||||
* @return array|null
|
||||
*/
|
||||
private function parseSyndicationTargets($syndicationTargets = null)
|
||||
{
|
||||
if ($syndicationTargets === null) {
|
||||
if ($syndicationTargets === null || $syndicationTargets === '') {
|
||||
return;
|
||||
}
|
||||
$syndicateTo = [];
|
||||
$data = json_decode($syndicationTargets, true);
|
||||
if (array_key_exists('syndicate-to', $data)) {
|
||||
foreach ($data['syndicate-to'] as $syn) {
|
||||
if (array_key_exists('uid', $data)) {
|
||||
$syndicateTo[] = [
|
||||
'target' => $data['uid'],
|
||||
'name' => $data['name'],
|
||||
];
|
||||
}
|
||||
foreach ($data as $syn) {
|
||||
if (array_key_exists('uid', $syn)) {
|
||||
$syndicateTo[] = [
|
||||
'target' => $syn['uid'],
|
||||
'name' => $syn['name'],
|
||||
];
|
||||
}
|
||||
}
|
||||
if (count($syndicateTo) > 0) {
|
||||
return $syndicateTo;
|
||||
}
|
||||
|
||||
return $syndicateTo;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use App\{Media, Place};
|
||||
use App\{Media, Note, Place};
|
||||
use Illuminate\Http\{Request, Response};
|
||||
use App\Exceptions\InvalidTokenException;
|
||||
use Ramsey\Uuid\Exception\UnsatisfiedDependencyException;
|
||||
use App\Services\{NoteService, PlaceService, TokenService};
|
||||
|
||||
|
@ -47,34 +48,67 @@ 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');
|
||||
try {
|
||||
$tokenData = $this->tokenService->validateToken($request->bearerToken());
|
||||
} catch (InvalidTokenException $e) {
|
||||
return response()->json([
|
||||
'response' => 'error',
|
||||
'error' => 'invalid_token',
|
||||
'error_description' => 'The provided token did not pass validation',
|
||||
], 400);
|
||||
}
|
||||
if ($tokenData->hasClaim('scope')) {
|
||||
if (($request->input('h') == 'entry') || ($request->input('type')[0] == 'h-entry')) {
|
||||
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
|
||||
return $this->returnInsufficientScopeResponse();
|
||||
}
|
||||
$data = [];
|
||||
$data['client-id'] = $tokenData->getClaim('client_id');
|
||||
if ($request->header('Content-Type') == 'application/json') {
|
||||
if (is_string($request->input('properties.content')[0])) {
|
||||
$data['content'] = $request->input('properties.content')[0]; //plaintext content
|
||||
}
|
||||
if (is_array($request->input('properties.content')[0])
|
||||
&& array_key_exists('html', $request->input('properties.content')[0])
|
||||
) {
|
||||
$data['content'] = $request->input('properties.content')[0]['html'];
|
||||
}
|
||||
$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['published'] = $request->input('properties.published')[0];
|
||||
//create checkin place
|
||||
if (array_key_exists('checkin', $request->input('properties'))) {
|
||||
$data['checkin'] = $request->input('properties.checkin.0.properties.url.0');
|
||||
try {
|
||||
$this->placeService->createPlaceFromCheckin($request->input('properties.checkin.0'));
|
||||
} catch (\Exception $e) {
|
||||
$data['checkin'] = null;
|
||||
}
|
||||
$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'));
|
||||
}
|
||||
} else {
|
||||
$data['content'] = $request->input('content');
|
||||
$data['in-reply-to'] = $request->input('in-reply-to');
|
||||
$data['location'] = $request->input('location');
|
||||
$data['published'] = $request->input('published');
|
||||
}
|
||||
$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';
|
||||
}
|
||||
}
|
||||
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';
|
||||
}
|
||||
|
@ -82,83 +116,145 @@ class MicropubController extends Controller
|
|||
$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');
|
||||
}
|
||||
$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') {
|
||||
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
|
||||
return $this->returnInsufficientScopeResponse();
|
||||
}
|
||||
$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);
|
||||
}
|
||||
if ($request->input('action') == 'update') {
|
||||
if (stristr($tokenData->getClaim('scope'), 'update') === false) {
|
||||
return $this->returnInsufficientScopeResponse();
|
||||
}
|
||||
$urlPath = parse_url($request->input('url'), PHP_URL_PATH);
|
||||
//is it a note we are updating?
|
||||
if (mb_substr($urlPath, 1, 5) === 'notes') {
|
||||
try {
|
||||
$note = Note::nb60(basename($urlPath))->first();
|
||||
} catch (\Exception $exception) {
|
||||
return response()->json([
|
||||
'error' => 'invalid_request',
|
||||
'error_description' => 'No known note with given ID',
|
||||
]);
|
||||
}
|
||||
//got the note, are we dealing with a “replace” request?
|
||||
if ($request->has('replace')) {
|
||||
foreach ($request->input('replace') as $property => $value) {
|
||||
if ($property == 'content') {
|
||||
$note->note = $value[0];
|
||||
}
|
||||
} 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');
|
||||
if ($property == 'syndication') {
|
||||
foreach ($value as $syndicationURL) {
|
||||
if (starts_with($syndicationURL, 'https://www.facebook.com')) {
|
||||
$note->facebook_url = $syndicationURL;
|
||||
}
|
||||
if (starts_with($syndicationURL, 'https://www.swarmapp.com')) {
|
||||
$note->swarm_url = $syndicationURL;
|
||||
}
|
||||
if (starts_with($syndicationURL, 'https://twitter.com')) {
|
||||
$note->tweet_id = basename(parse_url($syndicationURL, PHP_URL_PATH));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
$place = $this->placeService->createPlace($data);
|
||||
} catch (Exception $exception) {
|
||||
return response()->json(['error' => true], 400);
|
||||
}
|
||||
$note->save();
|
||||
|
||||
return response()->json([
|
||||
'response' => 'created',
|
||||
'location' => $place->longurl,
|
||||
], 201)->header('Location', $place->longurl);
|
||||
'response' => 'updated',
|
||||
]);
|
||||
}
|
||||
//how about “add”
|
||||
if ($request->has('add')) {
|
||||
foreach ($request->input('add') as $property => $value) {
|
||||
if ($property == 'syndication') {
|
||||
foreach ($value as $syndicationURL) {
|
||||
if (starts_with($syndicationURL, 'https://www.facebook.com')) {
|
||||
$note->facebook_url = $syndicationURL;
|
||||
}
|
||||
if (starts_with($syndicationURL, 'https://www.swarmapp.com')) {
|
||||
$note->swarm_url = $syndicationURL;
|
||||
}
|
||||
if (starts_with($syndicationURL, 'https://twitter.com')) {
|
||||
$note->tweet_id = basename(parse_url($syndicationURL, PHP_URL_PATH));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($property == 'photo') {
|
||||
foreach ($value as $photoURL) {
|
||||
if (start_with($photo, 'https://')) {
|
||||
$media = new Media();
|
||||
$media->path = $photoURL;
|
||||
$media->type = 'image';
|
||||
$media->save();
|
||||
$note->media()->save($media);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$note->save();
|
||||
|
||||
return response()->json([
|
||||
'response' => 'updated',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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',
|
||||
], 400);
|
||||
'error' => 'forbidden',
|
||||
'error_description' => 'The token has no scopes',
|
||||
], 403);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -172,68 +268,58 @@ 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
|
||||
try {
|
||||
$tokenData = $this->tokenService->validateToken($request->bearerToken());
|
||||
} catch (InvalidTokenException $e) {
|
||||
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' => $tokenData->getClaim('me'),
|
||||
'scope' => $tokenData->getClaim('scope'),
|
||||
'client_id' => $tokenData->getClaim('client_id'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -244,82 +330,65 @@ 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);
|
||||
try {
|
||||
$tokenData = $this->tokenService->validateToken($request->bearerToken());
|
||||
} catch (InvalidTokenException $e) {
|
||||
return response()->json([
|
||||
'response' => 'error',
|
||||
'error' => 'invalid_token',
|
||||
'error_description' => 'The provided token did not pass validation',
|
||||
], 400);
|
||||
}
|
||||
|
||||
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')) {
|
||||
if (stristr($token->getClaim('scope'), 'post') === false) {
|
||||
return $this->returnInsufficientScopeResponse();
|
||||
}
|
||||
|
||||
//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();
|
||||
|
||||
return response()->json([
|
||||
'response' => 'created',
|
||||
'location' => $media->url,
|
||||
], 201)->header('Location', $media->url);
|
||||
}
|
||||
|
||||
//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' => 'invalid_request',
|
||||
'error_description' => 'The uploaded file failed validation',
|
||||
], 400);
|
||||
'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 = $request->bearerToken();
|
||||
$media->path = $path;
|
||||
$media->type = $type;
|
||||
$media->save();
|
||||
|
||||
return response()->json([
|
||||
'response' => 'error',
|
||||
'error' => 'insufficient_scope',
|
||||
'error_description' => 'The provided token has insufficient scopes',
|
||||
], 401);
|
||||
'response' => 'created',
|
||||
'location' => $media->url,
|
||||
], 201)->header('Location', $media->url);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'response' => 'error',
|
||||
'error' => 'unauthorized',
|
||||
'error_description' => 'No token provided with request',
|
||||
], 401);
|
||||
'error' => 'invalid_request',
|
||||
'error_description' => 'The uploaded file failed validation',
|
||||
], 400);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'response' => 'error',
|
||||
'error' => 'no_token',
|
||||
'error_description' => 'There was no token provided with the request',
|
||||
'error' => 'invalid_request',
|
||||
'error_description' => 'The provided token has no scopes',
|
||||
], 400);
|
||||
}
|
||||
|
||||
|
@ -366,4 +435,13 @@ class MicropubController extends Controller
|
|||
|
||||
return 'download';
|
||||
}
|
||||
|
||||
private function returnInsufficientScopeResponse()
|
||||
{
|
||||
return response()->json([
|
||||
'response' => 'error',
|
||||
'error' => 'insufficient_scope',
|
||||
'error_description' => 'The token’s scope does not have the necessary requirements.',
|
||||
], 401);
|
||||
}
|
||||
}
|
||||
|
|
79
app/Http/Controllers/TokenEndpointController.php
Normal file
79
app/Http/Controllers/TokenEndpointController.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use IndieAuth\Client;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\TokenService;
|
||||
|
||||
class TokenEndpointController extends Controller
|
||||
{
|
||||
/**
|
||||
* The IndieAuth Client.
|
||||
*/
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* The Token handling service.
|
||||
*/
|
||||
protected $tokenService;
|
||||
|
||||
/**
|
||||
* Inject the dependencies.
|
||||
*
|
||||
* @param \IndieAuth\Client $client
|
||||
* @param \App\Services\TokenService $tokenService
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
Client $client = null,
|
||||
TokenService $tokenService = null
|
||||
) {
|
||||
$this->client = $client ?? new Client();
|
||||
$this->tokenService = $tokenService ?? new TokenService();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user has auth’d via the IndieAuth protocol, issue a valid token.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$authorizationEndpoint = $this->client->discoverAuthorizationEndpoint(normalize_url($request->input('me')));
|
||||
if ($authorizationEndpoint) {
|
||||
$auth = $this->client->verifyIndieAuthCode(
|
||||
$authorizationEndpoint,
|
||||
$request->input('code'),
|
||||
$request->input('me'),
|
||||
$request->input('redirect_uri'),
|
||||
$request->input('client_id'),
|
||||
$request->input('state')
|
||||
);
|
||||
if (array_key_exists('me', $auth)) {
|
||||
$scope = $auth['scope'] ?? '';
|
||||
$tokenData = [
|
||||
'me' => $request->input('me'),
|
||||
'client_id' => $request->input('client_id'),
|
||||
'scope' => $scope,
|
||||
];
|
||||
$token = $this->tokenService->getNewToken($tokenData);
|
||||
$content = http_build_query([
|
||||
'me' => $request->input('me'),
|
||||
'scope' => $scope,
|
||||
'access_token' => $token,
|
||||
]);
|
||||
|
||||
return response($content)->header(
|
||||
'Content-Type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
}
|
||||
|
||||
return response('There was an error verifying the authorisation code.', 400);
|
||||
}
|
||||
|
||||
return response('Can’t determine the authorisation endpoint.', 400);
|
||||
}
|
||||
}
|
|
@ -34,7 +34,8 @@ class Kernel extends HttpKernel
|
|||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\LinkHeadersMiddleware::class,
|
||||
\App\Http\Middleware\DevTokenMiddleware::class,
|
||||
//\App\Http\Middleware\DevTokenMiddleware::class,
|
||||
\App\Http\Middleware\LocalhostSessionMiddleware::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
|
@ -58,5 +59,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,
|
||||
];
|
||||
}
|
||||
|
|
26
app/Http/Middleware/LocalhostSessionMiddleware.php
Normal file
26
app/Http/Middleware/LocalhostSessionMiddleware.php
Normal 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 can’t 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);
|
||||
}
|
||||
}
|
28
app/Http/Middleware/VerifyMicropubToken.php
Normal file
28
app/Http/Middleware/VerifyMicropubToken.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
|
||||
class VerifyMicropubToken
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
if ($request->bearerToken() === null) {
|
||||
return response()->json([
|
||||
'response' => 'error',
|
||||
'error' => 'unauthorized',
|
||||
'error_description' => 'No access token was provided in the request',
|
||||
], 401);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
15
app/IndieWebUser.php
Normal file
15
app/IndieWebUser.php
Normal 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'];
|
||||
}
|
|
@ -28,6 +28,10 @@ class Media extends Model
|
|||
*/
|
||||
public function getUrlAttribute()
|
||||
{
|
||||
if (starts_with($this->path, 'https://')) {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
return config('filesystems.disks.s3.url') . '/' . $this->path;
|
||||
}
|
||||
}
|
||||
|
|
14
app/Note.php
14
app/Note.php
|
@ -191,6 +191,20 @@ class Note extends Model
|
|||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to select a note via a NewBase60 id.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $nb60id
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeNb60($query, $nb60id)
|
||||
{
|
||||
$numbers = new Numbers();
|
||||
|
||||
return $query->where('id', $numbers->b60tonum($nb60id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Take note that this method does two things, given @username (NOT [@username](URL)!)
|
||||
* we try to create a fancy hcard from our contact info. If this is not possible
|
||||
|
|
|
@ -33,7 +33,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
//Add tags for notes
|
||||
Note::created(function ($note) {
|
||||
$tagsToAdd = [];
|
||||
preg_match_all('/#([^\s<>]+)\b/', $note, $tags);
|
||||
preg_match_all('/#([^\s<>]+)\b/', $note->note, $tags);
|
||||
foreach ($tags[1] as $tag) {
|
||||
$tag = Tag::normalizeTag($tag);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ class EventServiceProvider extends ServiceProvider
|
|||
* @var array
|
||||
*/
|
||||
protected $listen = [
|
||||
'App\Events\SomeEvent' => [
|
||||
'App\Events\Event' => [
|
||||
'App\Listeners\EventListener',
|
||||
],
|
||||
];
|
||||
|
|
|
@ -45,14 +45,12 @@ class IndieAuthService
|
|||
session(['state' => $state]);
|
||||
$redirectURL = route('indieauth-callback');
|
||||
$clientId = route('micropub-client');
|
||||
$scope = 'post';
|
||||
$authorizationURL = $this->client->buildAuthorizationURL(
|
||||
$authEndpoint,
|
||||
$this->client->normalizeMeURL($domain),
|
||||
$redirectURL,
|
||||
$clientId,
|
||||
$state,
|
||||
$scope
|
||||
$state
|
||||
);
|
||||
|
||||
return $authorizationURL;
|
||||
|
|
|
@ -17,6 +17,16 @@ class NoteService
|
|||
*/
|
||||
public function createNote(array $data): Note
|
||||
{
|
||||
//check the input
|
||||
if (array_key_exists('content', $data) === false) {
|
||||
throw new \Exception('No content defined'); //we can’t fudge the data
|
||||
}
|
||||
if (array_key_exists('in-reply-to', $data) === false) {
|
||||
$data['in-reply-to'] = null;
|
||||
}
|
||||
if (array_key_exists('client-id', $data) === false) {
|
||||
$data['client-id'] = null;
|
||||
}
|
||||
$note = Note::create(
|
||||
[
|
||||
'note' => $data['content'],
|
||||
|
@ -25,6 +35,11 @@ class NoteService
|
|||
]
|
||||
);
|
||||
|
||||
if (array_key_exists('published', $data) && empty($data['published']) === false) {
|
||||
$carbon = new \Carbon\Carbon($data['published']);
|
||||
$note->created_at = $note->updated_at = $carbon->toDateTimeString();
|
||||
}
|
||||
|
||||
if (array_key_exists('location', $data) && $data['location'] !== null && $data['location'] !== 'no-location') {
|
||||
if (starts_with($data['location'], config('app.url'))) {
|
||||
//uri of form http://host/places/slug, we want slug
|
||||
|
@ -44,6 +59,13 @@ class NoteService
|
|||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('checkin', $data) && $data['checkin'] !== null) {
|
||||
$place = Place::where('foursquare', $data['checkin'])->first();
|
||||
if ($place !== null) {
|
||||
$note->place()->associate($place);
|
||||
}
|
||||
}
|
||||
|
||||
/* drop image support for now
|
||||
//add images to media library
|
||||
if ($request->hasFile('photo')) {
|
||||
|
@ -55,12 +77,17 @@ class NoteService
|
|||
*/
|
||||
//add support for media uploaded as URLs
|
||||
foreach ($data['photo'] as $photo) {
|
||||
// check the media was uploaded to my endpoint
|
||||
// check the media was uploaded to my endpoint, and use path
|
||||
if (starts_with($photo, config('filesystems.disks.s3.url'))) {
|
||||
$path = substr($photo, strlen(config('filesystems.disks.s3.url')));
|
||||
$media = Media::where('path', ltrim($path, '/'))->firstOrFail();
|
||||
$note->media()->save($media);
|
||||
} else {
|
||||
$media = Media::firstOrNew(['path' => $photo]);
|
||||
// currently assuming this is a photo from Swarm
|
||||
$media->type = 'image';
|
||||
$media->save();
|
||||
}
|
||||
$note->media()->save($media);
|
||||
}
|
||||
|
||||
$note->save();
|
||||
|
|
|
@ -36,4 +36,39 @@ class PlaceService
|
|||
|
||||
return $place;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a place from a h-card checkin, for exameple from OwnYourSwarm.
|
||||
*
|
||||
* @param array
|
||||
* @return bool
|
||||
*/
|
||||
public function createPlaceFromCheckin(array $checkin): bool
|
||||
{
|
||||
//check if the place exists if from swarm
|
||||
if (array_key_exists('url', $checkin['properties'])) {
|
||||
$search = Place::where('foursquare', $checkin['properties']['url'][0])->count();
|
||||
if ($search === 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (array_key_exists('name', $checkin['properties']) === false) {
|
||||
throw new \InvalidArgumentException('Missing required name');
|
||||
}
|
||||
if (array_key_exists('latitude', $checkin['properties']) === false) {
|
||||
throw new \InvalidArgumentException('Missing required longitude/latitude');
|
||||
}
|
||||
$place = new Place();
|
||||
$place->name = $checkin['properties']['name'][0];
|
||||
if (starts_with($checkin['properties']['url'][0], 'https://foursquare.com')) {
|
||||
$place->foursquare = $checkin['properties']['url'][0];
|
||||
}
|
||||
$place->location = new Point(
|
||||
(float) $checkin['properties']['latitude'][0],
|
||||
(float) $checkin['properties']['longitude'][0]
|
||||
);
|
||||
$place->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use RuntimeException;
|
||||
use Lcobucci\JWT\Token;
|
||||
use Lcobucci\JWT\Parser;
|
||||
use Lcobucci\JWT\Builder;
|
||||
use InvalidArgumentException;
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha256;
|
||||
use App\Exceptions\InvalidTokenException;
|
||||
use Lcobucci\JWT\{Builder, Parser, Token};
|
||||
|
||||
class TokenService
|
||||
{
|
||||
|
@ -39,17 +36,18 @@ class TokenService
|
|||
* @param string The token
|
||||
* @return mixed
|
||||
*/
|
||||
public function validateToken(string $token): ?Token
|
||||
public function validateToken(string $bearerToken): ?Token
|
||||
{
|
||||
$signer = new Sha256();
|
||||
try {
|
||||
$token = (new Parser())->parse((string) $token);
|
||||
} catch (InvalidArgumentException | RuntimeException $e) {
|
||||
return null;
|
||||
$token = (new Parser())->parse((string) $bearerToken);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new InvalidTokenException('Token could not be parsed');
|
||||
}
|
||||
if ($token->verify($signer, config('app.key'))) {
|
||||
//signuture valid
|
||||
return $token;
|
||||
if (! $token->verify($signer, config('app.key'))) {
|
||||
throw new InvalidTokenException('Token failed verification');
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
# Changelog
|
||||
|
||||
## Version 0.5 (2017-06-18)
|
||||
- Update micropub client to allow indieweb users
|
||||
- Update micropub endpoint to allow for entry updates
|
||||
- Add support for checkins, so we can use ownyourswarm
|
||||
|
||||
## Version 0.4.2 (2017-03-24)
|
||||
- fixed issue#47, only the slug was being sent by client, which was messing up endpoint code
|
||||
- minor changes to es6 code, bet lint-staged working again
|
||||
|
|
|
@ -42,7 +42,10 @@
|
|||
],
|
||||
"psr-4": {
|
||||
"App\\": "app/"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"helpers.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
|
@ -67,6 +70,7 @@
|
|||
},
|
||||
"config": {
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true
|
||||
"sort-packages": true,
|
||||
"optimize-autoloader": true
|
||||
}
|
||||
}
|
||||
|
|
381
composer.lock
generated
381
composer.lock
generated
|
@ -8,16 +8,16 @@
|
|||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.24.7",
|
||||
"version": "3.27.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "f062d7ea2123fe2aefef91da855c10ef8ff3af1c"
|
||||
"reference": "eb10e43cccf8e868f9622ab8ce2beb9fb756b5a8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f062d7ea2123fe2aefef91da855c10ef8ff3af1c",
|
||||
"reference": "f062d7ea2123fe2aefef91da855c10ef8ff3af1c",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/eb10e43cccf8e868f9622ab8ce2beb9fb756b5a8",
|
||||
"reference": "eb10e43cccf8e868f9622ab8ce2beb9fb756b5a8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -39,7 +39,7 @@
|
|||
"ext-simplexml": "*",
|
||||
"ext-spl": "*",
|
||||
"nette/neon": "^2.3",
|
||||
"phpunit/phpunit": "~4.0|~5.0",
|
||||
"phpunit/phpunit": "^4.8.35|^5.4.0",
|
||||
"psr/cache": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
|
@ -84,7 +84,7 @@
|
|||
"s3",
|
||||
"sdk"
|
||||
],
|
||||
"time": "2017-03-23T22:17:20+00:00"
|
||||
"time": "2017-05-11T21:23:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "barnabywalters/mf-cleaner",
|
||||
|
@ -180,6 +180,65 @@
|
|||
],
|
||||
"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",
|
||||
"version": "0.1",
|
||||
|
@ -685,16 +744,16 @@
|
|||
},
|
||||
{
|
||||
"name": "erusev/parsedown",
|
||||
"version": "1.6.1",
|
||||
"version": "1.6.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/erusev/parsedown.git",
|
||||
"reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb"
|
||||
"reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/erusev/parsedown/zipball/20ff8bbb57205368b4b42d094642a3e52dac85fb",
|
||||
"reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb",
|
||||
"url": "https://api.github.com/repos/erusev/parsedown/zipball/1bf24f7334fe16c88bf9d467863309ceaf285b01",
|
||||
"reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -723,7 +782,7 @@
|
|||
"markdown",
|
||||
"parser"
|
||||
],
|
||||
"time": "2016-11-02T15:56:58+00:00"
|
||||
"time": "2017-03-29T16:04:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ezyang/htmlpurifier",
|
||||
|
@ -1045,16 +1104,16 @@
|
|||
},
|
||||
{
|
||||
"name": "indieauth/client",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/indieweb/indieauth-client-php.git",
|
||||
"reference": "4b9bd766a92b8abbe420f5889bf7ebac7678151d"
|
||||
"reference": "f5f6efad79334d1ff9370fe4dce8ccf4814820fa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/indieweb/indieauth-client-php/zipball/4b9bd766a92b8abbe420f5889bf7ebac7678151d",
|
||||
"reference": "4b9bd766a92b8abbe420f5889bf7ebac7678151d",
|
||||
"url": "https://api.github.com/repos/indieweb/indieauth-client-php/zipball/f5f6efad79334d1ff9370fe4dce8ccf4814820fa",
|
||||
"reference": "f5f6efad79334d1ff9370fe4dce8ccf4814820fa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1080,7 +1139,7 @@
|
|||
}
|
||||
],
|
||||
"description": "IndieAuth Client Library",
|
||||
"time": "2017-02-09T23:42:05+00:00"
|
||||
"time": "2017-04-26T21:44:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "indieweb/link-rel-parser",
|
||||
|
@ -1405,16 +1464,16 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v5.4.16",
|
||||
"version": "v5.4.23",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "6cf379ec34d08bcdc9c7183e369a8fdf04ade80d"
|
||||
"reference": "ad82327705658dbf5f0ce72805caa950dfbe150d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/6cf379ec34d08bcdc9c7183e369a8fdf04ade80d",
|
||||
"reference": "6cf379ec34d08bcdc9c7183e369a8fdf04ade80d",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/ad82327705658dbf5f0ce72805caa950dfbe150d",
|
||||
"reference": "ad82327705658dbf5f0ce72805caa950dfbe150d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1530,20 +1589,20 @@
|
|||
"framework",
|
||||
"laravel"
|
||||
],
|
||||
"time": "2017-03-21T19:34:41+00:00"
|
||||
"time": "2017-05-11T20:10:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/scout",
|
||||
"version": "v3.0.2",
|
||||
"version": "v3.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/scout.git",
|
||||
"reference": "1ddb0fa6f165bf6a69864960102062e7cf3f989d"
|
||||
"reference": "64d28db58a054174eadf1d4df38dad81ff7e68dd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/scout/zipball/1ddb0fa6f165bf6a69864960102062e7cf3f989d",
|
||||
"reference": "1ddb0fa6f165bf6a69864960102062e7cf3f989d",
|
||||
"url": "https://api.github.com/repos/laravel/scout/zipball/64d28db58a054174eadf1d4df38dad81ff7e68dd",
|
||||
"reference": "64d28db58a054174eadf1d4df38dad81ff7e68dd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1590,7 +1649,7 @@
|
|||
"laravel",
|
||||
"search"
|
||||
],
|
||||
"time": "2017-03-01T14:37:40+00:00"
|
||||
"time": "2017-04-09T00:54:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/tinker",
|
||||
|
@ -1710,16 +1769,16 @@
|
|||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "0.15.3",
|
||||
"version": "0.15.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/commonmark.git",
|
||||
"reference": "c8b43ee5821362216f8e9ac684f0f59de164edcc"
|
||||
"reference": "c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c8b43ee5821362216f8e9ac684f0f59de164edcc",
|
||||
"reference": "c8b43ee5821362216f8e9ac684f0f59de164edcc",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c",
|
||||
"reference": "c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1775,20 +1834,20 @@
|
|||
"markdown",
|
||||
"parser"
|
||||
],
|
||||
"time": "2016-12-19T00:11:43+00:00"
|
||||
"time": "2017-05-09T12:47:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem",
|
||||
"version": "1.0.37",
|
||||
"version": "1.0.40",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/flysystem.git",
|
||||
"reference": "78b5cc4feb61a882302df4fbaf63b7662e5e4ccd"
|
||||
"reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/78b5cc4feb61a882302df4fbaf63b7662e5e4ccd",
|
||||
"reference": "78b5cc4feb61a882302df4fbaf63b7662e5e4ccd",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3828f0b24e2c1918bb362d57a53205d6dc8fde61",
|
||||
"reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1810,12 +1869,12 @@
|
|||
"league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
|
||||
"league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
|
||||
"league/flysystem-copy": "Allows you to use Copy.com storage",
|
||||
"league/flysystem-dropbox": "Allows you to use Dropbox storage",
|
||||
"league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
|
||||
"league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
|
||||
"league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
|
||||
"league/flysystem-webdav": "Allows you to use WebDAV storage",
|
||||
"league/flysystem-ziparchive": "Allows you to use ZipArchive adapter"
|
||||
"league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
|
||||
"spatie/flysystem-dropbox": "Allows you to use Dropbox storage"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
|
@ -1858,25 +1917,25 @@
|
|||
"sftp",
|
||||
"storage"
|
||||
],
|
||||
"time": "2017-03-22T15:43:14+00:00"
|
||||
"time": "2017-04-28T10:15:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem-aws-s3-v3",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.15",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
|
||||
"reference": "dc56a8faf3aff0841f9eae04b6af94a50657896c"
|
||||
"reference": "c947f36f977b495a57e857ae1630df0da35ec456"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/dc56a8faf3aff0841f9eae04b6af94a50657896c",
|
||||
"reference": "dc56a8faf3aff0841f9eae04b6af94a50657896c",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/c947f36f977b495a57e857ae1630df0da35ec456",
|
||||
"reference": "c947f36f977b495a57e857ae1630df0da35ec456",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"aws/aws-sdk-php": "^3.0.0",
|
||||
"league/flysystem": "~1.0",
|
||||
"league/flysystem": "^1.0.40",
|
||||
"php": ">=5.5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
|
@ -1905,7 +1964,7 @@
|
|||
}
|
||||
],
|
||||
"description": "Flysystem adapter for the AWS S3 SDK v3.x",
|
||||
"time": "2016-06-21T21:34:35+00:00"
|
||||
"time": "2017-04-28T10:21:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "martinbean/laravel-sluggable-trait",
|
||||
|
@ -2658,16 +2717,16 @@
|
|||
},
|
||||
{
|
||||
"name": "ramsey/uuid",
|
||||
"version": "3.6.0",
|
||||
"version": "3.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ramsey/uuid.git",
|
||||
"reference": "0b7bdfb180e72c8d76e75a649ced67e392201458"
|
||||
"reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/0b7bdfb180e72c8d76e75a649ced67e392201458",
|
||||
"reference": "0b7bdfb180e72c8d76e75a649ced67e392201458",
|
||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/4ae32dd9ab8860a4bbd750ad269cba7f06f7934e",
|
||||
"reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2679,9 +2738,9 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"apigen/apigen": "^4.1",
|
||||
"codeception/aspect-mock": "1.0.0",
|
||||
"codeception/aspect-mock": "^1.0 | ^2.0",
|
||||
"doctrine/annotations": "~1.2.0",
|
||||
"goaop/framework": "1.0.0-alpha.2",
|
||||
"goaop/framework": "1.0.0-alpha.2 | ^1.0 | ^2.1",
|
||||
"ircmaxell/random-lib": "^1.1",
|
||||
"jakub-onderka/php-parallel-lint": "^0.9.0",
|
||||
"mockery/mockery": "^0.9.4",
|
||||
|
@ -2736,23 +2795,24 @@
|
|||
"identifier",
|
||||
"uuid"
|
||||
],
|
||||
"time": "2017-03-18T15:38:09+00:00"
|
||||
"time": "2017-03-26T20:37:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sensiolabs/security-checker",
|
||||
"version": "v4.0.2",
|
||||
"version": "v4.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sensiolabs/security-checker.git",
|
||||
"reference": "56bded66985e22f6eac2cf86735fd21c625bff2f"
|
||||
"reference": "9e69eddf3bc49d1ee5c7908564da3141796d4bbc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/56bded66985e22f6eac2cf86735fd21c625bff2f",
|
||||
"reference": "56bded66985e22f6eac2cf86735fd21c625bff2f",
|
||||
"url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/9e69eddf3bc49d1ee5c7908564da3141796d4bbc",
|
||||
"reference": "9e69eddf3bc49d1ee5c7908564da3141796d4bbc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer/ca-bundle": "^1.0",
|
||||
"symfony/console": "~2.7|~3.0"
|
||||
},
|
||||
"bin": [
|
||||
|
@ -2780,20 +2840,20 @@
|
|||
}
|
||||
],
|
||||
"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",
|
||||
"version": "v5.4.6",
|
||||
"version": "v5.4.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/swiftmailer/swiftmailer.git",
|
||||
"reference": "81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e"
|
||||
"reference": "9a06dc570a0367850280eefd3f1dc2da45aef517"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e",
|
||||
"reference": "81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e",
|
||||
"url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/9a06dc570a0367850280eefd3f1dc2da45aef517",
|
||||
"reference": "9a06dc570a0367850280eefd3f1dc2da45aef517",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2834,20 +2894,20 @@
|
|||
"mail",
|
||||
"mailer"
|
||||
],
|
||||
"time": "2017-02-13T07:52:53+00:00"
|
||||
"time": "2017-05-01T15:54:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v3.2.6",
|
||||
"version": "v3.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "28fb243a2b5727774ca309ec2d92da240f1af0dd"
|
||||
"reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/28fb243a2b5727774ca309ec2d92da240f1af0dd",
|
||||
"reference": "28fb243a2b5727774ca309ec2d92da240f1af0dd",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38",
|
||||
"reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2897,20 +2957,20 @@
|
|||
],
|
||||
"description": "Symfony Console Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-03-06T19:30:27+00:00"
|
||||
"time": "2017-04-26T01:39:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
"version": "v3.2.6",
|
||||
"version": "v3.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/css-selector.git",
|
||||
"reference": "a48f13dc83c168f1253a5d2a5a4fb46c36244c4c"
|
||||
"reference": "02983c144038e697c959e6b06ef6666de759ccbc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/a48f13dc83c168f1253a5d2a5a4fb46c36244c4c",
|
||||
"reference": "a48f13dc83c168f1253a5d2a5a4fb46c36244c4c",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/02983c144038e697c959e6b06ef6666de759ccbc",
|
||||
"reference": "02983c144038e697c959e6b06ef6666de759ccbc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2950,20 +3010,20 @@
|
|||
],
|
||||
"description": "Symfony CssSelector Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-02-21T09:12:04+00:00"
|
||||
"time": "2017-05-01T14:55:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
"version": "v3.2.6",
|
||||
"version": "v3.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/debug.git",
|
||||
"reference": "b90c9f91ad8ac37d9f114e369042d3226b34dc1a"
|
||||
"reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/b90c9f91ad8ac37d9f114e369042d3226b34dc1a",
|
||||
"reference": "b90c9f91ad8ac37d9f114e369042d3226b34dc1a",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/fd6eeee656a5a7b384d56f1072243fe1c0e81686",
|
||||
"reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3007,20 +3067,20 @@
|
|||
],
|
||||
"description": "Symfony Debug Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-02-18T17:28:00+00:00"
|
||||
"time": "2017-04-19T20:17:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v3.2.6",
|
||||
"version": "v3.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
"reference": "b7a1b9e0a0f623ce43b4c8d775eb138f190c9d8d"
|
||||
"reference": "b8a401f733b43251e1d088c589368b2a94155e40"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7a1b9e0a0f623ce43b4c8d775eb138f190c9d8d",
|
||||
"reference": "b7a1b9e0a0f623ce43b4c8d775eb138f190c9d8d",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b8a401f733b43251e1d088c589368b2a94155e40",
|
||||
"reference": "b8a401f733b43251e1d088c589368b2a94155e40",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3067,20 +3127,20 @@
|
|||
],
|
||||
"description": "Symfony EventDispatcher Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-02-21T09:12:04+00:00"
|
||||
"time": "2017-05-01T14:58:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v3.2.6",
|
||||
"version": "v3.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10"
|
||||
"reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/92d7476d2df60cd851a3e13e078664b1deb8ce10",
|
||||
"reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/9cf076f8f492f4b1ffac40aae9c2d287b4ca6930",
|
||||
"reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3116,20 +3176,20 @@
|
|||
],
|
||||
"description": "Symfony Finder Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-02-21T09:12:04+00:00"
|
||||
"time": "2017-04-12T14:13:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-foundation",
|
||||
"version": "v3.2.6",
|
||||
"version": "v3.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-foundation.git",
|
||||
"reference": "c57009887010eb4e58bfca2970314a5b820b24b9"
|
||||
"reference": "9de6add7f731e5af7f5b2e9c0da365e43383ebef"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/c57009887010eb4e58bfca2970314a5b820b24b9",
|
||||
"reference": "c57009887010eb4e58bfca2970314a5b820b24b9",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/9de6add7f731e5af7f5b2e9c0da365e43383ebef",
|
||||
"reference": "9de6add7f731e5af7f5b2e9c0da365e43383ebef",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3169,20 +3229,20 @@
|
|||
],
|
||||
"description": "Symfony HttpFoundation Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-03-04T12:23:14+00:00"
|
||||
"time": "2017-05-01T14:55:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-kernel",
|
||||
"version": "v3.2.6",
|
||||
"version": "v3.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-kernel.git",
|
||||
"reference": "bc909e85b8585c9edf043d0fca871308c41bb9b4"
|
||||
"reference": "46e8b209abab55c072c47d72d5cd1d62c0585e05"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/bc909e85b8585c9edf043d0fca871308c41bb9b4",
|
||||
"reference": "bc909e85b8585c9edf043d0fca871308c41bb9b4",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/46e8b209abab55c072c47d72d5cd1d62c0585e05",
|
||||
"reference": "46e8b209abab55c072c47d72d5cd1d62c0585e05",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3251,7 +3311,7 @@
|
|||
],
|
||||
"description": "Symfony HttpKernel Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-03-10T18:35:31+00:00"
|
||||
"time": "2017-05-01T17:46:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
|
@ -3314,16 +3374,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v3.2.6",
|
||||
"version": "v3.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "68bfa8c83f24c0ac04ea7193bcdcda4519f41892"
|
||||
"reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/68bfa8c83f24c0ac04ea7193bcdcda4519f41892",
|
||||
"reference": "68bfa8c83f24c0ac04ea7193bcdcda4519f41892",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0",
|
||||
"reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3359,20 +3419,20 @@
|
|||
],
|
||||
"description": "Symfony Process Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-03-04T12:23:14+00:00"
|
||||
"time": "2017-04-12T14:13:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/routing",
|
||||
"version": "v3.2.6",
|
||||
"version": "v3.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/routing.git",
|
||||
"reference": "d6605f9a5767bc5bc4895e1c762ba93964608aee"
|
||||
"reference": "5029745d6d463585e8b487dbc83d6333f408853a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/d6605f9a5767bc5bc4895e1c762ba93964608aee",
|
||||
"reference": "d6605f9a5767bc5bc4895e1c762ba93964608aee",
|
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/5029745d6d463585e8b487dbc83d6333f408853a",
|
||||
"reference": "5029745d6d463585e8b487dbc83d6333f408853a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3434,20 +3494,20 @@
|
|||
"uri",
|
||||
"url"
|
||||
],
|
||||
"time": "2017-03-02T15:58:09+00:00"
|
||||
"time": "2017-04-12T14:13:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
"version": "v3.2.6",
|
||||
"version": "v3.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/translation.git",
|
||||
"reference": "0e1b15ce8fbf3890f4ccdac430ed5e07fdfe0690"
|
||||
"reference": "f4a04d2df710f81515df576b2de06bdeee518b83"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/0e1b15ce8fbf3890f4ccdac430ed5e07fdfe0690",
|
||||
"reference": "0e1b15ce8fbf3890f4ccdac430ed5e07fdfe0690",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/f4a04d2df710f81515df576b2de06bdeee518b83",
|
||||
"reference": "f4a04d2df710f81515df576b2de06bdeee518b83",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3498,20 +3558,20 @@
|
|||
],
|
||||
"description": "Symfony Translation Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-03-04T12:23:14+00:00"
|
||||
"time": "2017-04-12T14:13:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v3.2.6",
|
||||
"version": "v3.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-dumper.git",
|
||||
"reference": "4100f347aff890bc16b0b4b42843b599db257b2d"
|
||||
"reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/4100f347aff890bc16b0b4b42843b599db257b2d",
|
||||
"reference": "4100f347aff890bc16b0b4b42843b599db257b2d",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa47963ac7979ddbd42b2d646d1b056bddbf7bb8",
|
||||
"reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3522,9 +3582,11 @@
|
|||
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-iconv": "*",
|
||||
"twig/twig": "~1.20|~2.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
|
||||
"ext-symfony_debug": ""
|
||||
},
|
||||
"type": "library",
|
||||
|
@ -3564,7 +3626,7 @@
|
|||
"debug",
|
||||
"dump"
|
||||
],
|
||||
"time": "2017-02-20T13:45:48+00:00"
|
||||
"time": "2017-05-01T14:55:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "themattharris/tmhoauth",
|
||||
|
@ -3610,16 +3672,16 @@
|
|||
},
|
||||
{
|
||||
"name": "thujohn/twitter",
|
||||
"version": "2.2.2",
|
||||
"version": "2.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thujohn/twitter.git",
|
||||
"reference": "203d903856212835206675ae9c0504d74b681886"
|
||||
"reference": "ff414bdadba3f1570ca211355e5359ec266552d8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thujohn/twitter/zipball/203d903856212835206675ae9c0504d74b681886",
|
||||
"reference": "203d903856212835206675ae9c0504d74b681886",
|
||||
"url": "https://api.github.com/repos/thujohn/twitter/zipball/ff414bdadba3f1570ca211355e5359ec266552d8",
|
||||
"reference": "ff414bdadba3f1570ca211355e5359ec266552d8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3650,7 +3712,7 @@
|
|||
"laravel5",
|
||||
"twitter"
|
||||
],
|
||||
"time": "2017-01-31T23:37:52+00:00"
|
||||
"time": "2017-04-27T09:00:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tijsverkoyen/css-to-inline-styles",
|
||||
|
@ -3861,16 +3923,16 @@
|
|||
},
|
||||
{
|
||||
"name": "facebook/webdriver",
|
||||
"version": "1.4.0",
|
||||
"version": "1.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/php-webdriver.git",
|
||||
"reference": "3ea034c056189e11c0ce7985332a9f4b5b2b5db2"
|
||||
"reference": "eadb0b7a7c3e6578185197fd40158b08c3164c83"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/facebook/php-webdriver/zipball/3ea034c056189e11c0ce7985332a9f4b5b2b5db2",
|
||||
"reference": "3ea034c056189e11c0ce7985332a9f4b5b2b5db2",
|
||||
"url": "https://api.github.com/repos/facebook/php-webdriver/zipball/eadb0b7a7c3e6578185197fd40158b08c3164c83",
|
||||
"reference": "eadb0b7a7c3e6578185197fd40158b08c3164c83",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3909,7 +3971,7 @@
|
|||
"selenium",
|
||||
"webdriver"
|
||||
],
|
||||
"time": "2017-03-22T10:56:03+00:00"
|
||||
"time": "2017-04-28T14:54:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fzaninotto/faker",
|
||||
|
@ -4053,21 +4115,22 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/dusk",
|
||||
"version": "v1.0.10",
|
||||
"version": "v1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/dusk.git",
|
||||
"reference": "11537ac1a939a2194e9e3cdc2536e6e34eff9ea6"
|
||||
"reference": "6b81e97ae1ce384e3d8dbd020b2b9751c1449889"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/dusk/zipball/11537ac1a939a2194e9e3cdc2536e6e34eff9ea6",
|
||||
"reference": "11537ac1a939a2194e9e3cdc2536e6e34eff9ea6",
|
||||
"url": "https://api.github.com/repos/laravel/dusk/zipball/6b81e97ae1ce384e3d8dbd020b2b9751c1449889",
|
||||
"reference": "6b81e97ae1ce384e3d8dbd020b2b9751c1449889",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"facebook/webdriver": "~1.0",
|
||||
"illuminate/support": "~5.3",
|
||||
"illuminate/console": "~5.4",
|
||||
"illuminate/support": "~5.4",
|
||||
"nesbot/carbon": "~1.20",
|
||||
"php": ">=5.6.4",
|
||||
"symfony/console": "~3.2",
|
||||
|
@ -4104,7 +4167,7 @@
|
|||
"testing",
|
||||
"webdriver"
|
||||
],
|
||||
"time": "2017-03-03T14:36:19+00:00"
|
||||
"time": "2017-04-23T17:13:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maximebf/debugbar",
|
||||
|
@ -4172,12 +4235,12 @@
|
|||
"version": "0.9.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/padraic/mockery.git",
|
||||
"url": "https://github.com/mockery/mockery.git",
|
||||
"reference": "6fdb61243844dc924071d3404bb23994ea0b6856"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/padraic/mockery/zipball/6fdb61243844dc924071d3404bb23994ea0b6856",
|
||||
"url": "https://api.github.com/repos/mockery/mockery/zipball/6fdb61243844dc924071d3404bb23994ea0b6856",
|
||||
"reference": "6fdb61243844dc924071d3404bb23994ea0b6856",
|
||||
"shasum": ""
|
||||
},
|
||||
|
@ -4234,16 +4297,16 @@
|
|||
},
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||
"reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe"
|
||||
"reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe",
|
||||
"reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102",
|
||||
"reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -4272,7 +4335,7 @@
|
|||
"object",
|
||||
"object graph"
|
||||
],
|
||||
"time": "2017-01-26T22:05:40+00:00"
|
||||
"time": "2017-04-12T18:52:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-common",
|
||||
|
@ -4485,16 +4548,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "4.0.7",
|
||||
"version": "4.0.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "09e2277d14ea467e5a984010f501343ef29ffc69"
|
||||
"reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/09e2277d14ea467e5a984010f501343ef29ffc69",
|
||||
"reference": "09e2277d14ea467e5a984010f501343ef29ffc69",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
|
||||
"reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -4544,7 +4607,7 @@
|
|||
"testing",
|
||||
"xunit"
|
||||
],
|
||||
"time": "2017-03-01T09:12:17+00:00"
|
||||
"time": "2017-04-02T07:44:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
|
@ -4734,16 +4797,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "5.7.17",
|
||||
"version": "5.7.19",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "68752b665d3875f9a38a357e3ecb35c79f8673bf"
|
||||
"reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/68752b665d3875f9a38a357e3ecb35c79f8673bf",
|
||||
"reference": "68752b665d3875f9a38a357e3ecb35c79f8673bf",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/69c4f49ff376af2692bad9cebd883d17ebaa98a1",
|
||||
"reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -4812,7 +4875,7 @@
|
|||
"testing",
|
||||
"xunit"
|
||||
],
|
||||
"time": "2017-03-19T16:52:12+00:00"
|
||||
"time": "2017-04-03T02:22:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit-mock-objects",
|
||||
|
@ -5477,16 +5540,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v3.2.6",
|
||||
"version": "v3.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a"
|
||||
"reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/093e416ad096355149e265ea2e4cc1f9ee40ab1a",
|
||||
"reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/acec26fcf7f3031e094e910b94b002fa53d4e4d6",
|
||||
"reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -5528,20 +5591,20 @@
|
|||
],
|
||||
"description": "Symfony Yaml Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-03-07T16:47:02+00:00"
|
||||
"time": "2017-05-01T14:55:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/fdomdocument",
|
||||
"version": "1.6.1",
|
||||
"version": "1.6.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/theseer/fDOMDocument.git",
|
||||
"reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684"
|
||||
"reference": "8dcfd392135a5bd938c3c83ea71419501ad9855d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/d9ad139d6c2e8edf5e313ffbe37ff13344cf0684",
|
||||
"reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684",
|
||||
"url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/8dcfd392135a5bd938c3c83ea71419501ad9855d",
|
||||
"reference": "8dcfd392135a5bd938c3c83ea71419501ad9855d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -5568,7 +5631,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.",
|
||||
"homepage": "https://github.com/theseer/fDOMDocument",
|
||||
"time": "2015-05-27T22:58:02+00:00"
|
||||
"time": "2017-04-21T14:50:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
|
|
|
@ -46,8 +46,9 @@ return [
|
|||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => 'utf8',
|
||||
'collation' => 'utf8_unicode_ci',
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'prefix' => '',
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
|
|
|
@ -14,7 +14,7 @@ return [
|
|||
*/
|
||||
|
||||
'paths' => [
|
||||
realpath(base_path('resources/views')),
|
||||
resource_path('views'),
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class UpdateNotesTableAddSwarmUrl extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('notes', function (Blueprint $table) {
|
||||
$table->string('swarm_url')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('notes', function (Blueprint $table) {
|
||||
$table->dropColumn('swarm_url');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class UpdatePlacesTableAddFoursquareColumn extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('places', function (Blueprint $table) {
|
||||
$table->string('foursquare')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('places', function (Blueprint $table) {
|
||||
$table->dropColumn('foursquare');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -17,5 +17,6 @@ class DatabaseSeeder extends Seeder
|
|||
$this->call(PlacesTableSeeder::class);
|
||||
$this->call(NotesTableSeeder::class);
|
||||
$this->call(WebMentionsTableSeeder::class);
|
||||
$this->call(IndieWebUserTableSeeder::class);
|
||||
}
|
||||
}
|
||||
|
|
16
database/seeds/IndieWebUserTableSeeder.php
Normal file
16
database/seeds/IndieWebUserTableSeeder.php
Normal 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')]);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
echo "Putting the Laravel app in maintenance mode"
|
||||
php artisan down
|
||||
|
||||
echo "Updating composer dependencies"
|
||||
echo "Updating composer and dependencies"
|
||||
composer self-update
|
||||
composer install
|
||||
|
||||
echo "Caching Laravel route and config files"
|
||||
|
|
199
helpers.php
Normal file
199
helpers.php
Normal file
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
helpers.php
|
||||
*/
|
||||
|
||||
// sourced from https://github.com/flattr/normalize-url/blob/master/normalize_url.php
|
||||
if (! function_exists('normalize_url')) {
|
||||
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']}";
|
||||
}
|
||||
|
||||
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']);
|
||||
}
|
||||
}
|
||||
// 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 ($url['path'] == '/') {
|
||||
unset($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
|
||||
if (! function_exists('prettyPrintJson')) {
|
||||
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++;
|
||||
//no break
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -6,15 +6,15 @@
|
|||
"license": "CC0-1.0",
|
||||
"dependencies": {
|
||||
"alertify.js": "^1.0.12",
|
||||
"mapbox-gl": "^0.34.0",
|
||||
"mapbox-gl": "0.37.0",
|
||||
"marked": "^0.3.6",
|
||||
"normalize.css": "^5.0.0",
|
||||
"normalize.css": "7.0.0",
|
||||
"webStorage": "^1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.18.0",
|
||||
"babel-core": "^6.21.0",
|
||||
"babel-loader": "^6.2.10",
|
||||
"babel-loader": "7.0.0",
|
||||
"babel-preset-env": "^1.2.2",
|
||||
"babel-preset-es2015": "^6.18.0",
|
||||
"babel-preset-latest": "^6.16.0",
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
192
public/assets/prism/prism.css
vendored
192
public/assets/prism/prism.css
vendored
|
@ -1,84 +1,79 @@
|
|||
/* 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
|
||||
* Based on the slides of the talk “/Reg(exp){2}lained/”
|
||||
* @author Lea Verou
|
||||
* okaidia theme for JavaScript, CSS and HTML
|
||||
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
|
||||
* @author ocodia
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: white;
|
||||
text-shadow: 0 -.1em .2em black;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
line-height: 1.5;
|
||||
color: #f8f8f2;
|
||||
background: none;
|
||||
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-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%);
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
border: .3em solid hsl(30, 20%, 40%);
|
||||
border-radius: .5em;
|
||||
box-shadow: 1px 1px .5em black inset;
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #272822;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .15em .2em .05em;
|
||||
border-radius: .3em;
|
||||
border: .13em solid hsl(30, 20%, 40%);
|
||||
box-shadow: 1px 1px .3em -.1em black inset;
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: hsl(30, 20%, 50%);
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
opacity: .7;
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol {
|
||||
color: hsl(350, 40%, 70%);
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
.token.boolean,
|
||||
.token.number {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
|
@ -87,7 +82,7 @@ pre[class*="language-"] {
|
|||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: hsl(75, 70%, 60%);
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
|
@ -96,93 +91,76 @@ pre[class*="language-"] {
|
|||
.language-css .token.string,
|
||||
.style .token.string,
|
||||
.token.variable {
|
||||
color: hsl(40, 90%, 60%);
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.function {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.token.keyword {
|
||||
color: hsl(350, 40%, 70%);
|
||||
color: #66d9ef;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: #e90;
|
||||
color: #fd971f;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.token.deleted {
|
||||
color: red;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
pre.line-numbers {
|
||||
position: relative;
|
||||
padding-left: 3.8em;
|
||||
counter-reset: linenumber;
|
||||
position: relative;
|
||||
padding-left: 3.8em;
|
||||
counter-reset: linenumber;
|
||||
}
|
||||
|
||||
pre.line-numbers > code {
|
||||
position: relative;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.line-numbers .line-numbers-rows {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
font-size: 100%;
|
||||
left: -3.8em;
|
||||
width: 3em; /* works for line-numbers below 1000 lines */
|
||||
letter-spacing: -1px;
|
||||
border-right: 1px solid #999;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
font-size: 100%;
|
||||
left: -3.8em;
|
||||
width: 3em; /* works for line-numbers below 1000 lines */
|
||||
letter-spacing: -1px;
|
||||
border-right: 1px solid #999;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
}
|
||||
|
||||
.line-numbers-rows > span {
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
counter-increment: linenumber;
|
||||
}
|
||||
.line-numbers-rows > span {
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
counter-increment: linenumber;
|
||||
}
|
||||
|
||||
.line-numbers-rows > span:before {
|
||||
content: counter(linenumber);
|
||||
color: #999;
|
||||
display: block;
|
||||
padding-right: 0.8em;
|
||||
text-align: right;
|
||||
}
|
||||
.token.tab:not(:empty):before,
|
||||
.token.cr:before,
|
||||
.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';
|
||||
.line-numbers-rows > span:before {
|
||||
content: counter(linenumber);
|
||||
color: #999;
|
||||
display: block;
|
||||
padding-right: 0.8em;
|
||||
text-align: right;
|
||||
}
|
||||
.token a {
|
||||
color: inherit;
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
52
public/assets/prism/prism.js
vendored
52
public/assets/prism/prism.js
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
@ -7,10 +7,34 @@ Micropub Config «
|
|||
@section('content')
|
||||
<p>The values for your micropub endpoint.</p>
|
||||
<dl>
|
||||
<dt>Me (your url)</dt><dd>{{ $data['me'] }}</dd>
|
||||
<dt>Token</dt><dd>{{ $data['token'] }}</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>Media Endpoint</dt><dd>{{ $data['media-endpoint'] }}</dd>
|
||||
<dt>Me (your url)</dt><dd><code>{{ $data['me'] }}</code></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>@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>
|
||||
<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>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
|
||||
|
|
|
@ -89,36 +89,37 @@ Route::group(['domain' => config('url.longurl')], function () {
|
|||
Route::get('blog/{year?}/{month?}', 'ArticlesController@index');
|
||||
Route::get('blog/{year}/{month}/{slug}', 'ArticlesController@show');
|
||||
|
||||
//micropub new notes page
|
||||
//this needs to be first so `notes/new` doesn't match `notes/{id}`
|
||||
|
||||
|
||||
//Notes pages using NotesController
|
||||
Route::get('notes', 'NotesController@index');
|
||||
Route::get('notes/{id}', 'NotesController@show');
|
||||
Route::get('note/{id}', 'NotesController@redirect');
|
||||
Route::get('notes/tagged/{tag}', 'NotesController@tagged');
|
||||
|
||||
//indieauth
|
||||
// IndieAuth
|
||||
Route::post('indieauth/start', 'IndieAuthController@start')->name('indieauth-start');
|
||||
Route::get('indieauth/callback', 'IndieAuthController@callback')->name('indieauth-callback');
|
||||
Route::get('logout', 'IndieAuthController@logout')->name('indieauth-logout');
|
||||
Route::post('api/token', 'IndieAuthController@tokenEndpoint'); //hmmm?
|
||||
|
||||
// Token Endpoint
|
||||
Route::post('api/token', 'TokenEndpointController@create');
|
||||
|
||||
// Micropub Client
|
||||
Route::get('micropub/create', 'MicropubClientController@create')->name('micropub-client');
|
||||
Route::post('micropub', 'MicropubClientController@store')->name('micropub-client-post');
|
||||
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::post('micropub/update-syntax', 'MicropubClientController@updateSyntax')->name('micropub-update-syntax');
|
||||
Route::get('micropub/places', 'MicropubClientController@nearbyPlaces');
|
||||
Route::post('micropub/places', 'MicropubClientController@newPlace');
|
||||
Route::post('micropub/media', 'MicropubClientController@processMedia')->name('process-media');
|
||||
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');
|
||||
// Micropub Endpoints
|
||||
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');
|
||||
|
|
2
storage/framework/testing/.gitignore
vendored
Normal file
2
storage/framework/testing/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
|
@ -22,13 +22,16 @@ class MicropubClientTest extends DuskTestCase
|
|||
|
||||
public function test_client_page_creates_new_note()
|
||||
{
|
||||
\Artisan::call('token:generate');
|
||||
$faker = \Faker\Factory::create();
|
||||
$note = 'Fake note from #LaravelDusk: ' . $faker->text;
|
||||
$this->browse(function ($browser) use ($note) {
|
||||
$browser->visit(route('micropub-client'))
|
||||
->assertSeeLink('log out')
|
||||
->type('content', $note)
|
||||
->press('Submit');
|
||||
});
|
||||
sleep(2);
|
||||
$this->assertDatabaseHas('notes', ['note' => $note]);
|
||||
$newNote = \App\Note::where('note', $note)->first();
|
||||
$newNote->forceDelete();
|
||||
|
|
|
@ -19,11 +19,11 @@ class MicropubControllerTest extends TestCase
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_micropub_request_without_token_returns_400_response()
|
||||
public function test_micropub_request_without_token_returns_401_response()
|
||||
{
|
||||
$response = $this->get('/api/post');
|
||||
$response->assertStatus(400);
|
||||
$response->assertJsonFragment(['error_description' => 'No token provided with request']);
|
||||
$response->assertStatus(401);
|
||||
$response->assertJsonFragment(['error_description' => 'No access token was provided in the request']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -202,9 +202,9 @@ class MicropubControllerTest extends TestCase
|
|||
$response
|
||||
->assertJson([
|
||||
'response' => 'error',
|
||||
'error' => 'no_token'
|
||||
'error' => 'unauthorized'
|
||||
])
|
||||
->assertStatus(400);
|
||||
->assertStatus(401);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -231,9 +231,9 @@ class MicropubControllerTest extends TestCase
|
|||
$response
|
||||
->assertJson([
|
||||
'response' => 'error',
|
||||
'error' => 'invalid_token'
|
||||
'error' => 'insufficient_scope'
|
||||
])
|
||||
->assertStatus(400);
|
||||
->assertStatus(401);
|
||||
}
|
||||
|
||||
public function test_micropub_request_with_json_syntax_creates_new_place()
|
||||
|
@ -276,6 +276,47 @@ class MicropubControllerTest extends TestCase
|
|||
->assertStatus(201);
|
||||
}
|
||||
|
||||
public function test_micropub_request_with_json_syntax_update_replace_post()
|
||||
{
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'action' => 'update',
|
||||
'url' => config('app.url') . '/notes/A',
|
||||
'replace' => [
|
||||
'content' => ['replaced content'],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response
|
||||
->assertJson(['response' => 'updated'])
|
||||
->assertStatus(200);
|
||||
}
|
||||
|
||||
public function test_micropub_request_with_json_syntax_update_add_post()
|
||||
{
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'action' => 'update',
|
||||
'url' => config('app.url') . '/notes/A',
|
||||
'add' => [
|
||||
'syndication' => ['https://www.swarmapp.com/checkin/123'],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response
|
||||
->assertJson(['response' => 'updated'])
|
||||
->assertStatus(200);
|
||||
$this->assertDatabaseHas('notes', [
|
||||
'swarm_url' => 'https://www.swarmapp.com/checkin/123'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a valid token to be used in the tests.
|
||||
*
|
||||
|
@ -287,7 +328,7 @@ class MicropubControllerTest extends TestCase
|
|||
$token = (new Builder())
|
||||
->set('client_id', 'https://quill.p3k.io')
|
||||
->set('me', 'https://jonnybarnes.localhost')
|
||||
->set('scope', 'post')
|
||||
->set('scope', 'create update')
|
||||
->set('issued_at', time())
|
||||
->sign($signer, env('APP_KEY'))
|
||||
->getToken();
|
||||
|
|
69
tests/Feature/SwarmTest.php
Normal file
69
tests/Feature/SwarmTest.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Lcobucci\JWT\Builder;
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha256;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
class SwarmTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_faked_ownyourswarm_request()
|
||||
{
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'api/post',
|
||||
[
|
||||
'type' => ['h-entry'],
|
||||
'properties' => [
|
||||
'published' => [\Carbon\Carbon::now()->toDateTimeString()],
|
||||
'syndication' => ['https://www.swarmapp.com/checkin/abc'],
|
||||
'content' => [[
|
||||
'value' => 'My first #checkin using Example Product',
|
||||
'html' => 'My first #checkin using <a href="http://example.org">Example Product</a>',
|
||||
]],
|
||||
'checkin' => [[
|
||||
'type' => ['h-card'],
|
||||
'properties' => [
|
||||
'name' => ['Awesome Venue'],
|
||||
'url' => ['https://foursquare.com/v/123456'],
|
||||
'latitude' => ['1.23'],
|
||||
'longitude' => ['4.56'],
|
||||
],
|
||||
]],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response
|
||||
->assertStatus(201)
|
||||
->assertJson(['response' => 'created']);
|
||||
$this->assertDatabaseHas('places', [
|
||||
'foursquare' => 'https://foursquare.com/v/123456'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a valid token to be used in the tests.
|
||||
*
|
||||
* @return Lcobucci\JWT\Token\Plain $token
|
||||
*/
|
||||
private function getToken()
|
||||
{
|
||||
$signer = new Sha256();
|
||||
$token = (new Builder())
|
||||
->set('client_id', 'https://ownyourswarm.p3k.io')
|
||||
->set('me', 'https://jonnybarnes.localhost')
|
||||
->set('scope', 'create update')
|
||||
->set('issued_at', time())
|
||||
->sign($signer, env('APP_KEY'))
|
||||
->getToken();
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
38
tests/Feature/TokenEndpointTest.php
Normal file
38
tests/Feature/TokenEndpointTest.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
use IndieAuth\Client;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
class TokenEndpointTest extends TestCase
|
||||
{
|
||||
public function test_token_endpoint_issues_token()
|
||||
{
|
||||
$mockClient = Mockery::mock(Client::class);
|
||||
$mockClient->shouldReceive('discoverAuthorizationEndpoint')
|
||||
->with(normalize_url(config('app.url')))
|
||||
->once()
|
||||
->andReturn('https://indieauth.com/auth');
|
||||
$mockClient->shouldReceive('verifyIndieAuthCode')
|
||||
->andReturn([
|
||||
'me' => config('app.url'),
|
||||
'scope' => 'create update',
|
||||
]);
|
||||
$this->app->instance(Client::class, $mockClient);
|
||||
$response = $this->post('/api/token', [
|
||||
'me' => config('app.url'),
|
||||
'code' => 'abc123',
|
||||
'redirect_uri' => route('indieauth-callback'),
|
||||
'client_id' => route('micropub-client'),
|
||||
'state' => mt_rand(1000, 10000),
|
||||
]);
|
||||
parse_str($response->content(), $output);
|
||||
$this->assertEquals(config('app.url'), $output['me']);
|
||||
$this->assertTrue(array_key_exists('access_token', $output));
|
||||
}
|
||||
}
|
69
tests/Unit/HelpersTest.php
Normal file
69
tests/Unit/HelpersTest.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
class HelpersTest extends TestCase
|
||||
{
|
||||
public function test_normalize_url_is_idempotent()
|
||||
{
|
||||
$input = 'http://example.org:80/index.php?foo=bar&baz=1';
|
||||
$this->assertEquals(normalize_url(normalize_url($input)), normalize_url($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider urlProvider
|
||||
*/
|
||||
public function test_normalize_url($input, $output)
|
||||
{
|
||||
$this->assertEquals($output, normalize_url($input));
|
||||
}
|
||||
|
||||
public function urlProvider()
|
||||
{
|
||||
return [
|
||||
['https://example.org/', 'https://example.org'],
|
||||
['https://example.org:443/', 'https://example.org'],
|
||||
['http://www.foo.bar/index.php/', 'http://www.foo.bar'],
|
||||
['https://example.org/?foo=bar&baz=true', 'https://example.org?baz=true&foo=bar'],
|
||||
];
|
||||
}
|
||||
|
||||
public function test_pretty_print_json()
|
||||
{
|
||||
$json = <<<JSON
|
||||
{"glossary": {"title": "example glossary", "GlossDiv": {"title": "S", "GlossList": {"GlossEntry": {"ID": "SGML", "SortAs": "SGML", "GlossTerm": "Standard Generalized Markup Language", "Acronym": "SGML", "Abbrev": "ISO 8879:1986", "GlossDef": {"para": "A meta-markup language, used to create markup languages such as DocBook.", "GlossSeeAlso": ["GML", "XML"]}, "GlossSee": "markup"}}}}}
|
||||
JSON;
|
||||
$expected = <<<EXPECTED
|
||||
{
|
||||
"glossary": {
|
||||
"title": "example glossary",
|
||||
"GlossDiv": {
|
||||
"title": "S",
|
||||
"GlossList": {
|
||||
"GlossEntry": {
|
||||
"ID": "SGML",
|
||||
"SortAs": "SGML",
|
||||
"GlossTerm": "Standard Generalized Markup Language",
|
||||
"Acronym": "SGML",
|
||||
"Abbrev": "ISO 8879:1986",
|
||||
"GlossDef": {
|
||||
"para": "A meta-markup language, used to create markup languages such as DocBook.",
|
||||
"GlossSeeAlso": [
|
||||
"GML",
|
||||
"XML"
|
||||
]
|
||||
},
|
||||
"GlossSee": "markup"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECTED;
|
||||
$this->assertEquals($expected, prettyPrintJson($json));
|
||||
}
|
||||
}
|
38
tinker.config.php
Normal file
38
tinker.config.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Automatically alias Laravel Model's to their base classname.
|
||||
* Ex: "App\Models\User" now can just be accessed by "User"
|
||||
*/
|
||||
|
||||
if (! function_exists('aliasModels')) {
|
||||
function aliasModels() {
|
||||
$finder = new \Symfony\Component\Finder\Finder();
|
||||
$finder->files()->name('*.php')->in(base_path().'/app');
|
||||
foreach ($finder as $file) {
|
||||
$namespace = 'App\\';
|
||||
if ($relativePath = $file->getRelativePath()) {
|
||||
$namespace .= strtr($relativePath, '/', '\\') . '\\';
|
||||
}
|
||||
$class = $namespace . $file->getBasename('.php');
|
||||
try {
|
||||
$r = new \ReflectionClass($class);
|
||||
if ($r->isSubclassOf('Illuminate\\Database\\Eloquent\\Model')) {
|
||||
class_alias($class, $file->getBasename('.php'));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aliasModels();
|
||||
|
||||
return [
|
||||
'startupMessage' => '<info>Using local config file (tinker.config.php)</info>',
|
||||
|
||||
'commands' => [
|
||||
// new \App\Tinker\TestCommand,
|
||||
],
|
||||
];
|
|
@ -1,4 +1,5 @@
|
|||
[global]
|
||||
error_log = /tmp/php-fpm.error.log
|
||||
|
||||
[travis]
|
||||
user = {USER}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue