Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther
VAPID_PUBLIC_KEY='test'
VAPID_PRIVATE_KEY='test'
Binary file modified screenshots/7.9.1/original/original-subscriptions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified screenshots/7.9.1/resized/resized-subscriptions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
106 changes: 54 additions & 52 deletions src/Command/SendNotificationsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,59 +53,61 @@ protected function execute(InputInterface $input, OutputInterface $output): int

foreach ($notifications as $notification) {
foreach ($subscriptions as $subscription) {
switch ($subscription->getType()) {
case AppSubscriptionModel::TYPE_PUSH:
$publicKey = $subscription->getPublicKey();
$authenticationSecret = $subscription->getAuthenticationSecret();
$contentEncoding = $subscription->getContentEncoding();

if ($publicKey && $authenticationSecret && $contentEncoding) {
$payload = [
'tag' => uniqid('', true),
'title' => $notification->getTitle(),
'body' => $notification->getBody(),
'icon' => $notification->getIcon(),
];

$subscription = Subscription::create([
'endpoint' => $subscription->getEndpoint(),
'publicKey' => $publicKey,
'authToken' => $authenticationSecret,
'contentEncoding' => $contentEncoding,
]);

$webPush->queueNotification($subscription, json_encode($payload));
}
break;

case AppSubscriptionModel::TYPE_SLACK:
try {
$options = [
'json' => [
'text' => $notification->getTitle()."\r\n".$notification->getBody(),
],
];
$this->client->request('POST', $subscription->getEndpoint(), $options);
} catch (TransportException $e) {
$output->writeln('<error>Message failed to sent for subscription '.$subscription->getEndpoint().': '.$e->getMessage().'</error>');
}
break;

case AppSubscriptionModel::TYPE_TEAMS:
try {
$options = [
'json' => [
'@context' => 'https://schema.org/extensions',
'@type' => 'MessageCard',
if (true === in_array($notification->getType(), $subscription->getNotifications())) {
switch ($subscription->getType()) {
case AppSubscriptionModel::TYPE_PUSH:
$publicKey = $subscription->getPublicKey();
$authenticationSecret = $subscription->getAuthenticationSecret();
$contentEncoding = $subscription->getContentEncoding();

if ($publicKey && $authenticationSecret && $contentEncoding) {
$payload = [
'tag' => uniqid('', true),
'title' => $notification->getTitle(),
'text' => $notification->getBody(),
],
];
$this->client->request('POST', $subscription->getEndpoint(), $options);
} catch (TransportException $e) {
$output->writeln('<error>Message failed to sent for subscription '.$subscription->getEndpoint().': '.$e->getMessage().'</error>');
}
break;
'body' => $notification->getBody(),
'icon' => $notification->getIcon(),
];

$subscription = Subscription::create([
'endpoint' => $subscription->getEndpoint(),
'publicKey' => $publicKey,
'authToken' => $authenticationSecret,
'contentEncoding' => $contentEncoding,
]);

$webPush->queueNotification($subscription, json_encode($payload));
}
break;

case AppSubscriptionModel::TYPE_SLACK:
try {
$options = [
'json' => [
'text' => $notification->getTitle()."\r\n".$notification->getBody(),
],
];
$this->client->request('POST', $subscription->getEndpoint(), $options);
} catch (TransportException $e) {
$output->writeln('<error>Message failed to sent for subscription '.$subscription->getEndpoint().': '.$e->getMessage().'</error>');
}
break;

case AppSubscriptionModel::TYPE_TEAMS:
try {
$options = [
'json' => [
'@context' => 'https://schema.org/extensions',
'@type' => 'MessageCard',
'title' => $notification->getTitle(),
'text' => $notification->getBody(),
],
];
$this->client->request('POST', $subscription->getEndpoint(), $options);
} catch (TransportException $e) {
$output->writeln('<error>Message failed to sent for subscription '.$subscription->getEndpoint().': '.$e->getMessage().'</error>');
}
break;
}
}
}
}
Expand Down
128 changes: 92 additions & 36 deletions src/Controller/AppSubscriptionsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
namespace App\Controller;

use App\Controller\AbstractAppController;
use App\Exception\CallException;
use App\Form\Type\AppSubscriptionType;
use App\Model\CallRequestModel;
use App\Manager\AppSubscriptionManager;
use App\Manager\AppNotificationManager;
use App\Model\AppNotificationModel;
use App\Model\AppSubscriptionModel;
use DeviceDetector\DeviceDetector;
use Minishlink\WebPush\WebPush;
Expand All @@ -16,31 +19,29 @@
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

/**
* @Route("/admin")
*/
class AppSubscriptionsController extends AbstractAppController
{
public function __construct(AppSubscriptionManager $appSubscriptionManager, AppNotificationManager $appNotificationManager, Security $security)
public function __construct(AppSubscriptionManager $appSubscriptionManager, AppNotificationManager $appNotificationManager, Security $security, string $vapidPublicKey, string $vapidPrivateKey)
{
$this->appSubscriptionManager = $appSubscriptionManager;
$this->appNotificationManager = $appNotificationManager;
$this->user = $security->getUser();
$this->vapidPublicKey = $vapidPublicKey;
$this->vapidPrivateKey = $vapidPrivateKey;
}

/**
* @Route("/subscriptions", name="app_subscriptions")
*/
public function index(Request $request, string $vapidPublicKey, string $vapidPrivateKey): Response
public function index(Request $request): Response
{
$this->denyAccessUnlessGranted('APP_SUBSCRIPTIONS', 'global');

if ('' == $vapidPublicKey || '' == $vapidPrivateKey) {
$this->addFlash('warning', 'Run bin/console app:generate-vapid');
$this->addFlash('warning', 'Edit VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY in .env file');
}

if (false === $this->appNotificationManager->infoFileExists()) {
$this->addFlash('warning', 'Add to cron */5 * * * * cd '.str_replace('/public', '', $request->getBasePath()).' && bin/console app:send-notifications');
}
Expand All @@ -52,48 +53,101 @@ public function index(Request $request, string $vapidPublicKey, string $vapidPri

return $this->renderAbstract($request, 'Modules/subscription/subscription_index.html.twig', [
'subscriptions' => $subscriptions,
'applicationServerKey' => $vapidPublicKey,
'applicationServerKey' => $this->vapidPublicKey,
]);
}

/**
* @Route("/subscriptions/create", name="app_subscriptions_create")
* @Route("/subscriptions/create/{type}", name="app_subscriptions_create")
*/
public function create(Request $request): JsonResponse
public function create(Request $request, string $type): Response
{
$this->denyAccessUnlessGranted('APP_SUBSCRIPTIONS', 'global');

$json = [];
if (false === in_array($type, AppSubscriptionModel::getTypes())) {
throw new AccessDeniedException();
}

if (AppSubscriptionModel::TYPE_PUSH == $type) {
if ('' == $this->vapidPublicKey || '' == $this->vapidPrivateKey) {
$this->addFlash('warning', 'Run bin/console app:generate-vapid');
$this->addFlash('warning', 'Edit VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY in .env file');

throw new AccessDeniedException();
}
}

$dd = new DeviceDetector($request->headers->get('User-Agent'));
$dd->skipBotDetection();
$dd->parse();

if ($content = $request->getContent()) {
$content = json_decode($content, true);
$client = $dd->getClient();
$os = $dd->getOs();

$dd = new DeviceDetector($request->headers->get('User-Agent'));
$dd->skipBotDetection();
$dd->parse();
$subscription = new AppSubscriptionModel();
$subscription->setUserId($this->user->getId());
$subscription->setType($type);
$subscription->setIp($request->getClientIp());
$subscription->setOs($os ? $os['name'].' '.$os['version'] : false);
$subscription->setClient($client ? $client['name'].' '.$client['version'] : false);
$subscription->setNotifications(AppNotificationModel::getTypes());

$client = $dd->getClient();
$os = $dd->getOs();
$form = $this->createForm(AppSubscriptionType::class, $subscription, ['type' => $type]);

$subscription = new AppSubscriptionModel();
$subscription->setUserId($this->user->getId());
$subscription->setType($content['type']);
$subscription->setEndpoint($content['endpoint']);
if (AppSubscriptionModel::TYPE_PUSH == $content['type']) {
$subscription->setPublicKey($content['public_key']);
$subscription->setAuthenticationSecret($content['authentication_secret']);
$subscription->setContentEncoding($content['content_encoding']);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
try {
$callResponse = $this->appSubscriptionManager->send($subscription);

$this->addFlash('info', json_encode($callResponse->getContent()));

return $this->redirectToRoute('app_subscriptions');
} catch (CallException $e) {
$this->addFlash('danger', $e->getMessage());
}
$subscription->setIp($request->getClientIp());
$subscription->setOs($os ? $os['name'].' '.$os['version'] : false);
$subscription->setClient($client ? $client['name'].' '.$client['version'] : false);
}

return $this->renderAbstract($request, 'Modules/subscription/subscription_create.html.twig', [
'form' => $form->createView(),
'type' => $type,
'applicationServerKey' => $this->vapidPublicKey,
]);
}

/**
* @Route("/subscriptions/{id}/update", name="app_subscriptions_update")
*/
public function update(Request $request, string $id): Response
{
$this->denyAccessUnlessGranted('APP_SUBSCRIPTIONS', 'global');

$callResponse = $this->appSubscriptionManager->send($subscription);
$subscription = $this->appSubscriptionManager->getById($id);

return new JsonResponse(json_encode($callResponse->getContent()), JsonResponse::HTTP_OK);
if (null === $subscription) {
throw new NotFoundHttpException();
}

return new JsonResponse($json, JsonResponse::HTTP_OK);
$form = $this->createForm(AppSubscriptionType::class, $subscription, ['type' => $subscription->getType(), 'context' => 'update']);

$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
try {
$callResponse = $this->appSubscriptionManager->send($subscription);

$this->addFlash('info', json_encode($callResponse->getContent()));

return $this->redirectToRoute('app_subscriptions');
} catch (CallException $e) {
$this->addFlash('danger', $e->getMessage());
}
}

return $this->renderAbstract($request, 'Modules/subscription/subscription_update.html.twig', [
'subscription' => $subscription,
'form' => $form->createView(),
]);
}

/**
Expand All @@ -119,7 +173,7 @@ public function delete(Request $request, string $id): Response
/**
* @Route("/subscriptions/{id}/test", name="app_subscriptions_test")
*/
public function test(Request $request, string $id, string $vapidPublicKey, string $vapidPrivateKey): JsonResponse
public function test(Request $request, string $id): JsonResponse
{
$this->denyAccessUnlessGranted('APP_SUBSCRIPTIONS', 'global');

Expand All @@ -129,15 +183,17 @@ public function test(Request $request, string $id, string $vapidPublicKey, strin
throw new NotFoundHttpException();
}

$this->clusterHealth = $this->elasticsearchClusterManager->getClusterHealth();

$json = [];

switch ($subscription->getType()) {
case AppSubscriptionModel::TYPE_PUSH:
$apiKeys = [
'VAPID' => [
'subject' => 'https://github.com/stephanediondev/elasticsearch-admin',
'publicKey' => $vapidPublicKey,
'privateKey' => $vapidPrivateKey,
'publicKey' => $this->vapidPublicKey,
'privateKey' => $this->vapidPrivateKey,
],
];

Expand All @@ -150,7 +206,7 @@ public function test(Request $request, string $id, string $vapidPublicKey, strin
if ($publicKey && $authenticationSecret && $contentEncoding) {
$payload = [
'tag' => uniqid('', true),
'title' => 'test',
'title' => $this->clusterHealth['cluster_name'].': test',
'body' => 'test',
'icon' => 'favicon-green-144.png',
];
Expand Down
4 changes: 4 additions & 0 deletions src/Controller/ElasticsearchRepositoryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ public function create(Request $request, string $type): Response
{
$this->denyAccessUnlessGranted('REPOSITORIES_CREATE', 'global');

if (false === in_array($type, ElasticsearchRepositoryModel::getTypes())) {
throw new AccessDeniedException();
}

if ('s3' == $type && false === $this->callManager->hasPlugin('repository-s3')) {
throw new AccessDeniedException();
}
Expand Down
Loading