diff --git a/src/Activities/RemoveHandler.php b/src/Activities/RemoveHandler.php new file mode 100644 index 0000000..44989c8 --- /dev/null +++ b/src/Activities/RemoveHandler.php @@ -0,0 +1,63 @@ + 'handleRemove', + OutboxActivityEvent::NAME => 'handleRemove', + ); + } + + public function __construct( ObjectsService $objectsService, + CollectionsService $collectionsService ) + { + $this->objectsService = $objectsService; + $this->collectionsService = $collectionsService; + } + + public function handleRemove( ActivityEvent $event ) + { + $activity = $event->getActivity(); + if ( $activity['type'] !== 'Remove' ) { + return; + } + $collectionId = $activity['target']; + if ( is_array( $collectionId ) && array_key_exists( 'id', $collectionId ) ) { + $collectionId = $collectionId['id']; + } + $collection = $this->objectsService->dereference( $collectionId ); + $requestActor = $event->getRequest()->attributes->get( 'actor' ); + $requestActorHost = parse_url( $requestActor['id'], PHP_URL_HOST ); + $collectionHost = parse_url( $collection['id'], PHP_URL_HOST ); + if ( $requestActorHost !== $collectionHost ) { + throw new AccessDeniedHttpException(); + } + $objectId = $activity['object']; + if ( is_array( $objectId ) && array_key_exists( 'id', $objectId ) ) { + $objectId = $objectId['id']; + } + if ( ! is_string( $objectId ) ) { + return; + } + $this->collectionsService->removeItem( $collection, $objectId ); + } +} \ No newline at end of file diff --git a/test/Activities/RemoveHandlerTest.php b/test/Activities/RemoveHandlerTest.php new file mode 100644 index 0000000..48456c8 --- /dev/null +++ b/test/Activities/RemoveHandlerTest.php @@ -0,0 +1,151 @@ + array( + 'id' => 'https://elsewhere.com/collections/1', + ), + 'https://example.com/collections/1' => array( + 'id' => 'https://example.com/collections/1', + ), + ); + } + + public function testHandleRemove() + { + $testCases = array( + array( + 'id' => 'basicTest', + 'eventName' => InboxActivityEvent::NAME, + 'event' => new InboxActivityEvent( + array( + 'id' => 'https://elsewhere.com/removes/1', + 'type' => 'Remove', + 'object' => array( + 'id' => 'https://elsewhere.com/notes/1', + ), + 'target' => array( + 'id' => 'https://elsewhere.com/collections/1' + ) + ), + TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/1', + ) ), + self::requestWithAttributes( + 'https://example.com/actors/1/inbox', + array( + 'actor' => TestActivityPubObject::fromArray( array( + 'id' => 'https://elsewhere.com/actors/1' + ) ) + ) + ) + ), + 'expectedRemovedItemId' => 'https://elsewhere.com/notes/1', + ), + array( + 'id' => 'outboxTest', + 'eventName' => OutboxActivityEvent::NAME, + 'event' => new OutboxActivityEvent( + array( + 'id' => 'https://example.com/removes/1', + 'type' => 'Remove', + 'object' => array( + 'id' => 'https://example.com/notes/1', + ), + 'target' => array( + 'id' => 'https://example.com/collections/1' + ) + ), + TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/1', + ) ), + self::requestWithAttributes( + 'https://example.com/actors/1/inbox', + array( + 'actor' => TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/1' + ) ) + ) + ) + ), + 'expectedRemovedItemId' => 'https://example.com/notes/1', + ), + array( + 'id' => 'notAuthorizedTest', + 'eventName' => OutboxActivityEvent::NAME, + 'event' => new OutboxActivityEvent( + array( + 'id' => 'https://example.com/removes/1', + 'type' => 'Remove', + 'object' => array( + 'id' => 'https://example.com/notes/1', + ), + 'target' => array( + 'id' => 'https://elsewhere.com/collections/1' + ) + ), + TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/1', + ) ), + self::requestWithAttributes( + 'https://example.com/actors/1/inbox', + array( + 'actor' => TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/1' + ) ) + ) + ) + ), + 'expectedException' => AccessDeniedHttpException::class, + ), + ); + foreach ( $testCases as $testCase ) { + $objectsService = $this->getMock( ObjectsService::class ); + $objectsService->method( 'dereference')->willReturnCallback( function( $id ) { + $objects = self::getObjects(); + if ( array_key_exists( $id, $objects ) ) { + return TestActivityPubObject::fromArray( $objects[$id] ); + } else { + return null; + } + }); + $collectionsService = $this->getMockBuilder( CollectionsService::class ) + ->disableOriginalConstructor() + ->setMethods( array( 'removeItem' ) ) + ->getMock(); + if ( array_key_exists( 'expectedRemovedItemId', $testCase ) ) { + $collectionsService->expects( $this->once() ) + ->method( 'removeItem' ) + ->with( + $this->anything(), + $this->equalTo( $testCase['expectedRemovedItemId'] ) + ); + } else { + $collectionsService->expects( $this->never() ) + ->method( 'removeItem' ); + } + $removeHandler = new RemoveHandler( $objectsService, $collectionsService ); + $eventDispatcher = new EventDispatcher(); + $eventDispatcher->addSubscriber( $removeHandler ); + if ( array_key_exists( 'expectedException', $testCase ) ) { + $this->setExpectedException( $testCase['expectedException'] ); + } + $eventDispatcher->dispatch( $testCase['eventName'], $testCase['event'] ); + } + } +} \ No newline at end of file