Completed
Push — master ( 8a004a...215573 )
by Morris
13:08
created

Group_LDAP::getUserGidNumber()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Alexander Bergolth <[email protected]>
6
 * @author Alex Weirig <[email protected]>
7
 * @author alexweirig <[email protected]>
8
 * @author Andreas Fischer <[email protected]>
9
 * @author Arthur Schiwon <[email protected]>
10
 * @author Bart Visscher <[email protected]>
11
 * @author Christopher Schäpers <[email protected]>
12
 * @author Frédéric Fortier <[email protected]>
13
 * @author Joas Schilling <[email protected]>
14
 * @author Lukas Reschke <[email protected]>
15
 * @author Morris Jobke <[email protected]>
16
 * @author Nicolas Grekas <[email protected]>
17
 * @author Robin McCorkell <[email protected]>
18
 * @author Roeland Jago Douma <[email protected]>
19
 * @author Thomas Müller <[email protected]>
20
 * @author Vincent Petry <[email protected]>
21
 * @author Xuanwo <[email protected]>
22
 *
23
 * @license AGPL-3.0
24
 *
25
 * This code is free software: you can redistribute it and/or modify
26
 * it under the terms of the GNU Affero General Public License, version 3,
27
 * as published by the Free Software Foundation.
28
 *
29
 * This program is distributed in the hope that it will be useful,
30
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32
 * GNU Affero General Public License for more details.
33
 *
34
 * You should have received a copy of the GNU Affero General Public License, version 3,
35
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
36
 *
37
 */
38
39
namespace OCA\User_LDAP;
40
41
use OC\Cache\CappedMemoryCache;
42
43
class Group_LDAP extends BackendUtility implements \OCP\GroupInterface {
44
	protected $enabled = false;
45
46
	/**
47
	 * @var string[] $cachedGroupMembers array of users with gid as key
48
	 */
49
	protected $cachedGroupMembers;
50
51
	/**
52
	 * @var string[] $cachedGroupsByMember array of groups with uid as key
53
	 */
54
	protected $cachedGroupsByMember;
55
56
	public function __construct(Access $access) {
57
		parent::__construct($access);
58
		$filter = $this->access->connection->ldapGroupFilter;
0 ignored issues
show
Documentation introduced by
The property ldapGroupFilter does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
59
		$gassoc = $this->access->connection->ldapGroupMemberAssocAttr;
60
		if(!empty($filter) && !empty($gassoc)) {
61
			$this->enabled = true;
62
		}
63
64
		$this->cachedGroupMembers = new CappedMemoryCache();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \OC\Cache\CappedMemoryCache() of type object<OC\Cache\CappedMemoryCache> is incompatible with the declared type array<integer,string> of property $cachedGroupMembers.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
65
		$this->cachedGroupsByMember = new CappedMemoryCache();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \OC\Cache\CappedMemoryCache() of type object<OC\Cache\CappedMemoryCache> is incompatible with the declared type array<integer,string> of property $cachedGroupsByMember.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
66
	}
67
68
	/**
69
	 * is user in group?
70
	 * @param string $uid uid of the user
71
	 * @param string $gid gid of the group
72
	 * @return bool
73
	 *
74
	 * Checks whether the user is member of a group or not.
75
	 */
76
	public function inGroup($uid, $gid) {
77
		if(!$this->enabled) {
78
			return false;
79
		}
80
		$cacheKey = 'inGroup'.$uid.':'.$gid;
81
		$inGroup = $this->access->connection->getFromCache($cacheKey);
82
		if(!is_null($inGroup)) {
83
			return (bool)$inGroup;
84
		}
85
86
		$userDN = $this->access->username2dn($uid);
87
88
		if(isset($this->cachedGroupMembers[$gid])) {
89
			$isInGroup = in_array($userDN, $this->cachedGroupMembers[$gid]);
90
			return $isInGroup;
91
		}
92
93
		$cacheKeyMembers = 'inGroup-members:'.$gid;
94
		$members = $this->access->connection->getFromCache($cacheKeyMembers);
95
		if(!is_null($members)) {
96
			$this->cachedGroupMembers[$gid] = $members;
97
			$isInGroup = in_array($userDN, $members);
98
			$this->access->connection->writeToCache($cacheKey, $isInGroup);
99
			return $isInGroup;
100
		}
101
102
		$groupDN = $this->access->groupname2dn($gid);
103
		// just in case
104 View Code Duplication
		if(!$groupDN || !$userDN) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $groupDN of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Bug Best Practice introduced by
The expression $userDN of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
105
			$this->access->connection->writeToCache($cacheKey, false);
106
			return false;
107
		}
108
109
		//check primary group first
110 View Code Duplication
		if($gid === $this->getUserPrimaryGroup($userDN)) {
111
			$this->access->connection->writeToCache($cacheKey, true);
112
			return true;
113
		}
114
115
		//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
116
		$members = $this->_groupMembers($groupDN);
117
		$members = array_keys($members); // uids are returned as keys
118
		if(!is_array($members) || count($members) === 0) {
119
			$this->access->connection->writeToCache($cacheKey, false);
120
			return false;
121
		}
122
123
		//extra work if we don't get back user DNs
124
		if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
125
			$dns = array();
126
			$filterParts = array();
127
			$bytes = 0;
128
			foreach($members as $mid) {
129
				$filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
0 ignored issues
show
Documentation introduced by
The property ldapLoginFilter does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
130
				$filterParts[] = $filter;
131
				$bytes += strlen($filter);
132
				if($bytes >= 9000000) {
133
					// AD has a default input buffer of 10 MB, we do not want
134
					// to take even the chance to exceed it
135
					$filter = $this->access->combineFilterWithOr($filterParts);
136
					$bytes = 0;
137
					$filterParts = array();
138
					$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
139
					$dns = array_merge($dns, $users);
140
				}
141
			}
142
			if(count($filterParts) > 0) {
143
				$filter = $this->access->combineFilterWithOr($filterParts);
144
				$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
145
				$dns = array_merge($dns, $users);
146
			}
147
			$members = $dns;
148
		}
