Ajout de dernière update dans le bin de la console module

TODO: créé les pages de blog (sommaire,news) permettant d'afficher les fichiers md
This commit is contained in:
Emmanuel ROY 2020-04-01 11:54:24 +02:00
parent 474ce8ded1
commit 7b52c58faf
823 changed files with 96345 additions and 0 deletions

View file

@ -0,0 +1,84 @@
{
"type": "project",
"license": "proprietary",
"require": {
"php": "^7.2.5",
"ext-ctype": "*",
"ext-iconv": "*",
"sensio/framework-extra-bundle": "^5.1",
"symfony/asset": "5.0.*",
"symfony/console": "5.0.*",
"symfony/dotenv": "5.0.*",
"symfony/expression-language": "5.0.*",
"symfony/flex": "^1.3.1",
"symfony/form": "5.0.*",
"symfony/framework-bundle": "5.0.*",
"symfony/http-client": "5.0.*",
"symfony/intl": "5.0.*",
"symfony/mailer": "5.0.*",
"symfony/monolog-bundle": "^3.1",
"symfony/notifier": "5.0.*",
"symfony/orm-pack": "*",
"symfony/process": "5.0.*",
"symfony/security-bundle": "5.0.*",
"symfony/serializer-pack": "*",
"symfony/string": "5.0.*",
"symfony/translation": "5.0.*",
"symfony/twig-pack": "*",
"symfony/validator": "5.0.*",
"symfony/web-link": "5.0.*",
"symfony/yaml": "5.0.*"
},
"require-dev": {
"symfony/debug-pack": "*",
"symfony/maker-bundle": "^1.0",
"symfony/profiler-pack": "^1.0",
"symfony/test-pack": "*"
},
"config": {
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"paragonie/random_compat": "2.*",
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php71": "*",
"symfony/polyfill-php70": "*",
"symfony/polyfill-php56": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "5.0.*"
}
}
}

6754
console/skel/symfony-app/composer.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,23 @@
<?php
use Symfony\Component\Dotenv\Dotenv;
require dirname(__DIR__).'/vendor/autoload.php';
// Load cached env vars if the .env.local.php file exists
// Run "composer dump-env prod" to create it (requires symfony/flex >=1.2)
if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && (!isset($env['APP_ENV']) || ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV'])) {
foreach ($env as $k => $v) {
$_ENV[$k] = $_ENV[$k] ?? (isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v);
}
} elseif (!class_exists(Dotenv::class)) {
throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
} else {
// load all the .env files
(new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env');
}
$_SERVER += $_ENV;
$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev';
$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV'];
$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0';

View file

@ -0,0 +1,45 @@
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
/*App\Session\AuthBundle\SessionAuthBundle::class => ['all' => true],*/
];

View file

@ -0,0 +1,19 @@
framework:
cache:
# Unique name of your app: used to compute stable namespaces for cache keys.
#prefix_seed: your_vendor_name/app_name
# The "app" cache stores to the filesystem by default.
# The data in this cache should persist between deploys.
# Other options include:
# Redis
#app: cache.adapter.redis
#default_redis_provider: redis://localhost
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu
# Namespaced pools use the above "app" backend by default
#pools:
#my.dedicated.cache: null

View file

@ -0,0 +1,4 @@
debug:
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
# See the "server:dump" command to start a new server.
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"

View file

@ -0,0 +1,16 @@
services:
EasyCorp\EasyLog\EasyLogHandler:
public: false
arguments: ['%kernel.logs_dir%/%kernel.environment%.log']
#// FIXME: How to add this configuration automatically without messing up with the monolog configuration?
#monolog:
# handlers:
# buffered:
# type: buffer
# handler: easylog
# channels: ['!event']
# level: debug
# easylog:
# type: service
# id: EasyCorp\EasyLog\EasyLogHandler

View file

@ -0,0 +1,19 @@
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event"]
# uncomment to get logging in your browser
# you may have to allow bigger header sizes in your Web server configuration
#firephp:
# type: firephp
# level: info
#chromephp:
# type: chromephp
# level: info
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"]

View file

@ -0,0 +1,6 @@
web_profiler:
toolbar: true
intercept_redirects: false
framework:
profiler: { only_exceptions: false }

View file

@ -0,0 +1,18 @@
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
# IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file)
#server_version: '5.7'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App

View file

@ -0,0 +1,5 @@
doctrine_migrations:
dir_name: '%kernel.project_dir%/src/Migrations'
# namespace is arbitrary but should be different from App\Migrations
# as migrations classes should NOT be autoloaded
namespace: DoctrineMigrations

View file

@ -0,0 +1,3 @@
framework:
mailer:
dsn: '%env(MAILER_DSN)%'

View file

@ -0,0 +1,16 @@
framework:
notifier:
#chatter_transports:
# slack: '%env(SLACK_DSN)%'
# telegram: '%env(TELEGRAM_DSN)%'
#texter_transports:
# twilio: '%env(TWILIO_DSN)%'
# nexmo: '%env(NEXMO_DSN)%'
channel_policy:
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
urgent: ['email']
high: ['email']
medium: ['email']
low: ['email']
admin_recipients:
- { email: admin@example.com }

View file

@ -0,0 +1,20 @@
doctrine:
orm:
auto_generate_proxy_classes: false
metadata_cache_driver:
type: pool
pool: doctrine.system_cache_pool
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system

View file

@ -0,0 +1,24 @@
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404, 405]
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
nested:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]
deprecation:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log"
deprecation_filter:
type: filter
handler: deprecation
max_level: info
channels: ["php"]

View file

@ -0,0 +1,3 @@
framework:
router:
strict_requirements: null

View file

@ -0,0 +1,3 @@
framework:
router:
utf8: true

View file

@ -0,0 +1,51 @@
security:
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
# the name of your user provider can be anything
session_user_provider:
id: App\Security\AuthUserProvider
# secured_area:
# id: session_auth.user_provider
firewalls:
dev:
stateless: true
access_denied_handler: App\Security\AccessDeniedHandler
guard:
authenticators:
- App\Security\SessionAuthenticator
main:
stateless: true
access_denied_handler: App\Security\AccessDeniedHandler
guard:
authenticators:
- App\Security\SessionAuthenticator
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# secured_area:
# guard:
# authenticators:
# - session_auth.authenticator
# logout:
# path: logout #nom de la route de déconnexion
# target: /
# success_handler: session_auth.authenticator
encoders:
# use your user class name here
App\Security\AuthUser:
# Use native password encoder
# This value auto-selects the best possible hashing algorithm
# (i.e. Sodium when available).
algorithm: auto
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: '^/admin-test/admin', roles: ROLE_ADMIN }
- { path: '^/admin-test/unauthorized', roles: ROLE_USER }
- { path: '^/admin-test/page1', roles: ROLE_USER_CONNECTED }
- { path: '^/admin-test', roles: ROLE_USER }

View file

@ -0,0 +1,42 @@
#session_auth:
# #Activation du user_provider interne
# #par défaut TRUE
# use_default_provider : false
# #Le Provider doit implémenter Symfony\Component\Security\Core\User\UserProviderInterface
# #par défaut est utilise Besancon\AuthBundle\Security\User\AuthUserProvider
# #cette valeur n'est nécessaire que lorsque use_default_provider est a false
# #provider: Besancon\AuthBundle\Security\User\AuthUserProvider
# provider: App\Security\AuthUserProvider
# #Namespace de l'entité utilisateur
# #L'entité doit implémenter Symfony\Component\Security\Core\User\UserInterface
# #par défaut est utilise Besancon\AuthBundle\Security\User\AuthUser
# #user_entity: App\Besancon\AuthBundle\Security\User\AuthUser
# user_entity: App\Classes\AuthUser
# #nom de la route correspondant à la page d'accueil de l'application
# #par défaut est à NULL
# homepage: "index"
# #tag du service personnalisé permettant de gérer l'authentification
# #par défaut est à bes_auth.authentification (service par défaut)
# authentication_service: App\Security\Authentification
# #authentication_service: bes_auth.authentification
# #Mode d'authentification Cas ou Rsa
# #obligatoire pas de valeur par défaut
# type_auth: '%type_auth%'
# #Configuration pour le mode Cas
# #obligatoire si mode Cas choisi
# cas:
# #Serveur Cas
# hostname: "seshat25.ac-besancon.fr"
# #Port Cas
# port: 443
# #Uri Cas
# uri: ""
# #Configuration pour le mode Rsa
# #obligatoire si mode Rsa choisi
# rsa :
# #Url de déconnexion Rsa
# logout_url: http://webphppreprod3.in.ac-besancon.fr:8383/login/ct_logout.jsp
#
#parameters:
# type_auth: Session

View file

@ -0,0 +1,12 @@
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404, 405]
channels: ["!event"]
nested:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug

View file

@ -0,0 +1,2 @@
twig:
strict_variables: true

View file

@ -0,0 +1,3 @@
framework:
validation:
not_compromised_password: false

View file

@ -0,0 +1,6 @@
web_profiler:
toolbar: false
intercept_redirects: false
framework:
profiler: { collect: false }

View file

@ -0,0 +1,6 @@
framework:
default_locale: en
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- en

View file

@ -0,0 +1,2 @@
twig:
default_path: '%kernel.project_dir%/templates'

View file

@ -0,0 +1,8 @@
framework:
validation:
email_validation_mode: html5
# Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping:
# App\Entity\: []

View file

@ -0,0 +1,7 @@
controllers:
resource: ../../src/Controller/
type: annotation
kernel:
resource: ../../src/Kernel.php
type: annotation

View file

@ -0,0 +1,3 @@
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
prefix: /_error

View file

@ -0,0 +1,7 @@
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /_profiler

View file

@ -0,0 +1,43 @@
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
# App\Security\Authentification:
# autowire: true
# parent: App\Security\Abstracts\AuthFinal
# public: false
# autoconfigure: false
# arguments:
# $var: []
#
# session_auth.authenticator_factory:
# class: App\Security\AuthentificatorFactory
# public: false
#
# session_auth.user_provider:
# class: App\Security\AuthUserProvider
# public: false

View file

