]> BookStack Code Mirror - bookstack/blob - app/Http/Controllers/UserController.php
Revised webhooks list to new format
[bookstack] / app / Http / Controllers / UserController.php
1 <?php
2
3 namespace BookStack\Http\Controllers;
4
5 use BookStack\Auth\Access\SocialAuthService;
6 use BookStack\Auth\Queries\UsersAllPaginatedAndSorted;
7 use BookStack\Auth\Role;
8 use BookStack\Auth\User;
9 use BookStack\Auth\UserRepo;
10 use BookStack\Exceptions\ImageUploadException;
11 use BookStack\Exceptions\UserUpdateException;
12 use BookStack\Uploads\ImageRepo;
13 use Exception;
14 use Illuminate\Http\Request;
15 use Illuminate\Support\Facades\DB;
16 use Illuminate\Validation\Rules\Password;
17 use Illuminate\Validation\ValidationException;
18
19 class UserController extends Controller
20 {
21     protected UserRepo $userRepo;
22     protected ImageRepo $imageRepo;
23
24     /**
25      * UserController constructor.
26      */
27     public function __construct(UserRepo $userRepo, ImageRepo $imageRepo)
28     {
29         $this->userRepo = $userRepo;
30         $this->imageRepo = $imageRepo;
31     }
32
33     /**
34      * Display a listing of the users.
35      */
36     public function index(Request $request)
37     {
38         $this->checkPermission('users-manage');
39         $listDetails = [
40             'search' => $request->get('search', ''),
41             'sort'   => setting()->getForCurrentUser('users_sort', 'name'),
42             'order'  => setting()->getForCurrentUser('users_sort_order', 'asc'),
43         ];
44
45         $users = (new UsersAllPaginatedAndSorted())->run(20, $listDetails);
46
47         $this->setPageTitle(trans('settings.users'));
48         $users->appends(['search' => $listDetails['search']]);
49
50         return view('users.index', [
51             'users'       => $users,
52             'listDetails' => $listDetails,
53         ]);
54     }
55
56     /**
57      * Show the form for creating a new user.
58      */
59     public function create()
60     {
61         $this->checkPermission('users-manage');
62         $authMethod = config('auth.method');
63         $roles = Role::query()->orderBy('display_name', 'asc')->get();
64         $this->setPageTitle(trans('settings.users_add_new'));
65
66         return view('users.create', ['authMethod' => $authMethod, 'roles' => $roles]);
67     }
68
69     /**
70      * Store a new user in storage.
71      *
72      * @throws ValidationException
73      */
74     public function store(Request $request)
75     {
76         $this->checkPermission('users-manage');
77
78         $authMethod = config('auth.method');
79         $sendInvite = ($request->get('send_invite', 'false') === 'true');
80         $externalAuth = $authMethod === 'ldap' || $authMethod === 'saml2' || $authMethod === 'oidc';
81         $passwordRequired = ($authMethod === 'standard' && !$sendInvite);
82
83         $validationRules = [
84             'name'             => ['required', 'max:100'],
85             'email'            => ['required', 'email', 'unique:users,email'],
86             'language'         => ['string', 'max:15', 'alpha_dash'],
87             'roles'            => ['array'],
88             'roles.*'          => ['integer'],
89             'password'         => $passwordRequired ? ['required', Password::default()] : null,
90             'password-confirm' => $passwordRequired ? ['required', 'same:password'] : null,
91             'external_auth_id' => $externalAuth ? ['required'] : null,
92         ];
93
94         $validated = $this->validate($request, array_filter($validationRules));
95
96         DB::transaction(function () use ($validated, $sendInvite) {
97             $this->userRepo->create($validated, $sendInvite);
98         });
99
100         return redirect('/settings/users');
101     }
102
103     /**
104      * Show the form for editing the specified user.
105      */
106     public function edit(int $id, SocialAuthService $socialAuthService)
107     {
108         $this->checkPermissionOrCurrentUser('users-manage', $id);
109
110         /** @var User $user */
111         $user = User::query()->with(['apiTokens', 'mfaValues'])->findOrFail($id);
112
113         $authMethod = ($user->system_name) ? 'system' : config('auth.method');
114
115         $activeSocialDrivers = $socialAuthService->getActiveDrivers();
116         $mfaMethods = $user->mfaValues->groupBy('method');
117         $this->setPageTitle(trans('settings.user_profile'));
118         $roles = Role::query()->orderBy('display_name', 'asc')->get();
119
120         return view('users.edit', [
121             'user'                => $user,
122             'activeSocialDrivers' => $activeSocialDrivers,
123             'mfaMethods'          => $mfaMethods,
124             'authMethod'          => $authMethod,
125             'roles'               => $roles,
126         ]);
127     }
128
129     /**
130      * Update the specified user in storage.
131      *
132      * @throws UserUpdateException
133      * @throws ImageUploadException
134      * @throws ValidationException
135      */
136     public function update(Request $request, int $id)
137     {
138         $this->preventAccessInDemoMode();
139         $this->checkPermissionOrCurrentUser('users-manage', $id);
140
141         $validated = $this->validate($request, [
142             'name'             => ['min:2', 'max:100'],
143             'email'            => ['min:2', 'email', 'unique:users,email,' . $id],
144             'password'         => ['required_with:password_confirm', Password::default()],
145             'password-confirm' => ['same:password', 'required_with:password'],
146             'language'         => ['string', 'max:15', 'alpha_dash'],
147             'roles'            => ['array'],
148             'roles.*'          => ['integer'],
149             'external_auth_id' => ['string'],
150             'profile_image'    => array_merge(['nullable'], $this->getImageValidationRules()),
151         ]);
152
153         $user = $this->userRepo->getById($id);
154         $this->userRepo->update($user, $validated, userCan('users-manage'));
155
156         // Save profile image if in request
157         if ($request->hasFile('profile_image')) {
158             $imageUpload = $request->file('profile_image');
159             $this->imageRepo->destroyImage($user->avatar);
160             $image = $this->imageRepo->saveNew($imageUpload, 'user', $user->id);
161             $user->image_id = $image->id;
162             $user->save();
163         }
164
165         // Delete the profile image if reset option is in request
166         if ($request->has('profile_image_reset')) {
167             $this->imageRepo->destroyImage($user->avatar);
168         }
169
170         $redirectUrl = userCan('users-manage') ? '/settings/users' : "/settings/users/{$user->id}";
171
172         return redirect($redirectUrl);
173     }
174
175     /**
176      * Show the user delete page.
177      */
178     public function delete(int $id)
179     {
180         $this->checkPermissionOrCurrentUser('users-manage', $id);
181
182         $user = $this->userRepo->getById($id);
183         $this->setPageTitle(trans('settings.users_delete_named', ['userName' => $user->name]));
184
185         return view('users.delete', ['user' => $user]);
186     }
187
188     /**
189      * Remove the specified user from storage.
190      *
191      * @throws Exception
192      */
193     public function destroy(Request $request, int $id)
194     {
195         $this->preventAccessInDemoMode();
196         $this->checkPermissionOrCurrentUser('users-manage', $id);
197
198         $user = $this->userRepo->getById($id);
199         $newOwnerId = $request->get('new_owner_id', null);
200
201         $this->userRepo->destroy($user, $newOwnerId);
202
203         return redirect('/settings/users');
204     }
205
206     /**
207      * Update the user's preferred book-list display setting.
208      */
209     public function switchBooksView(Request $request, int $id)
210     {
211         return $this->switchViewType($id, $request, 'books');
212     }
213
214     /**
215      * Update the user's preferred shelf-list display setting.
216      */
217     public function switchShelvesView(Request $request, int $id)
218     {
219         return $this->switchViewType($id, $request, 'bookshelves');
220     }
221
222     /**
223      * Update the user's preferred shelf-view book list display setting.
224      */
225     public function switchShelfView(Request $request, int $id)
226     {
227         return $this->switchViewType($id, $request, 'bookshelf');
228     }
229
230     /**
231      * For a type of list, switch with stored view type for a user.
232      */
233     protected function switchViewType(int $userId, Request $request, string $listName)
234     {
235         $this->checkPermissionOrCurrentUser('users-manage', $userId);
236
237         $viewType = $request->get('view_type');
238         if (!in_array($viewType, ['grid', 'list'])) {
239             $viewType = 'list';
240         }
241
242         $user = $this->userRepo->getById($userId);
243         $key = $listName . '_view_type';
244         setting()->putUser($user, $key, $viewType);
245
246         return redirect()->back(302, [], "/settings/users/$userId");
247     }
248
249     /**
250      * Change the stored sort type for a particular view.
251      */
252     public function changeSort(Request $request, string $id, string $type)
253     {
254         $validSortTypes = ['books', 'bookshelves', 'shelf_books', 'users', 'roles', 'webhooks'];
255         if (!in_array($type, $validSortTypes)) {
256             return redirect()->back(500);
257         }
258
259         return $this->changeListSort($id, $request, $type);
260     }
261
262     /**
263      * Toggle dark mode for the current user.
264      */
265     public function toggleDarkMode()
266     {
267         $enabled = setting()->getForCurrentUser('dark-mode-enabled', false);
268         setting()->putUser(user(), 'dark-mode-enabled', $enabled ? 'false' : 'true');
269
270         return redirect()->back();
271     }
272
273     /**
274      * Update the stored section expansion preference for the given user.
275      */
276     public function updateExpansionPreference(Request $request, string $id, string $key)
277     {
278         $this->checkPermissionOrCurrentUser('users-manage', $id);
279         $keyWhitelist = ['home-details'];
280         if (!in_array($key, $keyWhitelist)) {
281             return response('Invalid key', 500);
282         }
283
284         $newState = $request->get('expand', 'false');
285
286         $user = $this->userRepo->getById($id);
287         setting()->putUser($user, 'section_expansion#' . $key, $newState);
288
289         return response('', 204);
290     }
291
292     public function updateCodeLanguageFavourite(Request $request)
293     {
294         $validated = $this->validate($request, [
295             'language' => ['required', 'string', 'max:20'],
296             'active'   => ['required', 'bool'],
297         ]);
298
299         $currentFavoritesStr = setting()->getForCurrentUser('code-language-favourites', '');
300         $currentFavorites = array_filter(explode(',', $currentFavoritesStr));
301
302         $isFav = in_array($validated['language'], $currentFavorites);
303         if (!$isFav && $validated['active']) {
304             $currentFavorites[] = $validated['language'];
305         } elseif ($isFav && !$validated['active']) {
306             $index = array_search($validated['language'], $currentFavorites);
307             array_splice($currentFavorites, $index, 1);
308         }
309
310         setting()->putUser(user(), 'code-language-favourites', implode(',', $currentFavorites));
311     }
312
313     /**
314      * Changed the stored preference for a list sort order.
315      */
316     protected function changeListSort(int $userId, Request $request, string $listName)
317     {
318         $this->checkPermissionOrCurrentUser('users-manage', $userId);
319
320         $sort = $request->get('sort');
321         // TODO - Need to find a better way to validate sort options
322         //   Probably better to do a simple validation here then validate at usage.
323         $validSorts = [
324             'name', 'created_at', 'updated_at', 'default', 'email', 'last_activity_at', 'display_name',
325             'users_count', 'permissions_count', 'endpoint', 'active',
326         ];
327         if (!in_array($sort, $validSorts)) {
328             $sort = 'name';
329         }
330
331         $order = $request->get('order');
332         if (!in_array($order, ['asc', 'desc'])) {
333             $order = 'asc';
334         }
335
336         $user = $this->userRepo->getById($userId);
337         $sortKey = $listName . '_sort';
338         $orderKey = $listName . '_sort_order';
339         setting()->putUser($user, $sortKey, $sort);
340         setting()->putUser($user, $orderKey, $order);
341
342         return redirect()->back(302, [], "/settings/users/$userId");
343     }
344 }