149
150
		$isInGroup = in_array($userDN, $members);
151
		$this->access->connection->writeToCache($cacheKey, $isInGroup);
152
		$this->access->connection->writeToCache($cacheKeyMembers, $members);
153
		$this->cachedGroupMembers[$gid] = $members;
154
155
		return $isInGroup;
156
	}
157
158
	/**
159
	 * @param string $dnGroup
160
	 * @return array
161
	 *
162
	 * For a group that has user membership defined by an LDAP search url attribute returns the users
163
	 * that match the search url otherwise returns an empty array.
164
	 */
165
	public function getDynamicGroupMembers($dnGroup) {
166
		$dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
0 ignored issues
show
Documentation introduced by
The property ldapDynamicGroupMemberURL does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
167
168
		if (empty($dynamicGroupMemberURL)) {
169
			return array();
170
		}
171
172
		$dynamicMembers = array();
173
		$memberURLs = $this->access->readAttribute(
174
			$dnGroup,
175
			$dynamicGroupMemberURL,
176
			$this->access->connection->ldapGroupFilter
0 ignored issues
show
Documentation introduced by
The property ldapGroupFilter does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
177
		);
178
		if ($memberURLs !== false) {
179
			// this group has the 'memberURL' attribute so this is a dynamic group
180
			// example 1: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(o=HeadOffice)
181
			// example 2: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(&(o=HeadOffice)(uidNumber>=500))
182
			$pos = strpos($memberURLs[0], '(');
183
			if ($pos !== false) {
184
				$memberUrlFilter = substr($memberURLs[0], $pos);
185
				$foundMembers = $this->access->searchUsers($memberUrlFilter,'dn');
186
				$dynamicMembers = array();
187
				foreach($foundMembers as $value) {
188
					$dynamicMembers[$value['dn'][0]] = 1;
189
				}
190
			} else {
191
				\OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
192
					'of group ' . $dnGroup, \OCP\Util::DEBUG);
193
			}
194
		}
195
		return $dynamicMembers;
196
	}
197
198
	/**
199
	 * @param string $dnGroup
200
	 * @param array|null &$seen
201
	 * @return array|mixed|null
202
	 */
203
	private function _groupMembers($dnGroup, &$seen = null) {
204
		if ($seen === null) {
205
			$seen = array();
206
		}
207
		$allMembers = array();
208
		if (array_key_exists($dnGroup, $seen)) {
209
			// avoid loops
210
			return array();
211
		}
212
		// used extensively in cron job, caching makes sense for nested groups
213
		$cacheKey = '_groupMembers'.$dnGroup;
214
		$groupMembers = $this->access->connection->getFromCache($cacheKey);
215
		if(!is_null($groupMembers)) {
216
			return $groupMembers;
217
		}
218
		$seen[$dnGroup] = 1;
219
		$members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr,
220
												$this->access->connection->ldapGroupFilter);
0 ignored issues
show
Documentation introduced by
The property ldapGroupFilter does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
221
		if (is_array($members)) {
222
			foreach ($members as $memberDN) {
223
				$allMembers[$memberDN] = 1;
224
				$nestedGroups = $this->access->connection->ldapNestedGroups;
0 ignored issues
show
Documentation introduced by
The property ldapNestedGroups does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
225
				if (!empty($nestedGroups)) {
226
					$subMembers = $this->_groupMembers($memberDN, $seen);
227
					if ($subMembers) {
228
						$allMembers = array_merge($allMembers, $subMembers);
229
					}
230
				}
231
			}
232
		}
233
234
		$allMembers = array_merge($allMembers, $this->getDynamicGroupMembers($dnGroup));
235
236
		$this->access->connection->writeToCache($cacheKey, $allMembers);
237
		return $allMembers;
238
	}
239
240
	/**
241
	 * @param string $DN
242
	 * @param array|null &$seen
243
	 * @return array
244
	 */
