diff --git a/src/Activities/ActivityEvent.php b/src/Activities/ActivityEvent.php index 4f6f519..3f0300e 100644 --- a/src/Activities/ActivityEvent.php +++ b/src/Activities/ActivityEvent.php @@ -28,7 +28,7 @@ class ActivityEvent extends Event */ protected $request; - protected function __construct( array $activity, ActivityPubObject $actor, + public function __construct( array $activity, ActivityPubObject $actor, Request $request ) { $this->activity = $activity; diff --git a/src/Controllers/PostController.php b/src/Controllers/PostController.php index 263c200..ab77e06 100644 --- a/src/Controllers/PostController.php +++ b/src/Controllers/PostController.php @@ -43,12 +43,13 @@ class PostController public function handle( Request $request ) { $uri = $this->getUriWithoutQuery( $request ); - $object = $this->objectsService->dereference( $uri ); + $object = $this->objectsService->query( array( 'id' => $uri ) ); if ( ! $object ) { throw new NotFoundHttpException; } - $actorWithInbox = $this->objectWithField( 'inbox', $uri ); - if ( $actorWithInbox ) { + $inboxField = $object->getReferencingField( 'inbox' ); + if ( $inboxField ) { + $actorWithInbox = $inboxField->getObject(); if ( ! $request->attributes->has( 'signed' ) || ! $this->authorized( $request, $actorWithInbox ) ) { throw new UnauthorizedHttpException(); @@ -61,8 +62,9 @@ class PostController $this->eventDispatcher->dispatch( InboxActivityEvent::NAME, $event ); return; } - $actorWithOutbox = $this->objectWithField( 'outbox', $uri ); - if ( $actorWithOutbox ) { + $outboxField = $object->getReferencingField( 'outbox' ); + if ( $outboxField ) { + $actorWithOutbox = $outboxField->getObject(); if ( ! $this->authorized( $request, $actorWithOutbox ) ) { throw new UnauthorizedHttpException(); } diff --git a/src/Entities/ActivityPubObject.php b/src/Entities/ActivityPubObject.php index 465586d..c46d44c 100644 --- a/src/Entities/ActivityPubObject.php +++ b/src/Entities/ActivityPubObject.php @@ -201,6 +201,36 @@ class ActivityPubObject implements ArrayAccess $this->lastUpdated = $time; } + /** + * Returns true if the object is referenced by a field with key $name + * + * @return boolean + */ + public function hasReferencingField( string $name ) + { + foreach( $this->getReferencingFields() as $field ) { + if ( $field->getName() === $name ) { + return true; + } + } + return false; + } + + /** + * Returns the referencing field named $field, if it exists + * + * @param string $name The name of the referencing to get + * @return Field|null + */ + public function getReferencingField( string $name ) + { + foreach( $this->getReferencingFields() as $field ) { + if ( $field->getName() === $name ) { + return $field; + } + } + } + /** * Adds a new field that references this object * diff --git a/src/Entities/Field.php b/src/Entities/Field.php index d1b4cfb..e39bc27 100644 --- a/src/Entities/Field.php +++ b/src/Entities/Field.php @@ -67,6 +67,15 @@ class Field */ protected $lastUpdated; + protected function __construct( DateTime $time = null ) + { + if ( ! $time ) { + $time = new DateTime( "now" ); + } + $this->created = $time; + $this->lastUpdated = $time; + } + /** * Create a new field with a string value * @@ -80,12 +89,10 @@ class Field if ( ! $time ) { $time = new DateTime( "now" ); } - $field = new Field(); + $field = new Field( $time ); $field->setObject( $object, $time ); $field->setName( $name ); - $field->setValue( $value ); - $field->setCreated( $time ); - $field->setLastUpdated( $time ); + $field->setValue( $value, $time ); return $field; } @@ -97,17 +104,18 @@ class Field * @param ActivityPubObject $targetObject The object that this field holds * @return Field The new field */ - public static function withObject( ActivityPubObject $object, string $name, Object $targetObject, DateTime $time = null ) + public static function withObject( ActivityPubObject $object, + string $name, + ActivityPubObject $targetObject, + DateTime $time = null ) { if ( ! $time ) { $time = new DateTime( "now" ); } - $field = new Field(); + $field = new Field( $time ); $field->setObject( $object, $time ); $field->setName( $name ); - $field->setTargetObject( $targetObject ); - $field->setCreated( $time ); - $field->setLastUpdated( $time ); + $field->setTargetObject( $targetObject, $time ); return $field; } diff --git a/test/Auth/AuthListenerTest.php b/test/Auth/AuthListenerTest.php index 38086eb..3045d72 100644 --- a/test/Auth/AuthListenerTest.php +++ b/test/Auth/AuthListenerTest.php @@ -4,7 +4,7 @@ namespace ActivityPub\Test\Auth; use ActivityPub\Auth\AuthListener; use ActivityPub\Objects\ObjectsService; use ActivityPub\Entities\ActivityPubObject; -use ActivityPub\Test\TestUtils\TestUtils; +use ActivityPub\Test\TestUtils\TestActivityPubObject; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -18,10 +18,10 @@ class AuthListenerTest extends TestCase { $this->objectsService = $this->createMock( ObjectsService::class ); $this->objectsService->method( 'dereference' )->will( $this->returnValueMap( array( - array( 'https://example.com/actor/1', TestUtils::objectFromArray( array( + array( 'https://example.com/actor/1', TestActivityPubObject::fromArray( array( 'id' => 'https://example.com/actor/1', ) ) ), - array( 'https://example.com/actor/2', TestUtils::objectFromArray( array( + array( 'https://example.com/actor/2', TestActivityPubObject::fromArray( array( 'id' => 'https://example.com/actor/2', ) ) ), ) ) ); @@ -45,7 +45,7 @@ class AuthListenerTest extends TestCase return 'https://example.com/actor/1'; }, 'expectedAttributes' => array( - 'actor' => TestUtils::objectFromArray( array( + 'actor' => TestActivityPubObject::fromArray( array( 'id' => 'https://example.com/actor/1', ) ), ), @@ -56,12 +56,12 @@ class AuthListenerTest extends TestCase return 'https://example.com/actor/1'; }, 'requestAttributes' => array( - 'actor' => TestUtils::objectFromArray( array( + 'actor' => TestActivityPubObject::fromArray( array( 'id' => 'https://example.com/actor/2', ) ), ), 'expectedAttributes' => array( - 'actor' => TestUtils::objectFromArray( array( + 'actor' => TestActivityPubObject::fromArray( array( 'id' => 'https://example.com/actor/2', ) ), ), diff --git a/test/Auth/AuthServiceTest.php b/test/Auth/AuthServiceTest.php index 686ffd6..7c1ce10 100644 --- a/test/Auth/AuthServiceTest.php +++ b/test/Auth/AuthServiceTest.php @@ -2,7 +2,7 @@ namespace ActivityPub\Test\Auth; use ActivityPub\Auth\AuthService; -use ActivityPub\Test\TestUtils\TestUtils; +use ActivityPub\Test\TestUtils\TestActivityPubObject; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; @@ -64,7 +64,7 @@ class AuthServiceTest extends TestCase if ( array_key_exists( 'actor', $testCase ) ) { $request->attributes->set( 'actor', $testCase['actor'] ); } - $object = TestUtils::objectFromArray( $testCase['object'] ); + $object = TestActivityPubObject::fromArray( $testCase['object'] ); $actual = $this->authService->isAuthorized( $request, $object ); $this->assertEquals( $testCase['expectedResult'], $actual, "Error on test $testCase[id]" diff --git a/test/Auth/SignatureListenerTest.php b/test/Auth/SignatureListenerTest.php index 81a7454..b3e48f8 100644 --- a/test/Auth/SignatureListenerTest.php +++ b/test/Auth/SignatureListenerTest.php @@ -7,8 +7,8 @@ use ActivityPub\Crypto\HttpSignatureService; use ActivityPub\Entities\ActivityPubObject; use ActivityPub\Entities\Field; use ActivityPub\Objects\ObjectsService; +use ActivityPub\Test\TestUtils\TestActivityPubObject; use ActivityPub\Test\TestUtils\TestDateTimeProvider; -use ActivityPub\Test\TestUtils\TestUtils; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -44,8 +44,8 @@ oYi+1hqp1fIekaxsyQIDAQAB $objectsService = $this->createMock( ObjectsService::class ); $objectsService->method( 'dereference' ) ->will( $this->returnValueMap( array( - array( self::KEY_ID, TestUtils::objectFromArray( self::KEY ) ), - array( self::ACTOR_ID, TestUtils::objectFromArray( self::ACTOR ) ), + array( self::KEY_ID, TestActivityPubObject::fromArray( self::KEY ) ), + array( self::ACTOR_ID, TestActivityPubObject::fromArray( self::ACTOR ) ), ) ) ); $this->signatureListener = new SignatureListener( $httpSignatureService, $objectsService @@ -85,7 +85,7 @@ oYi+1hqp1fIekaxsyQIDAQAB ), 'expectedAttributes' => array( 'signed' => true, - 'actor' => TestUtils::objectFromArray( array( + 'actor' => TestActivityPubObject::fromArray( array( 'id' => 'https://example.com/actor/1', ) ), ), @@ -96,13 +96,13 @@ oYi+1hqp1fIekaxsyQIDAQAB 'Authorization' => 'Signature keyId="https://example.com/actor/1/key",algorithm="rsa-sha256",headers="(request-target) host date", signature="qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="', ), 'requestAttributes' => array( - 'actor' => TestUtils::objectFromArray( array( + 'actor' => TestActivityPubObject::fromArray( array( 'id' => 'https://example.com/actor/2', ) ), ), 'expectedAttributes' => array( 'signed' => true, - 'actor' => TestUtils::objectFromArray( array( + 'actor' => TestActivityPubObject::fromArray( array( 'id' => 'https://example.com/actor/2', ) ), ), @@ -114,7 +114,7 @@ oYi+1hqp1fIekaxsyQIDAQAB ), 'expectedAttributes' => array( 'signed' => true, - 'actor' => TestUtils::objectFromArray( array( + 'actor' => TestActivityPubObject::fromArray( array( 'id' => 'https://example.com/actor/1', ) ), ), @@ -137,27 +137,11 @@ oYi+1hqp1fIekaxsyQIDAQAB } } $this->signatureListener->validateHttpSignature( $event ); - foreach ( $testCase['expectedAttributes'] as $expectedKey => $expectedValue ) { - $this->assertTrue( - $event->getRequest()->attributes->has( $expectedKey ), - "Error on test $testCase[id]" - ); - xdebug_break(); - if ( $expectedValue instanceof ActivityPubObject ) { - $this->assertTrue( - $expectedValue->equals( - $event->getRequest()->attributes->get( $expectedKey ) - ), - "Error on test $testCase[id]" - ); - } else { - $this->assertEquals( - $expectedValue, - $event->getRequest()->attributes->get( $expectedKey ), - "Error on test $testCase[id]" - ); - } - } + $this->assertEquals( + $testCase['expectedAttributes'], + $event->getRequest()->attributes->all(), + "Error on test $testCase[id]" + ); } } } diff --git a/test/Controllers/GetControllerTest.php b/test/Controllers/GetControllerTest.php index f9bad68..dda226d 100644 --- a/test/Controllers/GetControllerTest.php +++ b/test/Controllers/GetControllerTest.php @@ -8,7 +8,7 @@ use ActivityPub\Entities\Field; use ActivityPub\Objects\ContextProvider; use ActivityPub\Objects\CollectionsService; use ActivityPub\Objects\ObjectsService; -use ActivityPub\Test\TestUtils\TestUtils; +use ActivityPub\Test\TestUtils\TestActivityPubObject; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -59,7 +59,7 @@ class GetControllerTest extends TestCase $objectsService->method( 'dereference' )->will( $this->returnCallback( function( $uri ) { if ( array_key_exists( $uri, self::OBJECTS ) ) { - return TestUtils::objectFromArray( self::OBJECTS[$uri] ); + return TestActivityPubObject::fromArray( self::OBJECTS[$uri] ); } }) ); diff --git a/test/Controllers/PostControllerTest.php b/test/Controllers/PostControllerTest.php index a79c367..031b76d 100644 --- a/test/Controllers/PostControllerTest.php +++ b/test/Controllers/PostControllerTest.php @@ -1,14 +1,96 @@ array( + 'id' => 'https://example.com/actor/1/inbox', + ), + ); + + public function testPostController() { - // TODO implement me - $this->assertTrue( false ); + $objectsService = $this->createMock( ObjectsService::class ); + $objectsService->method( 'query' )->will( + $this->returnCallback( function( $query ) { + if ( array_key_exists( 'id', $query ) && + array_key_exists( $query['id'], self::OBJECTS ) ) { + return array( TestActivityPubObject::fromArray( + self::OBJECTS[$query['id']] + ) ); + } else { + return array(); + } + } ) + ); + $testCases = array( + array( + 'id' => 'basicInboxTest', + 'request' => Request::create( + 'https://example.com/actor/1/inbox', + Request::METHOD_POST, + array(), array(), array(), array(), + '{"type": "Create"}' + ), + 'requestAttributes' => array( + 'signed' => true, + 'actor' => TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actor/1', + 'inbox' => array( + 'id' => 'https://example.com/actor/1/inbox', + ) + ) ), + ), + 'expectedEventName' => InboxActivityEvent::NAME, + 'expectedEvent' => new InboxActivityEvent( + array( 'type' => 'Create' ), + TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actor/1', + 'inbox' => array( + 'id' => 'https://example.com/actor/1/inbox', + ) + ) ), + Request::create( + 'https://example.com/actor/1/inbox', + Request::METHOD_POST, + array(), array(), array(), array(), + '{"type": "Create"}' + ) + ), + ), + ); + foreach ( $testCases as $testCase ) { + $eventDispatcher = $this->getMockBuilder( EventDispatcher::class ) + ->setMethods( array( 'dispatch' ) ) + ->getMock(); + if ( array_key_exists( 'expectedEvent', $testCase ) ) { + $eventDispatcher->expects( $this->once() ) + ->method( 'dispatch' ) + ->with( + $this->equalTo($testCase['expectedEventName']), + $this->equalTo($testCase['expectedEvent']) + ); + } + $postController = new PostController( $eventDispatcher, $objectsService ); + $request = $testCase['request']; + if ( array_key_exists( 'requestAttributes', $testCase ) ) { + $request->attributes->add( $testCase['requestAttributes'] ); + } + if ( array_key_exists( 'expectedException', $testCase ) ) { + $this->expectException( $testCase['expectedException'] ); + } + $postController->handle( $request ); + } } } ?> diff --git a/test/Objects/CollectionsServiceTest.php b/test/Objects/CollectionsServiceTest.php index d7b3e5e..c66e899 100644 --- a/test/Objects/CollectionsServiceTest.php +++ b/test/Objects/CollectionsServiceTest.php @@ -5,7 +5,7 @@ use Exception; use ActivityPub\Auth\AuthService; use ActivityPub\Objects\ContextProvider; use ActivityPub\Objects\CollectionsService; -use ActivityPub\Test\TestUtils\TestUtils; +use ActivityPub\Test\TestUtils\TestActivityPubObject; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -370,7 +370,8 @@ class CollectionsServiceTest extends TestCase $request->attributes->add( $testCase['requestAttributes'] ); } $actual = $this->collectionsService->pageAndFilterCollection( - $testCase['request'], TestUtils::objectFromArray( $testCase['collection'] ) + $testCase['request'], + TestActivityPubObject::fromArray( $testCase['collection'] ) ); $this->assertEquals( $testCase['expectedResult'], $actual, "Error on test $testCase[id]" diff --git a/test/TestUtils/TestActivityPubObject.php b/test/TestUtils/TestActivityPubObject.php new file mode 100644 index 0000000..f8f9a73 --- /dev/null +++ b/test/TestUtils/TestActivityPubObject.php @@ -0,0 +1,65 @@ +fixedTime = $time; + parent::__construct( $time ); + } + + public function addField( Field $field, DateTime $time = null ) + { + parent::addField( $field, $time ); + $this->lastUpdated = $this->fixedTime; + } + + public function removeField( Field $field, DateTime $time = null ) + { + parent::removeField( $field, $time ); + $this->lastUpdated = $this->fixedTime; + } + + public function setLastUpdated( $lastUpdated ) + { + // do not change lastUpdated + } + + public static function fromArray( array $arr, DateTime $time = null ) + { + if ( ! $time ) { + $time = self::getDefaultTime(); + } + $object = new TestActivityPubObject( $time ); + foreach ( $arr as $name => $value ) { + if ( is_array( $value ) ) { + $child = self::fromArray( $value, $time ); + TestField::withObject( $object, $name, $child, $time ); + } else { + TestField::withValue( $object, $name, $value, $time ); + } + } + return $object; + } +} +?> diff --git a/test/TestUtils/TestField.php b/test/TestUtils/TestField.php new file mode 100644 index 0000000..2ee1968 --- /dev/null +++ b/test/TestUtils/TestField.php @@ -0,0 +1,46 @@ +fixedTime = $time; + } + + public function setTargetObject( $targetObject, $time = null ) + { + parent::setTargetObject( $targetObject, $time ); + $this->lastUpdated = $this->fixedTime; + } + + public function setValue( $value, $time = null ) + { + parent::setValue( $value, $time ); + $this->lastUpdated = $this->fixedTime; + } + + protected function setCreated( $timestamp ) + { + // don't set created + } + + protected function setLastupdated( $timestamp ) + { + // don't set lastUpdated + } + +} +?> diff --git a/test/TestUtils/TestUtils.php b/test/TestUtils/TestUtils.php deleted file mode 100644 index 74a743e..0000000 --- a/test/TestUtils/TestUtils.php +++ /dev/null @@ -1,22 +0,0 @@ - $value ) { - if ( is_array( $value ) ) { - $child = self::objectFromArray( $value ); - Field::withObject( $object, $name, $child ); - } else { - Field::withValue( $object, $name, $value ); - } - } - return $object; - } -} -?>