Merge pull request #3172 from aynsix/PHRAS-2680-webhook-privacy-and-security

PHRAS-2680 #comment merge of webhook privacy and security in worker context
This commit is contained in:
Nicolas Maillat
2020-03-18 11:17:49 +01:00
committed by GitHub
21 changed files with 144 additions and 53 deletions

View File

@@ -167,11 +167,11 @@ class AccountService
* @param string $login * @param string $login
* @throws AccountException * @throws AccountException
*/ */
public function deleteAccount($login = null) public function deleteAccount($login = null, array $grantedBaseIdList = array())
{ {
$user = $this->getUserOrCurrentUser($login); $user = $this->getUserOrCurrentUser($login);
$this->userManipulator->delete($user); $this->userManipulator->delete($user, $grantedBaseIdList);
} }
/** /**

View File

@@ -328,11 +328,9 @@ class RegistrationService
$autoReg = $acl->get_granted_base(); $autoReg = $acl->get_granted_base();
$granted = [];
foreach ($autoReg as $baseId => $collection) { foreach ($autoReg as $baseId => $collection) {
$granted[$baseId] = $collection->get_label($this->app['locale']); $granted[$baseId] = $collection->get_label($this->app['locale']);
}
if(count($granted) > 0) {
$this->app['manipulator.webhook-event']->create( $this->app['manipulator.webhook-event']->create(
WebhookEvent::USER_REGISTRATION_GRANTED, WebhookEvent::USER_REGISTRATION_GRANTED,
WebhookEvent::USER_REGISTRATION_TYPE, WebhookEvent::USER_REGISTRATION_TYPE,
@@ -340,8 +338,11 @@ class RegistrationService
'user_id' => $user->getId(), 'user_id' => $user->getId(),
'granted' => $granted, 'granted' => $granted,
'rejected' => [] 'rejected' => []
] ],
[$baseId]
); );
unset($granted);
} }

View File

@@ -515,9 +515,9 @@ class UserController extends Controller
$denyColl[] = $label; $denyColl[] = $label;
$hookData['rejected'][$bas] = $label; $hookData['rejected'][$bas] = $label;
} }
}
$this->app['manipulator.webhook-event']->create($hookName, $hookType, $hookData); $this->app['manipulator.webhook-event']->create($hookName, $hookType, $hookData, [$bas]);
}
if ($user->hasMailNotificationsActivated() && (0 !== count($acceptColl) || 0 !== count($denyColl))) { if ($user->hasMailNotificationsActivated() && (0 !== count($acceptColl) || 0 !== count($denyColl))) {
$message = ''; $message = '';

View File

@@ -518,7 +518,9 @@ class AccountController extends Controller
$this->getApiApplicationManipulator()->deleteApiApplications($applications); $this->getApiApplicationManipulator()->deleteApiApplications($applications);
// revoke access and delete phraseanet user account // get list of old granted base_id then revoke access and delete phraseanet user account
$oldGrantedBaseIds = array_keys($this->app->getAclForUser($user)->get_granted_base());
$list = array_keys($this->app['repo.collections-registry']->getBaseIdMap()); $list = array_keys($this->app['repo.collections-registry']->getBaseIdMap());
@@ -542,8 +544,9 @@ class AccountController extends Controller
$mail = null; $mail = null;
} }
$this->app['manipulator.user']->delete($user); $mail = MailSuccessAccountDelete::create($this->app, $receiver);
$this->app['manipulator.user']->delete($user, [$user->getId() => $oldGrantedBaseIds]);
if($mail) { if($mail) {
$this->deliver($mail); $this->deliver($mail);
} }

View File

@@ -33,7 +33,8 @@ class FeedEntrySubscriber extends AbstractNotificationSubscriber
$this->app['manipulator.webhook-event']->create( $this->app['manipulator.webhook-event']->create(
WebhookEvent::NEW_FEED_ENTRY, WebhookEvent::NEW_FEED_ENTRY,
WebhookEvent::FEED_ENTRY_TYPE, WebhookEvent::FEED_ENTRY_TYPE,
array_merge(array('feed_id' => $entry->getFeed()->getId()), $params) array_merge(array('feed_id' => $entry->getFeed()->getId()), $params),
$entry->getFeed()->getBaseId() ? [$entry->getFeed()->getBaseId()] : []
); );
$datas = json_encode($params); $datas = json_encode($params);

View File

@@ -41,13 +41,13 @@ class OrderSubscriber extends AbstractNotificationSubscriber
public function onCreate(OrderEvent $event) public function onCreate(OrderEvent $event)
{ {
$base_ids = array_unique(array_map(function (OrderElement $element) { $baseIds = array_unique(array_map(function (OrderElement $element) {
return $element->getBaseId(); return $element->getBaseId();
}, iterator_to_array($event->getOrder()->getElements()))); }, iterator_to_array($event->getOrder()->getElements())));
$query = $this->app['phraseanet.user-query']; $query = $this->app['phraseanet.user-query'];
/** @var User[] $users */ /** @var User[] $users */
$users = $query->on_base_ids($base_ids) $users = $query->on_base_ids($baseIds)
->who_have_right([\ACL::ORDER_MASTER]) ->who_have_right([\ACL::ORDER_MASTER])
->execute()->get_results(); ->execute()->get_results();
@@ -60,10 +60,12 @@ class OrderSubscriber extends AbstractNotificationSubscriber
'order_id' => $event->getOrder()->getId(), 'order_id' => $event->getOrder()->getId(),
]); ]);
$notifier = $this->notifierRegistry->getNotifier($event->getOrder()->getNotificationMethod()); // notify by webhook
$notifier = $this->notifierRegistry->getNotifier(Order::NOTIFY_WEBHOOK);
$notifier->notifyCreation($event->getOrder(), $event->getOrder()->getUser()); $notifier->notifyCreation($event->getOrder(), $event->getOrder()->getUser(), $baseIds);
// notify by mail
$notifier = $this->notifierRegistry->getNotifier(Order::NOTIFY_MAIL); $notifier = $this->notifierRegistry->getNotifier(Order::NOTIFY_MAIL);
foreach ($users as $user) { foreach ($users as $user) {
@@ -85,7 +87,13 @@ class OrderSubscriber extends AbstractNotificationSubscriber
public function onDeliver(OrderDeliveryEvent $event) public function onDeliver(OrderDeliveryEvent $event)
{ {
// notify by webhook
$notifier = $this->notifierRegistry->getNotifier(Order::NOTIFY_WEBHOOK);
$notifier->notifyDelivery($event->getDelivery(), $event->getDelivery()->getPartialOrder()->getBaseIds());
$notified = false; $notified = false;
// actually NotificationMethod is always by mail
$notifier = $this->notifierRegistry->getNotifier($event->getOrder()->getNotificationMethod()); $notifier = $this->notifierRegistry->getNotifier($event->getOrder()->getNotificationMethod());
$notificationData = json_encode([ $notificationData = json_encode([
'from' => $event->getDelivery()->getAdmin()->getId(), 'from' => $event->getDelivery()->getAdmin()->getId(),
@@ -109,7 +117,13 @@ class OrderSubscriber extends AbstractNotificationSubscriber
public function onDeny(OrderDeliveryEvent $event) public function onDeny(OrderDeliveryEvent $event)
{ {
// notify by webhook
$notifier = $this->notifierRegistry->getNotifier(Order::NOTIFY_WEBHOOK);
$notifier->notifyDenial($event->getDelivery(), $event->getDelivery()->getPartialOrder()->getBaseIds());
$notified = false; $notified = false;
// actually NotificationMethod is always by mail
$notifier = $this->notifierRegistry->getNotifier($event->getOrder()->getNotificationMethod()); $notifier = $this->notifierRegistry->getNotifier($event->getOrder()->getNotificationMethod());
$notificationData = json_encode([ $notificationData = json_encode([
'from' => $event->getDelivery()->getAdmin()->getId(), 'from' => $event->getDelivery()->getAdmin()->getId(),

View File

@@ -45,7 +45,7 @@ class WebhookUserEventSubscriber implements EventSubscriberInterface
'user_id' => $event->getUserId(), 'user_id' => $event->getUserId(),
'email' => $event->getEmailAddress(), 'email' => $event->getEmailAddress(),
'login' => $event->getLogin() 'login' => $event->getLogin()
]); ], $event->getGrantedBaseIds());
} }
public static function getSubscribedEvents() public static function getSubscribedEvents()

View File

@@ -36,4 +36,12 @@ class DeletedEvent extends UserEvent
{ {
return $this->args['email']; return $this->args['email'];
} }
/**
* @return array
*/
public function getGrantedBaseIds()
{
return $this->args['grantedBaseIds'];
}
} }

View File

@@ -73,10 +73,12 @@ class Edit extends \Alchemy\Phrasea\Helper\Helper
{ {
$list = array_keys($this->app->getAclForUser($this->app->getAuthenticatedUser())->get_granted_base([\ACL::CANADMIN])); $list = array_keys($this->app->getAclForUser($this->app->getAuthenticatedUser())->get_granted_base([\ACL::CANADMIN]));
$oldGrantedBaseIds = array_keys($this->app->getAclForUser($user)->get_granted_base());
$this->app->getAclForUser($user)->revoke_access_from_bases($list); $this->app->getAclForUser($user)->revoke_access_from_bases($list);
if ($this->app->getAclForUser($user)->is_phantom()) { if ($this->app->getAclForUser($user)->is_phantom()) {
$this->app['manipulator.user']->delete($user); $this->app['manipulator.user']->delete($user, [$user->getId() => $oldGrantedBaseIds]);
} }
return $this; return $this;

View File

@@ -68,6 +68,14 @@ class WebhookEvent
*/ */
private $created; private $created;
/**
* List of collection base_id concerned
* @var array
*
* @ORM\Column(name="collection_base_ids", type="json_array", nullable=true)
*/
private $collectionBaseIds;
/** /**
* @param \DateTime $created * @param \DateTime $created
* *
@@ -175,4 +183,24 @@ class WebhookEvent
return $this; return $this;
} }
/**
* @param array $collectionBaseIds
*
* @return $this
*/
public function setCollectionBaseIds(array $collectionBaseIds)
{
$this->collectionBaseIds = $collectionBaseIds;
return $this;
}
/**
* @return array
*/
public function getCollectionBaseIds()
{
return $this->collectionBaseIds;
}
} }

View File

@@ -126,8 +126,9 @@ class UserManipulator implements ManipulatorInterface
* Deletes a user. * Deletes a user.
* *
* @param User|User[] $users * @param User|User[] $users
* @param array $grantedBaseIdList List of the old granted base_id per userId [user_id => [base_id, ...] ]
*/ */
public function delete($users) public function delete($users, array $grantedBaseIdList = array())
{ {
/** @var User $user */ /** @var User $user */
foreach ($this->makeTraversable($users) as $user) { foreach ($this->makeTraversable($users) as $user) {
@@ -146,9 +147,10 @@ class UserManipulator implements ManipulatorInterface
new DeletedEvent( new DeletedEvent(
null, null,
array( array(
'user_id'=>$old_id, 'user_id' => $old_id,
'login'=>$old_login, 'login' => $old_login,
'email'=>$old_email 'email' => $old_email,
'grantedBaseIds' => isset($grantedBaseIdList[$old_id]) ? $grantedBaseIdList[$old_id] : []
) )
) )
); );

View File

@@ -40,7 +40,7 @@ class WebhookEventManipulator implements ManipulatorInterface
$this->publisher = $publisher; $this->publisher = $publisher;
} }
public function create($eventName, $type, array $data) public function create($eventName, $type, array $data, array $collectionBaseIds = array())
{ {
$event = new WebhookEvent(); $event = new WebhookEvent();
@@ -48,6 +48,10 @@ class WebhookEventManipulator implements ManipulatorInterface
$event->setType($type); $event->setType($type);
$event->setData($data); $event->setData($data);
if (count($collectionBaseIds) > 0) {
$event->setCollectionBaseIds($collectionBaseIds);
}
$this->update($event); $this->update($event);
$this->publisher->publishWebhookEvent($event); $this->publisher->publishWebhookEvent($event);

View File

@@ -172,7 +172,7 @@ class BaseOrderController extends Controller
$manager->persist($element); $manager->persist($element);
} }
$delivery = new OrderDelivery($order, $acceptor, count($basketElements)); $delivery = new OrderDelivery($order, $acceptor, count($basketElements), $partialOrder);
$this->dispatch(PhraseaEvents::ORDER_DELIVER, new OrderDeliveryEvent($delivery)); $this->dispatch(PhraseaEvents::ORDER_DELIVER, new OrderDeliveryEvent($delivery));
} }
@@ -198,11 +198,13 @@ class BaseOrderController extends Controller
$elements = $this->findRequestedElements($order_id, $elementIds, $acceptor); $elements = $this->findRequestedElements($order_id, $elementIds, $acceptor);
$order = $this->findOr404($order_id); $order = $this->findOr404($order_id);
$partialOrder = new PartialOrder($order, $elements);
$this->getOrderValidator()->deny($acceptor, new PartialOrder($order, $elements)); $this->getOrderValidator()->deny($acceptor, new PartialOrder($order, $elements));
try { try {
if (!empty($elements)) { if (!empty($elements)) {
$delivery = new OrderDelivery($order, $acceptor, count($elements)); $delivery = new OrderDelivery($order, $acceptor, count($elements), $partialOrder);
$this->dispatch(PhraseaEvents::ORDER_DENY, new OrderDeliveryEvent($delivery)); $this->dispatch(PhraseaEvents::ORDER_DENY, new OrderDeliveryEvent($delivery));
} }

View File

@@ -31,16 +31,23 @@ class OrderDelivery
*/ */
private $quantity; private $quantity;
/**
* @var PartialOrder
*/
private $partialOrder;
/** /**
* @param Order $deliveredOrder * @param Order $deliveredOrder
* @param User $manager * @param User $manager
* @param int $quantity * @param int $quantity
* @param PartialOrder $partialOrder
*/ */
public function __construct(Order $deliveredOrder, User $manager, $quantity) public function __construct(Order $deliveredOrder, User $manager, $quantity, PartialOrder $partialOrder)
{ {
$this->order = $deliveredOrder; $this->order = $deliveredOrder;
$this->admin = $manager; $this->admin = $manager;
$this->quantity = $quantity; $this->quantity = $quantity;
$this->partialOrder = $partialOrder;
} }
/** /**
@@ -66,4 +73,12 @@ class OrderDelivery
{ {
return $this->quantity; return $this->quantity;
} }
/**
* @return PartialOrder
*/
public function getPartialOrder()
{
return $this->partialOrder;
}
} }

View File

@@ -19,20 +19,23 @@ interface ValidationNotifier
/** /**
* @param Order $order * @param Order $order
* @param User $recipient * @param User $recipient
* @param array $baseIds
* @return void * @return void
*/ */
public function notifyCreation(Order $order, User $recipient); public function notifyCreation(Order $order, User $recipient, array $baseIds = array());
/** /**
* @param OrderDelivery $delivery * @param OrderDelivery $delivery
* @param array $baseIds
* @return void * @return void
*/ */
public function notifyDelivery(OrderDelivery $delivery); public function notifyDelivery(OrderDelivery $delivery, array $baseIds = array());
/** /**
* @param OrderDelivery $delivery * @param OrderDelivery $delivery
* @param array $baseIds
* @return void * @return void
*/ */
public function notifyDenial(OrderDelivery $delivery); public function notifyDenial(OrderDelivery $delivery, array $baseIds = array());
} }

View File

@@ -26,8 +26,9 @@ class CompositeNotifier implements ValidationNotifier
/** /**
* @param Order $order * @param Order $order
* @param User $recipient * @param User $recipient
* @param array $baseIds
*/ */
public function notifyCreation(Order $order, User $recipient) public function notifyCreation(Order $order, User $recipient, array $baseIds = array())
{ {
foreach ($this->notifiers as $notifier) { foreach ($this->notifiers as $notifier) {
$notifier->notifyCreation($order, $recipient); $notifier->notifyCreation($order, $recipient);
@@ -36,21 +37,23 @@ class CompositeNotifier implements ValidationNotifier
/** /**
* @param OrderDelivery $delivery * @param OrderDelivery $delivery
* @param array $baseIds
*/ */
public function notifyDelivery(OrderDelivery $delivery) public function notifyDelivery(OrderDelivery $delivery, array $baseIds = array())
{ {
foreach ($this->notifiers as $notifier) { foreach ($this->notifiers as $notifier) {
$notifier->notifyDelivery($delivery); $notifier->notifyDelivery($delivery, $baseIds);
} }
} }
/** /**
* @param OrderDelivery $delivery * @param OrderDelivery $delivery
* @param array $baseIds
*/ */
public function notifyDenial(OrderDelivery $delivery) public function notifyDenial(OrderDelivery $delivery, array $baseIds = array())
{ {
foreach ($this->notifiers as $notifier) { foreach ($this->notifiers as $notifier) {
$notifier->notifyDenial($delivery); $notifier->notifyDenial($delivery, $baseIds);
} }
} }
} }

View File

@@ -46,8 +46,9 @@ class MailNotifier implements ValidationNotifier
/** /**
* @param Order $order * @param Order $order
* @param User $recipient * @param User $recipient
* @param array $baseIds
*/ */
public function notifyCreation(Order $order, User $recipient) public function notifyCreation(Order $order, User $recipient, array $baseIds = array())
{ {
$mail = MailInfoNewOrder::create($this->application, Receiver::fromUser($recipient)); $mail = MailInfoNewOrder::create($this->application, Receiver::fromUser($recipient));
@@ -58,8 +59,9 @@ class MailNotifier implements ValidationNotifier
/** /**
* @param OrderDelivery $delivery * @param OrderDelivery $delivery
* @param array $baseIds
*/ */
public function notifyDelivery(OrderDelivery $delivery) public function notifyDelivery(OrderDelivery $delivery, array $baseIds = array())
{ {
$order = $delivery->getOrder(); $order = $delivery->getOrder();
@@ -85,8 +87,9 @@ class MailNotifier implements ValidationNotifier
/** /**
* @param OrderDelivery $delivery * @param OrderDelivery $delivery
* @param array $baseIds
*/ */
public function notifyDenial(OrderDelivery $delivery) public function notifyDenial(OrderDelivery $delivery, array $baseIds = array())
{ {
$sender = Emitter::fromUser($delivery->getAdmin()); $sender = Emitter::fromUser($delivery->getAdmin());
$recipient = Receiver::fromUser($delivery->getOrder()->getUser()); $recipient = Receiver::fromUser($delivery->getOrder()->getUser());

View File

@@ -47,21 +47,23 @@ class WebhookNotifier implements ValidationNotifier
/** /**
* @param Order $order * @param Order $order
* @param User $recipient * @param User $recipient
* @param array $baseIds
*/ */
public function notifyCreation(Order $order, User $recipient) public function notifyCreation(Order $order, User $recipient, array $baseIds = array())
{ {
$eventData = [ $eventData = [
'order_id' => $order->getId(), 'order_id' => $order->getId(),
'user_id' => $recipient->getId(), 'user_id' => $recipient->getId(),
]; ];
$this->getManipulator()->create(WebhookEvent::ORDER_CREATED, WebhookEvent::ORDER_TYPE, $eventData); $this->getManipulator()->create(WebhookEvent::ORDER_CREATED, WebhookEvent::ORDER_TYPE, $eventData, $baseIds);
} }
/** /**
* @param OrderDelivery $delivery * @param OrderDelivery $delivery
* @param array $baseIds
*/ */
public function notifyDelivery(OrderDelivery $delivery) public function notifyDelivery(OrderDelivery $delivery, array $baseIds = array())
{ {
$eventData = [ $eventData = [
'order_id' => $delivery->getOrder()->getId(), 'order_id' => $delivery->getOrder()->getId(),
@@ -69,13 +71,14 @@ class WebhookNotifier implements ValidationNotifier
'quantity' => $delivery->getQuantity() 'quantity' => $delivery->getQuantity()
]; ];
$this->getManipulator()->create(WebhookEvent::ORDER_DELIVERED, WebhookEvent::ORDER_TYPE, $eventData); $this->getManipulator()->create(WebhookEvent::ORDER_DELIVERED, WebhookEvent::ORDER_TYPE, $eventData, $baseIds);
} }
/** /**
* @param OrderDelivery $delivery * @param OrderDelivery $delivery
* @param array $baseIds
*/ */
public function notifyDenial(OrderDelivery $delivery) public function notifyDenial(OrderDelivery $delivery, array $baseIds = array())
{ {
$eventData = [ $eventData = [
'order_id' => $delivery->getOrder()->getId(), 'order_id' => $delivery->getOrder()->getId(),
@@ -83,6 +86,6 @@ class WebhookNotifier implements ValidationNotifier
'quantity' => $delivery->getQuantity() 'quantity' => $delivery->getQuantity()
]; ];
$this->getManipulator()->create(WebhookEvent::ORDER_DENIED, WebhookEvent::ORDER_TYPE, $eventData); $this->getManipulator()->create(WebhookEvent::ORDER_DENIED, WebhookEvent::ORDER_TYPE, $eventData, $baseIds);
} }
} }

View File

@@ -34,17 +34,16 @@ class FeedEntryProcessor implements ProcessorInterface
{ {
$data = $event->getData(); $data = $event->getData();
if (!isset($data->entry_id)) { if (!isset($data['entry_id'])) {
return null; return null;
} }
$entry = $this->entryRepository->find($data->entry_id); $entry = $this->entryRepository->find($data['entry_id']);
if (null === $entry) { if (null === $entry) {
return null; return null;
} }
$data = $event->getData();
$feed = $entry->getFeed(); $feed = $entry->getFeed();
$query = $this->userQuery; $query = $this->userQuery;
@@ -54,8 +53,8 @@ class FeedEntryProcessor implements ProcessorInterface
->include_templates(false) ->include_templates(false)
->email_not_null(true); ->email_not_null(true);
if ($feed->getCollection($this->app)) { if ($feed->getCollection($this->application)) {
$query->on_base_ids([$feed->getCollection($this->app)->get_base_id()]); $query->on_base_ids([$feed->getCollection($this->application)->get_base_id()]);
} }
$start = 0; $start = 0;
@@ -76,7 +75,7 @@ class FeedEntryProcessor implements ProcessorInterface
return [ return [
'event' => $event->getName(), 'event' => $event->getName(),
'users_were_notified' => isset($data->notify_email) ?: (bool) $data->notify_email, 'users_were_notified' => isset($data['notify_email']) ? (bool) $data['notify_email'] : false,
'feed' => [ 'feed' => [
'id' => $feed->getId(), 'id' => $feed->getId(),
'title' => $feed->getTitle(), 'title' => $feed->getTitle(),

View File

@@ -79,7 +79,7 @@ class UserDeletionTest extends \PhraseanetAuthenticatedWebTestCase
$apiLog = $apiLogManipulator->create($account, new Request(), new Response()); $apiLog = $apiLogManipulator->create($account, new Request(), new Response());
$apiLogId = $apiLog->getId(); $apiLogId = $apiLog->getId();
$this->userManipulator->delete($this->user, true); $this->userManipulator->delete($this->user);
$this->assertTrue($this->user->isDeleted(), 'User was not properly deleted'); $this->assertTrue($this->user->isDeleted(), 'User was not properly deleted');
$apiLogRepository->clear(); $apiLogRepository->clear();

View File

@@ -59,6 +59,6 @@ class FeedEntryProcessorTest extends \PhraseanetTestCase
self::$DI['app']['repo.feed-entries'], self::$DI['app']['repo.feed-entries'],
self::$DI['app']['phraseanet.user-query'] self::$DI['app']['phraseanet.user-query']
); );
$this->assertEquals($processor->process($event), null); $this->assertInternalType(\PHPUnit_Framework_Constraint_IsType::TYPE_ARRAY, $processor->process($event));
} }
} }