Completed
Push — master ( 0256f6...5411d6 )
by Morris
22:12 queued 06:46
created

Group_LDAP::createGroup()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 5
Ratio 45.45 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 5
loc 11
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
use OCP\GroupInterface;
43
44
class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLDAP {
45
	protected $enabled = false;
46
47
	/**
48
	 * @var string[] $cachedGroupMembers array of users with gid as key
49
	 */
50
	protected $cachedGroupMembers;
51
52
	/**
53
	 * @var string[] $cachedGroupsByMember array of groups with uid as key
54
	 */
55
	protected $cachedGroupsByMember;
56
57
	/** @var GroupPluginManager */
58
	protected $groupPluginManager;
59
60
	public function __construct(Access $access, GroupPluginManager $groupPluginManager) {
61
		parent::__construct($access);
62
		$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...
63
		$gassoc = $this->access->connection->ldapGroupMemberAssocAttr;
64
		if(!empty($filter) && !empty($gassoc)) {
65
			$this->enabled = true;
66
		}
67
68
		$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...
69
		$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...
70
		$this->groupPluginManager = $groupPluginManager;
71
	}
72
73
	/**
74
	 * is user in group?
75
	 * @param string $uid uid of the user
76
	 * @param string $gid gid of the group
77
	 * @return bool
78
	 *
79
	 * Checks whether the user is member of a group or not.
80
	 */
81
	public function inGroup($uid, $gid) {
82
		if(!$this->enabled) {
83
			return false;
84
		}
85
		$cacheKey = 'inGroup'.$uid.':'.$gid;
86
		$inGroup = $this->access->connection->getFromCache($cacheKey);
87
		if(!is_null($inGroup)) {
88
			return (bool)$inGroup;
89
		}
90
91
		$userDN = $this->access->username2dn($uid);
92
93
		if(isset($this->cachedGroupMembers[$gid])) {
94
			$isInGroup = in_array($userDN, $this->cachedGroupMembers[$gid]);
95
			return $isInGroup;
96
		}
97
98
		$cacheKeyMembers = 'inGroup-members:'.$gid;
99
		$members = $this->access->connection->getFromCache($cacheKeyMembers);
100
		if(!is_null($members)) {
101
			$this->cachedGroupMembers[$gid] = $members;
102
			$isInGroup = in_array($userDN, $members);
103
			$this->access->connection->writeToCache($cacheKey, $isInGroup);
104
			return $isInGroup;
105
		}
106
107
		$groupDN = $this->access->groupname2dn($gid);
108
		// just in case
109 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...
110
			$this->access->connection->writeToCache($cacheKey, false);
111
			return false;
112
		}
113
114
		//check primary group first
115 View Code Duplication
		if($gid === $this->getUserPrimaryGroup($userDN)) {
116
			$this->access->connection->writeToCache($cacheKey, true);
117
			return true;
118
		}
119
120
		//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
121
		$members = $this->_groupMembers($groupDN);
122
		$members = array_keys($members); // uids are returned as keys
123
		if(!is_array($members) || count($members) === 0) {
124
			$this->access->connection->writeToCache($cacheKey, false);
125
			return false;
126
		}
127
128
		//extra work if we don't get back user DNs
129
		if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
130
			$dns = array();
131
			$filterParts = array();
132
			$bytes = 0;
133
			foreach($members as $mid) {
134
				$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...
135
				$filterParts[] = $filter;
136
				$bytes += strlen($filter);
137
				if($bytes >= 9000000) {
138
					// AD has a default input buffer of 10 MB, we do not want
139
					// to take even the chance to exceed it
140
					$filter = $this->access->combineFilterWithOr($filterParts);
141
					$bytes = 0;
142
					$filterParts = array();
143
					$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
144
					$dns = array_merge($dns, $users);
145
				}
146
			}
147
			if(count($filterParts) > 0) {
148
				$filter = $this->access->combineFilterWithOr($filterParts);
149
				$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
150
				$dns = array_merge($dns, $users);
151
			}
152
			$members = $dns;
153
		}
154
155
		$isInGroup = in_array($userDN, $members);
156
		$this->access->connection->writeToCache($cacheKey, $isInGroup);
157
		$this->access->connection->writeToCache($cacheKeyMembers, $members);
158
		$this->cachedGroupMembers[$gid] = $members;
159
160
		return $isInGroup;
161
	}
162
163
	/**
164
	 * @param string $dnGroup
165
	 * @return array
166
	 *
167
	 * For a group that has user membership defined by an LDAP search url attribute returns the users
168
	 * that match the search url otherwise returns an empty array.
169
	 */
