Added check for last admin on role change
Will show error message if last admin and admin role is removed. Closes #1124 Also cleaned up user controller a little.
This commit is contained in:
parent
456afdcd4c
commit
2317bf2350
6 changed files with 74 additions and 10 deletions
|
@ -3,6 +3,7 @@
|
||||||
use Activity;
|
use Activity;
|
||||||
use BookStack\Entities\Repos\EntityRepo;
|
use BookStack\Entities\Repos\EntityRepo;
|
||||||
use BookStack\Exceptions\NotFoundException;
|
use BookStack\Exceptions\NotFoundException;
|
||||||
|
use BookStack\Exceptions\UserUpdateException;
|
||||||
use BookStack\Uploads\Image;
|
use BookStack\Uploads\Image;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Images;
|
use Images;
|
||||||
|
@ -42,7 +43,7 @@ class UserRepo
|
||||||
*/
|
*/
|
||||||
public function getById($id)
|
public function getById($id)
|
||||||
{
|
{
|
||||||
return $this->user->findOrFail($id);
|
return $this->user->newQuery()->findOrFail($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,6 +136,40 @@ class UserRepo
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the assigned user roles via an array of role IDs.
|
||||||
|
* @param User $user
|
||||||
|
* @param array $roles
|
||||||
|
* @throws UserUpdateException
|
||||||
|
*/
|
||||||
|
public function setUserRoles(User $user, array $roles)
|
||||||
|
{
|
||||||
|
if ($this->demotingLastAdmin($user, $roles)) {
|
||||||
|
throw new UserUpdateException(trans('errors.role_cannot_remove_only_admin'), $user->getEditUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->roles()->sync($roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given user is the last admin and their new roles no longer
|
||||||
|
* contains the admin role.
|
||||||
|
* @param User $user
|
||||||
|
* @param array $newRoles
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function demotingLastAdmin(User $user, array $newRoles) : bool
|
||||||
|
{
|
||||||
|
if ($this->isOnlyAdmin($user)) {
|
||||||
|
$adminRole = $this->role->getSystemRole('admin');
|
||||||
|
if (!in_array(strval($adminRole->id), $newRoles)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new basic instance of user.
|
* Create a new basic instance of user.
|
||||||
* @param array $data
|
* @param array $data
|
||||||
|
@ -143,7 +178,6 @@ class UserRepo
|
||||||
*/
|
*/
|
||||||
public function create(array $data, $verifyEmail = false)
|
public function create(array $data, $verifyEmail = false)
|
||||||
{
|
{
|
||||||
|
|
||||||
return $this->user->forceCreate([
|
return $this->user->forceCreate([
|
||||||
'name' => $data['name'],
|
'name' => $data['name'],
|
||||||
'email' => $data['email'],
|
'email' => $data['email'],
|
||||||
|
|
|
@ -11,7 +11,7 @@ class NotifyException extends \Exception
|
||||||
* @param string $message
|
* @param string $message
|
||||||
* @param string $redirectLocation
|
* @param string $redirectLocation
|
||||||
*/
|
*/
|
||||||
public function __construct($message, $redirectLocation)
|
public function __construct(string $message, string $redirectLocation = "/")
|
||||||
{
|
{
|
||||||
$this->message = $message;
|
$this->message = $message;
|
||||||
$this->redirectLocation = $redirectLocation;
|
$this->redirectLocation = $redirectLocation;
|
||||||
|
|
3
app/Exceptions/UserUpdateException.php
Normal file
3
app/Exceptions/UserUpdateException.php
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<?php namespace BookStack\Exceptions;
|
||||||
|
|
||||||
|
class UserUpdateException extends NotifyException {}
|
|
@ -3,6 +3,7 @@
|
||||||
use BookStack\Auth\Access\SocialAuthService;
|
use BookStack\Auth\Access\SocialAuthService;
|
||||||
use BookStack\Auth\User;
|
use BookStack\Auth\User;
|
||||||
use BookStack\Auth\UserRepo;
|
use BookStack\Auth\UserRepo;
|
||||||
|
use BookStack\Exceptions\UserUpdateException;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ class UserController extends Controller
|
||||||
/**
|
/**
|
||||||
* UserController constructor.
|
* UserController constructor.
|
||||||
* @param User $user
|
* @param User $user
|
||||||
* @param \BookStack\Auth\UserRepo $userRepo
|
* @param UserRepo $userRepo
|
||||||
*/
|
*/
|
||||||
public function __construct(User $user, UserRepo $userRepo)
|
public function __construct(User $user, UserRepo $userRepo)
|
||||||
{
|
{
|
||||||
|
@ -59,6 +60,7 @@ class UserController extends Controller
|
||||||
* Store a newly created user in storage.
|
* Store a newly created user in storage.
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
|
* @throws UserUpdateException
|
||||||
*/
|
*/
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
|
@ -89,7 +91,7 @@ class UserController extends Controller
|
||||||
|
|
||||||
if ($request->filled('roles')) {
|
if ($request->filled('roles')) {
|
||||||
$roles = $request->get('roles');
|
$roles = $request->get('roles');
|
||||||
$user->roles()->sync($roles);
|
$this->userRepo->setUserRoles($user, $roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->userRepo->downloadAndAssignUserAvatar($user);
|
$this->userRepo->downloadAndAssignUserAvatar($user);
|
||||||
|
@ -122,8 +124,9 @@ class UserController extends Controller
|
||||||
/**
|
/**
|
||||||
* Update the specified user in storage.
|
* Update the specified user in storage.
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param int $id
|
* @param int $id
|
||||||
* @return Response
|
* @return Response
|
||||||
|
* @throws UserUpdateException
|
||||||
*/
|
*/
|
||||||
public function update(Request $request, $id)
|
public function update(Request $request, $id)
|
||||||
{
|
{
|
||||||
|
@ -140,13 +143,13 @@ class UserController extends Controller
|
||||||
'setting' => 'array'
|
'setting' => 'array'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $this->user->findOrFail($id);
|
$user = $this->userRepo->getById($id);
|
||||||
$user->fill($request->all());
|
$user->fill($request->all());
|
||||||
|
|
||||||
// Role updates
|
// Role updates
|
||||||
if (userCan('users-manage') && $request->filled('roles')) {
|
if (userCan('users-manage') && $request->filled('roles')) {
|
||||||
$roles = $request->get('roles');
|
$roles = $request->get('roles');
|
||||||
$user->roles()->sync($roles);
|
$this->userRepo->setUserRoles($user, $roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Password updates
|
// Password updates
|
||||||
|
@ -185,7 +188,7 @@ class UserController extends Controller
|
||||||
return $this->currentUser->id == $id;
|
return $this->currentUser->id == $id;
|
||||||
});
|
});
|
||||||
|
|
||||||
$user = $this->user->findOrFail($id);
|
$user = $this->userRepo->getById($id);
|
||||||
$this->setPageTitle(trans('settings.users_delete_named', ['userName' => $user->name]));
|
$this->setPageTitle(trans('settings.users_delete_named', ['userName' => $user->name]));
|
||||||
return view('users/delete', ['user' => $user]);
|
return view('users/delete', ['user' => $user]);
|
||||||
}
|
}
|
||||||
|
@ -194,6 +197,7 @@ class UserController extends Controller
|
||||||
* Remove the specified user from storage.
|
* Remove the specified user from storage.
|
||||||
* @param int $id
|
* @param int $id
|
||||||
* @return Response
|
* @return Response
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function destroy($id)
|
public function destroy($id)
|
||||||
{
|
{
|
||||||
|
@ -279,7 +283,7 @@ class UserController extends Controller
|
||||||
$viewType = 'list';
|
$viewType = 'list';
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->user->findOrFail($id);
|
$user = $this->userRepo->getById($id);
|
||||||
setting()->putUser($user, 'bookshelves_view_type', $viewType);
|
setting()->putUser($user, 'bookshelves_view_type', $viewType);
|
||||||
|
|
||||||
return redirect()->back(302, [], "/settings/users/$id");
|
return redirect()->back(302, [], "/settings/users/$id");
|
||||||
|
|
|
@ -64,6 +64,7 @@ return [
|
||||||
'role_cannot_be_edited' => 'This role cannot be edited',
|
'role_cannot_be_edited' => 'This role cannot be edited',
|
||||||
'role_system_cannot_be_deleted' => 'This role is a system role and cannot be deleted',
|
'role_system_cannot_be_deleted' => 'This role is a system role and cannot be deleted',
|
||||||
'role_registration_default_cannot_delete' => 'This role cannot be deleted while set as the default registration role',
|
'role_registration_default_cannot_delete' => 'This role cannot be deleted while set as the default registration role',
|
||||||
|
'role_cannot_remove_only_admin' => 'This user is the only user assigned to the administrator role. Assign the administrator role to another user before attempting to remove it here.',
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
'comment_list' => 'An error occurred while fetching the comments.',
|
'comment_list' => 'An error occurred while fetching the comments.',
|
||||||
|
|
|
@ -78,6 +78,28 @@ class RolesTest extends BrowserKitTest
|
||||||
->dontSee($testRoleUpdateName);
|
->dontSee($testRoleUpdateName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_admin_role_cannot_be_removed_if_last_admin()
|
||||||
|
{
|
||||||
|
$adminRole = Role::where('system_name', '=', 'admin')->first();
|
||||||
|
$adminUser = $this->getAdmin();
|
||||||
|
$adminRole->users()->where('id', '!=', $adminUser->id)->delete();
|
||||||
|
$this->assertEquals($adminRole->users()->count(), 1);
|
||||||
|
|
||||||
|
$viewerRole = $this->getViewer()->roles()->first();
|
||||||
|
|
||||||
|
$editUrl = '/settings/users/' . $adminUser->id;
|
||||||
|
$this->actingAs($adminUser)->put($editUrl, [
|
||||||
|
'name' => $adminUser->name,
|
||||||
|
'email' => $adminUser->email,
|
||||||
|
'roles' => [
|
||||||
|
'viewer' => strval($viewerRole->id),
|
||||||
|
]
|
||||||
|
])->followRedirects();
|
||||||
|
|
||||||
|
$this->seePageIs($editUrl);
|
||||||
|
$this->see('This user is the only user assigned to the administrator role');
|
||||||
|
}
|
||||||
|
|
||||||
public function test_manage_user_permission()
|
public function test_manage_user_permission()
|
||||||
{
|
{
|
||||||
$this->actingAs($this->user)->visit('/settings/users')
|
$this->actingAs($this->user)->visit('/settings/users')
|
||||||
|
|
Loading…
Reference in a new issue