Skip to content

Commit dadaded

Browse files
committed
ISSUE-345: admin privileges
1 parent 601fbc1 commit dadaded

File tree

8 files changed

+232
-6
lines changed

8 files changed

+232
-6
lines changed

src/Domain/Identity/Model/Administrator.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use DateTime;
88
use Doctrine\ORM\Mapping as ORM;
9+
use InvalidArgumentException;
910
use PhpList\Core\Domain\Common\Model\Interfaces\CreationDate;
1011
use PhpList\Core\Domain\Common\Model\Interfaces\DomainModel;
1112
use PhpList\Core\Domain\Common\Model\Interfaces\Identity;
@@ -72,6 +73,7 @@ public function __construct()
7273
$this->createdAt = new DateTime();
7374
$this->updatedAt = null;
7475
$this->email = '';
76+
$this->privileges = null;
7577
}
7678

7779
public function getLoginName(): string
@@ -159,6 +161,24 @@ public function setPrivileges(Privileges $privileges): self
159161
return $this;
160162
}
161163

164+
/**
165+
* @SuppressWarnings(PHPMD.StaticAccess)
166+
* @throws InvalidArgumentException
167+
*/
168+
public function setPrivilegesFromArray(array $privilegesData): void
169+
{
170+
$privileges = new Privileges();
171+
foreach ($privilegesData as $key => $value) {
172+
$flag = PrivilegeFlag::tryFrom($key);
173+
if (!$flag) {
174+
throw new InvalidArgumentException('Unknown privilege key: '. $key);
175+
}
176+
177+
$privileges = $value ? $privileges->grant($flag) : $privileges->revoke($flag);
178+
}
179+
$this->setPrivileges($privileges);
180+
}
181+
162182
/**
163183
* @SuppressWarnings(PHPMD.StaticAccess)
164184
*/

src/Domain/Identity/Model/Dto/CreateAdministratorDto.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public function __construct(
1414
public readonly string $password,
1515
public readonly string $email,
1616
public readonly bool $isSuperUser = false,
17+
public readonly array $privileges = [],
1718
) {
1819
}
1920
}

src/Domain/Identity/Model/Dto/UpdateAdministratorDto.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public function __construct(
1212
public readonly ?string $password = null,
1313
public readonly ?string $email = null,
1414
public readonly ?bool $superAdmin = null,
15+
public readonly array $privileges = [],
1516
) {
1617
}
1718
}

src/Domain/Identity/Model/Privileges.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace PhpList\Core\Domain\Identity\Model;
66

7+
use InvalidArgumentException;
8+
use UnexpectedValueException;
9+
710
class Privileges
811
{
912
/**
@@ -14,7 +17,7 @@ class Privileges
1417
/**
1518
* @SuppressWarnings(PHPMD.StaticAccess)
1619
*/
17-
private function __construct(array $flags = [])
20+
public function __construct(?array $flags = [])
1821
{
1922
foreach (PrivilegeFlag::cases() as $flag) {
2023
$key = $flag->value;
@@ -28,9 +31,18 @@ public static function fromSerialized(?string $serialized): self
2831
return new self();
2932
}
3033

31-
$data = @unserialize($serialized);
34+
set_error_handler(function () {
35+
throw new InvalidArgumentException('Invalid serialized privileges string.');
36+
});
37+
38+
try {
39+
$data = unserialize($serialized);
40+
} finally {
41+
restore_error_handler();
42+
}
43+
3244
if (!is_array($data)) {
33-
return new self();
45+
throw new UnexpectedValueException('Privileges must deserialize to an array.');
3446
}
3547

3648
return new self($data);
@@ -65,4 +77,3 @@ public function all(): array
6577
return $this->flags;
6678
}
6779
}
68-

src/Domain/Identity/Service/AdministratorManager.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
namespace PhpList\Core\Domain\Identity\Service;
66

77
use Doctrine\ORM\EntityManagerInterface;
8+
use InvalidArgumentException;
89
use PhpList\Core\Domain\Identity\Model\Administrator;
910
use PhpList\Core\Domain\Identity\Model\Dto\CreateAdministratorDto;
1011
use PhpList\Core\Domain\Identity\Model\Dto\UpdateAdministratorDto;
12+
use PhpList\Core\Domain\Identity\Model\PrivilegeFlag;
13+
use PhpList\Core\Domain\Identity\Model\Privileges;
1114
use PhpList\Core\Security\HashGenerator;
1215

1316
class AdministratorManager
@@ -29,6 +32,7 @@ public function createAdministrator(CreateAdministratorDto $dto): Administrator
2932
$administrator->setSuperUser($dto->isSuperUser);
3033
$hashedPassword = $this->hashGenerator->createPasswordHash($dto->password);
3134
$administrator->setPasswordHash($hashedPassword);
35+
$administrator->setPrivilegesFromArray($dto->privileges);
3236