245
	private function _getGroupDNsFromMemberOf($DN, &$seen = null) {
246
		if ($seen === null) {
247
			$seen = array();
248
		}
249
		if (array_key_exists($DN, $seen)) {
250
			// avoid loops
251
			return array();
252
		}
253
		$seen[$DN] = 1;
254
		$groups = $this->access->readAttribute($DN, 'memberOf');
255
		if (!is_array($groups)) {
256
			return array();
257
		}
258
		$groups = $this->access->groupsMatchFilter($groups);
259
		$allGroups =  $groups;
260
		$nestedGroups = $this->access->connection->ldapNestedGroups;
0 ignored issues
show
Documentation introduced by
The property ldapNestedGroups does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
261
		if (intval($nestedGroups) === 1) {
262
			foreach ($groups as $group) {
263
				$subGroups = $this->_getGroupDNsFromMemberOf($group, $seen);
264
				$allGroups = array_merge($allGroups, $subGroups);
265
			}
266
		}
267
		return $allGroups;
268
	}
269
270
	/**
271
	 * translates a gidNumber into an ownCloud internal name
272
	 * @param string $gid as given by gidNumber on POSIX LDAP
273
	 * @param string $dn a DN that belongs to the same domain as the group
274
	 * @return string|bool
275
	 */
276
	public function gidNumber2Name($gid, $dn) {
0 ignored issues
show
Unused Code introduced by
The parameter $dn is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
277
		$cacheKey = 'gidNumberToName' . $gid;
278
		$groupName = $this->access->connection->getFromCache($cacheKey);
279
		if(!is_null($groupName) && isset($groupName)) {
280
			return $groupName;
281
		}
282
283
		//we need to get the DN from LDAP
284
		$filter = $this->access->combineFilterWithAnd([
285
			$this->access->connection->ldapGroupFilter,
0 ignored issues
show
Documentation introduced by
The property ldapGroupFilter does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
286
			'objectClass=posixGroup',
287
			$this->access->connection->ldapGidNumber . '=' . $gid
0 ignored issues
show
Documentation introduced by
The property ldapGidNumber does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
288
		]);
289
		$result = $this->access->searchGroups($filter, array('dn'), 1);
290
		if(empty($result)) {
291
			return false;
292
		}
293
		$dn = $result[0]['dn'][0];
294
295
		//and now the group name
296
		//NOTE once we have separate ownCloud group IDs and group names we can
297
		//directly read the display name attribute instead of the DN
298
		$name = $this->access->dn2groupname($dn);
299
300
		$this->access->connection->writeToCache($cacheKey, $name);
301
302
		return $name;
303
	}
304
305
	/**
306
	 * returns the entry's gidNumber
307
	 * @param string $dn
308
	 * @param string $attribute
309
	 * @return string|bool
310
	 */
311 View Code Duplication
	private function getEntryGidNumber($dn, $attribute) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
312
		$value = $this->access->readAttribute($dn, $attribute);
313
		if(is_array($value) && !empty($value)) {
314
			return $value[0];
315
		}
316
		return false;
317
	}
318
319
	/**
320
	 * returns the group's primary ID
321
	 * @param string $dn
322
	 * @return string|bool
323
	 */
324
	public function getGroupGidNumber($dn) {
325
		return $this->getEntryGidNumber($dn, 'gidNumber');
326
	}
327
328
	/**
329
	 * returns the user's gidNumber
330
	 * @param string $dn
331
	 * @return string|bool
332
	 */
333
	public function getUserGidNumber($dn) {
334
		$gidNumber = false;
335
		if($this->access->connection->hasGidNumber) {
336
			$gidNumber = $this->getEntryGidNumber($dn, 'gidNumber');
337
			if($gidNumber === false) {
338
				$this->access->connection->hasGidNumber = false;
339
			}
340
		}
341
		return $gidNumber;
342
	}
343
344
	/**
345
	 * returns a filter for a "users has specific gid" search or count operation
346
	 *
347
	 * @param string $groupDN
348
	 * @param string $search
349
	 * @return string
350
	 * @throws \Exception
351
	 */
352
	private function prepareFilterForUsersHasGidNumber($groupDN, $search = '') {
353
		$groupID = $this->getGroupGidNumber($groupDN);
354
		if($groupID === false) {
355
			throw new \Exception('Not a valid group');
356
		}
357
358
		$filterParts = [];
359
		$filterParts[] = $this->access->getFilterForUserCount();
360
		if ($search !== '') {
361
			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
362
		}
363
		$filterParts[] = $this->access->connection->ldapGidNumber .'=' . $groupID;
0 ignored issues
show
Documentation introduced by
The property ldapGidNumber does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
364
365
		$filter = $this->access->combineFilterWithAnd($filterParts);
366
367
		return $filter;
368
	}
369
370
	/**
371
	 * returns a list of users that have the given group as gid number
372
	 *
373
	 * @param string $groupDN
374
	 * @param string $search
375
	 * @param int $limit
376
	 * @param int $offset
377
	 * @return string[]
378
	 */
379 View Code Duplication
	public function getUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
380
		try {
381
			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
382
			$users = $this->access->fetchListOfUsers(
383
				$filter,
384
				[$this->access->connection->ldapUserDisplayName, 'dn'],
385
				$limit,
386
				$offset
387
			);
388
			return $this->access->nextcloudUserNames($users);
389
		} catch (\Exception $e) {
390
			return [];
391
		}
392
	}
393
394
	/**
395
	 * returns the number of users that have the given group as gid number
396
	 *
397
	 * @param string $groupDN
398
	 * @param string $search
399
	 * @param int $limit
400
	 * @param int $offset
401
	 * @return int
402
	 */
