Skip to content

Commit

Permalink
New feature #19201: Add column "active" to users, to be able to activ…
Browse files Browse the repository at this point in the history
…ate or deactivate users (#3550)

* Feature #CT-1343: Limit creation of new users

* Feature #CT-1343: Limit creation of new users

* Feature #CT-1343: Limit creation of new users

* Feature #CT-1343: Limit creation of new users

* Feature #CT-1343: Limit creation of new users

* Feature #CT-1343: Handle user status permission

* Feature #CT-1343: Handle user status permission

* Feature #CT-1343: Update the users count in navigation

* Feature #CT-1343: Update the users count in navigation

* Feature #CT-1343: DB update

* Feature #CT-1343: DB update

* Feature #CT-1343: remove user limit checks

* Feature #CT-1343: remove user limit checks

* Feature #CT-1343: Add status column

* Feature #CR-1343: remove status from insertUser

* Feature #CR-1343: remove user limit checks

* Feature #CR-1343: rename Update_616 to Update_617

* Feature #CR-1343: rename Update_616 to Update_618

* Feature #CT-1343: Limit creation of new users

* Feature #Cr-1343: Add column "active" to users, to be able to activate or deactivate users

* Feature #CR-1343: Add status to Auth plugin

* Feature #CR-1343: Add beforeFooterRender

* Feature #CR-1343: rename status to user_status

* Only merge arrays if data is an array

* Show more precise error message in user management if present

* Feature #CR-1343: rename status to user_status

* Feature #CR-1343: remove beforeFooterRender

* Block any non-active user from logging in

* Don't check user_status in authdb plugin

---------

Co-authored-by: Olle Haerstedt <olle.haerstedt@limesurvey.org>
  • Loading branch information
mohabmes and olleharstedt committed Dec 8, 2023
1 parent db2da46 commit bb14772
Show file tree
Hide file tree
Showing 20 changed files with 380 additions and 30 deletions.
2 changes: 1 addition & 1 deletion application/config/version.php
Expand Up @@ -12,7 +12,7 @@
*/

$config['versionnumber'] = '6.4.0-dev';
$config['dbversionnumber'] = 618;
$config['dbversionnumber'] = 619;
$config['buildnumber'] = '';
$config['updatable'] = true;
$config['templateapiversion'] = 3;
Expand Down
144 changes: 144 additions & 0 deletions application/controllers/UserManagementController.php
Expand Up @@ -420,6 +420,150 @@ public function actionDeleteUser()
]);
}

/**
* Show user activation confirmation
*
* @return void|string
* @throws CException
*/
public function actionActivationConfirm()
{
if (!Permission::model()->hasGlobalPermission('users', 'update')) {
throw new CHttpException(403, gT("You do not have permission to access this page."));
}
$userId = Yii::app()->request->getParam('userid');
$action = Yii::app()->request->getParam('action');

$userId = sanitize_int($userId);

$aData['userId'] = $userId;
$aData['action'] = $action;

return $this->renderPartial('partial/confirmuseractivation', $aData);
}


/**
* Stores the status settings
*
* @return void|string
* @throws CException
*/
public function actionUserActivateDeactivate()
{
if (!Permission::model()->hasGlobalPermission('users', 'delete')) {
throw new CHttpException(403, gT("You do not have permission to access this page."));
}
$userId = sanitize_int(Yii::app()->request->getParam('userid'));
$action = Yii::app()->request->getParam('action');
$oUser = User::model()->findByPk($userId);

if ($oUser == null) {
throw new CHttpException(404, gT("Invalid user id"));
} else {
if ($oUser->setActivationStatus($action)) {
return App()->getController()->renderPartial('/admin/super/_renderJson', [
'data' => [
'success' => true,
'message' => gT('Status successfully updated')
]
]);
};
}
return App()->getController()->renderPartial('/admin/super/_renderJson', [
'data' => [
'success' => false
]
]);
}

