diff --git a/core/img/actions/user-plus.svg b/core/img/actions/user-plus.svg new file mode 100644 index 0000000000000..16c59261a7334 --- /dev/null +++ b/core/img/actions/user-plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/img/actions/user-times.svg b/core/img/actions/user-times.svg new file mode 100644 index 0000000000000..f853784efc284 --- /dev/null +++ b/core/img/actions/user-times.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index 4a87dc7161c32..0477f23e552e0 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -408,6 +408,28 @@ public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = fa } } + /** + * returns how many users have logged in once + * + * @return int + * @since 12.0.0 + */ + public function countDisabledUsers() { + $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $queryBuilder->select($queryBuilder->createFunction('COUNT(*)')) + ->from('preferences') + ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core'))) + ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled'))) + ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'))); + + $query = $queryBuilder->execute(); + + $result = (int)$query->fetchColumn(); + $query->closeCursor(); + + return $result; + } + /** * returns how many users have logged in once * diff --git a/lib/public/IUserManager.php b/lib/public/IUserManager.php index 1ec392dfd8264..6c6724487de3e 100644 --- a/lib/public/IUserManager.php +++ b/lib/public/IUserManager.php @@ -154,6 +154,14 @@ public function countUsers(); */ public function callForAllUsers(\Closure $callback, $search = ''); + /** + * returns how many users have logged in once + * + * @return int + * @since 11.0.0 + */ + public function countDisabledUsers(); + /** * returns how many users have logged in once * diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index 7f6602a510c11..4fed265594044 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -225,6 +225,7 @@ private function formatUserForIndex(IUser $user, array $userGroups = null) { 'email' => $displayName, 'isRestoreDisabled' => !$restorePossible, 'isAvatarAvailable' => $avatarAvailable, + 'isEnabled' => $user->isEnabled(), ]; } @@ -253,11 +254,6 @@ private function getUsersForUID(array $userIDs) { * TODO: Tidy up and write unit tests - code is mainly static method calls */ public function index($offset = 0, $limit = 10, $gid = '', $pattern = '', $backend = '') { - // FIXME: The JS sends the group '_everyone' instead of no GID for the "all users" group. - if($gid === '_everyone') { - $gid = ''; - } - // Remove backends if(!empty($backend)) { $activeBackends = $this->userManager->getBackends(); @@ -272,15 +268,18 @@ public function index($offset = 0, $limit = 10, $gid = '', $pattern = '', $backe $users = []; if ($this->isAdmin) { - - if($gid !== '') { + if($gid !== '' && $gid !== '_disabledUsers') { $batch = $this->getUsersForUID($this->groupManager->displayNamesInGroup($gid, $pattern, $limit, $offset)); } else { $batch = $this->userManager->search($pattern, $limit, $offset); } foreach ($batch as $user) { - $users[] = $this->formatUserForIndex($user); + if( ($gid !== '_disabledUsers' && $user->isEnabled()) || + ($gid === '_disabledUsers' && !$user->isEnabled()) + ) { + $users[] = $this->formatUserForIndex($user); + } } } else { @@ -293,7 +292,7 @@ public function index($offset = 0, $limit = 10, $gid = '', $pattern = '', $backe $subAdminOfGroups = $gids; // Set the $gid parameter to an empty value if the subadmin has no rights to access a specific group - if($gid !== '' && !in_array($gid, $subAdminOfGroups)) { + if($gid !== '' && $gid !== '_disabledUsers' && !in_array($gid, $subAdminOfGroups)) { $gid = ''; } @@ -318,7 +317,11 @@ public function index($offset = 0, $limit = 10, $gid = '', $pattern = '', $backe $this->groupManager->getUserGroupIds($user), $subAdminOfGroups )); - $users[] = $this->formatUserForIndex($user, $userGroups); + if( ($gid !== '_disabledUsers' && $user->isEnabled()) || + ($gid === '_disabledUsers' && !$user->isEnabled()) + ) { + $users[] = $this->formatUserForIndex($user, $userGroups); + } } } @@ -335,12 +338,12 @@ public function index($offset = 0, $limit = 10, $gid = '', $pattern = '', $backe * @param string $email * @return DataResponse */ - public function create($username, $password, array $groups=array(), $email='') { + public function create($username, $password, array $groups=[], $email='') { if($email !== '' && !$this->mailer->validateMailAddress($email)) { return new DataResponse( - array( + [ 'message' => (string)$this->l10n->t('Invalid mail address') - ), + ], Http::STATUS_UNPROCESSABLE_ENTITY ); } @@ -364,9 +367,9 @@ public function create($username, $password, array $groups=array(), $email='') { if (empty($groups)) { return new DataResponse( - array( + [ 'message' => $this->l10n->t('No valid group selected'), - ), + ], Http::STATUS_FORBIDDEN ); } @@ -374,9 +377,9 @@ public function create($username, $password, array $groups=array(), $email='') { if ($this->userManager->userExists($username)) { return new DataResponse( - array( + [ 'message' => (string)$this->l10n->t('A user with that name already exists.') - ), + ], Http::STATUS_CONFLICT ); } @@ -385,9 +388,9 @@ public function create($username, $password, array $groups=array(), $email='') { if ($password === '') { if ($email === '') { return new DataResponse( - array( + [ 'message' => (string)$this->l10n->t('To send a password link to the user an email address is required.') - ), + ], Http::STATUS_UNPROCESSABLE_ENTITY ); } @@ -404,9 +407,9 @@ public function create($username, $password, array $groups=array(), $email='') { $message = $this->l10n->t('Unable to create user.'); } return new DataResponse( - array( + [ 'message' => (string) $message, - ), + ], Http::STATUS_FORBIDDEN ); } @@ -431,7 +434,7 @@ public function create($username, $password, array $groups=array(), $email='') { $emailTemplate = $this->newUserMailHelper->generateTemplate($user, $generatePasswordResetToken); $this->newUserMailHelper->sendMail($user, $emailTemplate); } catch(\Exception $e) { - $this->log->error("Can't send new user mail to $email: " . $e->getMessage(), array('app' => 'settings')); + $this->log->error("Can't send new user mail to $email: " . $e->getMessage(), ['app' => 'settings']); } } // fetch users groups @@ -444,9 +447,9 @@ public function create($username, $password, array $groups=array(), $email='') { } return new DataResponse( - array( - 'message' => (string)$this->l10n->t('Unable to create user.') - ), + [ + 'message' => (string) $this->l10n->t('Unable to create user.') + ], Http::STATUS_FORBIDDEN ); @@ -465,24 +468,24 @@ public function destroy($id) { if($userId === $id) { return new DataResponse( - array( + [ 'status' => 'error', - 'data' => array( - 'message' => (string)$this->l10n->t('Unable to delete user.') - ) - ), + 'data' => [ + 'message' => (string) $this->l10n->t('Unable to delete user.') + ] + ], Http::STATUS_FORBIDDEN ); } if(!$this->isAdmin && !$this->groupManager->getSubAdmin()->isUserAccessible($this->userSession->getUser(), $user)) { return new DataResponse( - array( + [ 'status' => 'error', - 'data' => array( + 'data' => [ 'message' => (string)$this->l10n->t('Authentication error') - ) - ), + ] + ], Http::STATUS_FORBIDDEN ); } @@ -490,29 +493,97 @@ public function destroy($id) { if($user) { if($user->delete()) { return new DataResponse( - array( + [ 'status' => 'success', - 'data' => array( + 'data' => [ 'username' => $id - ) - ), + ] + ], Http::STATUS_NO_CONTENT ); } } return new DataResponse( - array( + [ 'status' => 'error', - 'data' => array( + 'data' => [ 'message' => (string)$this->l10n->t('Unable to delete user.') - ) - ), + ] + ], Http::STATUS_FORBIDDEN ); } /** + * @NoAdminRequired + * + * @param string $id + * @param int $enabled + * @return DataResponse + */ + public function setEnabled($id, $enabled) { + $enabled = (bool)$enabled; + if($enabled) { + $errorMsgGeneral = (string) $this->l10n->t('Error while enabling user.'); + } else { + $errorMsgGeneral = (string) $this->l10n->t('Error while disabling user.'); + } + + $userId = $this->userSession->getUser()->getUID(); + $user = $this->userManager->get($id); + + if ($userId === $id) { + return new DataResponse( + [ + 'status' => 'error', + 'data' => [ + 'message' => $errorMsgGeneral + ] + ], Http::STATUS_FORBIDDEN + ); + } + + if($user) { + if (!$this->isAdmin && !$this->groupManager->getSubAdmin()->isUserAccessible($this->userSession->getUser(), $user)) { + return new DataResponse( + [ + 'status' => 'error', + 'data' => [ + 'message' => (string) $this->l10n->t('Authentication error') + ] + ], + Http::STATUS_FORBIDDEN + ); + } + + $user->setEnabled($enabled); + return new DataResponse( + [ + 'status' => 'success', + 'data' => [ + 'username' => $id, + 'enabled' => $enabled + ] + ] + ); + } else { + return new DataResponse( + [ + 'status' => 'error', + 'data' => [ + 'message' => $errorMsgGeneral + ] + ], + Http::STATUS_FORBIDDEN + ); + } + + } + + /** + * Set the mail address of a user + * * @NoAdminRequired * @NoSubadminRequired * @PasswordConfirmationRequired @@ -635,14 +706,14 @@ public function setUserSettings($avatarScope, $twitterScope ) { - if(!empty($email) && !$this->mailer->validateMailAddress($email)) { + if (!empty($email) && !$this->mailer->validateMailAddress($email)) { return new DataResponse( - array( + [ 'status' => 'error', - 'data' => array( - 'message' => (string)$this->l10n->t('Invalid mail address') - ) - ), + 'data' => [ + 'message' => (string) $this->l10n->t('Invalid mail address') + ] + ], Http::STATUS_UNPROCESSABLE_ENTITY ); } @@ -662,9 +733,9 @@ public function setUserSettings($avatarScope, try { $this->saveUserSettings($user, $data); return new DataResponse( - array( + [ 'status' => 'success', - 'data' => array( + 'data' => [ 'userId' => $user->getUID(), 'avatarScope' => $avatarScope, 'displayname' => $displayname, @@ -675,9 +746,9 @@ public function setUserSettings($avatarScope, 'websiteScope' => $websiteScope, 'address' => $address, 'addressScope' => $addressScope, - 'message' => (string)$this->l10n->t('Settings saved') - ) - ), + 'message' => (string) $this->l10n->t('Settings saved') + ] + ], Http::STATUS_OK ); } catch (ForbiddenException $e) { @@ -843,36 +914,36 @@ public function setEMailAddress($id, $mailAddress) { && !$this->groupManager->getSubAdmin()->isUserAccessible($this->userSession->getUser(), $user) ) { return new DataResponse( - array( + [ 'status' => 'error', - 'data' => array( - 'message' => (string)$this->l10n->t('Forbidden') - ) - ), + 'data' => [ + 'message' => (string) $this->l10n->t('Forbidden') + ] + ], Http::STATUS_FORBIDDEN ); } if($mailAddress !== '' && !$this->mailer->validateMailAddress($mailAddress)) { return new DataResponse( - array( + [ 'status' => 'error', - 'data' => array( - 'message' => (string)$this->l10n->t('Invalid mail address') - ) - ), + 'data' => [ + 'message' => (string) $this->l10n->t('Invalid mail address') + ] + ], Http::STATUS_UNPROCESSABLE_ENTITY ); } if (!$user) { return new DataResponse( - array( + [ 'status' => 'error', - 'data' => array( - 'message' => (string)$this->l10n->t('Invalid user') - ) - ), + 'data' => [ + 'message' => (string) $this->l10n->t('Invalid user') + ] + ], Http::STATUS_UNPROCESSABLE_ENTITY ); } @@ -880,12 +951,12 @@ public function setEMailAddress($id, $mailAddress) { // for the permission of setting a email address if (!$user->canChangeDisplayName()) { return new DataResponse( - array( + [ 'status' => 'error', - 'data' => array( - 'message' => (string)$this->l10n->t('Unable to change mail address') - ) - ), + 'data' => [ + 'message' => (string) $this->l10n->t('Unable to change mail address') + ] + ], Http::STATUS_FORBIDDEN ); } @@ -896,14 +967,14 @@ public function setEMailAddress($id, $mailAddress) { try { $this->saveUserSettings($user, $userData); return new DataResponse( - array( + [ 'status' => 'success', - 'data' => array( + 'data' => [ 'username' => $id, 'mailAddress' => $mailAddress, - 'message' => (string)$this->l10n->t('Email saved') - ) - ), + 'message' => (string) $this->l10n->t('Email saved') + ] + ], Http::STATUS_OK ); } catch (ForbiddenException $e) { diff --git a/settings/css/settings.css b/settings/css/settings.css index 5693224b94cc9..0777f7e4cf4ac 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -458,15 +458,16 @@ table.grid th, table.grid td { font-weight: normal; } td.name, td.password { padding-left:.8em; } -td.password>img,td.displayName>img, td.remove>a, td.quota>img { visibility:hidden; vertical-align: middle;} +td.password>img,td.displayName>img, td.quota>img { visibility:hidden; } td.password, td.quota, td.displayName { width:12em; cursor:pointer; } -td.password>span, td.quota>span, rd.displayName>span { margin-right: 1.2em; color: #C7C7C7; } +td.password>span, td.quota>span { margin-right: 1.2em; color: #C7C7C7; } span.usersLastLoginTooltip { white-space: nowrap; } /* dropdowns will be relative to this element */ #userlist { position: relative; } + #userlist .mailAddress, #userlist .storageLocation, #userlist .userBackend, @@ -501,13 +502,51 @@ span.usersLastLoginTooltip { white-space: nowrap; } display: none; } +.bubble { + z-index:1; + right: -6px; + top: auto; +} +.bubble:after { + right: 5px; +} + +.popovermenu a.menuitem { + height: 20px; + margin: 0; + padding: 0; + line-height: initial; +} + +#userlist .popovermenu { + margin-top: 4px; + border-top-right-radius: 3px; +} + +#userlist .popovermenu>ul.userActionsMenu { + right: 10px; +} + +#userlist .popovermenu>ul.userActionsMenu a span { + margin-left: 5px; +} + +#userlist .popovermenu { + display:none; +} + tr:hover>td.password>span, tr:hover>td.displayName>span { margin:0; cursor:pointer; } -tr:hover>td.remove>a, tr:hover>td.password>img,tr:hover>td.displayName>img, tr:hover>td.quota>img { visibility:visible; cursor:pointer; } -td.remove { +tr:hover>td.password>img,tr:hover>td.displayName>img, tr:hover>td.quota>img { visibility:visible; cursor:pointer; } +td.userActions { width: 25px; + text-align: center; } -tr:hover>td.remove>a { - float: left; +td.userActions .action { + position: relative; + top: 3px; +} +td.userActions .action:hover { + cursor: pointer; } div.recoveryPassword { left:50em; display:block; position:absolute; top:-1px; } diff --git a/settings/js/users/groups.js b/settings/js/users/groups.js index aac1609bce7a1..16621441a640a 100644 --- a/settings/js/users/groups.js +++ b/settings/js/users/groups.js @@ -48,7 +48,8 @@ GroupList = { }, getUserCount: function ($groupLiElement) { - return parseInt($groupLiElement.data('usercount'), 10); + var count = parseInt($groupLiElement.data('usercount'), 10); + return isNaN(count) ? 0 : count; }, modGroupCount: function(gid, diff) { @@ -208,10 +209,17 @@ GroupList = { }, + showDisabledUsers: function () { + UserList.empty(); + UserList.update('_disabledUsers'); + $userGroupList.find('li').removeClass('active'); + GroupList.getGroupLI('_disabledUsers').addClass('active'); + }, + showGroup: function (gid) { GroupList.activeGID = gid; UserList.empty(); - UserList.update(gid); + UserList.update(gid === '_everyone' ? '' : gid); $userGroupList.find('li').removeClass('active'); if (gid !== undefined) { //TODO: treat Everyone properly @@ -364,6 +372,11 @@ $(document).ready( function () { GroupList.showGroup(GroupList.getElementGID(this)); }); + // show disabled users + $userGroupList.on('click', '.disabledusers', function () { + GroupList.showDisabledUsers(); + }); + $('#newgroupname').on('input', function(){ GroupList.handleAddGroupInput(this.value); }); diff --git a/settings/js/users/users.js b/settings/js/users/users.js index 46f4e5977bef0..387709cd64cce 100644 --- a/settings/js/users/users.js +++ b/settings/js/users/users.js @@ -48,11 +48,12 @@ var UserList = { * 'backend': 'LDAP', * 'email': 'username@example.org' * 'isRestoreDisabled':false + * 'isEnabled': true * } */ add: function (user) { - if (this.currentGid && this.currentGid !== '_everyone' && _.indexOf(user.groups, this.currentGid) < 0) { - return; + if (this.currentGid && this.currentGid !== '_everyone' && this.currentGid !== '_disabledUsers' && _.indexOf(user.groups, this.currentGid) < 0) { + return false; } var $tr = $userListBody.find('tr:first-child').clone(); @@ -77,6 +78,7 @@ var UserList = { $tr.data('displayname', user.displayname); $tr.data('mailAddress', user.email); $tr.data('restoreDisabled', user.isRestoreDisabled); + $tr.data('userEnabled', user.isEnabled); $tr.find('.name').text(user.name); $tr.find('td.displayName > span').text(user.displayname); $tr.find('td.mailAddress > span').text(user.email); @@ -97,18 +99,17 @@ var UserList = { $tdSubadmins.find('.action').tooltip({placement: 'top'}); /** - * remove action + * user actions menu */ - if ($tr.find('td.remove img').length === 0 && OC.currentUser !== user.name) { - var deleteImage = $('').attr({ - src: OC.imagePath('core', 'actions/delete') + if ($tr.find('td.userActions > span > img').length === 0 && OC.currentUser !== user.name) { + var menuImage = $('').attr({ + src: OC.imagePath('core', 'actions/more') }); - var deleteLink = $('') - .attr({ href: '#', 'original-title': t('settings', 'Delete')}) - .append(deleteImage); - $tr.find('td.remove').append(deleteLink); + var menuLink = $('') + .append(menuImage); + $tr.find('td.userActions > span').replaceWith(menuLink); } else if (OC.currentUser === user.name) { - $tr.find('td.remove a').remove(); + $tr.find('td.userActions').empty(); } /** @@ -160,14 +161,6 @@ var UserList = { * append generated row to user list */ $tr.appendTo($userList); - if(UserList.isEmpty === true) { - //when the list was emptied, one row was left, necessary to keep - //add working and the layout unbroken. We need to remove this item - $tr.show(); - $userListBody.find('tr:first').remove(); - UserList.isEmpty = false; - UserList.checkUsersToLoad(); - } $quotaSelect.on('change', UserList.onQuotaSelect); @@ -338,6 +331,9 @@ var UserList = { getRestoreDisabled: function(element) { return ($(element).closest('tr').data('restoreDisabled') || ''); }, + getUserEnabled: function(element) { + return ($(element).closest('tr').data('userEnabled') || ''); + }, initDeleteHandling: function() { //set up handler UserDeleteHandler = new DeleteHandler('/settings/users/users', 'username', @@ -351,7 +347,7 @@ var UserList = { UserList.undoRemove); //when to mark user for delete - $userListBody.on('click', '.delete', function () { + $userListBody.on('click', '.action-remove', function () { // Call function for handling delete/undo var uid = UserList.getUID(this); @@ -908,6 +904,62 @@ $(document).ready(function () { UserList._triggerGroupEdit($td, isSubadminSelect); }); + $userListBody.on('click', '.toggleUserActions', function (event) { + event.stopPropagation(); + var $td = $(this).closest('td'); + var $tr = $($td).closest('tr'); + var menudiv = $td.find('.popovermenu'); + + if(menudiv.is(':visible')) { + menudiv.fadeOut(100); + return; + } + menudiv.find('.action-togglestate').empty(); + if($tr.data('userEnabled')) { + $('.action-togglestate', $td).html(''+t('settings', 'Disable')+''); + } else { + $('.action-togglestate', $td).html(''+t('settings', 'Enable')+''); + } + menudiv.click(function() { menudiv.fadeOut(100); }); + menudiv.hover('', function() { menudiv.fadeOut(100); }); + menudiv.fadeIn(100); + }); + + $userListBody.on('click', '.action-togglestate', function (event) { + event.stopPropagation(); + var $td = $(this).closest('td'); + var $tr = $td.closest('tr'); + var uid = UserList.getUID($td); + var setEnabled = UserList.getUserEnabled($td) ? 0 : 1; + $.post( + OC.generateUrl('/settings/users/{id}/setEnabled', {id: uid}), + {username: uid, enabled: setEnabled}, + function (result) { + if (result && result.status==='success'){ + var count = GroupList.getUserCount(GroupList.getGroupLI('_disabledUsers')); + $tr.remove(); + if(result.data.enabled == 1) { + $tr.data('userEnabled', true); + GroupList.setUserCount(GroupList.getGroupLI('_disabledUsers'), count-1); + } else { + $tr.data('userEnabled', false); + GroupList.setUserCount(GroupList.getGroupLI('_disabledUsers'), count+1); + } + } else { + OC.dialogs.alert(result.data.message, t('settings', 'Error while changing status of {user}', {user: uid})); + } + } + ).fail(function(result){ + var message = 'Unknown error'; + if( result.responseJSON && + result.responseJSON.data && + result.responseJSON.data.message) { + message = result.responseJSON.data.message; + } + OC.dialogs.alert(message, t('settings', 'Error while changing status of {user}', {user: uid})); + }); + }); + // init the quota field select box after it is shown the first time $('#app-settings').one('show', function() { $(this).find('#default_quota').singleSelect().on('change', UserList.onQuotaSelect); diff --git a/settings/routes.php b/settings/routes.php index ba0761856d4a1..fb85b11f390e6 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -53,6 +53,7 @@ ['name' => 'Users#setEMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'], ['name' => 'Users#setUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT'], ['name' => 'Users#getVerificationCode', 'url' => '/settings/users/{account}/verify', 'verb' => 'GET'], + ['name' => 'Users#setEnabled', 'url' => '/settings/users/{id}/setEnabled', 'verb' => 'POST'], ['name' => 'Users#stats', 'url' => '/settings/users/stats', 'verb' => 'GET'], ['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'], ['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'], diff --git a/settings/templates/users/part.grouplist.php b/settings/templates/users/part.grouplist.php index beb0f275a33de..5dfd7836f6a74 100644 --- a/settings/templates/users/part.grouplist.php +++ b/settings/templates/users/part.grouplist.php @@ -37,6 +37,15 @@ + + +
  • + t('Disabled')); ?> + + 0) { p($disabledUsersGroup['usercount']); } ?> + +
  • +
  • diff --git a/settings/templates/users/part.userlist.php b/settings/templates/users/part.userlist.php index 7e7e1561e2fc6..b908109ad2d9f 100644 --- a/settings/templates/users/part.userlist.php +++ b/settings/templates/users/part.userlist.php @@ -14,7 +14,7 @@ t('Storage location')); ?> t('User backend')); ?> t('Last login')); ?> -   + @@ -63,7 +63,21 @@ - + + + diff --git a/settings/users.php b/settings/users.php index 1986592af757b..4d214bf950226 100644 --- a/settings/users.php +++ b/settings/users.php @@ -45,6 +45,7 @@ // Set the sort option: SORT_USERCOUNT or SORT_GROUPNAME $sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT; +$isLDAPUsed = false; if (\OC_App::isEnabled('user_ldap')) { $isLDAPUsed = $groupManager->isBackendUsed('\OCA\User_LDAP\Group_LDAP') @@ -59,12 +60,15 @@ $isAdmin = OC_User::isAdminUser(OC_User::getUser()); +$isDisabled = !OC_User::isEnabled(OC_User::getUser()); + $groupsInfo = new \OC\Group\MetaData( OC_User::getUser(), $isAdmin, $groupManager, \OC::$server->getUserSession() ); + $groupsInfo->setSorting($sortGroupsBy); list($adminGroup, $groups) = $groupsInfo->get(); @@ -93,6 +97,13 @@ $subAdmins = false; } +$disabledUsers = $isLDAPUsed ? 0 : $userManager->countDisabledUsers(); +$disabledUsersGroup = [ + 'id' => '_disabledUsers', + 'name' => '_disabledUsers', + 'usercount' => $disabledUsers +]; + // load preset quotas $quotaPreset=$config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB'); $quotaPreset=explode(',', $quotaPreset); @@ -111,6 +122,7 @@ $tmpl->assign('groups', $groups); $tmpl->assign('sortGroups', $sortGroupsBy); $tmpl->assign('adminGroup', $adminGroup); +$tmpl->assign('disabledUsersGroup', $disabledUsersGroup); $tmpl->assign('isAdmin', (int)$isAdmin); $tmpl->assign('subadmins', $subAdmins); $tmpl->assign('numofgroups', count($groups) + count($adminGroup)); diff --git a/tests/Settings/Controller/UsersControllerTest.php b/tests/Settings/Controller/UsersControllerTest.php index 589c5e97ee0d9..5905023e96095 100644 --- a/tests/Settings/Controller/UsersControllerTest.php +++ b/tests/Settings/Controller/UsersControllerTest.php @@ -211,6 +211,9 @@ public function testIndexAdmin() { ->expects($this->once()) ->method('getBackendClassName') ->will($this->returnValue('OC_User_Database')); + $foo->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); $admin = $this->createMock(User::class); $admin ->expects($this->exactly(2)) @@ -240,6 +243,9 @@ public function testIndexAdmin() { ->expects($this->once()) ->method('getBackendClassName') ->willReturn(Dummy::class); + $admin->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); $bar = $this->createMock(User::class); $bar ->expects($this->exactly(2)) @@ -267,6 +273,15 @@ public function testIndexAdmin() { ->expects($this->once()) ->method('getBackendClassName') ->willReturn(Dummy::class); + $bar->expects($this->at(0)) + ->method('isEnabled') + ->willReturn(true); + $bar->expects($this->at(1)) + ->method('isEnabled') + ->willReturn(true); + $bar->expects($this->at(2)) + ->method('isEnabled') + ->willReturn(false); $this->groupManager ->expects($this->once()) @@ -330,6 +345,7 @@ public function testIndexAdmin() { 'email' => 'foo@bar.com', 'isRestoreDisabled' => false, 'isAvatarAvailable' => true, + 'isEnabled' => true, ), 1 => array( 'name' => 'admin', @@ -343,6 +359,7 @@ public function testIndexAdmin() { 'email' => 'admin@bar.com', 'isRestoreDisabled' => false, 'isAvatarAvailable' => false, + 'isEnabled' => true, ), 2 => array( 'name' => 'bar', @@ -356,6 +373,7 @@ public function testIndexAdmin() { 'email' => 'bar@dummy.com', 'isRestoreDisabled' => false, 'isAvatarAvailable' => true, + 'isEnabled' => false, ), ) ); @@ -399,6 +417,9 @@ public function testIndexSubAdmin() { ->expects($this->once()) ->method('getBackendClassName') ->will($this->returnValue('OC_User_Database')); + $foo->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); $admin = $this->createMock(User::class); $admin ->expects($this->exactly(2)) @@ -428,6 +449,9 @@ public function testIndexSubAdmin() { ->expects($this->once()) ->method('getBackendClassName') ->willReturn(Dummy::class); + $admin->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); $bar = $this->createMock(User::class); $bar ->expects($this->exactly(2)) @@ -455,6 +479,9 @@ public function testIndexSubAdmin() { ->expects($this->once()) ->method('getBackendClassName') ->willReturn(Dummy::class); + $bar->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); $this->groupManager ->expects($this->at(2)) @@ -532,6 +559,7 @@ public function testIndexSubAdmin() { 'email' => 'bar@dummy.com', 'isRestoreDisabled' => false, 'isAvatarAvailable' => true, + 'isEnabled' => true, ], 1=> [ 'name' => 'foo', @@ -545,6 +573,7 @@ public function testIndexSubAdmin() { 'email' => 'foo@bar.com', 'isRestoreDisabled' => false, 'isAvatarAvailable' => true, + 'isEnabled' => true, ], 2 => [ 'name' => 'admin', @@ -558,6 +587,7 @@ public function testIndexSubAdmin() { 'email' => 'admin@bar.com', 'isRestoreDisabled' => false, 'isAvatarAvailable' => false, + 'isEnabled' => true, ], ] ); @@ -600,6 +630,9 @@ public function testIndexWithSearch() { ->expects($this->once()) ->method('getBackendClassName') ->will($this->returnValue('OC_User_Database')); + $foo->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); $admin = $this->createMock(User::class); $admin ->expects($this->exactly(2)) @@ -629,6 +662,9 @@ public function testIndexWithSearch() { ->expects($this->once()) ->method('getBackendClassName') ->willReturn(Dummy::class); + $admin->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); $bar = $this->createMock(User::class); $bar ->expects($this->exactly(2)) @@ -656,6 +692,9 @@ public function testIndexWithSearch() { ->expects($this->once()) ->method('getBackendClassName') ->willReturn(Dummy::class); + $bar->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); $this->userManager ->expects($this->once()) @@ -692,6 +731,7 @@ public function testIndexWithSearch() { 'email' => 'foo@bar.com', 'isRestoreDisabled' => false, 'isAvatarAvailable' => true, + 'isEnabled' => true, ), 1 => array( 'name' => 'admin', @@ -705,6 +745,7 @@ public function testIndexWithSearch() { 'email' => 'admin@bar.com', 'isRestoreDisabled' => false, 'isAvatarAvailable' => false, + 'isEnabled' => true, ), 2 => array( 'name' => 'bar', @@ -718,6 +759,7 @@ public function testIndexWithSearch() { 'email' => 'bar@dummy.com', 'isRestoreDisabled' => false, 'isAvatarAvailable' => true, + 'isEnabled' => true, ), ) ); @@ -755,6 +797,10 @@ public function testIndexWithBackend() { ->expects($this->once()) ->method('getBackendClassName') ->will($this->returnValue('OC_User_Database')); + $user->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); + $this->userManager ->expects($this->once()) ->method('getBackends') @@ -793,6 +839,7 @@ public function testIndexWithBackend() { 'email' => null, 'isRestoreDisabled' => false, 'isAvatarAvailable' => true, + 'isEnabled' => true, ) ) ); @@ -832,6 +879,9 @@ public function testCreateSuccessfulWithoutGroupAdmin() { ->expects($this->once()) ->method('getBackendClassName') ->will($this->returnValue('bar')); + $user->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); $this->userManager ->expects($this->once()) @@ -864,6 +914,7 @@ public function testCreateSuccessfulWithoutGroupAdmin() { 'email' => null, 'isRestoreDisabled' => false, 'isAvatarAvailable' => true, + 'isEnabled' => true, ), Http::STATUS_CREATED ); @@ -888,6 +939,9 @@ public function testCreateSuccessfulWithGroupAdmin() { ->expects($this->once()) ->method('getBackendClassName') ->will($this->returnValue('bar')); + $user->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); $existingGroup = $this->getMockBuilder('\OCP\IGroup') ->disableOriginalConstructor()->getMock(); $existingGroup @@ -946,6 +1000,7 @@ public function testCreateSuccessfulWithGroupAdmin() { 'email' => null, 'isRestoreDisabled' => false, 'isAvatarAvailable' => true, + 'isEnabled' => true, ), Http::STATUS_CREATED ); @@ -975,6 +1030,9 @@ public function testCreateSuccessfulWithGroupSubAdmin() { ->method('getBackendClassName') ->will($this->returnValue('bar')); $subGroup1 = $this->createMock(IGroup::class); + $newUser->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); $subGroup1 ->expects($this->any()) ->method('getGID') @@ -1034,6 +1092,7 @@ public function testCreateSuccessfulWithGroupSubAdmin() { 'email' => null, 'isRestoreDisabled' => false, 'isAvatarAvailable' => true, + 'isEnabled' => true, ), Http::STATUS_CREATED ); @@ -1463,7 +1522,8 @@ public function testCreateSuccessfulWithValidEmailAdmin() { } private function mockUser($userId = 'foo', $displayName = 'M. Foo', - $lastLogin = 500, $home = '/home/foo', $backend = 'OC_User_Database') { + $lastLogin = 500, $home = '/home/foo', + $backend = 'OC_User_Database', $enabled = true) { $user = $this->createMock(User::class); $user ->expects($this->any()) @@ -1483,6 +1543,9 @@ private function mockUser($userId = 'foo', $displayName = 'M. Foo', ->expects($this->once()) ->method('getBackendClassName') ->will($this->returnValue($backend)); + $user->expects($this->any()) + ->method('isEnabled') + ->willReturn($enabled); $result = [ 'name' => $userId, @@ -1496,6 +1559,7 @@ private function mockUser($userId = 'foo', $displayName = 'M. Foo', 'email' => null, 'isRestoreDisabled' => false, 'isAvatarAvailable' => true, + 'isEnabled' => $enabled, ]; return [$user, $result]; @@ -2159,6 +2223,11 @@ public function setEmailAddressData() { /** * @dataProvider setEmailAddressData * + * @param string $mailAddress + * @param bool $isValid + * @param bool $expectsUpdate + * @param bool $canChangeDisplayName + * @param int $responseCode */ public function testSetEMailAddress($mailAddress, $isValid, $expectsUpdate, $canChangeDisplayName, $responseCode) { $user = $this->createMock(User::class); @@ -2230,6 +2299,9 @@ public function testCreateSuccessfulWithoutPasswordAndWithEmail() { ->expects($this->once()) ->method('getBackendClassName') ->willReturn('bar'); + $user + ->method('isEnabled') + ->willReturn(true); $this->userManager ->expects($this->once()) @@ -2279,6 +2351,7 @@ public function testCreateSuccessfulWithoutPasswordAndWithEmail() { 'email' => 'abc@example.org', 'isRestoreDisabled' => false, 'isAvatarAvailable' => true, + 'isEnabled' => true, ], Http::STATUS_CREATED ); @@ -2369,7 +2442,428 @@ public function testGetVerificationCodeInvalidUser() { $this->userSession->expects($this->once())->method('getUser')->willReturn(null); $result = $controller->getVerificationCode('account', false); - $this->assertSame(Http::STATUS_BAD_REQUEST ,$result->getStatus()); + $this->assertSame(Http::STATUS_BAD_REQUEST, $result->getStatus()); + } + + public function testDisableUserFailsDueSameUser() { + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('abc')); + $this->userSession + ->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)); + $expectedResponse = new DataResponse( + [ + 'status' => 'error', + 'data' => [ + 'message' => 'Error while disabling user.', + ], + ], + Http::STATUS_FORBIDDEN + ); + $response = $this->getController(true)->setEnabled('abc', false); + $this->assertEquals($expectedResponse, $response); + } + + public function testDisableUserFailsDueNoAdminAndNoSubadmin() { + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('def')); + $this->userSession + ->expects($this->exactly(2)) + ->method('getUser') + ->will($this->returnValue($user)); + $user2 = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user2->expects($this->never()) + ->method('setEnabled'); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('abc') + ->willReturn($user2); + + $subadmin = $this->createMock('\OC\SubAdmin'); + $subadmin->expects($this->once()) + ->method('isUserAccessible') + ->will($this->returnValue(false)); + $this->groupManager + ->expects($this->once()) + ->method('getSubAdmin') + ->willReturn($subadmin); + + $expectedResponse = new DataResponse( + [ + 'status' => 'error', + 'data' => [ + 'message' => 'Authentication error', + ], + ], + Http::STATUS_FORBIDDEN + ); + $response = $this->getController(false)->setEnabled('abc', false); + $this->assertEquals($expectedResponse, $response); + } + + public function testDisableUserFailsDueNoUser() { + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('def')); + $this->userSession + ->expects($this->exactly(1)) + ->method('getUser') + ->will($this->returnValue($user)); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('abc') + ->willReturn(null); + + $this->groupManager + ->expects($this->never()) + ->method('getSubAdmin'); + + $expectedResponse = new DataResponse( + [ + 'status' => 'error', + 'data' => [ + 'message' => 'Error while disabling user.', + ], + ], + Http::STATUS_FORBIDDEN + ); + $response = $this->getController(true)->setEnabled('abc', false); + $this->assertEquals($expectedResponse, $response); + } + + public function testDisableUserFailsDueNoUserForSubAdmin() { + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('def')); + $this->userSession + ->expects($this->exactly(1)) + ->method('getUser') + ->will($this->returnValue($user)); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('abc') + ->willReturn(null); + + $this->groupManager + ->expects($this->never()) + ->method('getSubAdmin'); + + $expectedResponse = new DataResponse( + [ + 'status' => 'error', + 'data' => [ + 'message' => 'Error while disabling user.', + ], + ], + Http::STATUS_FORBIDDEN + ); + $response = $this->getController(false)->setEnabled('abc', false); + $this->assertEquals($expectedResponse, $response); + } + + public function testDisableUserSuccessForAdmin() { + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('def')); + $this->userSession + ->expects($this->exactly(1)) + ->method('getUser') + ->will($this->returnValue($user)); + $user2 = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user2->expects($this->once()) + ->method('setEnabled') + ->with(false); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('abc') + ->willReturn($user2); + + $this->groupManager + ->expects($this->never()) + ->method('getSubAdmin'); + + $expectedResponse = new DataResponse( + [ + 'status' => 'success', + 'data' => [ + 'username' => 'abc', + 'enabled' => 0, + ], + ] + ); + $response = $this->getController(true)->setEnabled('abc', false); + $this->assertEquals($expectedResponse, $response); + } + + public function testDisableUserSuccessForSubAdmin() { + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('def')); + $this->userSession + ->expects($this->exactly(2)) + ->method('getUser') + ->will($this->returnValue($user)); + $user2 = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user2->expects($this->once()) + ->method('setEnabled'); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('abc') + ->willReturn($user2); + + $subadmin = $this->createMock('\OC\SubAdmin'); + $subadmin->expects($this->once()) + ->method('isUserAccessible') + ->will($this->returnValue(true)); + $this->groupManager + ->expects($this->once()) + ->method('getSubAdmin') + ->willReturn($subadmin); + + $expectedResponse = new DataResponse( + [ + 'status' => 'success', + 'data' => [ + 'username' => 'abc', + 'enabled' => 0, + ], + ] + ); + $response = $this->getController(false)->setEnabled('abc', false); + $this->assertEquals($expectedResponse, $response); + } + + public function testEnableUserFailsDueSameUser() { + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('abc')); + $this->userSession + ->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)); + $expectedResponse = new DataResponse( + [ + 'status' => 'error', + 'data' => [ + 'message' => 'Error while enabling user.', + ], + ], + Http::STATUS_FORBIDDEN + ); + $response = $this->getController(true)->setEnabled('abc', true); + $this->assertEquals($expectedResponse, $response); + } + + public function testEnableUserFailsDueNoAdminAndNoSubadmin() { + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('def')); + $this->userSession + ->expects($this->exactly(2)) + ->method('getUser') + ->will($this->returnValue($user)); + $user2 = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user2->expects($this->never()) + ->method('setEnabled'); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('abc') + ->willReturn($user2); + + $subadmin = $this->createMock('\OC\SubAdmin'); + $subadmin->expects($this->once()) + ->method('isUserAccessible') + ->will($this->returnValue(false)); + $this->groupManager + ->expects($this->once()) + ->method('getSubAdmin') + ->willReturn($subadmin); + + $expectedResponse = new DataResponse( + [ + 'status' => 'error', + 'data' => [ + 'message' => 'Authentication error', + ], + ], + Http::STATUS_FORBIDDEN + ); + $response = $this->getController(false)->setEnabled('abc', true); + $this->assertEquals($expectedResponse, $response); + } + + public function testEnableUserFailsDueNoUser() { + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('def')); + $this->userSession + ->expects($this->exactly(1)) + ->method('getUser') + ->will($this->returnValue($user)); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('abc') + ->willReturn(null); + + $this->groupManager + ->expects($this->never()) + ->method('getSubAdmin'); + + $expectedResponse = new DataResponse( + [ + 'status' => 'error', + 'data' => [ + 'message' => 'Error while enabling user.', + ], + ], + Http::STATUS_FORBIDDEN + ); + $response = $this->getController(true)->setEnabled('abc', true); + $this->assertEquals($expectedResponse, $response); + } + + public function testEnableUserFailsDueNoUserForSubAdmin() { + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('def')); + $this->userSession + ->expects($this->exactly(1)) + ->method('getUser') + ->will($this->returnValue($user)); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('abc') + ->willReturn(null); + + $this->groupManager + ->expects($this->never()) + ->method('getSubAdmin'); + + $expectedResponse = new DataResponse( + [ + 'status' => 'error', + 'data' => [ + 'message' => 'Error while enabling user.', + ], + ], + Http::STATUS_FORBIDDEN + ); + $response = $this->getController(false)->setEnabled('abc', true); + $this->assertEquals($expectedResponse, $response); + } + + public function testEnableUserSuccessForAdmin() { + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('def')); + $this->userSession + ->expects($this->exactly(1)) + ->method('getUser') + ->will($this->returnValue($user)); + $user2 = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user2->expects($this->once()) + ->method('setEnabled'); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('abc') + ->willReturn($user2); + + $this->groupManager + ->expects($this->never()) + ->method('getSubAdmin'); + + $expectedResponse = new DataResponse( + [ + 'status' => 'success', + 'data' => [ + 'username' => 'abc', + 'enabled' => 1, + ], + ] + ); + $response = $this->getController(true)->setEnabled('abc', true); + $this->assertEquals($expectedResponse, $response); + } + + public function testEnableUserSuccessForSubAdmin() { + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('def')); + $this->userSession + ->expects($this->exactly(2)) + ->method('getUser') + ->will($this->returnValue($user)); + $user2 = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user2->expects($this->once()) + ->method('setEnabled') + ->with(true); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('abc') + ->willReturn($user2); + + $subadmin = $this->createMock('\OC\SubAdmin'); + $subadmin->expects($this->once()) + ->method('isUserAccessible') + ->will($this->returnValue(true)); + $this->groupManager + ->expects($this->once()) + ->method('getSubAdmin') + ->willReturn($subadmin); + $expectedResponse = new DataResponse( + [ + 'status' => 'success', + 'data' => [ + 'username' => 'abc', + 'enabled' => 1, + ], + ] + ); + $response = $this->getController(false)->setEnabled('abc', true); + $this->assertEquals($expectedResponse, $response); } } diff --git a/tests/lib/User/ManagerTest.php b/tests/lib/User/ManagerTest.php index aaee64f186382..cf725aae6719b 100644 --- a/tests/lib/User/ManagerTest.php +++ b/tests/lib/User/ManagerTest.php @@ -510,6 +510,31 @@ public function testCountUsersTwoBackends() { $this->assertEquals(7 + 16, $users); } + public function testCountUsersOnlyDisabled() { + $manager = \OC::$server->getUserManager(); + // count other users in the db before adding our own + $countBefore = $manager->countDisabledUsers(); + + //Add test users + $user1 = $manager->createUser('testdisabledcount1', 'testdisabledcount1'); + + $user2 = $manager->createUser('testdisabledcount2', 'testdisabledcount2'); + $user2->setEnabled(false); + + $user3 = $manager->createUser('testdisabledcount3', 'testdisabledcount3'); + + $user4 = $manager->createUser('testdisabledcount4', 'testdisabledcount4'); + $user4->setEnabled(false); + + $this->assertEquals($countBefore + 2, $manager->countDisabledUsers()); + + //cleanup + $user1->delete(); + $user2->delete(); + $user3->delete(); + $user4->delete(); + } + public function testCountUsersOnlySeen() { $manager = \OC::$server->getUserManager(); // count other users in the db before adding our own