170
	public function getDynamicGroupMembers($dnGroup) {
171
		$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...
172
173
		if (empty($dynamicGroupMemberURL)) {
174
			return array();
175
		}
176
177
		$dynamicMembers = array();
178
		$memberURLs = $this->access->readAttribute(
179
			$dnGroup,
180
			$dynamicGroupMemberURL,
181
			$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...
182
		);
183
		if ($memberURLs !== false) {
184
			// this group has the 'memberURL' attribute so this is a dynamic group
185
			// example 1: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(o=HeadOffice)
186
			// example 2: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(&(o=HeadOffice)(uidNumber>=500))
187
			$pos = strpos($memberURLs[0], '(');
188
			if ($pos !== false) {
189
				$memberUrlFilter = substr($memberURLs[0], $pos);
190
				$foundMembers = $this->access->searchUsers($memberUrlFilter,'dn');
191
				$dynamicMembers = array();
192
				foreach($foundMembers as $value) {
193
					$dynamicMembers[$value['dn'][0]] = 1;
194
				}
195
			} else {
196
				\OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
197
					'of group ' . $dnGroup, \OCP\Util::DEBUG);
198
			}
199
		}
200
		return $dynamicMembers;
201
	}
202
203
	/**
204
	 * @param string $dnGroup
205
	 * @param array|null &$seen
206
	 * @return array|mixed|null
207
	 */
208
	private function _groupMembers($dnGroup, &$seen = null) {
209
		if ($seen === null) {
210
			$seen = array();
211
		}
212
		$allMembers = array();
213
		if (array_key_exists($dnGroup, $seen)) {
214
			// avoid loops
215
			return array();
216
		}
217
		// used extensively in cron job, caching makes sense for nested groups
218
		$cacheKey = '_groupMembers'.$dnGroup;
219
		$groupMembers = $this->access->connection->getFromCache($cacheKey);
220
		if(!is_null($groupMembers)) {
221
			return $groupMembers;
222
		}
223
		$seen[$dnGroup] = 1;
224
		$members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr,
225
												$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...
226
		if (is_array($members)) {
227
			foreach ($members as $memberDN) {
228
				$allMembers[$memberDN] = 1;
229
				$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...
230
				if (!empty($nestedGroups)) {
231
					$subMembers = $this->_groupMembers($memberDN, $seen);
232
					if ($subMembers) {
233
						$allMembers = array_merge($allMembers, $subMembers);
234
					}
235
				}
236
			}
237
		}
238
239
		$allMembers = array_merge($allMembers, $this->getDynamicGroupMembers($dnGroup));
240
241
		$this->access->connection->writeToCache($cacheKey, $allMembers);
242
		return $allMembers;
243
	}
244
245
	/**
246
	 * @param string $DN
247
	 * @param array|null &$seen
248
	 * @return array
249
	 */
250
	private function _getGroupDNsFromMemberOf($DN, &$seen = null) {
251
		if ($seen === null) {
252
			$seen = array();
253
		}
254
		if (array_key_exists($DN, $seen)) {
255
			// avoid loops
256
			return array();
257
		}
258
		$seen[$DN] = 1;
259
		$groups = $this->access->readAttribute($DN, 'memberOf');
260
		if (!is_array($groups)) {
261
			return array();
262
		}
263
		$groups = $this->access->groupsMatchFilter($groups);
264
		$allGroups =  $groups;
265
		$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...
266
		if (intval($nestedGroups) === 1) {
267
			foreach ($groups as $group) {
268
				$subGroups = $this->_getGroupDNsFromMemberOf($group, $seen);
269
				$allGroups = array_merge($allGroups, $subGroups);
270
			}
271
		}
272
		return $allGroups;
273
	}
274
275
	/**
276
	 * translates a gidNumber into an ownCloud internal name
277
	 * @param string $gid as given by gidNumber on POSIX LDAP
278
	 * @param string $dn a DN that belongs to the same domain as the group
279
	 * @return string|bool
280
	 */
281
	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...
282
		$cacheKey = 'gidNumberToName' . $gid;
283
		$groupName = $this->access->connection->getFromCache($cacheKey);
284
		if(!is_null($groupName) && isset($groupName)) {
285
			return $groupName;
286
		}
287
288
		//we need to get the DN from LDAP
289
		$filter = $this->access->combineFilterWithAnd([
290
			$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...
291
			'objectClass=posixGroup',
292
			$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...
293
		]);
294
		$result = $this->access->searchGroups($filter, array('dn'), 1);
295
		if(empty($result)) {
296
			return false;
297
		}
298
		$dn = $result[0]['dn'][0];
299
300
		//and now the group name
301
		//NOTE once we have separate ownCloud group IDs and group names we can
302
		//directly read the display name attribute instead of the DN
303
		$name = $this->access->dn2groupname($dn);
304
305
		$this->access->connection->writeToCache($cacheKey, $name);
306
307
		return $name;
308
	}
309
310
	/**
311
	 * returns the entry's gidNumber
312
	 * @param string $dn
313
	 * @param string $attribute
314
	 * @return string|bool
315
	 */
316 View Code Duplication
	private function getEntryGidNumber($dn, $attribute) {
317
		$value = $this->access->readAttribute($dn, $attribute);
318
		if(is_array($value) && !empty($value)) {
319
			return $value[0];
320
		}
321
		return false;
322
	}
