From 499e4d235d599a59a5c3931ccef7354db935e4a9 Mon Sep 17 00:00:00 2001 From: Jeremy Dormitzer Date: Sun, 20 Jan 2019 15:02:37 -0500 Subject: [PATCH] [WIP] Extract auth and collection paging/filtering to their own services --- src/Auth/AuthService.php | 70 +++++++++++++ src/Config/ActivityPubModule.php | 10 +- src/Controllers/GetObjectController.php | 100 +++++-------------- src/Objects/CollectionsService.php | 57 +++++++++++ test/Auth/AuthServiceTest.php | 14 +++ test/Controllers/GetObjectControllerTest.php | 20 +++- test/Objects/CollectionsServiceTest.php | 14 +++ 7 files changed, 207 insertions(+), 78 deletions(-) create mode 100644 src/Auth/AuthService.php create mode 100644 src/Objects/CollectionsService.php create mode 100644 test/Auth/AuthServiceTest.php create mode 100644 test/Objects/CollectionsServiceTest.php diff --git a/src/Auth/AuthService.php b/src/Auth/AuthService.php new file mode 100644 index 0000000..55ea5df --- /dev/null +++ b/src/Auth/AuthService.php @@ -0,0 +1,70 @@ +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; + } +} +?> diff --git a/src/Config/ActivityPubModule.php b/src/Config/ActivityPubModule.php index 0051141..50ba5e1 100644 --- a/src/Config/ActivityPubModule.php +++ b/src/Config/ActivityPubModule.php @@ -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 ) ); diff --git a/src/Controllers/GetObjectController.php b/src/Controllers/GetObjectController.php index 027d070..fab8a15 100644 --- a/src/Controllers/GetObjectController.php +++ b/src/Controllers/GetObjectController.php @@ -1,7 +1,9 @@ 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 ) - { - - } } ?> diff --git a/src/Objects/CollectionsService.php b/src/Objects/CollectionsService.php new file mode 100644 index 0000000..5bcd4c6 --- /dev/null +++ b/src/Objects/CollectionsService.php @@ -0,0 +1,57 @@ +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' ); + } + } +} +?> diff --git a/test/Auth/AuthServiceTest.php b/test/Auth/AuthServiceTest.php new file mode 100644 index 0000000..600807a --- /dev/null +++ b/test/Auth/AuthServiceTest.php @@ -0,0 +1,14 @@ +assertTrue( false ); + } +} +?> diff --git a/test/Controllers/GetObjectControllerTest.php b/test/Controllers/GetObjectControllerTest.php index 64d5e99..3cd573d 100644 --- a/test/Controllers/GetObjectControllerTest.php +++ b/test/Controllers/GetObjectControllerTest.php @@ -1,9 +1,11 @@ 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' ) ); + } } ?> diff --git a/test/Objects/CollectionsServiceTest.php b/test/Objects/CollectionsServiceTest.php new file mode 100644 index 0000000..eebfec3 --- /dev/null +++ b/test/Objects/CollectionsServiceTest.php @@ -0,0 +1,14 @@ +assertTrue( false ); + } +} +?>