@ -0,0 +1,19 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class AdminController extends AbstractController
{
/**
* @Route("/admin-test/admin", name="admin")
*/
public function adminAction()
{
// replace this example code with whatever you need
return $this->render('default/page.html.twig', [
'text' => 'admin',
]);
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends AbstractController
{
/**
* @Route("/admin-test", name="homepage")
*/
public function indexAction()
{
print_r("<pre>");
//print_r($this->get('session'));
print_r($_COOKIE);
print_r($_SESSION);
print_r("</pre>");
// replace this example code with whatever you need
return $this->render('default/page.html.twig', [
'text' => 'homepage',
]);
}
/**
* @Route("/admin-test/page1", name="page1")
*/
public function page1Action()
{
// replace this example code with whatever you need
return $this->render('default/page.html.twig', [
'text' => 'page1',
]);
}
/**
* @Route("/admin-test/page2", name="page2")
*/
public function page2Action()
{
// replace this example code with whatever you need
return $this->render('default/page.html.twig', [
'text' => 'page2',
]);
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class ErrorController extends AbstractController
{
/**
* @Route("/admin-test/unauthorized", name="unauthorized")
*/
public function indexAction()
{
print_r("<pre>");
//print_r($this->get('session'));
print_r($_COOKIE);
print_r($_SESSION);
print_r("</pre>");
// replace this example code with whatever you need
return $this->render('default/unauthorized.html.twig', [
]);
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace App\Events;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class OnAuthenticationFailureEvent extends Event {
const NAME = "session_auth.event.on_authentication_failure";
public function __construct(Request $request, AuthenticationException $exception) {
$this->request = $request;
$this->exception = $exception;
$this->response = new Response($exception->getMessage(), Response::HTTP_FORBIDDEN);
}
public function getRequest() {
return $this->request;
}
public function getException() {
return $this->exception;
}
public function getResponse() {
return $this->response;
}
public function setResponse($response) {
$this->response = $response;
return $this;
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Events;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Request;
class OnAuthenticationSuccessEvent extends Event {
const NAME = "session_auth.event.on_authentication_success";
public function __construct(Request $request, TokenInterface $token, $providerKey) {
$this->request = $request;
$this->token = $token;
$this->providerKey = $providerKey;
}
public function getRequest() {
return $this->request;
}
public function getToken() {
return $this->exception;
}
public function getProviderKey() {
return $this->providerKey;
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
private const CONFIG_EXTS = '.{php,xml,yaml,yml}';
public function registerBundles(): iterable
{
$contents = require $this->getProjectDir().'/config/bundles.php';
foreach ($contents as $class => $envs) {
if ($envs[$this->environment] ?? $envs['all'] ?? false) {
yield new $class();
}
}
}
public function getProjectDir(): string
{
return \dirname(__DIR__);
}
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
{
$container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php'));
$container->setParameter('container.dumper.inline_class_loader', \PHP_VERSION_ID < 70400 || $this->debug);
$container->setParameter('container.dumper.inline_factories', true);
$confDir = $this->getProjectDir().'/config';
$loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/{packages}/'.$this->environment.'/*'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob');
}
protected function configureRoutes(RouteCollectionBuilder $routes): void
{
$confDir = $this->getProjectDir().'/config';
$routes->import($confDir.'/{routes}/'.$this->environment.'/*'.self::CONFIG_EXTS, '/', 'glob');
$routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob');
$routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob');
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
use Twig\Environment;
class AccessDeniedHandler implements AccessDeniedHandlerInterface
{
public $twig;
public function __construct(Environment $twig)
{
$this->twig = $twig;
}
public function handle(Request $request, AccessDeniedException $accessDeniedException)
{
$content = $this->twig->render(
'default/unauthorized.html.twig', array()
);
$response = new Response($content, Response::HTTP_FORBIDDEN);
return $response;
}
}

View file

@ -0,0 +1,97 @@
<?php
namespace App\Security;
use Symfony\Component\Security\Core\User\UserInterface;
class AuthUser implements UserInterface{
private $id;
private $username;
private $status;
private $type;
private $salt;
private $credentials;
private $roles = [];
public function __construct($id, $username,$credentials, array $roles = [])
{
$this->username = $username;
$this->id = $id;
$this->credentials = $credentials;
$this->roles = $roles;
$this->salt = sha1(microtime(true));
}
public function getRoles()
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
if($this->getId() == 1587184) {
$roles[] = 'ROLE_ADMIN';
}
return array_unique($roles);
}
public function setRoles($roles)
{
return $this->roles = $roles;
}
public function getUsername()
{
return $this->username;
}
public function getUser(){
return $this;
}
public function getId()
{
return $this->id;
}
public function getCredentials()
{
return $this->credentials;
}
public function eraseCredentials()
{
// TODO: Implement eraseCredentials() method.
}
public function getPassword()
{
// TODO: Implement getPassword() method.
}
public function getSalt()
{
return $this->salt;
}
public function isEqualTo(UserInterface $user)
{
if (!$user instanceof AuthUser) {
return false;
}
if ($this->username !== $user->getUsername()) {
return false;
}
if ($this->id !== $user->getId()) {
return false;
}
if ($this->type !== $user->getType()) {
return false;
}
if ($this->status !== $user->getStatus()) {
return false;
}
return true;
}
}

View file

@ -0,0 +1,77 @@
<?php
namespace App\Security;
use App\Security\AuthUser;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class AuthUserProvider implements UserProviderInterface
{
/**
* Symfony calls this method if you use features like switch_user
* or remember_me.
*
* If you're not using these features, you do not need to implement
* this method.
*
* @return UserInterface
*
* @throws UsernameNotFoundException if the user is not found
*/
public function loadUserByUsername($username) {
$entity_user = $this->entity_user;
return $this->authService->getUser($username);
// Load a User object from your data source or throw UsernameNotFoundException.
// The $username argument may not actually be a username:
// it is whatever value is being returned by the getUsername()
// method in your User class.
// throw new \Exception('TODO: fill in loadUserByUsername() inside '.__FILE__);
}
/**
* Refreshes the user after being reloaded from the session.
*
* When a user is logged in, at the beginning of each request, the
* User object is loaded from the session and then this method is
* called. Your job is to make sure the user's data is still fresh by,
* for example, re-querying for fresh User data.
*
* If your firewall is "stateless: true" (for a pure API), this
* method is not called.
*
* @return UserInterface
*/
public function refreshUser(UserInterface $user) {
$user = $this->_ctrlInstanceUser($user);
return $this->loadUserByUsername($user->getUsername());
}
private function _ctrlInstanceUser(UserInterface $user) {
$entity_user = $this->entity_user;
if (!$user instanceof $entity_user) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $user;
}
/**
* Tells Symfony to use this provider for this User class.
*/
public function supportsClass($class)
{
return AuthUser::class === $class;
}
}

View file

@ -0,0 +1,152 @@
<?php
namespace App\Security;
use MVC\Classe\Dumper;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Twig\Environment;
class SessionAuthenticator extends AbstractGuardAuthenticator
{
public $router;
public $twig;
public function __construct(UrlGeneratorInterface $router,Environment $twig)
{
$this->router = $router;
$this->twig = $twig;
}
/**
* Called on every request to decide if this authenticator should be
* used for the request. Returning `false` will cause this authenticator
* to be skipped.
*/
public function supports(Request $request)
{
Dumper::dump("supports function");
Dumper::dump($_SESSION);
if (isset($_SESSION['id_utilisateur'])) {
return true;
}else{
return true;
}
}
/**
* Called on every request. Return whatever credentials you want to
* be passed to getUser() as $credentials.
*/
public function getCredentials(Request $request)
{
Dumper::dump("getCredentials");
return "X-AUTH-TOKEN-SESSION-API";
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
Dumper::dump("getUser");
if (!isset($_SESSION['id'])) {
$user = new \App\Security\AuthUser('0','not-connected',$credentials,['ROLE_USER']);
}else {
$user = new \App\Security\AuthUser($_SESSION['id'], $_SESSION['username'], $credentials, ['ROLE_USER', 'ROLE_USER_CONNECTED']);
}
Dumper::dump($user);
// if a User is returned, checkCredentials() is called
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
// Check credentials - e.g. make sure the password is valid.
// In case of an API token, no credential check is needed.
// Return `true` to cause authentication success
Dumper::dump("checkCredentials");
if($user->getCredentials() === $credentials) {
return true;
}else{
return false;
}
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
//return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$data = [
// you may want to customize or obfuscate the message first
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
// or to translate this message
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
];
// return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
// $url = $this->router->generate('unauthorized');
// return new RedirectResponse($url);
$content = $this->twig->render(
'default/unauthorized.html.twig', array()
);
$response = new Response($content, Response::HTTP_FORBIDDEN);
return $response;
}
/**
* Called when authentication is needed, but it's not sent
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$data = [
// you might translate this message
'message' => 'Authentication Required'
];
//return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
// $url = $this->router->generate('unauthorized');
// return new RedirectResponse($url);
$content = $this->twig->render(
'default/unauthorized.html.twig', array()
);
$response = new Response($content, Response::HTTP_FORBIDDEN);
return $response;
}
public function supportsRememberMe()
{
return false;
}
public function onLogoutSuccess(Request $request) {
//$homepage = $this->config["homepage"];
//return \phpCAS::logoutWithRedirectService($this->urlGenerator->generate($homepage, array(), UrlGeneratorInterface::ABSOLUTE_URL));
header('Location: /index.php');
return ;
}
}

View file

@ -0,0 +1,110 @@
<?php
namespace App\Session\AuthBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
/**
* This is the class that validates and merges configuration from your app/config files.
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/configuration.html}
*/
class Configuration implements ConfigurationInterface {
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder() {
$treeBuilder = new TreeBuilder('session_auth');
$rootNode = $treeBuilder->getRootNode();
$rootNode
->children()
->scalarNode('homepage')->defaultNull()->end()
->scalarNode('authentication_service')->defaultNull()->end()
->scalarNode('provider')->defaultNull()->end()
->booleanNode('use_default_provider')->defaultTrue()->end()
->scalarNode('user_entity')->defaultNull()->end()
->scalarNode('type_auth')->isRequired()->cannotBeEmpty()
->validate()
->ifNotInArray(array('Rsa', 'Cas','Session'))
->thenInvalid("La méthode d'authentification %s n'est pas gérée, seuls Rsa et Cas sont acceptés")
->end()
->end()
->scalarNode('environment')->end()
->end()
;
$rootNode
->validate()
->ifTrue(function ($v) {
if(!is_null($v['user_entity'])){
$class = $v['user_entity'];
if(!class_exists($class)){
return true;
}
return !array_key_exists("Symfony\Component\Security\Core\User\UserInterface", class_implements($class));
}
return false;
})
->thenInvalid("La classe renseignée pour 'entity' doit implémenter Symfony\Component\Security\Core\User\UserInterface")
->end();
$this->_addCasConfig($rootNode);
$this->_addRsaConfig($rootNode);
return $treeBuilder;
}
private function _addCasConfig(ArrayNodeDefinition $node) {
$node
->children()
->arrayNode('cas')->info('A déclarer si authentification pas CAS.')
->addDefaultsIfNotSet()
->treatNullLike(['hostname' => null])
->treatNullLike(['port' => null])
->treatNullLike(['uri' => null])
->children()
->scalarNode('hostname')->defaultNull()->end()
->scalarNode('port')->defaultNull()->end()
->scalarNode('uri')->defaultNull()->end()
->end()
->end()
->end()
;
$node
->validate()
->ifTrue(function ($v) {
$cas_config = $v['cas'];
return ($v['type_auth']=="Cas" && (is_null($cas_config['hostname']) || is_null($cas_config['port']) || is_null($cas_config['uri'])) );
})
->thenInvalid("En utilisant le type d'authentification Cas vous devez renseigner la section 'cas' et ses clés 'hostname', 'port', 'uri'")
->end();
}
private function _addRsaConfig(ArrayNodeDefinition $node) {
$node
->children()
->arrayNode('rsa')->addDefaultsIfNotSet()->info('A déclarer si authentification pas RSA.')
->addDefaultsIfNotSet()
->treatNullLike(['logout_url' => null])
->children()
->scalarNode('logout_url')->defaultNull()->end()
->end()
->end()
->end()
;
$node
->validate()
->ifTrue(function ($v) {
$rsa_config = $v['rsa'];
return ($v['type_auth']==="Rsa" && is_null($rsa_config['logout_url']));
})
->thenInvalid("En utilisant le type d'authentification Rsa vous devez renseigner la section 'rsa' et sa clé 'logout_url'")
->end();
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace App\Session\AuthBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ChildDefinition;
/**
* This is the class that loads and manages your bundle configuration.
*
* @link http://symfony.com/doc/current/cookbook/bundles/extension.html
*/
class SessionAuthExtension extends Extension {
/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container) {
$configs[0]['environment'] = $container->getParameter("kernel.environment");
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
//Chargement des parametres
$loader->load('parameters.yml');
//Chargement des services
$loader->load('services.yml');
//definition du service d'authentification par défaut dans le cas où ce ne serait pas un service
// fraichement créé par l'utilisateur dans le fichiers services.yaml
if(is_null($config["authentication_service"])){
$authentication_service = "session_auth.authentification";
}else{
$authentication_service = $config["authentication_service"];
}
if ($authentication_service == "session_auth.authentification") {
$container->register($authentication_service, \App\Besancon\AuthBundle\Security\DefaultAuthentication::class)
->addMethodCall('setGetterAttributes', array($config))
->setPublic(false);
}
//Creation du service @bes_auth.authenticator permettant la redirection sur le Cas ou le Rsa correspondant
$container->register('session_auth.authenticator', \Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator::class)
->setFactory(array(new Reference("session_auth.authenticator_factory"), 'getAuthenticator'))
->addArgument(new Reference($authentication_service))
->addArgument($config)
->addArgument(new Reference("router"))
->addArgument(new Reference("event_dispatcher"))
->setPublic(false);
//Création du service pour le provider par défaut ou pour le provider défini par l'utilisateur
if ($config["use_default_provider"]) {
//Creation du service @bes_auth.user_provider
$container->register('session_auth.user_provider', \App\Besancon\AuthBundle\Security\User\AuthUserProvider::class)
->addArgument(new Reference($authentication_service))
->addArgument($config)
->setPublic(false);
}else{
$container->register('session_auth.user_provider', $config["provider"])
->addArgument(new Reference($authentication_service))
->addArgument($config)
->setPublic(false);
}
$container->setDefinition('session_auth.configuration', new \Symfony\Component\DependencyInjection\Definition( \App\Besancon\AuthBundle\DependencyInjection\Configuration::class) )
->setArguments([
$config,
]);
}
public function getNamespace() {
return 'http://ac-besancon.fr/schema/dic/' . $this->getAlias();
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace App\Session\AuthBundle\Events;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Security\Core\User\UserInterface;
class CheckCredentialsEvent extends Event {
const NAME = "besancon_auth.event.check_credentials";
private $access = true;
public function __construct($credentials, UserInterface $user_interface) {
$this->credentials = $credentials;
$this->user_interface = $user_interface;
}
public function getCredentials() {
return $this->credentials;
}
public function getUserInterface() {
return $this->user_interface;
}
public function getAccess() {
return $this->access;
}
public function setAccess($access) {
$this->access = $access;
return $this;
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace App\Session\AuthBundle\Events;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class OnAuthenticationFailureEvent extends Event {
const NAME = "session_auth.event.on_authentication_failure";
public function __construct(Request $request, AuthenticationException $exception) {
$this->request = $request;
$this->exception = $exception;
$this->response = new Response($exception->getMessage(), Response::HTTP_FORBIDDEN);
}
public function getRequest() {
return $this->request;
}
public function getException() {
return $this->exception;
}
public function getResponse() {
return $this->response;
}
public function setResponse($response) {
$this->response = $response;
return $this;
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Session\AuthBundle\Events;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Request;
class OnAuthenticationSuccessEvent extends Event {
const NAME = "session_auth.event.on_authentication_success";
public function __construct(Request $request, TokenInterface $token, $providerKey) {
$this->request = $request;
$this->token = $token;
$this->providerKey = $providerKey;
}
public function getRequest() {
return $this->request;
}
public function getToken() {
return $this->exception;
}
public function getProviderKey() {
return $this->providerKey;
}
}

View file

@ -0,0 +1,366 @@
**AuthBundle**
========================
# Configuration minimale requise
Le bundle est compatible à partir de la version 3.4 de Symfony.
# Installation
## Installation via composer (recommandé)
Dans un premier temps renseigner le "repository" via la commande :
```bash
composer config repositories.authbundle git "ssh://git@gitlab1.in.ac-besancon.fr:1232/abelhadjali/authbundle.git"
```
Ceci va ajouter dans votre fichier composer.json les lignes suivantes
```json
...
"repositories": {
"authbundle": {
"type": "git",
"url": "ssh://git@gitlab1.in.ac-besancon.fr:1232/abelhadjali/authbundle.git"
}
}
...
```
Puis ajouter la dépendance au bundle en précisant le tag de la version souhaitée ici à partir de la v0.1
```bash
composer require ac-besancon/authbundle:^0.1
```
Enfin activer le bundle en suivant les instructions de la section [[AuthBundle#Activation du bundle|Activation du bundle]]
## Installation sans composer
### Récupérer les sources
*Copier et coller* le dossier Besancon du Bundle dans le repertoire _*src/*_ de votre projet *Symfony*.
### Déclaration du namespace
Dans le fichier `composer.json` et dans la section "autoload" de votre projet ajouter:
```json
"autoload": {
"psr-4": {
...
"Besancon\\AuthBundle\\": "src/Besancon/AuthBundle",
...
}
```
Puis executer la commande composer suivante :
```bash
composer dump-autoload
```
# Activation du bundle
Pour activer le Bundle, ouvrir le fichier app/AppKernel.php et y ajouter:
```php
// ...
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
// ...
new Besancon\AuthBundle\BesanconAuthBundle(),
);
// ...
}
// ...
}
```
# Configuration
======================
## Liste complète des options de configuration
La configuration est à déclaré dans le fichier *app/config/config.yml* du projet Symfony.
```yaml
besancon_auth:
#Activation du user_provider interne
#par défaut TRUE
use_default_provider : true
#Namespace de l'entité utilisateur
#L'entité doit implémenter Symfony\Component\Security\Core\User\UserInterface
#par défaut est utilise Besancon\AuthBundle\Security\User\AuthUser
user_entity: Mon\Entite\User
#nom de la route correspondant à la page d'accueil de l'application
#par défaut est à NULL
homepage: "homepage"
#tag du service personnalisé permettant de gérer l'authentification
#par défaut est à bes_auth.authentification (service par défaut)
authentication_service: mon_service.authentification
#Mode d'authentification Cas ou Rsa
#obligatoire pas de valeur par défaut
type_auth: Cas
#Configuration pour le mode Cas
#obligatoire si mode Cas choisi
cas:
#Serveur Cas
hostname: "seshat23.ac-besancon.fr"
#Port Cas
port: 8443
#Uri Cas
uri: ""
#Configuration pour le mode Rsa
#obligatoire si mode Rsa choisi
rsa :
#Url de déconnexion Rsa
logout_url: http://url.deconnexion.fr/login/ct_logout.jsp
```
## Configuration dans le firewall
Ouvrir le fichier app/config/security.yml du projet Symfony.
Si utilisation du _user provider_ interne *bes_auth.user_provider* , alors le déclarer dans la section _*providers*_ :
```yaml
...
providers:
app:
id: bes_auth.user_provider
...
```
Sinon préciser votre propre user provider
Toujours dans le même fichier, dans la section des _*firewalls*_, déclarer le _guard_ *bes_auth.authenticator* dans la zone à sécurisée :
```yaml
firewalls:
...
secured_area:
logout_on_user_change: true
...
guard:
authenticators:
- bes_auth.authenticator
logout:
path: auth_cas_logout #nom de la route de déconnexion
target: /
success_handler: bes_auth.authenticator
...
```
Plus d'infos sur le user provider :
* https://symfony.com/doc/current/security/entity_provider.html#using-a-custom-query-to-load-the-user
Il est donc important de définir la route de déconnexion dans le fichier *app/config/route.yml*
```yaml
...
auth_cas_logout:
path: /logout
...
```
## Configuration avancée
### Création d'un service d'authentification
Pour cela, créer un service qui hérite de *AuthAbstract* et implémente *AuthInterface*
```php
<?php
namespace AppBundle\Security\Auth;
use Besancon\AuthBundle\Security\Interfaces\AuthInterface;
use Doctrine\ORM\EntityManager;
use Besancon\AuthBundle\Security\Abstracts\AuthAbstract;
class MonService extends AuthAbstract implements AuthInterface {
...
}
```
Il faut ensuite implémenter les méthodes suivantes :
```php
/**
* Contrôle de l'accès à partir des attributs CAS ou RSA
*
* Vérifier les droits d'accès à l'application à partir des attributs récupérées des getters :
* - CasAttributes
* - RsaAttributes
*
* @param UserInterface $user
* L'entité user récupéré par le provider
*
* @return bool
* - true si accès autorisé
* - false si accès refusé
*/
public function ctrlAccess(UserInterface $user);
/**
* Calcule et retoune le(s) rôle(s) à partir des attributs CAS ou RSA
*
* Calculer le(s) rôle(s) à partir des attributs récupérées des getters :
* - CasAttributes
* - RsaAttributes
* Doit retourner un tableau même vide
*
* @return array
*/
public function getRoles();
/**
* Retourne un utilisateur pour la génération du token, si l'utilisateur n'existe pas en base de donnée
*
* ATTENTION : CETTE METHODE DOIT ÊTRE REDEFINIE SI UTILISATION D'UNE ENTITE UTILISTEUR
* DIFFERENTE DE CELLE UTILISEE PAR DEFAUT
*
* @param String $username
* uid de l'utilisateur récupéré de Cas ou Rsa
*
* @return UserInterface
*/
public function getUser($username);
/**
* Traitement personnalisé après récupération du token
*
* Il est possible d'enrichir le token (attributs...) ou d'effectuer des contrôles supplémentaire
*
* @param $token
* Token d'authification généré
*
* @return null
*/
public function onSuccess($token);
/**
* Traitement personnalisé lorsque la connexion n'a pas abouti
*
* Vérifié l'exception généré et adapter l'action (redirection, déconnexion...)
*
* Doit retourner un objet de type Response
*
* Exemple :
*
* ```
* public function onAuthenticationFailure(\Symfony\Component\Security\Core\Exception\AuthenticationException $exception)
* {
* $content = $this->twig->render(
* '@App/Test/forbiden.html.twig', array()
* );
* $response = new Response($content, Response::HTTP_FORBIDDEN);
* return $response;
* }
* ```
*
* @param AuthenticationException $exception
* Exception générée par le provider
*
* @return Symfony\Component\HttpFoundation\Response
*
*/
public function onAuthenticationFailure(\Symfony\Component\Security\Core\Exception\AuthenticationException $exception);
```
Enfin lorsque le service est prêt, le déclarer, en le reliant à la classe parent Besancon\AuthBundle\Security\Abstracts\AuthAbstract:
```yaml
mon_service.authentification:
class: AppBundle\Security\Auth\MonService
parent: Besancon\AuthBundle\Security\Abstracts\AuthAbstract
public: false
#OU si version Symfony >=3.4
AppBundle\Security\Auth\MonService:
autowire: true
parent: Besancon\AuthBundle\Security\Abstracts\AuthAbstract
public: false
autoconfigure: false
```
Puis déclarer dans la configuration ([[AuthBundle#Liste complète des options de configuration|Liste complète des options de configuration]]) du bundle le nom du service personnalisé :
```yaml
besancon_auth:
...
authentication_service: mon_service.authentification
#OU si version Symfony >=3.4
authentication_service: AppBundle\Security\Auth\MonService
...
```
# Personnaliser la page en cas d'échec d'authentification
En cas d'échec lors de l'authentification (exemple ctrlAccess() retourne false) , par défaut, le bundle renvoie une page blanche avec le message renvoyé par l'exception qui a généré l'erreur.
Afin de personnaliser cette page, il faut passer par la création d'un service comme indiqué dans le paragraphe [[AuthBundle#Création d'un service d'authentification|Création d'un service d'authentification]] et de redéfinir la méthode *onAuthenticationFailure*.
Voici un exemple :
```php
class MonService extends AuthAbstract implements AuthInterface
{
public function __construct(Twig_Environment $twig)
{
$this->twig = $twig;
}
...
public function onAuthenticationFailure(\Symfony\Component\Security\Core\Exception\AuthenticationException $exception)
{
$content = $this->twig->render(
'@App/Test/forbiden.html.twig', array()
);
$response = new Response($content, Response::HTTP_FORBIDDEN);
return $response;
}
}
```
Nous pouvons remarquer que dans cet exemple, le service prend en paramètre dans le constructeur $twig qui est l'instance de Twig de notre applciation.
Pour que cela fonctionne, il faut auparavant avoir passer le tag twig à notre service :
```php
...
AppBundle\Security\Auth\MonService:
autowire: true
parent: Besancon\AuthBundle\Security\Abstracts\AuthAbstract
public: false
autoconfigure: false
arguments: ['@twig']
...
```
Ainsi lorsqu'une personne tentera de se connecter et qu'il n'aura, par exemple, pas les droits nécessaires le template @App/Test/forbiden.html.twig sera chargé.

View file

@ -0,0 +1,40 @@
parameters:
#auth_cas devra s'appeler auth_multi
#bes_auth.authentication_service: bes_auth.authentification
session_auth:
type_auth: Session
environment: "%kernel.environment%"
cas:
#defini l'entité correspondant aux utilisateurs pour la création automatique des comptes
server:
cas_hostname: "seshat23.ac-besancon.fr"
cas_port: 8443
cas_uri: ""
route:
after_connect: "homepage"
rsa :
logout_url: http://webphppreprod.in.ac-besancon.fr/login/ct_logout.jsp
login_url: ~
route:
after_connect: "homepage"
#Gérer les droits d'accès à l'application en fonction des attributs CAS
access:
# allow:
# attributes :
# - ["[phpCAS][attributes][title]","[phpCAS][attributes][ABservice]"]
# - "[phpCAS][attributes][FrEduRne]"
# values : ["DIR|^DSS","^.*\\$TEC\\$"]
#deny:
#attributes : "title"
#values : "ENS"
#@TODO : Association profile CAS et Role de l'appli
# profil:
# cas:
# ROLE_ADMIN:
# key: "[phpCAS][attributes][typensi]"
# value: "A"
# ROLE_USER:
# key: "[phpCAS][attributes][FrEduRne]"
# value: "^0250069P"
# control: "regex"

View file

@ -0,0 +1,38 @@
parameters:
#auth_cas devra s'appeler auth_multi
session_auth:
environment: "%kernel.environment%"
#defini l'entité correspondant aux utilisateurs pour la création automatique des comptes
server:
cas_hostname: "seshat23.ac-besancon.fr"
cas_port: 8443
cas_uri: ""
route:
after_connect: "homepage"
#Gérer les droits d'accès à l'application en fonction des attributs CAS
access:
# allow:
# attributes :
# - ["[phpCAS][attributes][title]","[phpCAS][attributes][ABservice]"]
# - "[phpCAS][attributes][FrEduRne]"
# values : ["DIR|^DSS","^.*\\$TEC\\$"]
deny:
attributes : "title"
values : "ENS"
#@TODO : Association profile CAS et Role de l'appli
# profil:
# cas:
# ROLE_ADMIN:
# key: "[phpCAS][attributes][typensi]"
# value: "A"
# ROLE_USER:
# key: "[phpCAS][attributes][FrEduRne]"
# value: "^0250069P"
# control: "regex"
auth_rsa :
environment: "%kernel.environment%"
login_url: http://webphppreprod.in.ac-besancon.fr/login/ct_logon_mixte.jsp
logout_url: http://webphppreprod.in.ac-besancon.fr/login/ct_logout.jsp
route:
after_connect: "homepage"

View file

@ -0,0 +1,3 @@
#besancon_auth_homepage:
# path: /
# defaults: { _controller: BesanconAuthBundle:Default:index }

View file

@ -0,0 +1,11 @@
services:
session_auth.authenticator_factory:
class: App\Session\AuthBundle\Security\AuthenticatorFactory
public: false
#bes_auth.authentification:
# class: App\Besancon\AuthBundle\Security\Auth\Authentication
# parent: App\Besancon\AuthBundle\Security\Abstracts\AuthFinal
# public: false
# autoconfigure: false

View file

@ -0,0 +1,66 @@
Installation
============
1: Installation
---------------------------
Copier et coller le dossier Besancon du Bundle dans src/
2: Activer le Bundle
-------------------------
Pour activer le Bundle, ouvrir le fichier `app/AppKernel.php` et y ajouter:
```php
<?php
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
// ...
new Besancon\AuthBundle\BesanconAuthBundle(),
);
// ...
}
// ...
}
```
Puis dans le fichier `composer.json` de votre projet ajouter:
```json
"autoload": {
"psr-4": {
...
"Besancon\\AuthBundle\\": "src/Besancon/AuthBundle",
...
}
```
3: Authentification Cas
---------------------------
Si le Bundle est utilisé pour une athentification "Cas" alors télécharger la librairie phpCas dans votre projet
```console
$ composer require jasig/phpcas
```
Ouvrir le fichier `app/config/config.yml` et configurer :
```yml
besancon_auth:
homepage: "homepage" #nom de la route de l'accueil de l'application
type_auth: Cas
cas:
hostname: "serveurcas.ac-academy.fr" #serveur cas
port: 8443 #port cas
uri: "" #uri
```

View file

@ -0,0 +1,78 @@
<?php
/**
* Abstract AuthAbstract
*
* @package Besancon\AuthBundle\Security\Abstracts
* @author Amine BEL HADJ ALI <amine.belhadjali@ac-besancon.fr>
*
* @method setGetterAttributes()
* @method getUser()
* @abstract
*/
namespace App\Session\AuthBundle\Security\Abstracts;
use App\Session\AuthBundle\Utils\Config;
use Symfony\Component\HttpFoundation\Response;
abstract class AuthAbstract {
/**
* @var App\Besancon\AuthBundle\Security\Interfaces\AttributesInterface $ai Instance de CasAttributes ou RsaAttributes
*/
protected $ai;
/**
* Intancie le getters en fonction de la configuration
*
* Si dans la config le paramètre type_auth est défini à CAS alors
* intanciation du getter CasAttributes,
* Si la valeur est à RSA alors instanciation du getter RsaAttributes
*
* Cette instance peut ensuite être utilisée dans le service d'authentification
* qui héritera de AuthAbstract, en passant faisant appel à $this->ai
*
* @final
* @param $config
* configuration du Bundle
* @return void
*
* */
abstract public function setGetterAttributes($config);
/**
* Comportement par défaut lorsque l'authentification n'aboutie pas (accès non autorisé)
*
* il est possible de redéfinir cette méthode
* mais elle doit renvoyer une réponse HTTP exemple:
* - Symfony\Component\HttpFoundation\Response
* - Symfony\Component\HttpFoundation\JsonResponse
*
* @param \Symfony\Component\Security\Core\Exception\AuthenticationException $exception
* Exception généré par le guard
* @return Symfony\Component\HttpFoundation\Response
*
* */
abstract public function onAuthenticationFailure(\Symfony\Component\Security\Core\Exception\AuthenticationException $exception);
/**
* Renvoie une instance de l'utilisateur
*
* Ceci correspond à la class Besancon\AuthBundle\Security\User\AuthUser,
* il est possible de redéfinir cette méthode
* mais elle doit renvoyer un objet implementant Symfony\Component\Security\Core\User\UserInterface
*
* Est utilisé dans le userprovider par défaut Besancon\AuthBundle\Security\User\AuthUserProvider
*
* @see \Symfony\Component\Security\Core\User\UserInterface
* @see \Besancon\AuthBundle\Security\User\AuthUserProvider
*
* @param string $username
* Identifiant de l'utilisateur
* @return \Symfony\Component\Security\Core\User\UserInterface
*
*/
abstract public function getUser($username);
}

View file

@ -0,0 +1,87 @@
<?php
/**
* Abstract AuthAbstract
*
* @package Besancon\AuthBundle\Security\Abstracts
* @author Amine BEL HADJ ALI <amine.belhadjali@ac-besancon.fr>
*
* @method setGetterAttributes()
* @method getUser()
* @abstract
*/
namespace App\Session\AuthBundle\Security\Abstracts;
use App\Session\AuthBundle\Utils\Config;
use Symfony\Component\HttpFoundation\Response;
class AuthFinal extends AuthAbstract {
/**
* Intancie le getters en fonction de la configuration
*
* Si dans la config le paramètre type_auth est défini à CAS alors
* intanciation du getter CasAttributes,
* Si la valeur est à RSA alors instanciation du getter RsaAttributes
*
* Cette instance peut ensuite être utilisée dans le service d'authentification
* qui héritera de AuthAbstract, en passant faisant appel à $this->ai
*
* @final
* @param $config
* configuration du Bundle
* @return void
*
* */
public function setGetterAttributes($config) {
$type_auth = Config::getDeclaredType($config);
//dump('calls');
$getters = "\App\Session\AuthBundle\Security\Getters\\" . $type_auth . "Attributes";
$ai = new $getters();
$this->ai = $ai;
//dump($this->ai);
}
/**
* Comportement par défaut lorsque l'authentification n'aboutie pas (accès non autorisé)
*
* il est possible de redéfinir cette méthode
* mais elle doit renvoyer une réponse HTTP exemple:
* - Symfony\Component\HttpFoundation\Response
* - Symfony\Component\HttpFoundation\JsonResponse
*
* @param \Symfony\Component\Security\Core\Exception\AuthenticationException $exception
* Exception généré par le guard
* @return Symfony\Component\HttpFoundation\Response
*
* */
public function onAuthenticationFailure(\Symfony\Component\Security\Core\Exception\AuthenticationException $exception) {
return new Response($exception->getMessage(), Response::HTTP_FORBIDDEN);
}
/**
* Renvoie une instance de l'utilisateur
*
* Ceci correspond à la class Besancon\AuthBundle\Security\User\AuthUser,
* il est possible de redéfinir cette méthode
* mais elle doit renvoyer un objet implementant Symfony\Component\Security\Core\User\UserInterface
*
* Est utilisé dans le userprovider par défaut Besancon\AuthBundle\Security\User\AuthUserProvider
*
* @see \Symfony\Component\Security\Core\User\UserInterface
* @see \Besancon\AuthBundle\Security\User\AuthUserProvider
*
* @param string $username
* Identifiant de l'utilisateur
* @return \Symfony\Component\Security\Core\User\UserInterface
*
*/
public function getUser($username) {
$roles_service = $this->getRoles();
$roles = (!is_null($roles_service) && is_array($roles_service)) ? $roles_service : array();
$user = new \App\Besancon\AuthBundle\Security\User\AuthUser($username, md5("8sQaz87dPPsdanYakq86f" . $username), $roles);
return $user;
}
}

View file

@ -0,0 +1,42 @@
<?php
/**
* Abstract class GetterAbstract
*
* @package Besancon\AuthBundle\Security\Abstracts
* @author Amine BEL HADJ ALI <amine.belhadjali@ac-besancon.fr>
*/
namespace App\Session\AuthBundle\Security\Abstracts;
/**
* Description of GetterAbstract
*
* @author belhadjali
*/
abstract class GetterAbstract {
public function isACP(){
return $this->getFrEduFonctAdm() == "ACP";
}
public function isDIR(){
return $this->getFrEduFonctAdm() == "DIR";
}
public function isDEC(){
return $this->getFrEduFonctAdm() == "DEC";
}
public function isDIR1D(){
return $this->isDEC();
}
public function isIEN1D (){
return $this->getFrEduFonctAdm() == "IEN1D";
}
public function isDIO(){
return $this->getFrEduFonctAdm() == "IEN1D";
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
namespace App\Session\AuthBundle\Security;
use App\Session\AuthBundle\Security\Interfaces\AuthInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use App\Session\AuthBundle\Utils\Config;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class AuthenticatorFactory {
public static function getAuthenticator(AuthInterface $authService, Array $config, UrlGeneratorInterface $urlGenerator,EventDispatcherInterface $dispatcher) {
$type_auth = Config::getDeclaredType($config);
$authenticator_class = "App\Session\AuthBundle\Security\\" . $type_auth . "Authenticator";
$authenticator = new $authenticator_class($authService, $config, $urlGenerator, $dispatcher);
return $authenticator;
}
}

View file

@ -0,0 +1,114 @@
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
* Description of CasAuthenticator
*
* @author belhadjali
*/
namespace App\Session\AuthBundle\Security;
use App\Session\AuthBundle\Security\Interfaces\AuthInterface;
use App\Session\AuthBundle\Events\OnAuthenticationFailureEvent;
use App\Session\AuthBundle\Events\OnAuthenticationSuccessEvent;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Guard\AuthenticatorInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class CasAuthenticator extends AbstractFormLoginAuthenticator implements LogoutSuccessHandlerInterface, AuthenticatorInterface {
private $authService;
private $urlGenerator;
public function __construct(AuthInterface $authService, Array $config, UrlGeneratorInterface $urlGenerator, EventDispatcherInterface $dispatcher) {
$this->urlGenerator = $urlGenerator;
//Récupérer le service déaclaré authService
$this->authService = $authService;
$this->config = $config;
$this->dispatcher = $dispatcher;
if (php_sapi_name() !== 'cli') {
\phpCAS::client(CAS_VERSION_2_0, $this->config['cas']["hostname"], $this->config['cas']["port"], $this->config['cas']["uri"]);
\phpCAS::setNoCasServerValidation();
\phpCAS::forceAuthentication();
}
}
/**
* Called on every request. Return whatever credentials you want,
* or null to stop authentication.
*/
public function getCredentials(Request $request) {
return true;
}
public function getUser($credentials, UserProviderInterface $userProvider) {
$username = \phpCAS::getUser();
$user = $userProvider->loadUserByUsername($username);
return $user;
}
public function checkCredentials($credentials, UserInterface $user) {
return $this->authService->ctrlAccess($user);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) {
$event = new OnAuthenticationSuccessEvent($request, $token, $providerKey);
$this->dispatcher->dispatch(OnAuthenticationSuccessEvent::NAME, $event);
$this->authService->onSuccess($token);
// on success, let the request continue
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception) {
$event = new OnAuthenticationFailureEvent($request, $exception);
$this->dispatcher->dispatch(OnAuthenticationFailureEvent::NAME, $event);
return $this->authService->onAuthenticationFailure($exception);
}
/**
* Called when authentication is needed, but it's not sent
*/
// public function start(Request $request, AuthenticationException $authException = null) {
// $url = $this->router->generate('login');
// return new RedirectResponse($url);
// }
public function supportsRememberMe() {
return false;
}
//implementation LogoutSuccessHandlerInterface
public function onLogoutSuccess(Request $request) {
$homepage = $this->config["homepage"];
return \phpCAS::logoutWithRedirectService($this->urlGenerator->generate($homepage, array(), UrlGeneratorInterface::ABSOLUTE_URL));
}
protected function getLoginUrl() {
return \phpCas::getServerLoginURL();
}
public function supports(Request $request) {
if (isset($this->config['environment']) && $this->config['environment'] == "test") {
return false;
}
return true;
}
}

View file

@ -0,0 +1,106 @@
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
namespace App\Session\AuthBundle\Security;
use App\Session\AuthBundle\Security\Auth\Authentication;
use App\Session\AuthBundle\Security\Auth\User;
use App\Session\AuthBundle\Security\Auth\UserProvider;
use App\Session\AuthBundle\Security\Interfaces\AuthInterface;
use App\Session\AuthBundle\Security\Abstracts\AuthFinal;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider;
use Symfony\Component\Security\Core\Authentication\Provider\UserAuthenticationProvider;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
use Symfony\Component\Security\Core\User\UserChecker;
/**
* Description of DefaultAuthentication
*
* @author belhadjali
*/
class DefaultAuthentication extends AuthFinal implements AuthInterface {
/**
* @var string Uniquely identifies the secured area
*/
private $providerKey;
public function authentificate($token)
{
$username = $this->ai->getUsername();
$password = "";
$unauthenticatedToken = new UsernamePasswordToken(
$username,
$password,
'secured_area'
);
$userProvider = new UserProvider( new Authentication(),
array('user_entity' => 'App\Session\AuthBundle\Security\Auth\User',
'type_auth' => 'Cas'));
$userChecker = new UserChecker();
$defaultEncoder = new MessageDigestPasswordEncoder('sha512', true, 5000);
$encoders = [
User::class => $defaultEncoder,
];
$encoderFactory = new EncoderFactory($encoders);
$provider = new DaoAuthenticationProvider(
$userProvider,
$userChecker,
'secured_area',
$encoderFactory);
$authenticatedToken = $provider
->authenticate($unauthenticatedToken);
//$tokenStorage = new TokenStorage();
//$tokenStorage->setToken($authenticatedToken);
}
public function getRoles() {
return [];
}
public function onSuccess($token) {
//dump($this->ai);
//die('success');
//$this->authentificate($token);
$token->setAttribute("username", $this->ai->getUsername());
$token->setAttribute("complet_name", $this->ai->getCompletName());
$token->setAttribute("mail", $this->ai->getMail());
$token->setAttribute("FreDuRne", $this->ai->getFreDuRne());
return;
}
public function ctrlAccess(\Symfony\Component\Security\Core\User\UserInterface $user) {
//die('ctrlAccess');
return true;
}
public function getUser($username) {
return parent::getUser($username);
}
}

View file

@ -0,0 +1,76 @@
<?php
/**
* @package Besancon\AuthBundle\Security\Getters
* @author Amine BEL HADJ ALI <amine.belhadjali@ac-besancon.fr>
*/
namespace App\Session\AuthBundle\Security\Getters;
use App\Session\AuthBundle\Security\Interfaces\AttributesInterface;
/**
* Class CasAttributes
*
* Cette classe permet d'accèder aux informations (attributs) de l'utilisateur
* renvoyé par CAS à partir des méthodes d'accès définies dans l'interface AttributesInterface
*
*/
class CasAttributes implements AttributesInterface {
public function getFirstName() {
return \phpCAS::getAttribute("prenom");
}
public function getCompletName() {
return \phpCAS::getAttribute("nomcomplet");
}
public function getName() {
return \phpCAS::getAttribute("nom");
}
public function getDiscipline() {
return \phpCAS::getAttribute("discipline");
}
public function getFonctM() {
return \phpCAS::getAttribute("fonctm");
}
public function getRne() {
return \phpCAS::getAttribute("rne");
}
public function getFreDuRne() {
return \phpCAS::getAttribute("FrEduRne");
}
public function getFreDuRneResp() {
return \phpCAS::getAttribute("FrEduRneResp");
}
public function getMail() {
return \phpCAS::getAttribute("mail");
}
public function getTitle() {
return \phpCAS::getAttribute("title");
}
public function getUsername() {
return \phpCAS::getUser();
}
public function getFrEduResDel(){
return \phpCAS::getAttribute("FrEduResDel");
}
public function getFrEduFonctAdm() {
return \phpCAS::getAttribute("FrEduFonctAdm");
}
public function getGrade() {
return \phpCAS::getAttribute("grade");
}
}

View file

@ -0,0 +1,80 @@
<?php
/**
* Class RsaAttributes
*
* @package Besancon\AuthBundle\Security\Getters
* @author Amine BEL HADJ ALI <amine.belhadjali@ac-besancon.fr>
*
*/
namespace App\Besancon\AuthBundle\Security\Getters;
use App\Besancon\AuthBundle\Security\Interfaces\AttributesInterface;
/**
* Class RsaAttributes
*
* Cette classe permet d'accèder aux informations (entête HTTP) de l'utilisateur
* renvoyé par RSA CT à partir des méthodes d'accès définies dans l'interface AttributesInterface
*
*/
class RsaAttributes implements AttributesInterface {
public function getCompletName() {
return (isset($_SERVER['HTTP_CN'])) ? $_SERVER['HTTP_CN'] : null;
}
public function getDiscipline() {
return (isset($_SERVER['HTTP_DISCIPLINE'])) ? $_SERVER['HTTP_DISCIPLINE'] : null;
}
public function getFonctM() {
return (isset($_SERVER['HTTP_FONCTM'])) ? $_SERVER['HTTP_FONCTM'] : null;
}
public function getRne() {
return (isset($_SERVER['HTTP_RNE'])) ? $_SERVER['HTTP_FREDURNE'] : null;
}
public function getFreDuRne() {
return (isset($_SERVER['HTTP_FREDURNE'])) ? explode(',', $_SERVER['HTTP_FREDURNE']) : null;
}
public function getFreDuRneResp() {
return (isset($_SERVER['HTTP_FREDURNERESP'])) ? explode(',', $_SERVER['HTTP_FREDURNERESP']) : null;
}
public function getMail() {
return (isset($_SERVER['HTTP_CTEMAIL'])) ? $_SERVER['HTTP_CTEMAIL'] : null;
}
public function getTitle() {
return (isset($_SERVER['HTTP_TITLE'])) ? $_SERVER['HTTP_TITLE'] : null;
}
public function getUsername() {
return (isset($_SERVER['HTTP_CT_REMOTE_USER'])) ? $_SERVER['HTTP_CT_REMOTE_USER'] : null;
}
public function getFrEduResDel() {
return (isset($_SERVER['HTTP_FREDURESDEL'])) ? $_SERVER['HTTP_FREDURESDEL'] : null;
}
public function getFrEduFonctAdm() {
return (isset($_SERVER['HTTP_FREDUFONCTADM'])) ? $_SERVER['HTTP_FREDUFONCTADM'] : null;
}
public function getFirstName() {
return (isset($_SERVER['HTTP_CTFN'])) ? $_SERVER['HTTP_CTFN'] : null;
}
public function getName() {
return (isset($_SERVER['HTTP_CTLN'])) ? $_SERVER['HTTP_CTLN'] : null;
}
public function getGrade() {
return (isset($_SERVER['HTTP_GRADE'])) ? $_SERVER['HTTP_GRADE'] : null;
}
}

View file

@ -0,0 +1,76 @@
<?php
/**
* @package Besancon\AuthBundle\Security\Getters
* @author Amine BEL HADJ ALI <amine.belhadjali@ac-besancon.fr>
*/
namespace App\Session\AuthBundle\Security\Getters;
use App\Session\AuthBundle\Security\Interfaces\AttributesInterface;
/**
* Class CasAttributes
*
* Cette classe permet d'accèder aux informations (attributs) de l'utilisateur
* renvoyé par CAS à partir des méthodes d'accès définies dans l'interface AttributesInterface
*
*/
class SessionAttributes implements AttributesInterface {
public function getFirstName() {
return ;
}
public function getCompletName() {
return ;
}
public function getName() {
return ;
}
public function getDiscipline() {
return ;
}
public function getFonctM() {
return ;
}
public function getRne() {
return ;
}
public function getFreDuRne() {
return ;
}
public function getFreDuRneResp() {
return ;
}
public function getMail() {
return ;
}
public function getTitle() {
return ;
}
public function getUsername() {
return ;
}
public function getFrEduResDel(){
return ;
}
public function getFrEduFonctAdm() {
return ;
}
public function getGrade() {
return ;
}
}

View file

@ -0,0 +1,215 @@
<?php
/**
* Interface AttributesInterface
*
* @package Besancon\AuthBundle\Security\Interfaces
* @author Amine BEL HADJ ALI <amine.belhadjali@ac-besancon.fr>
*
*/
namespace App\Session\AuthBundle\Security\Interfaces;
/**
* Interface AttributesInterface
*
*/
interface AttributesInterface
{
const NO_VALUE = "X";
const FREDURNE_OFFSET_RNE = 0;
const FREDURNE_OFFSET_SECTEUR = 2;
const FREDURNE_OFFSET_FONCTION_EXERCICE = 3;
const FREDURNE_OFFSET_FONCTION_RNEUAJ = 4;
const FREDURNE_OFFSET_1CODETNA = 5; // 1er chiffre code nature nomenclature
const FREDURNE_OFFSET_CODETTY = 6; // code type etablissement nomenclature
const FREDURNE_OFFSET_CODETNA = 7; // code nature etablissement nomenclature
const FREDURNERESP_OFFSET_RNE = 0;
const FREDURNERESP_OFFSET_SECTEUR = 2; //PU ou PR
const FREDURNERESP_OFFSET_AFFECTATION = 3; // A pour Affectation anticipé N pour affectation normale F pour affectation qui fini le 31/08
const FREDURNERESP_OFFSET_1CODETNA = 4; // 1er chiffre code nature nomenclature
const FREDURNERESP_OFFSET_CODETTY = 5; // code type etablissement nomenclature
const FREDURNERESP_OFFSET_CODETNA = 6; // code nature nomenclature
const TYPE_LYCEE_GENERAL = "LYC";
const TYPE_LYCEE_PRO = "LP";
const TYPE_COLLEGE = "CLG";
const TYPE_SEGPA = "SES";
const CODE_NATURE_RECTORAT = ["802"];
const CODE_NATURE_DSDEN = ["806"];
const CODE_NATURE_INSPECTION = ["809"];
const CODE_NATURE_LYCEE_GENERAL_ET_TECHNO = ["300"];
const CODE_NATURE_LYCEE_TECHNO = ["301"];
const CODE_NATURE_LYCEE_GENERAL = ["302", "306"];
const CODE_NATURE_LYCEE_AGRICOLE = ["307"];
const CODE_NATURE_LYCEE_PRO = ["320"];
const CODE_NATURE_COLLEGE = ["340"];
const CODE_COLLEGE_NATURE_SPE = ["352"];
const CODE_NATURE_SEGPA = ["390"];
const GRADES_IEN = ["1152", "1151"];
const GRADES_RECTEUR = ["0201"];
const GRADES_SG = ["0211", "0911", "0912"];
const GRADES_ASG = ["0981"];
const GRADES_DASEN = ["0921", "0922"];
const GRADES_ADJOINT_DASEN = ["0971"];
const CODES_DISCIPLINE_ASH = ["N0006"];
const CODES_DISCIPLINE_DIR = ["D0010"];
const CODES_DISCIPLINE_ADJOINT_DIR = ["D0011"];
/**
* Renvoie le prénom de l'agent
*
* Correspond au champ "givenName" du LDAP
*
* @return string|null
* prénom de l'agent
*/
public function getFirstName();
/**
* Renvoie l'identifiant LDAP de l'agent
*
* Correspond au champ "uid" du LDAP
*
* @return string|null
* uid de l'agent
*/
public function getUsername();
/**
* Renvoie le nom de famille de l'agent
*
* Correspond au champ "sn" du LDAP
*
* @return string|null
* nom de l'agent
*/
public function getName();
/**
* Renvoie l'adresse mail de l'agent
*
* Correspond au champ "mail" du LDAP
*
* @return string|null
* adresse mail de l'agent
*/
public function getMail();
/**
* Renvoie le nom complet de l'agent
*
* Correspond au champ "cn" du LDAP
*
* @return string|null
* nom complete de l'agent
*/
public function getCompletName();
/**
* Renvoie le title de l'agent
*
* Correspond au champ "title" du LDAP
*
* @return string|null
* title de l'agent
*/
public function getTitle();
/**
* Renvoie le code discipline de l'agent
*
* Correspond au champ "discipline" du LDAP
*
* @return string|null
* code discipline de l'agent
*/
public function getDiscipline();
/**
* Renvoie l'établissements d'affectation de l'agent
*
* Correspond au champ "rne" du LDAP
*
* @return string|null
* * établissement d'affectation de l'agent
*/
public function getRne();
/**
* Renvoie l'établissements dexercice de l'agent
*
* Correspond au champ "FreDuRne" du LDAP
*
* @return array|null
* établissement(s) d'exercice de l'agent
*/
public function getFreDuRne();
/**
* Renvoie le(s) établissement(s) en responsabilité de l'agent
*
* Correspond au champ "FreDuRneResp" du LDAP
*
* @return array|null
* établissement(s) en responsabalité de l'agent
*/
public function getFreDuRneResp();
/**
* Renvoie le(s) déléguation(s)/attribution(s) de l'agent ouvrant des droits d'accès
* à une ressource d'une application pour un ou des rne
*
* Correspond au champ "FreDuRneDel" du LDAP
*
* @return array|null
* déléguation(s)/attribution(s) de l'agent
*/
public function getFrEduResDel();
/**
* Renvoie la fonction administrative de l'agent
* correspondant à un profil particulier
*
* Correspond au champ "FrEduFonctAdm" du LDAP
*
* @return string|null
* fonction administrative de l'agent
*/
public function getFrEduFonctAdm();
/**
* Renvoie la fonction de l'agent
* Attention : initialisé à la création de la fiche avec la même valeur que lattribut fonction.
* Puis, par lapplication Annuaire, lagent peut le modifier.
*
* Correspond au champ "fonctm" du LDAP
*
* @return string|null
* fonction de l'agent
*/
public function getFonctM();
/**
* Renvoie le grade de l'agent
* Alimenté à partir de la valeur agt.gradco
* Se référer à la base des nomenclatures dans la table N_GRADE pour voir
* les correspondances : http://infocentre.pleiade.education.fr/bcn/workspace/viewTable/n/N_GRADE
*
* Correspond au champ "grade" du LDAP
*
* @return string|null
* fonction de l'agent
*/
public function getGrade();
}

View file

@ -0,0 +1,100 @@
<?php
/**
* Interface AuthInterface
*
* Interface permettant de déclarer les méthodes incontournables pour l'authentification
*
*
* @package Besancon\AuthBundle\Security\Interfaces
* @author Amine BEL HADJ ALI <amine.belhadjali@ac-besancon.fr>
*
*/
namespace App\Session\AuthBundle\Security\Interfaces;
use Symfony\Component\Security\Core\User\UserInterface;
interface AuthInterface {
/**
* Contrôle de l'accès à partir des attributs CAS ou RSA
*
* Vérifier les droits d'accès à l'application à partir des attributs récupérées des getters :
* - CasAttributes
* - RsaAttributes
*
* @param UserInterface $user
* L'entité user récupéré par le provider
*
* @return bool
* - true si accès autorisé
* - false si accès refusé
*/
public function ctrlAccess(UserInterface $user);
/**
* Calcule et retoune le(s) rôle(s) à partir des attributs CAS ou RSA
*
* Calculer le(s) rôle(s) à partir des attributs récupérées des getters :
* - CasAttributes
* - RsaAttributes
* Doit retourner un tableau même vide
*
* @return array
*/
public function getRoles();
/**
* Retourne un utilisateur pour la génération du token, si l'utilisateur n'existe pas en base de donnée
*
* ATTENTION : CETTE METHODE DOIT ÊTRE REDEFINIE SI UTILISATION D'UNE ENTITE UTILISTEUR
* DIFFERENTE DE CELLE UTILISEE PAR DEFAUT
*
* @param String $username
* uid de l'utilisateur récupéré de Cas ou Rsa
*
* @return UserInterface
*/
public function getUser($username);
/**
* Traitement personnalisé après récupération du token
*
* Il est possible d'enrichir le token (attributs...) ou d'effectuer des contrôles supplémentaire
*
* @param $token
* Token d'authification généré
*
* @return null
*/
public function onSuccess($token);
/**
* Traitement personnalisé lorsque la connexion n'a pas abouti
*
* Vérifié l'exception généré et adapter l'action (redirection, déconnexion...)
*
* Doit retourner un objet de type Response
*
* Exemple :
*
* ```
* public function onAuthenticationFailure(\Symfony\Component\Security\Core\Exception\AuthenticationException $exception)
* {
* $content = $this->twig->render(
* '@App/Test/forbiden.html.twig', array()
* );
* $response = new Response($content, Response::HTTP_FORBIDDEN);
* return $response;
* }
* ```
*
* @param AuthenticationException $exception
* Exception générée par le provider
*
* @return Symfony\Component\HttpFoundation\Response
*
*/
public function onAuthenticationFailure(\Symfony\Component\Security\Core\Exception\AuthenticationException $exception);
}

View file

@ -0,0 +1,120 @@
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
* Description of CasAuthenticator
*
* @author belhadjali
*/
namespace App\Session\AuthBundle\Security;
use App\Session\AuthBundle\Security\Interfaces\AuthInterface;
use App\Session\AuthBundle\Events\OnAuthenticationFailureEvent;
use App\Session\AuthBundle\Events\OnAuthenticationSuccessEvent;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Guard\AuthenticatorInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class RsaAuthenticator extends AbstractFormLoginAuthenticator implements LogoutSuccessHandlerInterface, AuthenticatorInterface {
private $authService;
private $urlGenerator;
private $dispatcher;
public function __construct(AuthInterface $authService, Array $config, UrlGeneratorInterface $urlGenerator, EventDispatcherInterface $dispatcher) {
$this->urlGenerator = $urlGenerator;
//Récupérer le service déaclaré authService
$this->authService = $authService;
$this->config = $config;
$this->dispatcher = $dispatcher;
}
/**
* Called on every request. Return whatever credentials you want,
* or null to stop authentication.
*/
public function getCredentials(Request $request) {
if (!isset($_SERVER['HTTP_CT_REMOTE_USER']) || empty($_SERVER['HTTP_CT_REMOTE_USER'])) {
$this->returnRequest = $request->getUri();
throw new \LogicException("Impossible de continuer sous RSA : L'entête HTTP_CT_REMOTE_USER est vide ou manquante");
}
return true;
}
public function getUser($credentials, UserProviderInterface $userProvider) {
$username = $_SERVER['HTTP_CT_REMOTE_USER'];
$user = $userProvider->loadUserByUsername($username);
return $user;
}
public function checkCredentials($credentials, UserInterface $user) {
$this->authService->ctrlAccess($user);
// check credentials - e.g. make sure the password is valid
// no credential check is needed in this case
// return true to cause authentication success
return true;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) {
$event = new OnAuthenticationSuccessEvent($request, $token, $providerKey);
$this->dispatcher->dispatch(OnAuthenticationSuccessEvent::NAME, $event);
$this->authService->onSuccess($token);
// on success, let the request continue
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception) {
$event = new OnAuthenticationFailureEvent($request, $exception);
$this->dispatcher->dispatch(OnAuthenticationFailureEvent::NAME, $event);
return $this->authService->onAuthenticationFailure($exception);
}
/**
* Called when authentication is needed, but it's not sent
*/
// public function start(Request $request, AuthenticationException $authException = null) {
// $url = $this->router->generate('login');
// return new RedirectResponse($url);
// }
public function supportsRememberMe() {
return false;
}
//implementation LogoutSuccessHandlerInterface
public function onLogoutSuccess(Request $request) {
$redirect = (isset($_SERVER['HTTP_FREDUURLRETOUR'])) ? $_SERVER['HTTP_FREDUURLRETOUR'] : $this->config['rsa']['logout_url'];
return new RedirectResponse($redirect);
}
protected function getLoginUrl() {
$return_request = urlencode($this->returnRequest);
$params = "?CT_ORIG_URL=" . $return_request;
return $this->config['rsa']['login_url'] . $params;
}
public function supports(Request $request) {
if (isset($this->config['environment']) && $this->config['environment'] == "test") {
return false;
}
return true;
}
}

View file

@ -0,0 +1,127 @@
<?php
namespace App\Session\AuthBundle\Security;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
class SessionAuthenticator extends AbstractGuardAuthenticator
{
public $router;
public function __construct(UrlGeneratorInterface $router)
{
$this->router = $router;
}
/**
* Called on every request to decide if this authenticator should be
* used for the request. Returning `false` will cause this authenticator
* to be skipped.
*/
public function supports(Request $request)
{
if (isset($_SESSION['id_utilisateur'])) {
return true;
}else{
return true;
}
}
/**
* Called on every request. Return whatever credentials you want to
* be passed to getUser() as $credentials.
*/
public function getCredentials(Request $request)
{
return "X-AUTH-TOKEN-SESSION-API";
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
if (!isset($_SESSION['id_utilisateur'])) {
$user = new \App\Classes\AuthUser('','','','','',['ROLE_USER']);
}else {
$user = new \App\Classes\AuthUser($_SESSION['id_utilisateur'], $_SESSION['identifiant'], $_SESSION['status_compte'], $_SESSION['type_compte'],$credentials, ['ROLE_USER', 'ROLE_USER_CONNECTED']);
}
// if a User is returned, checkCredentials() is called
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
// Check credentials - e.g. make sure the password is valid.
// In case of an API token, no credential check is needed.
// Return `true` to cause authentication success
if($user->getCredentials() === $credentials) {
return true;
}else{
return false;
}
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
//return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$data = [
// you may want to customize or obfuscate the message first
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
// or to translate this message
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
];
// return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
$url = $this->router->generate('unauthorized');
return new RedirectResponse($url);
}
/**
* Called when authentication is needed, but it's not sent
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$data = [
// you might translate this message
'message' => 'Authentication Required'
];
//return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
$url = $this->router->generate('unauthorized');
return new RedirectResponse($url);
}
public function supportsRememberMe()
{
return false;
}
public function onLogoutSuccess(Request $request) {
//$homepage = $this->config["homepage"];
//return \phpCAS::logoutWithRedirectService($this->urlGenerator->generate($homepage, array(), UrlGeneratorInterface::ABSOLUTE_URL));
header('Location: /index.php');
return ;
}
}

View file

@ -0,0 +1,332 @@
<?php
namespace App\Session\AuthBundle\Security\Traits;
use App\Session\AuthBundle\Security\Interfaces\AttributesInterface;
trait ProfilsCalculator
{
//est recteur
public function isRecteur()
{
return in_array($this->ai->getDiscipline(), AttributesInterface::GRADES_RECTEUR);
}
//est secrétaire général d'académie
public function isSG()
{
return in_array($this->ai->getDiscipline(), AttributesInterface::GRADES_SG);
}
//est adjoint au secrétaire général d'académie
public function isASG()
{
return in_array($this->ai->getDiscipline(), AttributesInterface::GRADES_ASG);
}
//agent comptable
public function isACP()
{
return $this->ai->getFrEduFonctAdm() == "ACP";
}
//enseignant
public function isENS()
{
return $this->ai->getFrEduFonctAdm() == AttributesInterface::NO_VALUE && $this->ai->getTitle() == "ENS" && $this->ai->getFrEduRneResp() == AttributesInterface::NO_VALUE;
}
//agent issue d'AGAPE PRIVE
public function isAgentPrive()
{
return $this->ai->getTypensi() == "R";
}
//equipe de direction établissement
public function isGroupeDIR()
{
return $this->ai->getFrEduFonctAdm() == "DIR";
}
//directeur 2nd degré
public function isDIR()
{
return $this->isGroupeDIR() && in_array($this->ai->getDiscipline(), AttributesInterface::CODES_DISCIPLINE_DIR);
}
//directeur adjoint 2nd degré
public function isAdjointDIR()
{
return $this->isGroupeDIR() && in_array($this->ai->getDiscipline(), AttributesInterface::CODES_DISCIPLINE_ADJOINT_DIR);
}
//directeur d'ecole
public function isDEC()
{
return $this->ai->getFrEduFonctAdm() == "DEC";
}
//alias directeur d'ecole
public function isDIR1D()
{
return $this->isDEC();
}
//adaptation scolaire et de la scolarisation des élèves handicapé
public function isASH()
{
return in_array($this->ai->getDiscipline(), AttributesInterface::CODES_DISCIPLINE_ASH);
}
//est inspecteur
public function isIEN()
{
return (!is_null($this->ai->getGrade())) ? in_array($this->ai->getGrade(), AttributesInterface::GRADES_IEN) : $this->ai->getTitle() == "INS";
}
//est inspecteur 1er degré
public function isIEN1D()
{
return $this->isIEN() && $this->ai->getFrEduFonctAdm() == "IEN1D";
}
//est inspecteur ASH
public function isIENASH()
{
return $this->isASH() && $this->isIEN();
}
//est DASEN
public function isDASEN()
{
return in_array($this->ai->getGrade(), AttributesInterface::GRADES_DASEN);
}
//est adjoint DASEN
public function isAdjointDasen()
{
return in_array($this->ai->getGrade(), AttributesInterface::GRADES_ADJOINT_DASEN);
}
//est directeur CIO
public function isDIO()
{
return $this->ai->getFrEduFonctAdm() == "DIO";
}
public function filterFrEduRneByType($type)
{
if ($this->ai->getFrEduRne() == AttributesInterface::NO_VALUE) {
return [];
}
$FrEduRne = (!is_array($this->ai->getFrEduRne())) ? [$this->ai->getFrEduRne()] : $this->ai->getFrEduRne();
$uais = array_filter($FrEduRne, function ($value) use ($type) {
$arr_value = explode("$", $value);
if (!is_array($arr_value) || !array_key_exists(AttributesInterface::FREDURNE_OFFSET_CODETTY, $arr_value)) {
return false;
}
if (is_array($type)) {
return in_array($arr_value[AttributesInterface::FREDURNE_OFFSET_CODETTY], $type);
}
return $arr_value[AttributesInterface::FREDURNE_OFFSET_CODETTY] == $type;
});
return $uais;
}
public function filterFrEduRneByNature($nature)
{
if ($this->ai->getFrEduRne() == AttributesInterface::NO_VALUE) {
return [];
}
$FrEduRne = (!is_array($this->ai->getFrEduRne())) ? [$this->ai->getFrEduRne()] : $this->ai->getFrEduRne();
$uais = array_filter($FrEduRne, function ($value) use ($nature) {
$arr_value = explode("$", $value);
if (!is_array($arr_value) || !array_key_exists(AttributesInterface::FREDURNE_OFFSET_CODETNA, $arr_value)) {
return false;
}
if (is_array($nature)) {
return in_array($arr_value[AttributesInterface::FREDURNE_OFFSET_CODETNA], $nature);
}
return $arr_value[AttributesInterface::FREDURNE_OFFSET_CODETNA] == $nature;
});
return $uais;
}
public function filterFrEduRneRespByNature($nature)
{
if ($this->ai->getFrEduRneResp() == AttributesInterface::NO_VALUE) {
return [];
}
$FrEduRneResp = (!is_array($this->ai->getFrEduRneResp())) ? [$this->ai->getFrEduRneResp()] : $this->ai->getFrEduRneResp();
$uais = array_filter($FrEduRneResp, function ($value) use ($nature) {
$arr_value = explode("$", $value);
if (!is_array($arr_value) || !array_key_exists(AttributesInterface::FREDURNERESP_OFFSET_CODETNA, $arr_value)) {
return false;
}
if (is_array($nature)) {
return in_array($arr_value[AttributesInterface::FREDURNERESP_OFFSET_CODETNA], $nature);
}
return $arr_value[AttributesInterface::FREDURNERESP_OFFSET_CODETNA] == $nature;
});
return $uais;
}
public function filterFrEduRneRespByType($type)
{
if ($this->ai->getFrEduRneResp() == AttributesInterface::NO_VALUE) {
return [];
}
$FrEduRneResp = (!is_array($this->ai->getFrEduRneResp())) ? [$this->ai->getFrEduRneResp()] : $this->ai->getFrEduRneResp();
$uais = array_filter($FrEduRneResp, function ($value) use ($type) {
$arr_value = explode("$", $value);
if (!is_array($arr_value) || !array_key_exists(AttributesInterface::FREDURNERESP_OFFSET_CODETTY, $arr_value)) {
return false;
}
if (is_array($type)) {
return in_array($arr_value[AttributesInterface::FREDURNERESP_OFFSET_CODETTY], $type);
}
return $arr_value[AttributesInterface::FREDURNERESP_OFFSET_CODETTY] == $type;
});
return $uais;
}
// public function hasLYC()
// {
// return $this->findUaiRespByType(AttributesInterface::TYPE_LYCEE_GENERAL);
// }
// public function hasLYCP()
// {
// return $this->findUaiRespByType(AttributesInterface::TYPE_LYCEE_PRO);
// }
public function isAffectedToRectorat()
{
$result = $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_RECTORAT);
return (count($result)) ? true : false;
}
public function isAffectedToDSDEN()
{
$result = $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_DSDEN);
return (count($result)) ? true : false;
}
public function isAffectedToLYC()
{
$result = $this->filterFrEduRneByType(AttributesInterface::TYPE_LYCEE_GENERAL);
return (count($result)) ? true : false;
}
public function isAffectedToLP()
{
$result = $this->filterFrEduRneByType(AttributesInterface::TYPE_LYCEE_PRO);
return (count($result)) ? true : false;
}
public function isAffectedToInspection()
{
$result = $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_INSPECTION);
return (count($result)) ? true : false;
}
public function isAffectedToSEGPA()
{
$result = $this->filterFrEduRneByType(AttributesInterface::TYPE_SEGPA);
return (count($result)) ? true : false;
}
public function isRespOfLYC()
{
$result = $this->filterFrEduRneRespByType(AttributesInterface::TYPE_LYCEE_GENERAL);
return (count($result)) ? true : false;
}
public function isRespOfLP()
{
$result = $this->filterFrEduRneRespByType(AttributesInterface::TYPE_LYCEE_PRO);
return (count($result)) ? true : false;
}
public function isRespOfSEGPA()
{
$result = $this->filterFrEduRneRespByType(AttributesInterface::TYPE_SEGPA);
return (count($result)) ? true : false;
}
/****************************************************************************************
* Filtres sur FrEduRne
***************************************************************************************/
public function filterFrEduRneByLYCG()
{
return $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_LYCEE_GENERAL);
}
public function filterFrEduRneByLYCGT()
{
return $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_LYCEE_GENERAL_ET_TECHNO);
}
public function filterFrEduRneByLP()
{
return $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_LYCEE_GENERAL_ET_TECHNO);
}
public function filterFrEduRneByCLG()
{
return $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_COLLEGE);
}
public function filterFrEduRneByLYCAG()
{
return $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_LYCEE_AGRICOLE);
}
public function filterFrEduRneBySEGPA()
{
return $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_SEGPA);
}
/****************************************************************************************
* Filtres sur FrEduRneResp
***************************************************************************************/
public function filterFrEduRneRespByLYCG()
{
return $this->filterFrEduRneRespByNature(AttributesInterface::CODE_NATURE_LYCEE_GENERAL);
}
public function filterFrEduRneRespByLYCGT()
{
return $this->filterFrEduRneRespByNature(AttributesInterface::CODE_NATURE_LYCEE_GENERAL_ET_TECHNO);
}
public function filterFrEduRneRespByLP()
{
return $this->filterFrEduRneRespByNature(AttributesInterface::CODE_NATURE_LYCEE_GENERAL_ET_TECHNO);
}
public function filterFrEduRneRespByCLG()
{
return $this->filterFrEduRneRespByNature(AttributesInterface::CODE_NATURE_COLLEGE);
}
public function filterFrEduRneRespByLYCAG()
{
return $this->filterFrEduRneRespByNature(AttributesInterface::CODE_NATURE_LYCEE_AGRICOLE);
}
public function filterFrEduRneRespBySEGPA()
{
return $this->filterFrEduRneRespByNature(AttributesInterface::CODE_NATURE_SEGPA);
}
}

View file

@ -0,0 +1,76 @@
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
* Description of AuthUser
*
* @author belhadjali
*/
namespace App\Session\AuthBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
class AuthUser implements UserInterface, EquatableInterface {
private $username;
private $salt;
private $roles = [];
public function __construct($username, $salt, array $roles = []) {
$this->username = $username;
$this->salt = $salt;
$this->roles = $roles;
}
public function getRoles() {
return $this->roles;
}
public function setRoles($roles) {
return $this->roles = $roles;
}
public function addRole($role) {
return $this->roles[] = $role;
}
public function getPassword() {
return;
}
public function getSalt() {
return $this->salt;
}
public function getUsername() {
return $this->username;
}
public function eraseCredentials() {
}
public function isEqualTo(UserInterface $user) {
if (!$user instanceof AuthUser) {
return false;
}
if ($this->salt !== $user->getSalt()) {
return false;
}
if ($this->username !== $user->getUsername()) {
return false;
}
return true;
}
}

View file

@ -0,0 +1,58 @@
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
namespace App\Session\AuthBundle\Security\User;
use App\Besancon\AuthBundle\Security\Interfaces\AuthInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class AuthUserProvider implements UserProviderInterface {
public function __construct(AuthInterface $authService, Array $config) {
$this->config = $config;
if (!is_null($this->config['user_entity'])) {
$this->entity_user = "\\".$this->config['user_entity'];
} else {
$this->entity_user = "App\Session\AuthBundle\Security\User\AuthUser";
}
$this->authService = $authService;
}
public function loadUserByUsername($username) {
$entity_user = $this->entity_user;
return $this->authService->getUser($username);
}
private function _ctrlInstanceUser(UserInterface $user) {
$entity_user = $this->entity_user;
if (!$user instanceof $entity_user) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $user;
}
public function refreshUser(UserInterface $user) {
$user = $this->_ctrlInstanceUser($user);
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class) {
$entity_user = $this->entity_user;
return $this->entity_class === $class;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace App\Session\AuthBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class SessionAuthBundle extends Bundle
{
}

View file

@ -0,0 +1,17 @@
<?php
namespace App\Session\AuthBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class DefaultControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request('GET', '/');
$this->assertContains('Hello World', $client->getResponse()->getContent());
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace App\Session\AuthBundle\Utils;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
* Description of Controls
*
* @author belhadjali
*/
class Config {
public static function getDeclaredType($config) {
if (!isset($config['type_auth'])) {
throw new \LogicException('Paramètre type_auth manquant');
}
$type = $config['type_auth'];
self::typeIsSupported($type);
return self::formatType($type);
}
public static function formatType($type) {
return ucfirst(strtolower($type));
}
public static function typeIsSupported($type) {
$type_auth = self::formatType($type);
if (!in_array($type_auth, ['Rsa', 'Cas'])) {
throw new \LogicException('Seuls Cas et Rsa sont supportés pour le moment');
}
return true;
}
}

View file

@ -0,0 +1,12 @@
{
"name": "ac-besancon/authbundle",
"description": "Bundle Symfony 3 permettant de mettre en palce une authentification CAS ou RSA à travers le système de Guard",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Amine Belhadjali",
"email": "amine.belhadjali@ac-besancon.fr"
}
]
}

View file

@ -0,0 +1,44 @@
<?php
namespace App\Utils;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
* Description of Controls
*
* @author belhadjali
*/
class Config {
public static function getDeclaredType($config) {
// if (!isset($config['type_auth'])) {
// throw new \LogicException('Paramètre type_auth manquant');
// }
//
// $type = $config['type_auth'];
//
// self::typeIsSupported($type);
//
// return self::formatType($type);
return true;
}
public static function formatType($type) {
// return ucfirst(strtolower($type));
return;
}
public static function typeIsSupported($type) {
// $type_auth = self::formatType($type);
// if (!in_array($type_auth, ['Rsa', 'Cas'])) {
// throw new \LogicException('Seuls Cas et Rsa sont supportés pour le moment');
// }
return true;
}
}

View file

@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View file

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Cache;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\InvalidArgumentException;
/**
* Covers most simple to advanced caching needs.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface CacheInterface
{
/**
* Fetches a value from the pool or computes it if not found.
*
* On cache misses, a callback is called that should return the missing value.
* This callback is given a PSR-6 CacheItemInterface instance corresponding to the
* requested key, that could be used e.g. for expiration control. It could also
* be an ItemInterface instance when its additional features are needed.
*
* @param string $key The key of the item to retrieve from the cache
* @param callable|CallbackInterface $callback Should return the computed value for the given key/item
* @param float|null $beta A float that, as it grows, controls the likeliness of triggering
* early expiration. 0 disables it, INF forces immediate expiration.
* The default (or providing null) is implementation dependent but should
* typically be 1.0, which should provide optimal stampede protection.
* See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration
* @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()}
*
* @return mixed The value corresponding to the provided key
*
* @throws InvalidArgumentException When $key is not valid or when $beta is negative
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null);
/**
* Removes an item from the pool.
*
* @param string $key The key to delete
*
* @throws InvalidArgumentException When $key is not valid
*
* @return bool True if the item was successfully removed, false if there was any error
*/
public function delete(string $key): bool;
}

View file

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Cache;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException;
use Psr\Log\LoggerInterface;
/**
* An implementation of CacheInterface for PSR-6 CacheItemPoolInterface classes.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
trait CacheTrait
{
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
return $this->doGet($this, $key, $callback, $beta, $metadata);
}
/**
* {@inheritdoc}
*/
public function delete(string $key): bool
{
return $this->deleteItem($key);
}
private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null, LoggerInterface $logger = null)
{
if (0 > $beta = $beta ?? 1.0) {
throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', \get_class($this), $beta)) extends \InvalidArgumentException implements InvalidArgumentException {
};
}
$item = $pool->getItem($key);
$recompute = !$item->isHit() || INF === $beta;
$metadata = $item instanceof ItemInterface ? $item->getMetadata() : [];
if (!$recompute && $metadata) {
$expiry = $metadata[ItemInterface::METADATA_EXPIRY] ?? false;
$ctime = $metadata[ItemInterface::METADATA_CTIME] ?? false;
if ($recompute = $ctime && $expiry && $expiry <= ($now = microtime(true)) - $ctime / 1000 * $beta * log(random_int(1, PHP_INT_MAX) / PHP_INT_MAX)) {
// force applying defaultLifetime to expiry
$item->expiresAt(null);
$logger && $logger->info('Item "{key}" elected for early recomputation {delta}s before its expiration', [
'key' => $key,
'delta' => sprintf('%.1f', $expiry - $now),
]);
}
}
if ($recompute) {
$save = true;
$item->set($callback($item, $save));
if ($save) {
$pool->save($item);
}
}
return $item->get();
}
}

View file

@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Cache;
use Psr\Cache\CacheItemInterface;
/**
* Computes and returns the cached value of an item.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface CallbackInterface
{
/**
* @param CacheItemInterface|ItemInterface $item The item to compute the value for
* @param bool &$save Should be set to false when the value should not be saved in the pool
*
* @return mixed The computed value for the passed item
*/
public function __invoke(CacheItemInterface $item, bool &$save);
}

View file

@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Cache;
use Psr\Cache\CacheException;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\InvalidArgumentException;
/**
* Augments PSR-6's CacheItemInterface with support for tags and metadata.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface ItemInterface extends CacheItemInterface
{
/**
* References the Unix timestamp stating when the item will expire.
*/
const METADATA_EXPIRY = 'expiry';
/**
* References the time the item took to be created, in milliseconds.
*/
const METADATA_CTIME = 'ctime';
/**
* References the list of tags that were assigned to the item, as string[].
*/
const METADATA_TAGS = 'tags';
/**
* Reserved characters that cannot be used in a key or tag.
*/
const RESERVED_CHARACTERS = '{}()/\@:';
/**
* Adds a tag to a cache item.
*
* Tags are strings that follow the same validation rules as keys.
*
* @param string|string[] $tags A tag or array of tags
*
* @return $this
*
* @throws InvalidArgumentException When $tag is not valid
* @throws CacheException When the item comes from a pool that is not tag-aware
*/
public function tag($tags): self;
/**
* Returns a list of metadata info that were saved alongside with the cached value.
*
* See ItemInterface::METADATA_* consts for keys potentially found in the returned array.
*/
public function getMetadata(): array;
}

View file

@ -0,0 +1,19 @@
Copyright (c) 2018-2019 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,9 @@
Symfony Cache Contracts
=======================
A set of abstractions extracted out of the Symfony components.
Can be used to build on semantics that the Symfony components proved useful - and
that already have battle tested implementations.
See https://github.com/symfony/contracts/blob/master/README.md for more information.

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Cache;
use Psr\Cache\InvalidArgumentException;
/**
* Allows invalidating cached items using tags.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface TagAwareCacheInterface extends CacheInterface
{
/**
* Invalidates cached items using tags.
*
* When implemented on a PSR-6 pool, invalidation should not apply
* to deferred items. Instead, they should be committed as usual.
* This allows replacing old tagged values by new ones without
* race conditions.
*
* @param string[] $tags An array of tags to invalidate
*
* @return bool True on success
*
* @throws InvalidArgumentException When $tags is not valid
*/
public function invalidateTags(array $tags);
}

View file

@ -0,0 +1,34 @@
{
"name": "symfony/cache-contracts",
"type": "library",
"description": "Generic abstractions related to caching",
"keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": "^7.2.5",
"psr/cache": "^1.0"
},
"suggest": {
"symfony/cache-implementation": ""
},
"autoload": {
"psr-4": { "Symfony\\Contracts\\Cache\\": "" }
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
}
}

View file

@ -0,0 +1,3 @@
/Tests export-ignore
/phpunit.xml.dist export-ignore
/.gitignore export-ignore

View file

@ -0,0 +1,203 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
{
/**
* @internal
*/
protected const NS_SEPARATOR = ':';
use AbstractAdapterTrait;
use ContractsTrait;
private static $apcuSupported;
private static $phpFilesSupported;
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
{
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR;
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
}
$this->createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) use ($defaultLifetime) {
$item = new CacheItem();
$item->key = $key;
$item->value = $v = $value;
$item->isHit = $isHit;
$item->defaultLifetime = $defaultLifetime;
// Detect wrapped values that encode for their expiry and creation duration
// For compactness, these values are packed in the key of an array using
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
$item->value = $v[$k];
$v = unpack('Ve/Nc', substr($k, 1, -1));
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
}
return $item;
},
null,
CacheItem::class
);
$getId = \Closure::fromCallable([$this, 'getId']);
$this->mergeByLifetime = \Closure::bind(
static function ($deferred, $namespace, &$expiredIds) use ($getId) {
$byLifetime = [];
$now = microtime(true);
$expiredIds = [];
foreach ($deferred as $key => $item) {
$key = (string) $key;
if (null === $item->expiry) {
$ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
} elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
$expiredIds[] = $getId($key);
continue;
}
if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
unset($metadata[CacheItem::METADATA_TAGS]);
}
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
$byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item->value] : $item->value;
}
return $byLifetime;
},
null,
CacheItem::class
);
}
/**
* Returns the best possible adapter that your runtime supports.
*
* Using ApcuAdapter makes system caches compatible with read-only filesystems.
*
* @param string $namespace
* @param int $defaultLifetime
* @param string $version
* @param string $directory
*
* @return AdapterInterface
*/
public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null)
{
$opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true);
if (null !== $logger) {
$opcache->setLogger($logger);
}
if (!self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported()) {
return $opcache;
}
$apcu = new ApcuAdapter($namespace, (int) $defaultLifetime / 5, $version);
if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) {
$apcu->setLogger(new NullLogger());
} elseif (null !== $logger) {
$apcu->setLogger($logger);
}
return new ChainAdapter([$apcu, $opcache]);
}
public static function createConnection($dsn, array $options = [])
{
if (!\is_string($dsn)) {
throw new InvalidArgumentException(sprintf('The %s() method expect argument #1 to be string, %s given.', __METHOD__, \gettype($dsn)));
}
if (0 === strpos($dsn, 'redis:') || 0 === strpos($dsn, 'rediss:')) {
return RedisAdapter::createConnection($dsn, $options);
}
if (0 === strpos($dsn, 'memcached:')) {
return MemcachedAdapter::createConnection($dsn, $options);
}
throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn));
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function commit()
{
$ok = true;
$byLifetime = $this->mergeByLifetime;
$byLifetime = $byLifetime($this->deferred, $this->namespace, $expiredIds);
$retry = $this->deferred = [];
if ($expiredIds) {
$this->doDelete($expiredIds);
}
foreach ($byLifetime as $lifetime => $values) {
try {
$e = $this->doSave($values, $lifetime);
} catch (\Exception $e) {
}
if (true === $e || [] === $e) {
continue;
}
if (\is_array($e) || 1 === \count($values)) {
foreach (\is_array($e) ? $e : array_keys($values) as $id) {
$ok = false;
$v = $values[$id];
$type = \is_object($v) ? \get_class($v) : \gettype($v);
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
}
} else {
foreach ($values as $id => $v) {
$retry[$lifetime][] = $id;
}
}
}
// When bulk-save failed, retry each item individually
foreach ($retry as $lifetime => $ids) {
foreach ($ids as $id) {
try {
$v = $byLifetime[$lifetime][$id];
$e = $this->doSave([$id => $v], $lifetime);
} catch (\Exception $e) {
}
if (true === $e || [] === $e) {
continue;
}
$ok = false;
$type = \is_object($v) ? \get_class($v) : \gettype($v);
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
}
}
return $ok;
}
}

View file

@ -0,0 +1,323 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Psr\Log\LoggerAwareInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
/**
* Abstract for native TagAware adapters.
*
* To keep info on tags, the tags are both serialized as part of cache value and provided as tag ids
* to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate().
*
* @author Nicolas Grekas <p@tchwork.com>
* @author André Rømcke <andre.romcke+symfony@gmail.com>
*
* @internal
*/
abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface
{
use AbstractAdapterTrait;
use ContractsTrait;
private const TAGS_PREFIX = "\0tags\0";
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
{
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
}
$this->createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) use ($defaultLifetime) {
$item = new CacheItem();
$item->key = $key;
$item->defaultLifetime = $defaultLifetime;
$item->isTaggable = true;
// If structure does not match what we expect return item as is (no value and not a hit)
if (!\is_array($value) || !\array_key_exists('value', $value)) {
return $item;
}
$item->isHit = $isHit;
// Extract value, tags and meta data from the cache value
$item->value = $value['value'];
$item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? [];
if (isset($value['meta'])) {
// For compactness these values are packed, & expiry is offset to reduce size
$v = unpack('Ve/Nc', $value['meta']);
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
}
return $item;
},
null,
CacheItem::class
);
$getId = \Closure::fromCallable([$this, 'getId']);
$tagPrefix = self::TAGS_PREFIX;
$this->mergeByLifetime = \Closure::bind(
static function ($deferred, &$expiredIds) use ($getId, $tagPrefix) {
$byLifetime = [];
$now = microtime(true);
$expiredIds = [];
foreach ($deferred as $key => $item) {
$key = (string) $key;
if (null === $item->expiry) {
$ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
} elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
$expiredIds[] = $getId($key);
continue;
}
// Store Value and Tags on the cache value
if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
$value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]];
unset($metadata[CacheItem::METADATA_TAGS]);
} else {
$value = ['value' => $item->value, 'tags' => []];
}
if ($metadata) {
// For compactness, expiry and creation duration are packed, using magic numbers as separators
$value['meta'] = pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]);
}
// Extract tag changes, these should be removed from values in doSave()
$value['tag-operations'] = ['add' => [], 'remove' => []];
$oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? [];
foreach (array_diff($value['tags'], $oldTags) as $addedTag) {
$value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag);
}
foreach (array_diff($oldTags, $value['tags']) as $removedTag) {
$value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag);
}
$byLifetime[$ttl][$getId($key)] = $value;
}
return $byLifetime;
},
null,
CacheItem::class
);
}
/**
* Persists several cache items immediately.
*
* @param array $values The values to cache, indexed by their cache identifier
* @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning
* @param array[] $addTagData Hash where key is tag id, and array value is list of cache id's to add to tag
* @param array[] $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag
*
* @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not
*/
abstract protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array;
/**
* Removes multiple items from the pool and their corresponding tags.
*
* @param array $ids An array of identifiers that should be removed from the pool
*
* @return bool True if the items were successfully removed, false otherwise
*/
abstract protected function doDelete(array $ids);
/**
* Removes relations between tags and deleted items.
*
* @param array $tagData Array of tag => key identifiers that should be removed from the pool
*/
abstract protected function doDeleteTagRelations(array $tagData): bool;
/**
* Invalidates cached items using tags.
*
* @param string[] $tagIds An array of tags to invalidate, key is tag and value is tag id
*
* @return bool True on success
*/
abstract protected function doInvalidate(array $tagIds): bool;
/**
* Delete items and yields the tags they were bound to.
*/
protected function doDeleteYieldTags(array $ids): iterable
{
foreach ($this->doFetch($ids) as $id => $value) {
yield $id => \is_array($value) && \is_array($value['tags'] ?? null) ? $value['tags'] : [];
}
$this->doDelete($ids);
}
/**
* {@inheritdoc}
*/
public function commit(): bool
{
$ok = true;
$byLifetime = $this->mergeByLifetime;
$byLifetime = $byLifetime($this->deferred, $expiredIds);
$retry = $this->deferred = [];
if ($expiredIds) {
// Tags are not cleaned up in this case, however that is done on invalidateTags().
$this->doDelete($expiredIds);
}
foreach ($byLifetime as $lifetime => $values) {
try {
$values = $this->extractTagData($values, $addTagData, $removeTagData);
$e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
} catch (\Exception $e) {
}
if (true === $e || [] === $e) {
continue;
}
if (\is_array($e) || 1 === \count($values)) {
foreach (\is_array($e) ? $e : array_keys($values) as $id) {
$ok = false;
$v = $values[$id];
$type = \is_object($v) ? \get_class($v) : \gettype($v);
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
}
} else {
foreach ($values as $id => $v) {
$retry[$lifetime][] = $id;
}
}
}
// When bulk-save failed, retry each item individually
foreach ($retry as $lifetime => $ids) {
foreach ($ids as $id) {
try {
$v = $byLifetime[$lifetime][$id];
$values = $this->extractTagData([$id => $v], $addTagData, $removeTagData);
$e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
} catch (\Exception $e) {
}
if (true === $e || [] === $e) {
continue;
}
$ok = false;
$type = \is_object($v) ? \get_class($v) : \gettype($v);
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
}
}
return $ok;
}
/**
* {@inheritdoc}
*/
public function deleteItems(array $keys): bool
{
if (!$keys) {
return true;
}
$ok = true;
$ids = [];
$tagData = [];
foreach ($keys as $key) {
$ids[$key] = $this->getId($key);
unset($this->deferred[$key]);
}
try {
foreach ($this->doDeleteYieldTags(array_values($ids)) as $id => $tags) {
foreach ($tags as $tag) {
$tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id;
}
}
} catch (\Exception $e) {
$ok = false;
}
try {
if ((!$tagData || $this->doDeleteTagRelations($tagData)) && $ok) {
return true;
}
} catch (\Exception $e) {
}
// When bulk-delete failed, retry each item individually
foreach ($ids as $key => $id) {
try {
$e = null;
if ($this->doDelete([$id])) {
continue;
}
} catch (\Exception $e) {
}
$message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
$ok = false;
}
return $ok;
}
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags)
{
if (empty($tags)) {
return false;
}
$tagIds = [];
foreach (array_unique($tags) as $tag) {
$tagIds[] = $this->getId(self::TAGS_PREFIX.$tag);
}
if ($this->doInvalidate($tagIds)) {
return true;
}
return false;
}
/**
* Extracts tags operation data from $values set in mergeByLifetime, and returns values without it.
*/
private function extractTagData(array $values, ?array &$addTagData, ?array &$removeTagData): array
{
$addTagData = $removeTagData = [];
foreach ($values as $id => $value) {
foreach ($value['tag-operations']['add'] as $tag => $tagId) {
$addTagData[$tagId][] = $id;
}
foreach ($value['tag-operations']['remove'] as $tag => $tagId) {
$removeTagData[$tagId][] = $id;
}
unset($values[$id]['tag-operations']);
}
return $values;
}
}

View file

@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
/**
* Interface for adapters managing instances of Symfony's CacheItem.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface AdapterInterface extends CacheItemPoolInterface
{
/**
* {@inheritdoc}
*
* @return CacheItem
*/
public function getItem($key);
/**
* {@inheritdoc}
*
* @return \Traversable|CacheItem[]
*/
public function getItems(array $keys = []);
/**
* {@inheritdoc}
*
* @param string $prefix
*
* @return bool
*/
public function clear(/*string $prefix = ''*/);
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Traits\ApcuTrait;
class ApcuAdapter extends AbstractAdapter
{
use ApcuTrait;
/**
* @throws CacheException if APCu is not enabled
*/
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null)
{
$this->init($namespace, $defaultLifetime, $version);
}
}

View file

@ -0,0 +1,171 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Psr\Log\LoggerAwareInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ArrayTrait;
use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
{
use ArrayTrait;
private $createCacheItem;
/**
* @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
*/
public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true)
{
$this->storeSerialized = $storeSerialized;
$this->createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) use ($defaultLifetime) {
$item = new CacheItem();
$item->key = $key;
$item->value = $value;
$item->isHit = $isHit;
$item->defaultLifetime = $defaultLifetime;
return $item;
},
null,
CacheItem::class
);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
$item = $this->getItem($key);
$metadata = $item->getMetadata();
// ArrayAdapter works in memory, we don't care about stampede protection
if (INF === $beta || !$item->isHit()) {
$save = true;
$this->save($item->set($callback($item, $save)));
}
return $item->get();
}
/**
* {@inheritdoc}
*/
public function getItem($key)
{
if (!$isHit = $this->hasItem($key)) {
$this->values[$key] = $value = null;
} else {
$value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
}
$f = $this->createCacheItem;
return $f($key, $value, $isHit);
}
/**
* {@inheritdoc}
*/
public function getItems(array $keys = [])
{
foreach ($keys as $key) {
if (!\is_string($key) || !isset($this->expiries[$key])) {
CacheItem::validateKey($key);
}
}
return $this->generateItems($keys, microtime(true), $this->createCacheItem);
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItems(array $keys)
{
foreach ($keys as $key) {
$this->deleteItem($key);
}
return true;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function save(CacheItemInterface $item)
{
if (!$item instanceof CacheItem) {
return false;
}
$item = (array) $item;
$key = $item["\0*\0key"];
$value = $item["\0*\0value"];
$expiry = $item["\0*\0expiry"];
if (null !== $expiry && $expiry <= microtime(true)) {
$this->deleteItem($key);
return true;
}
if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
return false;
}
if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) {
$expiry = microtime(true) + $item["\0*\0defaultLifetime"];
}
$this->values[$key] = $value;
$this->expiries[$key] = null !== $expiry ? $expiry : PHP_INT_MAX;
return true;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
return $this->save($item);
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function commit()
{
return true;
}
/**
* {@inheritdoc}
*/
public function delete(string $key): bool
{
return $this->deleteItem($key);
}
}

View file

@ -0,0 +1,332 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* Chains several adapters together.
*
* Cached items are fetched from the first adapter having them in its data store.
* They are saved and deleted in all adapters at once.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
{
use ContractsTrait;
private $adapters = [];
private $adapterCount;
private $syncItem;
/**
* @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items
* @param int $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones
*/
public function __construct(array $adapters, int $defaultLifetime = 0)
{
if (!$adapters) {
throw new InvalidArgumentException('At least one adapter must be specified.');
}
foreach ($adapters as $adapter) {
if (!$adapter instanceof CacheItemPoolInterface) {
throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($adapter), CacheItemPoolInterface::class));
}
if ($adapter instanceof AdapterInterface) {
$this->adapters[] = $adapter;
} else {
$this->adapters[] = new ProxyAdapter($adapter);
}
}
$this->adapterCount = \count($this->adapters);
$this->syncItem = \Closure::bind(
static function ($sourceItem, $item, $sourceMetadata = null) use ($defaultLifetime) {
$sourceItem->isTaggable = false;
$sourceMetadata = $sourceMetadata ?? $sourceItem->metadata;
unset($sourceMetadata[CacheItem::METADATA_TAGS]);
$item->value = $sourceItem->value;
$item->expiry = $sourceMetadata[CacheItem::METADATA_EXPIRY] ?? $sourceItem->expiry;
$item->isHit = $sourceItem->isHit;
$item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata;
if (0 < $sourceItem->defaultLifetime && $sourceItem->defaultLifetime < $defaultLifetime) {
$defaultLifetime = $sourceItem->defaultLifetime;
}
if (0 < $defaultLifetime && ($item->defaultLifetime <= 0 || $defaultLifetime < $item->defaultLifetime)) {
$item->defaultLifetime = $defaultLifetime;
}
return $item;
},
null,
CacheItem::class
);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
$lastItem = null;
$i = 0;
$wrap = function (CacheItem $item = null) use ($key, $callback, $beta, &$wrap, &$i, &$lastItem, &$metadata) {
$adapter = $this->adapters[$i];
if (isset($this->adapters[++$i])) {
$callback = $wrap;
$beta = INF === $beta ? INF : 0;
}
if ($adapter instanceof CacheInterface) {
$value = $adapter->get($key, $callback, $beta, $metadata);
} else {
$value = $this->doGet($adapter, $key, $callback, $beta, $metadata);
}
if (null !== $item) {
($this->syncItem)($lastItem = $lastItem ?? $item, $item, $metadata);
}
return $value;
};
return $wrap();
}
/**
* {@inheritdoc}
*/
public function getItem($key)
{
$syncItem = $this->syncItem;
$misses = [];
foreach ($this->adapters as $i => $adapter) {
$item = $adapter->getItem($key);
if ($item->isHit()) {
while (0 <= --$i) {
$this->adapters[$i]->save($syncItem($item, $misses[$i]));
}
return $item;
}
$misses[$i] = $item;
}
return $item;
}
/**
* {@inheritdoc}
*/
public function getItems(array $keys = [])
{
return $this->generateItems($this->adapters[0]->getItems($keys), 0);
}
private function generateItems(iterable $items, int $adapterIndex)
{
$missing = [];
$misses = [];
$nextAdapterIndex = $adapterIndex + 1;
$nextAdapter = isset($this->adapters[$nextAdapterIndex]) ? $this->adapters[$nextAdapterIndex] : null;
foreach ($items as $k => $item) {
if (!$nextAdapter || $item->isHit()) {
yield $k => $item;
} else {
$missing[] = $k;
$misses[$k] = $item;
}
}
if ($missing) {
$syncItem = $this->syncItem;
$adapter = $this->adapters[$adapterIndex];
$items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex);
foreach ($items as $k => $item) {
if ($item->isHit()) {
$adapter->save($syncItem($item, $misses[$k]));
}
yield $k => $item;
}
}
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function hasItem($key)
{
foreach ($this->adapters as $adapter) {
if ($adapter->hasItem($key)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*
* @param string $prefix
*
* @return bool
*/
public function clear(/*string $prefix = ''*/)
{
$prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
$cleared = true;
$i = $this->adapterCount;
while ($i--) {
if ($this->adapters[$i] instanceof AdapterInterface) {
$cleared = $this->adapters[$i]->clear($prefix) && $cleared;
} else {
$cleared = $this->adapters[$i]->clear() && $cleared;
}
}
return $cleared;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItem($key)
{
$deleted = true;
$i = $this->adapterCount;
while ($i--) {
$deleted = $this->adapters[$i]->deleteItem($key) && $deleted;
}
return $deleted;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItems(array $keys)
{
$deleted = true;
$i = $this->adapterCount;
while ($i--) {
$deleted = $this->adapters[$i]->deleteItems($keys) && $deleted;
}
return $deleted;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function save(CacheItemInterface $item)
{
$saved = true;
$i = $this->adapterCount;
while ($i--) {
$saved = $this->adapters[$i]->save($item) && $saved;
}
return $saved;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
$saved = true;
$i = $this->adapterCount;
while ($i--) {
$saved = $this->adapters[$i]->saveDeferred($item) && $saved;
}
return $saved;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function commit()
{
$committed = true;
$i = $this->adapterCount;
while ($i--) {
$committed = $this->adapters[$i]->commit() && $committed;
}
return $committed;
}
/**
* {@inheritdoc}
*/
public function prune()
{
$pruned = true;
foreach ($this->adapters as $adapter) {
if ($adapter instanceof PruneableInterface) {
$pruned = $adapter->prune() && $pruned;
}
}
return $pruned;
}
/**
* {@inheritdoc}
*/
public function reset()
{
foreach ($this->adapters as $adapter) {
if ($adapter instanceof ResetInterface) {
$adapter->reset();
}
}
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Doctrine\Common\Cache\CacheProvider;
use Symfony\Component\Cache\Traits\DoctrineTrait;
class DoctrineAdapter extends AbstractAdapter
{
use DoctrineTrait;
public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
{
parent::__construct('', $defaultLifetime);
$this->provider = $provider;
$provider->setNamespace($namespace);
}
}

View file

@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\FilesystemTrait;
class FilesystemAdapter extends AbstractAdapter implements PruneableInterface
{
use FilesystemTrait;
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
{
$this->marshaller = $marshaller ?? new DefaultMarshaller();
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
}
}

View file

@ -0,0 +1,239 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\FilesystemTrait;
/**
* Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author André Rømcke <andre.romcke+symfony@gmail.com>
*/
class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface
{
use FilesystemTrait {
doClear as private doClearCache;
doSave as private doSaveCache;
}
/**
* Folder used for tag symlinks.
*/
private const TAG_FOLDER = 'tags';
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
{
$this->marshaller = new TagAwareMarshaller($marshaller);
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
}
/**
* {@inheritdoc}
*/
protected function doClear($namespace)
{
$ok = $this->doClearCache($namespace);
if ('' !== $namespace) {
return $ok;
}
set_error_handler(static function () {});
$chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
try {
foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) {
if (rename($dir, $renamed = substr_replace($dir, bin2hex(random_bytes(4)), -8))) {
$dir = $renamed.\DIRECTORY_SEPARATOR;
} else {
$dir .= \DIRECTORY_SEPARATOR;
$renamed = null;
}
for ($i = 0; $i < 38; ++$i) {
if (!file_exists($dir.$chars[$i])) {
continue;
}
for ($j = 0; $j < 38; ++$j) {
if (!file_exists($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) {
continue;
}
foreach (scandir($d, SCANDIR_SORT_NONE) ?: [] as $link) {
if ('.' !== $link && '..' !== $link && (null !== $renamed || !realpath($d.\DIRECTORY_SEPARATOR.$link))) {
unlink($d.\DIRECTORY_SEPARATOR.$link);
}
}
null === $renamed ?: rmdir($d);
}
null === $renamed ?: rmdir($dir.$chars[$i]);
}
null === $renamed ?: rmdir($renamed);
}
} finally {
restore_error_handler();
}
return $ok;
}
/**
* {@inheritdoc}
*/
protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array
{
$failed = $this->doSaveCache($values, $lifetime);
// Add Tags as symlinks
foreach ($addTagData as $tagId => $ids) {
$tagFolder = $this->getTagFolder($tagId);
foreach ($ids as $id) {
if ($failed && \in_array($id, $failed, true)) {
continue;
}
$file = $this->getFile($id);
if (!@symlink($file, $this->getFile($id, true, $tagFolder))) {
@unlink($file);
$failed[] = $id;
}
}
}
// Unlink removed Tags
foreach ($removeTagData as $tagId => $ids) {
$tagFolder = $this->getTagFolder($tagId);
foreach ($ids as $id) {
if ($failed && \in_array($id, $failed, true)) {
continue;
}
@unlink($this->getFile($id, false, $tagFolder));
}
}
return $failed;
}
/**
* {@inheritdoc}
*/
protected function doDeleteYieldTags(array $ids): iterable
{
foreach ($ids as $id) {
$file = $this->getFile($id);
if (!file_exists($file) || !$h = @fopen($file, 'rb')) {
continue;
}
if ((\PHP_VERSION_ID >= 70300 || '\\' !== \DIRECTORY_SEPARATOR) && !@unlink($file)) {
fclose($h);
continue;
}
$meta = explode("\n", fread($h, 4096), 3)[2] ?? '';
// detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
if (13 < \strlen($meta) && "\x9D" === $meta[0] && "\0" === $meta[5] && "\x5F" === $meta[9]) {
$meta[9] = "\0";
$tagLen = unpack('Nlen', $meta, 9)['len'];
$meta = substr($meta, 13, $tagLen);
if (0 < $tagLen -= \strlen($meta)) {
$meta .= fread($h, $tagLen);
}
try {
yield $id => '' === $meta ? [] : $this->marshaller->unmarshall($meta);
} catch (\Exception $e) {
yield $id => [];
}
}
fclose($h);
if (\PHP_VERSION_ID < 70300 && '\\' === \DIRECTORY_SEPARATOR) {
@unlink($file);
}
}
}
/**
* {@inheritdoc}
*/
protected function doDeleteTagRelations(array $tagData): bool
{
foreach ($tagData as $tagId => $idList) {
$tagFolder = $this->getTagFolder($tagId);
foreach ($idList as $id) {
@unlink($this->getFile($id, false, $tagFolder));
}
}
return true;
}
/**
* {@inheritdoc}
*/
protected function doInvalidate(array $tagIds): bool
{
foreach ($tagIds as $tagId) {
if (!file_exists($tagFolder = $this->getTagFolder($tagId))) {
continue;
}
set_error_handler(static function () {});
try {
if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -9))) {
$tagFolder = $renamed.\DIRECTORY_SEPARATOR;
} else {
$renamed = null;
}
foreach ($this->scanHashDir($tagFolder) as $itemLink) {
unlink(realpath($itemLink) ?: $itemLink);
unlink($itemLink);
}
if (null === $renamed) {
continue;
}
$chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
for ($i = 0; $i < 38; ++$i) {
for ($j = 0; $j < 38; ++$j) {
rmdir($tagFolder.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j]);
}
rmdir($tagFolder.$chars[$i]);
}
rmdir($renamed);
} finally {
restore_error_handler();
}
}
return true;
}
private function getTagFolder(string $tagId): string
{
return $this->getFile($tagId, false, $this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR;
}
}

View file

@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\MemcachedTrait;
class MemcachedAdapter extends AbstractAdapter
{
use MemcachedTrait;
protected $maxIdLength = 250;
/**
* Using a MemcachedAdapter with a TagAwareAdapter for storing tags is discouraged.
* Using a RedisAdapter is recommended instead. If you cannot do otherwise, be aware that:
* - the Memcached::OPT_BINARY_PROTOCOL must be enabled
* (that's the default when using MemcachedAdapter::createConnection());
* - tags eviction by Memcached's LRU algorithm will break by-tags invalidation;
* your Memcached memory should be large enough to never trigger LRU.
*
* Using a MemcachedAdapter as a pure items store is fine.
*/
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{
$this->init($client, $namespace, $defaultLifetime, $marshaller);
}
}

View file

@ -0,0 +1,156 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class NullAdapter implements AdapterInterface, CacheInterface
{
private $createCacheItem;
public function __construct()
{
$this->createCacheItem = \Closure::bind(
function ($key) {
$item = new CacheItem();
$item->key = $key;
$item->isHit = false;
return $item;
},
$this,
CacheItem::class
);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
$save = true;
return $callback(($this->createCacheItem)($key), $save);
}
/**
* {@inheritdoc}
*/
public function getItem($key)
{
$f = $this->createCacheItem;
return $f($key);
}
/**
* {@inheritdoc}
*/
public function getItems(array $keys = [])
{
return $this->generateItems($keys);
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function hasItem($key)
{
return false;
}
/**
* {@inheritdoc}
*
* @param string $prefix
*
* @return bool
*/
public function clear(/*string $prefix = ''*/)
{
return true;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItem($key)
{
return true;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItems(array $keys)
{
return true;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function save(CacheItemInterface $item)
{
return false;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
return false;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function commit()
{
return false;
}
/**
* {@inheritdoc}
*/
public function delete(string $key): bool
{
return $this->deleteItem($key);
}
private function generateItems(array $keys)
{
$f = $this->createCacheItem;
foreach ($keys as $key) {
yield $key => $f($key);
}
}
}

View file

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Doctrine\DBAL\Connection;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\PdoTrait;
class PdoAdapter extends AbstractAdapter implements PruneableInterface
{
use PdoTrait;
protected $maxIdLength = 255;
/**
* You can either pass an existing database connection as PDO instance or
* a Doctrine DBAL Connection or a DSN string that will be used to
* lazy-connect to the database when the cache is actually used.
*
* When a Doctrine DBAL Connection is passed, the cache table is created
* automatically when possible. Otherwise, use the createTable() method.
*
* List of available options:
* * db_table: The name of the table [default: cache_items]
* * db_id_col: The column where to store the cache id [default: item_id]
* * db_data_col: The column where to store the cache data [default: item_data]
* * db_lifetime_col: The column where to store the lifetime [default: item_lifetime]
* * db_time_col: The column where to store the timestamp [default: item_time]
* * db_username: The username when lazy-connect [default: '']
* * db_password: The password when lazy-connect [default: '']
* * db_connection_options: An array of driver-specific connection options [default: []]
*
* @param \PDO|Connection|string $connOrDsn a \PDO or Connection instance or DSN string or null
*
* @throws InvalidArgumentException When first argument is not PDO nor Connection nor string
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
* @throws InvalidArgumentException When namespace contains invalid characters
*/
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
{
$this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
}
}

View file

@ -0,0 +1,332 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Component\Cache\Traits\PhpArrayTrait;
use Symfony\Contracts\Cache\CacheInterface;
/**
* Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
* Warmed up items are read-only and run-time discovered items are cached using a fallback adapter.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
{
use PhpArrayTrait;
use ContractsTrait;
private $createCacheItem;
/**
* @param string $file The PHP file were values are cached
* @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit
*/
public function __construct(string $file, AdapterInterface $fallbackPool)
{
$this->file = $file;
$this->pool = $fallbackPool;
$this->createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) {
$item = new CacheItem();
$item->key = $key;
$item->value = $value;
$item->isHit = $isHit;
return $item;
},
null,
CacheItem::class
);
}
/**
* This adapter takes advantage of how PHP stores arrays in its latest versions.
*
* @param string $file The PHP file were values are cached
* @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit
*
* @return CacheItemPoolInterface
*/
public static function create($file, CacheItemPoolInterface $fallbackPool)
{
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
return new static($file, $fallbackPool);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
if (null === $this->values) {
$this->initialize();
}
if (!isset($this->keys[$key])) {
get_from_pool:
if ($this->pool instanceof CacheInterface) {
return $this->pool->get($key, $callback, $beta, $metadata);
}
return $this->doGet($this->pool, $key, $callback, $beta, $metadata);
}
$value = $this->values[$this->keys[$key]];
if ('N;' === $value) {
return null;
}
try {
if ($value instanceof \Closure) {
return $value();
}
} catch (\Throwable $e) {
unset($this->keys[$key]);
goto get_from_pool;
}
return $value;
}
/**
* {@inheritdoc}
*/
public function getItem($key)
{
if (!\is_string($key)) {
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
if (null === $this->values) {
$this->initialize();
}
if (!isset($this->keys[$key])) {
return $this->pool->getItem($key);
}
$value = $this->values[$this->keys[$key]];
$isHit = true;
if ('N;' === $value) {
$value = null;
} elseif ($value instanceof \Closure) {
try {
$value = $value();
} catch (\Throwable $e) {
$value = null;
$isHit = false;
}
}
$f = $this->createCacheItem;
return $f($key, $value, $isHit);
}
/**
* {@inheritdoc}
*/
public function getItems(array $keys = [])
{
foreach ($keys as $key) {
if (!\is_string($key)) {
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
}
if (null === $this->values) {
$this->initialize();
}
return $this->generateItems($keys);
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function hasItem($key)
{
if (!\is_string($key)) {
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
if (null === $this->values) {
$this->initialize();
}
return isset($this->keys[$key]) || $this->pool->hasItem($key);
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItem($key)
{
if (!\is_string($key)) {
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
if (null === $this->values) {
$this->initialize();
}
return !isset($this->keys[$key]) && $this->pool->deleteItem($key);
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItems(array $keys)
{
$deleted = true;
$fallbackKeys = [];
foreach ($keys as $key) {
if (!\is_string($key)) {
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
if (isset($this->keys[$key])) {
$deleted = false;
} else {
$fallbackKeys[] = $key;
}
}
if (null === $this->values) {
$this->initialize();
}
if ($fallbackKeys) {
$deleted = $this->pool->deleteItems($fallbackKeys) && $deleted;
}
return $deleted;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function save(CacheItemInterface $item)
{
if (null === $this->values) {
$this->initialize();
}
return !isset($this->keys[$item->getKey()]) && $this->pool->save($item);
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
if (null === $this->values) {
$this->initialize();
}
return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function commit()
{
return $this->pool->commit();
}
private function generateItems(array $keys): \Generator
{
$f = $this->createCacheItem;
$fallbackKeys = [];
foreach ($keys as $key) {
if (isset($this->keys[$key])) {
$value = $this->values[$this->keys[$key]];
if ('N;' === $value) {
yield $key => $f($key, null, true);
} elseif ($value instanceof \Closure) {
try {
yield $key => $f($key, $value(), true);
} catch (\Throwable $e) {
yield $key => $f($key, null, false);
}
} else {
yield $key => $f($key, $value, true);
}
} else {
$fallbackKeys[] = $key;
}
}
if ($fallbackKeys) {
yield from $this->pool->getItems($fallbackKeys);
}
}
/**
* @throws \ReflectionException When $class is not found and is required
*
* @internal to be removed in Symfony 5.0
*/
public static function throwOnRequiredClass($class)
{
$e = new \ReflectionException("Class $class does not exist");
$trace = debug_backtrace();
$autoloadFrame = [
'function' => 'spl_autoload_call',
'args' => [$class],
];
$i = 1 + array_search($autoloadFrame, $trace, true);
if (isset($trace[$i]['function']) && !isset($trace[$i]['class'])) {
switch ($trace[$i]['function']) {
case 'get_class_methods':
case 'get_class_vars':
case 'get_parent_class':
case 'is_a':
case 'is_subclass_of':
case 'class_exists':
case 'class_implements':
case 'class_parents':
case 'trait_exists':
case 'defined':
case 'interface_exists':
case 'method_exists':
case 'property_exists':
case 'is_callable':
return;
}
}
throw $e;
}
}

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Exception\CacheException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\PhpFilesTrait;
class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
{
use PhpFilesTrait;
/**
* @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
* Doing so is encouraged because it fits perfectly OPcache's memory model.
*
* @throws CacheException if OPcache is not enabled
*/
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
{
$this->appendOnly = $appendOnly;
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
$this->includeHandler = static function ($type, $msg, $file, $line) {
throw new \ErrorException($msg, 0, $type, $file, $line);
};
}
}

View file

@ -0,0 +1,269 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Component\Cache\Traits\ProxyTrait;
use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
{
use ProxyTrait;
use ContractsTrait;
private $namespace;
private $namespaceLen;
private $createCacheItem;
private $setInnerItem;
private $poolHash;
public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0)
{
$this->pool = $pool;
$this->poolHash = $poolHash = spl_object_hash($pool);
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace);
$this->namespaceLen = \strlen($namespace);
$this->createCacheItem = \Closure::bind(
static function ($key, $innerItem) use ($defaultLifetime, $poolHash) {
$item = new CacheItem();
$item->key = $key;
if (null === $innerItem) {
return $item;
}
$item->value = $v = $innerItem->get();
$item->isHit = $innerItem->isHit();
$item->innerItem = $innerItem;
$item->defaultLifetime = $defaultLifetime;
$item->poolHash = $poolHash;
// Detect wrapped values that encode for their expiry and creation duration
// For compactness, these values are packed in the key of an array using
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
$item->value = $v[$k];
$v = unpack('Ve/Nc', substr($k, 1, -1));
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
} elseif ($innerItem instanceof CacheItem) {
$item->metadata = $innerItem->metadata;
}
$innerItem->set(null);
return $item;
},
null,
CacheItem::class
);
$this->setInnerItem = \Closure::bind(
/**
* @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix
*/
static function (CacheItemInterface $innerItem, array $item) {
// Tags are stored separately, no need to account for them when considering this item's newly set metadata
if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) {
unset($metadata[CacheItem::METADATA_TAGS]);
}
if ($metadata) {
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
$item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]];
}
$innerItem->set($item["\0*\0value"]);
$innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6f', $item["\0*\0expiry"])) : null);
},
null,
CacheItem::class
);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
if (!$this->pool instanceof CacheInterface) {
return $this->doGet($this, $key, $callback, $beta, $metadata);
}
return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) {
$item = ($this->createCacheItem)($key, $innerItem);
$item->set($value = $callback($item, $save));
($this->setInnerItem)($innerItem, (array) $item);
return $value;
}, $beta, $metadata);
}
/**
* {@inheritdoc}
*/
public function getItem($key)
{
$f = $this->createCacheItem;
$item = $this->pool->getItem($this->getId($key));
return $f($key, $item);
}
/**
* {@inheritdoc}
*/
public function getItems(array $keys = [])
{
if ($this->namespaceLen) {
foreach ($keys as $i => $key) {
$keys[$i] = $this->getId($key);
}
}
return $this->generateItems($this->pool->getItems($keys));
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function hasItem($key)
{
return $this->pool->hasItem($this->getId($key));
}
/**
* {@inheritdoc}
*
* @param string $prefix
*
* @return bool
*/
public function clear(/*string $prefix = ''*/)
{
$prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
if ($this->pool instanceof AdapterInterface) {
return $this->pool->clear($this->namespace.$prefix);
}
return $this->pool->clear();
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItem($key)
{
return $this->pool->deleteItem($this->getId($key));
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItems(array $keys)
{
if ($this->namespaceLen) {
foreach ($keys as $i => $key) {
$keys[$i] = $this->getId($key);
}
}
return $this->pool->deleteItems($keys);
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function save(CacheItemInterface $item)
{
return $this->doSave($item, __FUNCTION__);
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
return $this->doSave($item, __FUNCTION__);
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function commit()
{
return $this->pool->commit();
}
private function doSave(CacheItemInterface $item, string $method)
{
if (!$item instanceof CacheItem) {
return false;
}
$item = (array) $item;
if (null === $item["\0*\0expiry"] && 0 < $item["\0*\0defaultLifetime"]) {
$item["\0*\0expiry"] = microtime(true) + $item["\0*\0defaultLifetime"];
}
if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) {
$innerItem = $item["\0*\0innerItem"];
} elseif ($this->pool instanceof AdapterInterface) {
// this is an optimization specific for AdapterInterface implementations
// so we can save a round-trip to the backend by just creating a new item
$f = $this->createCacheItem;
$innerItem = $f($this->namespace.$item["\0*\0key"], null);
} else {
$innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]);
}
($this->setInnerItem)($innerItem, $item);
return $this->pool->$method($innerItem);
}
private function generateItems(iterable $items)
{
$f = $this->createCacheItem;
foreach ($items as $key => $item) {
if ($this->namespaceLen) {
$key = substr($key, $this->namespaceLen);
}
yield $key => $f($key, $item);
}
}
private function getId($key): string
{
CacheItem::validateKey($key);
return $this->namespace.$key;
}
}

View file

@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ProxyTrait;
/**
* Turns a PSR-16 cache into a PSR-6 one.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
{
/**
* @internal
*/
protected const NS_SEPARATOR = '_';
use ProxyTrait;
private $miss;
public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0)
{
parent::__construct($namespace, $defaultLifetime);
$this->pool = $pool;
$this->miss = new \stdClass();
}
/**
* {@inheritdoc}
*/
protected function doFetch(array $ids)
{
foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
if ($this->miss !== $value) {
yield $key => $value;
}
}
}
/**
* {@inheritdoc}
*/
protected function doHave($id)
{
return $this->pool->has($id);
}
/**
* {@inheritdoc}
*/
protected function doClear($namespace)
{
return $this->pool->clear();
}
/**
* {@inheritdoc}
*/
protected function doDelete(array $ids)
{
return $this->pool->deleteMultiple($ids);
}
/**
* {@inheritdoc}
*/
protected function doSave(array $values, $lifetime)
{
return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
}
}

View file

@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\RedisTrait;
class RedisAdapter extends AbstractAdapter
{
use RedisTrait;
/**
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient The redis client
* @param string $namespace The default namespace
* @param int $defaultLifetime The default lifetime
*/
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
}
}

Some files were not shown because too many files have changed in this diff Show more