323
324
	/**
325
	 * returns the group's primary ID
326
	 * @param string $dn
327
	 * @return string|bool
328
	 */
329
	public function getGroupGidNumber($dn) {
330
		return $this->getEntryGidNumber($dn, 'gidNumber');
331
	}
332
333
	/**
334
	 * returns the user's gidNumber
335
	 * @param string $dn
336
	 * @return string|bool
337
	 */
338
	public function getUserGidNumber($dn) {
339
		$gidNumber = false;
340
		if($this->access->connection->hasGidNumber) {
341
			$gidNumber = $this->getEntryGidNumber($dn, $this->access->connection->ldapGidNumber);
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...
342
			if($gidNumber === false) {
343
				$this->access->connection->hasGidNumber = false;
344
			}
345
		}
346
		return $gidNumber;
347
	}
348
349
	/**
350
	 * returns a filter for a "users has specific gid" search or count operation
351
	 *
352
	 * @param string $groupDN
353
	 * @param string $search
354
	 * @return string
355
	 * @throws \Exception
356
	 */
357
	private function prepareFilterForUsersHasGidNumber($groupDN, $search = '') {
358
		$groupID = $this->getGroupGidNumber($groupDN);
359
		if($groupID === false) {
360
			throw new \Exception('Not a valid group');
361
		}
362
363
		$filterParts = [];
364
		$filterParts[] = $this->access->getFilterForUserCount();
365
		if ($search !== '') {
366
			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
367
		}
368
		$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...
369
370
		$filter = $this->access->combineFilterWithAnd($filterParts);
371
372
		return $filter;
373
	}
374
375
	/**
376
	 * returns a list of users that have the given group as gid number
377
	 *
378
	 * @param string $groupDN
379
	 * @param string $search
380
	 * @param int $limit
381
	 * @param int $offset
382
	 * @return string[]
383
	 */
384 View Code Duplication
	public function getUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
385
		try {
386
			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
387
			$users = $this->access->fetchListOfUsers(
388
				$filter,
389
				[$this->access->connection->ldapUserDisplayName, 'dn'],
390
				$limit,
391
				$offset
392
			);
393
			return $this->access->nextcloudUserNames($users);
394
		} catch (\Exception $e) {
395
			return [];
396
		}
397
	}
398
399
	/**
400
	 * returns the number of users that have the given group as gid number
401
	 *
402
	 * @param string $groupDN
403
	 * @param string $search
404
	 * @param int $limit
405
	 * @param int $offset
406
	 * @return int
407
	 */
408 View Code Duplication
	public function countUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
409
		try {
410
			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
411
			$users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
412
			return (int)$users;
413
		} catch (\Exception $e) {
414
			return 0;
415
		}
416
	}
417
418
	/**
419
	 * gets the gidNumber of a user
420
	 * @param string $dn
421
	 * @return string
422
	 */
423 View Code Duplication
	public function getUserGroupByGid($dn) {
424
		$groupID = $this->getUserGidNumber($dn);
425
		if($groupID !== false) {
426
			$groupName = $this->gidNumber2Name($groupID, $dn);
0 ignored issues
show
Bug introduced by
It seems like $groupID defined by $this->getUserGidNumber($dn) on line 424 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...
427
			if($groupName !== false) {
428
				return $groupName;
429
			}
430
		}
431
432
		return false;
433
	}
434
435
	/**
436
	 * translates a primary group ID into an Nextcloud internal name
437
	 * @param string $gid as given by primaryGroupID on AD
438
	 * @param string $dn a DN that belongs to the same domain as the group
439
	 * @return string|bool
440
	 */
441
	public function primaryGroupID2Name($gid, $dn) {
442
		$cacheKey = 'primaryGroupIDtoName';
443
		$groupNames = $this->access->connection->getFromCache($cacheKey);
444
		if(!is_null($groupNames) && isset($groupNames[$gid])) {
445
			return $groupNames[$gid];
446
		}
447
448
		$domainObjectSid = $this->access->getSID($dn);
449
		if($domainObjectSid === false) {
450
			return false;
451
		}
452
453
		//we need to get the DN from LDAP
454
		$filter = $this->access->combineFilterWithAnd(array(
455
			$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...
456
			'objectsid=' . $domainObjectSid . '-' . $gid
457
		));
458
		$result = $this->access->searchGroups($filter, array('dn'), 1);
459
		if(empty($result)) {
460
			return false;
461
		}
462
		$dn = $result[0]['dn'][0];
463
464
		//and now the group name
465
		//NOTE once we have separate Nextcloud group IDs and group names we can
466
		//directly read the display name attribute instead of the DN
467
		$name = $this->access->dn2groupname($dn);
468
469
		$this->access->connection->writeToCache($cacheKey, $name);
470
471
		return $name;
472
	}
473
474
	/**
475
	 * returns the entry's primary group ID
476
	 * @param string $dn
477
	 * @param string $attribute
478
	 * @return string|bool
479
	 */