/**
* Stores the status settings run via MassEdit
*
* @throws CException
*/
public function actionBatchStatus()
{
if (!Permission::model()->hasGlobalPermission('users', 'update')) {
throw new CHttpException(403, gT("You do not have permission to access this page."));
}

$userIds = json_decode(Yii::app()->request->getPost('sItems', "[]"));
$operation = Yii::app()->request->getPost('status_selector', '');
$results = $this->userActivation($userIds, $operation);

$error = null;
foreach ($results as $result) {
if (isset($result['error']) && $result['error'] == 'Reached the limit') {
$error = true;
}
}

$tableLabels = array(gT('User ID'), gT('Username'), gT('Status'));

$data = array(
'aResults' => $results,
'successLabel' => gT('Saved successfully'),
'tableLabels' => $tableLabels,
);

if ($error) {
$data['additionalMessage'] = $this->renderPartial('/userManagement/partial/planupgradebutton');
}

Yii::app()->getController()->renderPartial('ext.admin.survey.ListSurveysWidget.views.massive_actions._action_results', $data);
}


/**
* Activate / deactivate user
*
* @param array $userIds
* @param string $operation activate or deactivate
* @return array
*/
public function userActivation($userIds, $operation)
{
$results = [];
foreach ($userIds as $iUserId) {
$oUser = User::model()->findByPk($iUserId);
if ($oUser == null) {
throw new CHttpException(404, gT("Invalid user id"));
} else {
$results[$iUserId]['title'] = $oUser->users_name;
if (!$this->isAllowedToEdit($oUser)) {
$results[$iUserId]['error'] = gT('Unauthorized');
$results[$iUserId]['result'] = false;
continue;
}
$results[$iUserId]['result'] = $oUser->setActivationStatus($operation);
if (!$results[$iUserId]['result']) {
$results[$iUserId]['error'] = gT('Error');
}
}
}
return $results;
}

/**
* Check if the current user allowed to update $user
*
* @return boolean
*/
private function isAllowedToEdit($user)
{
$permission_superadmin_read = Permission::model()->hasGlobalPermission('superadmin', 'read');
$permission_users_update = Permission::model()->hasGlobalPermission('users', 'update');
$ownedOrCreated = $user->parent_id == App()->session['loginID']; // User is owned or created by you

return ( $permission_superadmin_read && !(Permission::isForcedSuperAdmin($user->uid) || $user->uid == App()->user->getId()))
|| (!$permission_superadmin_read && ($user->uid != App()->session['loginID'] //Can't change your own permissions
&& ( $permission_users_update && $ownedOrCreated)
&& !Permission::isForcedSuperAdmin($user->uid)
)
);
}

