src/Controller/WebhookController.php line 31

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Service\DoceboLearnerSync;
  4. use Psr\Log\LoggerInterface;
  5. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  6. use Symfony\Component\HttpFoundation\JsonResponse;
  7. use Symfony\Component\HttpFoundation\Request;
  8. use Symfony\Component\Routing\Annotation\Route;
  9. /**
  10. * Webhooks Docebo
  11. */
  12. class WebhookController extends AbstractController
  13. {
  14. private $syncUser;
  15. private $syncPassword;
  16. public function __construct(string $syncUser, string $syncPassword)
  17. {
  18. $this->syncUser = $syncUser;
  19. $this->syncPassword = $syncPassword;
  20. }
  21. /**
  22. * Webhook Docebo "sync-users-in-dash-app" : branch.user.added / branch.user.removed / user.updated / user.created / branch.created
  23. *
  24. * @Route("/sync-learners", name="sync_learners", methods={"POST"})
  25. */
  26. public function syncLearners(Request $request, DoceboLearnerSync $sync, LoggerInterface $logger)
  27. {
  28. if (!$this->isAuthorized($request)) {
  29. return new JsonResponse(['success' => false, 'message' => 'Unauthorized'], 401, ['WWW-Authenticate' => 'Basic realm="sync-learners"']);
  30. }
  31. $data = json_decode($request->getContent(), true);
  32. if (!is_array($data) || empty($data['event'])) {
  33. return new JsonResponse(['success' => false, 'message' => 'Invalid payload'], 400);
  34. }
  35. $event = $data['event'];
  36. $required = [
  37. 'branch.user.added' => ['user_id', 'branch_id'],
  38. 'branch.user.removed' => ['user_id', 'branch_id'],
  39. 'user.updated' => ['user_id'],
  40. 'user.created' => ['user_id'],
  41. 'branch.created' => ['branch_id'],
  42. ];
  43. $items = $data['payloads'] ?? [$data['payload'] ?? null];
  44. $results = [];
  45. try {
  46. foreach ($items as $item) {
  47. if (!is_array($item) || !$this->hasRequired($item, $required[$event] ?? [])
  48. || ($event === 'branch.created' && !isset($item['parent_id']))) {
  49. $results[] = ['status' => 'invalid_item'];
  50. continue;
  51. }
  52. switch ($event) {
  53. case 'branch.user.added':
  54. $results[] = $sync->addToBranch((int) $item['user_id'], (int) $item['branch_id'], $item['username'] ?? null);
  55. break;
  56. case 'branch.user.removed':
  57. $results[] = $sync->removeFromBranch((int) $item['user_id'], (int) $item['branch_id']);
  58. break;
  59. case 'user.updated':
  60. $results[] = $sync->updateUser((int) $item['user_id'], $item);
  61. break;
  62. case 'user.created':
  63. $results[] = $sync->createUser((int) $item['user_id'], $item);
  64. break;
  65. case 'branch.created':
  66. $results[] = $sync->branchCreated((int) $item['branch_id'], (int) $item['parent_id'], $item['code'] ?? null, $item['name'] ?? null);
  67. break;
  68. default:
  69. $results[] = ['status' => 'ignored'];
  70. break;
  71. }
  72. }
  73. } catch (\Throwable $e) {
  74. $logger->error('[sync-learners] ' . $e->getMessage(), [
  75. 'event' => $event,
  76. 'message_id' => $data['message_id'] ?? null,
  77. 'exception' => $e,
  78. ]);
  79. return new JsonResponse(['success' => false, 'message' => 'Internal error'], 500);
  80. }
  81. return new JsonResponse([
  82. 'success' => true,
  83. 'event' => $event,
  84. 'message_id' => $data['message_id'] ?? null,
  85. 'results' => $results,
  86. ]);
  87. }
  88. private function hasRequired(array $item, array $fields): bool
  89. {
  90. foreach ($fields as $field) {
  91. if (empty($item[$field])) {
  92. return false;
  93. }
  94. }
  95. return true;
  96. }
  97. private function isAuthorized(Request $request): bool
  98. {
  99. if ($this->syncUser === '' || $this->syncPassword === '') {
  100. return false;
  101. }
  102. $user = $request->getUser();
  103. $password = $request->getPassword();
  104. // Fallback si PHP_AUTH_USER n'est pas renseigné (CGI / proxy)
  105. if ($user === null) {
  106. $header = $request->headers->get('Authorization', '');
  107. if (stripos($header, 'Basic ') === 0) {
  108. $decoded = base64_decode(substr($header, 6), true);
  109. if ($decoded !== false && strpos($decoded, ':') !== false) {
  110. list($user, $password) = explode(':', $decoded, 2);
  111. }
  112. }
  113. }
  114. return hash_equals($this->syncUser, (string) $user) && hash_equals($this->syncPassword, (string) $password);
  115. }
  116. }