480 View Code Duplication
	private function getEntryGroupID($dn, $attribute) {
481
		$value = $this->access->readAttribute($dn, $attribute);
482
		if(is_array($value) && !empty($value)) {
483
			return $value[0];
484
		}
485
		return false;
486
	}
487
488
	/**
489
	 * returns the group's primary ID
490
	 * @param string $dn
491
	 * @return string|bool
492
	 */
493
	public function getGroupPrimaryGroupID($dn) {
494
		return $this->getEntryGroupID($dn, 'primaryGroupToken');
495
	}
496
497
	/**
498
	 * returns the user's primary group ID
499
	 * @param string $dn
500
	 * @return string|bool
501
	 */
502
	public function getUserPrimaryGroupIDs($dn) {
503
		$primaryGroupID = false;
504
		if($this->access->connection->hasPrimaryGroups) {
505
			$primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID');
506
			if($primaryGroupID === false) {
507
				$this->access->connection->hasPrimaryGroups = false;
508
			}
509
		}
510
		return $primaryGroupID;
511
	}
512
513
	/**
514
	 * returns a filter for a "users in primary group" search or count operation
515
	 *
516
	 * @param string $groupDN
517
	 * @param string $search
518
	 * @return string
519
	 * @throws \Exception
520
	 */
521
	private function prepareFilterForUsersInPrimaryGroup($groupDN, $search = '') {
522
		$groupID = $this->getGroupPrimaryGroupID($groupDN);
523
		if($groupID === false) {
524
			throw new \Exception('Not a valid group');
525
		}
526
527
		$filterParts = [];
528
		$filterParts[] = $this->access->getFilterForUserCount();
529
		if ($search !== '') {
530
			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
531
		}
532
		$filterParts[] = 'primaryGroupID=' . $groupID;
533
534
		$filter = $this->access->combineFilterWithAnd($filterParts);
535
536
		return $filter;
537
	}
538
539
	/**
540
	 * returns a list of users that have the given group as primary group
541
	 *
542
	 * @param string $groupDN
543
	 * @param string $search
544
	 * @param int $limit
545
	 * @param int $offset
546
	 * @return string[]
547
	 */
548 View Code Duplication
	public function getUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
549
		try {
550
			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
551
			$users = $this->access->fetchListOfUsers(
552
				$filter,
553
				array($this->access->connection->ldapUserDisplayName, 'dn'),
554
				$limit,
555
				$offset
556
			);
557
			return $this->access->nextcloudUserNames($users);
558
		} catch (\Exception $e) {
559
			return array();
560
		}
561
	}
562
563
	/**
564
	 * returns the number of users that have the given group as primary group
565
	 *
566
	 * @param string $groupDN
567
	 * @param string $search
568
	 * @param int $limit
569
	 * @param int $offset
570
	 * @return int
571
	 */
572 View Code Duplication
	public function countUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
573
		try {
574
			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
575
			$users = $this->access->countUsers($filter, array('dn'), $limit, $offset);
576
			return (int)$users;
577
		} catch (\Exception $e) {
578
			return 0;
579
		}
580
	}
581
582
	/**
583
	 * gets the primary group of a user
584
	 * @param string $dn
585
	 * @return string
586
	 */
587 View Code Duplication
	public function getUserPrimaryGroup($dn) {
588
		$groupID = $this->getUserPrimaryGroupIDs($dn);
589
		if($groupID !== false) {
590
			$groupName = $this->primaryGroupID2Name($groupID, $dn);
0 ignored issues
show
Bug introduced by
It seems like $groupID defined by $this->getUserPrimaryGroupIDs($dn) on line 588 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...
591
			if($groupName !== false) {
592
				return $groupName;
593
			}
594
		}
595
596
		return false;
597
	}
598
599
	/**
600
	 * Get all groups a user belongs to
601
	 * @param string $uid Name of the user
602
	 * @return array with group names
603
	 *
604
	 * This function fetches all groups a user belongs to. It does not check
605
	 * if the user exists at all.
606
	 *
607
	 * This function includes groups based on dynamic group membership.
608
	 */
609
	public function getUserGroups($uid) {
610
		if(!$this->enabled) {
611
			return array();
612
		}
613
		$cacheKey = 'getUserGroups'.$uid;
614
		$userGroups = $this->access->connection->getFromCache($cacheKey);
615
		if(!is_null($userGroups)) {
616
			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...
617
		}
618
		$userDN = $this->access->username2dn($uid);
619
		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...
620
			$this->access->connection->writeToCache($cacheKey, array());
621
			return array();
622
		}
623
624
		$groups = [];
625
		$primaryGroup = $this->getUserPrimaryGroup($userDN);
626
		$gidGroupName = $this->getUserGroupByGid($userDN);
627
628
		$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...
629
630
		if (!empty($dynamicGroupMemberURL)) {
631
			// look through dynamic groups to add them to the result array if needed
632
			$groupsToMatch = $this->access->fetchListOfGroups(
633
				$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...
634
			foreach($groupsToMatch as $dynamicGroup) {
635
				if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) {
636
					continue;
637
				}
638
				$pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '(');
639
				if ($pos !== false) {
640
					$memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0],$pos);