403 View Code Duplication
	public function countUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
404
		try {
405
			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
406
			$users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
407
			return (int)$users;
408
		} catch (\Exception $e) {
409
			return 0;
410
		}
411
	}
412
413
	/**
414
	 * gets the gidNumber of a user
415
	 * @param string $dn
416
	 * @return string
417
	 */
418 View Code Duplication
	public function getUserGroupByGid($dn) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
419
		$groupID = $this->getUserGidNumber($dn);
420
		if($groupID !== false) {
421
			$groupName = $this->gidNumber2Name($groupID, $dn);
0 ignored issues
show
Bug introduced by
It seems like $groupID defined by $this->getUserGidNumber($dn) on line 419 can also be of type boolean; however, OCA\User_LDAP\Group_LDAP::gidNumber2Name() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
422
			if($groupName !== false) {
423
				return $groupName;
424
			}
425
		}
426
427
		return false;
428
	}
429
430
	/**
431
	 * translates a primary group ID into an Nextcloud internal name
432
	 * @param string $gid as given by primaryGroupID on AD
433
	 * @param string $dn a DN that belongs to the same domain as the group
434
	 * @return string|bool
435
	 */
436
	public function primaryGroupID2Name($gid, $dn) {
437
		$cacheKey = 'primaryGroupIDtoName';
438
		$groupNames = $this->access->connection->getFromCache($cacheKey);
439
		if(!is_null($groupNames) && isset($groupNames[$gid])) {
440
			return $groupNames[$gid];
441
		}
442
443
		$domainObjectSid = $this->access->getSID($dn);
444
		if($domainObjectSid === false) {
445
			return false;
446
		}
447
448
		//we need to get the DN from LDAP
449
		$filter = $this->access->combineFilterWithAnd(array(
450
			$this->access->connection->ldapGroupFilter,
0 ignored issues
show
Documentation introduced by
The property ldapGroupFilter does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
451
			'objectsid=' . $domainObjectSid . '-' . $gid
452
		));
453
		$result = $this->access->searchGroups($filter, array('dn'), 1);
454
		if(empty($result)) {
455
			return false;
456
		}
457
		$dn = $result[0]['dn'][0];
458
459
		//and now the group name
460
		//NOTE once we have separate Nextcloud group IDs and group names we can
461
		//directly read the display name attribute instead of the DN
462
		$name = $this->access->dn2groupname($dn);
463
464
		$this->access->connection->writeToCache($cacheKey, $name);
465
466
		return $name;
467
	}
468
469
	/**
470
	 * returns the entry's primary group ID
471
	 * @param string $dn
472
	 * @param string $attribute
473
	 * @return string|bool
474
	 */
475 View Code Duplication
	private function getEntryGroupID($dn, $attribute) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
476
		$value = $this->access->readAttribute($dn, $attribute);
477
		if(is_array($value) && !empty($value)) {
478
			return $value[0];
479
		}
480
		return false;
481
	}
482
483
	/**
484
	 * returns the group's primary ID
485
	 * @param string $dn
486
	 * @return string|bool
487
	 */
488
	public function getGroupPrimaryGroupID($dn) {
489
		return $this->getEntryGroupID($dn, 'primaryGroupToken');
490
	}
491
492
	/**
493
	 * returns the user's primary group ID
494
	 * @param string $dn
495
	 * @return string|bool
496
	 */
497
	public function getUserPrimaryGroupIDs($dn) {
498
		$primaryGroupID = false;
499
		if($this->access->connection->hasPrimaryGroups) {
500
			$primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID');
501
			if($primaryGroupID === false) {
502
				$this->access->connection->hasPrimaryGroups = false;
503
			}
504
		}
505
		return $primaryGroupID;
506
	}
507
508
	/**
509
	 * returns a filter for a "users in primary group" search or count operation
510
	 *
511
	 * @param string $groupDN
512
	 * @param string $search
513
	 * @return string
514
	 * @throws \Exception
515
	 */
516
	private function prepareFilterForUsersInPrimaryGroup($groupDN, $search = '') {
517
		$groupID = $this->getGroupPrimaryGroupID($groupDN);
518
		if($groupID === false) {
519
			throw new \Exception('Not a valid group');
520
		}
521
522
		$filterParts = [];
523
		$filterParts[] = $this->access->getFilterForUserCount();
524
		if ($search !== '') {
525
			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
526
		}
527
		$filterParts[] = 'primaryGroupID=' . $groupID;
528
529
		$filter = $this->access->combineFilterWithAnd($filterParts);
530
531
		return $filter;
532
	}
533
534
	/**
535
	 * returns a list of users that have the given group as primary group
536
	 *
537
	 * @param string $groupDN
538
	 * @param string $search
539
	 * @param int $limit
540
	 * @param int $offset
541
	 * @return string[]
542
	 */
543 View Code Duplication
	public function getUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
544
		try {
545
			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
546
			$users = $this->access->fetchListOfUsers(
547
				$filter,
548
				array($this->access->connection->ldapUserDisplayName, 'dn'),
549
				$limit,
550
				$offset
551
			);
552
			return $this->access->nextcloudUserNames($users);
553
		} catch (\Exception $e) {
554
			return array();
555
		}
556
	}
557
558
	/**
559
	 * returns the number of users that have the given group as primary group
560
	 *
561
	 * @param string $groupDN
562
	 * @param string $search
563
	 * @param int $limit
564
	 * @param int $offset
565
	 * @return int
566
	 */
567 View Code Duplication
	public function countUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
568
		try {
569
			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
570
			$users = $this->access->countUsers($filter, array('dn'), $limit, $offset);
571
			return (int)$users;
572
		} catch (\Exception $e) {
573
			return 0;
574
		}
575
	}
576
577
	/**
578
	 * gets the primary group of a user
579
	 * @param string $dn
580
	 * @return string
581
	 */
582 View Code Duplication
	public function getUserPrimaryGroup($dn) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
583
		$groupID = $this->getUserPrimaryGroupIDs($dn);
584
		if($groupID !== false) {
585
			$groupName = $this->primaryGroupID2Name($groupID, $dn);
0 ignored issues
show
Bug introduced by
It seems like $groupID defined by $this->getUserPrimaryGroupIDs($dn) on line 583 can also be of type boolean; however, OCA\User_LDAP\Group_LDAP::primaryGroupID2Name() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
586
			if($groupName !== false) {
587
				return $groupName;
588
			}
589
		}
590
591
		return false;
592
	}
593
594
	/**
595
	 * Get all groups a user belongs to
596
	 * @param string $uid Name of the user
597
	 * @return array with group names
598
	 *
599
	 * This function fetches all groups a user belongs to. It does not check
600
	 * if the user exists at all.
601
	 *
602
	 * This function includes groups based on dynamic group membership.
603
	 */
604
	public function getUserGroups($uid) {
605
		if(!$this->enabled) {
606
			return array();
607
		}
608
		$cacheKey = 'getUserGroups'.$uid;
609
		$userGroups = $this->access->connection->getFromCache($cacheKey);
610
		if(!is_null($userGroups)) {
611
			return $userGroups;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $userGroups; (object|integer|double|string|array|boolean) is incompatible with the return type declared by the interface OCP\GroupInterface::getUserGroups of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
612
		}
613
		$userDN = $this->access->username2dn($uid);
614
		if(!$userDN) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $userDN of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
615
			$this->access->connection->writeToCache($cacheKey, array());
616
			return array();
617
		}
618
619
		$groups = [];
620
		$primaryGroup = $this->getUserPrimaryGroup($userDN);
621
		$gidGroupName = $this->getUserGroupByGid($userDN);
622
623
		$dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
0 ignored issues
show
Documentation introduced by
The property ldapDynamicGroupMemberURL does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
624
625
		if (!empty($dynamicGroupMemberURL)) {
626
			// look through dynamic groups to add them to the result array if needed
627
			$groupsToMatch = $this->access->fetchListOfGroups(
628
				$this->access->connection->ldapGroupFilter,array('dn',$dynamicGroupMemberURL));
0 ignored issues
show
Documentation introduced by
The property ldapGroupFilter does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
629
			foreach($groupsToMatch as $dynamicGroup) {
630
				if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) {
631
					continue;
632
				}
633
				$pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '(');
634
				if ($pos !== false) {
635
					$memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0],$pos);
636
					// apply filter via ldap search to see if this user is in this
637
					// dynamic group
638
					$userMatch = $this->access->readAttribute(
639
						$userDN,
640
						$this->access->connection->ldapUserDisplayName,
641
						$memberUrlFilter
642
					);
643
					if ($userMatch !== false) {
644
						// match found so this user is in this group
645
						$groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]);
646
						if(is_string($groupName)) {
647
							// be sure to never return false if the dn could not be
648
							// resolved to a name, for whatever reason.
649
							$groups[] = $groupName;
650
						}
651
					}
652
				} else {
653
					\OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
654
						'of group ' . print_r($dynamicGroup, true), \OCP\Util::DEBUG);
655
				}
656
			}
657
		}
658
659
		// if possible, read out membership via memberOf. It's far faster than
660
		// performing a search, which still is a fallback later.
661
		// memberof doesn't support memberuid, so skip it here.
662
		if(intval($this->access->connection->hasMemberOfFilterSupport) === 1
0 ignored issues
show
Documentation introduced by
The property hasMemberOfFilterSupport does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
663
			&& intval($this->access->connection->useMemberOfToDetectMembership) === 1
0 ignored issues
show
Documentation introduced by
The property useMemberOfToDetectMembership does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
664
		    && strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid'
665
		    ) {
666
			$groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
667
			if (is_array($groupDNs)) {
668
				foreach ($groupDNs as $dn) {
669
					$groupName = $this->access->dn2groupname($dn);
670
					if(is_string($groupName)) {
671
						// be sure to never return false if the dn could not be
672
						// resolved to a name, for whatever reason.
673
						$groups[] = $groupName;
674
					}
675
				}
676
			}
677
678
			if($primaryGroup !== false) {
679
				$groups[] = $primaryGroup;
680
			}
681
			if($gidGroupName !== false) {
682
				$groups[] = $gidGroupName;
683
			}
684
			$this->access->connection->writeToCache($cacheKey, $groups);
685
			return $groups;
686
		}
687
688
		//uniqueMember takes DN, memberuid the uid, so we need to distinguish
689
		if((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember')
690
			|| (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member')
691
		) {
692
			$uid = $userDN;
693
		} else if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
694
			$result = $this->access->readAttribute($userDN, 'uid');
695
			if ($result === false) {
696
				\OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on '.
697
					$this->access->connection->ldapHost, \OCP\Util::DEBUG);
698
			}
699
			$uid = $result[0];
700
		} else {
701
			// just in case
702
			$uid = $userDN;
703
		}
704
705
		if(isset($this->cachedGroupsByMember[$uid])) {
706
			$groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
707
		} else {
708
			$groupsByMember = array_values($this->getGroupsByMember($uid));
709
			$groupsByMember = $this->access->nextcloudGroupNames($groupsByMember);
710
			$this->cachedGroupsByMember[$uid] = $groupsByMember;
711
			$groups = array_merge($groups, $groupsByMember);
712
		}
713
714
		if($primaryGroup !== false) {
715
			$groups[] = $primaryGroup;
716
		}
717
		if($gidGroupName !== false) {
718
			$groups[] = $gidGroupName;
719
		}
720
721
		$groups = array_unique($groups, SORT_LOCALE_STRING);
722
		$this->access->connection->writeToCache($cacheKey, $groups);
723
724
		return $groups;
725
	}
726
727
	/**
728
	 * @param string $dn
729
	 * @param array|null &$seen
730
	 * @return array
731
	 */
732
	private function getGroupsByMember($dn, &$seen = null) {
733
		if ($seen === null) {
734
			$seen = array();
735
		}
736
		$allGroups = array();
737
		if (array_key_exists($dn, $seen)) {
738
			// avoid loops
739
			return array();
740
		}
741
		$seen[$dn] = true;
742
		$filter = $this->access->combineFilterWithAnd(array(
743
			$this->access->connection->ldapGroupFilter,
0 ignored issues
show
Documentation introduced by
The property ldapGroupFilter does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
744
			$this->access->connection->ldapGroupMemberAssocAttr.'='.$dn
745
		));
746
		$groups = $this->access->fetchListOfGroups($filter,
747
			array($this->access->connection->ldapGroupDisplayName, 'dn'));
0 ignored issues
show
Documentation introduced by
The property ldapGroupDisplayName does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
748
		if (is_array($groups)) {
749
			foreach ($groups as $groupobj) {
750
				$groupDN = $groupobj['dn'][0];
751
				$allGroups[$groupDN] = $groupobj;
752
				$nestedGroups = $this->access->connection->ldapNestedGroups;
0 ignored issues
show
Documentation introduced by
The property ldapNestedGroups does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
753
				if (!empty($nestedGroups)) {
754
					$supergroups = $this->getGroupsByMember($groupDN, $seen);
755
					if (is_array($supergroups) && (count($supergroups)>0)) {
756
						$allGroups = array_merge($allGroups, $supergroups);
757
					}
758
				}
759
			}
760
		}
761
		return $allGroups;
762
	}
763
764
	/**
765
	 * get a list of all users in a group
766
	 *
767
	 * @param string $gid
768
	 * @param string $search
769
	 * @param int $limit
770
	 * @param int $offset
771
	 * @return array with user ids
772
	 */
773
	public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
774
		if(!$this->enabled) {
775
			return array();
776
		}
777
		if(!$this->groupExists($gid)) {
778
			return array();
779
		}
780
		$search = $this->access->escapeFilterPart($search, true);
781
		$cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
782
		// check for cache of the exact query
783
		$groupUsers = $this->access->connection->getFromCache($cacheKey);
784
		if(!is_null($groupUsers)) {
785
			return $groupUsers;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $groupUsers; (object|integer|double|string|array|boolean) is incompatible with the return type declared by the interface OCP\GroupInterface::usersInGroup of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
786
		}
787
788
		// check for cache of the query without limit and offset
789
		$groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search);
790
		if(!is_null($groupUsers)) {
791
			$groupUsers = array_slice($groupUsers, $offset, $limit);
792
			$this->access->connection->writeToCache($cacheKey, $groupUsers);
793
			return $groupUsers;
794
		}
795
796
		if($limit === -1) {
797
			$limit = null;
798
		}
799
		$groupDN = $this->access->groupname2dn($gid);
800
		if(!$groupDN) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $groupDN of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
801
			// group couldn't be found, return empty resultset
802
			$this->access->connection->writeToCache($cacheKey, array());
803
			return array();
804
		}
805
806
		$primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset);
807
		$posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset);
808
		$members = array_keys($this->_groupMembers($groupDN));
809
		if(!$members && empty($posixGroupUsers) && empty($primaryUsers)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $members of type array<integer|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
810
			//in case users could not be retrieved, return empty result set
811
			$this->access->connection->writeToCache($cacheKey, []);
812
			return [];
813
		}
814
815
		$groupUsers = array();
816
		$isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
817
		$attrs = $this->access->userManager->getAttributes(true);
818
		foreach($members as $member) {
819
			if($isMemberUid) {
820
				//we got uids, need to get their DNs to 'translate' them to user names
821
				$filter = $this->access->combineFilterWithAnd(array(
822
					str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
0 ignored issues
show
Documentation introduced by
The property ldapLoginFilter does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
823
					$this->access->getFilterPartForUserSearch($search)
824
				));
825
				$ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
826
				if(count($ldap_users) < 1) {
827
					continue;
828
				}
829
				$groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
830
			} else {
831
				//we got DNs, check if we need to filter by search or we can give back all of them
832
				if ($search !== '') {
833
					if(!$this->access->readAttribute($member,
834
						$this->access->connection->ldapUserDisplayName,
835
						$this->access->getFilterPartForUserSearch($search))) {
836
						continue;
837
					}
838
				}
839
				// dn2username will also check if the users belong to the allowed base
840
				if($ocname = $this->access->dn2username($member)) {
841
					$groupUsers[] = $ocname;
842
				}
843
			}
844
		}
845
846
		$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers));
847
		natsort($groupUsers);
848
		$this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
849
		$groupUsers = array_slice($groupUsers, $offset, $limit);
850
851
		$this->access->connection->writeToCache($cacheKey, $groupUsers);
852
853
		return $groupUsers;
854
	}
855
856
	/**
857
	 * returns the number of users in a group, who match the search term
858
	 * @param string $gid the internal group name
859
	 * @param string $search optional, a search string
860
	 * @return int|bool
861
	 */
862
	public function countUsersInGroup($gid, $search = '') {
863
		$cacheKey = 'countUsersInGroup-'.$gid.'-'.$search;
864
		if(!$this->enabled || !$this->groupExists($gid)) {
865
			return false;
866
		}
867
		$groupUsers = $this->access->connection->getFromCache($cacheKey);
868
		if(!is_null($groupUsers)) {
869
			return $groupUsers;
870
		}
871
872
		$groupDN = $this->access->groupname2dn($gid);
873
		if(!$groupDN) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $groupDN of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
874
			// group couldn't be found, return empty result set
875
			$this->access->connection->writeToCache($cacheKey, false);
876
			return false;
877
		}
878
879
		$members = array_keys($this->_groupMembers($groupDN));
880
		$primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, '');
881
		if(!$members && $primaryUserCount === 0) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $members of type array<integer|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
882
			//in case users could not be retrieved, return empty result set
883
			$this->access->connection->writeToCache($cacheKey, false);
884
			return false;
885
		}
886
887
		if ($search === '') {
888
			$groupUsers = count($members) + $primaryUserCount;
889
			$this->access->connection->writeToCache($cacheKey, $groupUsers);
890
			return $groupUsers;
891
		}
892
		$search = $this->access->escapeFilterPart($search, true);
893
		$isMemberUid =
894
			(strtolower($this->access->connection->ldapGroupMemberAssocAttr)
895
			=== 'memberuid');
896
897
		//we need to apply the search filter
898
		//alternatives that need to be checked:
899
		//a) get all users by search filter and array_intersect them
900
		//b) a, but only when less than 1k 10k ?k users like it is
901
		//c) put all DNs|uids in a LDAP filter, combine with the search string
902
		//   and let it count.
903
		//For now this is not important, because the only use of this method
904
		//does not supply a search string
905
		$groupUsers = array();
906
		foreach($members as $member) {
907
			if($isMemberUid) {
908
				//we got uids, need to get their DNs to 'translate' them to user names
909
				$filter = $this->access->combineFilterWithAnd(array(
910
					str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
0 ignored issues
show
Documentation introduced by
The property ldapLoginFilter does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
911
					$this->access->getFilterPartForUserSearch($search)
912
				));
913
				$ldap_users = $this->access->fetchListOfUsers($filter, 'dn', 1);
914
				if(count($ldap_users) < 1) {
915
					continue;
916
				}
917
				$groupUsers[] = $this->access->dn2username($ldap_users[0]);
918
			} else {
919
				//we need to apply the search filter now
920
				if(!$this->access->readAttribute($member,
921
					$this->access->connection->ldapUserDisplayName,
922
					$this->access->getFilterPartForUserSearch($search))) {
923
					continue;
924
				}
925
				// dn2username will also check if the users belong to the allowed base
926
				if($ocname = $this->access->dn2username($member)) {
927
					$groupUsers[] = $ocname;
928
				}
929
			}
930
		}
931
932
		//and get users that have the group as primary
933
		$primaryUsers = $this->countUsersInPrimaryGroup($groupDN, $search);
934
935
		return count($groupUsers) + $primaryUsers;
936
	}
937
938
	/**
939
	 * get a list of all groups
940
	 *
941
	 * @param string $search
942
	 * @param $limit
943
	 * @param int $offset
944
	 * @return array with group names
945
	 *
946
	 * Returns a list with all groups (used by getGroups)
947
	 */
948
	protected function getGroupsChunk($search = '', $limit = -1, $offset = 0) {
949
		if(!$this->enabled) {
950
			return array();
951
		}
952
		$cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
953
954
		//Check cache before driving unnecessary searches
955
		\OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, \OCP\Util::DEBUG);
956
		$ldap_groups = $this->access->connection->getFromCache($cacheKey);
957
		if(!is_null($ldap_groups)) {
958
			return $ldap_groups;
959
		}
960
961
		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
962
		// error. With a limit of 0, we get 0 results. So we pass null.
963
		if($limit <= 0) {
964
			$limit = null;
965
		}
966
		$filter = $this->access->combineFilterWithAnd(array(
967
			$this->access->connection->ldapGroupFilter,
0 ignored issues
show
Documentation introduced by
The property ldapGroupFilter does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
968
			$this->access->getFilterPartForGroupSearch($search)
969
		));
970
		\OCP\Util::writeLog('user_ldap', 'getGroups Filter '.$filter, \OCP\Util::DEBUG);
971
		$ldap_groups = $this->access->fetchListOfGroups($filter,
972
				array($this->access->connection->ldapGroupDisplayName, 'dn'),
0 ignored issues
show
Documentation introduced by
The property ldapGroupDisplayName does not exist on object<OCA\User_LDAP\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
973
				$limit,
974
				$offset);
975
		$ldap_groups = $this->access->nextcloudGroupNames($ldap_groups);
976
977
		$this->access->connection->writeToCache($cacheKey, $ldap_groups);
978
		return $ldap_groups;
979
	}
980
981
	/**
982
	 * get a list of all groups using a paged search
983
	 *
984
	 * @param string $search
985
	 * @param int $limit
986
	 * @param int $offset
987
	 * @return array with group names
988
	 *
989
	 * Returns a list with all groups
990
	 * Uses a paged search if available to override a
991
	 * server side search limit.
992
	 * (active directory has a limit of 1000 by default)
993
	 */
994
	public function getGroups($search = '', $limit = -1, $offset = 0) {
995
		if(!$this->enabled) {
996
			return array();
997
		}
998
		$search = $this->access->escapeFilterPart($search, true);
999
		$pagingSize = intval($this->access->connection->ldapPagingSize);
1000
		if (!$this->access->connection->hasPagedResultSupport || $pagingSize <= 0) {
1001
			return $this->getGroupsChunk($search, $limit, $offset);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getGroupsC...arch, $limit, $offset); (array|object|integer|double|string|boolean) is incompatible with the return type declared by the interface OCP\GroupInterface::getGroups of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1002
		}
1003
		$maxGroups = 100000; // limit max results (just for safety reasons)
1004
		if ($limit > -1) {
1005
		   $overallLimit = min($limit + $offset, $maxGroups);
1006
		} else {
1007
		   $overallLimit = $maxGroups;
1008
		}
1009
		$chunkOffset = $offset;
1010
		$allGroups = array();
1011
		while ($chunkOffset < $overallLimit) {
1012
			$chunkLimit = min($pagingSize, $overallLimit - $chunkOffset);
1013
			$ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset);
1014
			$nread = count($ldapGroups);
1015
			\OCP\Util::writeLog('user_ldap', 'getGroups('.$search.'): read '.$nread.' at offset '.$chunkOffset.' (limit: '.$chunkLimit.')', \OCP\Util::DEBUG);
1016
			if ($nread) {
1017
				$allGroups = array_merge($allGroups, $ldapGroups);
1018
				$chunkOffset += $nread;
1019
			}
1020
			if ($nread < $chunkLimit) {
1021
				break;
1022
			}
1023
		}
1024
		return $allGroups;
1025
	}
1026
1027
	/**
1028
	 * @param string $group
1029
	 * @return bool
1030
	 */
1031
	public function groupMatchesFilter($group) {
1032
		return (strripos($group, $this->groupSearch) !== false);
0 ignored issues
show
Bug introduced by
The property groupSearch does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1033
	}
1034
1035
	/**
1036
	 * check if a group exists
1037
	 * @param string $gid
1038
	 * @return bool
1039
	 */
1040
	public function groupExists($gid) {
1041
		$groupExists = $this->access->connection->getFromCache('groupExists'.$gid);
1042
		if(!is_null($groupExists)) {
1043
			return (bool)$groupExists;
1044
		}
1045
1046
		//getting dn, if false the group does not exist. If dn, it may be mapped
1047
		//only, requires more checking.
1048
		$dn = $this->access->groupname2dn($gid);
1049
		if(!$dn) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dn of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1050
			$this->access->connection->writeToCache('groupExists'.$gid, false);
1051
			return false;
1052
		}
1053
1054
		//if group really still exists, we will be able to read its objectclass
1055 View Code Duplication
		if(!is_array($this->access->readAttribute($dn, ''))) {
1056
			$this->access->connection->writeToCache('groupExists'.$gid, false);
1057
			return false;
1058
		}
1059
1060
		$this->access->connection->writeToCache('groupExists'.$gid, true);
1061
		return true;
1062
	}
1063
1064
	/**
1065
	* Check if backend implements actions
1066
	* @param int $actions bitwise-or'ed actions
1067
	* @return boolean
1068
	*
1069
	* Returns the supported actions as int to be
1070
	* compared with OC_USER_BACKEND_CREATE_USER etc.
1071
	*/
1072
	public function implementsActions($actions) {
1073
		return (bool)(\OC\Group\Backend::COUNT_USERS & $actions);
1074
	}
1075
1076
	/**
1077
	 * Return access for LDAP interaction.
1078
	 * @return Access instance of Access for LDAP interaction
1079
	 */
1080
	public function getLDAPAccess() {
1081
		return $this->access;
1082
	}
1083
}
1084