<?php
namespace App\Controller;
use App\Entity\ApiClient;
use App\Form\ApiClientType;
use App\Repository\ApiClientRepository;
use App\Repository\ApiRequestLogRepository;
use App\Repository\QuestionApplicationRepository;
use App\Repository\SettingsRepository;
use App\Service\Api\EndpointCatalog;
use App\Service\Api\IpWhitelistChecker;
use App\Service\Api\TokenGenerator;
use Doctrine\ORM\EntityManagerInterface;
use Knp\Snappy\Pdf;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\String\Slugger\SluggerInterface;
/**
* @Route("/api-client")
*/
class ApiClientController extends AbstractController
{
/**
* @Route("/", name="api_client_index", methods={"GET"})
*/
public function index(ApiClientRepository $clients, ApiRequestLogRepository $logs): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$all = $clients->findBy([], ['name' => 'ASC']);
$since = (new \DateTime())->modify('-24 hours');
$stats = [];
foreach ($all as $c) {
$stats[$c->getId()] = $logs->countByClientSince($c, $since);
}
return $this->render('api_client/index.html.twig', [
'clients' => $all,
'stats_24h' => $stats,
]);
}
/**
* @Route("/new", name="api_client_new", methods={"GET", "POST"})
*/
public function new(Request $request, EntityManagerInterface $em, TokenGenerator $tokens, IpWhitelistChecker $ipCheck): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$client = new ApiClient();
$form = $this->createForm(ApiClientType::class, $client);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
[$rules, $errors] = $ipCheck->parseRules($form->get('ipWhitelistText')->getData());
if (count($errors) > 0) {
foreach ($errors as $e) {
$this->addFlash('error', $e);
}
return $this->render('api_client/new.html.twig', ['form' => $form->createView()]);
}
$client->setIpWhitelist(count($rules) > 0 ? $rules : null);
$client->setCreatedBy($this->getUser());
$raw = $tokens->generateRaw(TokenGenerator::PREFIX_LIVE);
$client->setTokenHash($tokens->hash($raw));
$client->setTokenLookup($tokens->lookup($raw));
$client->setTokenPrefix($tokens->prefixOf($raw));
$em->persist($client);
$em->flush();
$this->addFlash('success', 'API client "' . $client->getName() . '" is aangemaakt.');
$request->getSession()->set('api_client_raw_token_' . $client->getId(), $raw);
return $this->redirectToRoute('api_client_show', ['id' => $client->getId(), 'justCreated' => 1]);
}
return $this->render('api_client/new.html.twig', ['form' => $form->createView()]);
}
/**
* @Route("/{id}", name="api_client_show", methods={"GET"}, requirements={"id"="\d+"})
*/
public function show(ApiClient $client, Request $request, ApiRequestLogRepository $logs): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$justCreated = $request->query->getBoolean('justCreated');
$rawToken = null;
if ($justCreated) {
$rawToken = $request->getSession()->remove('api_client_raw_token_' . $client->getId());
}
$since24 = (new \DateTime())->modify('-24 hours');
$stats = [
'total_24h' => $logs->countByClientSince($client, $since24),
'errors_4xx_24h' => $logs->countErrorsByClientSince($client, $since24, 400, 499),
'errors_5xx_24h' => $logs->countErrorsByClientSince($client, $since24, 500, 599),
];
$daily = $logs->dailyCountForClient($client, 30);
$recent = $logs->findRecentByClient($client, 50);
return $this->render('api_client/show.html.twig', [
'client' => $client,
'rawToken' => $rawToken,
'stats' => $stats,
'daily' => $daily,
'recentLogs' => $recent,
]);
}
/**
* @Route("/{id}/edit", name="api_client_edit", methods={"GET", "POST"}, requirements={"id"="\d+"})
*/
public function edit(Request $request, ApiClient $client, EntityManagerInterface $em, IpWhitelistChecker $ipCheck): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$form = $this->createForm(ApiClientType::class, $client);
$rules = $client->getIpWhitelist();
$form->get('ipWhitelistText')->setData($rules ? implode("\n", $rules) : '');
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
[$parsedRules, $errors] = $ipCheck->parseRules($form->get('ipWhitelistText')->getData());
if (count($errors) > 0) {
foreach ($errors as $e) {
$this->addFlash('error', $e);
}
return $this->render('api_client/edit.html.twig', [
'form' => $form->createView(),
'client' => $client,
]);
}
$client->setIpWhitelist(count($parsedRules) > 0 ? $parsedRules : null);
$em->flush();
$this->addFlash('success', 'Instellingen bijgewerkt.');
return $this->redirectToRoute('api_client_show', ['id' => $client->getId()]);
}
return $this->render('api_client/edit.html.twig', [
'form' => $form->createView(),
'client' => $client,
]);
}
/**
* @Route("/{id}/suspend", name="api_client_suspend", methods={"POST"}, requirements={"id"="\d+"})
*/
public function suspend(Request $request, ApiClient $client, EntityManagerInterface $em): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$reason = trim((string) $request->request->get('reason', ''));
$client->setSuspended(true);
$client->setSuspendReason($reason ?: null);
$em->flush();
$this->addFlash('success', 'Client opgeschort.');
return $this->redirectToRoute('api_client_show', ['id' => $client->getId()]);
}
/**
* @Route("/{id}/resume", name="api_client_resume", methods={"POST"}, requirements={"id"="\d+"})
*/
public function resume(ApiClient $client, EntityManagerInterface $em): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$client->setSuspended(false);
$client->setSuspendReason(null);
$em->flush();
$this->addFlash('success', 'Client opnieuw geactiveerd.');
return $this->redirectToRoute('api_client_show', ['id' => $client->getId()]);
}
/**
* @Route("/{id}/revoke", name="api_client_revoke", methods={"POST"}, requirements={"id"="\d+"})
*/
public function revoke(Request $request, ApiClient $client, EntityManagerInterface $em): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$confirm = (string) $request->request->get('confirm_name', '');
if ($confirm !== $client->getName()) {
$this->addFlash('error', 'Bevestig door de naam exact in te typen.');
return $this->redirectToRoute('api_client_show', ['id' => $client->getId()]);
}
$client->setStatus(0);
$em->flush();
$this->addFlash('success', 'Client ingetrokken (key werkt niet meer).');
return $this->redirectToRoute('api_client_index');
}
/**
* @Route("/docs.pdf", name="api_client_docs_pdf", methods={"GET"})
*/
public function docsPdf(
QuestionApplicationRepository $apps,
EndpointCatalog $catalog,
SettingsRepository $settings,
Pdf $snappy,
SluggerInterface $slugger
): Response {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$domainSetting = $settings->findOneBy(['name' => 'domain']);
$domain = $domainSetting ? rtrim($domainSetting->getValue(), '/') : 'https://dev.jotop.nl';
$applications = $apps->findAllApiEnabled();
$html = $this->renderView('api_client/docs_pdf.html.twig', [
'domain' => $domain,
'applications' => $applications,
'endpoints' => $catalog->list(),
'generatedAt' => new \DateTime(),
]);
$slug = $slugger->slug('jotop-api-v1-docs-' . date('Ymd'));
$filename = $slug . '-' . uniqid();
$path = $this->getParameter('kernel.project_dir') . '/public/downloads/' . $filename . '.pdf';
$snappy->setOption('encoding', 'UTF-8');
$snappy->setOption('margin-top', 18);
$snappy->setOption('margin-bottom', 18);
$snappy->setOption('margin-left', 15);
$snappy->setOption('margin-right', 15);
$snappy->generateFromHtml($html, $path, [], true);
return $this->file($path, 'Jotop-API-v1-documentatie.pdf');
}
}