641
					// apply filter via ldap search to see if this user is in this
642
					// dynamic group
643
					$userMatch = $this->access->readAttribute(
644
						$userDN,
645
						$this->access->connection->ldapUserDisplayName,
646
						$memberUrlFilter
647
					);
648
					if ($userMatch !== false) {
649
						// match found so this user is in this group
650
						$groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]);
651
						if(is_string($groupName)) {
652
							// be sure to never return false if the dn could not be
653
							// resolved to a name, for whatever reason.
654
							$groups[] = $groupName;
655
						}
656
					}
657
				} else {
658
					\OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
659
						'of group ' . print_r($dynamicGroup, true), \OCP\Util::DEBUG);
660
				}
661
			}
662
		}
663
664
		// if possible, read out membership via memberOf. It's far faster than
665
		// performing a search, which still is a fallback later.
666
		// memberof doesn't support memberuid, so skip it here.
667
		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...
668
			&& 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...
669
		    && strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid'
670
		    ) {
671
			$groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
672
			if (is_array($groupDNs)) {
673
				foreach ($groupDNs as $dn) {
674
					$groupName = $this->access->dn2groupname($dn);
675
					if(is_string($groupName)) {
676
						// be sure to never return false if the dn could not be
677
						// resolved to a name, for whatever reason.
678
						$groups[] = $groupName;
679
					}
680
				}
681
			}
682
683
			if($primaryGroup !== false) {
684
				$groups[] = $primaryGroup;
685
			}
686
			if($gidGroupName !== false) {
687
				$groups[] = $gidGroupName;
688
			}
689
			$this->access->connection->writeToCache($cacheKey, $groups);
690
			return $groups;
691
		}
692
693
		//uniqueMember takes DN, memberuid the uid, so we need to distinguish
694
		if((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember')
695
			|| (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member')
696
		) {
697
			$uid = $userDN;
698
		} else if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
699
			$result = $this->access->readAttribute($userDN, 'uid');
700
			if ($result === false) {
701
				\OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on '.
702
					$this->access->connection->ldapHost, \OCP\Util::DEBUG);
703
			}
704
			$uid = $result[0];
705
		} else {
706
			// just in case
707
			$uid = $userDN;
708
		}
709
710
		if(isset($this->cachedGroupsByMember[$uid])) {
711
			$groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
712
		} else {
713
			$groupsByMember = array_values($this->getGroupsByMember($uid));
714
			$groupsByMember = $this->access->nextcloudGroupNames($groupsByMember);
715
			$this->cachedGroupsByMember[$uid] = $groupsByMember;
716
			$groups = array_merge($groups, $groupsByMember);
717
		}
718
719
		if($primaryGroup !== false) {
720
			$groups[] = $primaryGroup;
721
		}
722
		if($gidGroupName !== false) {
723
			$groups[] = $gidGroupName;
724
		}
725
726
		$groups = array_unique($groups, SORT_LOCALE_STRING);
727
		$this->access->connection->writeToCache($cacheKey, $groups);
728
729
		return $groups;
730
	}
731
732
	/**
733
	 * @param string $dn
734
	 * @param array|null &$seen
735
	 * @return array
736
	 */
737
	private function getGroupsByMember($dn, &$seen = null) {
738
		if ($seen === null) {
739
			$seen = array();
740
		}
741
		$allGroups = array();
742
		if (array_key_exists($dn, $seen)) {
743
			// avoid loops
744
			return array();
745
		}
746
		$seen[$dn] = true;
747
		$filter = $this->access->combineFilterWithAnd(array(
748
			$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...
749
			$this->access->connection->ldapGroupMemberAssocAttr.'='.$dn
750
		));
751
		$groups = $this->access->fetchListOfGroups($filter,
752
			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...
753
		if (is_array($groups)) {
754
			foreach ($groups as $groupobj) {
755
				$groupDN = $groupobj['dn'][0];
756
				$allGroups[$groupDN] = $groupobj;
757
				$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...
758
				if (!empty($nestedGroups)) {
759
					$supergroups = $this->getGroupsByMember($groupDN, $seen);
760
					if (is_array($supergroups) && (count($supergroups)>0)) {
761
						$allGroups = array_merge($allGroups, $supergroups);
762
					}
763
				}
764
			}
765
		}
766
		return $allGroups;
767
	}
768
769
	/**
770
	 * get a list of all users in a group
771
	 *
772
	 * @param string $gid
773
	 * @param string $search
774
	 * @param int $limit
775
	 * @param int $offset
776
	 * @return array with user ids
777
	 */
778
	public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
779
		if(!$this->enabled) {
780
			return array();
781
		}
782
		if(!$this->groupExists($gid)) {
783
			return array();
784
		}
785
		$search = $this->access->escapeFilterPart($search, true);
786
		$cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
787
		// check for cache of the exact query
788
		$groupUsers = $this->access->connection->getFromCache($cacheKey);
789
		if(!is_null($groupUsers)) {
790
			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...
791
		}
792
793
		// check for cache of the query without limit and offset
794
		$groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search);
