├── .gitignore ├── .php_cs ├── .travis.yml ├── CONTRIBUTORS.md ├── LICENSE ├── README.md ├── autoload.php ├── composer.json ├── contrib ├── pre-commit └── setup.sh ├── phpunit.xml.dist ├── src └── LightSaml │ └── SpBundle │ ├── Controller │ └── DefaultController.php │ ├── DependencyInjection │ ├── Configuration.php │ ├── LightSamlSpExtension.php │ └── Security │ │ └── Factory │ │ └── LightSamlSpFactory.php │ ├── LightSamlSpBundle.php │ ├── Resources │ ├── config │ │ ├── routing.yml │ │ ├── security.yml │ │ └── services.yml │ └── views │ │ ├── discovery.html.twig │ │ └── sessions.html.twig │ └── Security │ ├── Authentication │ ├── Provider │ │ └── LightsSamlSpAuthenticationProvider.php │ └── Token │ │ ├── SamlSpResponseToken.php │ │ ├── SamlSpToken.php │ │ ├── SamlSpTokenFactory.php │ │ └── SamlSpTokenFactoryInterface.php │ ├── Firewall │ └── LightSamlSpListener.php │ └── User │ ├── AttributeMapperInterface.php │ ├── SimpleAttributeMapper.php │ ├── SimpleUsernameMapper.php │ ├── UserCreatorInterface.php │ └── UsernameMapperInterface.php └── tests └── LightSaml └── SpBundle └── Tests ├── Controller └── DefaultControllerTest.php ├── DependencyInjection ├── ConfigurationTest.php ├── LightSamlSpExtensionTest.php └── Security │ └── Factory │ └── LightSamlSpFactoryTest.php ├── Functional ├── FunctionalTest.php └── app │ ├── AppKernel.php │ ├── config.yml │ └── routing.yml ├── LightSamlSpBundleTest.php └── Security ├── Authentication ├── Provider │ └── LightsSamlSpAuthenticationProviderTest.php └── Token │ ├── SamlSpTokenFactoryTest.php │ └── SamlSpTokenTest.php ├── Firewall └── LightSamlSpListenerTest.php └── User ├── SimpleAttributeMapperTest.php └── SimpleUsernameMapperTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Symfony directories 2 | vendor/* 3 | /.idea 4 | /composer.lock 5 | /bin 6 | /php-cs-fixer.phar 7 | /web/sp/*.log 8 | /web/idp/*.log 9 | /build/logs 10 | /phpda.json 11 | /phpda.svg 12 | /*.cache 13 | /tests/LightSaml/SpBundle/Tests/Functional/app/cache/* 14 | /tests/LightSaml/SpBundle/Tests/Functional/app/logs/* 15 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | in('src') 5 | ; 6 | 7 | $header = << 11 | 12 | This source file is subject to the MIT license that is bundled 13 | with this source code in the file LICENSE. 14 | EOT; 15 | 16 | return PhpCsFixer\Config::create() 17 | ->setRules(array( 18 | '@Symfony' => true, 19 | 'simplified_null_return' => false, 20 | 'phpdoc_no_empty_return' => false, 21 | 'no_mixed_echo_print' => ['use' => 'print'], 22 | 'header_comment' => ['header' => $header], 23 | )) 24 | ->setUsingCache(false) 25 | ->setFinder($finder) 26 | ; 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | - 7.1 7 | 8 | cache: 9 | directories: 10 | - $HOME/.composer/cache 11 | 12 | matrix: 13 | include: 14 | - php: 5.6 15 | env: COMPOSER_FLAGS="--prefer-lowest" 16 | 17 | before_install: 18 | - echo "memory_limit = -1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini 19 | - echo "session.gc_probability = 0" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini 20 | - composer self-update 21 | - composer --version 22 | - if [ "$TRAVIS_PHP_VERSION" == "7.1" ]; then wget http://get.sensiolabs.org/php-cs-fixer.phar -O php-cs-fixer.phar; fi 23 | - if [ "$TRAVIS_PHP_VERSION" == "7.1" ]; then php php-cs-fixer.phar --version; fi 24 | - if [ "$TRAVIS_PHP_VERSION" == "7.1" ]; then wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.1.0/php-coveralls.phar; fi 25 | 26 | install: 27 | - COMPOSER_ROOT_VERSION=dev-master composer update --prefer-source --no-interaction $COMPOSER_FLAGS 28 | 29 | script: 30 | - if [ "$TRAVIS_PHP_VERSION" == "7.1" ]; then php php-cs-fixer.phar fix --dry-run -v; fi 31 | - if [ "$TRAVIS_PHP_VERSION" == "7.1" ]; then bin/phpunit --coverage-clover build/logs/clover.xml; else bin/phpunit; fi 32 | 33 | after_script: 34 | - if [[ "$TRAVIS_PHP_VERSION" == "7.1" ]]; then php php-coveralls.phar -v; fi; 35 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | CONTRIBUTORS 2 | ============ 3 | 4 | LightSAML is the result of the work of many people who made the code better 5 | 6 | - Milos Tomic tmilos@lightsaml.com 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Milos Tomic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LightSAML SP Bundle 2 | =================== 3 | 4 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) 5 | [![Build Status](https://travis-ci.org/lightSAML/SpBundle.svg?branch=master)](https://travis-ci.org/lightSAML/SpBundle) 6 | [![Coverage Status](https://coveralls.io/repos/lightSAML/SpBundle/badge.svg?branch=master&service=github)](https://coveralls.io/github/lightSAML/SpBundle?branch=master) 7 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/928197ea-1587-4da5-b9ea-6a0e53eb8924/mini.png)](https://insight.sensiolabs.com/projects/928197ea-1587-4da5-b9ea-6a0e53eb8924) 8 | [![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/tmilos77) 9 | 10 | 11 | SAML 2.0 SP Symfony bundle based on LightSAML. 12 | 13 | [Getting Started](http://www.lightsaml.com/SP-Bundle/Getting-started/) 14 | -------------------------------------------------------------------------------- /autoload.php: -------------------------------------------------------------------------------- 1 | =5.6", 23 | "symfony/framework-bundle": "~2.7|~3.0|~4.0", 24 | "symfony/security-bundle": "~2.7|~3.0|~4.0", 25 | "lightsaml/symfony-bridge": "~1.3" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "^5.7", 29 | "sebastian/comparator": "^1.2.4|~2.0|~3.0", 30 | "symfony/symfony": "~2.7|~3.0|~4.0", 31 | "symfony/monolog-bundle": "~2.7|~3.0|~4.0" 32 | }, 33 | "config": { 34 | "bin-dir": "bin" 35 | }, 36 | "prefer-stable": true, 37 | "minimum-stability": "stable" 38 | } 39 | -------------------------------------------------------------------------------- /contrib/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Pre-commit Git hook. 3 | # Runs PHP CS Fixer on PHP files. 4 | # 5 | # If you absolutely must commit without testing, 6 | # use: git commit --no-verify 7 | 8 | # This will check only staged files to be commited. 9 | filenames=($(git diff --staged --name-only HEAD)) 10 | 11 | # This will set text to red in terminal. 12 | text_red=`tput setaf 1` 13 | # This will set the text to green in terminal. 14 | text_green=`tput setaf 2` 15 | # This will reset the terminal text to normal. 16 | text_reset=`tput sgr0` 17 | 18 | numberFilesChanged="${#filenames[@]}" 19 | 20 | if [[ $numberFilesChanged > 0 ]]; 21 | then 22 | echo "$numberFilesChanged files were changed, running php-cs-fixer" 23 | # PHP CS Fixer. 24 | for i in "${filenames[@]}" 25 | do 26 | if [[ $i == *.php ]]; 27 | then 28 | php php-cs-fixer.phar fix $i 29 | 30 | if [ $? -ne 0 ]; 31 | then 32 | # File had some issues. Now it is fine. Add this file to git again. 33 | git add $i 34 | fi 35 | fi 36 | done 37 | fi 38 | 39 | echo "${text_green}PHP CS Fixer finished execution successfully.${text_reset}" 40 | -------------------------------------------------------------------------------- /contrib/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ ! -f php-cs-fixer.phar ]; then 4 | echo "The php-cs-fixer.phar is required... downloading..." 5 | wget http://get.sensiolabs.org/php-cs-fixer.phar -O php-cs-fixer.phar || curl http://get.sensiolabs.org/php-cs-fixer.phar -o php-cs-fixer.phar || { echo >&2 "I require wget or curl but they are not installed. Aborting."; exit 1; } 6 | fi 7 | 8 | # Copy the pre-commit hook to the current repository hooks directory. 9 | cp contrib/pre-commit .git/hooks/pre-commit 10 | 11 | # Add execution permission for pre-commit file. 12 | chmod +x .git/hooks/pre-commit -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | tests 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | src 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Controller/DefaultController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\Controller; 13 | 14 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; 15 | use Symfony\Component\HttpFoundation\Request; 16 | 17 | class DefaultController extends Controller 18 | { 19 | public function metadataAction() 20 | { 21 | $profile = $this->get('ligthsaml.profile.metadata'); 22 | $context = $profile->buildContext(); 23 | $action = $profile->buildAction(); 24 | 25 | $action->execute($context); 26 | 27 | return $context->getHttpResponseContext()->getResponse(); 28 | } 29 | 30 | public function discoveryAction() 31 | { 32 | $parties = $this->get('lightsaml.container.build')->getPartyContainer()->getIdpEntityDescriptorStore()->all(); 33 | 34 | if (1 == count($parties)) { 35 | return $this->redirect($this->generateUrl('lightsaml_sp.login', ['idp' => $parties[0]->getEntityID()])); 36 | } 37 | 38 | return $this->render('@LightSamlSp/discovery.html.twig', [ 39 | 'parties' => $parties, 40 | ]); 41 | } 42 | 43 | public function loginAction(Request $request) 44 | { 45 | $idpEntityId = $request->get('idp'); 46 | if (null === $idpEntityId) { 47 | return $this->redirect($this->generateUrl($this->container->getParameter('lightsaml_sp.route.discovery'))); 48 | } 49 | 50 | $profile = $this->get('ligthsaml.profile.login_factory')->get($idpEntityId); 51 | $context = $profile->buildContext(); 52 | $action = $profile->buildAction(); 53 | 54 | $action->execute($context); 55 | 56 | return $context->getHttpResponseContext()->getResponse(); 57 | } 58 | 59 | public function sessionsAction() 60 | { 61 | $ssoState = $this->get('lightsaml.container.build')->getStoreContainer()->getSsoStateStore()->get(); 62 | 63 | return $this->render('@LightSamlSp/sessions.html.twig', [ 64 | 'sessions' => $ssoState->getSsoSessions(), 65 | ]); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\DependencyInjection; 13 | 14 | use LightSaml\ClaimTypes; 15 | use LightSaml\SpBundle\Security\User\SimpleUsernameMapper; 16 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 17 | use Symfony\Component\Config\Definition\ConfigurationInterface; 18 | 19 | class Configuration implements ConfigurationInterface 20 | { 21 | /** 22 | * Generates the configuration tree builder. 23 | * 24 | * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder 25 | */ 26 | public function getConfigTreeBuilder() 27 | { 28 | $treeBuilder = new TreeBuilder(); 29 | $root = $treeBuilder->root('light_saml_sp'); 30 | 31 | $root 32 | ->children() 33 | ->arrayNode('username_mapper') 34 | ->defaultValue([ 35 | ClaimTypes::EMAIL_ADDRESS, 36 | ClaimTypes::ADFS_1_EMAIL, 37 | ClaimTypes::COMMON_NAME, 38 | ClaimTypes::WINDOWS_ACCOUNT_NAME, 39 | 'urn:oid:0.9.2342.19200300.100.1.3', 40 | 'uid', 41 | 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6', 42 | SimpleUsernameMapper::NAME_ID, 43 | ]) 44 | ->prototype('scalar')->end() 45 | ->end() 46 | ->end() 47 | ; 48 | 49 | return $treeBuilder; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/DependencyInjection/LightSamlSpExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\DependencyInjection; 13 | 14 | use Symfony\Component\DependencyInjection\ContainerBuilder; 15 | use Symfony\Component\Config\FileLocator; 16 | use Symfony\Component\HttpKernel\DependencyInjection\Extension; 17 | use Symfony\Component\DependencyInjection\Loader; 18 | 19 | class LightSamlSpExtension extends Extension 20 | { 21 | /** 22 | * Loads a specific configuration. 23 | * 24 | * @param array $config An array of configuration values 25 | * @param ContainerBuilder $container A ContainerBuilder instance 26 | * 27 | * @throws \InvalidArgumentException When provided tag is not defined in this extension 28 | * 29 | * @api 30 | */ 31 | public function load(array $config, ContainerBuilder $container) 32 | { 33 | $configuration = new Configuration(); 34 | $config = $this->processConfiguration($configuration, $config); 35 | 36 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 37 | $loader->load('security.yml'); 38 | $loader->load('services.yml'); 39 | 40 | $this->configureSimpleUsernameMapper($config, $container); 41 | } 42 | 43 | private function configureSimpleUsernameMapper(array $config, ContainerBuilder $container) 44 | { 45 | $definition = $container->getDefinition('lightsaml_sp.username_mapper.simple'); 46 | $definition->replaceArgument(0, $config['username_mapper']); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/DependencyInjection/Security/Factory/LightSamlSpFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\DependencyInjection\Security\Factory; 13 | 14 | use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory; 15 | use Symfony\Component\Config\Definition\Builder\NodeDefinition; 16 | use Symfony\Component\DependencyInjection\ChildDefinition; 17 | use Symfony\Component\DependencyInjection\ContainerBuilder; 18 | use Symfony\Component\DependencyInjection\DefinitionDecorator; 19 | use Symfony\Component\DependencyInjection\Reference; 20 | 21 | class LightSamlSpFactory extends AbstractFactory 22 | { 23 | public function addConfiguration(NodeDefinition $node) 24 | { 25 | parent::addConfiguration($node); 26 | $node 27 | ->children() 28 | ->booleanNode('force')->defaultTrue()->end() 29 | ->scalarNode('username_mapper')->defaultValue('lightsaml_sp.username_mapper.simple')->end() 30 | ->scalarNode('user_creator')->defaultNull()->end() 31 | ->scalarNode('attribute_mapper')->defaultValue('lightsaml_sp.attribute_mapper.simple')->end() 32 | ->scalarNode('token_factory')->defaultValue('lightsaml_sp.token_factory')->end() 33 | ->end() 34 | ->end(); 35 | } 36 | 37 | /** 38 | * Subclasses must return the id of a service which implements the 39 | * AuthenticationProviderInterface. 40 | * 41 | * @param ContainerBuilder $container 42 | * @param string $id The unique id of the firewall 43 | * @param array $config The options array for this listener 44 | * @param string $userProviderId The id of the user provider 45 | * 46 | * @return string never null, the id of the authentication provider 47 | */ 48 | protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId) 49 | { 50 | if (class_exists('Symfony\Component\DependencyInjection\ChildDefinition')) { 51 | // Symfony >= 3.3 52 | $definition = new ChildDefinition('security.authentication.provider.lightsaml_sp'); 53 | } else { 54 | // Symfony < 3.3 55 | $definition = new DefinitionDecorator('security.authentication.provider.lightsaml_sp'); 56 | } 57 | 58 | $providerId = 'security.authentication.provider.lightsaml_sp.'.$id; 59 | $provider = $container 60 | ->setDefinition($providerId, $definition) 61 | ->replaceArgument(0, $id) 62 | ->replaceArgument(2, $config['force']) 63 | ; 64 | if (isset($config['provider'])) { 65 | $provider->replaceArgument(1, new Reference($userProviderId)); 66 | } 67 | if (isset($config['username_mapper'])) { 68 | $provider->replaceArgument(4, new Reference($config['username_mapper'])); 69 | } 70 | if (isset($config['user_creator'])) { 71 | $provider->replaceArgument(5, new Reference($config['user_creator'])); 72 | } 73 | if (isset($config['attribute_mapper'])) { 74 | $provider->replaceArgument(6, new Reference($config['attribute_mapper'])); 75 | } 76 | if (isset($config['token_factory'])) { 77 | $provider->replaceArgument(7, new Reference($config['token_factory'])); 78 | } 79 | 80 | return $providerId; 81 | } 82 | 83 | /** 84 | * Subclasses must return the id of the listener template. 85 | * 86 | * Listener definitions should inherit from the AbstractAuthenticationListener 87 | * like this: 88 | * 89 | * 93 | * 94 | * In the above case, this method would return "my.listener.id". 95 | * 96 | * @return string 97 | */ 98 | protected function getListenerId() 99 | { 100 | return 'security.authentication.listener.lightsaml_sp'; 101 | } 102 | 103 | /** 104 | * Defines the position at which the provider is called. 105 | * Possible values: pre_auth, form, http, and remember_me. 106 | * 107 | * @return string 108 | */ 109 | public function getPosition() 110 | { 111 | return 'form'; 112 | } 113 | 114 | public function getKey() 115 | { 116 | return 'light_saml_sp'; 117 | } 118 | 119 | protected function createEntryPoint($container, $id, $config, $defaultEntryPointId) 120 | { 121 | $entryPointId = 'security.authentication.form_entry_point.'.$id; 122 | 123 | if (class_exists('Symfony\Component\DependencyInjection\ChildDefinition')) { 124 | // Symfony >= 3.3 125 | $definition = new ChildDefinition('security.authentication.form_entry_point'); 126 | } else { 127 | // Symfony < 3.3 128 | $definition = new DefinitionDecorator('security.authentication.form_entry_point'); 129 | } 130 | 131 | $container 132 | ->setDefinition($entryPointId, $definition) 133 | ->addArgument(new Reference('security.http_utils')) 134 | ->addArgument($config['login_path']) 135 | ->addArgument($config['use_forward']) 136 | ; 137 | 138 | return $entryPointId; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/LightSamlSpBundle.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle; 13 | 14 | use LightSaml\SpBundle\DependencyInjection\Security\Factory\LightSamlSpFactory; 15 | use Symfony\Component\DependencyInjection\ContainerBuilder; 16 | use Symfony\Component\HttpKernel\Bundle\Bundle; 17 | 18 | class LightSamlSpBundle extends Bundle 19 | { 20 | public function build(ContainerBuilder $container) 21 | { 22 | parent::build($container); 23 | 24 | $extension = $container->getExtension('security'); 25 | $extension->addSecurityListenerFactory(new LightSamlSpFactory()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Resources/config/routing.yml: -------------------------------------------------------------------------------- 1 | lightsaml_sp.metadata: 2 | path: /metadata.xml 3 | defaults: { _controller: LightSaml\SpBundle\Controller\DefaultController::metadataAction } 4 | 5 | lightsaml_sp.discovery: 6 | path: /discovery 7 | defaults: { _controller: LightSaml\SpBundle\Controller\DefaultController::discoveryAction } 8 | 9 | lightsaml_sp.login: 10 | path: /login 11 | defaults: { _controller: LightSaml\SpBundle\Controller\DefaultController::loginAction} 12 | 13 | lightsaml_sp.login_check: 14 | path: /login_check 15 | 16 | lightsaml_sp.sessions: 17 | path: /sessions 18 | defaults: { _controller: LightSaml\SpBundle\Controller\DefaultController::sessionsAction } 19 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Resources/config/security.yml: -------------------------------------------------------------------------------- 1 | services: 2 | security.authentication.listener.lightsaml_sp: 3 | class: LightSaml\SpBundle\Security\Firewall\LightSamlSpListener 4 | parent: security.authentication.listener.abstract 5 | abstract: true 6 | calls: 7 | - [setProfile, ["@ligthsaml.profile.acs"]] 8 | 9 | security.authentication.provider.lightsaml_sp: 10 | class: LightSaml\SpBundle\Security\Authentication\Provider\LightsSamlSpAuthenticationProvider 11 | arguments: 12 | - ~ # provider key 13 | - ~ # user provider 14 | - ~ # force 15 | - "@security.user_checker" 16 | - "@lightsaml_sp.username_mapper.simple" # username mapper 17 | - ~ # user creator 18 | - "@lightsaml_sp.attribute_mapper.simple" # attribute mapper 19 | - ~ # token factory 20 | abstract: true 21 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Resources/config/services.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | lightsaml_sp.route.discovery: lightsaml_sp.discovery 3 | lightsaml.route.login_check: lightsaml_sp.login_check 4 | 5 | services: 6 | lightsaml_sp.username_mapper.simple: 7 | class: LightSaml\SpBundle\Security\User\SimpleUsernameMapper 8 | arguments: 9 | - [] 10 | 11 | lightsaml_sp.attribute_mapper.simple: 12 | class: LightSaml\SpBundle\Security\User\SimpleAttributeMapper 13 | 14 | lightsaml_sp.token_factory: 15 | class: LightSaml\SpBundle\Security\Authentication\Token\SamlSpTokenFactory 16 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Resources/views/discovery.html.twig: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Following IDPs are configured

10 |

Choose one

11 | {% for idp in parties %} 12 | {% if idp.allIdpSsoDescriptors %} 13 |

{{ idp.entityID }}

14 | {% endif %} 15 | {% else %} 16 |

There is no IDP configured

17 | {% endfor %} 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Resources/views/sessions.html.twig: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

SAML Sessions

10 | {% for session in sessions %} 11 |
    12 |
  • IDP: {{ session.idpEntityId }}
  • 13 |
  • SP: {{ session.spEntityId }}
  • 14 |
  • NameID: {{ session.nameId }}
  • 15 |
  • NameIDFormat: {{ session.nameIdFormat }}
  • 16 |
  • SessionIndex: {{ session.sessionIndex }}
  • 17 |
  • AuthnInstant: {{ session.sessionInstant|date('Y-m-d H:i:s P') }}
  • 18 |
  • FirstAuthOn: {{ session.firstAuthOn|date('Y-m-d H:i:s P') }}
  • 19 |
  • LastAuthOn: {{ session.lastAuthOn|date('Y-m-d H:i:s P') }}
  • 20 |
21 | {% else %} 22 |

There are no SAML sessions established

23 | {% endfor %} 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Security/Authentication/Provider/LightsSamlSpAuthenticationProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\Security\Authentication\Provider; 13 | 14 | use LightSaml\SpBundle\Security\Authentication\Token\SamlSpResponseToken; 15 | use LightSaml\SpBundle\Security\Authentication\Token\SamlSpToken; 16 | use LightSaml\SpBundle\Security\Authentication\Token\SamlSpTokenFactoryInterface; 17 | use LightSaml\SpBundle\Security\User\AttributeMapperInterface; 18 | use LightSaml\SpBundle\Security\User\UserCreatorInterface; 19 | use LightSaml\SpBundle\Security\User\UsernameMapperInterface; 20 | use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; 21 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 22 | use Symfony\Component\Security\Core\Exception\AuthenticationException; 23 | use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; 24 | use Symfony\Component\Security\Core\User\UserCheckerInterface; 25 | use Symfony\Component\Security\Core\User\UserInterface; 26 | use Symfony\Component\Security\Core\User\UserProviderInterface; 27 | 28 | class LightsSamlSpAuthenticationProvider implements AuthenticationProviderInterface 29 | { 30 | /** @var string */ 31 | private $providerKey; 32 | 33 | /** @var UserProviderInterface */ 34 | private $userProvider; 35 | 36 | /** @var bool */ 37 | private $force; 38 | 39 | /** @var UserCheckerInterface|null */ 40 | private $userChecker; 41 | 42 | /** @var UsernameMapperInterface|null */ 43 | private $usernameMapper; 44 | 45 | /** @var UserCreatorInterface */ 46 | private $userCreator; 47 | 48 | /** @var AttributeMapperInterface */ 49 | private $attributeMapper; 50 | 51 | /** @var SamlSpTokenFactoryInterface */ 52 | private $tokenFactory; 53 | 54 | /** 55 | * @param string $providerKey 56 | * @param UserProviderInterface|null $userProvider 57 | * @param bool $force 58 | * @param UserCheckerInterface|null $userChecker 59 | * @param UsernameMapperInterface|null $usernameMapper 60 | * @param UserCreatorInterface|null $userCreator 61 | * @param AttributeMapperInterface|null $attributeMapper 62 | * @param SamlSpTokenFactoryInterface|null $tokenFactory 63 | */ 64 | public function __construct( 65 | $providerKey, 66 | UserProviderInterface $userProvider = null, 67 | $force = false, 68 | UserCheckerInterface $userChecker = null, 69 | UsernameMapperInterface $usernameMapper = null, 70 | UserCreatorInterface $userCreator = null, 71 | AttributeMapperInterface $attributeMapper = null, 72 | SamlSpTokenFactoryInterface $tokenFactory = null 73 | ) { 74 | $this->providerKey = $providerKey; 75 | $this->userProvider = $userProvider; 76 | $this->force = $force; 77 | $this->userChecker = $userChecker; 78 | $this->usernameMapper = $usernameMapper; 79 | $this->userCreator = $userCreator; 80 | $this->attributeMapper = $attributeMapper; 81 | $this->tokenFactory = $tokenFactory; 82 | } 83 | 84 | /** 85 | * Attempts to authenticate a TokenInterface object. 86 | * 87 | * @param TokenInterface $token The TokenInterface instance to authenticate 88 | * 89 | * @return TokenInterface An authenticated TokenInterface instance, never null 90 | * 91 | * @throws AuthenticationException if the authentication fails 92 | */ 93 | public function authenticate(TokenInterface $token) 94 | { 95 | if ($token instanceof SamlSpResponseToken) { 96 | return $this->authenticateResponse($token); 97 | } elseif ($token instanceof SamlSpToken) { 98 | return $this->reauthenticate($token); 99 | } 100 | 101 | throw new \LogicException(sprintf('Unsupported token %s', get_class($token))); 102 | } 103 | 104 | private function authenticateResponse(SamlSpResponseToken $token) 105 | { 106 | $user = null; 107 | try { 108 | $user = $this->loadUser($token); 109 | } catch (UsernameNotFoundException $ex) { 110 | $user = $this->createUser($token); 111 | } 112 | 113 | if (null === $user && $this->force) { 114 | $user = $this->createDefaultUser($token); 115 | } 116 | 117 | if (null === $user) { 118 | $ex = new AuthenticationException('Unable to resolve user'); 119 | $ex->setToken($token); 120 | 121 | throw $ex; 122 | } 123 | 124 | if ($this->userChecker && $user instanceof UserInterface) { 125 | $this->userChecker->checkPreAuth($user); 126 | $this->userChecker->checkPostAuth($user); 127 | } 128 | 129 | $attributes = $this->getAttributes($token); 130 | 131 | if ($this->tokenFactory) { 132 | $result = $this->tokenFactory->create( 133 | $this->providerKey, 134 | $attributes, 135 | $user, 136 | $token 137 | ); 138 | } else { 139 | $result = new SamlSpToken( 140 | $user instanceof UserInterface ? $user->getRoles() : [], 141 | $this->providerKey, 142 | $attributes, 143 | $user 144 | ); 145 | } 146 | 147 | return $result; 148 | } 149 | 150 | private function reauthenticate(SamlSpToken $token) 151 | { 152 | $user = $token->getUser(); 153 | $result = new SamlSpToken( 154 | $user instanceof UserInterface ? $user->getRoles() : $token->getRoles(), 155 | $this->providerKey, 156 | $token->getAttributes(), 157 | $user 158 | ); 159 | 160 | return $result; 161 | } 162 | 163 | /** 164 | * Checks whether this provider supports the given token. 165 | * 166 | * @param TokenInterface $token A TokenInterface instance 167 | * 168 | * @return bool true if the implementation supports the Token, false otherwise 169 | */ 170 | public function supports(TokenInterface $token) 171 | { 172 | return $token instanceof SamlSpToken; 173 | } 174 | 175 | /** 176 | * @param SamlSpResponseToken $token 177 | * 178 | * @return UserInterface 179 | * 180 | * @throws UsernameNotFoundException 181 | */ 182 | private function loadUser(SamlSpResponseToken $token) 183 | { 184 | if (null === $this->usernameMapper || null === $this->userProvider) { 185 | throw new UsernameNotFoundException(); 186 | } 187 | 188 | $username = $this->usernameMapper->getUsername($token->getResponse()); 189 | 190 | $user = $this->userProvider->loadUserByUsername($username); 191 | 192 | if (false === $user instanceof UserInterface) { 193 | throw new \LogicException('User provider must return instance of UserInterface'); 194 | } 195 | 196 | return $user; 197 | } 198 | 199 | /** 200 | * @param SamlSpResponseToken $token 201 | * 202 | * @return null|UserInterface 203 | */ 204 | private function createUser(SamlSpResponseToken $token) 205 | { 206 | if (null === $this->userCreator) { 207 | return null; 208 | } 209 | 210 | $user = $this->userCreator->createUser($token->getResponse()); 211 | 212 | if ($user && false === $user instanceof UserInterface) { 213 | throw new \LogicException('User creator must return instance of UserInterface or null'); 214 | } 215 | 216 | return $user; 217 | } 218 | 219 | /** 220 | * @param SamlSpResponseToken $token 221 | * 222 | * @return string 223 | */ 224 | private function createDefaultUser(SamlSpResponseToken $token) 225 | { 226 | $result = null; 227 | if ($this->usernameMapper) { 228 | $result = $this->usernameMapper->getUsername($token->getResponse()); 229 | } 230 | if (!$result) { 231 | $result = 'Anon.'; 232 | } 233 | 234 | return $result; 235 | } 236 | 237 | /** 238 | * @param SamlSpResponseToken $token 239 | * 240 | * @return array 241 | */ 242 | private function getAttributes(SamlSpResponseToken $token) 243 | { 244 | if (null === $this->attributeMapper) { 245 | return []; 246 | } 247 | 248 | $attributes = $this->attributeMapper->getAttributes($token); 249 | 250 | if (false === is_array($attributes)) { 251 | throw new \LogicException('Attribute mapper must return array'); 252 | } 253 | 254 | return $attributes; 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Security/Authentication/Token/SamlSpResponseToken.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\Security\Authentication\Token; 13 | 14 | use LightSaml\Model\Protocol\Response; 15 | 16 | class SamlSpResponseToken extends SamlSpToken 17 | { 18 | /** @var Response */ 19 | private $response; 20 | 21 | public function __construct(Response $response, $providerKey) 22 | { 23 | parent::__construct([], $providerKey, [], null); 24 | 25 | $this->response = $response; 26 | 27 | $this->setAuthenticated(false); 28 | } 29 | 30 | /** 31 | * @return Response 32 | */ 33 | public function getResponse() 34 | { 35 | return $this->response; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Security/Authentication/Token/SamlSpToken.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\Security\Authentication\Token; 13 | 14 | use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; 15 | 16 | class SamlSpToken extends AbstractToken 17 | { 18 | /** @var string */ 19 | private $providerKey; 20 | 21 | /** 22 | * @param array $roles 23 | * @param string $providerKey 24 | * @param array $attributes 25 | * @param string|object $user 26 | */ 27 | public function __construct(array $roles, $providerKey, array $attributes, $user) 28 | { 29 | parent::__construct($roles); 30 | 31 | $this->providerKey = $providerKey; 32 | $this->setAttributes($attributes); 33 | if ($user) { 34 | $this->setUser($user); 35 | } 36 | 37 | $this->setAuthenticated(true); 38 | } 39 | 40 | /** 41 | * Returns the user credentials. 42 | * 43 | * @return mixed The user credentials 44 | */ 45 | public function getCredentials() 46 | { 47 | return ''; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getProviderKey() 54 | { 55 | return $this->providerKey; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Security/Authentication/Token/SamlSpTokenFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\Security\Authentication\Token; 13 | 14 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 15 | use Symfony\Component\Security\Core\User\UserInterface; 16 | 17 | class SamlSpTokenFactory implements SamlSpTokenFactoryInterface 18 | { 19 | /** 20 | * @param string $providerKey 21 | * @param array $attributes 22 | * @param mixed $user 23 | * @param SamlSpResponseToken $responseToken 24 | * 25 | * @return TokenInterface 26 | */ 27 | public function create($providerKey, array $attributes, $user, SamlSpResponseToken $responseToken) 28 | { 29 | $token = new SamlSpToken( 30 | $user instanceof UserInterface ? $user->getRoles() : [], 31 | $providerKey, 32 | $attributes, 33 | $user 34 | ); 35 | 36 | return $token; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Security/Authentication/Token/SamlSpTokenFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\Security\Authentication\Token; 13 | 14 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 15 | 16 | interface SamlSpTokenFactoryInterface 17 | { 18 | /** 19 | * @param string $providerKey 20 | * @param array $attributes 21 | * @param mixed $user 22 | * @param SamlSpResponseToken $responseToken 23 | * 24 | * @return TokenInterface 25 | */ 26 | public function create($providerKey, array $attributes, $user, SamlSpResponseToken $responseToken); 27 | } 28 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Security/Firewall/LightSamlSpListener.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\Security\Firewall; 13 | 14 | use LightSaml\Builder\Profile\ProfileBuilderInterface; 15 | use LightSaml\Model\Protocol\Response; 16 | use LightSaml\SpBundle\Security\Authentication\Token\SamlSpResponseToken; 17 | use Symfony\Component\HttpFoundation\Request; 18 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 19 | use Symfony\Component\Security\Core\Exception\AuthenticationException; 20 | use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener; 21 | 22 | class LightSamlSpListener extends AbstractAuthenticationListener 23 | { 24 | /** @var ProfileBuilderInterface */ 25 | private $profile; 26 | 27 | /** 28 | * @param ProfileBuilderInterface $profile 29 | * 30 | * @return LightSamlSpListener 31 | */ 32 | public function setProfile(ProfileBuilderInterface $profile) 33 | { 34 | $this->profile = $profile; 35 | 36 | return $this; 37 | } 38 | 39 | /** 40 | * Performs authentication. 41 | * 42 | * @param Request $request A Request instance 43 | * 44 | * @return TokenInterface|Response|null The authenticated token, null if full authentication is not possible, or a Response 45 | * 46 | * @throws AuthenticationException if the authentication fails 47 | */ 48 | protected function attemptAuthentication(Request $request) 49 | { 50 | $samlResponse = $this->receiveSamlResponse(); 51 | 52 | $token = new SamlSpResponseToken($samlResponse, $this->providerKey); 53 | 54 | $token = $this->authenticationManager->authenticate($token); 55 | 56 | return $token; 57 | } 58 | 59 | /** 60 | * @return \LightSaml\Model\Protocol\Response 61 | */ 62 | private function receiveSamlResponse() 63 | { 64 | $context = $this->profile->buildContext(); 65 | $action = $this->profile->buildAction(); 66 | 67 | $action->execute($context); 68 | 69 | return $context->getInboundMessage(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Security/User/AttributeMapperInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\Security\User; 13 | 14 | use LightSaml\SpBundle\Security\Authentication\Token\SamlSpResponseToken; 15 | 16 | interface AttributeMapperInterface 17 | { 18 | /** 19 | * @param SamlSpResponseToken $token 20 | * 21 | * @return array 22 | */ 23 | public function getAttributes(SamlSpResponseToken $token); 24 | } 25 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Security/User/SimpleAttributeMapper.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\Security\User; 13 | 14 | use LightSaml\Model\Assertion\Assertion; 15 | use LightSaml\Model\Assertion\Attribute; 16 | use LightSaml\Model\Assertion\AttributeStatement; 17 | use LightSaml\SpBundle\Security\Authentication\Token\SamlSpResponseToken; 18 | 19 | class SimpleAttributeMapper implements AttributeMapperInterface 20 | { 21 | /** 22 | * @param SamlSpResponseToken $token 23 | * 24 | * @return array 25 | */ 26 | public function getAttributes(SamlSpResponseToken $token) 27 | { 28 | $response = $token->getResponse(); 29 | $assertions = $response->getAllAssertions(); 30 | 31 | return array_reduce($assertions, [$this, 'resolveAttributesFromAssertion'], []); 32 | } 33 | 34 | /** 35 | * @param array $attributes 36 | * @param Assertion $assertion 37 | * 38 | * @return array 39 | */ 40 | private function resolveAttributesFromAssertion(array $attributes, Assertion $assertion) 41 | { 42 | $attributeStatements = $assertion->getAllAttributeStatements(); 43 | 44 | return array_reduce($attributeStatements, [$this, 'resolveAttributesFromAttributeStatement'], $attributes); 45 | } 46 | 47 | /** 48 | * @param array $attributes 49 | * @param AttributeStatement $attributeStatement 50 | * 51 | * @return array 52 | */ 53 | private function resolveAttributesFromAttributeStatement(array $attributes, AttributeStatement $attributeStatement) 54 | { 55 | $statementAttributes = $attributeStatement->getAllAttributes(); 56 | 57 | return array_reduce($statementAttributes, [$this, 'mapAttributeValues'], $attributes); 58 | } 59 | 60 | /** 61 | * @param array $attributes 62 | * @param Attribute $attribute 63 | * 64 | * @return array 65 | */ 66 | private function mapAttributeValues(array $attributes, Attribute $attribute) 67 | { 68 | $key = $attribute->getName(); 69 | $value = $attribute->getAllAttributeValues(); 70 | 71 | if (!array_key_exists($key, $attributes) && 1 === count($value)) { 72 | $value = array_shift($value); 73 | } 74 | 75 | if (array_key_exists($key, $attributes)) { 76 | $currentValue = (is_array($attributes[$key]) ? $attributes[$key] : [$attributes[$key]]); 77 | 78 | $value = array_merge($currentValue, $value); 79 | } 80 | 81 | $attributes[$key] = $value; 82 | 83 | return $attributes; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Security/User/SimpleUsernameMapper.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\Security\User; 13 | 14 | use LightSaml\Model\Assertion\Assertion; 15 | use LightSaml\Model\Protocol\Response; 16 | use LightSaml\SamlConstants; 17 | 18 | class SimpleUsernameMapper implements UsernameMapperInterface 19 | { 20 | const NAME_ID = '@name_id@'; 21 | 22 | /** @var string[] */ 23 | private $attributes; 24 | 25 | /** 26 | * @param string[] $attributes 27 | */ 28 | public function __construct(array $attributes) 29 | { 30 | $this->attributes = $attributes; 31 | } 32 | 33 | /** 34 | * @param Response $response 35 | * 36 | * @return string|null 37 | */ 38 | public function getUsername(Response $response) 39 | { 40 | foreach ($response->getAllAssertions() as $assertion) { 41 | $username = $this->getUsernameFromAssertion($assertion); 42 | if ($username) { 43 | return $username; 44 | } 45 | } 46 | 47 | return null; 48 | } 49 | 50 | /** 51 | * @param Assertion $assertion 52 | * 53 | * @return null|string 54 | */ 55 | private function getUsernameFromAssertion(Assertion $assertion) 56 | { 57 | foreach ($this->attributes as $attributeName) { 58 | if (self::NAME_ID == $attributeName) { 59 | if ($assertion->getSubject() && 60 | $assertion->getSubject()->getNameID() && 61 | $assertion->getSubject()->getNameID()->getValue() && 62 | SamlConstants::NAME_ID_FORMAT_TRANSIENT != $assertion->getSubject()->getNameID()->getFormat() 63 | ) { 64 | return $assertion->getSubject()->getNameID()->getValue(); 65 | } 66 | } else { 67 | foreach ($assertion->getAllAttributeStatements() as $attributeStatement) { 68 | $attribute = $attributeStatement->getFirstAttributeByName($attributeName); 69 | if ($attribute && $attribute->getFirstAttributeValue()) { 70 | return $attribute->getFirstAttributeValue(); 71 | } 72 | } 73 | } 74 | } 75 | 76 | return null; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Security/User/UserCreatorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\Security\User; 13 | 14 | use LightSaml\Model\Protocol\Response; 15 | use Symfony\Component\Security\Core\User\UserInterface; 16 | 17 | interface UserCreatorInterface 18 | { 19 | /** 20 | * @param Response $response 21 | * 22 | * @return UserInterface|null 23 | */ 24 | public function createUser(Response $response); 25 | } 26 | -------------------------------------------------------------------------------- /src/LightSaml/SpBundle/Security/User/UsernameMapperInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace LightSaml\SpBundle\Security\User; 13 | 14 | use LightSaml\Model\Protocol\Response; 15 | 16 | interface UsernameMapperInterface 17 | { 18 | /** 19 | * @param Response $response 20 | * 21 | * @return string|null 22 | */ 23 | public function getUsername(Response $response); 24 | } 25 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/Controller/DefaultControllerTest.php: -------------------------------------------------------------------------------- 1 | setContainer($containerMock = $this->getContainerMock()); 14 | 15 | $containerMock->expects($this->any()) 16 | ->method('get') 17 | ->with('ligthsaml.profile.metadata') 18 | ->willReturn($profileBuilderMock = $this->getProfileBuilderMock()); 19 | 20 | $actionMock = $this->getActionMock(); 21 | $contextMock = $this->getContextMock(); 22 | 23 | $profileBuilderMock->expects($this->any()) 24 | ->method('buildContext') 25 | ->willReturn($contextMock); 26 | $profileBuilderMock->expects($this->any()) 27 | ->method('buildAction') 28 | ->willReturn($actionMock); 29 | 30 | $contextMock->expects($this->once()) 31 | ->method('getHttpResponseContext') 32 | ->willReturn($httpResponseContext = $this->getHttpResponseContextMock()); 33 | 34 | $httpResponseContext->expects($this->once()) 35 | ->method('getResponse') 36 | ->willReturn($expectedResponse = new Response('')); 37 | 38 | $actualResponse = $controller->metadataAction(); 39 | 40 | $this->assertSame($expectedResponse, $actualResponse); 41 | } 42 | 43 | /** 44 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\DependencyInjection\ContainerInterface 45 | */ 46 | private function getContainerMock() 47 | { 48 | return $this->getMockBuilder(\Symfony\Component\DependencyInjection\ContainerInterface::class)->getMock(); 49 | } 50 | 51 | /** 52 | * @return \PHPUnit_Framework_MockObject_MockObject|\LightSaml\Builder\Profile\ProfileBuilderInterface 53 | */ 54 | private function getProfileBuilderMock() 55 | { 56 | return $this->getMockBuilder(\LightSaml\Builder\Profile\ProfileBuilderInterface::class)->getMock(); 57 | } 58 | 59 | /** 60 | * @return \PHPUnit_Framework_MockObject_MockObject|\LightSaml\Context\Profile\ProfileContext 61 | */ 62 | private function getContextMock() 63 | { 64 | return $this->getMockBuilder(\LightSaml\Context\Profile\ProfileContext::class) 65 | ->disableOriginalConstructor() 66 | ->getMock(); 67 | } 68 | 69 | /** 70 | * @return \PHPUnit_Framework_MockObject_MockObject|\LightSaml\Action\ActionInterface 71 | */ 72 | private function getActionMock() 73 | { 74 | return $this->getMockBuilder(\LightSaml\Action\ActionInterface::class)->getMock(); 75 | } 76 | 77 | /** 78 | * @return \PHPUnit_Framework_MockObject_MockObject|\LightSaml\Context\Profile\HttpResponseContext 79 | */ 80 | private function getHttpResponseContextMock() 81 | { 82 | return $this->getMockBuilder(\LightSaml\Context\Profile\HttpResponseContext::class)->getMock(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/DependencyInjection/ConfigurationTest.php: -------------------------------------------------------------------------------- 1 | processConfiguration($emptyConfig); 16 | } 17 | 18 | public function test_allow_set_username_mapper_scalar_array() 19 | { 20 | $config = [ 21 | 'light_saml_sp' => [ 22 | 'username_mapper' => [ 23 | 'a', 'b', 'c', 24 | ], 25 | ], 26 | ]; 27 | $this->processConfiguration($config); 28 | } 29 | 30 | public function test_sets_default_username_mapper() 31 | { 32 | $config = ['light_saml_sp' => []]; 33 | $processedConfig = $this->processConfiguration($config); 34 | $this->assertArrayHasKey('username_mapper', $processedConfig); 35 | $this->assertTrue(is_array($processedConfig['username_mapper'])); 36 | $this->assertEquals( 37 | [ 38 | ClaimTypes::EMAIL_ADDRESS, 39 | ClaimTypes::ADFS_1_EMAIL, 40 | ClaimTypes::COMMON_NAME, 41 | ClaimTypes::WINDOWS_ACCOUNT_NAME, 42 | 'urn:oid:0.9.2342.19200300.100.1.3', 43 | 'uid', 44 | 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6', 45 | SimpleUsernameMapper::NAME_ID, 46 | ], 47 | $processedConfig['username_mapper'] 48 | ); 49 | } 50 | 51 | /** 52 | * @param array $configs 53 | * 54 | * @return array 55 | */ 56 | protected function processConfiguration(array $configs) 57 | { 58 | $configuration = new Configuration(); 59 | $processor = new Processor(); 60 | 61 | return $processor->processConfiguration($configuration, $configs); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/DependencyInjection/LightSamlSpExtensionTest.php: -------------------------------------------------------------------------------- 1 | load($configs, $containerBuilder); 17 | } 18 | 19 | public function loads_service_provider() 20 | { 21 | return [ 22 | ['security.authentication.listener.lightsaml_sp'], 23 | ['security.authentication.provider.lightsaml_sp'], 24 | ['lightsaml_sp.username_mapper.simple'], 25 | ['lightsaml_sp.attribute_mapper.simple'], 26 | ['lightsaml_sp.token_factory'], 27 | ]; 28 | } 29 | /** 30 | * @dataProvider loads_service_provider 31 | */ 32 | public function test_loads_service($serviceId) 33 | { 34 | $configs = array(); 35 | $containerBuilder = new ContainerBuilder(new ParameterBag()); 36 | $extension = new LightSamlSpExtension(); 37 | $extension->load($configs, $containerBuilder); 38 | 39 | $this->assertTrue($containerBuilder->hasDefinition($serviceId)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/DependencyInjection/Security/Factory/LightSamlSpFactoryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('light_saml_sp', $factory->getKey()); 23 | } 24 | 25 | public function test_position() 26 | { 27 | $factory = new LightSamlSpFactory(); 28 | $this->assertEquals('form', $factory->getPosition()); 29 | } 30 | 31 | public function configuration_provider() 32 | { 33 | return [ 34 | ['force', BooleanNode::class, true], 35 | ['username_mapper', ScalarNode::class, 'lightsaml_sp.username_mapper.simple'], 36 | ['user_creator', ScalarNode::class, null], 37 | ['attribute_mapper', ScalarNode::class, 'lightsaml_sp.attribute_mapper.simple'], 38 | ['token_factory', ScalarNode::class, 'lightsaml_sp.token_factory'], 39 | ]; 40 | } 41 | 42 | /** 43 | * @dataProvider configuration_provider 44 | */ 45 | public function test_configuration($configurationName, $type, $defaultValue) 46 | { 47 | $factory = new LightSamlSpFactory(); 48 | $treeBuilder = new TreeBuilder(); 49 | $factory->addConfiguration($treeBuilder->root('name')); 50 | $children = $treeBuilder->buildTree()->getChildren(); 51 | $this->assertArrayHasKey($configurationName, $children); 52 | $this->assertInstanceOf($type, $children['force']); 53 | 54 | $this->assertEquals($defaultValue, $children[$configurationName]->getDefaultValue()); 55 | } 56 | 57 | public function test_create_returns_array_with_provider_listener_and_entry_point() 58 | { 59 | $containerBuilder = new ContainerBuilder(new ParameterBag()); 60 | $config = $this->getDefaultConfig(); 61 | $factory = new LightSamlSpFactory(); 62 | $result = $factory->create($containerBuilder, 'main', $config, 'user.provider.id', $defaultEntryPoint = null); 63 | $this->assertInternalType('array', $result); 64 | $this->assertCount(3, $result); 65 | $this->assertContainsOnly('string', $result); 66 | } 67 | 68 | public function test_returns_lightsaml_provider_with_sufix() 69 | { 70 | $containerBuilder = new ContainerBuilder(new ParameterBag()); 71 | $config = $this->getDefaultConfig(); 72 | $factory = new LightSamlSpFactory(); 73 | list($providerId) = $factory->create($containerBuilder, 'main', $config, 'user.provider.id', $defaultEntryPoint = null); 74 | $this->assertStringStartsWith('security.authentication.provider.lightsaml_sp', $providerId); 75 | $this->assertStringEndsWith('.main', $providerId); 76 | } 77 | 78 | public function test_returns_lightsaml_listener_with_sufix() 79 | { 80 | $containerBuilder = new ContainerBuilder(new ParameterBag()); 81 | $config = $this->getDefaultConfig(); 82 | $factory = new LightSamlSpFactory(); 83 | list(, $listenerId) = $factory->create($containerBuilder, 'main', $config, 'user.provider.id', $defaultEntryPoint = null); 84 | $this->assertStringStartsWith('security.authentication.listener.lightsaml_sp', $listenerId); 85 | $this->assertStringEndsWith('.main', $listenerId); 86 | } 87 | 88 | public function test_returns_entry_point() 89 | { 90 | $containerBuilder = new ContainerBuilder(new ParameterBag()); 91 | $config = $this->getDefaultConfig(); 92 | $factory = new LightSamlSpFactory(); 93 | list(, , $entryPointId) = $factory->create($containerBuilder, 'main', $config, 'user.provider.id', $defaultEntryPoint = null); 94 | $this->assertStringStartsWith('security.authentication.form_entry_point', $entryPointId); 95 | $this->assertStringEndsWith('.main', $entryPointId); 96 | } 97 | 98 | // TODO test values injected on provider in function createAuthProvider() 99 | 100 | public function test_creates_auth_provider_service() 101 | { 102 | $containerBuilder = new ContainerBuilder(new ParameterBag()); 103 | $config = $this->getDefaultConfig(); 104 | $factory = new LightSamlSpFactory(); 105 | list($providerId) = $factory->create($containerBuilder, 'main', $config, 'user.provider.id', $defaultEntryPoint = null); 106 | $this->assertTrue($containerBuilder->hasDefinition($providerId)); 107 | } 108 | 109 | public function test_injects_user_provider_to_auth_provider() 110 | { 111 | $containerBuilder = new ContainerBuilder(new ParameterBag()); 112 | $config = $this->getDefaultConfig(); 113 | $factory = new LightSamlSpFactory(); 114 | list($providerId) = $factory->create($containerBuilder, 'main', $config, $userProvider = 'user.provider.id', $defaultEntryPoint = null); 115 | $definition = $containerBuilder->getDefinition($providerId); 116 | /** @var \Symfony\Component\DependencyInjection\Reference $reference */ 117 | $reference = $definition->getArgument(1); 118 | $this->assertInstanceOf(\Symfony\Component\DependencyInjection\Reference::class, $reference); 119 | $this->assertEquals($userProvider, (string) $reference); 120 | } 121 | 122 | public function test_injects_username_mapper_to_auth_provider() 123 | { 124 | $containerBuilder = new ContainerBuilder(new ParameterBag()); 125 | $config = $this->getDefaultConfig(); 126 | $factory = new LightSamlSpFactory(); 127 | list($providerId) = $factory->create($containerBuilder, 'main', $config, $userProvider = 'user.provider.id', $defaultEntryPoint = null); 128 | $definition = $containerBuilder->getDefinition($providerId); 129 | /** @var \Symfony\Component\DependencyInjection\Reference $reference */ 130 | $reference = $definition->getArgument(4); 131 | $this->assertInstanceOf(\Symfony\Component\DependencyInjection\Reference::class, $reference); 132 | $this->assertEquals($config['username_mapper'], (string) $reference); 133 | } 134 | 135 | public function test_injects_user_creator_to_auth_provider() 136 | { 137 | $containerBuilder = new ContainerBuilder(new ParameterBag()); 138 | $config = $this->getDefaultConfig(); 139 | $factory = new LightSamlSpFactory(); 140 | list($providerId) = $factory->create($containerBuilder, 'main', $config, $userProvider = 'user.provider.id', $defaultEntryPoint = null); 141 | $definition = $containerBuilder->getDefinition($providerId); 142 | /** @var \Symfony\Component\DependencyInjection\Reference $reference */ 143 | $reference = $definition->getArgument(5); 144 | $this->assertInstanceOf(\Symfony\Component\DependencyInjection\Reference::class, $reference); 145 | $this->assertEquals($config['user_creator'], (string) $reference); 146 | } 147 | 148 | public function test_injects_attribute_mapper_to_auth_provider() 149 | { 150 | $containerBuilder = new ContainerBuilder(new ParameterBag()); 151 | $config = $this->getDefaultConfig(); 152 | $factory = new LightSamlSpFactory(); 153 | list($providerId) = $factory->create($containerBuilder, 'main', $config, $userProvider = 'user.provider.id', $defaultEntryPoint = null); 154 | $definition = $containerBuilder->getDefinition($providerId); 155 | /** @var \Symfony\Component\DependencyInjection\Reference $reference */ 156 | $reference = $definition->getArgument(6); 157 | $this->assertInstanceOf(\Symfony\Component\DependencyInjection\Reference::class, $reference); 158 | $this->assertEquals($config['attribute_mapper'], (string) $reference); 159 | } 160 | 161 | public function test_injects_token_factory_to_auth_provider() 162 | { 163 | $containerBuilder = new ContainerBuilder(new ParameterBag()); 164 | $config = $this->getDefaultConfig(); 165 | $factory = new LightSamlSpFactory(); 166 | list($providerId) = $factory->create($containerBuilder, 'main', $config, $userProvider = 'user.provider.id', $defaultEntryPoint = null); 167 | $definition = $containerBuilder->getDefinition($providerId); 168 | /** @var \Symfony\Component\DependencyInjection\Reference $reference */ 169 | $reference = $definition->getArgument(7); 170 | $this->assertInstanceOf(\Symfony\Component\DependencyInjection\Reference::class, $reference); 171 | $this->assertEquals($config['token_factory'], (string) $reference); 172 | } 173 | 174 | /** 175 | * @return array 176 | */ 177 | private function getDefaultConfig() 178 | { 179 | return [ 180 | 'force' => true, 181 | 'username_mapper' => 'lightsaml_sp.username_mapper.simple', 182 | 'token_factory' => 'lightsaml_sp.token_factory', 183 | 'user_creator' => 'some.user.creator', 184 | 'attribute_mapper' => 'some.attribute.mapper', 185 | 'remember_me' => true, 186 | 'provider' => 'some.provider', 187 | 'success_handler' => 'success_handler', 188 | 'failure_handler' => 'failure_handler', 189 | 'check_path' => '/login_check', 190 | 'use_forward' => false, 191 | 'require_previous_session' => true, 192 | 'always_use_default_target_path' => false, 193 | 'default_target_path' => '/', 194 | 'login_path' => '/login', 195 | 'target_path_parameter' => '_target_path', 196 | 'use_referer' => false, 197 | 'failure_path' => null, 198 | 'failure_forward' => false, 199 | 'failure_path_parameter' => '_failure_path', 200 | ]; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/Functional/FunctionalTest.php: -------------------------------------------------------------------------------- 1 | remove(__DIR__.'/app/cache'); 24 | } 25 | 26 | 27 | public function test_metadata() 28 | { 29 | $client = static::createClient(); 30 | 31 | $client->request('GET', '/saml/metadata.xml'); 32 | 33 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 34 | 35 | $xml = $client->getResponse()->getContent(); 36 | 37 | $root = new \SimpleXMLElement($xml); 38 | $this->assertEquals('EntityDescriptor', $root->getName()); 39 | $this->assertEquals(self::OWN_ENTITY_ID, $root['entityID']); 40 | $this->assertEquals(1, $root->SPSSODescriptor->count()); 41 | $this->assertEquals(2, $root->SPSSODescriptor->KeyDescriptor->count()); 42 | $this->assertEquals(1, $root->SPSSODescriptor->AssertionConsumerService->count()); 43 | } 44 | 45 | public function test_discovery() 46 | { 47 | $client = static::createClient(); 48 | 49 | $crawler = $client->request('GET', '/saml/discovery'); 50 | 51 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 52 | 53 | $idpCrawler = $crawler->filter('a[data-idp]'); 54 | $this->assertEquals(3, $idpCrawler->count()); 55 | $arr = []; 56 | foreach ($idpCrawler as $idp) { 57 | $arr[$idp->getAttribute('data-idp')] = 1; 58 | } 59 | $this->assertArrayHasKey('https://openidp.feide.no', $arr); 60 | $this->assertArrayHasKey('https://localhost/lightSAML/lightSAML-IDP', $arr); 61 | $this->assertArrayHasKey('https://idp.testshib.org/idp/shibboleth', $arr); 62 | } 63 | 64 | public function test_login() 65 | { 66 | $client = static::createClient(); 67 | $client->getContainer()->set('session', $sessionMock = $this->getMockBuilder(SessionInterface::class)->getMock()); 68 | 69 | $crawler = $client->request('GET', '/saml/login?idp=https://localhost/lightSAML/lightSAML-IDP'); 70 | 71 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 72 | 73 | $crawlerForm = $crawler->filter('form'); 74 | $this->assertEquals(1, $crawlerForm->count()); 75 | $this->assertEquals('https://localhost/lightsaml/lightSAML-IDP/web/idp/login.php', $crawlerForm->first()->attr('action')); 76 | 77 | $crawlerSamlRequest = $crawler->filter('input[name="SAMLRequest"]'); 78 | $this->assertEquals(1, $crawlerSamlRequest->count()); 79 | $code = $crawlerSamlRequest->first()->attr('value'); 80 | $xml = base64_decode($code); 81 | 82 | $root = new \SimpleXMLElement($xml); 83 | $this->assertEquals('AuthnRequest', $root->getName()); 84 | $this->assertEquals('https://localhost/lightsaml/lightSAML-IDP/web/idp/login.php', $root['Destination']); 85 | $this->assertEquals(1, $root->children('saml', true)->Issuer->count()); 86 | $this->assertEquals(self::OWN_ENTITY_ID, (string)$root->children('saml', true)->Issuer); 87 | } 88 | 89 | public function test_sessions() 90 | { 91 | $ssoState = new SsoState(); 92 | $ssoState->addSsoSession((new SsoSessionState())->setIdpEntityId('idp1')->setSpEntityId('sp1')); 93 | $ssoState->addSsoSession((new SsoSessionState())->setIdpEntityId('idp2')->setSpEntityId('sp2')); 94 | 95 | $ssoStateStoreMock = $this->getMockBuilder(SsoStateStoreInterface::class)->getMock(); 96 | $ssoStateStoreMock->method('get') 97 | ->willReturn($ssoState); 98 | 99 | $client = static::createClient(); 100 | $client->getContainer()->set('lightsaml.store.sso_state', $ssoStateStoreMock); 101 | 102 | $crawler = $client->request('GET', '/saml/sessions'); 103 | 104 | $crawlerSessions = $crawler->filter('ul[data-session]'); 105 | $this->assertEquals(2, $crawlerSessions->count()); 106 | 107 | $this->assertEquals('idp1', $crawlerSessions->first()->filter('li[data-idp]')->attr('data-idp')); 108 | $this->assertEquals('sp1', $crawlerSessions->first()->filter('li[data-sp]')->attr('data-sp')); 109 | $this->assertEquals('idp2', $crawlerSessions->last()->filter('li[data-idp]')->attr('data-idp')); 110 | $this->assertEquals('sp2', $crawlerSessions->last()->filter('li[data-sp]')->attr('data-sp')); 111 | 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/Functional/app/AppKernel.php: -------------------------------------------------------------------------------- 1 | load(__DIR__.'/config.yml'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/Functional/app/config.yml: -------------------------------------------------------------------------------- 1 | framework: 2 | secret: secret 3 | test: ~ 4 | router: { resource: "%kernel.root_dir%/routing.yml" } 5 | csrf_protection: ~ 6 | templating: { engines: ['twig'] } 7 | session: 8 | storage_id: session.mock_storage 9 | 10 | monolog: 11 | handlers: 12 | main: 13 | type: fingers_crossed 14 | action_level: error 15 | handler: nested 16 | nested: 17 | type: stream 18 | path: "%kernel.logs_dir%/%kernel.environment%.log" 19 | level: debug 20 | 21 | security: 22 | providers: 23 | dummy: 24 | memory: ~ 25 | firewalls: 26 | dummy: 27 | provider: dummy 28 | anonymous: ~ 29 | 30 | light_saml_symfony_bridge: 31 | own: 32 | entity_id: https://localhost/lightSAML/SPBundle 33 | credentials: 34 | - 35 | certificate: "%kernel.root_dir%/../../../../../../vendor/lightsaml/lightsaml/web/sp/saml.crt" 36 | key: "%kernel.root_dir%/../../../../../../vendor/lightsaml/lightsaml/web/sp/saml.key" 37 | password: ~ 38 | party: 39 | idp: 40 | files: 41 | - "%kernel.root_dir%/../../../../../../vendor/lightsaml/lightsaml/web/sp/openidp.feide.no.xml" 42 | - "%kernel.root_dir%/../../../../../../vendor/lightsaml/lightsaml/web/sp/localhost-lightsaml-lightsaml-idp.xml" 43 | - "%kernel.root_dir%/../../../../../../vendor/lightsaml/lightsaml/web/sp/testshib-providers.xml" 44 | store: 45 | id_state: id_store 46 | 47 | services: 48 | session.mock_storage: 49 | class: Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage 50 | id_store: 51 | class: LightSaml\Store\Id\IdArrayStore 52 | lightsaml.store.sso_state: 53 | class: LightSaml\Store\Sso\SsoStateFixedStore 54 | public: true -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/Functional/app/routing.yml: -------------------------------------------------------------------------------- 1 | lightsaml_sp: 2 | resource: "@LightSamlSpBundle/Resources/config/routing.yml" 3 | prefix: saml 4 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/LightSamlSpBundleTest.php: -------------------------------------------------------------------------------- 1 | getContainerBuilderMock(); 14 | $containerBuilderMock->expects($this->once()) 15 | ->method('getExtension') 16 | ->with('security') 17 | ->willReturn($extensionMock = $this->getExtensionMock()); 18 | 19 | $extensionMock->expects($this->once()) 20 | ->method('addSecurityListenerFactory') 21 | ->with($this->isInstanceOf(\LightSaml\SpBundle\DependencyInjection\Security\Factory\LightSamlSpFactory::class)); 22 | 23 | $bundle->build($containerBuilderMock); 24 | } 25 | 26 | /** 27 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\DependencyInjection\ContainerBuilder 28 | */ 29 | private function getContainerBuilderMock() 30 | { 31 | return $this->getMockBuilder(\Symfony\Component\DependencyInjection\ContainerBuilder::class)->getMock(); 32 | } 33 | 34 | /** 35 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension 36 | */ 37 | private function getExtensionMock() 38 | { 39 | return $this->getMockBuilder(\Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension::class)->getMock(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/Security/Authentication/Provider/LightsSamlSpAuthenticationProviderTest.php: -------------------------------------------------------------------------------- 1 | getUserProviderMock(), 36 | false, 37 | $this->getUserCheckerMock(), 38 | $this->getUsernameMapperMock(), 39 | $this->getUserCreatorMock(), 40 | $this->getAttributeMapperMock() 41 | ); 42 | } 43 | 44 | public function test_supports_saml_sp_response_token() 45 | { 46 | $provider = new LightsSamlSpAuthenticationProvider($providerKey = 'main'); 47 | $this->assertTrue($provider->supports(new SamlSpResponseToken(new Response(), $providerKey))); 48 | } 49 | 50 | public function test_supports_saml_sp_token() 51 | { 52 | $provider = new LightsSamlSpAuthenticationProvider($providerKey = 'main'); 53 | $this->assertTrue($provider->supports(new SamlSpToken([], $providerKey, [], 'user'))); 54 | } 55 | 56 | public function test_supports_reauthentication() 57 | { 58 | $provider = new LightsSamlSpAuthenticationProvider( 59 | $providerKey = 'main', 60 | $userProviderMock = $this->getUserProviderMock(), 61 | false, 62 | null, 63 | $usernameMapperMock = $this->getUsernameMapperMock() 64 | ); 65 | 66 | $user = 'some.user'; 67 | $roles = ['ROLE_USER']; 68 | $attributes = ['a' =>1, 'b' => 'bbb']; 69 | $inToken = new SamlSpToken($roles, $providerKey, $attributes, $user); 70 | 71 | /** @var SamlSpToken $outToken */ 72 | $outToken = $provider->authenticate($inToken); 73 | $this->assertInstanceOf(SamlSpToken::class, $outToken); 74 | $this->assertEquals($user, $outToken->getUser()); 75 | $this->assertEquals($roles, array_map(function ($r) { return $r->getRole(); }, $outToken->getRoles())); 76 | $this->assertEquals($providerKey, $outToken->getProviderKey()); 77 | $this->assertEquals($attributes, $outToken->getAttributes()); 78 | } 79 | 80 | public function test_creates_authenticated_token_with_user_and_his_roles() 81 | { 82 | $provider = new LightsSamlSpAuthenticationProvider( 83 | $providerKey = 'main', 84 | $userProviderMock = $this->getUserProviderMock(), 85 | false, 86 | null, 87 | $usernameMapperMock = $this->getUsernameMapperMock() 88 | ); 89 | 90 | $user = $this->getUserMock(); 91 | $user->expects($this->any()) 92 | ->method('getRoles') 93 | ->willReturn($expectedRoles = ['foo', 'bar']); 94 | 95 | $usernameMapperMock->expects($this->once()) 96 | ->method('getUsername') 97 | ->willReturn($expectedUsername = 'some.username'); 98 | 99 | $userProviderMock->expects($this->once()) 100 | ->method('loadUserByUsername') 101 | ->with($expectedUsername) 102 | ->willReturn($user); 103 | 104 | $authenticatedToken = $provider->authenticate(new SamlSpResponseToken(new Response(), $providerKey)); 105 | 106 | $this->assertInstanceOf(SamlSpToken::class, $authenticatedToken); 107 | $this->assertTrue($authenticatedToken->isAuthenticated()); 108 | $this->assertEquals($expectedRoles, array_map(function (Role $role) { 109 | return $role->getRole(); 110 | }, $authenticatedToken->getRoles())); 111 | $this->assertSame($user, $authenticatedToken->getUser()); 112 | } 113 | 114 | public function test_calls_user_creator_if_user_does_not_exist() 115 | { 116 | $provider = new LightsSamlSpAuthenticationProvider( 117 | $providerKey = 'main', 118 | null, 119 | false, 120 | null, 121 | null, 122 | $userCreatorMock = $this->getUserCreatorMock() 123 | ); 124 | 125 | $user = $this->getUserMock(); 126 | $user->expects($this->any()) 127 | ->method('getRoles') 128 | ->willReturn($expectedRoles = ['foo', 'bar']); 129 | 130 | $token = new SamlSpResponseToken(new Response(), $providerKey); 131 | 132 | $userCreatorMock->expects($this->once()) 133 | ->method('createUser') 134 | ->with($token->getResponse()) 135 | ->willReturn($user); 136 | 137 | $authenticatedToken = $provider->authenticate($token); 138 | 139 | $this->assertInstanceOf(SamlSpToken::class, $authenticatedToken); 140 | $this->assertTrue($authenticatedToken->isAuthenticated()); 141 | $this->assertEquals($expectedRoles, array_map(function (Role $role) { 142 | return $role->getRole(); 143 | }, $authenticatedToken->getRoles())); 144 | $this->assertSame($user, $authenticatedToken->getUser()); 145 | } 146 | 147 | /** 148 | * @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationException 149 | * @expectedExceptionMessage Unable to resolve user 150 | */ 151 | public function test_throws_authentication_exception_if_user_does_not_exists_and_its_not_created() 152 | { 153 | $provider = new LightsSamlSpAuthenticationProvider( 154 | $providerKey = 'main', 155 | $userProviderMock = $this->getUserProviderMock(), 156 | false, 157 | null, 158 | $usernameMapperMock = $this->getUsernameMapperMock(), 159 | $userCreatorMock = $this->getUserCreatorMock() 160 | ); 161 | 162 | $user = $this->getUserMock(); 163 | $user->expects($this->any()) 164 | ->method('getRoles') 165 | ->willReturn($expectedRoles = ['foo', 'bar']); 166 | 167 | $usernameMapperMock->expects($this->once()) 168 | ->method('getUsername') 169 | ->willReturn($expectedUsername = 'some.username'); 170 | 171 | $userProviderMock->expects($this->once()) 172 | ->method('loadUserByUsername') 173 | ->with($expectedUsername) 174 | ->willThrowException(new UsernameNotFoundException()); 175 | 176 | $token = new SamlSpResponseToken(new Response(), $providerKey); 177 | 178 | $userCreatorMock->expects($this->once()) 179 | ->method('createUser') 180 | ->with($token->getResponse()) 181 | ->willReturn(null); 182 | 183 | $provider->authenticate($token); 184 | } 185 | 186 | public function test_creates_authenticated_token_with_default_user_from_name_id_when_force_is_true_and_name_id_format_not_transient() 187 | { 188 | $provider = new LightsSamlSpAuthenticationProvider( 189 | $providerKey = 'main', 190 | $userProviderMock = $this->getUserProviderMock(), 191 | true, 192 | null, 193 | $usernameMapperMock = $this->getUsernameMapperMock(), 194 | $userCreatorMock = $this->getUserCreatorMock() 195 | ); 196 | 197 | $userProviderMock->expects($this->once()) 198 | ->method('loadUserByUsername') 199 | ->willThrowException(new UsernameNotFoundException()); 200 | 201 | $token = new SamlSpResponseToken(new Response(), $providerKey); 202 | 203 | $userCreatorMock->expects($this->once()) 204 | ->method('createUser') 205 | ->with($token->getResponse()) 206 | ->willReturn(null); 207 | 208 | $token->getResponse()->addAssertion( 209 | (new Assertion())->setSubject( 210 | (new Subject())->setNameID( 211 | new NameID($nameIdValue = 'some.name.id', SamlConstants::NAME_ID_FORMAT_PERSISTENT) 212 | ) 213 | ) 214 | ); 215 | 216 | $usernameMapperMock->expects($this->exactly(2)) 217 | ->method('getUsername') 218 | ->willReturn($nameIdValue); 219 | 220 | $authenticatedToken = $provider->authenticate($token); 221 | 222 | $this->assertTrue($authenticatedToken->isAuthenticated()); 223 | $this->assertTrue(is_string($authenticatedToken->getUser())); 224 | $this->assertEquals($nameIdValue, $authenticatedToken->getUser()); 225 | } 226 | 227 | public function test_creates_authenticated_token_with_default_user_from_attribute_email_when_force_is_true_and_no_name_id() 228 | { 229 | $provider = new LightsSamlSpAuthenticationProvider( 230 | $providerKey = 'main', 231 | $userProviderMock = $this->getUserProviderMock(), 232 | true, 233 | null, 234 | $usernameMapperMock = $this->getUsernameMapperMock(), 235 | $userCreatorMock = $this->getUserCreatorMock() 236 | ); 237 | 238 | $userProviderMock->expects($this->once()) 239 | ->method('loadUserByUsername') 240 | ->willThrowException(new UsernameNotFoundException()); 241 | 242 | $token = new SamlSpResponseToken(new Response(), $providerKey); 243 | 244 | $userCreatorMock->expects($this->once()) 245 | ->method('createUser') 246 | ->with($token->getResponse()) 247 | ->willReturn(null); 248 | 249 | $token->getResponse()->addAssertion( 250 | (new Assertion())->addItem( 251 | (new AttributeStatement()) 252 | ->addAttribute(new Attribute(ClaimTypes::PPID, 'foo')) 253 | ->addAttribute(new Attribute(ClaimTypes::EMAIL_ADDRESS, $email = 'email@domain.com')) 254 | ) 255 | ); 256 | 257 | $usernameMapperMock->expects($this->exactly(2)) 258 | ->method('getUsername') 259 | ->willReturn($email); 260 | 261 | $authenticatedToken = $provider->authenticate($token); 262 | 263 | $this->assertTrue($authenticatedToken->isAuthenticated()); 264 | $this->assertTrue(is_string($authenticatedToken->getUser())); 265 | $this->assertEquals($email, $authenticatedToken->getUser()); 266 | } 267 | 268 | public function test_creates_authenticated_token_with_attributes_from_attribute_mapper() 269 | { 270 | $provider = new LightsSamlSpAuthenticationProvider( 271 | $providerKey = 'main', 272 | $userProviderMock = $this->getUserProviderMock(), 273 | false, 274 | null, 275 | $usernameMapperMock = $this->getUsernameMapperMock(), 276 | null, 277 | $attributeMapperMock = $this->getAttributeMapperMock() 278 | ); 279 | 280 | $user = $this->getUserMock(); 281 | $user->expects($this->any()) 282 | ->method('getRoles') 283 | ->willReturn(['foo', 'bar']); 284 | 285 | $userProviderMock->expects($this->once()) 286 | ->method('loadUserByUsername') 287 | ->willReturn($user); 288 | 289 | $attributeMapperMock->expects($this->once()) 290 | ->method('getAttributes') 291 | ->with($this->isInstanceOf(SamlSpToken::class)) 292 | ->willReturn($expectedAttributes = ['a', 'b', 'c']); 293 | 294 | $authenticatedToken = $provider->authenticate(new SamlSpResponseToken(new Response(), $providerKey)); 295 | 296 | $this->assertInstanceOf(SamlSpToken::class, $authenticatedToken); 297 | $this->assertEquals($expectedAttributes, $authenticatedToken->getAttributes()); 298 | } 299 | 300 | public function test_calls_user_checker_if_provided() 301 | { 302 | $provider = new LightsSamlSpAuthenticationProvider( 303 | $providerKey = 'main', 304 | $userProviderMock = $this->getUserProviderMock(), 305 | false, 306 | $userCheckerMock = $this->getUserCheckerMock(), 307 | $usernameMapperMock = $this->getUsernameMapperMock() 308 | ); 309 | 310 | $user = $this->getUserMock(); 311 | $user->expects($this->any()) 312 | ->method('getRoles') 313 | ->willReturn($expectedRoles = ['foo', 'bar']); 314 | 315 | $userProviderMock->expects($this->once()) 316 | ->method('loadUserByUsername') 317 | ->willReturn($user); 318 | 319 | $userCheckerMock->expects($this->once()) 320 | ->method('checkPreAuth') 321 | ->with($user); 322 | 323 | $userCheckerMock->expects($this->once()) 324 | ->method('checkPostAuth') 325 | ->with($user); 326 | 327 | $provider->authenticate(new SamlSpResponseToken(new Response(), $providerKey)); 328 | } 329 | 330 | public function test_calls_token_factory_if_provided() 331 | { 332 | $provider = new LightsSamlSpAuthenticationProvider( 333 | $providerKey = 'main', 334 | $userProviderMock = $this->getUserProviderMock(), 335 | false, 336 | null, 337 | $usernameMapperMock = $this->getUsernameMapperMock(), 338 | null, 339 | null, 340 | $tokenFactoryMock = $this->getTokenFactoryMock() 341 | ); 342 | 343 | $responseToken = new SamlSpResponseToken(new Response(), $providerKey); 344 | 345 | $user = $this->getUserMock(); 346 | $user->expects($this->any()) 347 | ->method('getRoles') 348 | ->willReturn($expectedRoles = ['foo', 'bar']); 349 | 350 | $userProviderMock->expects($this->once()) 351 | ->method('loadUserByUsername') 352 | ->willReturn($user); 353 | 354 | $tokenFactoryMock->expects($this->once()) 355 | ->method('create') 356 | ->with($providerKey, $this->isType('array'), $user, $responseToken); 357 | 358 | $provider->authenticate($responseToken); 359 | } 360 | 361 | /** 362 | * @expectedException \LogicException 363 | * @expectedExceptionMessage Unsupported token 364 | */ 365 | public function test_throws_logic_exception_on_unsupported_token() 366 | { 367 | $provider = new LightsSamlSpAuthenticationProvider('main'); 368 | $provider->authenticate($this->getMockBuilder(TokenInterface::class)->getMock()); 369 | } 370 | 371 | /** 372 | * @expectedException \LogicException 373 | * @expectedExceptionMessage User provider must return instance of UserInterface 374 | */ 375 | public function test_throws_logic_exception_if_user_provider_returns_non_user_interface() 376 | { 377 | $provider = new LightsSamlSpAuthenticationProvider( 378 | $providerKey = 'main', 379 | $userProviderMock = $this->getUserProviderMock(), 380 | false, 381 | null, 382 | $usernameMapperMock = $this->getUsernameMapperMock() 383 | ); 384 | 385 | $userProviderMock->expects($this->once()) 386 | ->method('loadUserByUsername') 387 | ->willReturn(new \stdClass()); 388 | 389 | $provider->authenticate(new SamlSpResponseToken(new Response(), $providerKey)); 390 | } 391 | 392 | /** 393 | * @expectedException \LogicException 394 | * @expectedExceptionMessage User creator must return instance of UserInterface or null 395 | */ 396 | public function test_throws_logic_exception_if_user_creator_returns_non_null_and_non_user_interface() 397 | { 398 | $provider = new LightsSamlSpAuthenticationProvider( 399 | $providerKey = 'main', 400 | null, 401 | false, 402 | null, 403 | null, 404 | $userCreatorMock = $this->getUserCreatorMock() 405 | ); 406 | 407 | $userCreatorMock->expects($this->once()) 408 | ->method('createUser') 409 | ->willReturn(new \stdClass()); 410 | 411 | $token = new SamlSpResponseToken(new Response(), $providerKey); 412 | 413 | $provider->authenticate($token); 414 | } 415 | 416 | /** 417 | * @expectedException \LogicException 418 | * @expectedExceptionMessage Attribute mapper must return array 419 | */ 420 | public function test_throws_logic_exception_if_attribute_mapper_does_not_return_array() 421 | { 422 | $provider = new LightsSamlSpAuthenticationProvider( 423 | $providerKey = 'main', 424 | $userProviderMock = $this->getUserProviderMock(), 425 | false, 426 | null, 427 | $usernameMapperMock = $this->getUsernameMapperMock(), 428 | null, 429 | $attributeMapperMock = $this->getAttributeMapperMock() 430 | ); 431 | 432 | $user = $this->getUserMock(); 433 | $user->expects($this->any()) 434 | ->method('getRoles') 435 | ->willReturn(['foo', 'bar']); 436 | 437 | $userProviderMock->expects($this->once()) 438 | ->method('loadUserByUsername') 439 | ->willReturn($user); 440 | 441 | $attributeMapperMock->expects($this->once()) 442 | ->method('getAttributes') 443 | ->willReturn('foo'); 444 | 445 | $provider->authenticate(new SamlSpResponseToken(new Response(), $providerKey)); 446 | } 447 | 448 | /** 449 | * @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationException 450 | * @expectedExceptionMessage Unable to resolve user 451 | */ 452 | public function test_throws_authentication_exception_when_unable_to_resolve_user() 453 | { 454 | $provider = new LightsSamlSpAuthenticationProvider('main', null, false); 455 | $provider->authenticate(new SamlSpResponseToken(new Response(), 'main')); 456 | } 457 | 458 | /** 459 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\Security\Core\User\UserCheckerInterface 460 | */ 461 | private function getUserCheckerMock() 462 | { 463 | return $this->getMockBuilder(\Symfony\Component\Security\Core\User\UserCheckerInterface::class)->getMock(); 464 | } 465 | 466 | /** 467 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\Security\Core\User\UserInterface 468 | */ 469 | private function getUserMock() 470 | { 471 | return $this->getMockBuilder(\Symfony\Component\Security\Core\User\UserInterface::class)->getMock(); 472 | } 473 | 474 | /** 475 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\Security\Core\User\UserProviderInterface 476 | */ 477 | private function getUserProviderMock() 478 | { 479 | return $this->getMockBuilder(\Symfony\Component\Security\Core\User\UserProviderInterface::class)->getMock(); 480 | } 481 | 482 | /** 483 | * @return \PHPUnit_Framework_MockObject_MockObject|\LightSaml\SpBundle\Security\User\UsernameMapperInterface 484 | */ 485 | private function getUsernameMapperMock() 486 | { 487 | return $this->getMockBuilder(UsernameMapperInterface::class)->getMock(); 488 | } 489 | 490 | /** 491 | * @return \PHPUnit_Framework_MockObject_MockObject|\LightSaml\SpBundle\Security\User\UserCreatorInterface 492 | */ 493 | private function getUserCreatorMock() 494 | { 495 | return $this->getMockBuilder(UserCreatorInterface::class)->getMock(); 496 | } 497 | 498 | /** 499 | * @return \PHPUnit_Framework_MockObject_MockObject|\LightSaml\SpBundle\Security\User\AttributeMapperInterface 500 | */ 501 | private function getAttributeMapperMock() 502 | { 503 | return $this->getMockBuilder(AttributeMapperInterface::class)->getMock(); 504 | } 505 | 506 | /** 507 | * @return \PHPUnit_Framework_MockObject_MockObject|SamlSpTokenFactoryInterface 508 | */ 509 | private function getTokenFactoryMock() 510 | { 511 | return $this->getMockBuilder(SamlSpTokenFactoryInterface::class)->getMock(); 512 | } 513 | } 514 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/Security/Authentication/Token/SamlSpTokenFactoryTest.php: -------------------------------------------------------------------------------- 1 | create( 23 | $providerKey = 'main', 24 | $attributes = ['a'=>1], 25 | $user = new User('joe', '', ['ROLE_USER']), 26 | $responseToken = new SamlSpResponseToken(new Response(), $providerKey) 27 | ); 28 | 29 | $this->assertInstanceOf(SamlSpToken::class, $token); 30 | $roles = $token->getRoles(); 31 | $this->assertCount(1, $roles); 32 | $this->assertEquals('ROLE_USER', $roles[0]->getRole()); 33 | $this->assertEquals($attributes, $token->getAttributes()); 34 | $this->assertSame($user, $token->getUser()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/Security/Authentication/Token/SamlSpTokenTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('', $token->getCredentials()); 24 | } 25 | 26 | public function test_returns_provider_key_given_in_constructor() 27 | { 28 | $token = new SamlSpToken([], $expectedProviderKey = 'main', [], null); 29 | $this->assertEquals($expectedProviderKey, $token->getProviderKey()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/Security/Firewall/LightSamlSpListenerTest.php: -------------------------------------------------------------------------------- 1 | getTokenStorageMock(), 17 | $this->getAuthenticationManagerMock(), 18 | $this->getSessionAuthenticationStrategyMock(), 19 | $this->getHttpUtilsMock(), 20 | 'main', 21 | $this->getAuthenticationSuccessHandlerMock(), 22 | $this->getAuthenticationFailureHandlerMock(), 23 | [] 24 | ); 25 | } 26 | 27 | public function test_calls_profile_to_receive_response_and_authentication_manager_to_authenticate_token() 28 | { 29 | $listener = new LightSamlSpListener( 30 | $this->getTokenStorageMock(), 31 | $authenticationManagerMock = $this->getAuthenticationManagerMock(), 32 | $this->getSessionAuthenticationStrategyMock(), 33 | $httpUtilsMock = $this->getHttpUtilsMock(), 34 | 'main', 35 | $authenticationSuccessHandlerMock = $this->getAuthenticationSuccessHandlerMock(), 36 | $this->getAuthenticationFailureHandlerMock(), 37 | [] 38 | ); 39 | 40 | $profileBuilderMock = $this->getProfileBuilderMock(); 41 | $actionMock = $this->getActionMock(); 42 | $contextMock = $this->getContextMock(); 43 | 44 | $profileBuilderMock->expects($this->any()) 45 | ->method('buildContext') 46 | ->willReturn($contextMock); 47 | $profileBuilderMock->expects($this->any()) 48 | ->method('buildAction') 49 | ->willReturn($actionMock); 50 | 51 | $samlResponse = new Response(); 52 | 53 | $contextMock->expects($this->any()) 54 | ->method('getInboundMessage') 55 | ->willReturn($samlResponse); 56 | 57 | $actionMock->expects($this->once()) 58 | ->method('execute') 59 | ->willReturnCallback(function (ProfileContext $context) use ($samlResponse, $contextMock) { 60 | $this->assertSame($contextMock, $context); 61 | }); 62 | 63 | $authenticationManagerMock->expects($this->once()) 64 | ->method('authenticate') 65 | ->with($this->isInstanceOf(\LightSaml\SpBundle\Security\Authentication\Token\SamlSpResponseToken::class)) 66 | ->willReturn($authenticatedToken = new SamlSpToken([], 'main', [], new User('username', ''))); 67 | 68 | $listener->setProfile($profileBuilderMock); 69 | 70 | $requestMock = $this->getRequestMock(); 71 | $requestMock->expects($this->any()) 72 | ->method('hasSession') 73 | ->willReturn(true); 74 | $requestMock->expects($this->any()) 75 | ->method('hasPreviousSession') 76 | ->willReturn(true); 77 | $requestMock->expects($this->any()) 78 | ->method('getSession') 79 | ->willReturn($sessionMock = $this->getSessionMock()); 80 | 81 | $authenticationSuccessHandlerMock->expects($this->any()) 82 | ->method('onAuthenticationSuccess') 83 | ->willReturn($responseMock = $this->getResponseMock()); 84 | 85 | $eventMock = $this->getGetResponseEventMock(); 86 | $eventMock->expects($this->any()) 87 | ->method('getRequest') 88 | ->willReturn($requestMock); 89 | 90 | $httpUtilsMock->expects($this->any()) 91 | ->method('checkRequestPath') 92 | ->willReturn(true); 93 | 94 | $listener->handle($eventMock); 95 | } 96 | 97 | /** 98 | * @return \PHPUnit_Framework_MockObject_MockObject|\LightSaml\Context\Profile\ProfileContext 99 | */ 100 | private function getContextMock() 101 | { 102 | return $this->getMockBuilder(\LightSaml\Context\Profile\ProfileContext::class) 103 | ->disableOriginalConstructor() 104 | ->getMock(); 105 | } 106 | 107 | /** 108 | * @return \PHPUnit_Framework_MockObject_MockObject|\LightSaml\Action\ActionInterface 109 | */ 110 | private function getActionMock() 111 | { 112 | return $this->getMockBuilder(\LightSaml\Action\ActionInterface::class)->getMock(); 113 | } 114 | 115 | /** 116 | * @return \PHPUnit_Framework_MockObject_MockObject|\LightSaml\Builder\Profile\ProfileBuilderInterface 117 | */ 118 | private function getProfileBuilderMock() 119 | { 120 | return $this->getMockBuilder(\LightSaml\Builder\Profile\ProfileBuilderInterface::class)->getMock(); 121 | } 122 | 123 | /** 124 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\HttpFoundation\Request 125 | */ 126 | private function getRequestMock() 127 | { 128 | return $this->getMockBuilder(\Symfony\Component\HttpFoundation\Request::class)->getMock(); 129 | } 130 | 131 | /** 132 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\HttpFoundation\Response 133 | */ 134 | private function getResponseMock() 135 | { 136 | return $this->getMockBuilder(\Symfony\Component\HttpFoundation\Response::class)->getMock(); 137 | } 138 | 139 | /** 140 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\HttpFoundation\Session\SessionInterface 141 | */ 142 | private function getSessionMock() 143 | { 144 | return $this->getMockBuilder(\Symfony\Component\HttpFoundation\Session\SessionInterface::class)->getMock(); 145 | } 146 | 147 | /** 148 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\HttpKernel\Event\GetResponseEvent 149 | */ 150 | private function getGetResponseEventMock() 151 | { 152 | return $this->getMockBuilder(\Symfony\Component\HttpKernel\Event\GetResponseEvent::class) 153 | ->disableOriginalConstructor() 154 | ->getMock(); 155 | } 156 | 157 | /** 158 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface|\Symfony\Component\Security\Core\SecurityContextInterface 159 | */ 160 | private function getTokenStorageMock() 161 | { 162 | if (class_exists('\Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand')) { 163 | return $this->getMockBuilder(\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface::class)->getMock(); 164 | } else { // for symfony/security-bundle <= 2.6 165 | return $this->getMockBuilder(\Symfony\Component\Security\Core\SecurityContextInterface::class)->getMock(); 166 | } 167 | } 168 | 169 | /** 170 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface 171 | */ 172 | private function getAuthenticationManagerMock() 173 | { 174 | return $this->getMockBuilder(\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface::class)->getMock(); 175 | } 176 | 177 | /** 178 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface 179 | */ 180 | private function getSessionAuthenticationStrategyMock() 181 | { 182 | return $this->getMockBuilder(\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface::class)->getMock(); 183 | } 184 | 185 | /** 186 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\Security\Http\HttpUtils 187 | */ 188 | private function getHttpUtilsMock() 189 | { 190 | return $this->getMockBuilder(\Symfony\Component\Security\Http\HttpUtils::class)->getMock(); 191 | } 192 | 193 | /** 194 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface 195 | */ 196 | private function getAuthenticationSuccessHandlerMock() 197 | { 198 | return $this->getMockBuilder(\Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface::class)->getMock(); 199 | } 200 | 201 | /** 202 | * @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface 203 | */ 204 | private function getAuthenticationFailureHandlerMock() 205 | { 206 | return $this->getMockBuilder(\Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface::class)->getMock(); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/Security/User/SimpleAttributeMapperTest.php: -------------------------------------------------------------------------------- 1 | buildAssertion([ 17 | 'organization' => 'test', 18 | 'name' => 'John', 19 | 'email_address' => 'john@domain.com', 20 | 'test' => ['one', 'two'], 21 | ]); 22 | $response = $this->buildResponse($assertion); 23 | $samlSpResponseToken = $this->buildSamlSpResponseToken($response); 24 | 25 | $expectedAttributes = [ 26 | 'organization' => 'test', 27 | 'name' => 'John', 28 | 'email_address' => 'john@domain.com', 29 | 'test' => ['one', 'two'], 30 | ]; 31 | 32 | $simpleAttributeMapper = new SimpleAttributeMapper(); 33 | $actualAttributes = $simpleAttributeMapper->getAttributes($samlSpResponseToken); 34 | 35 | $this->assertEquals($expectedAttributes, $actualAttributes); 36 | } 37 | 38 | public function test_get_attributes_from_multi_assertions_response() 39 | { 40 | $assertion = $this->buildAssertion([ 41 | 'organization' => 'test', 42 | 'name' => 'John', 43 | 'email_address' => 'john@domain.com', 44 | 'test' => ['one', 'two'], 45 | ]); 46 | $response = $this->buildResponse($assertion); 47 | 48 | $assertion = $this->buildAssertion([ 49 | 'name' => 'Doe', 50 | 'email_address' => 'doe@domain.com', 51 | 'test' => ['three', 'four'], 52 | ]); 53 | $response = $this->buildResponse($assertion, $response); 54 | 55 | $samlSpResponseToken = $this->buildSamlSpResponseToken($response); 56 | 57 | $expectedAttributes = [ 58 | 'organization' => 'test', 59 | 'name' => ['John', 'Doe'], 60 | 'email_address' => ['john@domain.com', 'doe@domain.com'], 61 | 'test' => ['one', 'two', 'three', 'four'], 62 | ]; 63 | 64 | $simpleAttributeMapper = new SimpleAttributeMapper(); 65 | $actualAttributes = $simpleAttributeMapper->getAttributes($samlSpResponseToken); 66 | 67 | $this->assertEquals($expectedAttributes, $actualAttributes); 68 | } 69 | 70 | public function test_get_attributes_from_multi_attribute_statements_response() 71 | { 72 | $assertion = $this->buildAssertion([ 73 | 'organization' => 'test', 74 | 'name' => 'John', 75 | 'email_address' => 'john@domain.com', 76 | 'test' => ['one', 'two'] 77 | ]); 78 | $assertion = $this->buildAssertion([ 79 | 'name' => 'Doe', 80 | 'email_address' => 'doe@domain.com', 81 | 'test' => ['three', 'four'] 82 | ], $assertion); 83 | $response = $this->buildResponse($assertion); 84 | 85 | $samlSpResponseToken = $this->buildSamlSpResponseToken($response); 86 | 87 | $expectedAttributes = [ 88 | 'organization' => 'test', 89 | 'name' => ['John', 'Doe'], 90 | 'email_address' => ['john@domain.com', 'doe@domain.com'], 91 | 'test' => ['one', 'two', 'three', 'four'], 92 | ]; 93 | 94 | $simpleAttributeMapper = new SimpleAttributeMapper(); 95 | $actualAttributes = $simpleAttributeMapper->getAttributes($samlSpResponseToken); 96 | 97 | $this->assertEquals($expectedAttributes, $actualAttributes); 98 | } 99 | 100 | /** 101 | * @param Response $response 102 | * 103 | * @return \LightSaml\SpBundle\Security\Authentication\Token\SamlSpResponseToken 104 | */ 105 | private function buildSamlSpResponseToken(Response $response) 106 | { 107 | return new SamlSpResponseToken($response, 'test'); 108 | } 109 | 110 | /** 111 | * @param Assertion $assertion 112 | * @param Response $response 113 | * 114 | * @return Response 115 | */ 116 | private function buildResponse(Assertion $assertion, Response $response = null) 117 | { 118 | if (null == $response) { 119 | $response = new Response(); 120 | } 121 | 122 | $response->addAssertion($assertion); 123 | 124 | return $response; 125 | } 126 | 127 | /** 128 | * @param array $assertionAttributes 129 | * @param Assertion $assertion 130 | * 131 | * @return Assertion 132 | */ 133 | private function buildAssertion(array $assertionAttributes, Assertion $assertion = null) 134 | { 135 | if (null == $assertion) { 136 | $assertion = new Assertion(); 137 | } 138 | 139 | $assertion->addItem($attributeStatement = new AttributeStatement()); 140 | foreach ($assertionAttributes as $attributeName => $attributeValue) { 141 | $attributeStatement->addAttribute(new Attribute($attributeName, $attributeValue)); 142 | } 143 | 144 | return $assertion; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /tests/LightSaml/SpBundle/Tests/Security/User/SimpleUsernameMapperTest.php: -------------------------------------------------------------------------------- 1 | buildResponse($this->buildAssertion( 22 | [ClaimTypes::GIVEN_NAME => 'John', ClaimTypes::EMAIL_ADDRESS => 'user@domain.com', ClaimTypes::COMMON_NAME => 'user'], 23 | null, 24 | null 25 | )), 26 | [ClaimTypes::EMAIL_ADDRESS, ClaimTypes::COMMON_NAME], 27 | 'user@domain.com', 28 | ], 29 | [ 30 | $this->buildResponse($this->buildAssertion( 31 | [ClaimTypes::GIVEN_NAME => 'John', ClaimTypes::EMAIL_ADDRESS => 'user@domain.com', ClaimTypes::COMMON_NAME => 'user'], 32 | '123123123', 33 | SamlConstants::NAME_ID_FORMAT_TRANSIENT 34 | )), 35 | [SimpleUsernameMapper::NAME_ID, ClaimTypes::EMAIL_ADDRESS, ClaimTypes::COMMON_NAME], 36 | 'user@domain.com', 37 | ], 38 | [ 39 | $this->buildResponse($this->buildAssertion( 40 | [ClaimTypes::GIVEN_NAME => 'John', ClaimTypes::EMAIL_ADDRESS => 'user@domain.com', ClaimTypes::COMMON_NAME => 'user'], 41 | 'user_name_id', 42 | SamlConstants::NAME_ID_FORMAT_PERSISTENT 43 | )), 44 | [SimpleUsernameMapper::NAME_ID, ClaimTypes::EMAIL_ADDRESS, ClaimTypes::COMMON_NAME], 45 | 'user_name_id', 46 | ], 47 | [ 48 | $this->buildResponse($this->buildAssertion( 49 | [ClaimTypes::GIVEN_NAME => 'John', ClaimTypes::EMAIL_ADDRESS => 'user@domain.com', ClaimTypes::COMMON_NAME => 'user'], 50 | 'user_name_id', 51 | SamlConstants::NAME_ID_FORMAT_TRANSIENT 52 | )), 53 | [SimpleUsernameMapper::NAME_ID, ClaimTypes::ADFS_1_EMAIL, ClaimTypes::PPID], 54 | null, 55 | ], 56 | ]; 57 | } 58 | 59 | /** 60 | * @dataProvider resolves_username_from_attributes_provider 61 | */ 62 | public function test_resolves_username_from_attributes(Response $response, $attributeList, $expectedUsername) 63 | { 64 | $simpleUsernameMapper = new SimpleUsernameMapper($attributeList); 65 | $actualUsername = $simpleUsernameMapper->getUsername($response); 66 | $this->assertEquals($expectedUsername, $actualUsername); 67 | } 68 | 69 | /** 70 | * @param Assertion $assertion 71 | * @param Response $response 72 | * 73 | * @return \LightSaml\Model\Protocol\Response 74 | */ 75 | private function buildResponse(Assertion $assertion, Response $response = null) 76 | { 77 | if (null == $response) { 78 | $response = new Response(); 79 | } 80 | $response->addAssertion($assertion); 81 | 82 | return $response; 83 | } 84 | 85 | /** 86 | * @param array $assertionAttributes 87 | * @param string $nameId 88 | * @param string $nameIdFormat 89 | * @param Assertion $assertion 90 | * 91 | * @return \LightSaml\Model\Assertion\Assertion 92 | */ 93 | private function buildAssertion(array $assertionAttributes, $nameId, $nameIdFormat, Assertion $assertion = null) 94 | { 95 | if (null == $assertion) { 96 | $assertion = new Assertion(); 97 | } 98 | $assertion->addItem($attributeStatement = new AttributeStatement()); 99 | foreach ($assertionAttributes as $attributeName => $attributeValue) { 100 | $attributeStatement->addAttribute(new Attribute($attributeName, $attributeValue)); 101 | } 102 | if ($nameId) { 103 | $assertion->setSubject(new Subject()); 104 | $assertion->getSubject()->setNameID(new NameID($nameId, $nameIdFormat)); 105 | } 106 | 107 | return $assertion; 108 | } 109 | } 110 | --------------------------------------------------------------------------------