├── .gitignore ├── DependencyInjection └── EasySecurityExtension.php ├── EasySecurityBundle.php ├── LICENSE.md ├── README.md ├── Resources └── config │ └── services.xml ├── Security └── Security.php └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | phpunit.xml 3 | vendor/ 4 | -------------------------------------------------------------------------------- /DependencyInjection/EasySecurityExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace EasyCorp\Bundle\EasySecurityBundle\DependencyInjection; 13 | 14 | use Symfony\Component\HttpKernel\DependencyInjection\Extension; 15 | use Symfony\Component\DependencyInjection\ContainerBuilder; 16 | use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; 17 | use Symfony\Component\Config\FileLocator; 18 | 19 | class EasySecurityExtension extends Extension 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function load(array $configs, ContainerBuilder $container) 25 | { 26 | $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 27 | $loader->load('services.xml'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /EasySecurityBundle.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace EasyCorp\Bundle\EasySecurityBundle; 13 | 14 | use Symfony\Component\HttpKernel\Bundle\Bundle; 15 | 16 | class EasySecurityBundle extends Bundle 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Javier Eguiluz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ----- 2 | 3 | # THIS BUNDLE IS NO LONGER MAINTAINED. SYMFONY 3.4 ADDED A [SIMILAR FEATURE](https://github.com/symfony/symfony/pull/24337) SO THIS IS NO LONGER NEEDED. 4 | 5 | ----- 6 | 7 | EasySecurityBundle 8 | ================== 9 | 10 | This bundle provides useful shortcuts to hide the Symfony Security component 11 | complexity. 12 | 13 | Installation 14 | ------------ 15 | 16 | ### Step 1: Download the Bundle 17 | 18 | ```bash 19 | $ composer require easycorp/easy-security-bundle 20 | ``` 21 | 22 | This command requires you to have Composer installed globally, as explained 23 | in the [Composer documentation](https://getcomposer.org/doc/00-intro.md). 24 | 25 | ### Step 2: Enable the Bundle 26 | 27 | ```php 28 | get('security.token_storage')->getToken()->getUser(); 62 | // check their permissions 63 | $user = $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN'); 64 | // get the last login attempt error, if any 65 | $error = $this->get('security.authentication_utils')->getLastAuthenticationError(); 66 | ``` 67 | 68 | This bundle hides this complexity centralizing all the operations under the 69 | `security` service: 70 | 71 | ```php 72 | // get the current user 73 | $user = $this->get('security')->getUser(); 74 | // check their permissions 75 | $user = $this->get('security')->isGranted('ROLE_ADMIN'); 76 | // get the last login attempt error, if any 77 | $error = $this->get('security')->getLoginError(); 78 | ``` 79 | 80 | **2) It makes code less verbose** 81 | 82 | Sometimes, the code needed to do common tasks is ridiculously verbose. For 83 | example, to login a user programmatically, Symfony requires you to do the following: 84 | 85 | ```php 86 | $user = ... 87 | $token = new UsernamePasswordToken($user, $user->getPassword(), 'main', $user->getRoles()); 88 | $token->setAuthenticated(true); 89 | $this->get('security.token_storage')->setToken($token); 90 | $this->get('session')->set('_security_main', serialize($token)); 91 | $this->get('session')->save(); 92 | ``` 93 | 94 | This bundle makes login a user as simple as it can be: 95 | 96 | ```php 97 | $user = ... 98 | $this->get('security')->login($user); 99 | ``` 100 | 101 | **3) It fixes some unintuitive behaviors** 102 | 103 | In Symfony applications, the way to check if a user is anonymous, remembered or 104 | fully authenticated doesn't work as most people expect. For example, if a user 105 | logs in with their username + password using a form login, this will happen: 106 | 107 | ```php 108 | // returns true 109 | $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_ANONYMOUSLY'); 110 | // returns true 111 | $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_REMEMBERED'); 112 | // returns true 113 | $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY'); 114 | ``` 115 | 116 | Symfony grants the anonymous and remembered attributes to fully authenticated 117 | users, so it's complicated to differentiate between them. This bundle changes 118 | this unintuitive behavior and helps you know if a user is truly anonymous, 119 | remembered or authenticated. In the same example as before: 120 | 121 | ```php 122 | // returns false 123 | $this->get('security')->isAnonymous(); 124 | // returns false 125 | $this->get('security')->isRemembered(); 126 | // returns true 127 | $this->get('security')->isFullyAuthenticated(); 128 | ``` 129 | 130 | ### Injecting the `security` service 131 | 132 | These shortcuts can be used across your application if you inject the `security` 133 | service. For example, if you define your services in YAML format: 134 | 135 | ```yaml 136 | # app/config/services.yml 137 | services: 138 | app.my_service: 139 | # ... 140 | arguments: ['@security'] 141 | ``` 142 | 143 | Then, update the constructor of your service to get the `security` service: 144 | 145 | ```php 146 | // src/AppBundle/MyService.php 147 | // ... 148 | use EasyCorp\Bundle\EasySecurityBundle\Security\Security; 149 | 150 | class MyService 151 | { 152 | private $security; 153 | 154 | public function __construct(Security $security) 155 | { 156 | $this->security = $security; 157 | } 158 | 159 | public function myMethod() 160 | { 161 | // ... 162 | $user = $this->security->getUser(); 163 | } 164 | } 165 | ``` 166 | 167 | List of Shortcuts 168 | ----------------- 169 | 170 | ### Getting users 171 | 172 | * `getUser()`: returns the current application user. 173 | * `getImpersonatingUser()`: when impersonating a user, it returns the original 174 | user who started the impersonation. 175 | 176 | ### Checking permissions 177 | 178 | * `isGranted($attributes, $object = null)`: checks if the attributes (usually 179 | security roles) are granted for the current application user and the 180 | optionally given object. 181 | * `hasRole($role, $user = null)`: returns `true` if the current application user 182 | (or the optionally given user) has the given role. It takes into account the 183 | full role hierarchy. 184 | 185 | ### Types of users 186 | 187 | * `isAnonymous($user = null)`: returns `true` if the current application user (or 188 | the optionally given user) is anonymous. This behaves differently than Symfony 189 | built-in methods and it returns `true` only when the user is really anonymous. 190 | * `isRemembered($user = null)`: returns `true` if the current application user 191 | (or the optionally given user) is remembered. This behaves differently than 192 | Symfony built-in methods and it returns true only when the user is really 193 | remembered and they haven't introduced their credentials (username and password). 194 | * `isFullyAuthenticated($user = null)`: returns `true` if the current application 195 | user (or the optionally given user) is authenticated because they have 196 | introduced their credentials (username and password). 197 | * `isAuthenticated($user = null)`: returns `true` if the current application user 198 | (or the optionally given user) is authenticated in any way (because they have 199 | introduced their credentials (username and password) or they have been remembered). 200 | 201 | ### Login 202 | 203 | * `login(UserInterface $user, $firewallName = 'main')`: it logs in the given user 204 | in the `main` application firewall (or the optionally given firewall name). 205 | * `getLoginError()`: returns the error of the last failed login attempt, if any. 206 | * `getLoginUsername()`: returns the username of the last failed login attempt, 207 | if any. 208 | 209 | ### Passwords 210 | 211 | * `encodePassword($plainPassword, $user = null)`: returns the given plain 212 | password encoded/hashed using the encoder of the current application user or 213 | the optionally given user. 214 | * `isPasswordValid($plainPassword, $user = null)`: returns `true` if the given 215 | plain password is valid for the current application user or the optionally 216 | given user. 217 | -------------------------------------------------------------------------------- /Resources/config/services.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Security/Security.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace EasyCorp\Bundle\EasySecurityBundle\Security; 13 | 14 | use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; 15 | use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; 16 | use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; 17 | use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder; 18 | use Symfony\Component\Security\Core\Exception\AuthenticationException; 19 | use Symfony\Component\Security\Core\Role\Role; 20 | use Symfony\Component\Security\Core\Role\RoleHierarchy; 21 | use Symfony\Component\Security\Core\Role\RoleInterface; 22 | use Symfony\Component\Security\Core\Role\SwitchUserRole; 23 | use Symfony\Component\Security\Core\User\UserInterface; 24 | use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; 25 | 26 | /** 27 | * Provides useful shortcuts to hide the Symfony Security component complexity. 28 | */ 29 | class Security 30 | { 31 | private $tokenStorage; 32 | private $authorizationChecker; 33 | private $passwordEncoder; 34 | private $authenticationUtils; 35 | private $roleHierarchy; 36 | 37 | public function __construct(TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authorizationChecker, UserPasswordEncoder $passwordEncoder, AuthenticationUtils $authenticationUtils, RoleHierarchy $roleHierarchy) 38 | { 39 | $this->tokenStorage = $tokenStorage; 40 | $this->authorizationChecker = $authorizationChecker; 41 | $this->passwordEncoder = $passwordEncoder; 42 | $this->authenticationUtils = $authenticationUtils; 43 | $this->roleHierarchy = $roleHierarchy; 44 | } 45 | 46 | /** 47 | * Returns the current application user. 48 | * 49 | * @return mixed 50 | */ 51 | public function getUser() 52 | { 53 | return $this->tokenStorage->getToken()->getUser(); 54 | } 55 | 56 | /** 57 | * When impersonating a user, it returns the original user who started 58 | * the impersonation. 59 | * 60 | * @return mixed 61 | */ 62 | public function getImpersonatingUser() 63 | { 64 | if ($this->isGranted('ROLE_PREVIOUS_ADMIN')) { 65 | foreach ($this->tokenStorage->getToken()->getRoles() as $role) { 66 | if ($role instanceof SwitchUserRole) { 67 | return $role->getSource()->getUser(); 68 | } 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * Checks if the attributes (usually security roles) are granted for the 75 | * current application user and the optional given object. 76 | * 77 | * @param mixed $attributes 78 | * @param mixed $object 79 | * 80 | * @return bool 81 | */ 82 | public function isGranted($attributes, $object = null) 83 | { 84 | return $this->authorizationChecker->isGranted($attributes, $object); 85 | } 86 | 87 | /** 88 | * Returns the error of the last failed login attempt, if any. 89 | * 90 | * @return AuthenticationException|null 91 | */ 92 | public function getLoginError() 93 | { 94 | return $this->authenticationUtils->getLastAuthenticationError(); 95 | } 96 | 97 | /** 98 | * Returns the username of the last failed login attempt, if any. 99 | * 100 | * @return string|null 101 | */ 102 | public function getLoginUsername() 103 | { 104 | return $this->authenticationUtils->getLastUsername(); 105 | } 106 | 107 | /** 108 | * Returns true if the current application user (or the optionally given user) 109 | * has the given role. It takes into account the full role hierarchy. 110 | * 111 | * @param $role 112 | * @param null $user 113 | * 114 | * @return bool 115 | */ 116 | public function hasRole($role, $user = null) 117 | { 118 | $roleName = $role instanceof Role ? $role->getRole() : $role; 119 | 120 | $user = $user ?: $this->getUser(); 121 | 122 | if (!($user instanceof UserInterface)) { 123 | return false; 124 | } 125 | 126 | if (null === $this->roleHierarchy) { 127 | return in_array($roleName, $user->getRoles(), true); 128 | } 129 | 130 | $userRoles = $this->roleHierarchy->getReachableRoles($this->getUserRolesAsObjects($user)); 131 | foreach ($userRoles as $userRole) { 132 | if ($roleName === $userRole->getRole()) { 133 | return true; 134 | } 135 | } 136 | 137 | return false; 138 | } 139 | 140 | /** 141 | * Returns true if the current application user (or the optionally given user) 142 | * is anonymous. This behaves differently than Symfony built-in methods and 143 | * it returns true only when the user is really anonymous. 144 | * 145 | * @param null $user 146 | * 147 | * @return bool 148 | */ 149 | public function isAnonymous($user = null) 150 | { 151 | return !$this->isAuthenticated($user); 152 | } 153 | 154 | /** 155 | * Returns true if the current application user (or the optionally given user) 156 | * is remembered. This behaves differently than Symfony built-in methods and 157 | * it returns true only when the user is really remembered and they haven't 158 | * introduced their credentials (username and password). 159 | * 160 | * @param null $user 161 | * 162 | * @return bool 163 | */ 164 | public function isRemembered($user = null) 165 | { 166 | $user = $user ?: $this->getUser(); 167 | 168 | if ($this->isFullyAuthenticated($user)) { 169 | return false; 170 | } 171 | 172 | return $this->isGranted('IS_AUTHENTICATED_REMEMBERED', $user); 173 | } 174 | 175 | /** 176 | * Returns true if the current application user (or the optionally given user) 177 | * is authenticated because they have introduced their credentials (username 178 | * and password). 179 | * 180 | * @param null $user 181 | * 182 | * @return bool 183 | */ 184 | public function isFullyAuthenticated($user = null) 185 | { 186 | $user = $user ?: $this->getUser(); 187 | 188 | return $this->isGranted('IS_AUTHENTICATED_FULLY', $user); 189 | } 190 | 191 | /** 192 | * Returns true if the current application user (or the optionally given user) 193 | * is authenticated in any way (because they have introduced their credentials 194 | * (username and password) or they have been remembered). 195 | * 196 | * @param null $user 197 | * 198 | * @return bool 199 | */ 200 | public function isAuthenticated($user = null) 201 | { 202 | $user = $user ?: $this->getUser(); 203 | 204 | return $this->isGranted('IS_AUTHENTICATED_FULLY', $user) || $this->isGranted('IS_AUTHENTICATED_REMEMBERED', $user); 205 | } 206 | 207 | /** 208 | * It logs in the given user in the 'main' application firewall (or the 209 | * optionally given firewall name). 210 | * 211 | * @param UserInterface $user 212 | * @param string $firewallName 213 | * 214 | * @return UserInterface 215 | */ 216 | public function login(UserInterface $user, $firewallName = 'main') 217 | { 218 | $token = new UsernamePasswordToken($user, $user->getPassword(), $firewallName, $user->getRoles()); 219 | $this->tokenStorage->setToken($token); 220 | 221 | return $user; 222 | } 223 | 224 | /** 225 | * Returns the given plain password encoded/hashed using the encoder of the 226 | * current application user or the optionally given user. 227 | * 228 | * @param string $plainPassword 229 | * @param null $user 230 | * 231 | * @return string 232 | */ 233 | public function encodePassword($plainPassword, $user = null) 234 | { 235 | $user = $user ?: $this->getUser(); 236 | 237 | return $this->passwordEncoder->encodePassword($user, $plainPassword); 238 | } 239 | 240 | /** 241 | * Returns true if the given plain password is valid for the current 242 | * application user or the optionally given user. 243 | * 244 | * @param string $plainPassword 245 | * @param null $user 246 | * 247 | * @return bool 248 | */ 249 | public function isPasswordValid($plainPassword, $user = null) 250 | { 251 | $user = $user ?: $this->getUser(); 252 | 253 | return $this->passwordEncoder->isPasswordValid($user, $plainPassword); 254 | } 255 | 256 | /** 257 | * Returns an array with the roles of the given user turned into Role objects, 258 | * which are needed by methods such as getReachableRoles(). 259 | * 260 | * @param UserInterface $user 261 | * 262 | * @return RoleInterface[] 263 | */ 264 | private function getUserRolesAsObjects(UserInterface $user) 265 | { 266 | $userRoles = array(); 267 | foreach ($user->getRoles() as $userRole) { 268 | $userRoles[] = $userRole instanceof Role ? $userRole : new Role($userRole); 269 | } 270 | 271 | return $userRoles; 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easycorp/easy-security-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Useful security-related shortcuts for Symfony applications", 5 | "keywords": ["dx", "security", "symfony"], 6 | "homepage": "https://github.com/EasyCorp/easy-security-bundle", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Javier Eguiluz", 11 | "email": "javiereguiluz@gmail.com" 12 | }, 13 | { 14 | "name": "Project Contributors", 15 | "homepage": "https://github.com/EasyCorp/easy-security-bundle" 16 | } 17 | ], 18 | "require": { 19 | "php" : ">=5.3.0", 20 | "symfony/dependency-injection" : "~2.3|~3.0", 21 | "symfony/http-foundation" : "~2.3|~3.0", 22 | "symfony/http-kernel" : "~2.3|~3.0", 23 | "symfony/security" : "~2.6|~3.0", 24 | "symfony/security-bundle" : "~2.6|~3.0" 25 | }, 26 | "autoload": { 27 | "psr-4": { "EasyCorp\\Bundle\\EasySecurityBundle\\": "" }, 28 | "exclude-from-classmap": ["/Tests/"] 29 | } 30 | } 31 | --------------------------------------------------------------------------------