795
		if(!is_null($groupUsers)) {
796
			$groupUsers = array_slice($groupUsers, $offset, $limit);
797
			$this->access->connection->writeToCache($cacheKey, $groupUsers);
798
			return $groupUsers;
799
		}
800
801
		if($limit === -1) {
802
			$limit = null;
803
		}
804
		$groupDN = $this->access->groupname2dn($gid);
805
		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...
806
			// group couldn't be found, return empty resultset
807
			$this->access->connection->writeToCache($cacheKey, array());
808
			return array();
809
		}
810
811
		$primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset);
812
		$posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset);
813
		$members = array_keys($this->_groupMembers($groupDN));
814
		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...
815
			//in case users could not be retrieved, return empty result set
816
			$this->access->connection->writeToCache($cacheKey, []);
817
			return [];
818
		}
819
820
		$groupUsers = array();
821
		$isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
822
		$attrs = $this->access->userManager->getAttributes(true);
823
		foreach($members as $member) {
824
			if($isMemberUid) {
825
				//we got uids, need to get their DNs to 'translate' them to user names
826
				$filter = $this->access->combineFilterWithAnd(array(
827
					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...
828
					$this->access->getFilterPartForUserSearch($search)
829
				));
830
				$ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
831
				if(count($ldap_users) < 1) {
832
					continue;
833
				}
834
				$groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
835
			} else {
836
				//we got DNs, check if we need to filter by search or we can give back all of them
837
				if ($search !== '') {
838
					if(!$this->access->readAttribute($member,
839
						$this->access->connection->ldapUserDisplayName,
840
						$this->access->getFilterPartForUserSearch($search))) {
841
						continue;
842
					}
843
				}
844
				// dn2username will also check if the users belong to the allowed base
845
				if($ocname = $this->access->dn2username($member)) {
846
					$groupUsers[] = $ocname;
847
				}
848
			}
849
		}
850
851
		$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers));
852
		natsort($groupUsers);
853
		$this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
854
		$groupUsers = array_slice($groupUsers, $offset, $limit);
855
856
		$this->access->connection->writeToCache($cacheKey, $groupUsers);
857
858
		return $groupUsers;
859
	}
860
861
	/**
862
	 * returns the number of users in a group, who match the search term
863
	 * @param string $gid the internal group name
864
	 * @param string $search optional, a search string
865
	 * @return int|bool
866
	 */
867
	public function countUsersInGroup($gid, $search = '') {
868
		if ($this->groupPluginManager->implementsActions(GroupInterface::COUNT_USERS)) {
869
			return $this->groupPluginManager->countUsersInGroup($gid, $search);
870
		}
871
872
		$cacheKey = 'countUsersInGroup-'.$gid.'-'.$search;
873
		if(!$this->enabled || !$this->groupExists($gid)) {
874
			return false;
875
		}
876
		$groupUsers = $this->access->connection->getFromCache($cacheKey);
877
		if(!is_null($groupUsers)) {
878
			return $groupUsers;
879
		}
880
881
		$groupDN = $this->access->groupname2dn($gid);
882
		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...
883
			// group couldn't be found, return empty result set
884
			$this->access->connection->writeToCache($cacheKey, false);
885
			return false;
886
		}
887
888
		$members = array_keys($this->_groupMembers($groupDN));
889
		$primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, '');
890
		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...
891
			//in case users could not be retrieved, return empty result set
892
			$this->access->connection->writeToCache($cacheKey, false);
893
			return false;
894
		}
895
896
		if ($search === '') {
897
			$groupUsers = count($members) + $primaryUserCount;
898
			$this->access->connection->writeToCache($cacheKey, $groupUsers);
899
			return $groupUsers;
900
		}
901
		$search = $this->access->escapeFilterPart($search, true);
902
		$isMemberUid =
903
			(strtolower($this->access->connection->ldapGroupMemberAssocAttr)
904
			=== 'memberuid');
905
906
		//we need to apply the search filter
907
		//alternatives that need to be checked:
908
		//a) get all users by search filter and array_intersect them
909
		//b) a, but only when less than 1k 10k ?k users like it is
910
		//c) put all DNs|uids in a LDAP filter, combine with the search string
911
		//   and let it count.
912
		//For now this is not important, because the only use of this method
913
		//does not supply a search string
914
		$groupUsers = array();
915
		foreach($members as $member) {
916
			if($isMemberUid) {
917
				//we got uids, need to get their DNs to 'translate' them to user names
918
				$filter = $this->access->combineFilterWithAnd(array(
919
					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...
920
					$this->access->getFilterPartForUserSearch($search)
921
				));
922
				$ldap_users = $this->access->fetchListOfUsers($filter, 'dn', 1);
923
				if(count($ldap_users) < 1) {
924
					continue;
925
				}
926
				$groupUsers[] = $this->access->dn2username($ldap_users[0]);
927
			} else {
928
				//we need to apply the search filter now
929
				if(!$this->access->readAttribute($member,
930
					$this->access->connection->ldapUserDisplayName,
931
					$this->access->getFilterPartForUserSearch($search))) {
932
					continue;
933
				}
934
				// dn2username will also check if the users belong to the allowed base
935
				if($ocname = $this->access->dn2username($member)) {
936
					$groupUsers[] = $ocname;
937
				}
938
			}
939
		}
