Merge branch 'fix-disconnect' into 'develop'
refactor logout handling to retrieve EasyCheck URL dynamically from database See merge request easy-solutions/apps/easyportal!38
This commit is contained in:
commit
a0db7f64a8
2
.env
2
.env
|
|
@ -75,5 +75,3 @@ AWS_S3_PORTAL_URL=https://s3.amazonaws.com/portal
|
|||
###< aws/aws-sdk-php-symfony ###
|
||||
APP_URL='https://example.com'
|
||||
APP_DOMAIN='example.com'
|
||||
|
||||
EASYCHECK_URL='https://check.solutions-easy.com'
|
||||
|
|
@ -13,7 +13,6 @@ parameters:
|
|||
logos_directory: '%kernel.project_dir%/public/uploads/logos'
|
||||
oauth_sso_identifier: '%env(OAUTH_SSO_IDENTIFIER)%'
|
||||
oauth_sso_identifier_login: '%env(OAUTH_SSO_IDENTIFIER_LOGIN)%'
|
||||
easycheck_url: '%env(EASYCHECK_URL)%'
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
|
|
@ -62,7 +61,5 @@ services:
|
|||
# please note that last definitions always *replace* previous ones
|
||||
|
||||
App\EventListener\LogoutSubscriber:
|
||||
arguments:
|
||||
$easycheckUrl: '%env(EASYCHECK_URL)%'
|
||||
tags:
|
||||
- { name: kernel.event_subscriber }
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ Lorsqu'un utilisateur se déconnecte d'une application, il est **automatiquement
|
|||
└─> Redirection vers EasyCheck
|
||||
|
||||
6. EasyPortal → Redirection
|
||||
└─> GET https://check.../logout
|
||||
└─> GET {{ easycheck_url }}/logout
|
||||
|
||||
7. EasyCheck → Invalide la session
|
||||
└─> Session détruite, cookies supprimés
|
||||
|
|
@ -133,9 +133,6 @@ OAUTH_CLIENT_SECRET='secret-key'
|
|||
### EasyPortal (.env)
|
||||
|
||||
```bash
|
||||
# URL de l'application cliente (EasyCheck)
|
||||
EASYCHECK_URL='https://check.solutions-easy.moi'
|
||||
|
||||
# Configuration OAuth2 Server
|
||||
OAUTH_PRIVATE_KEY=%kernel.project_dir%/config/jwt/private.key
|
||||
OAUTH_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.key
|
||||
|
|
@ -143,6 +140,8 @@ OAUTH_PASSPHRASE='passphrase'
|
|||
OAUTH_ENCRYPTION_KEY='encryption-key'
|
||||
```
|
||||
|
||||
**Note** : L'URL d'EasyCheck n'est plus stockée dans une variable d'environnement mais récupérée dynamiquement depuis la table `oauth2_client` en base de données.
|
||||
|
||||
## Points importants
|
||||
|
||||
### Sécurité
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'oauth2_client')]
|
||||
class OAuth2Client
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'string', length: 32)]
|
||||
private ?string $identifier = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 128)]
|
||||
private ?string $name = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 128, nullable: true)]
|
||||
private ?string $secret = null;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $redirectUris = null;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $grants = null;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $scopes = null;
|
||||
|
||||
#[ORM\Column(type: 'boolean')]
|
||||
private bool $active = false;
|
||||
|
||||
#[ORM\Column(type: 'boolean', options: ['default' => false])]
|
||||
private bool $allowPlainTextPkce = false;
|
||||
|
||||
public function getIdentifier(): ?string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
public function setIdentifier(string $identifier): self
|
||||
{
|
||||
$this->identifier = $identifier;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSecret(): ?string
|
||||
{
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
public function setSecret(?string $secret): self
|
||||
{
|
||||
$this->secret = $secret;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRedirectUris(): ?string
|
||||
{
|
||||
return $this->redirectUris;
|
||||
}
|
||||
|
||||
public function setRedirectUris(?string $redirectUris): self
|
||||
{
|
||||
$this->redirectUris = $redirectUris;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRedirectUrisArray(): array
|
||||
{
|
||||
if (!$this->redirectUris) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($this->redirectUris, true);
|
||||
if (is_array($decoded)) {
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
return [$this->redirectUris];
|
||||
}
|
||||
|
||||
public function getGrants(): ?string
|
||||
{
|
||||
return $this->grants;
|
||||
}
|
||||
|
||||
public function setGrants(?string $grants): self
|
||||
{
|
||||
$this->grants = $grants;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getScopes(): ?string
|
||||
{
|
||||
return $this->scopes;
|
||||
}
|
||||
|
||||
public function setScopes(?string $scopes): self
|
||||
{
|
||||
$this->scopes = $scopes;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
public function setActive(bool $active): self
|
||||
{
|
||||
$this->active = $active;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isAllowPlainTextPkce(): bool
|
||||
{
|
||||
return $this->allowPlainTextPkce;
|
||||
}
|
||||
|
||||
public function setAllowPlainTextPkce(bool $allowPlainTextPkce): self
|
||||
{
|
||||
$this->allowPlainTextPkce = $allowPlainTextPkce;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBaseUrl(): ?string
|
||||
{
|
||||
$uris = $this->getRedirectUrisArray();
|
||||
if (empty($uris)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$firstUri = $uris[0];
|
||||
$parsed = parse_url($firstUri);
|
||||
|
||||
if (!isset($parsed['scheme'], $parsed['host']) || !$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$baseUrl = $parsed['scheme'] . '://' . $parsed['host'];
|
||||
|
||||
if (isset($parsed['port']) && !in_array($parsed['port'], [80, 443], true)) {
|
||||
$baseUrl .= ':' . $parsed['port'];
|
||||
}
|
||||
|
||||
return $baseUrl;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\EventListener;
|
||||
|
||||
use App\Repository\OAuth2ClientRepository;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
|
@ -10,7 +11,7 @@ use Symfony\Component\Security\Http\Event\LogoutEvent;
|
|||
class LogoutSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $easycheckUrl,
|
||||
private readonly OAuth2ClientRepository $oauth2ClientRepository,
|
||||
private readonly LoggerInterface $logger
|
||||
) {
|
||||
}
|
||||
|
|
@ -24,7 +25,21 @@ class LogoutSubscriber implements EventSubscriberInterface
|
|||
|
||||
public function onLogout(LogoutEvent $event): void
|
||||
{
|
||||
$easycheckLogoutUrl = $this->easycheckUrl . '/logout';
|
||||
$easycheckClient = $this->oauth2ClientRepository->findByName('check');
|
||||
|
||||
if (!$easycheckClient) {
|
||||
$this->logger->error('OAuth2 client "check" not found in database');
|
||||
return;
|
||||
}
|
||||
|
||||
$easycheckUrl = $easycheckClient->getBaseUrl();
|
||||
|
||||
if (!$easycheckUrl) {
|
||||
$this->logger->error('Unable to determine base URL for EasyCheck client');
|
||||
return;
|
||||
}
|
||||
|
||||
$easycheckLogoutUrl = $easycheckUrl . '/logout';
|
||||
|
||||
$this->logger->info('LogoutSubscriber triggered - redirecting to EasyCheck logout', [
|
||||
'easycheck_logout_url' => $easycheckLogoutUrl,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\OAuth2Client;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
class OAuth2ClientRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, OAuth2Client::class);
|
||||
}
|
||||
|
||||
public function findByName(string $name): ?OAuth2Client
|
||||
{
|
||||
return $this->findOneBy(['name' => $name]);
|
||||
}
|
||||
|
||||
public function findByIdentifier(string $identifier): ?OAuth2Client
|
||||
{
|
||||
return $this->findOneBy(['identifier' => $identifier]);
|
||||
}
|
||||
|
||||
public function findActiveClients(): array
|
||||
{
|
||||
return $this->createQueryBuilder('c')
|
||||
->where('c.active = :active')
|
||||
->setParameter('active', true)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue