* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Silex\Provider; use Pimple\Container; use Pimple\ServiceProviderInterface; use Silex\Application; use Silex\Api\BootableProviderInterface; use Silex\Api\ControllerProviderInterface; use Silex\Api\EventListenerProviderInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\RequestMatcher; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\UserChecker; use Symfony\Component\Security\Core\User\InMemoryUserProvider; use Symfony\Component\Security\Core\Encoder\EncoderFactory; use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; use Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder; use Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder; use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider; use Symfony\Component\Security\Core\Authentication\Provider\AnonymousAuthenticationProvider; use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler; use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Security\Core\Role\RoleHierarchy; use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator; use Symfony\Component\Security\Http\Firewall; use Symfony\Component\Security\Http\FirewallMap; use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener; use Symfony\Component\Security\Http\Firewall\AccessListener; use Symfony\Component\Security\Http\Firewall\BasicAuthenticationListener; use Symfony\Component\Security\Http\Firewall\LogoutListener; use Symfony\Component\Security\Http\Firewall\SwitchUserListener; use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener; use Symfony\Component\Security\Http\Firewall\ContextListener; use Symfony\Component\Security\Http\Firewall\ExceptionListener; use Symfony\Component\Security\Http\Firewall\ChannelListener; use Symfony\Component\Security\Http\EntryPoint\FormAuthenticationEntryPoint; use Symfony\Component\Security\Http\EntryPoint\BasicAuthenticationEntryPoint; use Symfony\Component\Security\Http\EntryPoint\RetryAuthenticationEntryPoint; use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; use Symfony\Component\Security\Http\Logout\SessionLogoutHandler; use Symfony\Component\Security\Http\Logout\DefaultLogoutSuccessHandler; use Symfony\Component\Security\Http\AccessMap; use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; use Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener; use Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider; /** * Symfony Security component Provider. * * @author Fabien Potencier */ class SecurityServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface, ControllerProviderInterface, BootableProviderInterface { protected $fakeRoutes; public function register(Container $app) { // used to register routes for login_check and logout $this->fakeRoutes = []; $that = $this; $app['security.role_hierarchy'] = []; $app['security.access_rules'] = []; $app['security.hide_user_not_found'] = true; $app['security.encoder.bcrypt.cost'] = 13; $app['security.authorization_checker'] = function ($app) { return new AuthorizationChecker($app['security.token_storage'], $app['security.authentication_manager'], $app['security.access_manager']); }; $app['security.token_storage'] = function ($app) { return new TokenStorage(); }; $app['user'] = $app->factory(function ($app) { if (null === $token = $app['security.token_storage']->getToken()) { return; } if (!is_object($user = $token->getUser())) { return; } return $user; }); $app['security.authentication_manager'] = function ($app) { $manager = new AuthenticationProviderManager($app['security.authentication_providers']); $manager->setEventDispatcher($app['dispatcher']); return $manager; }; // by default, all users use the digest encoder $app['security.encoder_factory'] = function ($app) { return new EncoderFactory([ 'Symfony\Component\Security\Core\User\UserInterface' => $app['security.default_encoder'], ]); }; // by default, all users use the BCrypt encoder $app['security.default_encoder'] = function ($app) { return $app['security.encoder.bcrypt']; }; $app['security.encoder.digest'] = function ($app) { return new MessageDigestPasswordEncoder(); }; $app['security.encoder.bcrypt'] = function ($app) { return new BCryptPasswordEncoder($app['security.encoder.bcrypt.cost']); }; $app['security.encoder.pbkdf2'] = function ($app) { return new Pbkdf2PasswordEncoder(); }; $app['security.user_checker'] = function ($app) { return new UserChecker(); }; $app['security.access_manager'] = function ($app) { return new AccessDecisionManager($app['security.voters']); }; $app['security.voters'] = function ($app) { return [ new RoleHierarchyVoter(new RoleHierarchy($app['security.role_hierarchy'])), new AuthenticatedVoter($app['security.trust_resolver']), ]; }; $app['security.firewall'] = function ($app) { if (isset($app['validator'])) { $app['security.validator.user_password_validator'] = function ($app) { return new UserPasswordValidator($app['security.token_storage'], $app['security.encoder_factory']); }; $app['validator.validator_service_ids'] = array_merge($app['validator.validator_service_ids'], ['security.validator.user_password' => 'security.validator.user_password_validator']); } return new Firewall($app['security.firewall_map'], $app['dispatcher']); }; $app['security.channel_listener'] = function ($app) { return new ChannelListener( $app['security.access_map'], new RetryAuthenticationEntryPoint( isset($app['request.http_port']) ? $app['request.http_port'] : 80, isset($app['request.https_port']) ? $app['request.https_port'] : 443 ), $app['logger'] ); }; // generate the build-in authentication factories foreach (['logout', 'pre_auth', 'guard', 'form', 'http', 'remember_me', 'anonymous'] as $type) { $entryPoint = null; if ('http' === $type) { $entryPoint = 'http'; } elseif ('form' === $type) { $entryPoint = 'form'; } elseif ('guard' === $type) { $entryPoint = 'guard'; } $app['security.authentication_listener.factory.'.$type] = $app->protect(function ($name, $options) use ($type, $app, $entryPoint) { if ($entryPoint && !isset($app['security.entry_point.'.$name.'.'.$entryPoint])) { $app['security.entry_point.'.$name.'.'.$entryPoint] = $app['security.entry_point.'.$entryPoint.'._proto']($name, $options); } if (!isset($app['security.authentication_listener.'.$name.'.'.$type])) { $app['security.authentication_listener.'.$name.'.'.$type] = $app['security.authentication_listener.'.$type.'._proto']($name, $options); } $provider = 'dao'; if ('anonymous' === $type) { $provider = 'anonymous'; } elseif ('guard' === $type) { $provider = 'guard'; } if (!isset($app['security.authentication_provider.'.$name.'.'.$provider])) { $app['security.authentication_provider.'.$name.'.'.$provider] = $app['security.authentication_provider.'.$provider.'._proto']($name, $options); } return [ 'security.authentication_provider.'.$name.'.'.$provider, 'security.authentication_listener.'.$name.'.'.$type, $entryPoint ? 'security.entry_point.'.$name.'.'.$entryPoint : null, $type, ]; }); } $app['security.firewall_map'] = function ($app) { $positions = ['logout', 'pre_auth', 'guard', 'form', 'http', 'remember_me', 'anonymous']; $providers = []; $configs = []; foreach ($app['security.firewalls'] as $name => $firewall) { $entryPoint = null; $pattern = isset($firewall['pattern']) ? $firewall['pattern'] : null; $users = isset($firewall['users']) ? $firewall['users'] : []; $security = isset($firewall['security']) ? (bool) $firewall['security'] : true; $stateless = isset($firewall['stateless']) ? (bool) $firewall['stateless'] : false; $context = isset($firewall['context']) ? $firewall['context'] : $name; $hosts = isset($firewall['hosts']) ? $firewall['hosts'] : null; $methods = isset($firewall['methods']) ? $firewall['methods'] : null; unset($firewall['pattern'], $firewall['users'], $firewall['security'], $firewall['stateless'], $firewall['context'], $firewall['methods'], $firewall['hosts']); $protected = false === $security ? false : count($firewall); $listeners = ['security.channel_listener']; if (is_string($users)) { $users = function () use ($app, $users) { return $app[$users]; }; } if ($protected) { if (!isset($app['security.context_listener.'.$context])) { if (!isset($app['security.user_provider.'.$name])) { $app['security.user_provider.'.$name] = is_array($users) ? $app['security.user_provider.inmemory._proto']($users) : $users; } $app['security.context_listener.'.$context] = $app['security.context_listener._proto']($name, [$app['security.user_provider.'.$name]]); } if (false === $stateless) { $listeners[] = 'security.context_listener.'.$context; } $factories = []; foreach ($positions as $position) { $factories[$position] = []; } foreach ($firewall as $type => $options) { if ('switch_user' === $type) { continue; } // normalize options if (!is_array($options)) { if (!$options) { continue; } $options = []; } if (!isset($app['security.authentication_listener.factory.'.$type])) { throw new \LogicException(sprintf('The "%s" authentication entry is not registered.', $type)); } $options['stateless'] = $stateless; list($providerId, $listenerId, $entryPointId, $position) = $app['security.authentication_listener.factory.'.$type]($name, $options); if (null !== $entryPointId) { $entryPoint = $entryPointId; } $factories[$position][] = $listenerId; $providers[] = $providerId; } foreach ($positions as $position) { foreach ($factories[$position] as $listener) { $listeners[] = $listener; } } $listeners[] = 'security.access_listener'; if (isset($firewall['switch_user'])) { $app['security.switch_user.'.$name] = $app['security.authentication_listener.switch_user._proto']($name, $firewall['switch_user']); $listeners[] = 'security.switch_user.'.$name; } if (!isset($app['security.exception_listener.'.$name])) { if (null === $entryPoint) { $app[$entryPoint = 'security.entry_point.'.$name.'.form'] = $app['security.entry_point.form._proto']($name, []); } $accessDeniedHandler = null; if (isset($app['security.access_denied_handler.'.$name])) { $accessDeniedHandler = $app['security.access_denied_handler.'.$name]; } $app['security.exception_listener.'.$name] = $app['security.exception_listener._proto']($entryPoint, $name, $accessDeniedHandler); } } $configs[$name] = [ 'pattern' => $pattern, 'listeners' => $listeners, 'protected' => $protected, 'methods' => $methods, 'hosts' => $hosts, ]; } $app['security.authentication_providers'] = array_map(function ($provider) use ($app) { return $app[$provider]; }, array_unique($providers)); $map = new FirewallMap(); foreach ($configs as $name => $config) { if (is_string($config['pattern'])) { $requestMatcher = new RequestMatcher($config['pattern'], $config['hosts'], $config['methods']); } else { $requestMatcher = $config['pattern']; } $map->add( $requestMatcher, array_map(function ($listenerId) use ($app, $name) { $listener = $app[$listenerId]; if (isset($app['security.remember_me.service.'.$name])) { if ($listener instanceof AbstractAuthenticationListener || $listener instanceof GuardAuthenticationListener) { $listener->setRememberMeServices($app['security.remember_me.service.'.$name]); } if ($listener instanceof LogoutListener) { $listener->addHandler($app['security.remember_me.service.'.$name]); } } return $listener; }, $config['listeners']), $config['protected'] ? $app['security.exception_listener.'.$name] : null ); } return $map; }; $app['security.access_listener'] = function ($app) { return new AccessListener( $app['security.token_storage'], $app['security.access_manager'], $app['security.access_map'], $app['security.authentication_manager'], $app['logger'] ); }; $app['security.access_map'] = function ($app) { $map = new AccessMap(); foreach ($app['security.access_rules'] as $rule) { if (is_string($rule[0])) { $rule[0] = new RequestMatcher($rule[0]); } elseif (is_array($rule[0])) { $rule[0] += [ 'path' => null, 'host' => null, 'methods' => null, 'ips' => null, 'attributes' => [], 'schemes' => null, ]; $rule[0] = new RequestMatcher($rule[0]['path'], $rule[0]['host'], $rule[0]['methods'], $rule[0]['ips'], $rule[0]['attributes'], $rule[0]['schemes']); } $map->add($rule[0], (array) $rule[1], isset($rule[2]) ? $rule[2] : null); } return $map; }; $app['security.trust_resolver'] = function ($app) { return new AuthenticationTrustResolver('Symfony\Component\Security\Core\Authentication\Token\AnonymousToken', 'Symfony\Component\Security\Core\Authentication\Token\RememberMeToken'); }; $app['security.session_strategy'] = function ($app) { return new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE); }; $app['security.http_utils'] = function ($app) { return new HttpUtils($app['url_generator'], $app['request_matcher']); }; $app['security.last_error'] = $app->protect(function (Request $request) { if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { return $request->attributes->get(Security::AUTHENTICATION_ERROR)->getMessage(); } $session = $request->getSession(); if ($session && $session->has(Security::AUTHENTICATION_ERROR)) { $message = $session->get(Security::AUTHENTICATION_ERROR)->getMessage(); $session->remove(Security::AUTHENTICATION_ERROR); return $message; } }); // prototypes (used by the Firewall Map) $app['security.context_listener._proto'] = $app->protect(function ($providerKey, $userProviders) use ($app) { return function () use ($app, $userProviders, $providerKey) { return new ContextListener( $app['security.token_storage'], $userProviders, $providerKey, $app['logger'], $app['dispatcher'] ); }; }); $app['security.user_provider.inmemory._proto'] = $app->protect(function ($params) use ($app) { return function () use ($app, $params) { $users = []; foreach ($params as $name => $user) { $users[$name] = ['roles' => (array) $user[0], 'password' => $user[1]]; } return new InMemoryUserProvider($users); }; }); $app['security.exception_listener._proto'] = $app->protect(function ($entryPoint, $name, $accessDeniedHandler = null) use ($app) { return function () use ($app, $entryPoint, $name, $accessDeniedHandler) { return new ExceptionListener( $app['security.token_storage'], $app['security.trust_resolver'], $app['security.http_utils'], $name, $app[$entryPoint], null, // errorPage $accessDeniedHandler, $app['logger'] ); }; }); $app['security.authentication.success_handler._proto'] = $app->protect(function ($name, $options) use ($app) { return function () use ($name, $options, $app) { $handler = new DefaultAuthenticationSuccessHandler( $app['security.http_utils'], $options ); $handler->setProviderKey($name); return $handler; }; }); $app['security.authentication.failure_handler._proto'] = $app->protect(function ($name, $options) use ($app) { return function () use ($name, $options, $app) { return new DefaultAuthenticationFailureHandler( $app, $app['security.http_utils'], $options, $app['logger'] ); }; }); $app['security.authentication_listener.guard._proto'] = $app->protect(function ($providerKey, $options) use ($app, $that) { return function () use ($app, $providerKey, $options, $that) { if (!isset($app['security.authentication.guard_handler'])) { $app['security.authentication.guard_handler'] = new GuardAuthenticatorHandler($app['security.token_storage'], $app['dispatcher']); } $authenticators = []; foreach ($options['authenticators'] as $authenticatorId) { $authenticators[] = $app[$authenticatorId]; } return new GuardAuthenticationListener( $app['security.authentication.guard_handler'], $app['security.authentication_manager'], $providerKey, $authenticators, $app['logger'] ); }; }); $app['security.authentication_listener.form._proto'] = $app->protect(function ($name, $options) use ($app, $that) { return function () use ($app, $name, $options, $that) { $that->addFakeRoute( 'match', $tmp = isset($options['check_path']) ? $options['check_path'] : '/login_check', str_replace('/', '_', ltrim($tmp, '/')) ); $class = isset($options['listener_class']) ? $options['listener_class'] : 'Symfony\\Component\\Security\\Http\\Firewall\\UsernamePasswordFormAuthenticationListener'; if (!isset($app['security.authentication.success_handler.'.$name])) { $app['security.authentication.success_handler.'.$name] = $app['security.authentication.success_handler._proto']($name, $options); } if (!isset($app['security.authentication.failure_handler.'.$name])) { $app['security.authentication.failure_handler.'.$name] = $app['security.authentication.failure_handler._proto']($name, $options); } return new $class( $app['security.token_storage'], $app['security.authentication_manager'], isset($app['security.session_strategy.'.$name]) ? $app['security.session_strategy.'.$name] : $app['security.session_strategy'], $app['security.http_utils'], $name, $app['security.authentication.success_handler.'.$name], $app['security.authentication.failure_handler.'.$name], $options, $app['logger'], $app['dispatcher'], isset($options['with_csrf']) && $options['with_csrf'] && isset($app['csrf.token_manager']) ? $app['csrf.token_manager'] : null ); }; }); $app['security.authentication_listener.http._proto'] = $app->protect(function ($providerKey, $options) use ($app) { return function () use ($app, $providerKey, $options) { return new BasicAuthenticationListener( $app['security.token_storage'], $app['security.authentication_manager'], $providerKey, $app['security.entry_point.'.$providerKey.'.http'], $app['logger'] ); }; }); $app['security.authentication_listener.anonymous._proto'] = $app->protect(function ($providerKey, $options) use ($app) { return function () use ($app, $providerKey, $options) { return new AnonymousAuthenticationListener( $app['security.token_storage'], $providerKey, $app['logger'] ); }; }); $app['security.authentication.logout_handler._proto'] = $app->protect(function ($name, $options) use ($app) { return function () use ($name, $options, $app) { return new DefaultLogoutSuccessHandler( $app['security.http_utils'], isset($options['target_url']) ? $options['target_url'] : '/' ); }; }); $app['security.authentication_listener.logout._proto'] = $app->protect(function ($name, $options) use ($app, $that) { return function () use ($app, $name, $options, $that) { $that->addFakeRoute( 'get', $tmp = isset($options['logout_path']) ? $options['logout_path'] : '/logout', str_replace('/', '_', ltrim($tmp, '/')) ); if (!isset($app['security.authentication.logout_handler.'.$name])) { $app['security.authentication.logout_handler.'.$name] = $app['security.authentication.logout_handler._proto']($name, $options); } $listener = new LogoutListener( $app['security.token_storage'], $app['security.http_utils'], $app['security.authentication.logout_handler.'.$name], $options, isset($options['with_csrf']) && $options['with_csrf'] && isset($app['csrf.token_manager']) ? $app['csrf.token_manager'] : null ); $invalidateSession = isset($options['invalidate_session']) ? $options['invalidate_session'] : true; if (true === $invalidateSession && false === $options['stateless']) { $listener->addHandler(new SessionLogoutHandler()); } return $listener; }; }); $app['security.authentication_listener.switch_user._proto'] = $app->protect(function ($name, $options) use ($app, $that) { return function () use ($app, $name, $options, $that) { return new SwitchUserListener( $app['security.token_storage'], $app['security.user_provider.'.$name], $app['security.user_checker'], $name, $app['security.access_manager'], $app['logger'], isset($options['parameter']) ? $options['parameter'] : '_switch_user', isset($options['role']) ? $options['role'] : 'ROLE_ALLOWED_TO_SWITCH', $app['dispatcher'] ); }; }); $app['security.entry_point.form._proto'] = $app->protect(function ($name, array $options) use ($app) { return function () use ($app, $options) { $loginPath = isset($options['login_path']) ? $options['login_path'] : '/login'; $useForward = isset($options['use_forward']) ? $options['use_forward'] : false; return new FormAuthenticationEntryPoint($app, $app['security.http_utils'], $loginPath, $useForward); }; }); $app['security.entry_point.http._proto'] = $app->protect(function ($name, array $options) use ($app) { return function () use ($app, $name, $options) { return new BasicAuthenticationEntryPoint(isset($options['real_name']) ? $options['real_name'] : 'Secured'); }; }); $app['security.entry_point.guard._proto'] = $app->protect(function ($name, array $options) use ($app) { if (isset($options['entry_point'])) { // if it's configured explicitly, use it! return $app[$options['entry_point']]; } $authenticatorIds = $options['authenticators']; if (1 == count($authenticatorIds)) { // if there is only one authenticator, use that as the entry point return $app[reset($authenticatorIds)]; } // we have multiple entry points - we must ask them to configure one throw new \LogicException(sprintf( 'Because you have multiple guard configurators, you need to set the "guard.entry_point" key to one of your configurators (%s)', implode(', ', $authenticatorIds) )); }); $app['security.authentication_provider.dao._proto'] = $app->protect(function ($name, $options) use ($app) { return function () use ($app, $name) { return new DaoAuthenticationProvider( $app['security.user_provider.'.$name], $app['security.user_checker'], $name, $app['security.encoder_factory'], $app['security.hide_user_not_found'] ); }; }); $app['security.authentication_provider.guard._proto'] = $app->protect(function ($name, $options) use ($app) { return function () use ($app, $name, $options) { $authenticators = []; foreach ($options['authenticators'] as $authenticatorId) { $authenticators[] = $app[$authenticatorId]; } return new GuardAuthenticationProvider( $authenticators, $app['security.user_provider.'.$name], $name, $app['security.user_checker'] ); }; }); $app['security.authentication_provider.anonymous._proto'] = $app->protect(function ($name, $options) use ($app) { return function () use ($app, $name) { return new AnonymousAuthenticationProvider($name); }; }); $app['security.authentication_utils'] = function ($app) { return new AuthenticationUtils($app['request_stack']); }; } public function subscribe(Container $app, EventDispatcherInterface $dispatcher) { $dispatcher->addSubscriber($app['security.firewall']); } public function connect(Application $app) { $controllers = $app['controllers_factory']; foreach ($this->fakeRoutes as $route) { list($method, $pattern, $name) = $route; $controllers->$method($pattern)->run(null)->bind($name); } return $controllers; } public function boot(Application $app) { $app->mount('/', $this->connect($app)); } public function addFakeRoute($method, $pattern, $name) { $this->fakeRoutes[] = [$method, $pattern, $name]; } }