Skip to content

Commit b93d732

Browse files
devtronicXWB
authored andcommitted
Migrated SF AdvancedUserInterface to FOS UserInterface (FriendsOfSymfony#2815)
* Migrated SF AdvancedUserInterface to FOS UserInterface AdvancedUserInterface is deprecated since Symfony 4.1 - symfony/symfony#23508 Issue: - FriendsOfSymfony#2803 Deprecation with Symfony 4.1 - AdvancedUserInterface * Code style fixed and using `getMockBuilder` instead of `createMock` * Code style fixed and using attributes instead of `$this->expectException` * Change to restart travis * EquatableInterface added to `UserInterface` and implementation added to `User` * Tests after merge of master fixed * Tests after merge of master fixed * Update README.md * Added compatibility for apps that check against AdvancedUserInterface * Code style fixed to pass all travis tests * fos_user.user_checker Service marked as non-public
1 parent f328299 commit b93d732

File tree

8 files changed

+290
-6
lines changed

8 files changed

+290
-6
lines changed

Model/User.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Doctrine\Common\Collections\ArrayCollection;
1515
use Doctrine\Common\Collections\Collection;
16+
use Symfony\Component\Security\Core\User\UserInterface as BaseUserInterface;
1617

1718
/**
1819
* Storage agnostic user object.
@@ -554,4 +555,28 @@ public function removeGroup(GroupInterface $group)
554555

555556
return $this;
556557
}
558+
559+
/**
560+
* {@inheritdoc}
561+
*/
562+
public function isEqualTo(BaseUserInterface $user)
563+
{
564+
if (!$user instanceof self) {
565+
return false;
566+
}
567+
568+
if ($this->password !== $user->getPassword()) {
569+
return false;
570+
}
571+
572+
if ($this->salt !== $user->getSalt()) {
573+
return false;
574+
}
575+
576+
if ($this->username !== $user->getUsername()) {
577+
return false;
578+
}
579+
580+
return true;
581+
}
557582
}

Model/UserInterface.php

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111

1212
namespace FOS\UserBundle\Model;
1313

14-
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
14+
use Symfony\Component\Security\Core\User\EquatableInterface;
15+
use Symfony\Component\Security\Core\User\UserInterface as BaseUserInterface;
1516

1617
/**
17-
* @author Thibault Duplessis <[email protected]>
18-
* @author Johannes M. Schmitt <[email protected]>
18+
* @internal Only for back compatibility. Remove / merge when dropping support for Symfony 4
1919
*/
20-
interface UserInterface extends AdvancedUserInterface, \Serializable
20+
interface FosUserInterface extends \Serializable
2121
{
2222
const ROLE_DEFAULT = 'ROLE_USER';
2323

@@ -227,4 +227,74 @@ public function addRole($role);
227227
* @return static
228228
*/
229229
public function removeRole($role);
230+
231+
/**
232+
* Checks whether the user's account has expired.
233+
*
234+
* Internally, if this method returns false, the authentication system
235+
* will throw an AccountExpiredException and prevent login.
236+
*
237+
* @return bool true if the user's account is non expired, false otherwise
238+
*
239+
* @see AccountExpiredException
240+
*/
241+
public function isAccountNonExpired();
242+
243+
/**
244+
* Checks whether the user is locked.
245+
*
246+
* Internally, if this method returns false, the authentication system
247+
* will throw a LockedException and prevent login.
248+
*
249+
* @return bool true if the user is not locked, false otherwise
250+
*
251+
* @see LockedException
252+
*/
253+
public function isAccountNonLocked();
254+
255+
/**
256+
* Checks whether the user's credentials (password) has expired.
257+
*
258+
* Internally, if this method returns false, the authentication system
259+
* will throw a CredentialsExpiredException and prevent login.
260+
*
261+
* @return bool true if the user's credentials are non expired, false otherwise
262+
*
263+
* @see CredentialsExpiredException
264+
*/
265+
public function isCredentialsNonExpired();
266+
267+
/**
268+
* Checks whether the user is enabled.
269+
*
270+
* Internally, if this method returns false, the authentication system
271+
* will throw a DisabledException and prevent login.
272+
*
273+
* @return bool true if the user is enabled, false otherwise
274+
*
275+
* @see DisabledException
276+
*/
277+
public function isEnabled();
278+
}
279+
280+
// This is required to support apps that explicitly check if a user is an instance of AdvancedUserInterface
281+
if (interface_exists('\Symfony\Component\Security\Core\User\AdvancedUserInterface')) {
282+
/**
283+
* @author Thibault Duplessis <[email protected]>
284+
* @author Johannes M. Schmitt <[email protected]>
285+
*
286+
* @deprecated since Symfony 4.1. Remove in Nov 2023 (End of support for security fixes SF 4.4)
287+
*/
288+
interface UserInterface extends FosUserInterface, \Symfony\Component\Security\Core\User\AdvancedUserInterface
289+
{
290+
}
291+
} else {
292+
/**
293+
* @author Thibault Duplessis <[email protected]>
294+
* @author Johannes M. Schmitt <[email protected]>
295+
* @author Julian Finkler <[email protected]>
296+
*/
297+
interface UserInterface extends FosUserInterface, BaseUserInterface, EquatableInterface
298+
{
299+
}
230300
}

