mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-30 02:03:16 +00:00
commit
72cff7ed12
12 changed files with 244 additions and 96 deletions
|
@ -17,6 +17,7 @@
|
||||||
- Add MediaBlocklist feature ([ba1f7e7e](https://github.com/pixelfed/pixelfed/commit/ba1f7e7e))
|
- Add MediaBlocklist feature ([ba1f7e7e](https://github.com/pixelfed/pixelfed/commit/ba1f7e7e))
|
||||||
- New Discover Layout, add trending hashtags, places and posts ([c251d41b](https://github.com/pixelfed/pixelfed/commit/c251d41b))
|
- New Discover Layout, add trending hashtags, places and posts ([c251d41b](https://github.com/pixelfed/pixelfed/commit/c251d41b))
|
||||||
- Add Password change email notification ([de1cca4f](https://github.com/pixelfed/pixelfed/commit/de1cca4f))
|
- Add Password change email notification ([de1cca4f](https://github.com/pixelfed/pixelfed/commit/de1cca4f))
|
||||||
|
- Add shared inbox ([4733ca9f](https://github.com/pixelfed/pixelfed/commit/4733ca9f))
|
||||||
|
|
||||||
### Updated
|
### Updated
|
||||||
- Updated PostComponent, fix remote urls ([42716ccc](https://github.com/pixelfed/pixelfed/commit/42716ccc))
|
- Updated PostComponent, fix remote urls ([42716ccc](https://github.com/pixelfed/pixelfed/commit/42716ccc))
|
||||||
|
@ -101,6 +102,7 @@
|
||||||
- Updated EmailService, make case insensitive. ([1b41d664](https://github.com/pixelfed/pixelfed/commit/1b41d664))
|
- Updated EmailService, make case insensitive. ([1b41d664](https://github.com/pixelfed/pixelfed/commit/1b41d664))
|
||||||
- Updated DiscoverController, fix trending api. ([2ab2c9a](https://github.com/pixelfed/pixelfed/commit/2ab2c9a))
|
- Updated DiscoverController, fix trending api. ([2ab2c9a](https://github.com/pixelfed/pixelfed/commit/2ab2c9a))
|
||||||
- Updated Dark Mode layout. ([d6f8170](https://github.com/pixelfed/pixelfed/commit/d6f8170))
|
- Updated Dark Mode layout. ([d6f8170](https://github.com/pixelfed/pixelfed/commit/d6f8170))
|
||||||
|
- Updated federation config, make sharedInbox enabled by default. ([6e3522c0](https://github.com/pixelfed/pixelfed/commit/6e3522c0))
|
||||||
|
|
||||||
## [v0.10.9 (2020-04-17)](https://github.com/pixelfed/pixelfed/compare/v0.10.8...v0.10.9)
|
## [v0.10.9 (2020-04-17)](https://github.com/pixelfed/pixelfed/compare/v0.10.8...v0.10.9)
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -90,7 +90,7 @@ class RegisterController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$restricted = RestrictedNames::get();
|
$restricted = RestrictedNames::get();
|
||||||
if (in_array($value, $restricted)) {
|
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
||||||
return $fail('Username cannot be used.');
|
return $fail('Username cannot be used.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -704,7 +704,7 @@ class DirectMessageController extends Controller
|
||||||
public function remoteDeliver($dm)
|
public function remoteDeliver($dm)
|
||||||
{
|
{
|
||||||
$profile = $dm->author;
|
$profile = $dm->author;
|
||||||
$url = $dm->recipient->inbox_url;
|
$url = $dm->recipient->sharedInbox ?? $dm->recipient->inbox_url;
|
||||||
|
|
||||||
$tags = [
|
$tags = [
|
||||||
[
|
[
|
||||||
|
@ -760,7 +760,7 @@ class DirectMessageController extends Controller
|
||||||
public function remoteDelete($dm)
|
public function remoteDelete($dm)
|
||||||
{
|
{
|
||||||
$profile = $dm->author;
|
$profile = $dm->author;
|
||||||
$url = $dm->recipient->inbox_url;
|
$url = $dm->recipient->sharedInbox ?? $dm->recipient->inbox_url;
|
||||||
|
|
||||||
$body = [
|
$body = [
|
||||||
'@context' => [
|
'@context' => [
|
||||||
|
|
|
@ -108,6 +108,17 @@ class FederationController extends Controller
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sharedInbox(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!config('federation.activitypub.enabled'), 404);
|
||||||
|
abort_if(!config('federation.activitypub.sharedInbox'), 404);
|
||||||
|
|
||||||
|
$headers = $request->headers->all();
|
||||||
|
$payload = $request->getContent();
|
||||||
|
dispatch(new InboxWorker($headers, $payload))->onQueue('high');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
public function userFollowing(Request $request, $username)
|
public function userFollowing(Request $request, $username)
|
||||||
{
|
{
|
||||||
abort_if(!config('federation.activitypub.enabled'), 404);
|
abort_if(!config('federation.activitypub.enabled'), 404);
|
||||||
|
|
|
@ -9,10 +9,10 @@ use App\Util\ActivityPub\{
|
||||||
Inbox
|
Inbox
|
||||||
};
|
};
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Zttp\Zttp;
|
use Zttp\Zttp;
|
||||||
|
|
||||||
class InboxValidator implements ShouldQueue
|
class InboxValidator implements ShouldQueue
|
||||||
|
|
|
@ -3,12 +3,17 @@
|
||||||
namespace App\Jobs\InboxPipeline;
|
namespace App\Jobs\InboxPipeline;
|
||||||
|
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\Util\ActivityPub\Inbox;
|
use App\Util\ActivityPub\{
|
||||||
|
Helpers,
|
||||||
|
HttpSignature,
|
||||||
|
Inbox
|
||||||
|
};
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Zttp\Zttp;
|
||||||
|
|
||||||
class InboxWorker implements ShouldQueue
|
class InboxWorker implements ShouldQueue
|
||||||
{
|
{
|
||||||
|
@ -26,10 +31,9 @@ class InboxWorker implements ShouldQueue
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct($headers, $profile, $payload)
|
public function __construct($headers, $payload)
|
||||||
{
|
{
|
||||||
$this->headers = $headers;
|
$this->headers = $headers;
|
||||||
$this->profile = $profile;
|
|
||||||
$this->payload = $payload;
|
$this->payload = $payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +44,116 @@ class InboxWorker implements ShouldQueue
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
(new Inbox($this->headers, $this->profile, $this->payload))->handle();
|
$profile = null;
|
||||||
|
$headers = $this->headers;
|
||||||
|
$payload = json_decode($this->payload, true, 8);
|
||||||
|
|
||||||
|
if(!isset($headers['signature']) || !isset($headers['date'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(empty($headers) || empty($payload)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->verifySignature($headers, $payload) == true) {
|
||||||
|
(new Inbox($headers, $profile, $payload))->handle();
|
||||||
|
return;
|
||||||
|
} else if($this->blindKeyRotation($headers, $payload) == true) {
|
||||||
|
(new Inbox($headers, $profile, $payload))->handle();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function verifySignature($headers, $payload)
|
||||||
|
{
|
||||||
|
$body = $this->payload;
|
||||||
|
$bodyDecoded = $payload;
|
||||||
|
$signature = is_array($headers['signature']) ? $headers['signature'][0] : $headers['signature'];
|
||||||
|
$date = is_array($headers['date']) ? $headers['date'][0] : $headers['date'];
|
||||||
|
if(!$signature) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!$date) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!now()->parse($date)->gt(now()->subDays(1)) ||
|
||||||
|
!now()->parse($date)->lt(now()->addDays(1))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$signatureData = HttpSignature::parseSignatureHeader($signature);
|
||||||
|
$keyId = Helpers::validateUrl($signatureData['keyId']);
|
||||||
|
$id = Helpers::validateUrl($bodyDecoded['id']);
|
||||||
|
$keyDomain = parse_url($keyId, PHP_URL_HOST);
|
||||||
|
$idDomain = parse_url($id, PHP_URL_HOST);
|
||||||
|
if(isset($bodyDecoded['object'])
|
||||||
|
&& is_array($bodyDecoded['object'])
|
||||||
|
&& isset($bodyDecoded['object']['attributedTo'])
|
||||||
|
) {
|
||||||
|
if(parse_url($bodyDecoded['object']['attributedTo'], PHP_URL_HOST) !== $keyDomain) {
|
||||||
|
return;
|
||||||
|
abort(400, 'Invalid request');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!$keyDomain || !$idDomain || $keyDomain !== $idDomain) {
|
||||||
|
return;
|
||||||
|
abort(400, 'Invalid request');
|
||||||
|
}
|
||||||
|
$actor = Profile::whereKeyId($keyId)->first();
|
||||||
|
if(!$actor) {
|
||||||
|
$actorUrl = is_array($bodyDecoded['actor']) ? $bodyDecoded['actor'][0] : $bodyDecoded['actor'];
|
||||||
|
$actor = Helpers::profileFirstOrNew($actorUrl);
|
||||||
|
}
|
||||||
|
if(!$actor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$pkey = openssl_pkey_get_public($actor->public_key);
|
||||||
|
$inboxPath = "/f/inbox";
|
||||||
|
list($verified, $headers) = HttpSignature::verify($pkey, $signatureData, $headers, $inboxPath, $body);
|
||||||
|
if($verified == 1) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function blindKeyRotation($headers, $payload)
|
||||||
|
{
|
||||||
|
$signature = is_array($headers['signature']) ? $headers['signature'][0] : $headers['signature'];
|
||||||
|
$date = is_array($headers['date']) ? $headers['date'][0] : $headers['date'];
|
||||||
|
if(!$signature) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!$date) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!now()->parse($date)->gt(now()->subDays(1)) ||
|
||||||
|
!now()->parse($date)->lt(now()->addDays(1))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$signatureData = HttpSignature::parseSignatureHeader($signature);
|
||||||
|
$keyId = Helpers::validateUrl($signatureData['keyId']);
|
||||||
|
$actor = Profile::whereKeyId($keyId)->whereNotNull('remote_url')->first();
|
||||||
|
if(!$actor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(Helpers::validateUrl($actor->remote_url) == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$res = Zttp::timeout(5)->withHeaders([
|
||||||
|
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||||
|
'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org',
|
||||||
|
])->get($actor->remote_url);
|
||||||
|
$res = json_decode($res->body(), true, 8);
|
||||||
|
if($res['publicKey']['id'] !== $actor->key_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$actor->public_key = $res['publicKey']['publicKeyPem'];
|
||||||
|
$actor->save();
|
||||||
|
return $this->verifySignature($headers, $payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,9 @@ class ProfileTransformer extends Fractal\TransformerAbstract
|
||||||
'mediaType' => 'image/jpeg',
|
'mediaType' => 'image/jpeg',
|
||||||
'url' => $profile->avatarUrl(),
|
'url' => $profile->avatarUrl(),
|
||||||
],
|
],
|
||||||
|
'endpoints' => [
|
||||||
|
'sharedInbox' => config('app.url') . '/f/inbox'
|
||||||
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,6 +136,11 @@ class Helpers {
|
||||||
$url = $url[0];
|
$url = $url[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$hash = hash('sha256', $url);
|
||||||
|
$key = "helpers:url:valid:sha256-{$hash}";
|
||||||
|
$ttl = now()->addMinutes(5);
|
||||||
|
|
||||||
|
$valid = Cache::remember($key, $ttl, function() use($url) {
|
||||||
$localhosts = [
|
$localhosts = [
|
||||||
'127.0.0.1', 'localhost', '::1'
|
'127.0.0.1', 'localhost', '::1'
|
||||||
];
|
];
|
||||||
|
@ -169,6 +174,9 @@ class Helpers {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $url;
|
||||||
|
});
|
||||||
|
|
||||||
return $valid;
|
return $valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,19 +202,25 @@ class Helpers {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fetchFromUrl($url)
|
public static function fetchFromUrl($url = false)
|
||||||
{
|
{
|
||||||
$url = self::validateUrl($url);
|
if(self::validateUrl($url) == false) {
|
||||||
if($url == false) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$res = Zttp::withHeaders(self::zttpUserAgent())->get($url);
|
|
||||||
|
$hash = hash('sha256', $url);
|
||||||
|
$key = "helpers:url:fetcher:sha256-{$hash}";
|
||||||
|
$ttl = now()->addMinutes(5);
|
||||||
|
|
||||||
|
return Cache::remember($key, $ttl, function() use($url) {
|
||||||
|
$res = Zttp::withoutVerifying()->withHeaders(self::zttpUserAgent())->get($url);
|
||||||
$res = json_decode($res->body(), true, 8);
|
$res = json_decode($res->body(), true, 8);
|
||||||
if(json_last_error() == JSON_ERROR_NONE) {
|
if(json_last_error() == JSON_ERROR_NONE) {
|
||||||
return $res;
|
return $res;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fetchProfileFromUrl($url)
|
public static function fetchProfileFromUrl($url)
|
||||||
|
@ -444,6 +458,7 @@ class Helpers {
|
||||||
$profile->name = isset($res['name']) ? Purify::clean($res['name']) : 'user';
|
$profile->name = isset($res['name']) ? Purify::clean($res['name']) : 'user';
|
||||||
$profile->bio = isset($res['summary']) ? Purify::clean($res['summary']) : null;
|
$profile->bio = isset($res['summary']) ? Purify::clean($res['summary']) : null;
|
||||||
$profile->last_fetched_at = now();
|
$profile->last_fetched_at = now();
|
||||||
|
$profile->sharedInbox = isset($res['endpoints']) && isset($res['endpoints']['sharedInbox']) && Helpers::validateUrl($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : null;
|
||||||
$profile->save();
|
$profile->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -323,7 +323,7 @@ class Inbox
|
||||||
public function handleFollowActivity()
|
public function handleFollowActivity()
|
||||||
{
|
{
|
||||||
$actor = $this->actorFirstOrCreate($this->payload['actor']);
|
$actor = $this->actorFirstOrCreate($this->payload['actor']);
|
||||||
$target = $this->profile;
|
$target = $this->actorFirstOrCreate($this->payload['object']);
|
||||||
if(!$actor || $actor->domain == null || $target->domain !== null) {
|
if(!$actor || $actor->domain == null || $target->domain !== null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -470,7 +470,7 @@ class Inbox
|
||||||
$profile->statuses()->delete();
|
$profile->statuses()->delete();
|
||||||
$profile->delete();
|
$profile->delete();
|
||||||
return;
|
return;
|
||||||
}
|
} else {
|
||||||
$type = $this->payload['object']['type'];
|
$type = $this->payload['object']['type'];
|
||||||
$typeCheck = in_array($type, ['Person', 'Tombstone']);
|
$typeCheck = in_array($type, ['Person', 'Tombstone']);
|
||||||
if(!Helpers::validateUrl($actor) || !Helpers::validateUrl($obj['id']) || !$typeCheck) {
|
if(!Helpers::validateUrl($actor) || !Helpers::validateUrl($obj['id']) || !$typeCheck) {
|
||||||
|
@ -521,6 +521,7 @@ class Inbox
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function handleLikeActivity()
|
public function handleLikeActivity()
|
||||||
{
|
{
|
||||||
|
|
|
@ -177,6 +177,7 @@ class RestrictedNames
|
||||||
'help-center_',
|
'help-center_',
|
||||||
'help_center-',
|
'help_center-',
|
||||||
'i',
|
'i',
|
||||||
|
'inbox',
|
||||||
'img',
|
'img',
|
||||||
'imgs',
|
'imgs',
|
||||||
'image',
|
'image',
|
||||||
|
|
|
@ -15,12 +15,12 @@ return [
|
||||||
'enabled' => env('ACTIVITY_PUB', false),
|
'enabled' => env('ACTIVITY_PUB', false),
|
||||||
'outbox' => env('AP_OUTBOX', true),
|
'outbox' => env('AP_OUTBOX', true),
|
||||||
'inbox' => env('AP_INBOX', true),
|
'inbox' => env('AP_INBOX', true),
|
||||||
'sharedInbox' => env('AP_SHAREDINBOX', false),
|
'sharedInbox' => env('AP_SHAREDINBOX', true),
|
||||||
|
|
||||||
'remoteFollow' => env('AP_REMOTE_FOLLOW', false),
|
'remoteFollow' => env('AP_REMOTE_FOLLOW', false),
|
||||||
|
|
||||||
'delivery' => [
|
'delivery' => [
|
||||||
'timeout' => env('ACTIVITYPUB_DELIVERY_TIMEOUT', 2.0),
|
'timeout' => env('ACTIVITYPUB_DELIVERY_TIMEOUT', 30.0),
|
||||||
'concurrency' => env('ACTIVITYPUB_DELIVERY_CONCURRENCY', 10),
|
'concurrency' => env('ACTIVITYPUB_DELIVERY_CONCURRENCY', 10),
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'enabled' => env('AP_LOGGER_ENABLED', false),
|
'enabled' => env('AP_LOGGER_ENABLED', false),
|
||||||
|
|
|
@ -4,6 +4,7 @@ use Illuminate\Http\Request;
|
||||||
|
|
||||||
$middleware = ['auth:api','twofactor','validemail','localization', 'throttle:60,1'];
|
$middleware = ['auth:api','twofactor','validemail','localization', 'throttle:60,1'];
|
||||||
|
|
||||||
|
Route::post('/f/inbox', 'FederationController@sharedInbox');
|
||||||
Route::post('/users/{username}/inbox', 'FederationController@userInbox');
|
Route::post('/users/{username}/inbox', 'FederationController@userInbox');
|
||||||
|
|
||||||
Route::group(['prefix' => 'api'], function() use($middleware) {
|
Route::group(['prefix' => 'api'], function() use($middleware) {
|
||||||
|
|
Loading…
Reference in a new issue