From 85232a3fb6b35159601d52a4b4578af96a15896b Mon Sep 17 00:00:00 2001 From: Sebastian Neuser Date: Mon, 16 Dec 2024 11:21:46 +0100 Subject: [PATCH] feat(provisioning): Implement role provisioning --- app/Console/Commands/ProvisionCommand.php | 9 ++ app/Services/ProvisioningService.php | 47 +++++++ .../Backend/Unit/ProvisioningServiceTest.php | 120 ++++++++++++++++++ 3 files changed, 176 insertions(+) diff --git a/app/Console/Commands/ProvisionCommand.php b/app/Console/Commands/ProvisionCommand.php index 5ca76c90c..8e82e1f1f 100644 --- a/app/Console/Commands/ProvisionCommand.php +++ b/app/Console/Commands/ProvisionCommand.php @@ -44,6 +44,9 @@ public function handle() if ($data->servers->wipe) { $this->provision->server->destroy(); } + if ($data->roles->wipe) { + $this->provision->role->destroy(); + } // Add new instances Log::notice('Provisioning {n} servers', ['n' => count($data->servers->add)]); @@ -60,5 +63,11 @@ public function handle() foreach ($data->room_types->add as $item) { $this->provision->roomType->create($item); } + + Log::notice('Provisioning {n} roles', ['n' => count($data->roles->add)]); + foreach ($data->roles->add as $item) { + $item->permissions = (array) $item->permissions; + $this->provision->role->create($item); + } } } diff --git a/app/Services/ProvisioningService.php b/app/Services/ProvisioningService.php index 51fb07dd0..7494d7945 100644 --- a/app/Services/ProvisioningService.php +++ b/app/Services/ProvisioningService.php @@ -3,6 +3,8 @@ namespace App\Services; use App\Enums\ServerStatus; +use App\Models\Permission; +use App\Models\Role; use App\Models\RoomType; use App\Models\Server; use App\Models\ServerPool; @@ -174,6 +176,48 @@ public function destroy(array $match = []) } } +class RoleProvisioner extends AbstractProvisioner +{ + protected string $model = Role::class; + + protected array $expectedProperties = [ + 'name' => 'required|string', + 'permissions' => 'required|array:rooms,meetings,settings,users,roles,roomTypes,servers,serverPools', + 'permissions.rooms' => 'required|list', + 'permissions.meetings' => 'required|list', + 'permissions.settings' => 'required|list', + 'permissions.users' => 'required|list', + 'permissions.roles' => 'required|list', + 'permissions.roomTypes' => 'required|list', + 'permissions.servers' => 'required|list', + 'permissions.serverPools' => 'required|list', + ]; + + public function create(object $properties) + { + $this->createWrapper($properties, function ($role) use ($properties) { + foreach ($properties->permissions as $group => $perms) { + foreach ($perms as $item) { + $permName = "$group.$item"; + $perm = Permission::firstWhere('name', $permName); + if (is_null($perm)) { + throw new RecordsNotFoundException("Could not find permission with name '$permName'"); + } + $permissions[] = $perm->id; + } + } + $role->name = $properties->name; + $role->save(); + $role->permissions()->sync($permissions); + }); + } + + public function destroy(array $match = []) + { + $this->destroyWrapper($match); + } +} + class ProvisioningService { public ServerProvisioner $server; @@ -182,10 +226,13 @@ class ProvisioningService public RoomTypeProvisioner $roomType; + public RoleProvisioner $role; + public function __construct() { $this->server = new ServerProvisioner; $this->serverPool = new ServerPoolProvisioner; $this->roomType = new RoomTypeProvisioner; + $this->role = new RoleProvisioner; } } diff --git a/tests/Backend/Unit/ProvisioningServiceTest.php b/tests/Backend/Unit/ProvisioningServiceTest.php index 4ad78e1ea..6341c4432 100644 --- a/tests/Backend/Unit/ProvisioningServiceTest.php +++ b/tests/Backend/Unit/ProvisioningServiceTest.php @@ -3,10 +3,12 @@ namespace Tests\Backend\Unit; use App\Enums\ServerStatus; +use App\Models\Role; use App\Models\RoomType; use App\Models\Server; use App\Models\ServerPool; use App\Services\ProvisioningService; +use Database\Seeders\RolesAndPermissionsSeeder; use Illuminate\Database\RecordsNotFoundException; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\Backend\TestCase; @@ -20,6 +22,8 @@ protected function setUp(): void { parent::setUp(); + $this->seed(RolesAndPermissionsSeeder::class); + $this->svc = new ProvisioningService; $this->testServer = (object) [ @@ -41,6 +45,47 @@ protected function setUp(): void 'color' => '#aaaaaa', 'server_pool' => $this->testServerPool->name, ]; + $this->testRole = (object) [ + 'name' => 'Testrole', + 'permissions' => [ + 'rooms' => [ + 'viewAll', + 'manage', + ], + 'meetings' => [ + 'viewAny', + ], + 'settings' => [ + 'viewAny', + 'update', + ], + 'users' => [ + 'viewAny', + 'view', + 'update', + 'create', + 'delete', + ], + 'roles' => [ + 'viewAny', + 'view', + ], + 'roomTypes' => [ + 'view', + 'update', + 'create', + 'delete', + ], + 'servers' => [ + 'viewAny', + 'view', + ], + 'serverPools' => [ + 'viewAny', + 'view', + ], + ], + ]; for ($i = 1; $i <= 3; $i++) { $server = new Server; @@ -271,4 +316,79 @@ public function test_room_type_delete_named() $this->assertNotNull(RoomType::firstWhere('name', "Existing {$this->testRoomType->name} 1")); $this->assertNotNull(RoomType::firstWhere('name', "Existing {$this->testRoomType->name} 3")); } + + /** + * Test role creation + */ + public function test_role_create() + { + $this->svc->role->create($this->testRole); + $role = Role::firstWhere('name', $this->testRole->name); + $this->assertNotNull($role); + foreach ($this->testRole->permissions as $group => $perms) { + foreach ($perms as $perm) { + $wanted_permissions[] = "$group.$perm"; + } + } + $saved_permissions = array_map(fn ($it) => $it->name, $role->permissions->all()); + $this->assertEquals($wanted_permissions, $saved_permissions); + } + + /** + * Test role creation with invalid permissions + */ + public function test_role_create_invalid() + { + $this->testRole->permissions['fnord'] = ['foo', 'bar']; + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Invalid role definition'); + $this->svc->role->create($this->testRole); + $this->assertNull(Role::firstWhere('name', $this->testRole->name)); + } + + /** + * Test role creation with incomplete permissions spec + */ + public function test_role_create_incomplete() + { + unset($this->testRole->permissions['rooms']); + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Invalid role definition'); + $this->svc->role->create($this->testRole); + $this->assertNull(Role::firstWhere('name', $this->testRole->name)); + } + + /** + * Test role creation with incomplete permissions spec + */ + public function test_role_create_non_existing_permission() + { + $this->testRole->permissions['rooms'] = ['show']; + $this->expectException(RecordsNotFoundException::class); + $this->expectExceptionMessage("Could not find permission with name 'rooms.show'"); + $this->svc->role->create($this->testRole); + $this->assertNull(Role::firstWhere('name', $this->testRole->name)); + } + + /** + * Test deletion of all roles + */ + public function test_role_delete_all() + { + $this->assertEquals(2, count(Role::all())); + $this->svc->role->destroy(); + $this->assertEquals(0, count(Role::all())); + } + + /** + * Test deletion of specified role + */ + public function test_role_delete_named() + { + $this->assertEquals(2, count(Role::all())); + $this->svc->role->destroy(['name' => 'User']); + $this->assertEquals(1, count(Role::all())); + $this->assertNull(Role::firstWhere('name', 'User')); + $this->assertNotNull(Role::firstWhere('name', 'Superuser')); + } }