Skip to content

Commit

Permalink
Allow per-model permission level management.
Browse files Browse the repository at this point in the history
  • Loading branch information
zipavlin committed Sep 17, 2024
1 parent 4fa57e7 commit 3a7bd95
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 51 deletions.
10 changes: 8 additions & 2 deletions docs/content/1_docs/10_user-management/2_advanced-permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ In addition to this we have to configure the permissions' system. There are 3 le
Set the `twill.permissions.level` to the desired type. And also set the modules to manage in
the `twill.permissions.modules` key.

Beside setting global `twill.permissions.level` it is also possible to set per-module level by adding
it to `twill.permissions.modules` as keyed item as shown for model `pages` in example below.

```php {7-10}
<?php

Expand All @@ -37,8 +40,11 @@ return [
'permissions-management'
],
'permissions' => [
'level' => \A17\Twill\Enums\PermissionLevel::LEVEL_ROLE,
'modules' => ['blogs'],
'level' => \A17\Twill\Enums\PermissionLevel::LEVEL_ROLE_GROUP,
'modules' => [
'blogs',
'pages' => \A17\Twill\Enums\PermissionLevel::LEVEL_ROLE_GROUP_ITEM
],
],
]
```
Expand Down
5 changes: 4 additions & 1 deletion src/Http/Controllers/Admin/GroupController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace A17\Twill\Http\Controllers\Admin;

use A17\Twill\Enums\PermissionLevel;
use A17\Twill\Facades\TwillPermissions;
use A17\Twill\Models\Contracts\TwillModelContract;
use A17\Twill\Services\Listings\Columns\Text;
Expand Down Expand Up @@ -67,8 +68,10 @@ protected function getIndexOption($option, $item = null): mixed

protected function formData($request): array
{
// Divide permissions based on level to minimize instance fetching
return [
'permissionModules' => Permission::permissionableParentModuleItems(),
'permissionModulesLevelGroup' => Permission::permissionableModulesForLevel([PermissionLevel::LEVEL_ROLE_GROUP, PermissionLevel::LEVEL_ROLE_GROUP_ITEM]),
'permissionModulesLevelItem' => Permission::permissionableParentModuleItemsForLevel(PermissionLevel::LEVEL_ROLE_GROUP_ITEM),
];
}

Expand Down
19 changes: 3 additions & 16 deletions src/Http/Controllers/Admin/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -201,20 +201,16 @@ protected function formData($request)
$titleThumbnail = $user->cmsImage($role, $crop, $params);
}

if (TwillPermissions::levelIs(PermissionLevel::LEVEL_ROLE_GROUP_ITEM)) {
$permissionsData = [
'permissionModules' => $this->getPermissionModules(),
];
}

// Fetch modules with LEVEL_ROLE_GROUP_ITEM permission level
return [
'roleList' => $this->getRoleList(),
'titleThumbnail' => $titleThumbnail ?? null,
'with2faSettings' => $with2faSettings,
'qrCode' => $qrCode ?? null,
'groupPermissionMapping' => $this->getGroupPermissionMapping(),
'groupOptions' => $this->getGroups(),
] + ($permissionsData ?? []);
'permissionModules' => Permission::permissionableParentModuleItemsForLevel(PermissionLevel::LEVEL_ROLE_GROUP_ITEM)
];
}

/**
Expand Down Expand Up @@ -354,15 +350,6 @@ private function getRoleList()
})->values()->toArray();
}

private function getPermissionModules()
{
if (config('twill.enabled.permissions-management')) {
return Permission::permissionableParentModuleItems();
}

return [];
}

public function getSubmitOptions(Model $item): ?array
{
// Use options from form template
Expand Down
59 changes: 58 additions & 1 deletion src/Models/Permission.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
use A17\Twill\Enums\PermissionLevel;
use A17\Twill\Exceptions\ModuleNotFoundException;
use A17\Twill\Facades\TwillPermissions;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Foundation\Auth\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Model as BaseModel;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;

class Permission extends BaseModel
{
Expand Down Expand Up @@ -87,14 +90,43 @@ public static function available($scope)
}
}

/**
* Retrieve the list of modules with specific permission levels that can be applied.
*
* @return Collection
*/
public static function permissionableModulesWithLevel()
{
return collect(config('twill.permissions.modules', []))
->mapWithKeys(function ($item, $key) {
if (is_integer($key)) {
return [$item => config('twill.permissions.level', PermissionLevel::LEVEL_ROLE)];
}
return [$key => $item];
});
}

/**
* Retrieve the list of modules that permissions can be applied to for specific permission level.
*
* @return Collection
*/
public static function permissionableModulesForLevel(string|array $level)
{
if (is_string($level)) {
return self::permissionableModulesWithLevel()->filter(fn($item, $key) => $item === $level)->keys();
}
return self::permissionableModulesWithLevel()->filter(fn($item, $key) => in_array($item, $level))->keys();
}

