Skip to content

Commit addb4d4

Browse files
Add subshop support for role
1 parent e221ee8 commit addb4d4

7 files changed

Lines changed: 118 additions & 22 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ curl -X POST https://your-shop.com/api/login \
4040
-d '{"username": "user@example.com", "password": "password"}'
4141
```
4242

43+
To authenticate against a specific subshop, pass the `shp` query parameter:
44+
45+
```bash
46+
curl -X POST "https://your-shop.com/api/login?shp=2" \
47+
-H "Content-Type: application/json" \
48+
-d '{"username": "user@example.com", "password": "password"}'
49+
```
50+
4351
Response:
4452

4553
```json

src/Security/User/ApiUserProvider.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ public function loadUserByIdentifier(string $identifier): UserInterface
3333
->from('oxuser')
3434
->where('OXUSERNAME = :username')
3535
->andWhere('OXACTIVE = 1')
36-
->andWhere('OXSHOPID = :shopId')
36+
->andWhere('(OXSHOPID = :shopId OR OXRIGHTS = :mallAdmin)')
3737
->setParameter('username', $identifier)
38-
->setParameter('shopId', $this->context->getCurrentShopId());
38+
->setParameter('shopId', $this->context->getCurrentShopId())
39+
->setParameter('mallAdmin', 'malladmin');
3940

4041
$userData = $queryBuilder->execute()->fetchAssociative();
4142

src/Security/User/RoleResolver.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,25 @@
99

1010
namespace OxidEsales\AuthComponent\Security\User;
1111