3337
$this->entityManager->persist($administrator);
3438
$this->entityManager->flush();
@@ -51,6 +55,7 @@ public function updateAdministrator(Administrator $administrator, UpdateAdminist
5155
$hashedPassword = $this->hashGenerator->createPasswordHash($dto->password);
5256
$administrator->setPasswordHash($hashedPassword);
5357
}
58+
$administrator->setPrivilegesFromArray($dto->privileges);
5459

5560
$this->entityManager->flush();
5661
}

tests/Unit/Domain/Identity/Model/AdministratorTest.php

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44

55
namespace PhpList\Core\Tests\Unit\Domain\Identity\Model;
66

7+
use DateTime;
78
use PhpList\Core\Domain\Common\Model\Interfaces\DomainModel;
89
use PhpList\Core\Domain\Identity\Model\Administrator;
10+
use PhpList\Core\Domain\Identity\Model\Privileges;
11+
use PhpList\Core\Domain\Identity\Model\PrivilegeFlag;
912
use PhpList\Core\TestingSupport\Traits\ModelTestTrait;
1013
use PhpList\Core\TestingSupport\Traits\SimilarDatesAssertionTrait;
1114
use PHPUnit\Framework\TestCase;
@@ -75,7 +78,7 @@ public function testUpdateModificationDateSetsModificationDateToNow(): void
7578
{
7679
$this->subject->updateUpdatedAt();
7780

78-
self::assertSimilarDates(new \DateTime(), $this->subject->getUpdatedAt());
81+
self::assertSimilarDates(new DateTime(), $this->subject->getUpdatedAt());
7982
}
8083

8184
public function testGetPasswordHashInitiallyReturnsEmptyString(): void
@@ -98,7 +101,7 @@ public function testGetPasswordChangeDateInitiallyReturnsNull(): void
98101

