diff --git a/server-connection/databaselist/app/Http/Controllers/Api/V1/GroupPermissionController.php b/server-connection/databaselist/app/Http/Controllers/Api/V1/GroupPermissionController.php new file mode 100644 index 0000000..64c1c1e --- /dev/null +++ b/server-connection/databaselist/app/Http/Controllers/Api/V1/GroupPermissionController.php @@ -0,0 +1,79 @@ +orderBy('permission')->get(); + + $grouped = $rows->groupBy('group_key')->map(fn ($items) => $items->pluck('permission')->values()); + + return $this->ok([ + 'group_permissions' => $grouped, + 'available_permissions' => $this->permissions->allPermissions(), + ]); + } + + public function store(Request $request): JsonResponse + { + $data = $request->validate([ + 'group_key' => ['required', 'string', 'max:100'], + 'permission' => ['required', 'string', 'max:100', 'in:' . implode(',', $this->permissions->allPermissions())], + ]); + + $row = GroupPermission::firstOrCreate([ + 'group_key' => $data['group_key'], + 'permission' => $data['permission'], + ]); + + $this->permissions->flushCacheForGroups([$data['group_key']]); + + return $this->ok($row->toArray(), [], $row->wasRecentlyCreated ? 201 : 200); + } + + public function destroy(Request $request, string $group, string $permission): JsonResponse + { + $deleted = GroupPermission::where('group_key', $group) + ->where('permission', $permission) + ->delete(); + + $this->permissions->flushCacheForGroups([$group]); + + return $this->ok(['deleted' => (bool) $deleted]); + } + + public function syncGroup(Request $request, string $group): JsonResponse + { + $data = $request->validate([ + 'permissions' => ['required', 'array'], + 'permissions.*' => ['string', 'in:' . implode(',', $this->permissions->allPermissions())], + ]); + + GroupPermission::where('group_key', $group)->delete(); + + foreach ($data['permissions'] as $permission) { + GroupPermission::create(['group_key' => $group, 'permission' => $permission]); + } + + $this->permissions->flushCacheForGroups([$group]); + + return $this->ok(['group_key' => $group, 'permissions' => $data['permissions']]); + } +} diff --git a/server-connection/databaselist/app/Http/Controllers/Api/V1/MeController.php b/server-connection/databaselist/app/Http/Controllers/Api/V1/MeController.php index c87d6d3..b4b899f 100644 --- a/server-connection/databaselist/app/Http/Controllers/Api/V1/MeController.php +++ b/server-connection/databaselist/app/Http/Controllers/Api/V1/MeController.php @@ -4,19 +4,53 @@ namespace App\Http\Controllers\Api\V1; use App\Http\Controllers\Api\Concerns\RespondsWithJson; use App\Http\Controllers\Controller; +use App\Models\LdapUserMeta; +use App\Services\LdapAuthService; use App\Support\ApiActor; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Storage; class MeController extends Controller { use RespondsWithJson; + public function __construct( + private readonly LdapAuthService $ldapAuth, + ) { + } + public function show(Request $request): JsonResponse { /** @var ApiActor $actor */ $actor = $request->attributes->get('api_actor'); - return $this->ok($actor->toArray()); + $base = $actor->toArray(); + + if ($actor->type === 'ldap_jwt') { + $profile = $this->ldapAuth->getProfile($actor->identifier); + $meta = LdapUserMeta::where('username', $actor->identifier)->first(); + + $avatarUrl = null; + if ($meta?->avatar) { + try { + $avatarUrl = Storage::disk('oss')->url($meta->avatar); + } catch (\Throwable) { + $avatarUrl = null; + } + } + + $base = array_merge($base, [ + 'display_name' => $profile['display_name'] ?? $actor->identifier, + 'email' => $profile['email'] ?? null, + 'domain' => $profile['domain'] ?? null, + 'last_logon' => $profile['last_logon'] ?? null, + 'phone' => $meta?->phone, + 'other_email' => $meta?->other_email, + 'avatar' => $avatarUrl, + ]); + } + + return $this->ok($base); } } diff --git a/server-connection/databaselist/app/Models/GroupPermission.php b/server-connection/databaselist/app/Models/GroupPermission.php new file mode 100644 index 0000000..90805bc --- /dev/null +++ b/server-connection/databaselist/app/Models/GroupPermission.php @@ -0,0 +1,23 @@ +pluck('permission') + ->unique() + ->values() + ->all(); + } +} diff --git a/server-connection/databaselist/app/Services/ApiAuthService.php b/server-connection/databaselist/app/Services/ApiAuthService.php index 85bb442..d59189c 100644 --- a/server-connection/databaselist/app/Services/ApiAuthService.php +++ b/server-connection/databaselist/app/Services/ApiAuthService.php @@ -15,6 +15,7 @@ class ApiAuthService private readonly ApiTokenService $apiTokens, private readonly LdapJwtService $ldapJwt, private readonly ResourceServer $resourceServer, + private readonly PermissionService $permissions, ) { } @@ -59,11 +60,16 @@ class ApiAuthService return null; } + $groups = $payload['groups'] ?? []; + $resolvedPermissions = $this->permissions->permissionsForGroups($groups); + return new ApiActor( 'ldap_jwt', $payload['username'], $payload['scopes'], - $payload['is_admin'] + $payload['is_admin'], + $groups, + $resolvedPermissions, ); } diff --git a/server-connection/databaselist/app/Services/LdapJwtService.php b/server-connection/databaselist/app/Services/LdapJwtService.php index 29478bf..0b2afb1 100644 --- a/server-connection/databaselist/app/Services/LdapJwtService.php +++ b/server-connection/databaselist/app/Services/LdapJwtService.php @@ -45,7 +45,8 @@ class LdapJwtService return [ 'username' => (string) ($payload['sub'] ?? ''), 'is_admin' => (bool) ($payload['is_admin'] ?? false), - 'scopes' => array_values((array) ($payload['scopes'] ?? [])), + 'scopes' => array_values((array) ($payload['scopes'] ?? [])), + 'groups' => array_values((array) ($payload['groups'] ?? [])), ]; } catch (UnexpectedValueException|\DomainException|\InvalidArgumentException) { return null; diff --git a/server-connection/databaselist/app/Services/PermissionService.php b/server-connection/databaselist/app/Services/PermissionService.php new file mode 100644 index 0000000..3946648 --- /dev/null +++ b/server-connection/databaselist/app/Services/PermissionService.php @@ -0,0 +1,58 @@ +permissionsForGroups($groups), true); + } + + public function allPermissions(): array + { + return [ + 'profile.view', + 'profile.edit', + 'dashboard.view', + 'cmdb.view', + 'cmdb.manage', + 'cmdb.assign_owner', + 'monitoring.view', + 'ticketing.view', + 'ticketing.create', + 'ticketing.manage', + 'mail.view', + 'asset_mgmt.view', + 'asset_mgmt.manage', + 'geonet_agent.view', + 'admin.users', + 'admin.groups', + 'admin.permissions', + ]; + } + + public function flushCacheForGroups(array $groups): void + { + $cacheKey = 'group_perms:' . md5(implode(',', $groups)); + Cache::forget($cacheKey); + } +} diff --git a/server-connection/databaselist/database/migrations/2026_06_25_000001_create_group_permissions_table.php b/server-connection/databaselist/database/migrations/2026_06_25_000001_create_group_permissions_table.php new file mode 100644 index 0000000..d285660 --- /dev/null +++ b/server-connection/databaselist/database/migrations/2026_06_25_000001_create_group_permissions_table.php @@ -0,0 +1,25 @@ +id(); + $table->string('group_key', 100)->index(); + $table->string('permission', 100)->index(); + $table->timestamps(); + + $table->unique(['group_key', 'permission']); + }); + } + + public function down(): void + { + Schema::dropIfExists('group_permissions'); + } +}; diff --git a/server-connection/databaselist/database/seeders/GroupPermissionSeeder.php b/server-connection/databaselist/database/seeders/GroupPermissionSeeder.php new file mode 100644 index 0000000..44e9670 --- /dev/null +++ b/server-connection/databaselist/database/seeders/GroupPermissionSeeder.php @@ -0,0 +1,96 @@ + [ + 'profile.view', 'profile.edit', + 'dashboard.view', + 'cmdb.view', 'cmdb.manage', 'cmdb.assign_owner', + 'monitoring.view', + 'ticketing.view', 'ticketing.create', 'ticketing.manage', + 'mail.view', + 'asset_mgmt.view', 'asset_mgmt.manage', + 'geonet_agent.view', + 'admin.users', 'admin.groups', 'admin.permissions', + ], + 'IT-Admin' => [ + 'profile.view', 'profile.edit', + 'dashboard.view', + 'cmdb.view', 'cmdb.manage', 'cmdb.assign_owner', + 'monitoring.view', + 'ticketing.view', 'ticketing.create', 'ticketing.manage', + 'asset_mgmt.view', 'asset_mgmt.manage', + 'geonet_agent.view', + ], + 'IT-Staff' => [ + 'profile.view', 'profile.edit', + 'dashboard.view', + 'cmdb.view', 'cmdb.manage', 'cmdb.assign_owner', + 'monitoring.view', + 'ticketing.view', 'ticketing.create', + 'asset_mgmt.view', + 'geonet_agent.view', + ], + 'Management' => [ + 'profile.view', 'profile.edit', + 'dashboard.view', + 'cmdb.view', + 'monitoring.view', + 'ticketing.view', 'ticketing.create', + 'mail.view', + 'asset_mgmt.view', + ], + 'Staff' => [ + 'profile.view', 'profile.edit', + 'dashboard.view', + 'ticketing.view', 'ticketing.create', + 'mail.view', + ], + 'HR' => [ + 'profile.view', 'profile.edit', + 'dashboard.view', + 'ticketing.view', 'ticketing.create', + 'mail.view', + ], + ]; + + public function run(): void + { + foreach ($this->defaults as $group => $permissions) { + foreach ($permissions as $permission) { + GroupPermission::firstOrCreate([ + 'group_key' => $group, + 'permission' => $permission, + ]); + } + } + } +} diff --git a/server-connection/databaselist/routes/api.php b/server-connection/databaselist/routes/api.php index 27dc07e..0d375f7 100644 --- a/server-connection/databaselist/routes/api.php +++ b/server-connection/databaselist/routes/api.php @@ -2,6 +2,7 @@ use App\Http\Controllers\Api\V1\AuthController; use App\Http\Controllers\Api\V1\DatabaseController; +use App\Http\Controllers\Api\V1\GroupPermissionController; use App\Http\Controllers\Api\V1\LdapGroupController; use App\Http\Controllers\Api\V1\LdapUserController; use App\Http\Controllers\Api\V1\MeController; @@ -56,5 +57,18 @@ Route::prefix('v1')->group(function () { Route::delete('/ldap/groups/{group}/members/{username}', [LdapGroupController::class, 'removeMember']) ->middleware('api.scope:ldap:write') ->name('api.v1.ldap.groups.members.remove'); + + Route::get('/group-permissions', [GroupPermissionController::class, 'index']) + ->middleware('api.scope:admin') + ->name('api.v1.group-permissions.index'); + Route::post('/group-permissions', [GroupPermissionController::class, 'store']) + ->middleware('api.scope:admin') + ->name('api.v1.group-permissions.store'); + Route::put('/group-permissions/{group}', [GroupPermissionController::class, 'syncGroup']) + ->middleware('api.scope:admin') + ->name('api.v1.group-permissions.sync'); + Route::delete('/group-permissions/{group}/{permission}', [GroupPermissionController::class, 'destroy']) + ->middleware('api.scope:admin') + ->name('api.v1.group-permissions.destroy'); }); });