├── .gitignore ├── .travis.yml ├── BeSimpleSsoAuthBundle.php ├── Buzz └── AdaptiveClient.php ├── Controller ├── Controller.php ├── OpenSsoController.php └── TrustedSsoController.php ├── DependencyInjection ├── BeSimpleSsoAuthExtension.php ├── Compiler │ └── FactoryPass.php ├── Configuration.php └── Security │ └── Factory │ ├── AbstractSsoFactory.php │ ├── OpenSsoFactory.php │ └── TrustedSsoFactory.php ├── Exception ├── ConfigNotFoundException.php ├── InvalidConfigurationException.php ├── NotFoundException.php ├── ProtocolNotFoundException.php └── ServerNotFoundException.php ├── README.md ├── Resources ├── config │ ├── cas.xml │ ├── factory.xml │ └── security_listeners.xml ├── doc │ ├── example.md │ ├── fosuser.md │ ├── index.md │ ├── install.md │ ├── protocols.md │ └── trusted.md ├── meta │ └── LICENSE └── views │ └── TrustedSso │ ├── login.html.twig │ └── logout.html.twig ├── Security ├── Core │ ├── Authentication │ │ ├── Provider │ │ │ └── SsoAuthenticationProvider.php │ │ └── Token │ │ │ └── SsoToken.php │ └── User │ │ ├── SpawnedUserProvider.php │ │ └── UserFactoryInterface.php └── Http │ ├── Authentication │ └── SsoAuthenticationFailureHandler.php │ ├── EntryPoint │ └── TrustedSsoAuthenticationEntryPoint.php │ ├── Firewall │ └── TrustedSsoAuthenticationListener.php │ └── Logout │ ├── SsoLogoutHandler.php │ └── SsoLogoutSuccessHandler.php ├── Sso ├── AbstractComponent.php ├── AbstractProtocol.php ├── AbstractServer.php ├── AbstractValidation.php ├── Cas │ ├── PlainValidation.php │ ├── Protocol.php │ ├── Server.php │ └── XmlValidation.php ├── ComponentInterface.php ├── Factory.php ├── Manager.php ├── ProtocolInterface.php ├── ServerInterface.php └── ValidationInterface.php ├── Tests ├── AppKernel.php ├── Controller │ ├── Controller.php │ ├── Server │ │ ├── CasController.php │ │ └── Controller.php │ ├── TestController.php │ └── TrustedSsoController.php ├── Form │ ├── Login.php │ └── LoginType.php ├── Functional │ ├── AnonTest.php │ ├── LoginTest.php │ └── WebTestCase.php ├── HttpClient.php └── Resources │ ├── config │ ├── cas.yml │ └── common.yml │ ├── routing │ ├── cas.yml │ └── common.yml │ └── views │ ├── cas │ ├── invalid_v1.txt.twig │ ├── invalid_v2.xml.twig │ ├── valid_v1.txt.twig │ └── valid_v2.xml.twig │ ├── common │ ├── link.html.twig │ ├── login.html.twig │ └── message.html.twig │ └── layout.html.twig ├── composer.json └── phpunit.xml.dist /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | phpunit.xml 3 | vendor/* 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | 7 | before_script: "composer install --dev" 8 | -------------------------------------------------------------------------------- /BeSimpleSsoAuthBundle.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class BeSimpleSsoAuthBundle extends Bundle 15 | { 16 | /** 17 | * {@inheritdoc} 18 | */ 19 | public function build(ContainerBuilder $container) 20 | { 21 | parent::build($container); 22 | 23 | $ext = $container->getExtension('security'); 24 | $ext->addSecurityListenerFactory(new TrustedSsoFactory()); 25 | 26 | $container->addCompilerPass(new FactoryPass()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Buzz/AdaptiveClient.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class AdaptiveClient implements ClientInterface 15 | { 16 | private $client; 17 | private $options; 18 | 19 | public function __construct(array $options = array()) 20 | { 21 | $this->options = $options; 22 | $this->client = function_exists('curl_init') ? new Curl() : new FileGetContents(); 23 | } 24 | 25 | public function send(RequestInterface $request, MessageInterface $response) 26 | { 27 | $this->client->send($request, $response, $this->options); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Controller/Controller.php: -------------------------------------------------------------------------------- 1 | container=$container; 23 | } 24 | 25 | protected function render($view, array $parameters) 26 | { 27 | return $this 28 | ->container 29 | ->get('templating') 30 | ->renderResponse($view, $parameters) 31 | ; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Controller/OpenSsoController.php: -------------------------------------------------------------------------------- 1 | render( 14 | 'BeSimpleSsoAuthBundle:TrustedSso:login.html.twig', 15 | array( 16 | 'manager' => $manager, 17 | 'request' => $request, 18 | 'exception' => $exception 19 | ) 20 | ); 21 | } 22 | 23 | public function logoutAction(Manager $manager, Request $request) 24 | { 25 | return $this->render( 26 | 'BeSimpleSsoAuthBundle:TrustedSso:logout.html.twig', 27 | array( 28 | 'manager' => $manager, 29 | 'request' => $request 30 | ) 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DependencyInjection/BeSimpleSsoAuthExtension.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class BeSimpleSsoAuthExtension extends Extension 15 | { 16 | public function load(array $config, ContainerBuilder $container) 17 | { 18 | $processor = new Processor(); 19 | $providers = $processor->processConfiguration(new Configuration($container->getParameter('kernel.debug')), $config); 20 | 21 | foreach ($providers as $id => $provider) { 22 | $container->setParameter(sprintf('be_simple.sso_auth.manager.%s', $id), $provider); 23 | } 24 | 25 | $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); 26 | $loader->load('security_listeners.xml'); 27 | $loader->load('factory.xml'); 28 | $loader->load('cas.xml'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/FactoryPass.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class FactoryPass implements CompilerPassInterface 12 | { 13 | /** 14 | * @param ContainerBuilder $container 15 | */ 16 | public function process(ContainerBuilder $container) 17 | { 18 | if (!$container->has('be_simple.sso_auth.factory')) { 19 | return; 20 | } 21 | 22 | $factoryBuilder = $container->getDefinition('be_simple.sso_auth.factory'); 23 | 24 | foreach ($container->findTaggedServiceIds('be_simple.sso_auth.protocol') as $id => $attributes) { 25 | $factoryBuilder->addMethodCall('addProtocol', array($attributes[0]['id'], $id)); 26 | } 27 | 28 | foreach ($container->findTaggedServiceIds('be_simple.sso_auth.server') as $id => $attributes) { 29 | $factoryBuilder->addMethodCall('addServer', array($attributes[0]['id'], $id)); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Configuration implements ConfigurationInterface 14 | { 15 | /** 16 | * @var bool 17 | */ 18 | private $debug; 19 | 20 | /** 21 | * @param bool $debug 22 | */ 23 | public function __construct($debug) 24 | { 25 | $this->debug = (Boolean) $debug; 26 | } 27 | 28 | /** 29 | * @return TreeBuilder 30 | */ 31 | public function getConfigTreeBuilder() 32 | { 33 | $treeBuilder = new TreeBuilder(); 34 | 35 | $serverDefinition = $treeBuilder 36 | ->root('be_simple_sso_auth') 37 | ->fixXmlConfig('provider') 38 | ->useAttributeAsKey('id') 39 | ->prototype('array'); 40 | 41 | $this->setComponentDefinition($serverDefinition, 'protocol'); 42 | $this->setComponentDefinition($serverDefinition, 'server'); 43 | 44 | return $treeBuilder; 45 | } 46 | 47 | /** 48 | * @param \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition $serverDefinition 49 | * @param string $name 50 | * 51 | * todo: validate component configuration 52 | */ 53 | private function setComponentDefinition(ArrayNodeDefinition $serverDefinition, $name) 54 | { 55 | $serverDefinition 56 | ->children() 57 | ->arrayNode($name) 58 | ->useAttributeAsKey('id') 59 | ->beforeNormalization() 60 | ->ifString()->then(function($value) { 61 | return array('id' => $value); 62 | }) 63 | ->end() 64 | ->prototype('scalar'); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /DependencyInjection/Security/Factory/AbstractSsoFactory.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | abstract class AbstractSsoFactory extends AbstractFactory 15 | { 16 | public function __construct() 17 | { 18 | $this->addOption('create_users', false); 19 | $this->addOption('created_users_roles', array('ROLE_USER')); 20 | $this->addOption('login_action', 'BeSimpleSsoAuthBundle:TrustedSso:login'); 21 | $this->addOption('logout_action', 'BeSimpleSsoAuthBundle:TrustedSso:logout'); 22 | } 23 | 24 | public function create(ContainerBuilder $container, $id, $config, $userProviderId, $defaultEntryPointId) 25 | { 26 | $this->createLogoutSuccessHandler($container, $id, $config); 27 | 28 | return parent::create($container, $id, $config, $userProviderId, $defaultEntryPointId); 29 | } 30 | 31 | public function getPosition() 32 | { 33 | return 'form'; 34 | } 35 | 36 | protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId) 37 | { 38 | $provider = 'security.authentication.provider.sso.'.$id; 39 | 40 | $container 41 | ->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.sso')) 42 | ->replaceArgument(0, new Reference($userProviderId)) 43 | ->replaceArgument(2, $config['create_users']) 44 | ->replaceArgument(3, $config['created_users_roles']) 45 | ; 46 | 47 | return $provider; 48 | } 49 | 50 | public function addConfiguration(NodeDefinition $node) 51 | { 52 | parent::addConfiguration($node); 53 | 54 | $node 55 | ->children() 56 | ->arrayNode('created_users_roles') 57 | ->prototype('scalar')->end() 58 | ->end() 59 | ->end() 60 | ; 61 | } 62 | 63 | protected function createLogoutSuccessHandler(ContainerBuilder $container, $id, $config) 64 | { 65 | $templateHandler = 'security.logout.sso.success_handler'; 66 | $realHandler = 'security.logout.success_handler'; 67 | 68 | // don't know if this is the right way, but it works 69 | $container 70 | ->setDefinition($realHandler.'.'.$id, new DefinitionDecorator($templateHandler)) 71 | ->addArgument($config) 72 | ; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /DependencyInjection/Security/Factory/OpenSsoFactory.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class OpenSsoFactory extends AbstractSsoFactory 11 | { 12 | public function __construct() 13 | { 14 | parent::__construct(); 15 | } 16 | 17 | public function getKey() 18 | { 19 | return 'open_sso'; 20 | } 21 | 22 | protected function getListenerId() 23 | { 24 | return 'security.authentication.listener.open_sso'; 25 | } 26 | 27 | protected function createEntryPoint($container, $id, $config, $defaultEntryPoint) 28 | { 29 | $entryPointId = 'security.authentication.open_sso_entry_point.'.$id; 30 | 31 | $container 32 | ->setDefinition($entryPointId, new DefinitionDecorator('security.authentication.open_sso_entry_point')) 33 | ->addArgument($config) 34 | ; 35 | 36 | return $entryPointId; 37 | } 38 | 39 | protected function createListener($container, $id, $config, $userProvider) 40 | { 41 | $listenerId = parent::createListener($container, $id, $config, $userProvider); 42 | 43 | $container 44 | ->getDefinition($listenerId) 45 | ->replaceArgument(5, $config) 46 | ; 47 | 48 | return $listenerId; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /DependencyInjection/Security/Factory/TrustedSsoFactory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class TrustedSsoFactory extends AbstractSsoFactory 12 | { 13 | public function __construct() 14 | { 15 | parent::__construct(); 16 | 17 | $this->addOption('manager'); 18 | } 19 | 20 | public function getKey() 21 | { 22 | return 'trusted_sso'; 23 | } 24 | 25 | protected function getListenerId() 26 | { 27 | return 'security.authentication.listener.trusted_sso'; 28 | } 29 | 30 | protected function createEntryPoint($container, $id, $config, $defaultEntryPoint) 31 | { 32 | $entryPointId = 'security.authentication.trusted_sso_entry_point.'.$id; 33 | 34 | $container 35 | ->setDefinition($entryPointId, new DefinitionDecorator('security.authentication.trusted_sso_entry_point')) 36 | ->addArgument($config) 37 | ; 38 | 39 | return $entryPointId; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Exception/ConfigNotFoundException.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class ConfigNotFoundException extends NotFoundException 9 | { 10 | public function __construct($id, $code = null, $previous = null) 11 | { 12 | parent::__construct($id, 'Config', $code, $previous); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Exception/InvalidConfigurationException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class InvalidConfigurationException extends BaseException 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /Exception/NotFoundException.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class NotFoundException extends \InvalidArgumentException 9 | { 10 | public function __construct($id, $subject = 'Service', $code = null, $previous = null) 11 | { 12 | parent::__construct(sprintf('[BeSimpleSsoAuthBundle] %s "%s" is not defined.', $subject, $id), $code, $previous); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Exception/ProtocolNotFoundException.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class ProtocolNotFoundException extends NotFoundException 9 | { 10 | public function __construct($id, $code = null, $previous = null) 11 | { 12 | parent::__construct($id, 'Protocol', $code, $previous); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Exception/ServerNotFoundException.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class ServerNotFoundException extends NotFoundException 9 | { 10 | public function __construct($id, $code = null, $previous = null) 11 | { 12 | parent::__construct($id, 'Server', $code, $previous); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SSO authentication for Symfony2 2 | =============================== 3 | 4 | 5 | This bundle helps you to bring SSO authentication to your Symfony2 project. 6 | 7 | 8 | It works in two ways: 9 | 10 | - **trusted**: authentication is done against a known server (like with CAS) 11 | - **open**: authentication is done with server of user's choice (like with OpenId) 12 | 13 | 14 | Only CAS protocol is implemented for now, many other are planned. 15 | 16 | 17 | - [Read documentation](https://github.com/BeSimple/BeSimpleSsoAuthBundle/blob/master/Resources/doc/index.md) 18 | - [See the license](https://github.com/BeSimple/BeSimpleSsoAuthBundle/blob/master/Resources/meta/LICENSE) 19 | -------------------------------------------------------------------------------- /Resources/config/cas.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | BeSimple\SsoAuthBundle\Sso\Cas\Protocol 9 | BeSimple\SsoAuthBundle\Sso\Cas\Server 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Resources/config/factory.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | BeSimple\SsoAuthBundle\Sso\Factory 9 | BeSimple\SsoAuthBundle\Buzz\AdaptiveClient 10 | 64 11 | TRUE 12 | 32 13 | 3 14 | 10004 15 | 16 | 13 17 | 5 18 | 19 | 20 | 21 | 22 | 23 | %be_simple.sso_auth.client.option.curlopt_ssl_verifypeer.value% 24 | %be_simple.sso_auth.client.option.curlopt_sslversion.value% 25 | %be_simple.sso_auth.client.option.curlopt_proxy.value% 26 | %be_simple.sso_auth.client.option.curlopt_timeout.value% 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Resources/config/security_listeners.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | BeSimple\SsoAuthBundle\Security\Http\EntryPoint\TrustedSsoAuthenticationEntryPoint 9 | BeSimple\SsoAuthBundle\Security\Core\Authentication\Provider\SsoAuthenticationProvider 10 | BeSimple\SsoAuthBundle\Security\Http\Firewall\TrustedSsoAuthenticationListener 11 | BeSimple\SsoAuthBundle\Security\Http\Logout\SsoLogoutHandler 12 | BeSimple\SsoAuthBundle\Security\Http\Logout\SsoLogoutSuccessHandler 13 | BeSimple\SsoAuthBundle\Security\Http\Authentication\SsoAuthenticationFailureHandler 14 | FALSE 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | %security.authentication.hide_user_not_found% 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Resources/doc/example.md: -------------------------------------------------------------------------------- 1 | Authentication through SSO CAS Server with Symfony2 2 | =================================================== 3 | 4 | - use the Bundle : BeSimpleSsoAuthBundle (install with Composer) 5 | - be careful on dependences : Buzz needs a recent version of libcurl (7.19 ??) 6 | 7 | 8 | Configure SSO 9 | ------------- 10 | 11 | In config.yml: 12 | 13 | be_simple_sso_auth: 14 | admin_sso: 15 | protocol: 16 | id: cas 17 | version: 2 18 | server: 19 | id: cas 20 | login_url: https://cas.server.tld/login 21 | logout_url: https://cas.server.tld/logout 22 | validation_url: https://cas.server.tld/serviceValidate 23 | 24 | 25 | 26 | Create a firewall 27 | ----------------- 28 | 29 | # app/config/security.yml 30 | my_firewall: 31 | pattern: ^/ 32 | anonymous: ~ 33 | trusted_sso: 34 | manager: admin_sso 35 | login_action: false # BeSimpleSsoAuthBundle:TrustedSso:login 36 | logout_action: false # BeSimpleSsoAuthBundle:TrustedSso:logout 37 | create_users: true 38 | created_users_roles: [ROLE_USER ] 39 | check_path: / 40 | 41 | 42 | Create all routes (mandatory even if there is no controller) 43 | ------------------------------------------------------------ 44 | 45 | # app/config/routing.yml 46 | login: 47 | pattern: /login 48 | logout: 49 | pattern: /logout 50 | 51 | 52 | Providers 53 | --------- 54 | 55 | Example with Propel: 56 | 57 | providers: 58 | administrators: 59 | propel: 60 | class: Altern\CdtBundle\Model\User 61 | property: username 62 | 63 | The propel User Class must implement \Symfony\Component\Security\Core\User\UserInterface 64 | 65 | Customize the "Username does not exist" error page 66 | -------------------------------------------------- 67 | 68 | When a user successfully authenticates, but is not in the user provider's data store (or a user provider is not configured at all), 69 | then a generic error page is shown indicating that the user was not found. You can customize this error page by overriding the Twig error template, 70 | as described here: http://symfony.com/doc/current/cookbook/controller/error_pages.html 71 | 72 | If necessary, you can disable SSL Certificate Verification 73 | ---------------------------------------------------------- 74 | 75 | This is handy when using a development server that does not have a valid certificate, but it should not be done in production. 76 | 77 | # app/config/parameters.yml 78 | be_simple.sso_auth.client.option.curlopt_ssl_verifypeer.value: FALSE 79 | -------------------------------------------------------------------------------- /Resources/doc/fosuser.md: -------------------------------------------------------------------------------- 1 | Using BeSimpleSsoAuthBundle with FosUserBundle 2 | ====================================== 3 | 4 | In order to use FosUserBundle in conjunction with BeSimpleSsoAuthBundle, you need to add the provider directive in security.yml 5 | 6 | ``` 7 | // other directives 8 | 9 | providers: 10 | fos_userbundle: 11 | id: fos_user.user_provider.username 12 | 13 | // other directives 14 | 15 | firewalls: 16 | main: 17 | ... 18 | provider: fos_userbundle 19 | .... 20 | ``` 21 | 22 | Then you can create a user with the same username as your SSO. 23 | -------------------------------------------------------------------------------- /Resources/doc/index.md: -------------------------------------------------------------------------------- 1 | BeSimpleSsoAuthBundle documentation summary 2 | =========================================== 3 | 4 | 5 | The basics 6 | ---------- 7 | 8 | - [Installation & setup](install.md) 9 | - [Configure trusted SSO](trusted.md) 10 | - [Using FosUserBundle as user provider](fosuser.md) 11 | - Configure open SSO (*to come*) 12 | 13 | 14 | Advanced use 15 | ------------ 16 | 17 | - Customize SSO server (*to come*) 18 | - Add a protocol (*to come*) 19 | - Manage users (*to come*) 20 | 21 | 22 | Some notes 23 | ---------- 24 | 25 | - [Some SSO protocols](protocols.md) 26 | - [Example] (example.md) 27 | -------------------------------------------------------------------------------- /Resources/doc/install.md: -------------------------------------------------------------------------------- 1 | Get started with BeSimpleSsoAuthBundle 2 | ====================================== 3 | 4 | 5 | Get the sources, enable bundle & run the tests. 6 | 7 | 8 | Install bundle & dependency 9 | --------------------------- 10 | 11 | Add the requirement in your `composer.json` file: 12 | 13 | { 14 | "require": { 15 | "besimple/sso-auth-bundle": "@dev" 16 | } 17 | } 18 | 19 | Then run `composer update besimple/sso-auth-bundle`. 20 | 21 | Enable bundle & dependency 22 | -------------------------- 23 | 24 | 25 | Add bundle to your kernel class: 26 | 27 | // app/AppKernel.php 28 | $bundles = array( 29 | // ... 30 | new BeSimple\SsoAuthBundle\BeSimpleSsoAuthBundle(), 31 | // ... 32 | ); 33 | 34 | 35 | Add bundle to your config file: 36 | 37 | # app/config/config.yml 38 | be_simple_sso_auth: ~ 39 | -------------------------------------------------------------------------------- /Resources/doc/protocols.md: -------------------------------------------------------------------------------- 1 | SSO protocols 2 | ============= 3 | 4 | 5 | **Currently implemented:** 6 | 7 | - CAS: http://www.jasig.org/cas 8 | 9 | 10 | **Planed implementations:** 11 | 12 | - CoSign: http://cosign.sourceforge.net/ 13 | - WebAuth: http://webauth.stanford.edu/ 14 | - OpenID: http://openid.net/ 15 | - OAuth: http://oauth.net/ 16 | 17 | 18 | **Other systems:** 19 | 20 | - LDAP: http://en.wikipedia.org/wiki/LDAP 21 | - JOSSO: http://www.josso.org/confluence/display/JOSSO1/JOSSO+-+Java+Open+Single+Sign-On+Project+Home 22 | - OpenAM: http://www.forgerock.com/openam.html 23 | - PubCookie: http://www.pubcookie.org/ 24 | -------------------------------------------------------------------------------- /Resources/doc/trusted.md: -------------------------------------------------------------------------------- 1 | BeSimpleSsoAuthBundle trusted SSO configuration 2 | =============================================== 3 | 4 | 5 | Firewall configuration 6 | ---------------------- 7 | 8 | 9 | **Config entries:** 10 | 11 | BeSimpleSsoAuthBundle adds 5 configuration entries to standard (form_login) firewall which are: 12 | 13 | - `manager`: name of the manager configured under `be_simple_sso_auth` config section (see below). 14 | - `login_action`: when login required, user is forwarded to this action (which by default tell him to 15 | follow given link to authenticate). Set to `false` to auto-redirect user to SSO login form. 16 | - `logout_action`: same as `login_action`, but for logout. 17 | - `create_users`: authorize user provider to create not found users if implementing UserFactoryInterface. 18 | - `created_users_roles`: an array of roles to assign to users created by user provider. 19 | 20 | Other required configuration entries are: 21 | 22 | - `login_path`: path to redirect to when login needed. 23 | - `check_path`: path to redirect to when checking login informations. 24 | 25 | Other optional configuration entries are: 26 | 27 | - `always_use_default_target_path` (default false): if true, always redirect to default target path when logged in. 28 | - `default_target_path` (default '/'): target path to redirect to when `always_use_default_target_path` is true. 29 | - `target_path_parameter` (default '_target_path'): 30 | - `use_referer` (default false): 31 | - `failure_path` (default null): go to this path on failure. 32 | - `failure_forward` (default false): forward (true) or redirect (false) to failure path. 33 | 34 | 35 | **An example in YAML format:** 36 | 37 | # security.yml 38 | 39 | security: 40 | firewalls: 41 | my_firewall: 42 | pattern: ^/admin/.*$ 43 | trusted_sso: 44 | manager: admin_sso 45 | login_action: BeSimpleSsoAuthBundle:TrustedSso:login 46 | logout_action: BeSimpleSsoAuthBundle:TrustedSso:logout 47 | create_users: true 48 | created_users_roles: [ROLE_USER, ROLE_ADMIN] 49 | 50 | 51 | Manager configuration 52 | --------------------- 53 | 54 | 55 | Now you must configure your `my_manager` manager. 56 | 57 | 58 | **An example in YAML format:** 59 | 60 | # config.yml 61 | 62 | be_simple_sso_auth: 63 | admin_sso: 64 | protocol: 65 | id: cas 66 | version: 2 67 | server: 68 | id: cas 69 | login_url: http://cas.server.tld/login 70 | logout_url: http://cas.server.tld/logout 71 | validation_url: http://cas.server.tld/serviceValidate 72 | -------------------------------------------------------------------------------- /Resources/meta/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Jean-François Simon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Resources/views/TrustedSso/login.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "::base.html.twig" %} 2 | 3 | {% block body %} 4 | 5 |

You need to authenticate

6 | 7 | {% if exception %} 8 |

{{ exception.message }}

9 | {% endif %} 10 | 11 |

12 | Follow this link to authenticate with external server : 13 | {{ manager.server.loginUrl }}. 14 |

15 | 16 | {% endblock body %} 17 | -------------------------------------------------------------------------------- /Resources/views/TrustedSso/logout.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "::base.html.twig" %} 2 | 3 | {% block body %} 4 | 5 |

You're about to logout

6 | 7 |

8 | Follow this link to logout from external server : 9 | {{ manager.server.logoutUrl }}. 10 |

11 | 12 | {% endblock body %} 13 | -------------------------------------------------------------------------------- /Security/Core/Authentication/Provider/SsoAuthenticationProvider.php: -------------------------------------------------------------------------------- 1 | userProvider = $userProvider; 59 | $this->userChecker = $userChecker; 60 | $this->createUsers = $createUsers; 61 | $this->createdUsersRoles = $createdUsersRoles; 62 | $this->hideUserNotFound = $hideUserNotFound; 63 | } 64 | 65 | /** 66 | * @throws \Symfony\Component\Security\Core\Exception\AuthenticationServiceException 67 | * 68 | * @param \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token 69 | * 70 | * @return SsoToken|null 71 | */ 72 | public function authenticate(TokenInterface $token) 73 | { 74 | if (!$this->supports($token)) { 75 | return null; 76 | } 77 | 78 | $validation = $token->getManager()->validateCredentials($token->getCredentials()); 79 | if (!$validation->isSuccess()) { 80 | throw new BadCredentialsException('Authentication has not been validated by SSO provider.'); 81 | } 82 | 83 | $user = $this->provideUser($validation->getUsername(), $validation->getAttributes()); 84 | $this->userChecker->checkPreAuth($user); 85 | $this->userChecker->checkPostAuth($user); 86 | 87 | $authenticatedToken = new SsoToken($token->getManager(), $token->getCredentials(), $user, $user->getRoles(), $validation->getAttributes()); 88 | foreach ($token->getAttributes() as $name => $value) { 89 | if ('sso:validation' == $name) { 90 | continue; 91 | } 92 | $authenticatedToken->setAttribute($name, $value); 93 | } 94 | 95 | return $authenticatedToken; 96 | } 97 | 98 | /** 99 | * {@inheritdoc} 100 | */ 101 | public function supports(TokenInterface $token) 102 | { 103 | return $token instanceof SsoToken; 104 | } 105 | 106 | /** 107 | * @throws \Symfony\Component\Security\Core\Exception\UsernameNotFoundException 108 | * @throws \Symfony\Component\Security\Core\Exception\BadCredentialsException 109 | * 110 | * @param string $username 111 | * @param array $attributes 112 | * 113 | * @return UserInterface 114 | */ 115 | protected function provideUser($username, array $attributes = array()) 116 | { 117 | try { 118 | $user = $this->retrieveUser($username); 119 | } catch (UsernameNotFoundException $notFound) { 120 | if ($this->createUsers && $this->userProvider instanceof UserFactoryInterface) { 121 | $user = $this->createUser($username, $attributes); 122 | } elseif ($this->hideUserNotFound) { 123 | throw new BadCredentialsException('Bad credentials', 0, $notFound); 124 | } else { 125 | throw $notFound; 126 | } 127 | } 128 | 129 | return $user; 130 | } 131 | 132 | /** 133 | * @throws \Symfony\Component\Security\Core\Exception\AuthenticationServiceException 134 | * 135 | * @param string $username 136 | * 137 | * @return UserInterface 138 | */ 139 | protected function retrieveUser($username) 140 | { 141 | try { 142 | $user = $this->userProvider->loadUserByUsername($username); 143 | 144 | if (!$user instanceof UserInterface) { 145 | throw new AuthenticationServiceException('The user provider must return an UserInterface object.'); 146 | } 147 | } catch (UsernameNotFoundException $notFound) { 148 | throw $notFound; 149 | } catch (\Exception $repositoryProblem) { 150 | throw new AuthenticationServiceException($repositoryProblem->getMessage(), 0, $repositoryProblem); 151 | } 152 | 153 | return $user; 154 | } 155 | 156 | /** 157 | * @throws AuthenticationServiceException 158 | * 159 | * @param string $username 160 | * @param array $attributes 161 | * 162 | * @return UserInterface 163 | */ 164 | protected function createUser($username, array $attributes = array()) 165 | { 166 | if (!$this->userProvider instanceof UserFactoryInterface) { 167 | throw new AuthenticationServiceException('UserProvider must implement UserFactoryInterface to create unknown users.'); 168 | } 169 | 170 | try { 171 | $user = $this->userProvider->createUser($username, $this->createdUsersRoles, $attributes); 172 | 173 | if (!$user instanceof UserInterface) { 174 | throw new AuthenticationServiceException('The user provider must create an UserInterface object.'); 175 | } 176 | } catch (\Exception $repositoryProblem) { 177 | throw new AuthenticationServiceException($repositoryProblem->getMessage(), 0, $repositoryProblem); 178 | } 179 | 180 | return $user; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Security/Core/Authentication/Token/SsoToken.php: -------------------------------------------------------------------------------- 1 | manager = $manager; 29 | $this->credentials = $credentials; 30 | 31 | $this->setAttribute('sso:validation', $validationAttributes); 32 | 33 | if (!is_null($user)) { 34 | $this->setUser($user); 35 | 36 | parent::setAuthenticated(true); 37 | } 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function setAuthenticated($isAuthenticated) 44 | { 45 | if ($isAuthenticated) { 46 | throw new \LogicException('Cannot set this token to trusted after instantiation.'); 47 | } 48 | 49 | parent::setAuthenticated(false); 50 | } 51 | 52 | public function getCredentials() 53 | { 54 | return $this->credentials; 55 | } 56 | 57 | public function getManager() 58 | { 59 | return $this->manager; 60 | } 61 | 62 | public function getValidationAttributes() 63 | { 64 | if (!$this->hasAttribute('sso:validation')) { 65 | return array(); 66 | } 67 | 68 | return $this->getAttribute('sso:validation'); 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function eraseCredentials() 75 | { 76 | parent::eraseCredentials(); 77 | 78 | $this->credentials = null; 79 | } 80 | 81 | public function serialize() 82 | { 83 | return serialize(array($this->credentials, $this->manager, parent::serialize())); 84 | } 85 | 86 | public function unserialize($str) 87 | { 88 | list($this->credentials, $this->manager, $parentStr) = unserialize($str); 89 | parent::unserialize($parentStr); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Security/Core/User/SpawnedUserProvider.php: -------------------------------------------------------------------------------- 1 | 12 | * 13 | * This provider just spawn users on demand, affecting them given $roles. 14 | */ 15 | class SpawnedUserProvider implements UserProviderInterface 16 | { 17 | /** 18 | * @var array 19 | */ 20 | private $roles; 21 | 22 | /** 23 | * Constructor. 24 | * 25 | * @param array $roles An array of roles 26 | */ 27 | public function __construct(array $roles = array()) 28 | { 29 | $this->roles = $roles; 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function loadUserByUsername($username) 36 | { 37 | return $this->spawnUser($username); 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | public function refreshUser(UserInterface $user) 44 | { 45 | if (!$user instanceof User) { 46 | throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); 47 | } 48 | 49 | return $this->spawnUser($user->getUsername()); 50 | } 51 | 52 | /** 53 | * {@inheritDoc} 54 | */ 55 | public function supportsClass($class) 56 | { 57 | return $class === 'Symfony\Component\Security\Core\User\User'; 58 | } 59 | 60 | /** 61 | * Spawns a new user with given username. 62 | * 63 | * @param string $username 64 | * 65 | * @return \Symfony\Component\Security\Core\User\User 66 | */ 67 | private function spawnUser($username) 68 | { 69 | return new User($username, null, $this->roles, true, true, true, true); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Security/Core/User/UserFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Interface implemented by user providers able to create new users. 10 | */ 11 | interface UserFactoryInterface 12 | { 13 | /** 14 | * Creates a new user for the given username 15 | * 16 | * @param string $username The username 17 | * @param array $roles Roles assigned to user 18 | * @param array $attributes Attributes provided by SSO server 19 | * 20 | * @return \Symfony\Component\Security\Core\User\UserInterface 21 | */ 22 | public function createUser($username, array $roles, array $attributes); 23 | } 24 | -------------------------------------------------------------------------------- /Security/Http/Authentication/SsoAuthenticationFailureHandler.php: -------------------------------------------------------------------------------- 1 | templating = $templating; 20 | } 21 | 22 | /** 23 | * This is called when an interactive authentication attempt fails. 24 | * 25 | * @param Request $request 26 | * @param AuthenticationException $exception 27 | * 28 | * @return Response 29 | */ 30 | public function onAuthenticationFailure(Request $request, AuthenticationException $exception) 31 | { 32 | if ($request->isXmlHttpRequest()) { 33 | $result = array('success' => false); 34 | return new Response(json_encode($result)); 35 | } else { 36 | // Handle non XmlHttp request. 37 | $parameters = array( 38 | 'status_text' => $exception->getMessage(), 39 | 'status_code' => $exception->getCode(), 40 | ); 41 | 42 | return $this->templating->renderResponse('TwigBundle:Exception:error.html.twig', $parameters); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Security/Http/EntryPoint/TrustedSsoAuthenticationEntryPoint.php: -------------------------------------------------------------------------------- 1 | httpKernel = $httpKernel; 37 | $this->factory = $factory; 38 | $this->config = $config; 39 | } 40 | 41 | /** 42 | * @param Request $request 43 | * @param null|AuthenticationException $authException 44 | * @return Response 45 | */ 46 | public function start(Request $request, AuthenticationException $authException = null) 47 | { 48 | $action = $this->config['login_action']; 49 | $manager = $this->factory->getManager($this->config['manager'], $request->getUriForPath($this->config['check_path'])); 50 | 51 | if ($action) { 52 | $subRequest = $request->duplicate(null, null, array( 53 | '_controller' => $action, 54 | 'manager' => $manager, 55 | 'request' => $request, 56 | 'exception' => $authException, 57 | )); 58 | return $this->httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); 59 | } 60 | 61 | return new RedirectResponse($manager->getServer()->getLoginUrl()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Security/Http/Firewall/TrustedSsoAuthenticationListener.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 16 | } 17 | 18 | protected function attemptAuthentication(Request $request) 19 | { 20 | $manager = $this->factory->getManager($this->options['manager'], $request->getUriForPath($this->options['check_path'])); 21 | 22 | if (!$manager->getProtocol()->isValidationRequest($request)) { 23 | return null; 24 | } 25 | 26 | return $this->authenticationManager->authenticate($manager->createToken($request)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Security/Http/Logout/SsoLogoutHandler.php: -------------------------------------------------------------------------------- 1 | getManager()->processLogout($token); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Security/Http/Logout/SsoLogoutSuccessHandler.php: -------------------------------------------------------------------------------- 1 | httpKernel = $httpKernel; 36 | $this->factory = $factory; 37 | $this->config = $config; 38 | } 39 | 40 | /** 41 | * @param Request $request 42 | * 43 | * @return Response 44 | */ 45 | public function onLogoutSuccess(Request $request) 46 | { 47 | $action = $this->config['logout_action']; 48 | $manager = $this->factory->getManager($this->config['manager'], $request->getUriForPath($this->config['check_path'])); 49 | 50 | if ($action) { 51 | $subRequest = $request->duplicate(null, null, array( 52 | '_controller' => $action, 53 | 'manager' => $manager, 54 | 'request' => $request, 55 | )); 56 | return $this->httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); 57 | } 58 | 59 | return new RedirectResponse($manager->getServer()->getLogoutUrl()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sso/AbstractComponent.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | abstract class AbstractComponent implements ComponentInterface 9 | { 10 | /** 11 | * @var array 12 | */ 13 | protected $config; 14 | 15 | /** 16 | * Constructor. 17 | */ 18 | public function __construct() 19 | { 20 | $this->config = array(); 21 | } 22 | 23 | /** 24 | * Returns server config. 25 | * 26 | * @return array 27 | */ 28 | public function getConfig() 29 | { 30 | return $this->config; 31 | } 32 | 33 | /** 34 | * Setup server configuration. 35 | * 36 | * @param array $config 37 | * 38 | * @return \BeSimple\SsoAuthBundle\Sso\AbstractComponent 39 | */ 40 | public function setConfig(array $config) 41 | { 42 | $this->config = $config; 43 | 44 | return $this; 45 | } 46 | 47 | /** 48 | * Returns a configuration value 49 | * 50 | * @param $name 51 | * @return string|null 52 | */ 53 | public function getConfigValue($name) 54 | { 55 | return array_key_exists($name, $this->config) ? $this->config[$name] : null; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sso/AbstractProtocol.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | abstract class AbstractProtocol extends AbstractComponent implements ProtocolInterface 15 | { 16 | /** 17 | * {@inheritdoc} 18 | */ 19 | public function processLogout(SsoToken $token) 20 | { 21 | // does nothing most of the case 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sso/AbstractServer.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | abstract class AbstractServer extends AbstractComponent implements ServerInterface 12 | { 13 | /** 14 | * {@inheritdoc} 15 | */ 16 | public function getLoginUrl() 17 | { 18 | return $this->getConfigValue('login_url'); 19 | } 20 | 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function getLogoutUrl() 25 | { 26 | return $this->getConfigValue('logout_url'); 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function getLogoutTarget() 33 | { 34 | return $this->getConfigValue('logout_target'); 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function getValidationUrl() 41 | { 42 | return $this->getConfigValue('validation_url'); 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function getCheckUrl() 49 | { 50 | return $this->getConfigValue('check_url'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sso/AbstractValidation.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | abstract class AbstractValidation implements ValidationInterface 11 | { 12 | /** 13 | * Response not validated 14 | */ 15 | const STATUS_NONE = 0; 16 | 17 | /** 18 | * Validation successful 19 | */ 20 | const STATUS_VALID = 1; 21 | 22 | /** 23 | * Validation returns an error 24 | */ 25 | const STATUS_INVALID = -1; 26 | 27 | /** 28 | * @var int 29 | */ 30 | private $status; 31 | 32 | /** 33 | * @var \Buzz\Message\Response 34 | */ 35 | private $response; 36 | 37 | /** 38 | * @var string 39 | */ 40 | private $credentials; 41 | 42 | /** 43 | * @var string 44 | */ 45 | protected $username; 46 | 47 | /** 48 | * @var array 49 | */ 50 | protected $attributes; 51 | 52 | /** 53 | * @var string 54 | */ 55 | protected $error; 56 | 57 | /** 58 | * Constructor. 59 | * 60 | * @param \Buzz\Message\Response $response 61 | * @param string $credentials 62 | */ 63 | public function __construct(Response $response, $credentials) 64 | { 65 | $this->status = self::STATUS_NONE; 66 | $this->response = $response; 67 | $this->credentials = $credentials; 68 | $this->username = ''; 69 | $this->attributes = array(); 70 | $this->error = ''; 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function getResponse() 77 | { 78 | return $this->response; 79 | } 80 | 81 | /** 82 | * {@inheritdoc} 83 | */ 84 | public function isSuccess() 85 | { 86 | if ($this->status === self::STATUS_NONE) { 87 | $this->status = $this->validateResponse($this->response) 88 | ? self::STATUS_VALID 89 | : self::STATUS_INVALID; 90 | } 91 | 92 | return $this->status === self::STATUS_VALID; 93 | } 94 | 95 | /** 96 | * {@inheritdoc} 97 | */ 98 | public function getCredentials() 99 | { 100 | return $this->credentials; 101 | } 102 | 103 | /** 104 | * {@inheritdoc} 105 | */ 106 | public function getUsername() 107 | { 108 | return $this->username; 109 | } 110 | 111 | /** 112 | * {@inheritdoc} 113 | */ 114 | public function getAttributes() 115 | { 116 | return $this->attributes; 117 | } 118 | 119 | /** 120 | * {@inheritdoc} 121 | */ 122 | public function getError() 123 | { 124 | return $this->error; 125 | } 126 | 127 | /** 128 | * {@inheritdoc} 129 | */ 130 | abstract protected function validateResponse(Response $response); 131 | } 132 | -------------------------------------------------------------------------------- /Sso/Cas/PlainValidation.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class PlainValidation extends AbstractValidation implements ValidationInterface 13 | { 14 | /** 15 | * {@inheritdoc} 16 | */ 17 | protected function validateResponse(Response $response) 18 | { 19 | $content = $response->getContent(); 20 | $data = explode("\n", str_replace("\n\n", "\n", str_replace("\r", "\n", $content))); 21 | $success = strtolower($data[0]) === 'yes'; 22 | $message = (count($data) > 1 && $data[1]) ? $data[1] : null; 23 | 24 | if ($success) { 25 | $this->username = $message; 26 | } else { 27 | $this->error = $message; 28 | } 29 | 30 | return $success; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sso/Cas/Protocol.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class Protocol extends AbstractProtocol 17 | { 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function isValidationRequest(SymfonyRequest $request) 22 | { 23 | return $request->query->has('ticket'); 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function extractCredentials(SymfonyRequest $request) 30 | { 31 | return $request->query->get('ticket'); 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function executeValidation(ClientInterface $client, BuzzRequest $request, $credentials) 38 | { 39 | $response = new BuzzResponse(); 40 | $client->send($request, $response); 41 | 42 | switch ($this->getConfigValue('version')) { 43 | case 1: return new PlainValidation($response, $credentials); 44 | case 2: return new XmlValidation($response, $credentials); 45 | } 46 | 47 | throw new InvalidConfigurationException('Version should either be 1 or 2.'); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /Sso/Cas/Server.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Server extends AbstractServer 13 | { 14 | /** 15 | * @return string 16 | */ 17 | public function getLoginUrl() 18 | { 19 | return sprintf('%s?service=%s', parent::getLoginUrl(), urlencode($this->getCheckUrl())); 20 | } 21 | 22 | /** 23 | * @return string 24 | */ 25 | public function getLogoutUrl() 26 | { 27 | $service = sprintf('service=%s', urlencode($this->getCheckUrl())); 28 | $url = $this->getLogoutTarget() ? sprintf('&url=%s', urlencode($this->getLogoutTarget())) : null; 29 | 30 | return sprintf('%s?%s%s', parent::getLogoutUrl(), $service, $url); 31 | } 32 | 33 | /** 34 | * @return string 35 | */ 36 | public function getValidationUrl() 37 | { 38 | return sprintf('%s?service=%s', parent::getValidationUrl(), urlencode($this->getCheckUrl())); 39 | } 40 | 41 | /** 42 | * @param string $credentials 43 | * 44 | * @return \Buzz\Message\Request 45 | */ 46 | public function buildValidationRequest($credentials) 47 | { 48 | $request = new Request(); 49 | $request->fromUrl(sprintf( 50 | '%s&ticket=%s', 51 | $this->getValidationUrl(), 52 | $credentials 53 | )); 54 | 55 | return $request; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Sso/Cas/XmlValidation.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class XmlValidation extends AbstractValidation implements ValidationInterface 13 | { 14 | /** 15 | * {@inheritdoc} 16 | */ 17 | protected function validateResponse(Response $response) 18 | { 19 | $content = $response->getContent(); 20 | $success = false; 21 | $xml = new \DOMDocument(); 22 | 23 | if ($xml->loadXML($content)) { 24 | foreach ($xml->firstChild->childNodes as $child) { 25 | if ($child->nodeName === 'cas:authenticationSuccess') { 26 | $root = $child; 27 | $success = true; 28 | break; 29 | } elseif ($child->nodeName === 'cas:authenticationFailure') { 30 | $root = $child; 31 | $success = false; 32 | break; 33 | } 34 | } 35 | 36 | if ($success) { 37 | foreach ($root->childNodes as $child) { 38 | switch ($child->nodeName) { 39 | 40 | case 'cas:user': 41 | $this->username = $child->textContent; 42 | break; 43 | 44 | case 'cas:attributes': 45 | foreach($child->childNodes as $attr) { 46 | if ($attr->nodeName == 'cas:attribute') { 47 | $this->getCasAttributes($attr); 48 | } else if ($attr->nodeName != '#text') { 49 | $this->attributes[$attr->nodeName] = $attr->textContent; 50 | } 51 | } 52 | break; 53 | 54 | case 'cas:attribute': 55 | $this->getCasAttributes($child); 56 | break; 57 | 58 | case '#text': 59 | break; 60 | 61 | default: 62 | $this->attributes[substr($child->nodeName, 4)] = $child->textContent; 63 | } 64 | } 65 | } else { 66 | $this->error = (string)$root->textContent; 67 | } 68 | 69 | } else { 70 | $success = false; 71 | $this->error = 'Invalid response'; 72 | } 73 | 74 | return $success; 75 | } 76 | 77 | /** 78 | * @param \DOMElement $node 79 | */ 80 | protected function getCasAttributes(\DOMElement $node) 81 | { 82 | $name = $node->attributes->getNamedItem('name')->value; 83 | $value = $node->attributes->getNamedItem('value')->value; 84 | if ($name && $value) { 85 | $this->attributes[$name] = $value; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sso/ComponentInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface ComponentInterface 9 | { 10 | /** 11 | * Setup configuration. 12 | * 13 | * @param array $config 14 | */ 15 | public function setConfig(array $config); 16 | } 17 | -------------------------------------------------------------------------------- /Sso/Factory.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Factory 16 | { 17 | /** 18 | * @var \Symfony\Component\DependencyInjection\ContainerInterface 19 | */ 20 | private $container; 21 | 22 | /** 23 | * @var array 24 | */ 25 | private $servers; 26 | 27 | /** 28 | * @var array 29 | */ 30 | private $protocols; 31 | 32 | /** 33 | * @var array 34 | */ 35 | private $managers; 36 | 37 | /** 38 | * @var \Buzz\Client\ClientInterface 39 | */ 40 | private $client; 41 | 42 | /** 43 | * @param \Symfony\Component\DependencyInjection\ContainerInterface $container 44 | * @param \Buzz\Client\ClientInterface $client 45 | */ 46 | public function __construct(ContainerInterface $container, ClientInterface $client) 47 | { 48 | $this->container = $container; 49 | $this->servers = array(); 50 | $this->protocols = array(); 51 | $this->managers = array(); 52 | $this->client = $client; 53 | } 54 | 55 | /** 56 | * @param string $id 57 | * @param string $service 58 | */ 59 | public function addServer($id, $service) 60 | { 61 | $this->servers[$id] = $service; 62 | } 63 | 64 | /** 65 | * @param string $id 66 | * @param string $service 67 | */ 68 | public function addProtocol($id, $service) 69 | { 70 | $this->protocols[$id] = $service; 71 | } 72 | 73 | /** 74 | * @param string $id 75 | * @param string $checkUrl 76 | * 77 | * @return Manager 78 | */ 79 | public function getManager($id, $checkUrl) 80 | { 81 | if (!isset($this->managers[$id])) { 82 | $this->managers[$id] = $this->createManager($id, $checkUrl); 83 | } 84 | 85 | return $this->managers[$id]; 86 | } 87 | 88 | /** 89 | * @param string $id 90 | * @param string $checkUrl 91 | * 92 | * @return Manager 93 | * 94 | * @throws \BeSimple\SsoAuthBundle\Exception\ConfigNotFoundException 95 | */ 96 | private function createManager($id, $checkUrl) 97 | { 98 | $parameter = sprintf('be_simple.sso_auth.manager.%s', $id); 99 | 100 | if (!$this->container->hasParameter($parameter)) { 101 | throw new ConfigNotFoundException($id); 102 | } 103 | 104 | $config = $this->container->getParameter($parameter); 105 | if (!isset($config['server']['check_url'])) { 106 | $config['server']['check_url'] = $checkUrl; 107 | } 108 | 109 | return new Manager($this->getServer($config['server']), $this->getProtocol($config['protocol']), $this->client); 110 | } 111 | 112 | /** 113 | * @param array $config 114 | * 115 | * @return ServerInterface 116 | * 117 | * @throws \BeSimple\SsoAuthBundle\Exception\ServerNotFoundException 118 | */ 119 | private function getServer(array $config) 120 | { 121 | $id = $config['id']; 122 | 123 | if (!isset($this->servers[$id])) { 124 | throw new ServerNotFoundException($id); 125 | } 126 | 127 | return $this->container->get($this->servers[$id])->setConfig($config); 128 | } 129 | 130 | /** 131 | * @param array $config 132 | * 133 | * @return ProtocolInterface 134 | * 135 | * @throws \BeSimple\SsoAuthBundle\Exception\ProtocolNotFoundException 136 | */ 137 | private function getProtocol(array $config) 138 | { 139 | $id = $config['id']; 140 | 141 | if (!isset($this->protocols[$id])) { 142 | throw new ProtocolNotFoundException($id); 143 | } 144 | 145 | return $this->container->get($this->protocols[$id])->setConfig($config); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Sso/Manager.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Manager 14 | { 15 | /** 16 | * @var \BeSimple\SsoAuthBundle\Sso\ServerInterface 17 | */ 18 | private $server; 19 | 20 | /** 21 | * @var \BeSimple\SsoAuthBundle\Sso\ProtocolInterface 22 | */ 23 | private $protocol; 24 | 25 | /** 26 | * @var \Buzz\Client\ClientInterface 27 | */ 28 | private $client; 29 | 30 | /** 31 | * Constructor. 32 | * 33 | * @param ServerInterface $server 34 | * @param ProtocolInterface $protocol 35 | * @param \Buzz\Client\ClientInterface $client 36 | */ 37 | public function __construct(ServerInterface $server, ProtocolInterface $protocol, ClientInterface $client) 38 | { 39 | $this->server = $server; 40 | $this->protocol = $protocol; 41 | $this->client = $client; 42 | } 43 | 44 | /** 45 | * Validates given credentials. 46 | * 47 | * @param string $credentials 48 | * 49 | * @return ValidationInterface 50 | */ 51 | public function validateCredentials($credentials) 52 | { 53 | $request = $this->server->buildValidationRequest($credentials); 54 | $validation = $this->protocol->executeValidation($this->client, $request, $credentials); 55 | 56 | return $validation; 57 | } 58 | 59 | /** 60 | * Creates a token from the request. 61 | * 62 | * @param \Symfony\Component\HttpFoundation\Request $request 63 | * 64 | * @return \BeSimple\SsoAuthBundle\Security\Core\Authentication\Token\SsoToken 65 | */ 66 | public function createToken(Request $request) 67 | { 68 | return new SsoToken($this, $this->protocol->extractCredentials($request)); 69 | } 70 | 71 | /** 72 | * @return \BeSimple\SsoAuthBundle\Sso\ServerInterface 73 | */ 74 | public function getServer() 75 | { 76 | return $this->server; 77 | } 78 | 79 | /** 80 | * @return \BeSimple\SsoAuthBundle\Sso\ProtocolInterface 81 | */ 82 | public function getProtocol() 83 | { 84 | return $this->protocol; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Sso/ProtocolInterface.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | interface ProtocolInterface extends ComponentInterface 15 | { 16 | /** 17 | * Processes internal logout operations. 18 | * 19 | * @param \BeSimple\SsoAuthBundle\Security\Core\Authentication\Token\SsoToken $token 20 | * 21 | * @return void 22 | */ 23 | public function processLogout(SsoToken $token); 24 | 25 | /** 26 | * Is given request an authentication validation request? 27 | * 28 | * @param \Symfony\Component\HttpFoundation\Request $request 29 | * 30 | * @return bool 31 | */ 32 | public function isValidationRequest(SymfonyRequest $request); 33 | 34 | /** 35 | * Extract credentials from the validation request. 36 | * 37 | * @param \Symfony\Component\HttpFoundation\Request $request 38 | * 39 | * @return string 40 | */ 41 | public function extractCredentials(SymfonyRequest $request); 42 | 43 | /** 44 | * Handles validation request. 45 | * 46 | * Authentication is only effective if the SSO server validates user credentials. 47 | * The application sends a validation request and receive a success or error message. 48 | * 49 | * @param \Buzz\Client\ClientInterface $client 50 | * @param \Buzz\Message\Request $request 51 | * @param string $credentials 52 | * 53 | * @return ValidationInterface A validation object 54 | * 55 | * @throws \BeSimple\SsoAuthBundle\Exception\InvalidConfigurationException 56 | */ 57 | public function executeValidation(ClientInterface $client, BuzzRequest $request, $credentials); 58 | } 59 | -------------------------------------------------------------------------------- /Sso/ServerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface ServerInterface extends ComponentInterface 9 | { 10 | /** 11 | * Returns the login URL of this server. 12 | * 13 | * Depending on the protocol, this URL can point to a login form 14 | * or just an API on which to post login data (or even both). 15 | * 16 | * @return string 17 | */ 18 | public function getLoginUrl(); 19 | 20 | /** 21 | * Returns the logout URL of this server. 22 | * 23 | * @return string 24 | */ 25 | public function getLogoutUrl(); 26 | 27 | /** 28 | * Returns the URL to be redirected to after logout. 29 | * 30 | * @return string 31 | */ 32 | public function getLogoutTarget(); 33 | 34 | /** 35 | * Returns the check URL. 36 | * 37 | * @return string 38 | */ 39 | public function getCheckUrl(); 40 | 41 | /** 42 | * Builds a validation request for given credentials. 43 | * 44 | * @param string $credentials 45 | * 46 | * @return \Buzz\Message\RequestInterface 47 | */ 48 | public function buildValidationRequest($credentials); 49 | } 50 | -------------------------------------------------------------------------------- /Sso/ValidationInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface ValidationInterface 9 | { 10 | 11 | /** 12 | * Returns validation response. 13 | * 14 | * @return \Buzz\Message\Response 15 | */ 16 | public function getResponse(); 17 | 18 | /** 19 | * Is validation successful? 20 | * 21 | * @return bool Validation success 22 | */ 23 | public function isSuccess(); 24 | 25 | /** 26 | * Returns SSO credentials (token). 27 | * 28 | * @return string A SSO token 29 | */ 30 | public function getCredentials(); 31 | 32 | /** 33 | * Returns username if validation is successful. 34 | * 35 | * @return string The username 36 | */ 37 | public function getUsername(); 38 | 39 | /** 40 | * Returns attributes given by SSO server. 41 | * 42 | * @return array An array of attributes 43 | */ 44 | public function getAttributes(); 45 | 46 | /** 47 | * Returns an error message if validation was not successful. 48 | * 49 | * @return string An error message 50 | */ 51 | public function getError(); 52 | } 53 | -------------------------------------------------------------------------------- /Tests/AppKernel.php: -------------------------------------------------------------------------------- 1 | tmpPath = $tmpPath; 17 | $this->configFile = $configFile; 18 | 19 | parent::__construct($environment, $debug); 20 | } 21 | 22 | public function registerBundles() 23 | { 24 | $bundles = array( 25 | new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), 26 | new \Symfony\Bundle\SecurityBundle\SecurityBundle(), 27 | new \Symfony\Bundle\TwigBundle\TwigBundle(), 28 | new \BeSimple\SsoAuthBundle\BeSimpleSsoAuthBundle(), 29 | ); 30 | 31 | if (in_array($this->getEnvironment(), array('dev', 'test'))) { 32 | $bundles[] = new \Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); 33 | } 34 | 35 | return $bundles; 36 | } 37 | 38 | public function init() 39 | { 40 | } 41 | 42 | public function getRootDir() 43 | { 44 | return __DIR__; 45 | } 46 | 47 | public function getCacheDir() 48 | { 49 | return $this->tmpPath.'/cache/'.$this->environment; 50 | } 51 | 52 | public function getLogDir() 53 | { 54 | return $this->tmpPath.'/logs'; 55 | } 56 | 57 | public function registerContainerConfiguration(LoaderInterface $loader) 58 | { 59 | $loader->load($this->configFile); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Tests/Controller/Controller.php: -------------------------------------------------------------------------------- 1 | container = $container; 16 | } 17 | 18 | /** 19 | * @param string $view 20 | * @param array $parameters 21 | * @return \Symfony\Component\HttpFoundation\Response 22 | */ 23 | protected function render($view, array $parameters = array()) 24 | { 25 | $reference = new TemplateReference($view, 'twig'); 26 | 27 | return new Response($this->container->get('templating')->render($reference, $parameters)); 28 | } 29 | 30 | /** 31 | * @return \Symfony\Component\HttpFoundation\Request 32 | */ 33 | protected function getRequest() 34 | { 35 | return $this->container->get('request'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/Controller/Server/CasController.php: -------------------------------------------------------------------------------- 1 | query->get('service')); 18 | 19 | return sprintf('%s?ticket=%s', $url, $login->getCredentials()); 20 | } 21 | 22 | /** 23 | * @param \Symfony\Component\HttpFoundation\Request $request 24 | * @return string 25 | */ 26 | protected function getLogoutRedirectUrl(Request $request) 27 | { 28 | if ($request->query->has('url')) { 29 | return urldecode($request->query->get('url')); 30 | } 31 | 32 | return urldecode($request->query->get('service')); 33 | } 34 | 35 | /** 36 | * @param \Symfony\Component\HttpFoundation\Request $request 37 | * @return string 38 | */ 39 | protected function getCredentials(Request $request) 40 | { 41 | return $request->query->get('ticket'); 42 | } 43 | 44 | /** 45 | * @param \Symfony\Component\HttpFoundation\Request $request 46 | * @param string $name 47 | * @return string 48 | */ 49 | protected function getValidationView(Request $request, $name) 50 | { 51 | return 2 === (int) $request->attributes->get('version') 52 | ? sprintf('cas/%s_v2.xml.twig', $name) 53 | : sprintf('cas/%s_v1.txt.twig', $name); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Tests/Controller/Server/Controller.php: -------------------------------------------------------------------------------- 1 | createLoginForm(); 22 | $request = $this->getRequest(); 23 | 24 | if ($request->getMethod() == 'POST') { 25 | $form->bind($request); 26 | 27 | return new RedirectResponse($this->getLoginRedirectUrl($request, $form->getData())); 28 | } 29 | 30 | return $this->render('common/login.html.twig', array( 31 | 'form' => $form->createView(), 32 | 'action' => $request->getRequestUri()) 33 | ); 34 | } 35 | 36 | /** 37 | * @return \Symfony\Component\HttpFoundation\Response 38 | */ 39 | public function validationAction() 40 | { 41 | $credentials = $this->getCredentials($this->getRequest()); 42 | $name = $this->isValidCredentials($credentials) ? 'valid' : 'invalid'; 43 | $view = $this->getValidationView($this->getRequest(), $name); 44 | 45 | return $this->render($view, array('username' => $credentials)); 46 | } 47 | 48 | public function logoutAction() 49 | { 50 | return $this->render('common/link.html.twig', array( 51 | 'message' => self::LOGOUT_MESSAGE, 52 | 'url' => $this->getLogoutRedirectUrl($this->getRequest()), 53 | )); 54 | } 55 | 56 | /** 57 | * @return \Symfony\Component\Form\Form 58 | */ 59 | protected function createLoginForm() 60 | { 61 | return $this->container->get('form.factory')->create(new LoginType(), new Login()); 62 | } 63 | 64 | /** 65 | * @param string $credentials 66 | * @return bool 67 | */ 68 | protected function isValidCredentials($credentials) 69 | { 70 | return in_array($credentials, array('user', 'admin')); 71 | } 72 | 73 | /** 74 | * @abstract 75 | * @param \Symfony\Component\HttpFoundation\Request $request 76 | * @param Login $login 77 | * @return string 78 | */ 79 | abstract protected function getLoginRedirectUrl(Request $request, Login $login); 80 | 81 | /** 82 | * @abstract 83 | * @param \Symfony\Component\HttpFoundation\Request $request 84 | * @return string 85 | */ 86 | abstract protected function getLogoutRedirectUrl(Request $request); 87 | 88 | /** 89 | * @abstract 90 | * @param \Symfony\Component\HttpFoundation\Request $request 91 | * @return string 92 | */ 93 | abstract protected function getCredentials(Request $request); 94 | 95 | /** 96 | * @abstract 97 | * @param \Symfony\Component\HttpFoundation\Request $request 98 | * @param string $name 99 | * @return string 100 | */ 101 | abstract protected function getValidationView(Request $request, $name); 102 | } 103 | -------------------------------------------------------------------------------- /Tests/Controller/TestController.php: -------------------------------------------------------------------------------- 1 | renderMessage(self::HOME_MESSAGE); 18 | } 19 | 20 | public function anonAction() 21 | { 22 | return $this->renderMessage(self::ANON_MESSAGE); 23 | } 24 | 25 | public function securedAction() 26 | { 27 | return $this->renderMessage(self::SECURED_MESSAGE); 28 | } 29 | 30 | public function userAction() 31 | { 32 | return $this->renderMessage(self::USER_MESSAGE); 33 | } 34 | 35 | public function adminAction() 36 | { 37 | return $this->renderMessage(self::ADMIN_MESSAGE); 38 | } 39 | 40 | public function forbiddenAction() 41 | { 42 | return $this->renderMessage(self::FORBIDDEN_MESSAGE); 43 | } 44 | 45 | public function loginAction() 46 | { 47 | return $this->renderMessage(self::LOGIN_MESSAGE); 48 | } 49 | 50 | private function renderMessage($message) 51 | { 52 | return $this->render('common/message.html.twig', array('message' => $message)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/Controller/TrustedSsoController.php: -------------------------------------------------------------------------------- 1 | render('common/link.html.twig', array( 17 | 'message' => self::LOGIN_REQUIRED_MESSAGE, 18 | 'url' => $manager->getServer()->getLoginUrl() 19 | )); 20 | } 21 | 22 | public function logoutAction(Manager $manager, Request $request) 23 | { 24 | return $this->render('common/link.html.twig', array( 25 | 'message' => self::LOGOUT_REDIRECT_MESSAGE, 26 | 'url' => $manager->getServer()->getLogoutUrl() 27 | )); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/Form/Login.php: -------------------------------------------------------------------------------- 1 | username) > 0 && $this->username === $this->password; 13 | } 14 | 15 | public function getCredentials() 16 | { 17 | return $this->isValid() ? $this->username : null; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/Form/LoginType.php: -------------------------------------------------------------------------------- 1 | add('username', 'text'); 13 | $builder->add('password', 'password'); 14 | } 15 | 16 | public function getName() 17 | { 18 | return 'login'; 19 | } 20 | } -------------------------------------------------------------------------------- /Tests/Functional/AnonTest.php: -------------------------------------------------------------------------------- 1 | processTest($clientName, '/anon', TestController::ANON_MESSAGE); 18 | } 19 | 20 | /** 21 | * @dataProvider provideClients 22 | */ 23 | public function testSecured($clientName) 24 | { 25 | $this->processTest($clientName, '/secured', TrustedSsoController::LOGIN_REQUIRED_MESSAGE); 26 | } 27 | 28 | /** 29 | * @dataProvider provideClients 30 | */ 31 | public function testUser($clientName) 32 | { 33 | $this->processTest($clientName, '/secured/user', TrustedSsoController::LOGIN_REQUIRED_MESSAGE); 34 | } 35 | 36 | /** 37 | * @dataProvider provideClients 38 | */ 39 | public function testAdmin($clientName) 40 | { 41 | $this->processTest($clientName, '/secured/admin', TrustedSsoController::LOGIN_REQUIRED_MESSAGE); 42 | } 43 | 44 | private function processTest($clientName, $url, $expectedMessage) 45 | { 46 | $client = $this->createSsoClient($clientName); 47 | $crawler = $client->request('GET', $url); 48 | $message = $crawler->filter('#message')->text(); 49 | $this->assertEquals($expectedMessage, $message); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/Functional/LoginTest.php: -------------------------------------------------------------------------------- 1 | createSsoClient($clientName); 18 | // client follow redirects 19 | $client->followRedirects(); 20 | 21 | // go to secured page -> got login required 22 | $crawler = $client->request('GET', $securedUrl); 23 | $this->assertEquals(TrustedSsoController::LOGIN_REQUIRED_MESSAGE, $crawler->filter('#message')->text()); 24 | 25 | // click link -> got login form 26 | $crawler = $client->click($crawler->filter('#url')->link()); 27 | $this->assertEquals('login', $crawler->filter('form')->attr('id')); 28 | 29 | // fill form & submit -> got expected message 30 | $form = $crawler->filter('input[type=submit]')->form(); 31 | $crawler = $client->submit($form, array('login[username]' => $login, 'login[password]' => $login)); 32 | $this->assertEquals($expectedMessage, $crawler->filter('#message')->text()); 33 | 34 | if (!$login === self::LOGIN_INVALID) { 35 | // check validation attributes 36 | $attrs = static::$kernel->getContainer()->get('security.context')->getToken()->getValidationAttributes(); 37 | $this->assertEquals(array('attr1' => 'val1', 'attr2' => 'val2'), $attrs); 38 | } 39 | 40 | // logout -> got logout redirect 41 | $crawler = $client->request('GET', '/secured/logout'); 42 | $this->assertEquals(TrustedSsoController::LOGOUT_REDIRECT_MESSAGE, $crawler->filter('#message')->text()); 43 | 44 | // click link -> got logout done 45 | $crawler = $client->click($crawler->filter('#url')->link()); 46 | $this->assertEquals(ServerController::LOGOUT_MESSAGE, $crawler->filter('#message')->text()); 47 | 48 | // click link -> go to homepage 49 | $crawler = $client->click($crawler->filter('#url')->link()); 50 | $this->assertEquals(TestController::HOME_MESSAGE, $crawler->filter('#message')->text()); 51 | } 52 | 53 | public function provideLoginData() 54 | { 55 | $data = array(); 56 | foreach ($this->provideClients() as $client) { 57 | foreach ($this->provideCases() as $case) { 58 | $data[] = array_merge($client, $case); 59 | } 60 | } 61 | 62 | return $data; 63 | } 64 | 65 | private function provideCases() 66 | { 67 | return array( 68 | array('/secured', self::LOGIN_USER, TestController::SECURED_MESSAGE), 69 | array('/secured', self::LOGIN_INVALID, TestController::LOGIN_MESSAGE), 70 | array('/secured/user', self::LOGIN_USER, TestController::USER_MESSAGE), 71 | array('/secured/admin', self::LOGIN_ADMIN, TestController::ADMIN_MESSAGE), 72 | array('/secured/admin', self::LOGIN_USER, TestController::FORBIDDEN_MESSAGE) 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Tests/Functional/WebTestCase.php: -------------------------------------------------------------------------------- 1 | $name)); 33 | } 34 | 35 | protected function getXml(Crawler $crawler) 36 | { 37 | $document = new \DOMDocument('1.0', 'utf-8'); 38 | $crawler->each(function(\DOMElement $node) use ($document) { 39 | $document->appendChild($document->importNode($node, true)); 40 | }); 41 | return html_entity_decode($document->saveXML()); 42 | } 43 | 44 | static protected function createKernel(array $options = array()) 45 | { 46 | static::$tmpPath = sys_get_temp_dir().'/be_simple_sso_auth_bundle_tests'; 47 | static::$configFile = __DIR__.'/../Resources/config/'.$options['sso_server_name'].'.yml'; 48 | 49 | if (file_exists(static::$tmpPath)) { 50 | $fs = new Filesystem(); 51 | $fs->remove(static::$tmpPath); 52 | } 53 | 54 | $kernel = new AppKernel( 55 | static::$tmpPath, 56 | static::$configFile, 57 | isset($options['environment']) ? $options['environment'] : 'test', 58 | isset($options['debug']) ? $options['debug'] : true 59 | ); 60 | 61 | HttpClient::setKernel($kernel); 62 | 63 | return $kernel; 64 | } 65 | 66 | /** 67 | * Shuts the kernel down if it was used in the test 68 | * and remove temp files. 69 | */ 70 | protected function tearDown() 71 | { 72 | if (null !== static::$kernel) { 73 | static::$kernel->shutdown(); 74 | 75 | $fs = new Filesystem(); 76 | $fs->remove(static::$tmpPath); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Tests/HttpClient.php: -------------------------------------------------------------------------------- 1 | getUrl(), $buzzRequest->getMethod()); 25 | $response = static::$kernel->handle($request); 26 | 27 | $buzzResponse->setContent($response->getContent()); 28 | 29 | // kernel handling set session_id to empty string 30 | session_id($session); 31 | } 32 | 33 | public function setTimeout($timeout) 34 | { 35 | } 36 | 37 | public function setMaxRedirects($maxRedirects) 38 | { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/Resources/config/cas.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: common.yml } 3 | 4 | framework: 5 | router: 6 | resource: "%kernel.root_dir%/Resources/routing/cas.yml" 7 | 8 | services: 9 | be_simple.sso_auth.server_controller.cas: 10 | class: BeSimple\SsoAuthBundle\Tests\Controller\Server\CasController 11 | arguments: [@service_container] 12 | 13 | security: 14 | firewalls: 15 | cas: 16 | access_denied_url: /forbidden 17 | pattern: ^/secured.*$ 18 | trusted_sso: 19 | manager: cas 20 | login_path: /login 21 | check_path: /secured/check 22 | login_action: be_simple.sso_auth.test_controller.trusted:loginAction 23 | logout_action: be_simple.sso_auth.test_controller.trusted:logoutAction 24 | logout: 25 | path: /secured/logout 26 | 27 | be_simple_sso_auth: 28 | cas: 29 | protocol: 30 | id: cas 31 | version: 2 32 | server: 33 | id: cas 34 | login_url: /server/login 35 | logout_url: /server/logout 36 | logout_target: /home 37 | validation_url: /server/validation 38 | -------------------------------------------------------------------------------- /Tests/Resources/config/common.yml: -------------------------------------------------------------------------------- 1 | framework: 2 | secret: test 3 | csrf_protection: 4 | enabled: true 5 | validation: { enabled: true, enable_annotations: true } 6 | templating: { engines: ["twig"] } 7 | form: ~ 8 | test: ~ 9 | session: 10 | cookie_lifetime: 3600 11 | storage_id: session.storage.filesystem 12 | 13 | services: 14 | logger: 15 | class: Symfony\Component\HttpKernel\Log\NullLogger 16 | be_simple.sso_auth.test_controller.test: 17 | class: BeSimple\SsoAuthBundle\Tests\Controller\TestController 18 | arguments: [@service_container] 19 | be_simple.sso_auth.test_controller.trusted: 20 | class: BeSimple\SsoAuthBundle\Tests\Controller\TrustedSsoController 21 | arguments: [@service_container] 22 | be_simple.sso_auth.client: 23 | class: BeSimple\SsoAuthBundle\Tests\HttpClient 24 | 25 | twig: 26 | debug: %kernel.debug% 27 | strict_variables: %kernel.debug% 28 | paths: 29 | - %kernel.root_dir%/Resources/views 30 | 31 | security: 32 | encoders: 33 | Symfony\Component\Security\Core\User\User: plaintext 34 | role_hierarchy: 35 | ROLE_ADMIN: ROLE_USER 36 | ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] 37 | providers: 38 | in_memory: 39 | memory: 40 | users: 41 | "user": { password: user, roles: ["ROLE_USER"] } 42 | "admin": { password: admin, roles: ["ROLE_ADMIN"] } 43 | access_control: 44 | - { path: ^/secured/user, roles: ROLE_USER } 45 | - { path: ^/secured/admin, roles: ROLE_ADMIN } 46 | -------------------------------------------------------------------------------- /Tests/Resources/routing/cas.yml: -------------------------------------------------------------------------------- 1 | _common: 2 | resource: common.yml 3 | 4 | server_login: 5 | pattern: /server/login 6 | defaults: { _controller: be_simple.sso_auth.server_controller.cas:loginAction } 7 | 8 | server_validation: 9 | pattern: /server/validation 10 | defaults: { _controller: be_simple.sso_auth.server_controller.cas:validationAction, version: 2 } 11 | 12 | server_logout: 13 | pattern: /server/logout 14 | defaults: { _controller: be_simple.sso_auth.server_controller.cas:logoutAction } 15 | -------------------------------------------------------------------------------- /Tests/Resources/routing/common.yml: -------------------------------------------------------------------------------- 1 | home: 2 | pattern: /home 3 | defaults: { _controller: be_simple.sso_auth.test_controller.test:homeAction } 4 | 5 | anon: 6 | pattern: /anon 7 | defaults: { _controller: be_simple.sso_auth.test_controller.test:anonAction } 8 | 9 | secured: 10 | pattern: /secured 11 | defaults: { _controller: be_simple.sso_auth.test_controller.test:securedAction } 12 | 13 | secured_check: 14 | pattern: /secured/check 15 | 16 | secured_user: 17 | pattern: /secured/user 18 | defaults: { _controller: be_simple.sso_auth.test_controller.test:userAction } 19 | 20 | secured_admin: 21 | pattern: /secured/admin 22 | defaults: { _controller: be_simple.sso_auth.test_controller.test:adminAction } 23 | 24 | logout: 25 | pattern: /secured/logout 26 | 27 | forbidden: 28 | pattern: /forbidden 29 | defaults: { _controller: be_simple.sso_auth.test_controller.test:forbiddenAction } 30 | 31 | login: 32 | pattern: /login 33 | defaults: { _controller: be_simple.sso_auth.test_controller.test:loginAction } 34 | -------------------------------------------------------------------------------- /Tests/Resources/views/cas/invalid_v1.txt.twig: -------------------------------------------------------------------------------- 1 | no 2 | error message -------------------------------------------------------------------------------- /Tests/Resources/views/cas/invalid_v2.xml.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | error message 4 | 5 | 6 | -------------------------------------------------------------------------------- /Tests/Resources/views/cas/valid_v1.txt.twig: -------------------------------------------------------------------------------- 1 | yes 2 | {{ username }} -------------------------------------------------------------------------------- /Tests/Resources/views/cas/valid_v2.xml.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ username }} 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/Resources/views/common/link.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "::layout.html.twig" %} 2 | 3 | {% block content %} 4 |

{{ message }}

5 | {{ url }} 6 | {% endblock %} -------------------------------------------------------------------------------- /Tests/Resources/views/common/login.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "::layout.html.twig" %} 2 | 3 | {% block content %} 4 |
5 | {{ form_widget(form) }} 6 | 7 |
8 | {% endblock %} -------------------------------------------------------------------------------- /Tests/Resources/views/common/message.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "::layout.html.twig" %} 2 | 3 | {% block content %} 4 |

{{ message }}

5 | {% endblock %} -------------------------------------------------------------------------------- /Tests/Resources/views/layout.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | BeSimple's SsoAuthBundle Tests 4 | 5 | 6 | {% block content %}{% endblock %} 7 | 8 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "besimple/sso-auth-bundle", 3 | "type": "symfony-bundle", 4 | "description": "SSO authentication your Symfony2 project ", 5 | "keywords": ["CAS", "SSO", "Symfony2", "authentication"], 6 | "homepage": "https://github.com/BeSimple/BeSimpleSsoAuthBundle", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Jean-François Simon", 11 | "email": "contact@jfsimon.fr" 12 | }, 13 | { 14 | "name": "BeSimple Community", 15 | "homepage": "https://github.com/BeSimple/BeSimpleSsoAuthBundle/contributors" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=5.3.3", 20 | "kriswallsmith/buzz": ">=0.7,<=0.16.1", 21 | "symfony/framework-bundle": "~2.1|~3.0", 22 | "symfony/twig-bundle": "~2.1|~3.0", 23 | "symfony/security-bundle": "~2.1|~3.0" 24 | }, 25 | "require-dev": { 26 | "symfony/browser-kit": "~2.1", 27 | "symfony/css-selector": "~2.1", 28 | "symfony/filesystem": "~2.1", 29 | "symfony/form": "~2.1", 30 | "symfony/validator": "~2.1", 31 | "symfony/web-profiler-bundle": "~2.1", 32 | "symfony/yaml": "~2.1", 33 | "symfony/expression-language": "~2.4" 34 | }, 35 | "autoload": { 36 | "psr-0": { "BeSimple\\SsoAuthBundle": "" } 37 | }, 38 | "target-dir": "BeSimple/SsoAuthBundle", 39 | "extra": { 40 | "branch-alias": { 41 | "dev-master": "1.0.x-dev" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | . 19 | 20 | Resources 21 | Tests 22 | vendor 23 | 24 | 25 | 26 | 27 | 28 | --------------------------------------------------------------------------------