940
941
		//and get users that have the group as primary
942
		$primaryUsers = $this->countUsersInPrimaryGroup($groupDN, $search);
943
944
		return count($groupUsers) + $primaryUsers;
945
	}
946
947
	/**
948
	 * get a list of all groups
949
	 *
950
	 * @param string $search
951
	 * @param $limit
952
	 * @param int $offset
953
	 * @return array with group names
954
	 *
955
	 * Returns a list with all groups (used by getGroups)
956
	 */
957
	protected function getGroupsChunk($search = '', $limit = -1, $offset = 0) {
958
		if(!$this->enabled) {
959
			return array();
960
		}
961
		$cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
962
963
		//Check cache before driving unnecessary searches
964
		\OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, \OCP\Util::DEBUG);
965
		$ldap_groups = $this->access->connection->getFromCache($cacheKey);
966
		if(!is_null($ldap_groups)) {
967
			return $ldap_groups;
968
		}
969
970
		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
971
		// error. With a limit of 0, we get 0 results. So we pass null.
972
		if($limit <= 0) {
973
			$limit = null;
974
		}
975
		$filter = $this->access->combineFilterWithAnd(array(
976
			$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...
977
			$this->access->getFilterPartForGroupSearch($search)
978
		));
979
		\OCP\Util::writeLog('user_ldap', 'getGroups Filter '.$filter, \OCP\Util::DEBUG);
980
		$ldap_groups = $this->access->fetchListOfGroups($filter,
981
				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...
982
				$limit,
983
				$offset);
984
		$ldap_groups = $this->access->nextcloudGroupNames($ldap_groups);
985
986
		$this->access->connection->writeToCache($cacheKey, $ldap_groups);
987
		return $ldap_groups;
988
	}
989
990
	/**
991
	 * get a list of all groups using a paged search
992
	 *
993
	 * @param string $search
994
	 * @param int $limit
995
	 * @param int $offset
996
	 * @return array with group names
997
	 *
998
	 * Returns a list with all groups
999
	 * Uses a paged search if available to override a
1000
	 * server side search limit.
1001
	 * (active directory has a limit of 1000 by default)
1002
	 */
1003
	public function getGroups($search = '', $limit = -1, $offset = 0) {
1004
		if(!$this->enabled) {
1005
			return array();
1006
		}
1007
		$search = $this->access->escapeFilterPart($search, true);
1008
		$pagingSize = intval($this->access->connection->ldapPagingSize);
1009
		if (!$this->access->connection->hasPagedResultSupport || $pagingSize <= 0) {
1010
			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...
1011
		}
1012
		$maxGroups = 100000; // limit max results (just for safety reasons)
1013
		if ($limit > -1) {
1014
		   $overallLimit = min($limit + $offset, $maxGroups);
1015
		} else {
1016
		   $overallLimit = $maxGroups;
1017
		}
1018
		$chunkOffset = $offset;
1019
		$allGroups = array();
1020
		while ($chunkOffset < $overallLimit) {
1021
			$chunkLimit = min($pagingSize, $overallLimit - $chunkOffset);
1022
			$ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset);
1023
			$nread = count($ldapGroups);
1024
			\OCP\Util::writeLog('user_ldap', 'getGroups('.$search.'): read '.$nread.' at offset '.$chunkOffset.' (limit: '.$chunkLimit.')', \OCP\Util::DEBUG);
1025
			if ($nread) {
1026
				$allGroups = array_merge($allGroups, $ldapGroups);
1027
				$chunkOffset += $nread;
1028
			}
1029
			if ($nread < $chunkLimit) {
1030
				break;
1031
			}
1032
		}
1033
		return $allGroups;
1034
	}
1035
1036
	/**
1037
	 * @param string $group
1038
	 * @return bool
1039
	 */
1040
	public function groupMatchesFilter($group) {
1041
		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...
1042
	}
1043
1044
	/**
1045
	 * check if a group exists
1046
	 * @param string $gid
1047
	 * @return bool
1048
	 */
1049
	public function groupExists($gid) {
1050
		$groupExists = $this->access->connection->getFromCache('groupExists'.$gid);
1051
		if(!is_null($groupExists)) {
1052
			return (bool)$groupExists;
1053
		}
1054
1055
		//getting dn, if false the group does not exist. If dn, it may be mapped
1056
		//only, requires more checking.
1057
		$dn = $this->access->groupname2dn($gid);
1058
		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...
1059
			$this->access->connection->writeToCache('groupExists'.$gid, false);
1060
			return false;
1061
		}
1062
1063
		//if group really still exists, we will be able to read its objectclass