99102
public function testSetPasswordHashSetsPasswordChangeDateToNow(): void
100103
{
101-
$date = new \DateTime();
104+
$date = new DateTime();
102105
$this->subject->setPasswordHash('Zaphod Beeblebrox');
103106

104107
self::assertSimilarDates($date, $this->subject->getPasswordChangeDate());
@@ -127,4 +130,42 @@ public function testSetSuperUserSetsSuperUser(): void
127130

128131
self::assertTrue($this->subject->isSuperUser());
129132
}
133+
134+
public function testGetPrivilegesInitiallyReturnsEmptyPrivileges(): void
135+
{
136+
$privileges = $this->subject->getPrivileges();
137+
138+
self::assertInstanceOf(Privileges::class, $privileges);
139+
140+
foreach (PrivilegeFlag::cases() as $flag) {
141+
self::assertFalse($privileges->has($flag));
142+
}
143+
}
144+
145+
public function testSetPrivilegesSetsPrivileges(): void
146+
{
147+
$privileges = Privileges::fromSerialized('');
148+
$privileges = $privileges->grant(PrivilegeFlag::Subscribers);
149+
150+
$this->subject->setPrivileges($privileges);
151+
152+
$retrievedPrivileges = $this->subject->getPrivileges();
153+
self::assertTrue($retrievedPrivileges->has(PrivilegeFlag::Subscribers));
154+
self::assertFalse($retrievedPrivileges->has(PrivilegeFlag::Campaigns));
155+
}
156+
157+
public function testSetPrivilegesWithMultiplePrivileges(): void
158+
{
159+
$privileges = Privileges::fromSerialized('');
160+
$privileges = $privileges
161+
->grant(PrivilegeFlag::Subscribers)
162+
->grant(PrivilegeFlag::Campaigns);
163+
164+
$this->subject->setPrivileges($privileges);
165+
166+
$retrievedPrivileges = $this->subject->getPrivileges();
167+
self::assertTrue($retrievedPrivileges->has(PrivilegeFlag::Subscribers));
168+
self::assertTrue($retrievedPrivileges->has(PrivilegeFlag::Campaigns));
169+
self::assertFalse($retrievedPrivileges->has(PrivilegeFlag::Statistics));
170+
}
130171
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\Core\Tests\Unit\Domain\Identity\Model;
6+
7+
use PhpList\Core\Domain\Identity\Model\PrivilegeFlag;
8+
use PHPUnit\Framework\TestCase;
9+
10+
/**
11+
* Testcase for the PrivilegeFlag enum.
12+
*/
13+
class PrivilegeFlagTest extends TestCase
14+
{
15+
public function testEnumHasSubscribersCase(): void
16+
{
17+
self::assertSame('subscribers', PrivilegeFlag::Subscribers->value);
18+
}
19+
20+
public function testEnumHasCampaignsCase(): void
21+
{
22+
self::assertSame('campaigns', PrivilegeFlag::Campaigns->value);
23+
}
24+
25+
public function testEnumHasStatisticsCase(): void
26+
{
27+
self::assertSame('statistics', PrivilegeFlag::Statistics->value);
28+
}
29+
30+
public function testEnumHasSettingsCase(): void
31+
{
32+
self::assertSame('settings', PrivilegeFlag::Settings->value);
33+
}
34+
35+
public function testEnumHasFourCases(): void
36+
{
37+
$cases = PrivilegeFlag::cases();
38+
39+
self::assertCount(4, $cases);
40+
self::assertContains(PrivilegeFlag::Subscribers, $cases);
41+
self::assertContains(PrivilegeFlag::Campaigns, $cases);
42+
self::assertContains(PrivilegeFlag::Statistics, $cases);
43+
self::assertContains(PrivilegeFlag::Settings, $cases);
44+
}
45+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\Core\Tests\Unit\Domain\Identity\Model;
6+
7+
use InvalidArgumentException;
8+
use PhpList\Core\Domain\Identity\Model\Privileges;
9+
use PhpList\Core\Domain\Identity\Model\PrivilegeFlag;
10+
use PHPUnit\Framework\TestCase;
11+
12+
/**
13+
* Testcase for the Privileges class.
14+
*/
15+
class PrivilegesTest extends TestCase
16+
{
17+
private Privileges $subject;
18+
19+
protected function setUp(): void
20+
{
21+
$this->subject = Privileges::fromSerialized('');
22+
}
23+
24+
public function testFromSerializedWithInvalidDataThrowsError(): void
25+
{
26+
$this->expectException(InvalidArgumentException::class);
27+
$this->expectExceptionMessage('Invalid serialized privileges string.');
28+
29+
Privileges::fromSerialized('invalid data');
30+
}
31+
32+
public function testFromSerializedWithValidDataReturnsPopulatedPrivileges(): void
33+
{
34+
$data = [PrivilegeFlag::Subscribers->value => true];
35+
$serialized = serialize($data);
36+
37+
$privileges = Privileges::fromSerialized($serialized);
38+
39+
self::assertTrue($privileges->has(PrivilegeFlag::Subscribers));
40+
}
41+
42+
public function testToSerializedReturnsSerializedData(): void
43+
{
44+
$privileges = Privileges::fromSerialized('');
45+
$privileges = $privileges->grant(PrivilegeFlag::Subscribers);
46+
47+
$serialized = $privileges->toSerialized();
48+
$data = unserialize($serialized);
49+
50+
self::assertTrue($data[PrivilegeFlag::Subscribers->value]);
51+
}
52+
53+
public function testHasReturnsFalseForUnsetPrivilege(): void
54+
{
55+
self::assertFalse($this->subject->has(PrivilegeFlag::Subscribers));
56+
}
57+
58+
public function testHasReturnsTrueForSetPrivilege(): void
59+
{
60+
$this->subject = $this->subject->grant(PrivilegeFlag::Subscribers);
61+
62+
self::assertTrue($this->subject->has(PrivilegeFlag::Subscribers));
63+
}
64+
65+
public function testGrantSetsPrivilege(): void
66+
{
67+
$result = $this->subject->grant(PrivilegeFlag::Subscribers);
68+
69+
self::assertTrue($result->has(PrivilegeFlag::Subscribers));
70+
}
71+
72+
public function testGrantReturnsNewInstance(): void
73+
{
74+
$result = $this->subject->grant(PrivilegeFlag::Subscribers);
75+
76+
self::assertNotSame($this->subject, $result);
77+
}
78+
79+
public function testRevokeClearsPrivilege(): void
80+
{
81+
$this->subject = $this->subject->grant(PrivilegeFlag::Subscribers);
82+
$result = $this->subject->revoke(PrivilegeFlag::Subscribers);
83+
84+
self::assertFalse($result->has(PrivilegeFlag::Subscribers));
85+
}
86+
87+
public function testRevokeReturnsNewInstance(): void
88+
{
89+
$result = $this->subject->revoke(PrivilegeFlag::Subscribers);
90+
91+
self::assertNotSame($this->subject, $result);
92+
}
93+
94+
public function testAllReturnsAllPrivileges(): void
95+
{
96+
$this->subject = $this->subject->grant(PrivilegeFlag::Subscribers);
97+
$all = $this->subject->all();
98+
99+
self::assertTrue($all[PrivilegeFlag::Subscribers->value]);
100+
self::assertFalse($all[PrivilegeFlag::Campaigns->value]);
101+
}
102+
}

0 commit comments

Comments
 (0)