<?php
declare(strict_types=1);
namespace App\Controller;
use App\Entity\User;
use App\Security\Authenticate;
use Doctrine\ORM\EntityManagerInterface;
use OneLogin\Saml2\Auth;
use OneLogin\Saml2\Response as AuthResponse;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
class DefaultController extends AbstractController
{
public const URL_USERNAME_ATTRIBUTE = 'http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname';
public const PATTERN_RESPONSE_SAML = '%<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">(.+)@convictionsrh.com</saml%';
public function __construct(
private readonly EntityManagerInterface $em,
private readonly TranslatorInterface $translator,
private readonly UserPasswordHasherInterface $passwordHasher
) {
}
#[Route('/', name: 'index')]
public function index(): Response
{
return $this->redirectToRoute('editor_index');
}
#[Route('/acs', name: 'acs', methods: ['GET', 'POST'])]
public function acs(Authenticate $authenticate): Response
{
$oneloginSettings = $this->getParameter('nbgrp_onelogin_saml.onelogin_settings');
if (!\is_array($oneloginSettings)) {
throw new \UnexpectedValueException($this->translator->trans('onelogin.exception.arraySettings'));
}
$auth = new Auth($oneloginSettings['default']);
if (!isset($_POST['SAMLResponse'])) {
return new Response(
$this->translator->trans('onelogin.exception.authFailed'),
Response::HTTP_BAD_REQUEST
);
}
$response = new AuthResponse($auth->getSettings(), $_POST['SAMLResponse']);
$attr = $response->getAttributes();
$username = $attr[self::URL_USERNAME_ATTRIBUTE][0] ?? null;
$user = $this->em->getRepository(User::class)->findOneBy(['email' => $username]);
if (null === $user && preg_match(self::PATTERN_RESPONSE_SAML, $response->document->saveHTML(), $matches)) {
$username = $matches[1];
$user = $this->em->getRepository(User::class)->findOneBy(['email' => $username]);
}
$responseMessage = null;
if (null !== $user) {
if ($user->isDeleted()) {
$responseMessage = $this->translator->trans('onelogin.userDeleted');
}
} else {
$user = new User();
$user->setEmail($username);
$user->setDeleted(false);
// Create PWD.
$plainPassword = substr(
str_shuffle(
strtolower(
sha1(
rand() . time() . uniqid()
)
)
),
0,
16
);
$encodedPassword = $this->passwordHasher->hashPassword($user, $plainPassword);
$user->setPassword($encodedPassword);
$this->em->persist($user);
$this->em->flush();
}
if (null !== $responseMessage) {
return new Response($responseMessage, Response::HTTP_FORBIDDEN);
}
echo $this->translator->trans('onelogin.connected', ['%username%' => $username]);
$authenticate->login($user);
return $this->redirectToRoute('editor_index');
}
#[Route('/connect', name: 'connect', methods: ['GET', 'POST'])]
public function connect(): Response
{
$auth = new Auth();
$metadata = $auth->getSettings()->getSPMetadata();
$response = new Response($metadata);
$response->headers->set('Content-Type', 'xml');
return $response;
}
// This will never be executed: Symfony will intercept this first and handle the logout automatically.
#[Route('/logout', name: 'logout')]
public function logout(): void
{
throw new RuntimeException('This should never be reached!');
}
}