[WIP] Extract auth and collection paging/filtering to their own services
This commit is contained in:
parent
f2d42130ea
commit
499e4d235d
70
src/Auth/AuthService.php
Normal file
70
src/Auth/AuthService.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
namespace ActivityPub\Auth;
|
||||
|
||||
use ActivityPub\Entities\ActivityPubObject;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class AuthService
|
||||
{
|
||||
public function requestAuthorizedToView( Request $request,
|
||||
ActivityPubObject $object )
|
||||
{
|
||||
if ( ! $this->hasAudience( $object ) ) {
|
||||
return true;
|
||||
}
|
||||
$audience = $this->getAudience( $object );
|
||||
if ( in_array( 'https://www.w3.org/ns/activitystreams#Public', $audience ) ) {
|
||||
return true;
|
||||
}
|
||||
return $request->attributes->has( 'actor' ) &&
|
||||
in_array( $request->attributes->get( 'actor' ), $audience );
|
||||
}
|
||||
|
||||
private function hasAudience( ActivityPubObject $object )
|
||||
{
|
||||
$arr = $object->asArray( 0 );
|
||||
return array_key_exists( 'audience', $arr ) ||
|
||||
array_key_exists( 'to', $arr ) ||
|
||||
array_key_exists( 'bto', $arr ) ||
|
||||
array_key_exists( 'cc', $arr ) ||
|
||||
array_key_exists( 'bcc', $arr );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all of the $object's audience actors, i.e.
|
||||
* the contents of the to, bto, cc, bcc, and audience fields, as
|
||||
* well as the actor who created to object
|
||||
*
|
||||
* @param ActivityPubObject $object
|
||||
* @return array The audience members, collapsed to an array of ids
|
||||
*/
|
||||
private function getAudience( ActivityPubObject $object )
|
||||
{
|
||||
// TODO do I need to traverse the inReplyTo chain here?
|
||||
$objectArr = $object->asArray( 0 );
|
||||
$audience = array();
|
||||
if ( array_key_exists( 'to', $objectArr ) ) {
|
||||
$audience = array_merge( $audience, $objectArr['to'] );
|
||||
}
|
||||
if ( array_key_exists( 'bto', $objectArr ) ) {
|
||||
$audience = array_merge( $audience, $objectArr['bto'] );
|
||||
}
|
||||
if ( array_key_exists( 'cc', $objectArr ) ) {
|
||||
$audience = array_merge( $audience, $objectArr['cc'] );
|
||||
}
|
||||
if ( array_key_exists( 'bcc', $objectArr ) ) {
|
||||
$audience = array_merge( $audience, $objectArr['bcc'] );
|
||||
}
|
||||
if ( array_key_exists( 'audience', $objectArr ) ) {
|
||||
$audience = array_merge( $audience, $objectArr['audience'] );
|
||||
}
|
||||
if ( array_key_exists( 'attributedTo', $objectArr ) ) {
|
||||
$audience[] = $objectArr['attributedTo'];
|
||||
}
|
||||
if ( array_key_exists( 'actor', $objectArr ) ) {
|
||||
$audience[] = $objectArr['actor'];
|
||||
}
|
||||
return $audience;
|
||||
}
|
||||
}
|
||||
?>
|
@ -2,6 +2,7 @@
|
||||
namespace ActivityPub\Config;
|
||||
|
||||
use ActivityPub\Auth\AuthListener;
|
||||
use ActivityPub\Auth\AuthService;
|
||||
use ActivityPub\Auth\SignatureListener;
|
||||
use ActivityPub\Controllers\GetObjectController;
|
||||
use ActivityPub\Controllers\InboxController;
|
||||
@ -9,6 +10,7 @@ use ActivityPub\Controllers\OutboxController;
|
||||
use ActivityPub\Crypto\HttpSignatureService;
|
||||
use ActivityPub\Database\PrefixNamingStrategy;
|
||||
use ActivityPub\Http\ControllerResolver;
|
||||
use ActivityPub\Objects\CollectionsService;
|
||||
use ActivityPub\Objects\ObjectsService;
|
||||
use ActivityPub\Utils\SimpleDateTimeProvider;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
@ -74,8 +76,14 @@ class ActivityPubModule
|
||||
$this->injector->register( AuthListener::class, AuthListener::class )
|
||||
->addArgument( $options['authFunction'] );
|
||||
|
||||
$this->injector->register( CollectionsService::class, CollectionsService::class );
|
||||
|
||||
$this->injector->register( AuthService::class, AuthService::class );
|
||||
|
||||
$this->injector->register( GetObjectController::class, GetObjectController::class )
|
||||
->addArgument( new Reference( ObjectsService::class ) );
|
||||
->addArgument( new Reference( ObjectsService::class ) )
|
||||
->addArgument( new Reference( CollectionsService::class ) )
|
||||
->addArgument( new Reference( AuthService::class ) );
|
||||
|
||||
$this->injector->register( InboxController::class, InboxController::class )
|
||||
->addArgument( new Reference( ObjectsService::class ) );
|
||||
|
@ -1,7 +1,9 @@
|
||||
<?php
|
||||
namespace ActivityPub\Controllers;
|
||||
|
||||
use ActivityPub\Auth\AuthService;
|
||||
use ActivityPub\Entities\ActivityPubObject;
|
||||
use ActivityPub\Objects\CollectionsService;
|
||||
use ActivityPub\Objects\ObjectsService;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@ -13,14 +15,29 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
*/
|
||||
class GetObjectController
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ObjectsService
|
||||
*/
|
||||
private $objectsService;
|
||||
|
||||
public function __construct( ObjectsService $objectsService )
|
||||
/**
|
||||
* @var CollectionsService
|
||||
*/
|
||||
private $collectionsService;
|
||||
|
||||
/**
|
||||
* @var AuthService
|
||||
*/
|
||||
private $authService;
|
||||
|
||||
public function __construct( ObjectsService $objectsService,
|
||||
CollectionsService $collectionsService,
|
||||
AuthService $authService )
|
||||
{
|
||||
$this->objectsService = $objectsService;
|
||||
$this->collectionsService = $collectionsService;
|
||||
$this->authService = $authService;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -32,11 +49,15 @@ class GetObjectController
|
||||
public function handle( Request $request )
|
||||
{
|
||||
$uri = $request->getUri();
|
||||
$queryPos = strpos( $uri, '?' );
|
||||
if ( $queryPos !== false ) {
|
||||
$uri = substr( $uri, 0, $queryPos );
|
||||
}
|
||||
$object = $this->objectsService->dereference( $uri );
|
||||
if ( ! $object ) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
if ( ! $this->requestAuthorizedToView( $request, $object ) ) {
|
||||
if ( ! $this->authService->requestAuthorizedToView( $request, $object ) ) {
|
||||
throw new UnauthorizedHttpException(
|
||||
'Signature realm="ActivityPub",headers="(request-target) host date"'
|
||||
);
|
||||
@ -44,82 +65,9 @@ class GetObjectController
|
||||
if ( $object->hasField( 'type' ) &&
|
||||
( $object['type'] === 'Collection' ||
|
||||
$object['type'] === 'OrderedCollection' ) ) {
|
||||
return $this->pageAndFilterCollection( $request, $object );
|
||||
return $this->collectionsService->pageAndFilterCollection( $request, $object );
|
||||
}
|
||||
return new JsonResponse( $object->asArray() );
|
||||
}
|
||||
|
||||
private function requestAuthorizedToView( Request $request,
|
||||
ActivityPubObject $object )
|
||||
{
|
||||
if ( ! $this->hasAudience( $object ) ) {
|
||||
return true;
|
||||
}
|
||||
$audience = $this->getAudience( $object );
|
||||
if ( in_array( 'https://www.w3.org/ns/activitystreams#Public', $audience ) ) {
|
||||
return true;
|
||||
}
|
||||
return $request->attributes->has( 'actor' ) &&
|
||||
in_array( $request->attributes->get( 'actor' ), $audience );
|
||||
}
|
||||
|
||||
private function hasAudience( ActivityPubObject $object )
|
||||
{
|
||||
$arr = $object->asArray( 0 );
|
||||
return array_key_exists( 'audience', $arr ) ||
|
||||
array_key_exists( 'to', $arr ) ||
|
||||
array_key_exists( 'bto', $arr ) ||
|
||||
array_key_exists( 'cc', $arr ) ||
|
||||
array_key_exists( 'bcc', $arr );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all of the $object's audience actors, i.e.
|
||||
* the contents of the to, bto, cc, bcc, and audience fields, as
|
||||
* well as the actor who created to object
|
||||
*
|
||||
* @param ActivityPubObject $object
|
||||
* @return array The audience members, collapsed to an array of ids
|
||||
*/
|
||||
private function getAudience( ActivityPubObject $object )
|
||||
{
|
||||
// TODO do I need to traverse the inReplyTo chain here?
|
||||
$objectArr = $object->asArray( 0 );
|
||||
$audience = array();
|
||||
if ( array_key_exists( 'to', $objectArr ) ) {
|
||||
$audience = array_merge( $audience, $objectArr['to'] );
|
||||
}
|
||||
if ( array_key_exists( 'bto', $objectArr ) ) {
|
||||
$audience = array_merge( $audience, $objectArr['bto'] );
|
||||
}
|
||||
if ( array_key_exists( 'cc', $objectArr ) ) {
|
||||
$audience = array_merge( $audience, $objectArr['cc'] );
|
||||
}
|
||||
if ( array_key_exists( 'bcc', $objectArr ) ) {
|
||||
$audience = array_merge( $audience, $objectArr['bcc'] );
|
||||
}
|
||||
if ( array_key_exists( 'audience', $objectArr ) ) {
|
||||
$audience = array_merge( $audience, $objectArr['audience'] );
|
||||
}
|
||||
if ( array_key_exists( 'attributedTo', $objectArr ) ) {
|
||||
$audience[] = $objectArr['attributedTo'];
|
||||
}
|
||||
if ( array_key_exists( 'actor', $objectArr ) ) {
|
||||
$audience[] = $objectArr['actor'];
|
||||
}
|
||||
return $audience;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array representation of the $collection
|
||||
*
|
||||
* If the collection's size is greater than 30, return a PagedCollection instead,
|
||||
* and filter all items by the request's permissions
|
||||
*/
|
||||
private function pageAndFilterCollection( Request $request,
|
||||
ActivityPubObject $collection )
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
57
src/Objects/CollectionsService.php
Normal file
57
src/Objects/CollectionsService.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
namespace ActivityPub\Objects;
|
||||
|
||||
use ActivityPub\Entities\ActivityPubObject;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class CollectionsService
|
||||
{
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
/**
|
||||
* Returns an array representation of the $collection
|
||||
*
|
||||
* If the collection's size is greater than 30, return a PagedCollection instead,
|
||||
* and filter all items by the request's permissions
|
||||
*/
|
||||
public function pageAndFilterCollection( Request $request,
|
||||
ActivityPubObject $collection )
|
||||
{
|
||||
// expected behavior:
|
||||
// - request with no 'offset' param returns the collection object,
|
||||
// with the first page appended as with Pleroma
|
||||
// - request with an 'offset' param returns the collection page starting
|
||||
// at that offset with the next PAGE_SIZE items
|
||||
if ( $request->query->has( 'offset' ) ) {
|
||||
// return a filtered collection page
|
||||
}
|
||||
// else return the collection itself with the first page
|
||||
}
|
||||
|
||||
private function getCollectionPage( ActivityPubObject $collection,
|
||||
int $offset,
|
||||
int $pageSize )
|
||||
{
|
||||
$itemsKey = 'items';
|
||||
$pageType = 'CollectionPage';
|
||||
if ( $this->isOrdered( $collection ) ) {
|
||||
$itemsKey = 'orderedItems';
|
||||
$pageType = 'OrderedCollectionPage';
|
||||
}
|
||||
// Create and return the page as an array
|
||||
}
|
||||
|
||||
private function isOrdered( ActivityPubObject $collection )
|
||||
{
|
||||
if ( $collection->hasField( 'type' ) &&
|
||||
$collection['type'] === 'OrderedCollection' ) {
|
||||
return true;
|
||||
} else if ( $collection->hasField( 'type' ) &&
|
||||
$collection['type'] === 'Collection' ) {
|
||||
return false;
|
||||
} else {
|
||||
throw new InvalidArgumentException( 'Not a collection' );
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
14
test/Auth/AuthServiceTest.php
Normal file
14
test/Auth/AuthServiceTest.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace ActivityPub\Test\Auth;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AuthServiceTest extends TestCase
|
||||
{
|
||||
public function testAuthService()
|
||||
{
|
||||
// TODO implement me
|
||||
$this->assertTrue( false );
|
||||
}
|
||||
}
|
||||
?>
|
@ -1,9 +1,11 @@
|
||||
<?php
|
||||
namespace ActivityPub\Test\Controllers;
|
||||
|
||||
use ActivityPub\Auth\AuthService;
|
||||
use ActivityPub\Controllers\GetObjectController;
|
||||
use ActivityPub\Entities\ActivityPubObject;
|
||||
use ActivityPub\Entities\Field;
|
||||
use ActivityPub\Objects\CollectionsService;
|
||||
use ActivityPub\Objects\ObjectsService;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
@ -59,7 +61,11 @@ class GetObjectControllerTest extends TestCase
|
||||
}
|
||||
})
|
||||
);
|
||||
$this->getObjectController = new GetObjectController( $objectsService );
|
||||
$collectionsService = new CollectionsService();
|
||||
$authService = new AuthService();
|
||||
$this->getObjectController = new GetObjectController(
|
||||
$objectsService, $collectionsService, $authService
|
||||
);
|
||||
}
|
||||
|
||||
private function objectFromArray( $array ) {
|
||||
@ -138,5 +144,17 @@ class GetObjectControllerTest extends TestCase
|
||||
);
|
||||
$this->assertEquals( 'application/json', $response->headers->get( 'Content-Type' ) );
|
||||
}
|
||||
|
||||
public function testItDisregardsQueryParams()
|
||||
{
|
||||
$request = Request::create( 'https://example.com/objects/1?foo=bar&baz=qux' );
|
||||
$response = $this->getObjectController->handle( $request );
|
||||
$this->assertNotNull( $response );
|
||||
$this->assertEquals(
|
||||
json_encode( self::OBJECTS['https://example.com/objects/1'] ),
|
||||
$response->getContent()
|
||||
);
|
||||
$this->assertEquals( 'application/json', $response->headers->get( 'Content-Type' ) );
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
14
test/Objects/CollectionsServiceTest.php
Normal file
14
test/Objects/CollectionsServiceTest.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace ActivityPub\Test\Objects;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CollectionsServiceTest extends TestCase
|
||||
{
|
||||
public function testCollectionsService()
|
||||
{
|
||||
// TODO implement me
|
||||
$this->assertTrue( false );
|
||||
}
|
||||
}
|
||||
?>
|
Loading…
Reference in New Issue
Block a user