Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow per-model permission level management #2656

Open
wants to merge 2 commits into
base: 3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading