Merge pull request #8059 from owncloud/countUsersInGroup

add optional countUsersInGroup method to group backends
This commit is contained in:
Lukas Reschke 2014-04-14 20:45:12 +02:00
commit 340089f270
8 changed files with 317 additions and 1 deletions

View File

@ -276,6 +276,84 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
return $groupUsers; return $groupUsers;
} }
/**
* @brief returns the number of users in a group, who match the search term
* @param string the internal group name
* @param string optional, a search string
* @returns int | bool
*/
public function countUsersInGroup($gid, $search = '') {
$cachekey = 'countUsersInGroup-'.$gid.'-'.$search;
if(!$this->enabled || !$this->groupExists($gid)) {
return false;
}
$groupUsers = $this->access->connection->getFromCache($cachekey);
if(!is_null($groupUsers)) {
return $groupUsers;
}
$groupDN = $this->access->groupname2dn($gid);
if(!$groupDN) {
// group couldn't be found, return empty resultset
$this->access->connection->writeToCache($cachekey, false);
return false;
}
$members = array_keys($this->_groupMembers($groupDN));
if(!$members) {
//in case users could not be retrieved, return empty resultset
$this->access->connection->writeToCache($cachekey, false);
return false;
}
if(empty($search)) {
$groupUsers = count($members);
$this->access->connection->writeToCache($cachekey, $groupUsers);
return $groupUsers;
}
$isMemberUid =
(strtolower($this->access->connection->ldapGroupMemberAssocAttr)
=== 'memberuid');
//we need to apply the search filter
//alternatives that need to be checked:
//a) get all users by search filter and array_intersect them
//b) a, but only when less than 1k 10k ?k users like it is
//c) put all DNs|uids in a LDAP filter, combine with the search string
// and let it count.
//For now this is not important, because the only use of this method
//does not supply a search string
$groupUsers = array();
foreach($members as $member) {
if($isMemberUid) {
//we got uids, need to get their DNs to 'tranlsate' them to usernames
$filter = $this->access->combineFilterWithAnd(array(
\OCP\Util::mb_str_replace('%uid', $member,
$this->access->connection->ldapLoginFilter, 'UTF-8'),
$this->access->getFilterPartForUserSearch($search)
));
$ldap_users = $this->access->fetchListOfUsers($filter, 'dn');
if(count($ldap_users) < 1) {
continue;
}
$groupUsers[] = $this->access->dn2username($ldap_users[0]);
} else {
//we need to apply the search filter now
if(!$this->access->readAttribute($member,
$this->access->connection->ldapUserDisplayName,
$this->access->getFilterPartForUserSearch($search))) {
continue;
}
// dn2username will also check if the users belong to the allowed base
if($ocname = $this->access->dn2username($member)) {
$groupUsers[] = $ocname;
}
}
}
return count($groupUsers);
}
/** /**
* @brief get a list of all display names in a group * @brief get a list of all display names in a group
* @returns array with display names (value) and user ids(key) * @returns array with display names (value) and user ids(key)
@ -418,6 +496,9 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
* compared with OC_USER_BACKEND_CREATE_USER etc. * compared with OC_USER_BACKEND_CREATE_USER etc.
*/ */
public function implementsActions($actions) { public function implementsActions($actions) {
return (bool)(OC_GROUP_BACKEND_GET_DISPLAYNAME & $actions); return (bool)((
OC_GROUP_BACKEND_GET_DISPLAYNAME
| OC_GROUP_BACKEND_COUNT_USERS
) & $actions);
} }
} }

View File

@ -144,6 +144,17 @@ class Group_Proxy extends lib\Proxy implements \OCP\GroupInterface {
return $users; return $users;
} }
/**
* @brief returns the number of users in a group, who match the search term
* @param string the internal group name
* @param string optional, a search string
* @returns int | bool
*/
public function countUsersInGroup($gid, $search = '') {
return $this->handleRequest(
$gid, 'countUsersInGroup', array($gid, $search));
}
/** /**
* @brief get a list of all display names in a group * @brief get a list of all display names in a group
* @returns array with display names (value) and user ids(key) * @returns array with display names (value) and user ids(key)

View File

@ -0,0 +1,115 @@
<?php
/**
* ownCloud
*
* @author Arthur Schiwon
* @copyright 2014 Arthur Schiwon <blizzz@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\user_ldap\tests;
namespace OCA\user_ldap\tests;
use \OCA\user_ldap\GROUP_LDAP as GroupLDAP;
use \OCA\user_ldap\lib\Access;
use \OCA\user_ldap\lib\Connection;
use \OCA\user_ldap\lib\ILDAPWrapper;
class Test_Group_Ldap extends \PHPUnit_Framework_TestCase {
private function getAccessMock() {
static $conMethods;
static $accMethods;
if(is_null($conMethods) || is_null($accMethods)) {
$conMethods = get_class_methods('\OCA\user_ldap\lib\Connection');
$accMethods = get_class_methods('\OCA\user_ldap\lib\Access');
}
$lw = $this->getMock('\OCA\user_ldap\lib\ILDAPWrapper');
$connector = $this->getMock('\OCA\user_ldap\lib\Connection',
$conMethods,
array($lw, null, null));
$access = $this->getMock('\OCA\user_ldap\lib\Access',
$accMethods,
array($connector, $lw));
return $access;
}
private function enableGroups($access) {
$access->connection->expects($this->any())
->method('__get')
->will($this->returnCallback(function($name) {
// if($name === 'ldapLoginFilter') {
// return '%uid';
// }
return 1;
}));
}
public function testCountEmptySearchString() {
$access = $this->getAccessMock();
$this->enableGroups($access);
$access->expects($this->any())
->method('groupname2dn')
->will($this->returnValue('cn=group,dc=foo,dc=bar'));
$access->expects($this->any())
->method('readAttribute')
->will($this->returnValue(array('u11', 'u22', 'u33', 'u34')));
$groupBackend = new GroupLDAP($access);
$users = $groupBackend->countUsersInGroup('group');
$this->assertSame(4, $users);
}
public function testCountWithSearchString() {
$access = $this->getAccessMock();
$this->enableGroups($access);
$access->expects($this->any())
->method('groupname2dn')
->will($this->returnValue('cn=group,dc=foo,dc=bar'));
$access->expects($this->any())
->method('readAttribute')
->will($this->returnCallback(function($name) {
//the search operation will call readAttribute, thus we need
//to anaylze the "dn". All other times we just need to return
//something that is neither null or false, but once an array
//with the users in the group so we do so all other times for
//simplicicity.
if(strpos($name, 'u') === 0) {
return strpos($name, '3');
}
return array('u11', 'u22', 'u33', 'u34');
}));
$access->expects($this->any())
->method('dn2username')
->will($this->returnValue('foobar'));
$groupBackend = new GroupLDAP($access);
$users = $groupBackend->countUsersInGroup('group', '3');
$this->assertSame(2, $users);
}
}

View File

@ -34,6 +34,7 @@ define('OC_GROUP_BACKEND_DELETE_GROUP', 0x00000010);
define('OC_GROUP_BACKEND_ADD_TO_GROUP', 0x00000100); define('OC_GROUP_BACKEND_ADD_TO_GROUP', 0x00000100);
define('OC_GROUP_BACKEND_REMOVE_FROM_GOUP', 0x00001000); define('OC_GROUP_BACKEND_REMOVE_FROM_GOUP', 0x00001000);
define('OC_GROUP_BACKEND_GET_DISPLAYNAME', 0x00010000); define('OC_GROUP_BACKEND_GET_DISPLAYNAME', 0x00010000);
define('OC_GROUP_BACKEND_COUNT_USERS', 0x00100000);
/** /**
* Abstract base class for user management * Abstract base class for user management
@ -45,6 +46,7 @@ abstract class OC_Group_Backend implements OC_Group_Interface {
OC_GROUP_BACKEND_ADD_TO_GROUP => 'addToGroup', OC_GROUP_BACKEND_ADD_TO_GROUP => 'addToGroup',
OC_GROUP_BACKEND_REMOVE_FROM_GOUP => 'removeFromGroup', OC_GROUP_BACKEND_REMOVE_FROM_GOUP => 'removeFromGroup',
OC_GROUP_BACKEND_GET_DISPLAYNAME => 'displayNamesInGroup', OC_GROUP_BACKEND_GET_DISPLAYNAME => 'displayNamesInGroup',
OC_GROUP_BACKEND_COUNT_USERS => 'countUsersInGroup',
); );
/** /**

View File

@ -211,6 +211,20 @@ class OC_Group_Database extends OC_Group_Backend {
return $users; return $users;
} }
/**
* @brief get the number of all users matching the search string in a group
* @param string $gid
* @param string $search
* @param int $limit
* @param int $offset
* @return int | false
*/
public function countUsersInGroup($gid, $search = '') {
$stmt = OC_DB::prepare('SELECT COUNT(`uid`) AS `count` FROM `*PREFIX*group_user` WHERE `gid` = ? AND `uid` LIKE ?');
$result = $stmt->execute(array($gid, $search.'%'));
return $result->fetchOne();
}
/** /**
* @brief get a list of all display names in a group * @brief get a list of all display names in a group
* @param string $gid * @param string $gid

View File

@ -157,4 +157,14 @@ class OC_Group_Dummy extends OC_Group_Backend {
} }
} }
/**
* @brief get the number of all users in a group
* @returns int | bool
*/
public function countUsersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
if(isset($this->groups[$gid])) {
return count($this->groups[$gid]);
}
}
} }

View File

@ -186,6 +186,27 @@ class Group {
return array_values($users); return array_values($users);
} }
/**
* returns the number of users matching the search string
*
* @param string $search
* @return int | bool
*/
public function count($search) {
$users = false;
foreach ($this->backends as $backend) {
if($backend->implementsActions(OC_GROUP_BACKEND_COUNT_USERS)) {
if($users === false) {
//we could directly add to a bool variable, but this would
//be ugly
$users = 0;
}
$users += $backend->countUsersInGroup($this->gid, $search);
}
}
return $users;
}
/** /**
* search for users in the group by displayname * search for users in the group by displayname
* *

View File

@ -299,6 +299,68 @@ class Group extends \PHPUnit_Framework_TestCase {
$this->assertEquals('user1', $user1->getUID()); $this->assertEquals('user1', $user1->getUID());
} }
public function testCountUsers() {
$backend1 = $this->getMock('OC_Group_Database');
$userManager = $this->getUserManager();
$group = new \OC\Group\Group('group1', array($backend1), $userManager);
$backend1->expects($this->once())
->method('countUsersInGroup')
->with('group1', '2')
->will($this->returnValue(3));
$backend1->expects($this->any())
->method('implementsActions')
->will($this->returnValue(true));
$users = $group->count('2');
$this->assertSame(3, $users);
}
public function testCountUsersMultipleBackends() {
$backend1 = $this->getMock('OC_Group_Database');
$backend2 = $this->getMock('OC_Group_Database');
$userManager = $this->getUserManager();
$group = new \OC\Group\Group('group1', array($backend1, $backend2), $userManager);
$backend1->expects($this->once())
->method('countUsersInGroup')
->with('group1', '2')
->will($this->returnValue(3));
$backend1->expects($this->any())
->method('implementsActions')
->will($this->returnValue(true));
$backend2->expects($this->once())
->method('countUsersInGroup')
->with('group1', '2')
->will($this->returnValue(4));
$backend2->expects($this->any())
->method('implementsActions')
->will($this->returnValue(true));
$users = $group->count('2');
$this->assertSame(7, $users);
}
public function testCountUsersNoMethod() {
$backend1 = $this->getMock('OC_Group_Database');
$userManager = $this->getUserManager();
$group = new \OC\Group\Group('group1', array($backend1), $userManager);
$backend1->expects($this->never())
->method('countUsersInGroup');
$backend1->expects($this->any())
->method('implementsActions')
->will($this->returnValue(false));
$users = $group->count('2');
$this->assertSame(false, $users);
}
public function testDelete() { public function testDelete() {
$backend = $this->getMock('OC_Group_Database'); $backend = $this->getMock('OC_Group_Database');
$userManager = $this->getUserManager(); $userManager = $this->getUserManager();