/**
* Retrieve the list of modules that permissions can be applied to.
*
* @return Collection
*/
public static function permissionableModules()
{
return collect(config('twill.permissions.modules', []));
return self::permissionableModulesWithLevel()->keys();
}

/**
Expand All @@ -115,6 +147,31 @@ public static function permissionableParentModuleItems()
});
}

/**
* Retrieve a collection of items that belongs to models with specific permission level.
*
* @param string $level
* @return Collection
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public static function permissionableParentModuleItemsForLevel(string $level)
{
return self::permissionableModulesWithLevel()
->filter(fn($item, $key) => $item === $level)
->keys()
->filter(function ($module) {
return !strpos($module, '.');
})
->mapWithKeys(function ($module) {
try {
return [$module => getRepositoryByModuleName($module)->get([], [], [], -1)];
} catch (ModuleNotFoundException $e) {
return [];
}
});
}

/**
* Get the parent permissionable model (one of the permissionale module).
*
Expand Down
9 changes: 8 additions & 1 deletion src/Repositories/Behaviors/HandleGroupPermissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,21 @@ public function getFormFieldsHandleGroupPermissions($object, $fields)
}

// Add active module permissions
foreach (Permission::permissionableModules() as $moduleName) {
foreach (Permission::permissionableModulesForLevel([PermissionLevel::LEVEL_ROLE_GROUP, PermissionLevel::LEVEL_ROLE_GROUP_ITEM]) as $moduleName) {
$modulePermission = $object->permissions()->module()->ofModuleName($moduleName)->first();
if ($modulePermission) {
$fields['module_' . $moduleName . '_permissions'] = $modulePermission->name;
} else {
$fields['module_' . $moduleName . '_permissions'] = 'none';
}
}

// Add LEVEL_ROLE_GROUP_ITEM permission level modules with items
foreach ($object->permissions()->moduleItem()->get() as $permission) {
$model = $permission->permissionable()->first();
$moduleName = getModuleNameByModel($model);
$fields[$moduleName . '_' . $model->id . '_permission'] = $permission->name;
}
} elseif (TwillPermissions::levelIs(PermissionLevel::LEVEL_ROLE_GROUP_ITEM)) {
// Add active item permissions
foreach ($object->permissions()->moduleItem()->get() as $permission) {
Expand Down
2 changes: 1 addition & 1 deletion src/Repositories/Behaviors/HandlePermissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public function afterSaveHandlePermissions($object, $fields)

private function shouldProcessPermissions($moduleName): bool
{
return TwillPermissions::levelIs(PermissionLevel::LEVEL_ROLE_GROUP_ITEM)
return TwillPermissions::levelForModuleIs($moduleName, PermissionLevel::LEVEL_ROLE_GROUP_ITEM)
&& TwillPermissions::getPermissionModule($moduleName);
}

Expand Down
5 changes: 2 additions & 3 deletions src/Repositories/Behaviors/HandleUserPermissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace A17\Twill\Repositories\Behaviors;

use A17\Twill\Enums\PermissionLevel;
use A17\Twill\Facades\TwillPermissions;
use A17\Twill\Models\Model;
use A17\Twill\Models\Permission;
use A17\Twill\Models\User;
Expand All @@ -23,7 +22,7 @@ public function getFormFieldsHandleUserPermissions($object, $fields)
{
if (
!config('twill.enabled.permissions-management') ||
!TwillPermissions::levelIs(PermissionLevel::LEVEL_ROLE_GROUP_ITEM)
Permission::permissionableModulesForLevel(PermissionLevel::LEVEL_ROLE_GROUP_ITEM)->isEmpty()
) {
return $fields;
}
Expand Down Expand Up @@ -58,7 +57,7 @@ public function afterSaveHandleUserPermissions($object, $fields)

$this->addOrRemoveUserToEveryoneGroup($object);

if (TwillPermissions::levelIs(PermissionLevel::LEVEL_ROLE_GROUP_ITEM)) {
if (Permission::permissionableModulesForLevel(PermissionLevel::LEVEL_ROLE_GROUP_ITEM)->isNotEmpty()) {
$this->updateUserItemPermissions($object, $fields);
}
}
Expand Down
17 changes: 17 additions & 0 deletions src/TwillPermissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ public function levelIs(string $level): bool
return $this->enabled() && config('twill.permissions.level') === $level;
}

/**
* Check specific permission level for module
*
* @param string $module
* @param string $level
* @return bool
* @throws \Exception
*/
public function levelForModuleIs(string $module, string $level): bool
{
if (!PermissionLevel::isValid($level)) {
throw new \Exception('Invalid permission level. Check TwillPermissions for available levels');
}

return $this->enabled() && Permission::permissionableModulesWithLevel()->get($module) === $level;
}

