Skip to content

Commit fbe0c6b

Browse files
committed
Refactor access control to use interface and trait
Introduced AccessUserInterface and HasAccess trait to standardize user access methods. Updated ExternalUser to implement the interface and use the trait. Refactored PermissionsClient and LoadAccess middleware to depend on the interface, improving flexibility. Added tests for the trait and PermissionsClient.
1 parent 10aed25 commit fbe0c6b

File tree

7 files changed

+181
-44
lines changed

7 files changed

+181
-44
lines changed

src/Auth/ExternalUser.php

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44

55
use Illuminate\Foundation\Auth\User as Authenticatable;
66
use Illuminate\Notifications\Notifiable;
7+
use Kroderdev\LaravelMicroserviceCore\Contracts\AccessUserInterface;
8+
use Kroderdev\LaravelMicroserviceCore\Traits\HasAccess;
79

8-
class ExternalUser extends Authenticatable
10+
class ExternalUser extends Authenticatable implements AccessUserInterface
911
{
10-
use Notifiable;
12+
use Notifiable, HasAccess;
1113

1214
protected $attributes = [];
13-
protected array $roles = [];
14-
protected array $permissions = [];
1515

1616
public function __construct(array $attributes = [])
1717
{
@@ -25,30 +25,4 @@ public function getAuthIdentifier() { return $this->attributes[$this->getAuthIde
2525
public function getAuthPassword() { return null; }
2626
public function __get($key){ return $this->attributes[$key] ?? null; }
2727

28-
// Permissions
29-
public function loadAccess(array $roles, array $permissions): void
30-
{
31-
$this->roles = $roles;
32-
$this->permissions = $permissions;
33-
}
34-
35-
public function hasRole(string $role): bool
36-
{
37-
return in_array($role, $this->roles);
38-
}
39-
40-
public function hasPermissionTo(string $permission): bool
41-
{
42-
return in_array($permission, $this->permissions);
43-
}
44-
45-
public function getRoleNames(): array
46-
{
47-
return $this->roles;
48-
}
49-
50-
public function getPermissions(): array
51-
{
52-
return $this->permissions;
53-
}
5428
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Kroderdev\LaravelMicroserviceCore\Contracts;
4+
5+
use Illuminate\Contracts\Auth\Authenticatable;
6+
7+
/**
8+
* Contract for user models that expose roles and permissions.
9+
*/
10+
interface AccessUserInterface extends Authenticatable
11+
{
12+
/** Load roles and permissions for the user */
13+
public function loadAccess(array $roles, array $permissions): void;
14+
15+
/** Check if the user has the given role */
16+
public function hasRole(string $role): bool;
17+
18+
/** Check if the user has the given permission */
19+
public function hasPermissionTo(string $permission): bool;
20+
21+
/** Return all role names */
22+
public function getRoleNames(): array;
23+
24+
/** Return all permissions */
25+
public function getPermissions(): array;
26+
}

src/Http/Middleware/LoadAccess.php

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
namespace Kroderdev\LaravelMicroserviceCore\Http\Middleware;
44

55
use Closure;
6-
use Firebase\JWT\JWT;
7-
use Firebase\JWT\Key;
86
use Illuminate\Http\Request;
97
use Illuminate\Support\Facades\Auth;
10-
use Kroderdev\LaravelMicroserviceCore\Auth\ExternalUser;
8+
use Kroderdev\LaravelMicroserviceCore\Contracts\AccessUserInterface;
119
use Kroderdev\LaravelMicroserviceCore\Services\PermissionsClient;
1210
use Symfony\Component\HttpFoundation\Response;
1311

@@ -34,15 +32,16 @@ public function handle(Request $request, Closure $next): Response
3432
], Response::HTTP_UNAUTHORIZED);
3533
}
3634

37-
try {
38-
$access = app(PermissionsClient::class)->getAccessFor($user);
39-
$user->loadAccess(
40-
$access['roles'] ?? [],
41-
$access['permissions'] ?? []
42-
);
43-
} catch (\Throwable $e) {
44-
// Do nothing
45-
// $user->loadAccess([], []);
35+
if ($user instanceof AccessUserInterface) {
36+
try {
37+
$access = app(PermissionsClient::class)->getAccessFor($user);
38+
$user->loadAccess(
39+
$access['roles'] ?? [],
40+
$access['permissions'] ?? []
41+
);
42+
} catch (\Throwable $e) {
43+
// Do nothing
44+
}
4645
}
4746

4847
return $next($request);

src/Services/PermissionsClient.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace Kroderdev\LaravelMicroserviceCore\Services;
44

55
use Illuminate\Support\Facades\Cache;
6-
use Kroderdev\LaravelMicroserviceCore\Auth\ExternalUser;
6+
use Kroderdev\LaravelMicroserviceCore\Contracts\AccessUserInterface;
77
use Kroderdev\LaravelMicroserviceCore\Contracts\ApiGatewayClientInterface;
88

99
class PermissionsClient
@@ -15,7 +15,7 @@ public function __construct(ApiGatewayClientInterface $gateway)
1515
$this->gateway = $gateway;
1616
}
1717

18-
public function getAccessFor(ExternalUser $user): array
18+
public function getAccessFor(AccessUserInterface $user): array
1919
{
2020
$cacheKey = "user_access:{$user->getAuthIdentifier()}";
2121
$ttl = config('microservice.permissions_cache_ttl', 60);

src/Traits/HasAccess.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Kroderdev\LaravelMicroserviceCore\Traits;
4+
5+
trait HasAccess
6+
{
7+
protected array $roles = [];
8+
protected array $permissions = [];
9+
10+
/**
11+
* Load roles and permissions for the user.
12+
*/
13+
public function loadAccess(array $roles, array $permissions): void
14+
{
15+
$this->roles = $roles;
16+
$this->permissions = $permissions;
17+
}
18+
19+
/**
20+
* Determine if the user has the given role.
21+
*/
22+
public function hasRole(string $role): bool
23+
{
24+
return in_array($role, $this->roles);
25+
}
26+
27+
/**
28+
* Determine if the user has the given permission.
29+
*/
30+
public function hasPermissionTo(string $permission): bool
31+
{
32+
return in_array($permission, $this->permissions);
33+
}
34+
35+
/**
36+
* Get all role names for the user.
37+
*/
38+
public function getRoleNames(): array
39+
{
40+
return $this->roles;
41+
}
42+
43+
/**
44+
* Get all permissions for the user.
45+
*/
46+
public function getPermissions(): array
47+
{
48+
return $this->permissions;
49+
}
50+
}

tests/Auth/AccessTraitTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Tests\Auth;
4+
5+
use Illuminate\Foundation\Auth\User;
6+
use Kroderdev\LaravelMicroserviceCore\Traits\HasAccess;
7+
use Orchestra\Testbench\TestCase;
8+
9+
class DefaultUser extends User
10+
{
11+
use HasAccess;
12+
protected $fillable = ['id'];
13+
}
14+
15+
class AccessTraitTest extends TestCase
16+
{
17+
/** @test */
18+
public function trait_adds_access_methods_to_user()
19+
{
20+
$user = new DefaultUser(['id' => 1]);
21+
$user->loadAccess(['admin'], ['edit.posts']);
22+
23+
$this->assertTrue($user->hasRole('admin'));
24+
$this->assertFalse($user->hasRole('guest'));
25+
$this->assertTrue($user->hasPermissionTo('edit.posts'));
26+
$this->assertEquals(['admin'], $user->getRoleNames());
27+
$this->assertEquals(['edit.posts'], $user->getPermissions());
28+
}
29+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace Tests\Services;
4+
5+
use Illuminate\Support\Facades\Cache;
6+
use Orchestra\Testbench\TestCase;
7+
use Kroderdev\LaravelMicroserviceCore\Contracts\ApiGatewayClientInterface;
8+
use Kroderdev\LaravelMicroserviceCore\Services\PermissionsClient;
9+
use Kroderdev\LaravelMicroserviceCore\Traits\HasAccess;
10+
use Kroderdev\LaravelMicroserviceCore\Contracts\AccessUserInterface;
11+
use Illuminate\Foundation\Auth\User;
12+
13+
require_once __DIR__.'/FakeGatewayClient.php';
14+
15+
class DummyUser extends User implements AccessUserInterface
16+
{
17+
use HasAccess;
18+
protected $fillable = ['id'];
19+
}
20+
21+
class PermissionsClientTest extends TestCase
22+
{
23+
protected FakeGatewayClient $gateway;
24+
25+
protected function setUp(): void
26+
{
27+
parent::setUp();
28+
$this->gateway = new class extends FakeGatewayClient {
29+
public function get(string $uri, array $query = [])
30+
{
31+
parent::get($uri, $query);
32+
return new class {
33+
public function failed() { return false; }
34+
public function json() { return ['roles' => ['admin'], 'permissions' => ['edit.posts']]; }
35+
};
36+
}
37+
};
38+
$this->app->bind(ApiGatewayClientInterface::class, fn () => $this->gateway);
39+
Cache::flush();
40+
}
41+
42+
protected function getPackageProviders($app)
43+
{
44+
return [\Kroderdev\LaravelMicroserviceCore\Providers\MicroserviceServiceProvider::class];
45+
}
46+
47+
/** @test */
48+
public function retrieves_access_for_any_user_model()
49+
{
50+
$client = new PermissionsClient($this->app->make(ApiGatewayClientInterface::class));
51+
$user = new DummyUser(['id' => 5]);
52+
53+
$access = $client->getAccessFor($user);
54+
55+
$this->assertEquals(['admin'], $access['roles']);
56+
$this->assertEquals(['edit.posts'], $access['permissions']);
57+
$this->assertSame('/auth/permissions/5', $this->gateway->getCalls()[0]['uri']);
58+
}
59+
}

0 commit comments

Comments
 (0)