Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
mhetru committed Oct 6, 2016
0 parents commit c45ffc4
Show file tree
Hide file tree
Showing 12 changed files with 424 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
composer.lock
vendor/
16 changes: 16 additions & 0 deletions Controller/LogoutController.php
@@ -0,0 +1,16 @@
<?php

namespace L3\Bundle\CasBundle\Controller;


use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class LogoutController extends Controller {
public function logoutAction() {
if(array_key_exists('casLogoutTarget', $this->container->getParameter('cas'))) {
\phpCas::logoutWithRedirectService($this->container->getParameter('cas')['casLogoutTarget']);
} else {
\phpCAS::logout();
}
}
}
36 changes: 36 additions & 0 deletions DependencyInjection/Configuration.php
@@ -0,0 +1,36 @@
<?php

namespace L3\Bundle\CasBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
* 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/extension.html#cookbook-bundles-extension-config-class}
*/
class Configuration implements ConfigurationInterface
{
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('l3_cas');

$rootNode
->children()
->scalarNode('host')->defaultValue(300)->end()
->scalarNode('path')->defaultValue('')->end()
->scalarNode('port')->defaultValue(443)->end()
->scalarNode('ca')->defaultNull()->end()
->booleanNode('handleLogoutRequest')->defaultValue(false)->end()
->scalarNode('casLogoutTarget')->defaultNull()->end()
->booleanNode('force')->defaultValue(true)->end()
->end();

return $treeBuilder;
}
}
30 changes: 30 additions & 0 deletions DependencyInjection/L3CasExtension.php
@@ -0,0 +1,30 @@
<?php

namespace L3\Bundle\CasBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;

/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class L3CasExtension extends Extension
{
/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);

$container->setParameter('cas', $config);

$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}
17 changes: 17 additions & 0 deletions L3CasBundle.php
@@ -0,0 +1,17 @@
<?php

namespace L3\Bundle\CasBundle;

use L3\Bundle\CasBundle\Security\CasFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class L3CasBundle extends Bundle {
public function build(ContainerBuilder $container) {
parent::build($container);

$extension = $container->getExtension('security');
$extension->addSecurityListenerFactory(new CasFactory());
}

}
82 changes: 82 additions & 0 deletions README.md
@@ -0,0 +1,82 @@
Bundle CAS for Symfony2.

Allow wrappe PHPCas with authentication Symfony2. Support the Single Sign Out in contrary of BeSimpleSSoBundle

Installation of Bundle.
---
Install the Bundle with add this line in your require in composer.json :
```
"l3/cas-bundle": "~1.0"
```
Launch the command **composer update** pour install the package and add the Bundle in AppKernel.php
```
<?php
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
// ...
new L3\Bundle\CasBundle\L3CasBundle(),
);
// ...
}
// ...
}
```

Configuration of the bundle
---
In the configuration files (parameters.yml, config.yml, config_prod.yml...), configure your cas server :
```
l3_cas:
host: cas-test.univ-lille3.fr # Serveur CAS
path: ~ # Chemin de l'application CAS si elle ne se trouve pas à la racine
port: 443 # Port du serveur CAS
ca: false # Définition d'un certificat SSL pour le serveur CAS
handleLogoutRequest: true # Activiation du Single Sign Out (défaut: false)
casLogoutTarget: https://ent-test.univ-lille3.fr # Page de redirection après la déconnexion de l'application
force: false # Permet de checker et non de forcer le CAS, utilisateur : __NO_USER__ si non connecté (Information: Si force désactivé, le Single Sign Out peut ne plus fonctionner).
```

Puis configurer le pare-feu :
```
# app/config/security.yml
security:
providers:
# ...
firewalls:
dev:
pattern: ^/(_(profiler|wdt|error)|css|images|js)/
security: false
l3_firewall:
pattern: ^/
security: true
cas: true # Activation du CAS
```

Configuration of the Single Sign Out
---
In order to use the Single Sign Out, it is recommanded to disable the system of the sessions PHP in Symfony2 :
```
# app/config/config.yml
framework:
# ...
session:
handler_id: ~
save_path: ~
```
**Information :** The bundle check complementary with PHPCas to detect some disconnections requests not fully implemented by PHPCAS (see L3\Bundle\CasBundle\Security\CasListener::checkHandleLogout() for more details)

UserProvider
---
For LDAP users, you can use the LdapUserBundle
8 changes: 8 additions & 0 deletions Resources/config/services.yml
@@ -0,0 +1,8 @@
services:
cas.security.authentication.provider:
class: L3\Bundle\CasBundle\Security\CasProvider
arguments: ['', '']

cas.security.authentication.listener:
class: L3\Bundle\CasBundle\Security\CasListener
arguments: [@security.context, @security.authentication.manager, %cas%]
42 changes: 42 additions & 0 deletions Security/CasFactory.php
@@ -0,0 +1,42 @@
<?php