public function levelIsOneOf(array $levels): bool
{
foreach ($levels as $level) {
Expand Down
47 changes: 24 additions & 23 deletions views/groups/form.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,22 @@
:max="999"
/>

@if(\A17\Twill\Facades\TwillPermissions::levelIs(\A17\Twill\Enums\PermissionLevel::LEVEL_ROLE_GROUP))
<x-twill::fieldRows title="Content permissions">
@if(config('twill.support_subdomain_admin_routing'))
<x-twill::fieldRows title="Subdomain access">
@foreach(config('twill.app_names') as $subdomain => $subdomainTitle)
<x-twill::checkbox
:name="'subdomain_access_' . $subdomain"
:label="$subdomainTitle"
/>
@endforeach
</x-twill::fieldRows>
@endif
@stop


@section('fieldsets')
@if($permissionModulesLevelGroup->isNotEmpty())
<a17-fieldset title='Per-module permissions' id='modules'>
<x-twill::checkbox
name="manage-modules"
label="Manage all modules"
Expand All @@ -27,7 +41,7 @@
<x-twill::formConnectedFields field-name="manage-modules"
:fieldValues="false"
>
@foreach($permissionModules as $moduleName => $moduleItems)
@foreach($permissionModulesLevelGroup as $moduleName)
<x-twill::select
:name="'module_' . $moduleName . '_permissions'"
:label="ucfirst($moduleName) . ' permissions'"
Expand All @@ -49,25 +63,12 @@
/>
@endforeach
</x-twill::formConnectedFields>
</x-twill::fieldRows>
</a17-fieldset>
@endif

@if(config('twill.support_subdomain_admin_routing'))
<x-twill::fieldRows title="Subdomain access">
@foreach(config('twill.app_names') as $subdomain => $subdomainTitle)
<x-twill::checkbox
:name="'subdomain_access_' . $subdomain"
:label="$subdomainTitle"
/>
@endforeach
</x-twill::fieldRows>
@endif
@stop

@if(\A17\Twill\Facades\TwillPermissions::levelIs(\A17\Twill\Enums\PermissionLevel::LEVEL_ROLE_GROUP_ITEM))
@can('edit-user-groups')
@section('fieldsets')
@foreach($permissionModules as $moduleName => $moduleItems)
@if($permissionModulesLevelItem->isNotEmpty())
@can('edit-user-groups')
@foreach($permissionModulesLevelItem as $moduleName => $moduleItems)
<a17-fieldset title='{{ ucfirst($moduleName) . " Permissions"}}' id='{{ $moduleName }}'>
<x-twill::select-permissions
:items-in-selects-tables="$moduleItems"
Expand All @@ -76,6 +77,6 @@
/>
</a17-fieldset>
@endforeach
@stop
@endcan
@endif
@endcan
@endif
@stop
2 changes: 1 addition & 1 deletion views/layouts/form.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
@yield('fieldsets')
@endif

@if(\A17\Twill\Facades\TwillPermissions::levelIs(\A17\Twill\Enums\PermissionLevel::LEVEL_ROLE_GROUP_ITEM))
@if(\A17\Twill\Facades\TwillPermissions::levelForModuleIs(getModuleNameByModel($item), \A17\Twill\Enums\PermissionLevel::LEVEL_ROLE_GROUP_ITEM))
@if($showPermissionFieldset ?? null)
@can('manage-item', isset($item) ? $item : null)
<x-twill::formFieldset id="permissions"
Expand Down
2 changes: 1 addition & 1 deletion views/roles/form.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
'label' => 'Edit ' . $module_name
]
],
(\A17\Twill\Facades\TwillPermissions::levelIs(\A17\Twill\Enums\PermissionLevel::LEVEL_ROLE_GROUP_ITEM) ? [['value' => 'manage-module', 'label' => 'Manage ' . $module_name ]] : []))"
(\A17\Twill\Facades\TwillPermissions::levelForModuleIs($module_name, \A17\Twill\Enums\PermissionLevel::LEVEL_ROLE_GROUP_ITEM) ? [['value' => 'manage-module', 'label' => 'Manage ' . $module_name ]] : []))"
/>
@endforeach
</x-twill::formConnectedFields>
Expand Down
2 changes: 1 addition & 1 deletion views/users/form.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ function($locale) {


@section('fieldsets')
@if(\A17\Twill\Facades\TwillPermissions::levelIs(\A17\Twill\Enums\PermissionLevel::LEVEL_ROLE_GROUP_ITEM))
@if($permissionModules->isNotEmpty())
@can('edit-users')
@unless($item->isSuperAdmin() || $item->id === $currentUser->id)
<x-twill::formConnectedFields
Expand Down

0 comments on commit 3a7bd95

Please sign in to comment.