Skip to content
Snippets Groups Projects
Commit 66026c88 authored by Pierre Fleutot's avatar Pierre Fleutot
Browse files

Création app Symfony

parents
No related branches found
No related tags found
No related merge requests found
Showing
with 674 additions and 0 deletions
<?xml version="1.0" encoding="UTF-8"?>
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="tests/bootstrap.php"
convertDeprecationsToExceptions="false"
>
<php>
<ini name="display_errors" value="1" />
<ini name="error_reporting" value="-1" />
<server name="APP_ENV" value="test" force="true" />
<server name="SHELL_VERBOSITY" value="-1" />
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
<server name="SYMFONY_PHPUNIT_VERSION" value="9.5" />
</php>
<testsuites>
<testsuite name="Project Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
</listeners>
<!-- Run `composer require symfony/panther` before enabling this extension -->
<!--
<extensions>
<extension class="Symfony\Component\Panther\ServerExtension" />
</extensions>
-->
</phpunit>
This diff is collapsed.
<?php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
<?php
namespace App\Command;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use League\Bundle\OAuth2ServerBundle\Model\Client;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Uid\UuidV1;
class BootstrapCommand extends Command
{
protected static $defaultName = "app:bootstrap";
protected static $defaultDescription = "Bootstrap the application database";
/**
* @var EntityManagerInterface
*/
private $em;
/**
* @var UserPasswordHasherInterface
*/
private $passwordHasher;
public function __construct(EntityManagerInterface $em, UserPasswordHasherInterface $passwordHasher)
{
parent::__construct();
$this->em = $em;
$this->passwordHasher = $passwordHasher;
}
protected function configure(): void
{
$this
->addOption('email', null, InputOption::VALUE_REQUIRED, 'User email adddress', 'me@davegebler.com')
->addOption('password', null, InputOption::VALUE_REQUIRED, 'User password', 'password')
->addOption('redirect-uris', null, InputOption::VALUE_REQUIRED, 'Redirect URIs', 'http://localhost:8080/callback')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$email = $input->getOption('email');
$password = $input->getOption('password');
$clientName = 'Test Client';
$clientId = 'testclient';
$clientSecret = 'testpass';
$clientDescription = 'Test Client App';
$scopes = ['profile', 'email', 'blog_read'];
$grantTypes = ['authorization_code', 'refresh_token'];
$redirectUris = explode(',', $input->getOption('redirect-uris'));
// Create the user
$user = new User();
$user->setEmail($email);
$user->setPassword($this->passwordHasher->hashPassword($user, $password));
$user->setRoles(['ROLE_SUPER_ADMIN']);
$user->setUuid(new UuidV1());
$this->em->persist($user);
$this->em->flush();
// Create the client
$conn = $this->em->getConnection();
$conn->insert('oauth2_client', [
'identifier' => $clientId,
'secret' => $clientSecret,
'name' => $clientName,
'redirect_uris' => implode(' ',$redirectUris),
'grants' => implode(' ',$grantTypes),
'scopes' => implode(' ',$scopes),
'active' => 1,
'allow_plain_text_pkce' => 0,
]);
$conn->insert('oauth2_client_profile', [
'client_id' => $clientId,
'name' => $clientDescription,
]);
$io->success('Bootstrap complete.');
return Command::SUCCESS;
}
}
<?php
namespace App\Controller;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class IndexController extends AbstractController
{
// /**
// * @Route("/", name="app_index")
// */
// public function index(): Response
// {
// return $this->render('index/index.html.twig', [
// 'controller_name' => 'IndexController',
// ]);
// }
/**
* @Route("/authorize", name="authorize")
*/
public function authorize(): Response
{
return $this->render('index/index.html.twig', [
'controller_name' => 'IndexController',
]);
}
/**
* @Route("/api/test", name="app_api_test")
*/
public function apiTest(): Response
{
/** @var User $user */
$user = $this->getUser();
return $this->json([
'message' => 'You successfully authenticated!',
'email' => $user->getEmail(),
]);
}
/**
* @Route(".well-known/jwks.json", name="app_jwks", methods={"GET"})
*/
public function jwks(): Response
{
// Load the public key from the filesystem and use OpenSSL to parse it.
$kernelDirectory = $this->getParameter('kernel.project_dir');
$publicKey = openssl_pkey_get_public(file_get_contents($kernelDirectory . '/var/keys/public.key'));
$details = openssl_pkey_get_details($publicKey);
$jwks = [
'keys' => [
[
'kty' => 'RSA',
'alg' => 'RS256',
'use' => 'sig',
'kid' => '1',
'n' => strtr(rtrim(base64_encode($details['rsa']['n']), '='), '+/', '-_'),
'e' => strtr(rtrim(base64_encode($details['rsa']['e']), '='), '+/', '-_'),
],
],
];
return $this->json($jwks);
}
}
<?php
namespace App\Controller;
use App\Entity\OAuth2ClientProfile;
use App\Entity\OAuth2UserConsent;
use App\Entity\User;
use Doctrine\Persistence\ManagerRegistry;
use League\Bundle\OAuth2ServerBundle\Model\Client;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class LoginController extends AbstractController
{
/**
* @var UserPasswordHasherInterface
*/
private $passwordEncoder;
/**
* @var ManagerRegistry
*/
private $em;
public function __construct(UserPasswordHasherInterface $passwordEncoder, ManagerRegistry $doctrine)
{
$this->passwordEncoder = $passwordEncoder;
$this->em = $doctrine;
}
/**
* @Route("/login", name="app_login")
*/
public function index(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
return $this->redirectToRoute('app_index');
}
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('login/index.html.twig', [
'controller_name' => 'LoginController',
'error' => $error,
'last_username' => $lastUsername,
]);
}
/**
* @Route("/consent", name="app_consent", methods={"GET", "POST"})
*/
public function consent(Request $request): Response
{
$clientId = $request->query->get('client_id');
if (!$clientId || !ctype_alnum($clientId) || !$this->getUser()) {
return $this->redirectToRoute('app_index');
}
$appClient = $this->em->getRepository(Client::class)->findOneBy(['identifier' => $clientId]);
if (!$appClient) {
return $this->redirectToRoute('app_index');
}
$appProfile = $this->em->getRepository(OAuth2ClientProfile::class)->findOneBy(['client' => $appClient]);
$appName = $appProfile->getName();
// Get the client scopes
$requestedScopes = explode(' ', $request->query->get('scope'));
// Get the client scopes in the database
$clientScopes = $appClient->getScopes();
// Check all requested scopes are in the client scopes
if (count(array_diff($requestedScopes, $clientScopes)) > 0) {
return $this->redirectToRoute('app_index');
}
// Check if the user has already consented to the scopes
/** @var User $user */
$user = $this->getUser();
$userConsents = $user->getOAuth2UserConsents()->filter(
function (OAuth2UserConsent $consent) use ($appClient) {
return $consent->getClient() === $appClient;
}
)->first() ?: null;
$userScopes = $userConsents->getScopes() ?? [];
$hasExistingScopes = count($userScopes) > 0;
// If user has already consented to the scopes, give consent
if (count(array_diff($requestedScopes, $userScopes)) === 0) {
$request->getSession()->set('consent_granted', true);
return $this->redirectToRoute('oauth2_authorize', $request->query->all());
}
// Remove the scopes to which the user has already consented
$requestedScopes = array_diff($requestedScopes, $userScopes);
// Map the requested scopes to scope names
$scopeNames = [
'profile' => 'Your profile',
'email' => 'Your email address',
'blog_read' => 'Your blog posts (read)',
'blog_write' => 'Your blog posts (write)',
];
// Get all the scope names in the requested scopes.
$requestedScopeNames = array_map(function($scope) use ($scopeNames) { return $scopeNames[$scope]; }, $requestedScopes);
$existingScopes = array_map(function($scope) use ($scopeNames) { $scopeNames[$scope]; }, $userScopes);
if ($request->isMethod('POST')) {
if ($request->request->get('consent') === 'yes') {
$request->getSession()->set('consent_granted', true);
// Add the requested scopes to the user's scopes
$consents = $userConsents ?? new OAuth2UserConsent();;
$consents->setScopes(array_merge($requestedScopes, $userScopes));
$consents->setClient($appClient);
$consents->setCreated(new \DateTimeImmutable());
$consents->setExpires(new \DateTimeImmutable('+30 days'));
$consents->setIpAddress($request->getClientIp());
$user->addOAuth2UserConsent($consents);
$this->em->getManager()->persist($consents);
$this->em->getManager()->flush();
}
if ($request->request->get('consent') === 'no') {
$request->getSession()->set('consent_granted', false);
}
return $this->redirectToRoute('oauth2_authorize', $request->query->all());
}
return $this->render('login/consent.html.twig', [
'app_name' => $appName,
'scopes' => $requestedScopeNames,
'has_existing_scopes' => $hasExistingScopes,
'existing_scopes' => $existingScopes,
]);
}
}
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class SecurityController extends AbstractController
{
/**
* @Route("/logout", name="app_logout", methods={"GET"})
*/
public function logout(): void
{
// controller can be blank: it will never be called!
throw new \Exception('Don\'t forget to activate logout in security.yaml');
}
}
\ No newline at end of file
<?php
namespace App\Entity;
use DateTimeImmutable;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\Traits\AccessTokenTrait;
use League\OAuth2\Server\Entities\Traits\EntityTrait;
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;
final class AccessToken implements AccessTokenEntityInterface
{
use AccessTokenTrait;
use EntityTrait;
use TokenEntityTrait;
private function convertToJWT()
{
$this->initJwtConfiguration();
return $this->jwtConfiguration->builder()
->permittedFor($this->getClient()->getIdentifier())
->identifiedBy($this->getIdentifier())
->issuedAt(new DateTimeImmutable())
->canOnlyBeUsedAfter(new DateTimeImmutable())
->expiresAt($this->getExpiryDateTime())
->relatedTo((string) $this->getUserIdentifier())
->withClaim('scopes', $this->getScopes())
->withClaim('kid', '1')
->withClaim('custom', ['foo' => 'bar'])
->getToken($this->jwtConfiguration->signer(), $this->jwtConfiguration->signingKey());
}
}
\ No newline at end of file
<?php
namespace App\Entity;
use App\Repository\OAuth2ClientProfileRepository;
use Doctrine\ORM\Mapping as ORM;
use League\Bundle\OAuth2ServerBundle\Model\Client;
/**
* @ORM\Entity(repositoryClass=OAuth2ClientProfileRepository::class)
*/
class OAuth2ClientProfile
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\OneToOne(targetEntity=Client::class, cascade={"persist", "remove"})
* @ORM\JoinColumn(referencedColumnName="identifier", nullable=false)
*/
private $client;
/**
* @ORM\Column(type="string", length=150)
*/
private $name;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $description;
public function getId(): ?int
{
return $this->id;
}
public function getClient(): ?Client
{
return $this->client;
}
public function setClient(?Client $client): self
{
$this->client = $client;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
}
<?php
namespace App\Entity;
use App\Repository\OAuth2UserConsentRepository;
use Doctrine\ORM\Mapping as ORM;
use League\Bundle\OAuth2ServerBundle\Model\Client;
/**
* @ORM\Entity(repositoryClass=OAuth2UserConsentRepository::class)
*/
class OAuth2UserConsent
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=User::class, inversedBy="oAuth2UserConsents")
* @ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* @ORM\ManyToOne(targetEntity=Client::class)
* @ORM\JoinColumn(referencedColumnName="identifier", nullable=false)
*/
private $client;
/**
* @ORM\Column(type="datetime")
*/
private $created;
/**
* @ORM\Column(type="datetime")
*/
private $expires;
/**
* @ORM\Column(type="array", nullable=true)
*/
private $scopes = [];
public function getId(): ?int
{
return $this->id;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
public function getClient(): ?Client
{
return $this->client;
}
public function setClient(?Client $client): self
{
$this->client = $client;
return $this;
}
public function getCreated(): ?\DateTimeInterface
{
return $this->created;
}
public function setCreated(\DateTimeInterface $created): self
{
$this->created = $created;
return $this;
}
public function getExpires(): ?\DateTimeInterface
{
return $this->expires;
}
public function setExpires(\DateTimeInterface $expires): self
{
$this->expires = $expires;
return $this;
}
public function getScopes(): ?array
{
return $this->scopes;
}
public function setScopes(?array $scopes): self
{
$this->scopes = $scopes;
return $this;
}
}
This diff is collapsed.
<?php
namespace App\EventSubscriber;
use League\Bundle\OAuth2ServerBundle\Event\AuthorizationRequestResolveEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\FirewallMapInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class AuthorizationCodeSubscriber implements EventSubscriberInterface
{
use TargetPathTrait;
/**
* @var Security
*/
private $security;
/**
* @var UrlGeneratorInterface
*/
private $urlGenerator;
/**
* @var RequestStack
*/
private $requestStack;
private $firewallName;
public function __construct(Security $security, UrlGeneratorInterface $urlGenerator, RequestStack $requestStack, FirewallMapInterface $firewallMap)
{
$this->security = $security;
$this->urlGenerator = $urlGenerator;
$this->requestStack = $requestStack;
$this->firewallName = $requestStack->getCurrentRequest()
? $firewallMap->getFirewallConfig($requestStack->getCurrentRequest())->getName()
: ''
;
}
public function onLeagueOauth2ServerEventAuthorizationRequestResolve(AuthorizationRequestResolveEvent $event): void
{
$request = $this->requestStack->getCurrentRequest();
$user = $this->security->getUser();
$this->saveTargetPath($request->getSession(), $this->firewallName, $request->getUri());
$response = new RedirectResponse($this->urlGenerator->generate('app_login'), 307);
if ($user instanceof UserInterface) {
if ($request->getSession()->get('consent_granted') !== null) {
$event->resolveAuthorization($request->getSession()->get('consent_granted'));
$request->getSession()->remove('consent_granted');
return;
}
$response = new RedirectResponse($this->urlGenerator->generate('app_consent', $request->query->all()), 307);
}
$event->setResponse($response);
}
public static function getSubscribedEvents(): array
{
return [
'league.oauth2_server.event.authorization_request_resolve' => 'onLeagueOauth2ServerEventAuthorizationRequestResolve',
];
}
}
\ No newline at end of file
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment