<?php
namespace App\Controller\Plugin;
use App\Entity\FeatureAccess;
use App\Entity\User;
use App\Service\Ai\FeatureAccessService;
use App\Service\Ai\PluginAccessToken;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\Routing\Annotation\Route;
/**
* Endpoints utilisés par les plugins Euro-Office (cross-origin) pour
* - obtenir un access_token court-lived (auth Symfony)
* - récupérer la liste des features autorisées/refusées pour le user courant
*
* L'access_token /api/plugin/access-token est servi same-origin (cookie session
* de la page workroom) puis transmis en Bearer aux plugins iframe.
*/
class PluginTokenController extends AbstractController
{
public function __construct(
private readonly PluginAccessToken $accessToken,
private readonly FeatureAccessService $featureAccess,
private readonly RateLimiterFactory $pluginTokenLimiter,
) {}
#[Route('/api/plugin/access-token', name: 'plugin_access_token', methods: ['GET'], options: ['expose' => true])]
public function token(Request $request): JsonResponse
{
/** @var User|null $user */
$user = $this->getUser();
if (!$user) return new JsonResponse(['error' => 'unauthorized'], 401);
// Rate-limit (cf. plugin_token in rate_limiter.yaml). Sans cette limite,
// l'endpoint pouvait servir un nombre illimité de JWTs (DoS / spam).
$limit = $this->pluginTokenLimiter->create('user-'.$user->getId())->consume();
if (!$limit->isAccepted()) {
$resp = new JsonResponse(['error' => 'rate_limited', 'retryAfter' => $limit->getRetryAfter()->getTimestamp() - time()], 429);
$resp->headers->set('Retry-After', (string) ($limit->getRetryAfter()->getTimestamp() - time()));
return $resp;
}
return new JsonResponse([
'token' => $this->accessToken->generate($user),
'expiresIn' => $this->accessToken->getTtl(),
]);
}
/**
* Retourne la map des features autorisées/refusées pour l'utilisateur
* courant. Utilisé par le plugin "labomega-bootstrap" (autostart) pour
* masquer les boutons des plugins désactivés dans la barre OnlyOffice.
*
* Auth : session cookie (same-origin) OU Bearer / X-Plugin-Token
* (cross-origin depuis un plugin iframe).
*/
#[Route('/api/plugin/features', name: 'plugin_features', methods: ['GET'], options: ['expose' => true])]
public function features(Request $request): JsonResponse
{
$user = $this->resolveUser($request);
if (!$user) return new JsonResponse(['error' => 'unauthorized'], 401);
$r = new JsonResponse(null);
$r->setEncodingOptions(JSON_UNESCAPED_UNICODE);
$r->setData([
'features' => $this->featureAccess->getResolvedAccessMap($user),
'disabled' => $this->featureAccess->getDisabledFeatures($user),
'available' => FeatureAccess::FEATURES,
]);
return $r;
}
private function resolveUser(Request $request): ?User
{
$u = $this->getUser();
if ($u instanceof User) return $u;
$token = $request->query->get('access_token')
?? $request->headers->get('X-Plugin-Token');
if (!$token) {
$auth = $request->headers->get('Authorization', '');
if (str_starts_with($auth, 'Bearer ')) $token = substr($auth, 7);
}
return $token ? $this->accessToken->resolveUser($token) : null;
}
}