namespace L3\Bundle\CasBundle\Security;


use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Reference;

class CasFactory implements SecurityFactoryInterface {
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) {
$providerId = 'security.authentication.provider.cas.'.$id;
$container
->setDefinition($providerId, new DefinitionDecorator('cas.security.authentication.provider'))
->replaceArgument(0, new Reference($userProvider));

$listenerId = 'security.authentication.listener.cas.'.$id;
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('cas.security.authentication.listener'));

return array($providerId, $listenerId, $defaultEntryPoint);
}

/**
* Defines the position at which the provider is called.
* Possible values: pre_auth, form, http, and remember_me.
*
* @return string
*/
public function getPosition() {
return 'pre_auth';
}

public function getKey() {
return 'cas';
}

public function addConfiguration(NodeDefinition $builder) {
}

}
109 changes: 109 additions & 0 deletions Security/CasListener.php
@@ -0,0 +1,109 @@
<?php

namespace L3\Bundle\CasBundle\Security;


use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;

class CasListener implements ListenerInterface {
protected $securityContext;
protected $authenticationManager;
protected $config;

public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, $config) {
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
$this->config = $config;
}

public function handle(GetResponseEvent $event) {
if(!isset($_SESSION)) session_start();

\phpCAS::setDebug(false);
\phpCas::client(CAS_VERSION_2_0, $this->getParameter('host'), $this->getParameter('port'), is_null($this->getParameter('path')) ? '' : $this->getParameter('path'), true);
if(is_bool($this->getParameter('ca')) && $this->getParameter('ca') == false) {
\phpCAS::setNoCasServerValidation();
} else {
\phpCAS::setCasServerCACert($this->getParameter('ca'));
}
if($this->getParameter('handleLogoutRequest')) {
if($event->getRequest()->request->has('logoutRequest')) {
$this->checkHandleLogout($event);
}
$logoutRequest = $event->getRequest()->request->get('logoutRequest');

\phpCAS::handleLogoutRequests(true);
} else {
\phpCAS::handleLogoutRequests(false);
}
if($this->getParameter('force')) {
\phpCAS::forceAuthentication();
$force = true;
} else {
$force = false;
if(!isset($_SESSION['cas_user'])) {
$auth = \phpCAS::checkAuthentication();
if($auth) $_SESSION['cas_user'] = \phpCAS::getUser();
else $_SESSION['cas_user'] = false;
}
}

if(!$force) {
if(!$_SESSION['cas_user']) {
$token = new CasToken(array('ROLE_ANON'));
$token->setUser('__NO_USER__');
} else {
$token = new CasToken();
$token->setUser($_SESSION['cas_user']);
}
$this->securityContext->setToken($this->authenticationManager->authenticate($token));
return;
}

$token = new CasToken();
$token->setUser(\phpCAS::getUser());

try {
$authToken = $this->authenticationManager->authenticate($token);
$this->securityContext->setToken($authToken);
} catch(AuthenticationException $failed) {
$response = new Response();
$response->setStatusCode(403);
$event->setResponse($response);
}
}

public function getParameter($key) {
if(!array_key_exists($key, $this->config)) {
throw new InvalidConfigurationException('l3_cas.' . $key . ' is not defined');
}
return $this->config[$key];
}

/**
* Cette fonction sert à vérifier le global logout, PHPCAS n'arrive en effet pas à le gérer étrangement dans Symfony2
* @param GetResponseEvent $event
*/
public function checkHandleLogout(GetResponseEvent $event) {
// Récupération du paramètre
$logoutRequest = $event->getRequest()->request->get('logoutRequest');
// Les chaines recherchés
$open = '<samlp:SessionIndex>';
$close = '</samlp:SessionIndex>';

// Isolation de la clé de session
$begin = strpos($logoutRequest, $open);
$end = strpos($logoutRequest, $close, $begin);
$sessionID = substr($logoutRequest, $begin+strlen($open), $end-strlen($close)-$begin+1);

// Changement de session et destruction pour forcer l'authentification CAS à la prochaine visite
session_id($sessionID);
session_destroy();
}
}
32 changes: 32 additions & 0 deletions Security/CasProvider.php
@@ -0,0 +1,32 @@
<?php

namespace L3\Bundle\CasBundle\Security;


use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;

class CasProvider implements AuthenticationProviderInterface {
private $userProvider;
private $config;

public function __construct(UserProviderInterface $userProvider) {
$this->userProvider = $userProvider;
}

public function authenticate(TokenInterface $token) {
$user = $this->userProvider->loadUserByUsername($token->getUsername());

$authenticatedToken = new CasToken($user->getRoles());
$authenticatedToken->setUser($user);



return $authenticatedToken;
}

public function supports(TokenInterface $token) {
return $token instanceof CasToken;
}
}

0 comments on commit c45ffc4

Please sign in to comment.