/**
* Show user delete confirmation
*
Expand Down
5 changes: 5 additions & 0 deletions application/core/LSUserIdentity.php
Expand Up @@ -67,6 +67,11 @@ public function authenticate()
if (is_null($this->plugin)) {
$result->setError(self::ERROR_UNKNOWN_HANDLER);
} else {
// Never allow login for non-active users.
$user = User::model()->findByAttributes(array('users_name' => $this->username));
if ($user && (int) $user->user_status === 0) {
throw new CHttpException(403, gT("You do not have permission to access this page."));
}
// Delegate actual authentication to plugin
$authEvent = new PluginEvent('newUserSession', $this); // TODO: rename the plugin function authenticate()
$authEvent->set('identity', $this);
Expand Down
9 changes: 8 additions & 1 deletion application/core/plugins/AuthLDAP/AuthLDAP.php
Expand Up @@ -293,7 +293,14 @@ private function _createNewUser($oEvent, $new_user, $password = null)
} else {
$parentID = 1;
}
$iNewUID = User::insertUser($new_user, $new_pass, $new_full_name, $parentID, $new_email);
$status = true;
$preCollectedUserArray = $oEvent->get('preCollectedUserArray', []);
if (!empty($preCollectedUserArray)) {
if (!empty($preCollectedUserArray['status'])) {
$status = $preCollectedUserArray['status'];
}
}
$iNewUID = User::insertUser($new_user, $new_pass, $new_full_name, $parentID, $new_email, null, $status);
if (!$iNewUID) {
$oEvent->set('errorCode', self::ERROR_ALREADY_EXISTING_USER);
$oEvent->set('errorMessageTitle', '');
Expand Down
11 changes: 9 additions & 2 deletions application/core/plugins/Authdb/Authdb.php
Expand Up @@ -44,6 +44,7 @@ public function createNewUser()
$oEvent = $this->getEvent();
$preCollectedUserArray = $oEvent->get('preCollectedUserArray', []);
$expires = null;
$status = true;

if (empty($preCollectedUserArray)) {
// Do nothing if the user to be added is not DB type
Expand All @@ -54,6 +55,9 @@ public function createNewUser()
$new_email = flattenText(Yii::app()->request->getPost('new_email'), false, true);
$new_full_name = flattenText(Yii::app()->request->getPost('new_full_name'), false, true);
$presetPassword = null;
if (Yii::app()->request->getPost('status')) {
$status = flattenText(Yii::app()->request->getPost('status'), false, true);
}
if (Yii::app()->request->getPost('expires')) {
$expires = flattenText(Yii::app()->request->getPost('expires'), false, true);
}
Expand All @@ -62,11 +66,14 @@ public function createNewUser()
$new_email = flattenText($preCollectedUserArray['email']);
$new_full_name = flattenText($preCollectedUserArray['full_name']);
$presetPassword = flattenText($preCollectedUserArray['password']);
if (!empty($preCollectedUserArray['status'])) {
$status = $preCollectedUserArray['status'];
}
if (!empty($preCollectedUserArray['expires'])) {
$expires = $preCollectedUserArray['expires'];
}
}

if (!LimeMailer::validateAddress($new_email)) {
$oEvent->set('errorCode', self::ERROR_INVALID_EMAIL);
$oEvent->set('errorMessageTitle', gT("Failed to add user"));
Expand All @@ -75,7 +82,7 @@ public function createNewUser()
}

$new_pass = $presetPassword ?? createPassword();
$iNewUID = User::insertUser($new_user, $new_pass, $new_full_name, Yii::app()->session['loginID'], $new_email, $expires);
$iNewUID = User::insertUser($new_user, $new_pass, $new_full_name, Yii::app()->session['loginID'], $new_email, $expires, $status);
if (!$iNewUID) {
$oEvent->set('errorCode', self::ERROR_ALREADY_EXISTING_USER);
$oEvent->set('errorMessageTitle', '');
Expand Down
Expand Up @@ -10,6 +10,9 @@
$tableLabels = array(gT('ID'), gT('Title'), gT('Status'));
}
?>
<?php if(isset($additionalMessage)):?>
<?php echo $additionalMessage?>
<?php endif;?>
<table class="table table-striped">
<thead>
<?php foreach($tableLabels as $label):?>
Expand Down
16 changes: 16 additions & 0 deletions application/helpers/update/updates/Update_619.php
@@ -0,0 +1,16 @@
<?php

namespace LimeSurvey\Helpers\Update;

use LimeSurvey\Helpers\Update\DatabaseUpdateBase;

class Update_619 extends DatabaseUpdateBase
{
/**
* @inheritDoc
*/
public function up()
{
$this->db->createCommand()->addColumn('{{users}}', 'user_status', 'BOOLEAN DEFAULT TRUE');
}
}
65 changes: 61 additions & 4 deletions application/models/User.php
Expand Up @@ -43,6 +43,7 @@
* @property string $last_login
* @property Permissiontemplates[] $roles
* @property UserGroup[] $groups
* @property bool $user_status User's account status (true: activated | false: deactivated)
*/
class User extends LSActiveRecord
{
Expand All @@ -59,6 +60,7 @@ class User extends LSActiveRecord
* @var string $lang Default value for user language
*/
public $lang = 'auto';

public $searched_value;

/**
Expand Down Expand Up @@ -159,6 +161,7 @@ public function attributeLabels()
'modified' => gT('Modified at'),
'last_login' => gT('Last recorded login'),
'expires' => gT("Expiry date/time:"),
'user_status' => gT("Status"),
];
}

Expand Down Expand Up @@ -230,9 +233,10 @@ public function getFormattedDateCreated()
* @param int $parent_user
* @param string $new_email
* @param string|null $expires
* @param boolean $status
* @return integer|boolean User ID if success
*/
public static function insertUser($new_user, $new_pass, $new_full_name, $parent_user, $new_email, $expires = null)
public static function insertUser($new_user, $new_pass, $new_full_name, $parent_user, $new_email, $expires = null, $status = true)
{
$oUser = new self();
$oUser->users_name = $new_user;
Expand All @@ -244,6 +248,7 @@ public static function insertUser($new_user, $new_pass, $new_full_name, $parent_
$oUser->created = date('Y-m-d H:i:s');
$oUser->modified = date('Y-m-d H:i:s');
$oUser->expires = $expires;
$oUser->user_status = $status;
if ($oUser->save()) {
return $oUser->uid;
} else {
Expand Down Expand Up @@ -504,7 +509,6 @@ public function getManagementButtons()
$permission_users_update = Permission::model()->hasGlobalPermission('users', 'update');
$permission_users_delete = Permission::model()->hasGlobalPermission('users', 'delete');
$userManager = new UserManager(App()->user, $this);

// User is owned or created by you
$ownedOrCreated = $this->parent_id == App()->session['loginID'];

Expand All @@ -516,7 +520,6 @@ public function getManagementButtons()
$changeOwnershipUrl = App()->getController()->createUrl('userManagement/takeOwnership');
$deleteUrl = App()->getController()->createUrl('userManagement/deleteConfirm', ['userid' => $this->uid, 'user' => $this->full_name]);


$dropdownItems = [];
$dropdownItems[] = [
'title' => gT('User details'),
Expand All @@ -540,6 +543,37 @@ public function getManagementButtons()
)
)
];

$permission = ( $permission_superadmin_read && !(Permission::isForcedSuperAdmin($this->uid) || $this->uid == App()->user->getId()))
|| (!$permission_superadmin_read && ($this->uid != App()->session['loginID'] //Can't change your own permissions
&& ( $permission_users_update && $ownedOrCreated)
&& !Permission::isForcedSuperAdmin($this->uid)
)
);

if ($this->user_status) {
$activateUrl = App()->getController()->createUrl('userManagement/activationConfirm', ['userid' => $this->uid, 'action' => 'deactivate']);
$dropdownItems[] = [
'title' => gT('Deactivate'),
'iconClass' => "ri-user-unfollow-fill text-danger",
'linkClass' => $permission ? "UserManagement--action--openmodal UserManagement--action--status" : '',
'linkAttributes' => [
'data-href' => $permission ? $activateUrl : '#',
],
'enabledCondition' => $permission
];
} else {
$activateUrl = App()->getController()->createUrl('userManagement/activationConfirm', ['userid' => $this->uid, 'action' => 'activate']);
$dropdownItems[] = [
'title' => gT('Activate'),
'iconClass' => "ri-user-follow-fill",
'linkClass' => $permission ? "UserManagement--action--openmodal UserManagement--action--status" : '',
'linkAttributes' => [
'data-href' => $permission ? $activateUrl : '#',
],
'enabledCondition' => $permission
];
}
$dropdownItems[] = [
'title' => gT('Edit permissions'),
'iconClass' => "ri-lock-fill",
Expand Down Expand Up @@ -731,6 +765,12 @@ public function getManagementColums()
"name" => "parentUserName",
"header" => gT("Created by"),
],
[
"name" => "user_status",
"header" => gT("Status"),
'headerHtmlOptions' => ['class' => 'hidden'],
'htmlOptions' => ['class' => 'hidden activation']
],
];

// NOTE: Super Administrators with just the "read" flag also have these flags
Expand Down Expand Up @@ -866,7 +906,7 @@ public function search()

$getUser = Yii::app()->request->getParam('User');
if (!empty($getUser['parentUserName'])) {
$getParentName = $getUser['parentUserName'];
$getParentName = $getUser['parentUserName'];
$criteria->join = "LEFT JOIN {{users}} u ON t.parent_id = u.uid";
$criteria->compare('u.users_name', $getParentName, true, 'OR');
}
Expand Down Expand Up @@ -1040,4 +1080,21 @@ public function canEdit($managerId = null)
return Permission::model()->hasGlobalPermission('users', 'update', $managerId)
&& $this->parent_id == $managerId;
}

/**
* Set user activation status
*
* @param string $status
* @return bool
*/
public function setActivationStatus($status = 'activate')
{
if ($status == 'activate') {
$this->user_status = 1;
} else {
$this->user_status = 0;
}

return $this->save();
}
}

0 comments on commit bb14772

Please sign in to comment.