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

Feature/superuser change role #956

Open
wants to merge 15 commits into
base: 9.next
Choose a base branch
from
5 changes: 4 additions & 1 deletion Docs/Documentation/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ NOTE: SOME keys were hidden in this doc page, please refer to `vendor/cakedc/use
// configure Remember Me component
'active' => true,
],
'Superuser' => ['allowedToChangePasswords' => false], // able to reset any users password
'Superuser' => [
'allowedToChangePasswords' => false,// able to reset any users password
'allowedToChangeRoles' => false // able to change user roles
],
],
//Default authentication/authorization setup
'Auth' => [
Expand Down
10 changes: 9 additions & 1 deletion config/users.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,15 @@
]
]
],
'Superuser' => ['allowedToChangePasswords' => false], // able to reset any users password
'Superuser' => [
'allowedToChangePasswords' => false,// able to reset any users password
'allowedToChangeRoles' => false // able to change user roles
],
'AvailableRoles'=>[
'superuser',
peter2796b marked this conversation as resolved.
Show resolved Hide resolved
'admin',
'user'
],
],
'OneTimePasswordAuthenticator' => [
'checker' => \CakeDC\Auth\Authentication\DefaultOneTimePasswordAuthenticationChecker::class,
Expand Down
137 changes: 137 additions & 0 deletions src/Controller/Traits/RoleManagementTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php
declare(strict_types=1);

/**
* Copyright 2010 - 2019, Cake Development Corporation (https://www.cakedc.com)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2010 - 2018, Cake Development Corporation (https://www.cakedc.com)
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/

namespace CakeDC\Users\Controller\Traits;

use Cake\Core\Configure;
use CakeDC\Users\Exception\ConfigNotSetException;
use CakeDC\Users\Exception\UserNotFoundException;
use CakeDC\Users\Plugin;
use Exception;

/**
* Covers the password management: reset, change
*
* @property \Cake\Http\ServerRequest $request
*/
trait RoleManagementTrait
{

/**
* Change Role
* Can be used by superadmin to change user roles
*
* @param int|string|null $id user_id, null for logged in user id
* @return mixed
*/
public function changeRole($id = null)
{

$identity = $this->getRequest()->getAttribute('identity');
$identity = $identity ?? [];
$userId = $identity['id'] ?? null;

if ($userId) {
if ($this->canUserEditRole($id, $identity)) {
// superuser update user roles
$user = $this->getUsersTable()->get($id);
$configRoles = $this->getConfigRoles();
$availableRoles = [];
peter2796b marked this conversation as resolved.
Show resolved Hide resolved
foreach ($configRoles as $role) {
$availableRoles[$role] = $role;
}
$redirect = ['action' => 'index'];
} else {
$this->Flash->error(
__d('cake_d_c/users', 'Changing role is not allowed')
);
return $this->redirect(Configure::read('Users.Profile.route'));

return;
}
} else {
$this->Flash->error(
__d('cake_d_c/users', 'Login to perform this action')
);
return $this->redirect(Configure::read('Users.Profile.route'));
}

if ($this->getRequest()->is(['post', 'put'])) {
try {
if (!in_array($this->getRequest()->getData()['role'], $availableRoles)) {
throw new Exception('Invalid role supplied');
}
$user = $this->getUsersTable()->patchEntity(
$user,
$this->getRequest()->getData(),
[
'accessibleFields' => [
'role' => true,
],
]
);


if ($user->getErrors()) {
$this->Flash->error(__d('cake_d_c/users', 'Role could not be changed'));
} else {
$user->is_superuser = $user->role === 'superuser';
peter2796b marked this conversation as resolved.
Show resolved Hide resolved
$result = $this->getUsersTable()->save($user);
if ($result) {
$event = $this->dispatchEvent(Plugin::EVENT_AFTER_CHANGE_ROLE, ['user' => $result]);
if (!empty($event) && is_array($event->getResult())) {
return $this->redirect($event->getResult());
}
$this->Flash->success(__d('cake_d_c/users', 'Role has been changed successfully'));

return $this->redirect($redirect);
} else {
$this->Flash->error(__d('cake_d_c/users', 'Role could not be changed'));
}
}
} catch (UserNotFoundException $exception) {
$this->Flash->error(__d('cake_d_c/users', 'User was not found'));
} catch (Exception $exception) {
$this->Flash->error(__d('cake_d_c/users', 'Role could not be changed'));
$this->log($exception->getMessage());
}
}
$this->set(compact('user', 'availableRoles'));
$this->set('_serialize', ['user']);
}

/**
* Checks and returns boolean value if the user can edit the role
* @param $id - id of profile/user who's role is being changed
* @param $identity
* @return bool
*/
protected function canUserEditRole($id, $identity)
{
return $id && $identity['is_superuser'] && Configure::read('Users.Superuser.allowedToChangeRoles');
}


/**
* @return array|false[]|mixed
*/
protected function getConfigRoles()
{
$configRoles = Configure::read('Users.AvailableRoles');
if (!$configRoles || (is_array($configRoles) && count($configRoles)) == 0) {
peter2796b marked this conversation as resolved.
Show resolved Hide resolved
throw new ConfigNotSetException('No Available role found in the users config. please set Users.AvailableRoles');
}
return $configRoles;

peter2796b marked this conversation as resolved.
Show resolved Hide resolved
}
}
2 changes: 2 additions & 0 deletions src/Controller/UsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use CakeDC\Users\Controller\Traits\ProfileTrait;
use CakeDC\Users\Controller\Traits\ReCaptchaTrait;
use CakeDC\Users\Controller\Traits\RegisterTrait;
use CakeDC\Users\Controller\Traits\RoleManagementTrait;
use CakeDC\Users\Controller\Traits\SimpleCrudTrait;
use CakeDC\Users\Controller\Traits\SocialTrait;
use CakeDC\Users\Controller\Traits\U2fTrait;
Expand All @@ -37,6 +38,7 @@ class UsersController extends AppController
use ProfileTrait;
use ReCaptchaTrait;
use RegisterTrait;
use RoleManagementTrait;
use SimpleCrudTrait;
use SocialTrait;
use U2fTrait;
Expand Down
31 changes: 31 additions & 0 deletions src/Exception/ConfigNotSetException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);

/**
* Copyright 2010 - 2019, Cake Development Corporation (https://www.cakedc.com)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2010 - 2018, Cake Development Corporation (https://www.cakedc.com)
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/

namespace CakeDC\Users\Exception;

use Cake\Core\Exception\Exception;

class ConfigNotSetException extends Exception
{
/**
* ConfigNotSetException constructor.
*
* @param array|string $message message
* @param int $code code
* @param null $previous previous
*/
public function __construct($message, $code = 500, $previous = null)
peter2796b marked this conversation as resolved.
Show resolved Hide resolved
{
parent::__construct($message, $code, $previous);
}
}
1 change: 1 addition & 0 deletions src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class Plugin extends BasePlugin implements AuthenticationServiceProviderInterfac
public const EVENT_BEFORE_REGISTER = 'Users.Global.beforeRegister';
public const EVENT_AFTER_REGISTER = 'Users.Global.afterRegister';
public const EVENT_AFTER_CHANGE_PASSWORD = 'Users.Global.afterResetPassword';
public const EVENT_AFTER_CHANGE_ROLE = 'Users.Global.afterRoleChanged';
public const EVENT_BEFORE_SOCIAL_LOGIN_USER_CREATE = 'Users.Global.beforeSocialLoginUserCreate';
public const EVENT_BEFORE_SOCIAL_LOGIN_REDIRECT = 'Users.Global.beforeSocialLoginRedirect';
public const EVENT_SOCIAL_LOGIN_EXISTING_ACCOUNT = 'Users.Global.socialLoginExistingAccount';
Expand Down
12 changes: 12 additions & 0 deletions templates/Users/change_role.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div class="users form">
<?= $this->Flash->render('auth') ?>
<?= $this->Form->create($user) ?>
<fieldset>
<legend><?= __d('cake_d_c/users', 'Select a new role') ?></legend>

<?= $this->Form->select('role', $availableRoles, ['empty' => false]); ?>

</fieldset>
<?= $this->Form->button(__d('cake_d_c/users', 'Submit')); ?>
<?= $this->Form->end() ?>
</div>
1 change: 1 addition & 0 deletions templates/Users/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<td class="actions">
<?= $this->Html->link(__d('cake_d_c/users', 'View'), ['action' => 'view', $user->id]) ?>
<?= $this->Html->link(__d('cake_d_c/users', 'Change password'), ['action' => 'changePassword', $user->id]) ?>
<?= $this->Html->link(__d('cake_d_c/users', 'Change role'), ['action' => 'changeRole', $user->id]) ?>
<?= $this->Html->link(__d('cake_d_c/users', 'Edit'), ['action' => 'edit', $user->id]) ?>
<?= $this->Form->postLink(__d('cake_d_c/users', 'Delete'), ['action' => 'delete', $user->id], ['confirm' => __d('cake_d_c/users', 'Are you sure you want to delete # {0}?', $user->id)]) ?>
</td>
Expand Down
Loading