1064 View Code Duplication
		if(!is_array($this->access->readAttribute($dn, ''))) {
1065
			$this->access->connection->writeToCache('groupExists'.$gid, false);
1066
			return false;
1067
		}
1068
1069
		$this->access->connection->writeToCache('groupExists'.$gid, true);
1070
		return true;
1071
	}
1072
1073
	/**
1074
	* Check if backend implements actions
1075
	* @param int $actions bitwise-or'ed actions
1076
	* @return boolean
1077
	*
1078
	* Returns the supported actions as int to be
1079
	* compared with GroupInterface::CREATE_GROUP etc.
1080
	*/
1081
	public function implementsActions($actions) {
1082
		return (bool)((GroupInterface::COUNT_USERS |
1083
				$this->groupPluginManager->getImplementedActions()) & $actions);
1084
	}
1085
1086
	/**
1087
	 * Return access for LDAP interaction.
1088
	 * @return Access instance of Access for LDAP interaction
1089
	 */
1090
	public function getLDAPAccess($gid) {
1091
		return $this->access;
1092
	}
1093
1094
	/**
1095
	 * create a group
1096
	 * @param string $gid
1097
	 * @return bool
1098
	 * @throws \Exception
1099
	 */
1100
	public function createGroup($gid) {
1101
		if ($this->groupPluginManager->implementsActions(GroupInterface::CREATE_GROUP)) {
1102 View Code Duplication
			if ($dn = $this->groupPluginManager->createGroup($gid)) {
1103
				//updates group mapping
1104
				$this->access->dn2ocname($dn, $gid, false);
1105
				$this->access->connection->writeToCache("groupExists".$gid, true);
1106
			}
1107
			return $dn != null;
1108
		}
1109
		throw new \Exception('Could not create group in LDAP backend.');
1110
	}
1111
1112
	/**
1113
	 * delete a group
1114
	 * @param string $gid gid of the group to delete
1115
	 * @return bool
1116
	 * @throws \Exception
1117
	 */
1118
	public function deleteGroup($gid) {
1119
		if ($this->groupPluginManager->implementsActions(GroupInterface::DELETE_GROUP)) {
1120 View Code Duplication
			if ($ret = $this->groupPluginManager->deleteGroup($gid)) {
1121
				#delete group in nextcloud internal db
1122
				$this->access->getGroupMapper()->unmap($gid);
1123
				$this->access->connection->writeToCache("groupExists".$gid, false);
1124
			}
1125
			return $ret;
1126
		}
1127
		throw new \Exception('Could not delete group in LDAP backend.');
1128
	}
1129
1130
	/**
1131
	 * Add a user to a group
1132
	 * @param string $uid Name of the user to add to group
1133
	 * @param string $gid Name of the group in which add the user
1134
	 * @return bool
1135
	 * @throws \Exception
1136
	 */
1137 View Code Duplication
	public function addToGroup($uid, $gid) {
1138
		if ($this->groupPluginManager->implementsActions(GroupInterface::ADD_TO_GROUP)) {
1139
			if ($ret = $this->groupPluginManager->addToGroup($uid, $gid)) {
1140
				$this->access->connection->clearCache();
1141
			}
1142
			return $ret;
1143
		}
1144
		throw new \Exception('Could not add user to group in LDAP backend.');
1145
	}
1146
1147
	/**
1148
	 * Removes a user from a group
1149
	 * @param string $uid Name of the user to remove from group
1150
	 * @param string $gid Name of the group from which remove the user
1151
	 * @return bool
1152
	 * @throws \Exception
1153
	 */
1154 View Code Duplication
	public function removeFromGroup($uid, $gid) {
1155
		if ($this->groupPluginManager->implementsActions(GroupInterface::REMOVE_FROM_GROUP)) {
1156
			if ($ret = $this->groupPluginManager->removeFromGroup($uid, $gid)) {
1157
				$this->access->connection->clearCache();
1158
			}
1159
			return $ret;
1160
		}
1161
		throw new \Exception('Could not remove user from group in LDAP backend.');
1162
	}
1163
1164
	/**
1165
	 * Gets group details
1166
	 * @param string $gid Name of the group
1167
	 * @return array | false
1168
	 * @throws \Exception
1169
	 */
1170
	public function getGroupDetails($gid) {
1171
		if ($this->groupPluginManager->implementsActions(GroupInterface::GROUP_DETAILS)) {
1172
			return $this->groupPluginManager->getGroupDetails($gid);
1173
		}
1174
		throw new \Exception('Could not get group details in LDAP backend.');
1175
	}
1176
1177
	/**
1178
	 * Return LDAP connection resource from a cloned connection.
1179
	 * The cloned connection needs to be closed manually.
1180
	 * of the current access.
1181
	 * @param string $gid
1182
	 * @return resource of the LDAP connection
1183
	 */
1184
	public function getNewLDAPConnection($gid) {
1185
		$connection = clone $this->access->getConnection();
1186
		return $connection->getConnectionResource();
1187
	}
1188
1189
}
1190