Resources/config/security.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
</service>
4646

4747
<service id="FOS\UserBundle\Controller\SecurityController" alias="fos_user.security.controller" public="true" />
48+
49+
<service id="fos_user.user_checker" class="FOS\UserBundle\Security\UserChecker" public="false" />
4850
</services>
4951

5052
</container>

Resources/doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ in your application:
274274
firewalls:
275275
main:
276276
pattern: ^/
277+
user_checker: fos_user.user_checker
277278
form_login:
278279
provider: fos_userbundle
279280
csrf_token_generator: security.csrf.token_manager

Security/UserChecker.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSUserBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
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 FOS\UserBundle\Security;
13+
14+
use Symfony\Component\Security\Core\Exception\AccountExpiredException;
15+
use Symfony\Component\Security\Core\Exception\CredentialsExpiredException;
16+
use Symfony\Component\Security\Core\Exception\DisabledException;
17+
use Symfony\Component\Security\Core\Exception\LockedException;
18+
use Symfony\Component\Security\Core\User\UserChecker as BaseUserChecker;
19+
use Symfony\Component\Security\Core\User\UserInterface as BaseUserInterface;
20+
21+
/**
22+
* UserChecker checks the user account flags.
23+
*
24+
* @author Julian Finkler (Devtronic) <[email protected]>
25+
*/
26+
class UserChecker extends BaseUserChecker
27+
{
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
public function checkPreAuth(BaseUserInterface $user)
32+
{
33+
if (!$user->isAccountNonLocked()) {
34+
$ex = new LockedException('User account is locked.');
35+
$ex->setUser($user);
36+
throw $ex;
37+
}
38+
39+
if (!$user->isEnabled()) {
40+
$ex = new DisabledException('User account is disabled.');
41+
$ex->setUser($user);
42+
throw $ex;
43+
}
44+
45+
if (!$user->isAccountNonExpired()) {
46+
$ex = new AccountExpiredException('User account has expired.');
47+
$ex->setUser($user);
48+
throw $ex;
49+
}
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function checkPostAuth(BaseUserInterface $user)
56+
{
57+
if (!$user->isCredentialsNonExpired()) {
58+
$ex = new CredentialsExpiredException('User credentials have expired.');
59+
$ex->setUser($user);
60+
throw $ex;
61+
}
62+
}
63+
}

Tests/Mailer/MailerTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
use FOS\UserBundle\Mailer\Mailer;
1515
use PHPUnit\Framework\TestCase;
16-
use Swift_Events_EventDispatcher;
1716
use Swift_Mailer;
1817
use Swift_Transport_NullTransport;
1918

@@ -84,7 +83,7 @@ private function getMailer()
8483
return new Mailer(
8584
new Swift_Mailer(
8685
new Swift_Transport_NullTransport(
87-
$this->getMockBuilder(Swift_Events_EventDispatcher::class)->getMock()
86+
$this->getMockBuilder('\Swift_Events_EventDispatcher')->getMock()
8887
)
8988
),
9089
$this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock(),

Tests/Model/UserTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,25 @@ public function testFalseHasRole()
7979
$this->assertTrue($user->hasRole($newrole));
8080
}
8181

82+
public function testIsEqualTo()
83+
{
84+
$user = $this->getUser();
85+
$this->assertTrue($user->isEqualTo($user));
86+
$this->assertFalse($user->isEqualTo($this->getMockBuilder('FOS\UserBundle\Model\UserInterface')->getMock()));
87+
88+
$user2 = $this->getUser();
89+
$user2->setPassword('secret');
90+
$this->assertFalse($user->isEqualTo($user2));
91+
92+
$user3 = $this->getUser();
93+
$user3->setSalt('pepper');
94+
$this->assertFalse($user->isEqualTo($user3));
95+
96+
$user4 = $this->getUser();
97+
$user4->setUsername('f00b4r');
98+
$this->assertFalse($user->isEqualTo($user4));
99+
}
100+
82101
/**
83102
* @return User
84103
*/

Tests/Security/UserCheckerTest.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSUserBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
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 FOS\UserBundle\Tests\Security;
13+
14+
use FOS\UserBundle\Security\UserChecker;
15+
use PHPUnit\Framework\TestCase;
16+
17+
class UserCheckerTest extends TestCase
18+
{
19+
/**
20+
* @expectedException \Symfony\Component\Security\Core\Exception\LockedException
21+
* @expectedExceptionMessage User account is locked.
22+
*/
23+
public function testCheckPreAuthFailsLockedOut()
24+
{
25+
$userMock = $this->getUser(false, false, false, false);
26+
$checker = new UserChecker();
27+
$checker->checkPreAuth($userMock);
28+
}
29+
30+
/**
31+
* @expectedException \Symfony\Component\Security\Core\Exception\DisabledException
32+
* @expectedExceptionMessage User account is disabled.
33+
*/
34+
public function testCheckPreAuthFailsIsEnabled()
35+
{
36+
$userMock = $this->getUser(true, false, false, false);
37+
$checker = new UserChecker();
38+
$checker->checkPreAuth($userMock);
39+
}
40+
41+
/**
42+
* @expectedException \Symfony\Component\Security\Core\Exception\AccountExpiredException
43+
* @expectedExceptionMessage User account has expired.
44+
*/
45+
public function testCheckPreAuthFailsIsAccountNonExpired()
46+
{
47+
$userMock = $this->getUser(true, true, false, false);
48+
$checker = new UserChecker();
49+
$checker->checkPreAuth($userMock);
50+
}
51+
52+
public function testCheckPreAuthSuccess()
53+
{
54+
$userMock = $this->getUser(true, true, true, false);
55+
$checker = new UserChecker();
56+
57+
try {
58+
$this->assertNull($checker->checkPreAuth($userMock));
59+
} catch (\Exception $ex) {
60+
$this->fail();
61+
}
62+
}
63+
64+
/**
65+
* @expectedException \Symfony\Component\Security\Core\Exception\CredentialsExpiredException
66+
* @expectedExceptionMessage User credentials have expired.
67+
*/
68+
public function testCheckPostAuthFailsIsCredentialsNonExpired()
69+
{
70+
$userMock = $this->getUser(true, true, true, false);
71+
$checker = new UserChecker();
72+
$checker->checkPostAuth($userMock);
73+
}
74+
75+
public function testCheckPostAuthSuccess()
76+
{
77+
$userMock = $this->getUser(true, true, true, true);
78+
$checker = new UserChecker();
79+
80+
try {
81+
$this->assertNull($checker->checkPostAuth($userMock));
82+
} catch (\Exception $ex) {
83+
$this->fail();
84+
}
85+
}
86+
87+
private function getUser($isAccountNonLocked, $isEnabled, $isAccountNonExpired, $isCredentialsNonExpired)
88+
{
89+
$userMock = $this->getMockBuilder('FOS\UserBundle\Model\User')->getMock();
90+
$userMock
91+
->method('isAccountNonLocked')
92+
->willReturn($isAccountNonLocked);
93+
$userMock
94+
->method('isEnabled')
95+
->willReturn($isEnabled);
96+
$userMock
97+
->method('isAccountNonExpired')
98+
->willReturn($isAccountNonExpired);
99+
$userMock
100+
->method('isCredentialsNonExpired')
101+
->willReturn($isCredentialsNonExpired);
102+
103+
return $userMock;
104+
}
105+
}

0 commit comments

Comments
 (0)