]> BookStack Code Mirror - bookstack/blob - app/Entities/Tools/PermissionsUpdater.php
Permissions: Cleanup after review of enum implementation PR
[bookstack] / app / Entities / Tools / PermissionsUpdater.php
1 <?php
2
3 namespace BookStack\Entities\Tools;
4
5 use BookStack\Activity\ActivityType;
6 use BookStack\Entities\Models\Book;
7 use BookStack\Entities\Models\Bookshelf;
8 use BookStack\Entities\Models\Entity;
9 use BookStack\Facades\Activity;
10 use BookStack\Permissions\Models\EntityPermission;
11 use BookStack\Permissions\Permission;
12 use BookStack\Users\Models\Role;
13 use BookStack\Users\Models\User;
14 use Illuminate\Http\Request;
15
16 class PermissionsUpdater
17 {
18     /**
19      * Update an entities permissions from a permission form submit request.
20      */
21     public function updateFromPermissionsForm(Entity $entity, Request $request): void
22     {
23         $permissions = $request->get('permissions', null);
24         $ownerId = $request->get('owned_by', null);
25
26         $entity->permissions()->delete();
27
28         if (!is_null($permissions)) {
29             $entityPermissionData = $this->formatPermissionsFromRequestToEntityPermissions($permissions);
30             $entity->permissions()->createMany($entityPermissionData);
31         }
32
33         if (!is_null($ownerId)) {
34             $this->updateOwnerFromId($entity, intval($ownerId));
35         }
36
37         $entity->save();
38         $entity->rebuildPermissions();
39
40         Activity::add(ActivityType::PERMISSIONS_UPDATE, $entity);
41     }
42
43     /**
44      * Update permissions from API request data.
45      */
46     public function updateFromApiRequestData(Entity $entity, array $data): void
47     {
48         if (isset($data['role_permissions'])) {
49             $entity->permissions()->where('role_id', '!=', 0)->delete();
50             $rolePermissionData = $this->formatPermissionsFromApiRequestToEntityPermissions($data['role_permissions'] ?? [], false);
51             $entity->permissions()->createMany($rolePermissionData);
52         }
53
54         if (array_key_exists('fallback_permissions', $data)) {
55             $entity->permissions()->where('role_id', '=', 0)->delete();
56         }
57
58         if (isset($data['fallback_permissions']['inheriting']) && $data['fallback_permissions']['inheriting'] !== true) {
59             $fallbackData = $data['fallback_permissions'];
60             $fallbackData['role_id'] = 0;
61             $rolePermissionData = $this->formatPermissionsFromApiRequestToEntityPermissions([$fallbackData], true);
62             $entity->permissions()->createMany($rolePermissionData);
63         }
64
65         if (isset($data['owner_id'])) {
66             $this->updateOwnerFromId($entity, intval($data['owner_id']));
67         }
68
69         $entity->save();
70         $entity->rebuildPermissions();
71
72         Activity::add(ActivityType::PERMISSIONS_UPDATE, $entity);
73     }
74
75     /**
76      * Update the owner of the given entity.
77      * Checks the user exists in the system first.
78      * Does not save the model, just updates it.
79      */
80     protected function updateOwnerFromId(Entity $entity, int $newOwnerId): void
81     {
82         $newOwner = User::query()->find($newOwnerId);
83         if (!is_null($newOwner)) {
84             $entity->owned_by = $newOwner->id;
85         }
86     }
87
88     /**
89      * Format permissions provided from a permission form to be EntityPermission data.
90      */
91     protected function formatPermissionsFromRequestToEntityPermissions(array $permissions): array
92     {
93         $formatted = [];
94
95         foreach ($permissions as $roleId => $info) {
96             $entityPermissionData = ['role_id' => $roleId];
97             foreach (Permission::genericForEntity() as $permission) {
98                 $permName = $permission->value;
99                 $entityPermissionData[$permName] = (($info[$permName] ?? false) === "true");
100             }
101             $formatted[] = $entityPermissionData;
102         }
103
104         return $this->filterEntityPermissionDataUponRole($formatted, true);
105     }
106
107     protected function formatPermissionsFromApiRequestToEntityPermissions(array $permissions, bool $allowFallback): array
108     {
109         $formatted = [];
110
111         foreach ($permissions as $requestPermissionData) {
112             $entityPermissionData = ['role_id' => $requestPermissionData['role_id']];
113             foreach (Permission::genericForEntity() as $permission) {
114                 $permName = $permission->value;
115                 $entityPermissionData[$permName] = boolval($requestPermissionData[$permName] ?? false);
116             }
117             $formatted[] = $entityPermissionData;
118         }
119
120         return $this->filterEntityPermissionDataUponRole($formatted, $allowFallback);
121     }
122
123     protected function filterEntityPermissionDataUponRole(array $entityPermissionData, bool $allowFallback): array
124     {
125         $roleIds = [];
126         foreach ($entityPermissionData as $permissionEntry) {
127             $roleIds[] = intval($permissionEntry['role_id']);
128         }
129
130         $actualRoleIds = array_unique(array_values(array_filter($roleIds)));
131         $rolesById = Role::query()->whereIn('id', $actualRoleIds)->get('id')->keyBy('id');
132
133         return array_values(array_filter($entityPermissionData, function ($data) use ($rolesById, $allowFallback) {
134             if (intval($data['role_id']) === 0) {
135                 return $allowFallback;
136             }
137
138             return $rolesById->has($data['role_id']);
139         }));
140     }
141
142     /**
143      * Copy down the permissions of the given shelf to all child books.
144      */
145     public function updateBookPermissionsFromShelf(Bookshelf $shelf, $checkUserPermissions = true): int
146     {
147         $shelfPermissions = $shelf->permissions()->get(['role_id', 'view', 'create', 'update', 'delete'])->toArray();
148         $shelfBooks = $shelf->books()->get(['id', 'owned_by']);
149         $updatedBookCount = 0;
150
151         /** @var Book $book */
152         foreach ($shelfBooks as $book) {
153             if ($checkUserPermissions && !userCan(Permission::RestrictionsManage, $book)) {
154                 continue;
155             }
156             $book->permissions()->delete();
157             $book->permissions()->createMany($shelfPermissions);
158             $book->rebuildPermissions();
159             $updatedBookCount++;
160         }
161
162         return $updatedBookCount;
163     }
164 }