12+
use OxidEsales\EshopCommunity\Internal\Transition\Utility\ContextInterface;
13+
1214
final readonly class RoleResolver implements RoleResolverInterface
1315
{
1416
/**
1517
* @param array<string, list<string>> $roleMapping Map of rights string to additional roles
1618
*/
1719
public function __construct(
20+
private ContextInterface $context,
1821
private array $roleMapping = []
1922
) {
2023
}
2124

2225
public function resolveRoles(string $rights): array
2326
{
24-
$roles = ['ROLE_USER'];
27+
$roles = array_merge(['ROLE_USER'], $this->roleMapping[$rights] ?? []);
2528

26-
if (isset($this->roleMapping[$rights])) {
27-
$roles = array_merge($roles, $this->roleMapping[$rights]);
29+
if ($rights === (string) $this->context->getCurrentShopId()) {
30+
$roles[] = 'ROLE_ADMIN';
2831
}
2932

3033
return $roles;

src/Security/User/services.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ parameters:
44
- ROLE_ADMIN
55
oxid_jwt_authenticator.role_mapping:
66
malladmin: ['ROLE_ADMIN', 'ROLE_ADMIN_MALL']
7-
'1': ['ROLE_ADMIN']
87

98
services:
109
_defaults:

tests/Integration/Security/ApiAuthenticationTest.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,10 @@ protected function setUp(): void
5555
$container = ContainerFactory::getInstance()->getContainer();
5656
$this->queryBuilderFactory = $container->get(QueryBuilderFactoryInterface::class);
5757

58-
$roleResolver = new RoleResolver([
58+
$context = $container->get(ContextInterface::class);
59+
$roleResolver = new RoleResolver($context, [
5960
'malladmin' => ['ROLE_ADMIN', 'ROLE_ADMIN_MALL'],
60-
'1' => ['ROLE_ADMIN'],
6161
]);
62-
$context = $container->get(ContextInterface::class);
6362
$this->userProvider = new ApiUserProvider($this->queryBuilderFactory, $roleResolver, $context);
6463

6564
$passwordHasher = new OxidPasswordHasher($container->get(PasswordServiceBridgeInterface::class));

tests/Integration/Security/ApiUserProviderTest.php

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ final class ApiUserProviderTest extends TestCase
2828
private string $testMallAdminUsername;
2929
private string $testAdminId;
3030
private string $testAdminUsername;
31+
private string $testSubshopAdminId;
32+
private string $testSubshopAdminUsername;
3133

3234
protected function setUp(): void
3335
{
@@ -37,16 +39,16 @@ protected function setUp(): void
3739
$this->queryBuilderFactory = $container->get(QueryBuilderFactoryInterface::class);
3840
$context = $container->get(ContextInterface::class);
3941

40-
$roleResolver = new RoleResolver([
42+
$roleResolver = new RoleResolver($context, [
4143
'malladmin' => ['ROLE_ADMIN', 'ROLE_ADMIN_MALL'],
42-
'1' => ['ROLE_ADMIN'],
4344
]);
4445
$this->userProvider = new ApiUserProvider($this->queryBuilderFactory, $roleResolver, $context);
4546

4647
$timestamp = uniqid('', true);
4748
$this->testUsername = "test-user-provider-{$timestamp}@example.com";
4849
$this->testMallAdminUsername = "test-malladmin-{$timestamp}@example.com";
4950
$this->testAdminUsername = "test-admin-{$timestamp}@example.com";
51+
$this->testSubshopAdminUsername = "test-subshopadmin-{$timestamp}@example.com";
5052

5153
$this->createTestUsers();
5254
}
@@ -127,6 +129,62 @@ public function testRefreshUserWithMallAdmin(): void
127129
$this->assertContains('ROLE_ADMIN_MALL', $refreshedUser->getRoles());
128130
}
129131

132+
public function testSubshopAdminDoesNotGetAdminRoleOnDifferentShop(): void
133+
{
134+
$user = $this->userProvider->loadUserByIdentifier($this->testSubshopAdminUsername);
135+
136+
$this->assertContains('ROLE_USER', $user->getRoles());
137+
$this->assertNotContains('ROLE_ADMIN', $user->getRoles());
138+
}
139+
140+
public function testMallAdminIsFoundRegardlessOfShopId(): void
141+
{
142+
$connection = $this->queryBuilderFactory->create()->getConnection();
143+
$id = uniqid('malladmin_other_', true);
144+
$username = "test-malladmin-othershop-{$id}@example.com";
145+
146+
$connection->insert('oxuser', [
147+
'OXID' => $id,
148+
'OXUSERNAME' => $username,
149+
'OXPASSWORD' => hash('sha512', 'testpassword'),
150+
'OXRIGHTS' => 'malladmin',
151+
'OXACTIVE' => 1,
152+
'OXSHOPID' => 999,
153+
]);
154+
155+
try {
156+
$user = $this->userProvider->loadUserByIdentifier($username);
157+
158+
$this->assertContains('ROLE_ADMIN', $user->getRoles());
159+
$this->assertContains('ROLE_ADMIN_MALL', $user->getRoles());
160+
} finally {
161+
$connection->delete('oxuser', ['OXID' => $id]);
162+
}
163+
}
164+
165+
public function testRegularUserFromDifferentShopIsNotFound(): void
166+
{
167+
$connection = $this->queryBuilderFactory->create()->getConnection();
168+
$id = uniqid('user_other_', true);
169+
$username = "test-user-othershop-{$id}@example.com";
170+
171+
$connection->insert('oxuser', [
172+
'OXID' => $id,
173+
'OXUSERNAME' => $username,
174+
'OXPASSWORD' => hash('sha512', 'testpassword'),
175+
'OXRIGHTS' => 'user',
176+
'OXACTIVE' => 1,
177+
'OXSHOPID' => 999,
178+
]);
179+
180+
try {
181+
$this->expectException(UserNotFoundException::class);
182+
$this->userProvider->loadUserByIdentifier($username);
183+
} finally {
184+
$connection->delete('oxuser', ['OXID' => $id]);
185+
}
186+
}
187+
130188
public function testRefreshUserWithNumericAdmin(): void
131189
{
132190
$originalUser = new ApiUser($this->testAdminId, $this->testAdminUsername, ['ROLE_USER', 'ROLE_ADMIN']);
@@ -169,16 +227,26 @@ private function createTestUsers(): void
169227
'OXACTIVE' => 1,
170228
'OXSHOPID' => 1,
171229
]);
230+
231+
$this->testSubshopAdminId = uniqid('subshopadmin_', true);
232+
$connection->insert('oxuser', [
233+
'OXID' => $this->testSubshopAdminId,
234+
'OXUSERNAME' => $this->testSubshopAdminUsername,
235+
'OXPASSWORD' => hash('sha512', 'testpassword'),
236+
'OXRIGHTS' => '2',
237+
'OXACTIVE' => 1,
238+
'OXSHOPID' => 1,
239+
]);
172240
}
173241

174242
private function deleteTestUsers(): void
175243
{
176-
if (isset($this->testUserId, $this->testMallAdminId, $this->testAdminId)) {
244+
if (isset($this->testUserId, $this->testMallAdminId, $this->testAdminId, $this->testSubshopAdminId)) {
177245
$queryBuilder = $this->queryBuilderFactory->create();
178246
$queryBuilder
179247
->delete('oxuser')
180248
->where('OXID IN (:ids)')
181-
->setParameter('ids', [$this->testUserId, $this->testMallAdminId, $this->testAdminId], \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
249+
->setParameter('ids', [$this->testUserId, $this->testMallAdminId, $this->testAdminId, $this->testSubshopAdminId], \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
182250
->execute();
183251
}
184252
}

tests/Unit/Security/RoleResolverTest.php

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,28 @@
1010
namespace OxidEsales\AuthComponent\Tests\Unit\Security;
1111

1212
use OxidEsales\AuthComponent\Security\User\RoleResolver;
13+
use OxidEsales\EshopCommunity\Internal\Transition\Utility\ContextInterface;
1314
use PHPUnit\Framework\TestCase;
1415

1516
final class RoleResolverTest extends TestCase
1617
{
1718
private array $defaultRoleMapping = [
1819
'malladmin' => ['ROLE_ADMIN', 'ROLE_ADMIN_MALL'],
19-
'1' => ['ROLE_ADMIN'],
2020
];
2121

22+
private ContextInterface $context;
23+
24+
protected function setUp(): void
25+
{
26+
parent::setUp();
27+
28+
$this->context = $this->createMock(ContextInterface::class);
29+
$this->context->method('getCurrentShopId')->willReturn(1);
30+
}
31+
2232
public function testResolveRolesForRegularUser(): void
2333
{
24-
$resolver = new RoleResolver($this->defaultRoleMapping);
34+
$resolver = new RoleResolver($this->context, $this->defaultRoleMapping);
2535
$roles = $resolver->resolveRoles('user');
2636

2737
$this->assertContains('ROLE_USER', $roles);
@@ -31,27 +41,35 @@ public function testResolveRolesForRegularUser(): void
3141

3242
public function testResolveRolesForMallAdmin(): void
3343
{
34-
$resolver = new RoleResolver($this->defaultRoleMapping);
44+
$resolver = new RoleResolver($this->context, $this->defaultRoleMapping);
3545
$roles = $resolver->resolveRoles('malladmin');
3646

3747
$this->assertContains('ROLE_USER', $roles);
3848
$this->assertContains('ROLE_ADMIN', $roles);
3949
$this->assertContains('ROLE_ADMIN_MALL', $roles);
4050
}
4151

42-
public function testResolveRolesForNumericAdmin(): void
52+
public function testResolveRolesForNumericAdminMatchingCurrentShop(): void
4353
{
44-
$resolver = new RoleResolver($this->defaultRoleMapping);
54+
$resolver = new RoleResolver($this->context, $this->defaultRoleMapping);
4555
$roles = $resolver->resolveRoles('1');
4656

4757
$this->assertContains('ROLE_USER', $roles);
4858
$this->assertContains('ROLE_ADMIN', $roles);
4959
$this->assertFalse(in_array('ROLE_ADMIN_MALL', $roles, true));
5060
}
5161

62+
public function testResolveRolesForNumericAdminNotMatchingCurrentShop(): void
63+
{
64+
$resolver = new RoleResolver($this->context, $this->defaultRoleMapping);
65+
$roles = $resolver->resolveRoles('2');
66+
67+
$this->assertSame(['ROLE_USER'], $roles);
68+
}
69+
5270
public function testResolveRolesForEmptyRights(): void
5371
{
54-
$resolver = new RoleResolver($this->defaultRoleMapping);
72+
$resolver = new RoleResolver($this->context, $this->defaultRoleMapping);
5573
$roles = $resolver->resolveRoles('');
5674

5775
$this->assertContains('ROLE_USER', $roles);
@@ -60,7 +78,7 @@ public function testResolveRolesForEmptyRights(): void
6078

6179
public function testResolveRolesAlwaysContainsRoleUser(): void
6280
{
63-
$resolver = new RoleResolver($this->defaultRoleMapping);
81+
$resolver = new RoleResolver($this->context, $this->defaultRoleMapping);
6482

6583
$this->assertContains('ROLE_USER', $resolver->resolveRoles('user'));
6684
$this->assertContains('ROLE_USER', $resolver->resolveRoles('malladmin'));
@@ -75,7 +93,7 @@ public function testResolveRolesWithCustomMapping(): void
7593
'superuser' => ['ROLE_SUPER', 'ROLE_ADMIN'],
7694
'editor' => ['ROLE_EDITOR'],
7795
];
78-
$resolver = new RoleResolver($customMapping);
96+
$resolver = new RoleResolver($this->context, $customMapping);
7997

8098
$superuserRoles = $resolver->resolveRoles('superuser');
8199
$this->assertContains('ROLE_USER', $superuserRoles);
@@ -90,7 +108,7 @@ public function testResolveRolesWithCustomMapping(): void
90108

91109
public function testResolveRolesWithEmptyMapping(): void
92110
{
93-
$resolver = new RoleResolver([]);
111+
$resolver = new RoleResolver($this->context, []);
94112
$roles = $resolver->resolveRoles('malladmin');
95113

96114
$this->assertSame(['ROLE_USER'], $roles);

0 commit comments

Comments
 (0)