Completed
Pull Request — master (#7057)
by Blizzz
13:29
created
apps/user_ldap/lib/Proxy.php 1 patch
Indentation   +166 added lines, -166 removed lines patch added patch discarded remove patch
@@ -35,170 +35,170 @@
 block discarded – undo
35 35
 use OCA\User_LDAP\User\Manager;
36 36
 
37 37
 abstract class Proxy {
38
-	static private $accesses = array();
39
-	private $ldap = null;
40
-
41
-	/** @var \OCP\ICache|null */
42
-	private $cache;
43
-
44
-	/**
45
-	 * @param ILDAPWrapper $ldap
46
-	 */
47
-	public function __construct(ILDAPWrapper $ldap) {
48
-		$this->ldap = $ldap;
49
-		$memcache = \OC::$server->getMemCacheFactory();
50
-		if($memcache->isAvailable()) {
51
-			$this->cache = $memcache->create();
52
-		}
53
-	}
54
-
55
-	/**
56
-	 * @param string $configPrefix
57
-	 */
58
-	private function addAccess($configPrefix) {
59
-		static $ocConfig;
60
-		static $fs;
61
-		static $log;
62
-		static $avatarM;
63
-		static $userMap;
64
-		static $groupMap;
65
-		static $db;
66
-		static $coreUserManager;
67
-		static $coreNotificationManager;
68
-		if($fs === null) {
69
-			$ocConfig = \OC::$server->getConfig();
70
-			$fs       = new FilesystemHelper();
71
-			$log      = new LogWrapper();
72
-			$avatarM  = \OC::$server->getAvatarManager();
73
-			$db       = \OC::$server->getDatabaseConnection();
74
-			$userMap  = new UserMapping($db);
75
-			$groupMap = new GroupMapping($db);
76
-			$coreUserManager = \OC::$server->getUserManager();
77
-			$coreNotificationManager = \OC::$server->getNotificationManager();
78
-		}
79
-		$userManager =
80
-			new Manager($ocConfig, $fs, $log, $avatarM, new \OCP\Image(), $db,
81
-				$coreUserManager, $coreNotificationManager);
82
-		$connector = new Connection($this->ldap, $configPrefix);
83
-		$access = new Access($connector, $this->ldap, $userManager, new Helper(\OC::$server->getConfig()), \OC::$server);
84
-		$access->setUserMapper($userMap);
85
-		$access->setGroupMapper($groupMap);
86
-		self::$accesses[$configPrefix] = $access;
87
-	}
88
-
89
-	/**
90
-	 * @param string $configPrefix
91
-	 * @return mixed
92
-	 */
93
-	protected function getAccess($configPrefix) {
94
-		if(!isset(self::$accesses[$configPrefix])) {
95
-			$this->addAccess($configPrefix);
96
-		}
97
-		return self::$accesses[$configPrefix];
98
-	}
99
-
100
-	/**
101
-	 * @param string $uid
102
-	 * @return string
103
-	 */
104
-	protected function getUserCacheKey($uid) {
105
-		return 'user-'.$uid.'-lastSeenOn';
106
-	}
107
-
108
-	/**
109
-	 * @param string $gid
110
-	 * @return string
111
-	 */
112
-	protected function getGroupCacheKey($gid) {
113
-		return 'group-'.$gid.'-lastSeenOn';
114
-	}
115
-
116
-	/**
117
-	 * @param string $id
118
-	 * @param string $method
119
-	 * @param array $parameters
120
-	 * @param bool $passOnWhen
121
-	 * @return mixed
122
-	 */
123
-	abstract protected function callOnLastSeenOn($id, $method, $parameters, $passOnWhen);
124
-
125
-	/**
126
-	 * @param string $id
127
-	 * @param string $method
128
-	 * @param array $parameters
129
-	 * @return mixed
130
-	 */
131
-	abstract protected function walkBackends($id, $method, $parameters);
132
-
133
-	/**
134
-	 * @param string $id
135
-	 * @return Access
136
-	 */
137
-	abstract public function getLDAPAccess($id);
138
-
139
-	/**
140
-	 * Takes care of the request to the User backend
141
-	 * @param string $id
142
-	 * @param string $method string, the method of the user backend that shall be called
143
-	 * @param array $parameters an array of parameters to be passed
144
-	 * @param bool $passOnWhen
145
-	 * @return mixed, the result of the specified method
146
-	 */
147
-	protected function handleRequest($id, $method, $parameters, $passOnWhen = false) {
148
-		$result = $this->callOnLastSeenOn($id,  $method, $parameters, $passOnWhen);
149
-		if($result === $passOnWhen) {
150
-			$result = $this->walkBackends($id, $method, $parameters);
151
-		}
152
-		return $result;
153
-	}
154
-
155
-	/**
156
-	 * @param string|null $key
157
-	 * @return string
158
-	 */
159
-	private function getCacheKey($key) {
160
-		$prefix = 'LDAP-Proxy-';
161
-		if($key === null) {
162
-			return $prefix;
163
-		}
164
-		return $prefix.md5($key);
165
-	}
166
-
167
-	/**
168
-	 * @param string $key
169
-	 * @return mixed|null
170
-	 */
171
-	public function getFromCache($key) {
172
-		if($this->cache === null) {
173
-			return null;
174
-		}
175
-
176
-		$key = $this->getCacheKey($key);
177
-		$value = $this->cache->get($key);
178
-		if ($value === null) {
179
-			return null;
180
-		}
181
-
182
-		return json_decode(base64_decode($value));
183
-	}
184
-
185
-	/**
186
-	 * @param string $key
187
-	 * @param mixed $value
188
-	 */
189
-	public function writeToCache($key, $value) {
190
-		if($this->cache === null) {
191
-			return;
192
-		}
193
-		$key   = $this->getCacheKey($key);
194
-		$value = base64_encode(json_encode($value));
195
-		$this->cache->set($key, $value, 2592000);
196
-	}
197
-
198
-	public function clearCache() {
199
-		if($this->cache === null) {
200
-			return;
201
-		}
202
-		$this->cache->clear($this->getCacheKey(null));
203
-	}
38
+    static private $accesses = array();
39
+    private $ldap = null;
40
+
41
+    /** @var \OCP\ICache|null */
42
+    private $cache;
43
+
44
+    /**
45
+     * @param ILDAPWrapper $ldap
46
+     */
47
+    public function __construct(ILDAPWrapper $ldap) {
48
+        $this->ldap = $ldap;
49
+        $memcache = \OC::$server->getMemCacheFactory();
50
+        if($memcache->isAvailable()) {
51
+            $this->cache = $memcache->create();
52
+        }
53
+    }
54
+
55
+    /**
56
+     * @param string $configPrefix
57
+     */
58
+    private function addAccess($configPrefix) {
59
+        static $ocConfig;
60
+        static $fs;
61
+        static $log;
62
+        static $avatarM;
63
+        static $userMap;
64
+        static $groupMap;
65
+        static $db;
66
+        static $coreUserManager;
67
+        static $coreNotificationManager;
68
+        if($fs === null) {
69
+            $ocConfig = \OC::$server->getConfig();
70
+            $fs       = new FilesystemHelper();
71
+            $log      = new LogWrapper();
72
+            $avatarM  = \OC::$server->getAvatarManager();
73
+            $db       = \OC::$server->getDatabaseConnection();
74
+            $userMap  = new UserMapping($db);
75
+            $groupMap = new GroupMapping($db);
76
+            $coreUserManager = \OC::$server->getUserManager();
77
+            $coreNotificationManager = \OC::$server->getNotificationManager();
78
+        }
79
+        $userManager =
80
+            new Manager($ocConfig, $fs, $log, $avatarM, new \OCP\Image(), $db,
81
+                $coreUserManager, $coreNotificationManager);
82
+        $connector = new Connection($this->ldap, $configPrefix);
83
+        $access = new Access($connector, $this->ldap, $userManager, new Helper(\OC::$server->getConfig()), \OC::$server);
84
+        $access->setUserMapper($userMap);
85
+        $access->setGroupMapper($groupMap);
86
+        self::$accesses[$configPrefix] = $access;
87
+    }
88
+
89
+    /**
90
+     * @param string $configPrefix
91
+     * @return mixed
92
+     */
93
+    protected function getAccess($configPrefix) {
94
+        if(!isset(self::$accesses[$configPrefix])) {
95
+            $this->addAccess($configPrefix);
96
+        }
97
+        return self::$accesses[$configPrefix];
98
+    }
99
+
100
+    /**
101
+     * @param string $uid
102
+     * @return string
103
+     */
104
+    protected function getUserCacheKey($uid) {
105
+        return 'user-'.$uid.'-lastSeenOn';
106
+    }
107
+
108
+    /**
109
+     * @param string $gid
110
+     * @return string
111
+     */
112
+    protected function getGroupCacheKey($gid) {
113
+        return 'group-'.$gid.'-lastSeenOn';
114
+    }
115
+
116
+    /**
117
+     * @param string $id
118
+     * @param string $method
119
+     * @param array $parameters
120
+     * @param bool $passOnWhen
121
+     * @return mixed
122
+     */
123
+    abstract protected function callOnLastSeenOn($id, $method, $parameters, $passOnWhen);
124
+
125
+    /**
126
+     * @param string $id
127
+     * @param string $method
128
+     * @param array $parameters
129
+     * @return mixed
130
+     */
131
+    abstract protected function walkBackends($id, $method, $parameters);
132
+
133
+    /**
134
+     * @param string $id
135
+     * @return Access
136
+     */
137
+    abstract public function getLDAPAccess($id);
138
+
139
+    /**
140
+     * Takes care of the request to the User backend
141
+     * @param string $id
142
+     * @param string $method string, the method of the user backend that shall be called
143
+     * @param array $parameters an array of parameters to be passed
144
+     * @param bool $passOnWhen
145
+     * @return mixed, the result of the specified method
146
+     */
147
+    protected function handleRequest($id, $method, $parameters, $passOnWhen = false) {
148
+        $result = $this->callOnLastSeenOn($id,  $method, $parameters, $passOnWhen);
149
+        if($result === $passOnWhen) {
150
+            $result = $this->walkBackends($id, $method, $parameters);
151
+        }
152
+        return $result;
153
+    }
154
+
155
+    /**
156
+     * @param string|null $key
157
+     * @return string
158
+     */
159
+    private function getCacheKey($key) {
160
+        $prefix = 'LDAP-Proxy-';
161
+        if($key === null) {
162
+            return $prefix;
163
+        }
164
+        return $prefix.md5($key);
165
+    }
166
+
167
+    /**
168
+     * @param string $key
169
+     * @return mixed|null
170
+     */
171
+    public function getFromCache($key) {
172
+        if($this->cache === null) {
173
+            return null;
174
+        }
175
+
176
+        $key = $this->getCacheKey($key);
177
+        $value = $this->cache->get($key);
178
+        if ($value === null) {
179
+            return null;
180
+        }
181
+
182
+        return json_decode(base64_decode($value));
183
+    }
184
+
185
+    /**
186
+     * @param string $key
187
+     * @param mixed $value
188
+     */
189
+    public function writeToCache($key, $value) {
190
+        if($this->cache === null) {
191
+            return;
192
+        }
193
+        $key   = $this->getCacheKey($key);
194
+        $value = base64_encode(json_encode($value));
195
+        $this->cache->set($key, $value, 2592000);
196
+    }
197
+
198
+    public function clearCache() {
199
+        if($this->cache === null) {
200
+            return;
201
+        }
202
+        $this->cache->clear($this->getCacheKey(null));
203
+    }
204 204
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/User_LDAP.php 1 patch
Indentation   +511 added lines, -511 removed lines patch added patch discarded remove patch
@@ -47,518 +47,518 @@
 block discarded – undo
47 47
 use OCP\Util;
48 48
 
49 49
 class User_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserInterface, IUserLDAP {
50
-	/** @var \OCP\IConfig */
51
-	protected $ocConfig;
52
-
53
-	/** @var INotificationManager */
54
-	protected $notificationManager;
55
-
56
-	/** @var string */
57
-	protected $currentUserInDeletionProcess;
58
-
59
-	/**
60
-	 * @param Access $access
61
-	 * @param \OCP\IConfig $ocConfig
62
-	 * @param \OCP\Notification\IManager $notificationManager
63
-	 * @param IUserSession $userSession
64
-	 */
65
-	public function __construct(Access $access, IConfig $ocConfig, INotificationManager $notificationManager, IUserSession $userSession) {
66
-		parent::__construct($access);
67
-		$this->ocConfig = $ocConfig;
68
-		$this->notificationManager = $notificationManager;
69
-		$this->registerHooks($userSession);
70
-	}
71
-
72
-	protected function registerHooks(IUserSession $userSession) {
73
-		$userSession->listen('\OC\User', 'preDelete', [$this, 'preDeleteUser']);
74
-		$userSession->listen('\OC\User', 'postDelete', [$this, 'postDeleteUser']);
75
-	}
76
-
77
-	public function preDeleteUser(IUser $user) {
78
-		$this->currentUserInDeletionProcess = $user->getUID();
79
-	}
80
-
81
-	public function postDeleteUser() {
82
-		$this->currentUserInDeletionProcess = null;
83
-	}
84
-
85
-	/**
86
-	 * checks whether the user is allowed to change his avatar in Nextcloud
87
-	 * @param string $uid the Nextcloud user name
88
-	 * @return boolean either the user can or cannot
89
-	 */
90
-	public function canChangeAvatar($uid) {
91
-		$user = $this->access->userManager->get($uid);
92
-		if(!$user instanceof User) {
93
-			return false;
94
-		}
95
-		if($user->getAvatarImage() === false) {
96
-			return true;
97
-		}
98
-
99
-		return false;
100
-	}
101
-
102
-	/**
103
-	 * returns the username for the given login name, if available
104
-	 *
105
-	 * @param string $loginName
106
-	 * @return string|false
107
-	 */
108
-	public function loginName2UserName($loginName) {
109
-		$cacheKey = 'loginName2UserName-'.$loginName;
110
-		$username = $this->access->connection->getFromCache($cacheKey);
111
-		if(!is_null($username)) {
112
-			return $username;
113
-		}
114
-
115
-		try {
116
-			$ldapRecord = $this->getLDAPUserByLoginName($loginName);
117
-			$user = $this->access->userManager->get($ldapRecord['dn'][0]);
118
-			if($user instanceof OfflineUser) {
119
-				// this path is not really possible, however get() is documented
120
-				// to return User or OfflineUser so we are very defensive here.
121
-				$this->access->connection->writeToCache($cacheKey, false);
122
-				return false;
123
-			}
124
-			$username = $user->getUsername();
125
-			$this->access->connection->writeToCache($cacheKey, $username);
126
-			return $username;
127
-		} catch (NotOnLDAP $e) {
128
-			$this->access->connection->writeToCache($cacheKey, false);
129
-			return false;
130
-		}
131
-	}
50
+    /** @var \OCP\IConfig */
51
+    protected $ocConfig;
52
+
53
+    /** @var INotificationManager */
54
+    protected $notificationManager;
55
+
56
+    /** @var string */
57
+    protected $currentUserInDeletionProcess;
58
+
59
+    /**
60
+     * @param Access $access
61
+     * @param \OCP\IConfig $ocConfig
62
+     * @param \OCP\Notification\IManager $notificationManager
63
+     * @param IUserSession $userSession
64
+     */
65
+    public function __construct(Access $access, IConfig $ocConfig, INotificationManager $notificationManager, IUserSession $userSession) {
66
+        parent::__construct($access);
67
+        $this->ocConfig = $ocConfig;
68
+        $this->notificationManager = $notificationManager;
69
+        $this->registerHooks($userSession);
70
+    }
71
+
72
+    protected function registerHooks(IUserSession $userSession) {
73
+        $userSession->listen('\OC\User', 'preDelete', [$this, 'preDeleteUser']);
74
+        $userSession->listen('\OC\User', 'postDelete', [$this, 'postDeleteUser']);
75
+    }
76
+
77
+    public function preDeleteUser(IUser $user) {
78
+        $this->currentUserInDeletionProcess = $user->getUID();
79
+    }
80
+
81
+    public function postDeleteUser() {
82
+        $this->currentUserInDeletionProcess = null;
83
+    }
84
+
85
+    /**
86
+     * checks whether the user is allowed to change his avatar in Nextcloud
87
+     * @param string $uid the Nextcloud user name
88
+     * @return boolean either the user can or cannot
89
+     */
90
+    public function canChangeAvatar($uid) {
91
+        $user = $this->access->userManager->get($uid);
92
+        if(!$user instanceof User) {
93
+            return false;
94
+        }
95
+        if($user->getAvatarImage() === false) {
96
+            return true;
97
+        }
98
+
99
+        return false;
100
+    }
101
+
102
+    /**
103
+     * returns the username for the given login name, if available
104
+     *
105
+     * @param string $loginName
106
+     * @return string|false
107
+     */
108
+    public function loginName2UserName($loginName) {
109
+        $cacheKey = 'loginName2UserName-'.$loginName;
110
+        $username = $this->access->connection->getFromCache($cacheKey);
111
+        if(!is_null($username)) {
112
+            return $username;
113
+        }
114
+
115
+        try {
116
+            $ldapRecord = $this->getLDAPUserByLoginName($loginName);
117
+            $user = $this->access->userManager->get($ldapRecord['dn'][0]);
118
+            if($user instanceof OfflineUser) {
119
+                // this path is not really possible, however get() is documented
120
+                // to return User or OfflineUser so we are very defensive here.
121
+                $this->access->connection->writeToCache($cacheKey, false);
122
+                return false;
123
+            }
124
+            $username = $user->getUsername();
125
+            $this->access->connection->writeToCache($cacheKey, $username);
126
+            return $username;
127
+        } catch (NotOnLDAP $e) {
128
+            $this->access->connection->writeToCache($cacheKey, false);
129
+            return false;
130
+        }
131
+    }
132 132
 	
133
-	/**
134
-	 * returns the username for the given LDAP DN, if available
135
-	 *
136
-	 * @param string $dn
137
-	 * @return string|false with the username
138
-	 */
139
-	public function dn2UserName($dn) {
140
-		return $this->access->dn2username($dn);
141
-	}
142
-
143
-	/**
144
-	 * returns an LDAP record based on a given login name
145
-	 *
146
-	 * @param string $loginName
147
-	 * @return array
148
-	 * @throws NotOnLDAP
149
-	 */
150
-	public function getLDAPUserByLoginName($loginName) {
151
-		//find out dn of the user name
152
-		$attrs = $this->access->userManager->getAttributes();
153
-		$users = $this->access->fetchUsersByLoginName($loginName, $attrs);
154
-		if(count($users) < 1) {
155
-			throw new NotOnLDAP('No user available for the given login name on ' .
156
-				$this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort);
157
-		}
158
-		return $users[0];
159
-	}
160
-
161
-	/**
162
-	 * Check if the password is correct without logging in the user
163
-	 *
164
-	 * @param string $uid The username
165
-	 * @param string $password The password
166
-	 * @return false|string
167
-	 */
168
-	public function checkPassword($uid, $password) {
169
-		try {
170
-			$ldapRecord = $this->getLDAPUserByLoginName($uid);
171
-		} catch(NotOnLDAP $e) {
172
-			if($this->ocConfig->getSystemValue('loglevel', Util::WARN) === Util::DEBUG) {
173
-				\OC::$server->getLogger()->logException($e, ['app' => 'user_ldap']);
174
-			}
175
-			return false;
176
-		}
177
-		$dn = $ldapRecord['dn'][0];
178
-		$user = $this->access->userManager->get($dn);
179
-
180
-		if(!$user instanceof User) {
181
-			Util::writeLog('user_ldap',
182
-				'LDAP Login: Could not get user object for DN ' . $dn .
183
-				'. Maybe the LDAP entry has no set display name attribute?',
184
-				Util::WARN);
185
-			return false;
186
-		}
187
-		if($user->getUsername() !== false) {
188
-			//are the credentials OK?
189
-			if(!$this->access->areCredentialsValid($dn, $password)) {
190
-				return false;
191
-			}
192
-
193
-			$this->access->cacheUserExists($user->getUsername());
194
-			$user->processAttributes($ldapRecord);
195
-			$user->markLogin();
196
-
197
-			return $user->getUsername();
198
-		}
199
-
200
-		return false;
201
-	}
202
-
203
-	/**
204
-	 * Set password
205
-	 * @param string $uid The username
206
-	 * @param string $password The new password
207
-	 * @return bool
208
-	 */
209
-	public function setPassword($uid, $password) {
210
-		$user = $this->access->userManager->get($uid);
211
-
212
-		if(!$user instanceof User) {
213
-			throw new \Exception('LDAP setPassword: Could not get user object for uid ' . $uid .
214
-				'. Maybe the LDAP entry has no set display name attribute?');
215
-		}
216
-		if($user->getUsername() !== false && $this->access->setPassword($user->getDN(), $password)) {
217
-			$ldapDefaultPPolicyDN = $this->access->connection->ldapDefaultPPolicyDN;
218
-			$turnOnPasswordChange = $this->access->connection->turnOnPasswordChange;
219
-			if (!empty($ldapDefaultPPolicyDN) && (intval($turnOnPasswordChange) === 1)) {
220
-				//remove last password expiry warning if any
221
-				$notification = $this->notificationManager->createNotification();
222
-				$notification->setApp('user_ldap')
223
-					->setUser($uid)
224
-					->setObject('pwd_exp_warn', $uid)
225
-				;
226
-				$this->notificationManager->markProcessed($notification);
227
-			}
228
-			return true;
229
-		}
230
-
231
-		return false;
232
-	}
233
-
234
-	/**
235
-	 * Get a list of all users
236
-	 *
237
-	 * @param string $search
238
-	 * @param integer $limit
239
-	 * @param integer $offset
240
-	 * @return string[] an array of all uids
241
-	 */
242
-	public function getUsers($search = '', $limit = 10, $offset = 0) {
243
-		$search = $this->access->escapeFilterPart($search, true);
244
-		$cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset;
245
-
246
-		//check if users are cached, if so return
247
-		$ldap_users = $this->access->connection->getFromCache($cachekey);
248
-		if(!is_null($ldap_users)) {
249
-			return $ldap_users;
250
-		}
251
-
252
-		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
253
-		// error. With a limit of 0, we get 0 results. So we pass null.
254
-		if($limit <= 0) {
255
-			$limit = null;
256
-		}
257
-		$filter = $this->access->combineFilterWithAnd(array(
258
-			$this->access->connection->ldapUserFilter,
259
-			$this->access->connection->ldapUserDisplayName . '=*',
260
-			$this->access->getFilterPartForUserSearch($search)
261
-		));
262
-
263
-		Util::writeLog('user_ldap',
264
-			'getUsers: Options: search '.$search.' limit '.$limit.' offset '.$offset.' Filter: '.$filter,
265
-			Util::DEBUG);
266
-		//do the search and translate results to Nextcloud names
267
-		$ldap_users = $this->access->fetchListOfUsers(
268
-			$filter,
269
-			$this->access->userManager->getAttributes(true),
270
-			$limit, $offset);
271
-		$ldap_users = $this->access->nextcloudUserNames($ldap_users);
272
-		Util::writeLog('user_ldap', 'getUsers: '.count($ldap_users). ' Users found', Util::DEBUG);
273
-
274
-		$this->access->connection->writeToCache($cachekey, $ldap_users);
275
-		return $ldap_users;
276
-	}
277
-
278
-	/**
279
-	 * checks whether a user is still available on LDAP
280
-	 *
281
-	 * @param string|\OCA\User_LDAP\User\User $user either the Nextcloud user
282
-	 * name or an instance of that user
283
-	 * @return bool
284
-	 * @throws \Exception
285
-	 * @throws \OC\ServerNotAvailableException
286
-	 */
287
-	public function userExistsOnLDAP($user) {
288
-		if(is_string($user)) {
289
-			$user = $this->access->userManager->get($user);
290
-		}
291
-		if(is_null($user)) {
292
-			return false;
293
-		}
294
-
295
-		$dn = $user->getDN();
296
-		//check if user really still exists by reading its entry
297
-		if(!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) {
298
-			$lcr = $this->access->connection->getConnectionResource();
299
-			if(is_null($lcr)) {
300
-				throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost);
301
-			}
302
-
303
-			try {
304
-				$uuid = $this->access->getUserMapper()->getUUIDByDN($dn);
305
-				if(!$uuid) {
306
-					return false;
307
-				}
308
-				$newDn = $this->access->getUserDnByUuid($uuid);
309
-				//check if renamed user is still valid by reapplying the ldap filter
310
-				if(!is_array($this->access->readAttribute($newDn, '', $this->access->connection->ldapUserFilter))) {
311
-					return false;
312
-				}
313
-				$this->access->getUserMapper()->setDNbyUUID($newDn, $uuid);
314
-				return true;
315
-			} catch (\Exception $e) {
316
-				return false;
317
-			}
318
-		}
319
-
320
-		if($user instanceof OfflineUser) {
321
-			$user->unmark();
322
-		}
323
-
324
-		return true;
325
-	}
326
-
327
-	/**
328
-	 * check if a user exists
329
-	 * @param string $uid the username
330
-	 * @return boolean
331
-	 * @throws \Exception when connection could not be established
332
-	 */
333
-	public function userExists($uid) {
334
-		$userExists = $this->access->connection->getFromCache('userExists'.$uid);
335
-		if(!is_null($userExists)) {
336
-			return (bool)$userExists;
337
-		}
338
-		//getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking.
339
-		$user = $this->access->userManager->get($uid);
340
-
341
-		if(is_null($user)) {
342
-			Util::writeLog('user_ldap', 'No DN found for '.$uid.' on '.
343
-				$this->access->connection->ldapHost, Util::DEBUG);
344
-			$this->access->connection->writeToCache('userExists'.$uid, false);
345
-			return false;
346
-		} else if($user instanceof OfflineUser) {
347
-			//express check for users marked as deleted. Returning true is
348
-			//necessary for cleanup
349
-			return true;
350
-		}
351
-
352
-		$result = $this->userExistsOnLDAP($user);
353
-		$this->access->connection->writeToCache('userExists'.$uid, $result);
354
-		if($result === true) {
355
-			$user->update();
356
-		}
357
-		return $result;
358
-	}
359
-
360
-	/**
361
-	* returns whether a user was deleted in LDAP
362
-	*
363
-	* @param string $uid The username of the user to delete
364
-	* @return bool
365
-	*/
366
-	public function deleteUser($uid) {
367
-		$marked = $this->ocConfig->getUserValue($uid, 'user_ldap', 'isDeleted', 0);
368
-		if(intval($marked) === 0) {
369
-			\OC::$server->getLogger()->notice(
370
-				'User '.$uid . ' is not marked as deleted, not cleaning up.',
371
-				array('app' => 'user_ldap'));
372
-			return false;
373
-		}
374
-		\OC::$server->getLogger()->info('Cleaning up after user ' . $uid,
375
-			array('app' => 'user_ldap'));
376
-
377
-		$this->access->getUserMapper()->unmap($uid);
378
-		$this->access->userManager->invalidate($uid);
379
-		return true;
380
-	}
381
-
382
-	/**
383
-	 * get the user's home directory
384
-	 *
385
-	 * @param string $uid the username
386
-	 * @return bool|string
387
-	 * @throws NoUserException
388
-	 * @throws \Exception
389
-	 */
390
-	public function getHome($uid) {
391
-		// user Exists check required as it is not done in user proxy!
392
-		if(!$this->userExists($uid)) {
393
-			return false;
394
-		}
395
-
396
-		$cacheKey = 'getHome'.$uid;
397
-		$path = $this->access->connection->getFromCache($cacheKey);
398
-		if(!is_null($path)) {
399
-			return $path;
400
-		}
401
-
402
-		// early return path if it is a deleted user
403
-		$user = $this->access->userManager->get($uid);
404
-		if($user instanceof OfflineUser) {
405
-			if($this->currentUserInDeletionProcess !== null
406
-				&& $this->currentUserInDeletionProcess === $user->getOCName()
407
-			) {
408
-				return $user->getHomePath();
409
-			} else {
410
-				throw new NoUserException($uid . ' is not a valid user anymore');
411
-			}
412
-		} else if ($user === null) {
413
-			throw new NoUserException($uid . ' is not a valid user anymore');
414
-		}
415
-
416
-		$path = $user->getHomePath();
417
-		$this->access->cacheUserHome($uid, $path);
418
-
419
-		return $path;
420
-	}
421
-
422
-	/**
423
-	 * get display name of the user
424
-	 * @param string $uid user ID of the user
425
-	 * @return string|false display name
426
-	 */
427
-	public function getDisplayName($uid) {
428
-		if(!$this->userExists($uid)) {
429
-			return false;
430
-		}
431
-
432
-		$cacheKey = 'getDisplayName'.$uid;
433
-		if(!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
434
-			return $displayName;
435
-		}
436
-
437
-		//Check whether the display name is configured to have a 2nd feature
438
-		$additionalAttribute = $this->access->connection->ldapUserDisplayName2;
439
-		$displayName2 = '';
440
-		if ($additionalAttribute !== '') {
441
-			$displayName2 = $this->access->readAttribute(
442
-				$this->access->username2dn($uid),
443
-				$additionalAttribute);
444
-		}
445
-
446
-		$displayName = $this->access->readAttribute(
447
-			$this->access->username2dn($uid),
448
-			$this->access->connection->ldapUserDisplayName);
449
-
450
-		if($displayName && (count($displayName) > 0)) {
451
-			$displayName = $displayName[0];
452
-
453
-			if (is_array($displayName2)){
454
-				$displayName2 = count($displayName2) > 0 ? $displayName2[0] : '';
455
-			}
456
-
457
-			$user = $this->access->userManager->get($uid);
458
-			if ($user instanceof User) {
459
-				$displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
460
-				$this->access->connection->writeToCache($cacheKey, $displayName);
461
-			}
462
-			if ($user instanceof OfflineUser) {
463
-				/** @var OfflineUser $user*/
464
-				$displayName = $user->getDisplayName();
465
-			}
466
-			return $displayName;
467
-		}
468
-
469
-		return null;
470
-	}
471
-
472
-	/**
473
-	 * Get a list of all display names
474
-	 *
475
-	 * @param string $search
476
-	 * @param string|null $limit
477
-	 * @param string|null $offset
478
-	 * @return array an array of all displayNames (value) and the corresponding uids (key)
479
-	 */
480
-	public function getDisplayNames($search = '', $limit = null, $offset = null) {
481
-		$cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset;
482
-		if(!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
483
-			return $displayNames;
484
-		}
485
-
486
-		$displayNames = array();
487
-		$users = $this->getUsers($search, $limit, $offset);
488
-		foreach ($users as $user) {
489
-			$displayNames[$user] = $this->getDisplayName($user);
490
-		}
491
-		$this->access->connection->writeToCache($cacheKey, $displayNames);
492
-		return $displayNames;
493
-	}
494
-
495
-	/**
496
-	* Check if backend implements actions
497
-	* @param int $actions bitwise-or'ed actions
498
-	* @return boolean
499
-	*
500
-	* Returns the supported actions as int to be
501
-	* compared with \OC\User\Backend::CREATE_USER etc.
502
-	*/
503
-	public function implementsActions($actions) {
504
-		return (bool)((Backend::CHECK_PASSWORD
505
-			| Backend::GET_HOME
506
-			| Backend::GET_DISPLAYNAME
507
-			| Backend::PROVIDE_AVATAR
508
-			| Backend::COUNT_USERS
509
-			| ((intval($this->access->connection->turnOnPasswordChange) === 1)?(Backend::SET_PASSWORD):0))
510
-			& $actions);
511
-	}
512
-
513
-	/**
514
-	 * @return bool
515
-	 */
516
-	public function hasUserListings() {
517
-		return true;
518
-	}
519
-
520
-	/**
521
-	 * counts the users in LDAP
522
-	 *
523
-	 * @return int|bool
524
-	 */
525
-	public function countUsers() {
526
-		$filter = $this->access->getFilterForUserCount();
527
-		$cacheKey = 'countUsers-'.$filter;
528
-		if(!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
529
-			return $entries;
530
-		}
531
-		$entries = $this->access->countUsers($filter);
532
-		$this->access->connection->writeToCache($cacheKey, $entries);
533
-		return $entries;
534
-	}
535
-
536
-	/**
537
-	 * Backend name to be shown in user management
538
-	 * @return string the name of the backend to be shown
539
-	 */
540
-	public function getBackendName(){
541
-		return 'LDAP';
542
-	}
133
+    /**
134
+     * returns the username for the given LDAP DN, if available
135
+     *
136
+     * @param string $dn
137
+     * @return string|false with the username
138
+     */
139
+    public function dn2UserName($dn) {
140
+        return $this->access->dn2username($dn);
141
+    }
142
+
143
+    /**
144
+     * returns an LDAP record based on a given login name
145
+     *
146
+     * @param string $loginName
147
+     * @return array
148
+     * @throws NotOnLDAP
149
+     */
150
+    public function getLDAPUserByLoginName($loginName) {
151
+        //find out dn of the user name
152
+        $attrs = $this->access->userManager->getAttributes();
153
+        $users = $this->access->fetchUsersByLoginName($loginName, $attrs);
154
+        if(count($users) < 1) {
155
+            throw new NotOnLDAP('No user available for the given login name on ' .
156
+                $this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort);
157
+        }
158
+        return $users[0];
159
+    }
160
+
161
+    /**
162
+     * Check if the password is correct without logging in the user
163
+     *
164
+     * @param string $uid The username
165
+     * @param string $password The password
166
+     * @return false|string
167
+     */
168
+    public function checkPassword($uid, $password) {
169
+        try {
170
+            $ldapRecord = $this->getLDAPUserByLoginName($uid);
171
+        } catch(NotOnLDAP $e) {
172
+            if($this->ocConfig->getSystemValue('loglevel', Util::WARN) === Util::DEBUG) {
173
+                \OC::$server->getLogger()->logException($e, ['app' => 'user_ldap']);
174
+            }
175
+            return false;
176
+        }
177
+        $dn = $ldapRecord['dn'][0];
178
+        $user = $this->access->userManager->get($dn);
179
+
180
+        if(!$user instanceof User) {
181
+            Util::writeLog('user_ldap',
182
+                'LDAP Login: Could not get user object for DN ' . $dn .
183
+                '. Maybe the LDAP entry has no set display name attribute?',
184
+                Util::WARN);
185
+            return false;
186
+        }
187
+        if($user->getUsername() !== false) {
188
+            //are the credentials OK?
189
+            if(!$this->access->areCredentialsValid($dn, $password)) {
190
+                return false;
191
+            }
192
+
193
+            $this->access->cacheUserExists($user->getUsername());
194
+            $user->processAttributes($ldapRecord);
195
+            $user->markLogin();
196
+
197
+            return $user->getUsername();
198
+        }
199
+
200
+        return false;
201
+    }
202
+
203
+    /**
204
+     * Set password
205
+     * @param string $uid The username
206
+     * @param string $password The new password
207
+     * @return bool
208
+     */
209
+    public function setPassword($uid, $password) {
210
+        $user = $this->access->userManager->get($uid);
211
+
212
+        if(!$user instanceof User) {
213
+            throw new \Exception('LDAP setPassword: Could not get user object for uid ' . $uid .
214
+                '. Maybe the LDAP entry has no set display name attribute?');
215
+        }
216
+        if($user->getUsername() !== false && $this->access->setPassword($user->getDN(), $password)) {
217
+            $ldapDefaultPPolicyDN = $this->access->connection->ldapDefaultPPolicyDN;
218
+            $turnOnPasswordChange = $this->access->connection->turnOnPasswordChange;
219
+            if (!empty($ldapDefaultPPolicyDN) && (intval($turnOnPasswordChange) === 1)) {
220
+                //remove last password expiry warning if any
221
+                $notification = $this->notificationManager->createNotification();
222
+                $notification->setApp('user_ldap')
223
+                    ->setUser($uid)
224
+                    ->setObject('pwd_exp_warn', $uid)
225
+                ;
226
+                $this->notificationManager->markProcessed($notification);
227
+            }
228
+            return true;
229
+        }
230
+
231
+        return false;
232
+    }
233
+
234
+    /**
235
+     * Get a list of all users
236
+     *
237
+     * @param string $search
238
+     * @param integer $limit
239
+     * @param integer $offset
240
+     * @return string[] an array of all uids
241
+     */
242
+    public function getUsers($search = '', $limit = 10, $offset = 0) {
243
+        $search = $this->access->escapeFilterPart($search, true);
244
+        $cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset;
245
+
246
+        //check if users are cached, if so return
247
+        $ldap_users = $this->access->connection->getFromCache($cachekey);
248
+        if(!is_null($ldap_users)) {
249
+            return $ldap_users;
250
+        }
251
+
252
+        // if we'd pass -1 to LDAP search, we'd end up in a Protocol
253
+        // error. With a limit of 0, we get 0 results. So we pass null.
254
+        if($limit <= 0) {
255
+            $limit = null;
256
+        }
257
+        $filter = $this->access->combineFilterWithAnd(array(
258
+            $this->access->connection->ldapUserFilter,
259
+            $this->access->connection->ldapUserDisplayName . '=*',
260
+            $this->access->getFilterPartForUserSearch($search)
261
+        ));
262
+
263
+        Util::writeLog('user_ldap',
264
+            'getUsers: Options: search '.$search.' limit '.$limit.' offset '.$offset.' Filter: '.$filter,
265
+            Util::DEBUG);
266
+        //do the search and translate results to Nextcloud names
267
+        $ldap_users = $this->access->fetchListOfUsers(
268
+            $filter,
269
+            $this->access->userManager->getAttributes(true),
270
+            $limit, $offset);
271
+        $ldap_users = $this->access->nextcloudUserNames($ldap_users);
272
+        Util::writeLog('user_ldap', 'getUsers: '.count($ldap_users). ' Users found', Util::DEBUG);
273
+
274
+        $this->access->connection->writeToCache($cachekey, $ldap_users);
275
+        return $ldap_users;
276
+    }
277
+
278
+    /**
279
+     * checks whether a user is still available on LDAP
280
+     *
281
+     * @param string|\OCA\User_LDAP\User\User $user either the Nextcloud user
282
+     * name or an instance of that user
283
+     * @return bool
284
+     * @throws \Exception
285
+     * @throws \OC\ServerNotAvailableException
286
+     */
287
+    public function userExistsOnLDAP($user) {
288
+        if(is_string($user)) {
289
+            $user = $this->access->userManager->get($user);
290
+        }
291
+        if(is_null($user)) {
292
+            return false;
293
+        }
294
+
295
+        $dn = $user->getDN();
296
+        //check if user really still exists by reading its entry
297
+        if(!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) {
298
+            $lcr = $this->access->connection->getConnectionResource();
299
+            if(is_null($lcr)) {
300
+                throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost);
301
+            }
302
+
303
+            try {
304
+                $uuid = $this->access->getUserMapper()->getUUIDByDN($dn);
305
+                if(!$uuid) {
306
+                    return false;
307
+                }
308
+                $newDn = $this->access->getUserDnByUuid($uuid);
309
+                //check if renamed user is still valid by reapplying the ldap filter
310
+                if(!is_array($this->access->readAttribute($newDn, '', $this->access->connection->ldapUserFilter))) {
311
+                    return false;
312
+                }
313
+                $this->access->getUserMapper()->setDNbyUUID($newDn, $uuid);
314
+                return true;
315
+            } catch (\Exception $e) {
316
+                return false;
317
+            }
318
+        }
319
+
320
+        if($user instanceof OfflineUser) {
321
+            $user->unmark();
322
+        }
323
+
324
+        return true;
325
+    }
326
+
327
+    /**
328
+     * check if a user exists
329
+     * @param string $uid the username
330
+     * @return boolean
331
+     * @throws \Exception when connection could not be established
332
+     */
333
+    public function userExists($uid) {
334
+        $userExists = $this->access->connection->getFromCache('userExists'.$uid);
335
+        if(!is_null($userExists)) {
336
+            return (bool)$userExists;
337
+        }
338
+        //getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking.
339
+        $user = $this->access->userManager->get($uid);
340
+
341
+        if(is_null($user)) {
342
+            Util::writeLog('user_ldap', 'No DN found for '.$uid.' on '.
343
+                $this->access->connection->ldapHost, Util::DEBUG);
344
+            $this->access->connection->writeToCache('userExists'.$uid, false);
345
+            return false;
346
+        } else if($user instanceof OfflineUser) {
347
+            //express check for users marked as deleted. Returning true is
348
+            //necessary for cleanup
349
+            return true;
350
+        }
351
+
352
+        $result = $this->userExistsOnLDAP($user);
353
+        $this->access->connection->writeToCache('userExists'.$uid, $result);
354
+        if($result === true) {
355
+            $user->update();
356
+        }
357
+        return $result;
358
+    }
359
+
360
+    /**
361
+     * returns whether a user was deleted in LDAP
362
+     *
363
+     * @param string $uid The username of the user to delete
364
+     * @return bool
365
+     */
366
+    public function deleteUser($uid) {
367
+        $marked = $this->ocConfig->getUserValue($uid, 'user_ldap', 'isDeleted', 0);
368
+        if(intval($marked) === 0) {
369
+            \OC::$server->getLogger()->notice(
370
+                'User '.$uid . ' is not marked as deleted, not cleaning up.',
371
+                array('app' => 'user_ldap'));
372
+            return false;
373
+        }
374
+        \OC::$server->getLogger()->info('Cleaning up after user ' . $uid,
375
+            array('app' => 'user_ldap'));
376
+
377
+        $this->access->getUserMapper()->unmap($uid);
378
+        $this->access->userManager->invalidate($uid);
379
+        return true;
380
+    }
381
+
382
+    /**
383
+     * get the user's home directory
384
+     *
385
+     * @param string $uid the username
386
+     * @return bool|string
387
+     * @throws NoUserException
388
+     * @throws \Exception
389
+     */
390
+    public function getHome($uid) {
391
+        // user Exists check required as it is not done in user proxy!
392
+        if(!$this->userExists($uid)) {
393
+            return false;
394
+        }
395
+
396
+        $cacheKey = 'getHome'.$uid;
397
+        $path = $this->access->connection->getFromCache($cacheKey);
398
+        if(!is_null($path)) {
399
+            return $path;
400
+        }
401
+
402
+        // early return path if it is a deleted user
403
+        $user = $this->access->userManager->get($uid);
404
+        if($user instanceof OfflineUser) {
405
+            if($this->currentUserInDeletionProcess !== null
406
+                && $this->currentUserInDeletionProcess === $user->getOCName()
407
+            ) {
408
+                return $user->getHomePath();
409
+            } else {
410
+                throw new NoUserException($uid . ' is not a valid user anymore');
411
+            }
412
+        } else if ($user === null) {
413
+            throw new NoUserException($uid . ' is not a valid user anymore');
414
+        }
415
+
416
+        $path = $user->getHomePath();
417
+        $this->access->cacheUserHome($uid, $path);
418
+
419
+        return $path;
420
+    }
421
+
422
+    /**
423
+     * get display name of the user
424
+     * @param string $uid user ID of the user
425
+     * @return string|false display name
426
+     */
427
+    public function getDisplayName($uid) {
428
+        if(!$this->userExists($uid)) {
429
+            return false;
430
+        }
431
+
432
+        $cacheKey = 'getDisplayName'.$uid;
433
+        if(!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
434
+            return $displayName;
435
+        }
436
+
437
+        //Check whether the display name is configured to have a 2nd feature
438
+        $additionalAttribute = $this->access->connection->ldapUserDisplayName2;
439
+        $displayName2 = '';
440
+        if ($additionalAttribute !== '') {
441
+            $displayName2 = $this->access->readAttribute(
442
+                $this->access->username2dn($uid),
443
+                $additionalAttribute);
444
+        }
445
+
446
+        $displayName = $this->access->readAttribute(
447
+            $this->access->username2dn($uid),
448
+            $this->access->connection->ldapUserDisplayName);
449
+
450
+        if($displayName && (count($displayName) > 0)) {
451
+            $displayName = $displayName[0];
452
+
453
+            if (is_array($displayName2)){
454
+                $displayName2 = count($displayName2) > 0 ? $displayName2[0] : '';
455
+            }
456
+
457
+            $user = $this->access->userManager->get($uid);
458
+            if ($user instanceof User) {
459
+                $displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
460
+                $this->access->connection->writeToCache($cacheKey, $displayName);
461
+            }
462
+            if ($user instanceof OfflineUser) {
463
+                /** @var OfflineUser $user*/
464
+                $displayName = $user->getDisplayName();
465
+            }
466
+            return $displayName;
467
+        }
468
+
469
+        return null;
470
+    }
471
+
472
+    /**
473
+     * Get a list of all display names
474
+     *
475
+     * @param string $search
476
+     * @param string|null $limit
477
+     * @param string|null $offset
478
+     * @return array an array of all displayNames (value) and the corresponding uids (key)
479
+     */
480
+    public function getDisplayNames($search = '', $limit = null, $offset = null) {
481
+        $cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset;
482
+        if(!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
483
+            return $displayNames;
484
+        }
485
+
486
+        $displayNames = array();
487
+        $users = $this->getUsers($search, $limit, $offset);
488
+        foreach ($users as $user) {
489
+            $displayNames[$user] = $this->getDisplayName($user);
490
+        }
491
+        $this->access->connection->writeToCache($cacheKey, $displayNames);
492
+        return $displayNames;
493
+    }
494
+
495
+    /**
496
+     * Check if backend implements actions
497
+     * @param int $actions bitwise-or'ed actions
498
+     * @return boolean
499
+     *
500
+     * Returns the supported actions as int to be
501
+     * compared with \OC\User\Backend::CREATE_USER etc.
502
+     */
503
+    public function implementsActions($actions) {
504
+        return (bool)((Backend::CHECK_PASSWORD
505
+            | Backend::GET_HOME
506
+            | Backend::GET_DISPLAYNAME
507
+            | Backend::PROVIDE_AVATAR
508
+            | Backend::COUNT_USERS
509
+            | ((intval($this->access->connection->turnOnPasswordChange) === 1)?(Backend::SET_PASSWORD):0))
510
+            & $actions);
511
+    }
512
+
513
+    /**
514
+     * @return bool
515
+     */
516
+    public function hasUserListings() {
517
+        return true;
518
+    }
519
+
520
+    /**
521
+     * counts the users in LDAP
522
+     *
523
+     * @return int|bool
524
+     */
525
+    public function countUsers() {
526
+        $filter = $this->access->getFilterForUserCount();
527
+        $cacheKey = 'countUsers-'.$filter;
528
+        if(!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
529
+            return $entries;
530
+        }
531
+        $entries = $this->access->countUsers($filter);
532
+        $this->access->connection->writeToCache($cacheKey, $entries);
533
+        return $entries;
534
+    }
535
+
536
+    /**
537
+     * Backend name to be shown in user management
538
+     * @return string the name of the backend to be shown
539
+     */
540
+    public function getBackendName(){
541
+        return 'LDAP';
542
+    }
543 543
 	
544
-	/**
545
-	 * Return access for LDAP interaction.
546
-	 * @param string $uid
547
-	 * @return Access instance of Access for LDAP interaction
548
-	 */
549
-	public function getLDAPAccess($uid) {
550
-		return $this->access;
551
-	}
544
+    /**
545
+     * Return access for LDAP interaction.
546
+     * @param string $uid
547
+     * @return Access instance of Access for LDAP interaction
548
+     */
549
+    public function getLDAPAccess($uid) {
550
+        return $this->access;
551
+    }
552 552
 	
553
-	/**
554
-	 * Return LDAP connection resource from a cloned connection.
555
-	 * The cloned connection needs to be closed manually.
556
-	 * of the current access.
557
-	 * @param string $uid
558
-	 * @return resource of the LDAP connection
559
-	 */
560
-	public function getNewLDAPConnection($uid) {
561
-		$connection = clone $this->access->getConnection();
562
-		return $connection->getConnectionResource();
563
-	}
553
+    /**
554
+     * Return LDAP connection resource from a cloned connection.
555
+     * The cloned connection needs to be closed manually.
556
+     * of the current access.
557
+     * @param string $uid
558
+     * @return resource of the LDAP connection
559
+     */
560
+    public function getNewLDAPConnection($uid) {
561
+        $connection = clone $this->access->getConnection();
562
+        return $connection->getConnectionResource();
563
+    }
564 564
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/User/Manager.php 1 patch
Indentation   +193 added lines, -193 removed lines patch added patch discarded remove patch
@@ -44,223 +44,223 @@
 block discarded – undo
44 44
  * cache
45 45
  */
46 46
 class Manager {
47
-	/** @var IUserTools */
48
-	protected $access;
47
+    /** @var IUserTools */
48
+    protected $access;
49 49
 
50
-	/** @var IConfig */
51
-	protected $ocConfig;
50
+    /** @var IConfig */
51
+    protected $ocConfig;
52 52
 
53
-	/** @var IDBConnection */
54
-	protected $db;
53
+    /** @var IDBConnection */
54
+    protected $db;
55 55
 
56
-	/** @var IUserManager */
57
-	protected $userManager;
56
+    /** @var IUserManager */
57
+    protected $userManager;
58 58
 
59
-	/** @var INotificationManager */
60
-	protected $notificationManager;
59
+    /** @var INotificationManager */
60
+    protected $notificationManager;
61 61
 
62
-	/** @var FilesystemHelper */
63
-	protected $ocFilesystem;
62
+    /** @var FilesystemHelper */
63
+    protected $ocFilesystem;
64 64
 
65
-	/** @var LogWrapper */
66
-	protected $ocLog;
65
+    /** @var LogWrapper */
66
+    protected $ocLog;
67 67
 
68
-	/** @var Image */
69
-	protected $image;
68
+    /** @var Image */
69
+    protected $image;
70 70
 
71
-	/** @param \OCP\IAvatarManager */
72
-	protected $avatarManager;
71
+    /** @param \OCP\IAvatarManager */
72
+    protected $avatarManager;
73 73
 
74
-	/**
75
-	 * @var CappedMemoryCache $usersByDN
76
-	 */
77
-	protected $usersByDN;
78
-	/**
79
-	 * @var CappedMemoryCache $usersByUid
80
-	 */
81
-	protected $usersByUid;
74
+    /**
75
+     * @var CappedMemoryCache $usersByDN
76
+     */
77
+    protected $usersByDN;
78
+    /**
79
+     * @var CappedMemoryCache $usersByUid
80
+     */
81
+    protected $usersByUid;
82 82
 
83
-	/**
84
-	 * @param IConfig $ocConfig
85
-	 * @param \OCA\User_LDAP\FilesystemHelper $ocFilesystem object that
86
-	 * gives access to necessary functions from the OC filesystem
87
-	 * @param  \OCA\User_LDAP\LogWrapper $ocLog
88
-	 * @param IAvatarManager $avatarManager
89
-	 * @param Image $image an empty image instance
90
-	 * @param IDBConnection $db
91
-	 * @throws \Exception when the methods mentioned above do not exist
92
-	 */
93
-	public function __construct(IConfig $ocConfig,
94
-								FilesystemHelper $ocFilesystem, LogWrapper $ocLog,
95
-								IAvatarManager $avatarManager, Image $image,
96
-								IDBConnection $db, IUserManager $userManager,
97
-								INotificationManager $notificationManager) {
83
+    /**
84
+     * @param IConfig $ocConfig
85
+     * @param \OCA\User_LDAP\FilesystemHelper $ocFilesystem object that
86
+     * gives access to necessary functions from the OC filesystem
87
+     * @param  \OCA\User_LDAP\LogWrapper $ocLog
88
+     * @param IAvatarManager $avatarManager
89
+     * @param Image $image an empty image instance
90
+     * @param IDBConnection $db
91
+     * @throws \Exception when the methods mentioned above do not exist
92
+     */
93
+    public function __construct(IConfig $ocConfig,
94
+                                FilesystemHelper $ocFilesystem, LogWrapper $ocLog,
95
+                                IAvatarManager $avatarManager, Image $image,
96
+                                IDBConnection $db, IUserManager $userManager,
97
+                                INotificationManager $notificationManager) {
98 98
 
99
-		$this->ocConfig            = $ocConfig;
100
-		$this->ocFilesystem        = $ocFilesystem;
101
-		$this->ocLog               = $ocLog;
102
-		$this->avatarManager       = $avatarManager;
103
-		$this->image               = $image;
104
-		$this->db                  = $db;
105
-		$this->userManager         = $userManager;
106
-		$this->notificationManager = $notificationManager;
107
-		$this->usersByDN           = new CappedMemoryCache();
108
-		$this->usersByUid          = new CappedMemoryCache();
109
-	}
99
+        $this->ocConfig            = $ocConfig;
100
+        $this->ocFilesystem        = $ocFilesystem;
101
+        $this->ocLog               = $ocLog;
102
+        $this->avatarManager       = $avatarManager;
103
+        $this->image               = $image;
104
+        $this->db                  = $db;
105
+        $this->userManager         = $userManager;
106
+        $this->notificationManager = $notificationManager;
107
+        $this->usersByDN           = new CappedMemoryCache();
108
+        $this->usersByUid          = new CappedMemoryCache();
109
+    }
110 110
 
111
-	/**
112
-	 * @brief binds manager to an instance of IUserTools (implemented by
113
-	 * Access). It needs to be assigned first before the manager can be used.
114
-	 * @param IUserTools
115
-	 */
116
-	public function setLdapAccess(IUserTools $access) {
117
-		$this->access = $access;
118
-	}
111
+    /**
112
+     * @brief binds manager to an instance of IUserTools (implemented by
113
+     * Access). It needs to be assigned first before the manager can be used.
114
+     * @param IUserTools
115
+     */
116
+    public function setLdapAccess(IUserTools $access) {
117
+        $this->access = $access;
118
+    }
119 119
 
120
-	/**
121
-	 * @brief creates an instance of User and caches (just runtime) it in the
122
-	 * property array
123
-	 * @param string $dn the DN of the user
124
-	 * @param string $uid the internal (owncloud) username
125
-	 * @return \OCA\User_LDAP\User\User
126
-	 */
127
-	private function createAndCache($dn, $uid) {
128
-		$this->checkAccess();
129
-		$user = new User($uid, $dn, $this->access, $this->ocConfig,
130
-			$this->ocFilesystem, clone $this->image, $this->ocLog,
131
-			$this->avatarManager, $this->userManager, 
132
-			$this->notificationManager);
133
-		$this->usersByDN[$dn]   = $user;
134
-		$this->usersByUid[$uid] = $user;
135
-		return $user;
136
-	}
120
+    /**
121
+     * @brief creates an instance of User and caches (just runtime) it in the
122
+     * property array
123
+     * @param string $dn the DN of the user
124
+     * @param string $uid the internal (owncloud) username
125
+     * @return \OCA\User_LDAP\User\User
126
+     */
127
+    private function createAndCache($dn, $uid) {
128
+        $this->checkAccess();
129
+        $user = new User($uid, $dn, $this->access, $this->ocConfig,
130
+            $this->ocFilesystem, clone $this->image, $this->ocLog,
131
+            $this->avatarManager, $this->userManager, 
132
+            $this->notificationManager);
133
+        $this->usersByDN[$dn]   = $user;
134
+        $this->usersByUid[$uid] = $user;
135
+        return $user;
136
+    }
137 137
 
138
-	/**
139
-	 * removes a user entry from the cache
140
-	 * @param $uid
141
-	 */
142
-	public function invalidate($uid) {
143
-		if(!isset($this->usersByUid[$uid])) {
144
-			return;
145
-		}
146
-		$dn = $this->usersByUid[$uid]->getDN();
147
-		unset($this->usersByUid[$uid]);
148
-		unset($this->usersByDN[$dn]);
149
-	}
138
+    /**
139
+     * removes a user entry from the cache
140
+     * @param $uid
141
+     */
142
+    public function invalidate($uid) {
143
+        if(!isset($this->usersByUid[$uid])) {
144
+            return;
145
+        }
146
+        $dn = $this->usersByUid[$uid]->getDN();
147
+        unset($this->usersByUid[$uid]);
148
+        unset($this->usersByDN[$dn]);
149
+    }
150 150
 
151
-	/**
152
-	 * @brief checks whether the Access instance has been set
153
-	 * @throws \Exception if Access has not been set
154
-	 * @return null
155
-	 */
156
-	private function checkAccess() {
157
-		if(is_null($this->access)) {
158
-			throw new \Exception('LDAP Access instance must be set first');
159
-		}
160
-	}
151
+    /**
152
+     * @brief checks whether the Access instance has been set
153
+     * @throws \Exception if Access has not been set
154
+     * @return null
155
+     */
156
+    private function checkAccess() {
157
+        if(is_null($this->access)) {
158
+            throw new \Exception('LDAP Access instance must be set first');
159
+        }
160
+    }
161 161
 
162
-	/**
163
-	 * returns a list of attributes that will be processed further, e.g. quota,
164
-	 * email, displayname, or others.
165
-	 * @param bool $minimal - optional, set to true to skip attributes with big
166
-	 * payload
167
-	 * @return string[]
168
-	 */
169
-	public function getAttributes($minimal = false) {
170
-		$attributes = array_merge(Access::UUID_ATTRIBUTES, ['dn', 'uid', 'samaccountname', 'memberof']);
171
-		$possible = array(
172
-			$this->access->getConnection()->ldapExpertUUIDUserAttr,
173
-			$this->access->getConnection()->ldapQuotaAttribute,
174
-			$this->access->getConnection()->ldapEmailAttribute,
175
-			$this->access->getConnection()->ldapUserDisplayName,
176
-			$this->access->getConnection()->ldapUserDisplayName2,
177
-		);
178
-		foreach($possible as $attr) {
179
-			if(!is_null($attr)) {
180
-				$attributes[] = $attr;
181
-			}
182
-		}
162
+    /**
163
+     * returns a list of attributes that will be processed further, e.g. quota,
164
+     * email, displayname, or others.
165
+     * @param bool $minimal - optional, set to true to skip attributes with big
166
+     * payload
167
+     * @return string[]
168
+     */
169
+    public function getAttributes($minimal = false) {
170
+        $attributes = array_merge(Access::UUID_ATTRIBUTES, ['dn', 'uid', 'samaccountname', 'memberof']);
171
+        $possible = array(
172
+            $this->access->getConnection()->ldapExpertUUIDUserAttr,
173
+            $this->access->getConnection()->ldapQuotaAttribute,
174
+            $this->access->getConnection()->ldapEmailAttribute,
175
+            $this->access->getConnection()->ldapUserDisplayName,
176
+            $this->access->getConnection()->ldapUserDisplayName2,
177
+        );
178
+        foreach($possible as $attr) {
179
+            if(!is_null($attr)) {
180
+                $attributes[] = $attr;
181
+            }
182
+        }
183 183
 
184
-		$homeRule = $this->access->getConnection()->homeFolderNamingRule;
185
-		if(strpos($homeRule, 'attr:') === 0) {
186
-			$attributes[] = substr($homeRule, strlen('attr:'));
187
-		}
184
+        $homeRule = $this->access->getConnection()->homeFolderNamingRule;
185
+        if(strpos($homeRule, 'attr:') === 0) {
186
+            $attributes[] = substr($homeRule, strlen('attr:'));
187
+        }
188 188
 
189
-		if(!$minimal) {
190
-			// attributes that are not really important but may come with big
191
-			// payload.
192
-			$attributes = array_merge($attributes, array(
193
-				'jpegphoto',
194
-				'thumbnailphoto'
195
-			));
196
-		}
189
+        if(!$minimal) {
190
+            // attributes that are not really important but may come with big
191
+            // payload.
192
+            $attributes = array_merge($attributes, array(
193
+                'jpegphoto',
194
+                'thumbnailphoto'
195
+            ));
196
+        }
197 197
 
198
-		return $attributes;
199
-	}
198
+        return $attributes;
199
+    }
200 200
 
201
-	/**
202
-	 * Checks whether the specified user is marked as deleted
203
-	 * @param string $id the Nextcloud user name
204
-	 * @return bool
205
-	 */
206
-	public function isDeletedUser($id) {
207
-		$isDeleted = $this->ocConfig->getUserValue(
208
-			$id, 'user_ldap', 'isDeleted', 0);
209
-		return intval($isDeleted) === 1;
210
-	}
201
+    /**
202
+     * Checks whether the specified user is marked as deleted
203
+     * @param string $id the Nextcloud user name
204
+     * @return bool
205
+     */
206
+    public function isDeletedUser($id) {
207
+        $isDeleted = $this->ocConfig->getUserValue(
208
+            $id, 'user_ldap', 'isDeleted', 0);
209
+        return intval($isDeleted) === 1;
210
+    }
211 211
 
212
-	/**
213
-	 * creates and returns an instance of OfflineUser for the specified user
214
-	 * @param string $id
215
-	 * @return \OCA\User_LDAP\User\OfflineUser
216
-	 */
217
-	public function getDeletedUser($id) {
218
-		return new OfflineUser(
219
-			$id,
220
-			$this->ocConfig,
221
-			$this->db,
222
-			$this->access->getUserMapper());
223
-	}
212
+    /**
213
+     * creates and returns an instance of OfflineUser for the specified user
214
+     * @param string $id
215
+     * @return \OCA\User_LDAP\User\OfflineUser
216
+     */
217
+    public function getDeletedUser($id) {
218
+        return new OfflineUser(
219
+            $id,
220
+            $this->ocConfig,
221
+            $this->db,
222
+            $this->access->getUserMapper());
223
+    }
224 224
 
225
-	/**
226
-	 * @brief returns a User object by it's Nextcloud username
227
-	 * @param string $id the DN or username of the user
228
-	 * @return \OCA\User_LDAP\User\User|\OCA\User_LDAP\User\OfflineUser|null
229
-	 */
230
-	protected function createInstancyByUserName($id) {
231
-		//most likely a uid. Check whether it is a deleted user
232
-		if($this->isDeletedUser($id)) {
233
-			return $this->getDeletedUser($id);
234
-		}
235
-		$dn = $this->access->username2dn($id);
236
-		if($dn !== false) {
237
-			return $this->createAndCache($dn, $id);
238
-		}
239
-		return null;
240
-	}
225
+    /**
226
+     * @brief returns a User object by it's Nextcloud username
227
+     * @param string $id the DN or username of the user
228
+     * @return \OCA\User_LDAP\User\User|\OCA\User_LDAP\User\OfflineUser|null
229
+     */
230
+    protected function createInstancyByUserName($id) {
231
+        //most likely a uid. Check whether it is a deleted user
232
+        if($this->isDeletedUser($id)) {
233
+            return $this->getDeletedUser($id);
234
+        }
235
+        $dn = $this->access->username2dn($id);
236
+        if($dn !== false) {
237
+            return $this->createAndCache($dn, $id);
238
+        }
239
+        return null;
240
+    }
241 241
 
242
-	/**
243
-	 * @brief returns a User object by it's DN or Nextcloud username
244
-	 * @param string $id the DN or username of the user
245
-	 * @return \OCA\User_LDAP\User\User|\OCA\User_LDAP\User\OfflineUser|null
246
-	 * @throws \Exception when connection could not be established
247
-	 */
248
-	public function get($id) {
249
-		$this->checkAccess();
250
-		if(isset($this->usersByDN[$id])) {
251
-			return $this->usersByDN[$id];
252
-		} else if(isset($this->usersByUid[$id])) {
253
-			return $this->usersByUid[$id];
254
-		}
242
+    /**
243
+     * @brief returns a User object by it's DN or Nextcloud username
244
+     * @param string $id the DN or username of the user
245
+     * @return \OCA\User_LDAP\User\User|\OCA\User_LDAP\User\OfflineUser|null
246
+     * @throws \Exception when connection could not be established
247
+     */
248
+    public function get($id) {
249
+        $this->checkAccess();
250
+        if(isset($this->usersByDN[$id])) {
251
+            return $this->usersByDN[$id];
252
+        } else if(isset($this->usersByUid[$id])) {
253
+            return $this->usersByUid[$id];
254
+        }
255 255
 
256
-		if($this->access->stringResemblesDN($id) ) {
257
-			$uid = $this->access->dn2username($id);
258
-			if($uid !== false) {
259
-				return $this->createAndCache($id, $uid);
260
-			}
261
-		}
256
+        if($this->access->stringResemblesDN($id) ) {
257
+            $uid = $this->access->dn2username($id);
258
+            if($uid !== false) {
259
+                return $this->createAndCache($id, $uid);
260
+            }
261
+        }
262 262
 
263
-		return $this->createInstancyByUserName($id);
264
-	}
263
+        return $this->createInstancyByUserName($id);
264
+    }
265 265
 
266 266
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/User/User.php 1 patch
Indentation   +644 added lines, -644 removed lines patch added patch discarded remove patch
@@ -43,654 +43,654 @@
 block discarded – undo
43 43
  * represents an LDAP user, gets and holds user-specific information from LDAP
44 44
  */
45 45
 class User {
46
-	/**
47
-	 * @var IUserTools
48
-	 */
49
-	protected $access;
50
-	/**
51
-	 * @var Connection
52
-	 */
53
-	protected $connection;
54
-	/**
55
-	 * @var IConfig
56
-	 */
57
-	protected $config;
58
-	/**
59
-	 * @var FilesystemHelper
60
-	 */
61
-	protected $fs;
62
-	/**
63
-	 * @var Image
64
-	 */
65
-	protected $image;
66
-	/**
67
-	 * @var LogWrapper
68
-	 */
69
-	protected $log;
70
-	/**
71
-	 * @var IAvatarManager
72
-	 */
73
-	protected $avatarManager;
74
-	/**
75
-	 * @var IUserManager
76
-	 */
77
-	protected $userManager;
78
-	/**
79
-	 * @var INotificationManager
80
-	 */
81
-	protected $notificationManager;
82
-	/**
83
-	 * @var string
84
-	 */
85
-	protected $dn;
86
-	/**
87
-	 * @var string
88
-	 */
89
-	protected $uid;
90
-	/**
91
-	 * @var string[]
92
-	 */
93
-	protected $refreshedFeatures = array();
94
-	/**
95
-	 * @var string
96
-	 */
97
-	protected $avatarImage;
98
-
99
-	/**
100
-	 * DB config keys for user preferences
101
-	 */
102
-	const USER_PREFKEY_FIRSTLOGIN  = 'firstLoginAccomplished';
103
-	const USER_PREFKEY_LASTREFRESH = 'lastFeatureRefresh';
104
-
105
-	/**
106
-	 * @brief constructor, make sure the subclasses call this one!
107
-	 * @param string $username the internal username
108
-	 * @param string $dn the LDAP DN
109
-	 * @param IUserTools $access an instance that implements IUserTools for
110
-	 * LDAP interaction
111
-	 * @param IConfig $config
112
-	 * @param FilesystemHelper $fs
113
-	 * @param Image $image any empty instance
114
-	 * @param LogWrapper $log
115
-	 * @param IAvatarManager $avatarManager
116
-	 * @param IUserManager $userManager
117
-	 * @param INotificationManager $notificationManager
118
-	 */
119
-	public function __construct($username, $dn, IUserTools $access,
120
-		IConfig $config, FilesystemHelper $fs, Image $image,
121
-		LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager,
122
-		INotificationManager $notificationManager) {
46
+    /**
47
+     * @var IUserTools
48
+     */
49
+    protected $access;
50
+    /**
51
+     * @var Connection
52
+     */
53
+    protected $connection;
54
+    /**
55
+     * @var IConfig
56
+     */
57
+    protected $config;
58
+    /**
59
+     * @var FilesystemHelper
60
+     */
61
+    protected $fs;
62
+    /**
63
+     * @var Image
64
+     */
65
+    protected $image;
66
+    /**
67
+     * @var LogWrapper
68
+     */
69
+    protected $log;
70
+    /**
71
+     * @var IAvatarManager
72
+     */
73
+    protected $avatarManager;
74
+    /**
75
+     * @var IUserManager
76
+     */
77
+    protected $userManager;
78
+    /**
79
+     * @var INotificationManager
80
+     */
81
+    protected $notificationManager;
82
+    /**
83
+     * @var string
84
+     */
85
+    protected $dn;
86
+    /**
87
+     * @var string
88
+     */
89
+    protected $uid;
90
+    /**
91
+     * @var string[]
92
+     */
93
+    protected $refreshedFeatures = array();
94
+    /**
95
+     * @var string
96
+     */
97
+    protected $avatarImage;
98
+
99
+    /**
100
+     * DB config keys for user preferences
101
+     */
102
+    const USER_PREFKEY_FIRSTLOGIN  = 'firstLoginAccomplished';
103
+    const USER_PREFKEY_LASTREFRESH = 'lastFeatureRefresh';
104
+
105
+    /**
106
+     * @brief constructor, make sure the subclasses call this one!
107
+     * @param string $username the internal username
108
+     * @param string $dn the LDAP DN
109
+     * @param IUserTools $access an instance that implements IUserTools for
110
+     * LDAP interaction
111
+     * @param IConfig $config
112
+     * @param FilesystemHelper $fs
113
+     * @param Image $image any empty instance
114
+     * @param LogWrapper $log
115
+     * @param IAvatarManager $avatarManager
116
+     * @param IUserManager $userManager
117
+     * @param INotificationManager $notificationManager
118
+     */
119
+    public function __construct($username, $dn, IUserTools $access,
120
+        IConfig $config, FilesystemHelper $fs, Image $image,
121
+        LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager,
122
+        INotificationManager $notificationManager) {
123 123
 	
124
-		if ($username === null) {
125
-			$log->log("uid for '$dn' must not be null!", Util::ERROR);
126
-			throw new \InvalidArgumentException('uid must not be null!');
127
-		} else if ($username === '') {
128
-			$log->log("uid for '$dn' must not be an empty string", Util::ERROR);
129
-			throw new \InvalidArgumentException('uid must not be an empty string!');
130
-		}
131
-
132
-		$this->access              = $access;
133
-		$this->connection          = $access->getConnection();
134
-		$this->config              = $config;
135
-		$this->fs                  = $fs;
136
-		$this->dn                  = $dn;
137
-		$this->uid                 = $username;
138
-		$this->image               = $image;
139
-		$this->log                 = $log;
140
-		$this->avatarManager       = $avatarManager;
141
-		$this->userManager         = $userManager;
142
-		$this->notificationManager = $notificationManager;
143
-
144
-		\OCP\Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
145
-	}
146
-
147
-	/**
148
-	 * @brief updates properties like email, quota or avatar provided by LDAP
149
-	 * @return null
150
-	 */
151
-	public function update() {
152
-		if(is_null($this->dn)) {
153
-			return null;
154
-		}
155
-
156
-		$hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap',
157
-				self::USER_PREFKEY_FIRSTLOGIN, 0);
158
-
159
-		if($this->needsRefresh()) {
160
-			$this->updateEmail();
161
-			$this->updateQuota();
162
-			if($hasLoggedIn !== 0) {
163
-				//we do not need to try it, when the user has not been logged in
164
-				//before, because the file system will not be ready.
165
-				$this->updateAvatar();
166
-				//in order to get an avatar as soon as possible, mark the user
167
-				//as refreshed only when updating the avatar did happen
168
-				$this->markRefreshTime();
169
-			}
170
-		}
171
-	}
172
-
173
-	/**
174
-	 * processes results from LDAP for attributes as returned by getAttributesToRead()
175
-	 * @param array $ldapEntry the user entry as retrieved from LDAP
176
-	 */
177
-	public function processAttributes($ldapEntry) {
178
-		$this->markRefreshTime();
179
-		//Quota
180
-		$attr = strtolower($this->connection->ldapQuotaAttribute);
181
-		if(isset($ldapEntry[$attr])) {
182
-			$this->updateQuota($ldapEntry[$attr][0]);
183
-		} else {
184
-			if ($this->connection->ldapQuotaDefault !== '') {
185
-				$this->updateQuota();
186
-			}
187
-		}
188
-		unset($attr);
189
-
190
-		//displayName
191
-		$displayName = $displayName2 = '';
192
-		$attr = strtolower($this->connection->ldapUserDisplayName);
193
-		if(isset($ldapEntry[$attr])) {
194
-			$displayName = strval($ldapEntry[$attr][0]);
195
-		}
196
-		$attr = strtolower($this->connection->ldapUserDisplayName2);
197
-		if(isset($ldapEntry[$attr])) {
198
-			$displayName2 = strval($ldapEntry[$attr][0]);
199
-		}
200
-		if ($displayName !== '') {
201
-			$this->composeAndStoreDisplayName($displayName);
202
-			$this->access->cacheUserDisplayName(
203
-				$this->getUsername(),
204
-				$displayName,
205
-				$displayName2
206
-			);
207
-		}
208
-		unset($attr);
209
-
210
-		//Email
211
-		//email must be stored after displayname, because it would cause a user
212
-		//change event that will trigger fetching the display name again
213
-		$attr = strtolower($this->connection->ldapEmailAttribute);
214
-		if(isset($ldapEntry[$attr])) {
215
-			$this->updateEmail($ldapEntry[$attr][0]);
216
-		}
217
-		unset($attr);
218
-
219
-		// LDAP Username, needed for s2s sharing
220
-		if(isset($ldapEntry['uid'])) {
221
-			$this->storeLDAPUserName($ldapEntry['uid'][0]);
222
-		} else if(isset($ldapEntry['samaccountname'])) {
223
-			$this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
224
-		}
225
-
226
-		//homePath
227
-		if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
228
-			$attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
229
-			if(isset($ldapEntry[$attr])) {
230
-				$this->access->cacheUserHome(
231
-					$this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
232
-			}
233
-		}
234
-
235
-		//memberOf groups
236
-		$cacheKey = 'getMemberOf'.$this->getUsername();
237
-		$groups = false;
238
-		if(isset($ldapEntry['memberof'])) {
239
-			$groups = $ldapEntry['memberof'];
240
-		}
241
-		$this->connection->writeToCache($cacheKey, $groups);
242
-
243
-		//Avatar
244
-		$attrs = array('jpegphoto', 'thumbnailphoto');
245
-		foreach ($attrs as $attr)  {
246
-			if(isset($ldapEntry[$attr])) {
247
-				$this->avatarImage = $ldapEntry[$attr][0];
248
-				// the call to the method that saves the avatar in the file
249
-				// system must be postponed after the login. It is to ensure
250
-				// external mounts are mounted properly (e.g. with login
251
-				// credentials from the session).
252
-				\OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin');
253
-				break;
254
-			}
255
-		}
256
-	}
257
-
258
-	/**
259
-	 * @brief returns the LDAP DN of the user
260
-	 * @return string
261
-	 */
262
-	public function getDN() {
263
-		return $this->dn;
264
-	}
265
-
266
-	/**
267
-	 * @brief returns the Nextcloud internal username of the user
268
-	 * @return string
269
-	 */
270
-	public function getUsername() {
271
-		return $this->uid;
272
-	}
273
-
274
-	/**
275
-	 * returns the home directory of the user if specified by LDAP settings
276
-	 * @param string $valueFromLDAP
277
-	 * @return bool|string
278
-	 * @throws \Exception
279
-	 */
280
-	public function getHomePath($valueFromLDAP = null) {
281
-		$path = strval($valueFromLDAP);
282
-		$attr = null;
283
-
284
-		if (is_null($valueFromLDAP)
285
-		   && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0
286
-		   && $this->access->connection->homeFolderNamingRule !== 'attr:')
287
-		{
288
-			$attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
289
-			$homedir = $this->access->readAttribute(
290
-				$this->access->username2dn($this->getUsername()), $attr);
291
-			if ($homedir && isset($homedir[0])) {
292
-				$path = $homedir[0];
293
-			}
294
-		}
295
-
296
-		if ($path !== '') {
297
-			//if attribute's value is an absolute path take this, otherwise append it to data dir
298
-			//check for / at the beginning or pattern c:\ resp. c:/
299
-			if(   '/' !== $path[0]
300
-			   && !(3 < strlen($path) && ctype_alpha($path[0])
301
-			       && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
302
-			) {
303
-				$path = $this->config->getSystemValue('datadirectory',
304
-						\OC::$SERVERROOT.'/data' ) . '/' . $path;
305
-			}
306
-			//we need it to store it in the DB as well in case a user gets
307
-			//deleted so we can clean up afterwards
308
-			$this->config->setUserValue(
309
-				$this->getUsername(), 'user_ldap', 'homePath', $path
310
-			);
311
-			return $path;
312
-		}
313
-
314
-		if(    !is_null($attr)
315
-			&& $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true)
316
-		) {
317
-			// a naming rule attribute is defined, but it doesn't exist for that LDAP user
318
-			throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
319
-		}
320
-
321
-		//false will apply default behaviour as defined and done by OC_User
322
-		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
323
-		return false;
324
-	}
325
-
326
-	public function getMemberOfGroups() {
327
-		$cacheKey = 'getMemberOf'.$this->getUsername();
328
-		$memberOfGroups = $this->connection->getFromCache($cacheKey);
329
-		if(!is_null($memberOfGroups)) {
330
-			return $memberOfGroups;
331
-		}
332
-		$groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
333
-		$this->connection->writeToCache($cacheKey, $groupDNs);
334
-		return $groupDNs;
335
-	}
336
-
337
-	/**
338
-	 * @brief reads the image from LDAP that shall be used as Avatar
339
-	 * @return string data (provided by LDAP) | false
340
-	 */
341
-	public function getAvatarImage() {
342
-		if(!is_null($this->avatarImage)) {
343
-			return $this->avatarImage;
344
-		}
345
-
346
-		$this->avatarImage = false;
347
-		$attributes = array('jpegPhoto', 'thumbnailPhoto');
348
-		foreach($attributes as $attribute) {
349
-			$result = $this->access->readAttribute($this->dn, $attribute);
350
-			if($result !== false && is_array($result) && isset($result[0])) {
351
-				$this->avatarImage = $result[0];
352
-				break;
353
-			}
354
-		}
355
-
356
-		return $this->avatarImage;
357
-	}
358
-
359
-	/**
360
-	 * @brief marks the user as having logged in at least once
361
-	 * @return null
362
-	 */
363
-	public function markLogin() {
364
-		$this->config->setUserValue(
365
-			$this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1);
366
-	}
367
-
368
-	/**
369
-	 * @brief marks the time when user features like email have been updated
370
-	 * @return null
371
-	 */
372
-	public function markRefreshTime() {
373
-		$this->config->setUserValue(
374
-			$this->uid, 'user_ldap', self::USER_PREFKEY_LASTREFRESH, time());
375
-	}
376
-
377
-	/**
378
-	 * @brief checks whether user features needs to be updated again by
379
-	 * comparing the difference of time of the last refresh to now with the
380
-	 * desired interval
381
-	 * @return bool
382
-	 */
383
-	private function needsRefresh() {
384
-		$lastChecked = $this->config->getUserValue($this->uid, 'user_ldap',
385
-			self::USER_PREFKEY_LASTREFRESH, 0);
386
-
387
-		//TODO make interval configurable
388
-		if((time() - intval($lastChecked)) < 86400 ) {
389
-			return false;
390
-		}
391
-		return  true;
392
-	}
393
-
394
-	/**
395
-	 * Stores a key-value pair in relation to this user
396
-	 *
397
-	 * @param string $key
398
-	 * @param string $value
399
-	 */
400
-	private function store($key, $value) {
401
-		$this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
402
-	}
403
-
404
-	/**
405
-	 * Composes the display name and stores it in the database. The final
406
-	 * display name is returned.
407
-	 *
408
-	 * @param string $displayName
409
-	 * @param string $displayName2
410
-	 * @returns string the effective display name
411
-	 */
412
-	public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
413
-		$displayName2 = strval($displayName2);
414
-		if($displayName2 !== '') {
415
-			$displayName .= ' (' . $displayName2 . ')';
416
-		}
417
-		$this->store('displayName', $displayName);
418
-		return $displayName;
419
-	}
420
-
421
-	/**
422
-	 * Stores the LDAP Username in the Database
423
-	 * @param string $userName
424
-	 */
425
-	public function storeLDAPUserName($userName) {
426
-		$this->store('uid', $userName);
427
-	}
428
-
429
-	/**
430
-	 * @brief checks whether an update method specified by feature was run
431
-	 * already. If not, it will marked like this, because it is expected that
432
-	 * the method will be run, when false is returned.
433
-	 * @param string $feature email | quota | avatar (can be extended)
434
-	 * @return bool
435
-	 */
436
-	private function wasRefreshed($feature) {
437
-		if(isset($this->refreshedFeatures[$feature])) {
438
-			return true;
439
-		}
440
-		$this->refreshedFeatures[$feature] = 1;
441
-		return false;
442
-	}
443
-
444
-	/**
445
-	 * fetches the email from LDAP and stores it as Nextcloud user value
446
-	 * @param string $valueFromLDAP if known, to save an LDAP read request
447
-	 * @return null
448
-	 */
449
-	public function updateEmail($valueFromLDAP = null) {
450
-		if($this->wasRefreshed('email')) {
451
-			return;
452
-		}
453
-		$email = strval($valueFromLDAP);
454
-		if(is_null($valueFromLDAP)) {
455
-			$emailAttribute = $this->connection->ldapEmailAttribute;
456
-			if ($emailAttribute !== '') {
457
-				$aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
458
-				if(is_array($aEmail) && (count($aEmail) > 0)) {
459
-					$email = strval($aEmail[0]);
460
-				}
461
-			}
462
-		}
463
-		if ($email !== '') {
464
-			$user = $this->userManager->get($this->uid);
465
-			if (!is_null($user)) {
466
-				$currentEmail = strval($user->getEMailAddress());
467
-				if ($currentEmail !== $email) {
468
-					$user->setEMailAddress($email);
469
-				}
470
-			}
471
-		}
472
-	}
473
-
474
-	/**
475
-	 * Overall process goes as follow:
476
-	 * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
477
-	 * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
478
-	 * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
479
-	 * 4. check if the target user exists and set the quota for the user.
480
-	 *
481
-	 * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
482
-	 * parameter can be passed with the value of the attribute. This value will be considered as the
483
-	 * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
484
-	 * fetch all the user's attributes in one call and use the fetched values in this function.
485
-	 * The expected value for that parameter is a string describing the quota for the user. Valid
486
-	 * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
487
-	 * bytes), '1234 MB' (quota in MB - check the \OC_Helper::computerFileSize method for more info)
488
-	 *
489
-	 * fetches the quota from LDAP and stores it as Nextcloud user value
490
-	 * @param string $valueFromLDAP the quota attribute's value can be passed,
491
-	 * to save the readAttribute request
492
-	 * @return null
493
-	 */
494
-	public function updateQuota($valueFromLDAP = null) {
495
-		if($this->wasRefreshed('quota')) {
496
-			return;
497
-		}
498
-
499
-		$quota = false;
500
-		if(is_null($valueFromLDAP)) {
501
-			$quotaAttribute = $this->connection->ldapQuotaAttribute;
502
-			if ($quotaAttribute !== '') {
503
-				$aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
504
-				if($aQuota && (count($aQuota) > 0)) {
505
-					if ($this->verifyQuotaValue($aQuota[0])) {
506
-						$quota = $aQuota[0];
507
-					} else {
508
-						$this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', \OCP\Util::WARN);
509
-					}
510
-				}
511
-			}
512
-		} else {
513
-			if ($this->verifyQuotaValue($valueFromLDAP)) {
514
-				$quota = $valueFromLDAP;
515
-			} else {
516
-				$this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', \OCP\Util::WARN);
517
-			}
518
-		}
519
-
520
-		if ($quota === false) {
521
-			// quota not found using the LDAP attribute (or not parseable). Try the default quota
522
-			$defaultQuota = $this->connection->ldapQuotaDefault;
523
-			if ($this->verifyQuotaValue($defaultQuota)) {
524
-				$quota = $defaultQuota;
525
-			}
526
-		}
527
-
528
-		$targetUser = $this->userManager->get($this->uid);
529
-		if ($targetUser) {
530
-			if($quota !== false) {
531
-				$targetUser->setQuota($quota);
532
-			} else {
533
-				$this->log->log('not suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', \OCP\Util::WARN);
534
-			}
535
-		} else {
536
-			$this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', \OCP\Util::ERROR);
537
-		}
538
-	}
539
-
540
-	private function verifyQuotaValue($quotaValue) {
541
-		return $quotaValue === 'none' || $quotaValue === 'default' || \OC_Helper::computerFileSize($quotaValue) !== false;
542
-	}
543
-
544
-	/**
545
-	 * called by a post_login hook to save the avatar picture
546
-	 *
547
-	 * @param array $params
548
-	 */
549
-	public function updateAvatarPostLogin($params) {
550
-		if(isset($params['uid']) && $params['uid'] === $this->getUsername()) {
551
-			$this->updateAvatar();
552
-		}
553
-	}
554
-
555
-	/**
556
-	 * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
557
-	 * @return null
558
-	 */
559
-	public function updateAvatar() {
560
-		if($this->wasRefreshed('avatar')) {
561
-			return;
562
-		}
563
-		$avatarImage = $this->getAvatarImage();
564
-		if($avatarImage === false) {
565
-			//not set, nothing left to do;
566
-			return;
567
-		}
568
-		$this->image->loadFromBase64(base64_encode($avatarImage));
569
-		$this->setOwnCloudAvatar();
570
-	}
571
-
572
-	/**
573
-	 * @brief sets an image as Nextcloud avatar
574
-	 * @return null
575
-	 */
576
-	private function setOwnCloudAvatar() {
577
-		if(!$this->image->valid()) {
578
-			$this->log->log('jpegPhoto data invalid for '.$this->dn, \OCP\Util::ERROR);
579
-			return;
580
-		}
581
-		//make sure it is a square and not bigger than 128x128
582
-		$size = min(array($this->image->width(), $this->image->height(), 128));
583
-		if(!$this->image->centerCrop($size)) {
584
-			$this->log->log('croping image for avatar failed for '.$this->dn, \OCP\Util::ERROR);
585
-			return;
586
-		}
587
-
588
-		if(!$this->fs->isLoaded()) {
589
-			$this->fs->setup($this->uid);
590
-		}
591
-
592
-		try {
593
-			$avatar = $this->avatarManager->getAvatar($this->uid);
594
-			$avatar->set($this->image);
595
-		} catch (\Exception $e) {
596
-			\OC::$server->getLogger()->notice(
597
-				'Could not set avatar for ' . $this->dn	. ', because: ' . $e->getMessage(),
598
-				['app' => 'user_ldap']);
599
-		}
600
-	}
601
-
602
-	/**
603
-	 * called by a post_login hook to handle password expiry
604
-	 *
605
-	 * @param array $params
606
-	 */
607
-	public function handlePasswordExpiry($params) {
608
-		$ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
609
-		if (empty($ppolicyDN) || (intval($this->connection->turnOnPasswordChange) !== 1)) {
610
-			return;//password expiry handling disabled
611
-		}
612
-		$uid = $params['uid'];
613
-		if(isset($uid) && $uid === $this->getUsername()) {
614
-			//retrieve relevant user attributes
615
-			$result = $this->access->search('objectclass=*', $this->dn, ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
124
+        if ($username === null) {
125
+            $log->log("uid for '$dn' must not be null!", Util::ERROR);
126
+            throw new \InvalidArgumentException('uid must not be null!');
127
+        } else if ($username === '') {
128
+            $log->log("uid for '$dn' must not be an empty string", Util::ERROR);
129
+            throw new \InvalidArgumentException('uid must not be an empty string!');
130
+        }
131
+
132
+        $this->access              = $access;
133
+        $this->connection          = $access->getConnection();
134
+        $this->config              = $config;
135
+        $this->fs                  = $fs;
136
+        $this->dn                  = $dn;
137
+        $this->uid                 = $username;
138
+        $this->image               = $image;
139
+        $this->log                 = $log;
140
+        $this->avatarManager       = $avatarManager;
141
+        $this->userManager         = $userManager;
142
+        $this->notificationManager = $notificationManager;
143
+
144
+        \OCP\Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
145
+    }
146
+
147
+    /**
148
+     * @brief updates properties like email, quota or avatar provided by LDAP
149
+     * @return null
150
+     */
151
+    public function update() {
152
+        if(is_null($this->dn)) {
153
+            return null;
154
+        }
155
+
156
+        $hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap',
157
+                self::USER_PREFKEY_FIRSTLOGIN, 0);
158
+
159
+        if($this->needsRefresh()) {
160
+            $this->updateEmail();
161
+            $this->updateQuota();
162
+            if($hasLoggedIn !== 0) {
163
+                //we do not need to try it, when the user has not been logged in
164
+                //before, because the file system will not be ready.
165
+                $this->updateAvatar();
166
+                //in order to get an avatar as soon as possible, mark the user
167
+                //as refreshed only when updating the avatar did happen
168
+                $this->markRefreshTime();
169
+            }
170
+        }
171
+    }
172
+
173
+    /**
174
+     * processes results from LDAP for attributes as returned by getAttributesToRead()
175
+     * @param array $ldapEntry the user entry as retrieved from LDAP
176
+     */
177
+    public function processAttributes($ldapEntry) {
178
+        $this->markRefreshTime();
179
+        //Quota
180
+        $attr = strtolower($this->connection->ldapQuotaAttribute);
181
+        if(isset($ldapEntry[$attr])) {
182
+            $this->updateQuota($ldapEntry[$attr][0]);
183
+        } else {
184
+            if ($this->connection->ldapQuotaDefault !== '') {
185
+                $this->updateQuota();
186
+            }
187
+        }
188
+        unset($attr);
189
+
190
+        //displayName
191
+        $displayName = $displayName2 = '';
192
+        $attr = strtolower($this->connection->ldapUserDisplayName);
193
+        if(isset($ldapEntry[$attr])) {
194
+            $displayName = strval($ldapEntry[$attr][0]);
195
+        }
196
+        $attr = strtolower($this->connection->ldapUserDisplayName2);
197
+        if(isset($ldapEntry[$attr])) {
198
+            $displayName2 = strval($ldapEntry[$attr][0]);
199
+        }
200
+        if ($displayName !== '') {
201
+            $this->composeAndStoreDisplayName($displayName);
202
+            $this->access->cacheUserDisplayName(
203
+                $this->getUsername(),
204
+                $displayName,
205
+                $displayName2
206
+            );
207
+        }
208
+        unset($attr);
209
+
210
+        //Email
211
+        //email must be stored after displayname, because it would cause a user
212
+        //change event that will trigger fetching the display name again
213
+        $attr = strtolower($this->connection->ldapEmailAttribute);
214
+        if(isset($ldapEntry[$attr])) {
215
+            $this->updateEmail($ldapEntry[$attr][0]);
216
+        }
217
+        unset($attr);
218
+
219
+        // LDAP Username, needed for s2s sharing
220
+        if(isset($ldapEntry['uid'])) {
221
+            $this->storeLDAPUserName($ldapEntry['uid'][0]);
222
+        } else if(isset($ldapEntry['samaccountname'])) {
223
+            $this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
224
+        }
225
+
226
+        //homePath
227
+        if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
228
+            $attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
229
+            if(isset($ldapEntry[$attr])) {
230
+                $this->access->cacheUserHome(
231
+                    $this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
232
+            }
233
+        }
234
+
235
+        //memberOf groups
236
+        $cacheKey = 'getMemberOf'.$this->getUsername();
237
+        $groups = false;
238
+        if(isset($ldapEntry['memberof'])) {
239
+            $groups = $ldapEntry['memberof'];
240
+        }
241
+        $this->connection->writeToCache($cacheKey, $groups);
242
+
243
+        //Avatar
244
+        $attrs = array('jpegphoto', 'thumbnailphoto');
245
+        foreach ($attrs as $attr)  {
246
+            if(isset($ldapEntry[$attr])) {
247
+                $this->avatarImage = $ldapEntry[$attr][0];
248
+                // the call to the method that saves the avatar in the file
249
+                // system must be postponed after the login. It is to ensure
250
+                // external mounts are mounted properly (e.g. with login
251
+                // credentials from the session).
252
+                \OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin');
253
+                break;
254
+            }
255
+        }
256
+    }
257
+
258
+    /**
259
+     * @brief returns the LDAP DN of the user
260
+     * @return string
261
+     */
262
+    public function getDN() {
263
+        return $this->dn;
264
+    }
265
+
266
+    /**
267
+     * @brief returns the Nextcloud internal username of the user
268
+     * @return string
269
+     */
270
+    public function getUsername() {
271
+        return $this->uid;
272
+    }
273
+
274
+    /**
275
+     * returns the home directory of the user if specified by LDAP settings
276
+     * @param string $valueFromLDAP
277
+     * @return bool|string
278
+     * @throws \Exception
279
+     */
280
+    public function getHomePath($valueFromLDAP = null) {
281
+        $path = strval($valueFromLDAP);
282
+        $attr = null;
283
+
284
+        if (is_null($valueFromLDAP)
285
+           && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0
286
+           && $this->access->connection->homeFolderNamingRule !== 'attr:')
287
+        {
288
+            $attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
289
+            $homedir = $this->access->readAttribute(
290
+                $this->access->username2dn($this->getUsername()), $attr);
291
+            if ($homedir && isset($homedir[0])) {
292
+                $path = $homedir[0];
293
+            }
294
+        }
295
+
296
+        if ($path !== '') {
297
+            //if attribute's value is an absolute path take this, otherwise append it to data dir
298
+            //check for / at the beginning or pattern c:\ resp. c:/
299
+            if(   '/' !== $path[0]
300
+               && !(3 < strlen($path) && ctype_alpha($path[0])
301
+                   && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
302
+            ) {
303
+                $path = $this->config->getSystemValue('datadirectory',
304
+                        \OC::$SERVERROOT.'/data' ) . '/' . $path;
305
+            }
306
+            //we need it to store it in the DB as well in case a user gets
307
+            //deleted so we can clean up afterwards
308
+            $this->config->setUserValue(
309
+                $this->getUsername(), 'user_ldap', 'homePath', $path
310
+            );
311
+            return $path;
312
+        }
313
+
314
+        if(    !is_null($attr)
315
+            && $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true)
316
+        ) {
317
+            // a naming rule attribute is defined, but it doesn't exist for that LDAP user
318
+            throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
319
+        }
320
+
321
+        //false will apply default behaviour as defined and done by OC_User
322
+        $this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
323
+        return false;
324
+    }
325
+
326
+    public function getMemberOfGroups() {
327
+        $cacheKey = 'getMemberOf'.$this->getUsername();
328
+        $memberOfGroups = $this->connection->getFromCache($cacheKey);
329
+        if(!is_null($memberOfGroups)) {
330
+            return $memberOfGroups;
331
+        }
332
+        $groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
333
+        $this->connection->writeToCache($cacheKey, $groupDNs);
334
+        return $groupDNs;
335
+    }
336
+
337
+    /**
338
+     * @brief reads the image from LDAP that shall be used as Avatar
339
+     * @return string data (provided by LDAP) | false
340
+     */
341
+    public function getAvatarImage() {
342
+        if(!is_null($this->avatarImage)) {
343
+            return $this->avatarImage;
344
+        }
345
+
346
+        $this->avatarImage = false;
347
+        $attributes = array('jpegPhoto', 'thumbnailPhoto');
348
+        foreach($attributes as $attribute) {
349
+            $result = $this->access->readAttribute($this->dn, $attribute);
350
+            if($result !== false && is_array($result) && isset($result[0])) {
351
+                $this->avatarImage = $result[0];
352
+                break;
353
+            }
354
+        }
355
+
356
+        return $this->avatarImage;
357
+    }
358
+
359
+    /**
360
+     * @brief marks the user as having logged in at least once
361
+     * @return null
362
+     */
363
+    public function markLogin() {
364
+        $this->config->setUserValue(
365
+            $this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1);
366
+    }
367
+
368
+    /**
369
+     * @brief marks the time when user features like email have been updated
370
+     * @return null
371
+     */
372
+    public function markRefreshTime() {
373
+        $this->config->setUserValue(
374
+            $this->uid, 'user_ldap', self::USER_PREFKEY_LASTREFRESH, time());
375
+    }
376
+
377
+    /**
378
+     * @brief checks whether user features needs to be updated again by
379
+     * comparing the difference of time of the last refresh to now with the
380
+     * desired interval
381
+     * @return bool
382
+     */
383
+    private function needsRefresh() {
384
+        $lastChecked = $this->config->getUserValue($this->uid, 'user_ldap',
385
+            self::USER_PREFKEY_LASTREFRESH, 0);
386
+
387
+        //TODO make interval configurable
388
+        if((time() - intval($lastChecked)) < 86400 ) {
389
+            return false;
390
+        }
391
+        return  true;
392
+    }
393
+
394
+    /**
395
+     * Stores a key-value pair in relation to this user
396
+     *
397
+     * @param string $key
398
+     * @param string $value
399
+     */
400
+    private function store($key, $value) {
401
+        $this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
402
+    }
403
+
404
+    /**
405
+     * Composes the display name and stores it in the database. The final
406
+     * display name is returned.
407
+     *
408
+     * @param string $displayName
409
+     * @param string $displayName2
410
+     * @returns string the effective display name
411
+     */
412
+    public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
413
+        $displayName2 = strval($displayName2);
414
+        if($displayName2 !== '') {
415
+            $displayName .= ' (' . $displayName2 . ')';
416
+        }
417
+        $this->store('displayName', $displayName);
418
+        return $displayName;
419
+    }
420
+
421
+    /**
422
+     * Stores the LDAP Username in the Database
423
+     * @param string $userName
424
+     */
425
+    public function storeLDAPUserName($userName) {
426
+        $this->store('uid', $userName);
427
+    }
428
+
429
+    /**
430
+     * @brief checks whether an update method specified by feature was run
431
+     * already. If not, it will marked like this, because it is expected that
432
+     * the method will be run, when false is returned.
433
+     * @param string $feature email | quota | avatar (can be extended)
434
+     * @return bool
435
+     */
436
+    private function wasRefreshed($feature) {
437
+        if(isset($this->refreshedFeatures[$feature])) {
438
+            return true;
439
+        }
440
+        $this->refreshedFeatures[$feature] = 1;
441
+        return false;
442
+    }
443
+
444
+    /**
445
+     * fetches the email from LDAP and stores it as Nextcloud user value
446
+     * @param string $valueFromLDAP if known, to save an LDAP read request
447
+     * @return null
448
+     */
449
+    public function updateEmail($valueFromLDAP = null) {
450
+        if($this->wasRefreshed('email')) {
451
+            return;
452
+        }
453
+        $email = strval($valueFromLDAP);
454
+        if(is_null($valueFromLDAP)) {
455
+            $emailAttribute = $this->connection->ldapEmailAttribute;
456
+            if ($emailAttribute !== '') {
457
+                $aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
458
+                if(is_array($aEmail) && (count($aEmail) > 0)) {
459
+                    $email = strval($aEmail[0]);
460
+                }
461
+            }
462
+        }
463
+        if ($email !== '') {
464
+            $user = $this->userManager->get($this->uid);
465
+            if (!is_null($user)) {
466
+                $currentEmail = strval($user->getEMailAddress());
467
+                if ($currentEmail !== $email) {
468
+                    $user->setEMailAddress($email);
469
+                }
470
+            }
471
+        }
472
+    }
473
+
474
+    /**
475
+     * Overall process goes as follow:
476
+     * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
477
+     * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
478
+     * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
479
+     * 4. check if the target user exists and set the quota for the user.
480
+     *
481
+     * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
482
+     * parameter can be passed with the value of the attribute. This value will be considered as the
483
+     * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
484
+     * fetch all the user's attributes in one call and use the fetched values in this function.
485
+     * The expected value for that parameter is a string describing the quota for the user. Valid
486
+     * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
487
+     * bytes), '1234 MB' (quota in MB - check the \OC_Helper::computerFileSize method for more info)
488
+     *
489
+     * fetches the quota from LDAP and stores it as Nextcloud user value
490
+     * @param string $valueFromLDAP the quota attribute's value can be passed,
491
+     * to save the readAttribute request
492
+     * @return null
493
+     */
494
+    public function updateQuota($valueFromLDAP = null) {
495
+        if($this->wasRefreshed('quota')) {
496
+            return;
497
+        }
498
+
499
+        $quota = false;
500
+        if(is_null($valueFromLDAP)) {
501
+            $quotaAttribute = $this->connection->ldapQuotaAttribute;
502
+            if ($quotaAttribute !== '') {
503
+                $aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
504
+                if($aQuota && (count($aQuota) > 0)) {
505
+                    if ($this->verifyQuotaValue($aQuota[0])) {
506
+                        $quota = $aQuota[0];
507
+                    } else {
508
+                        $this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', \OCP\Util::WARN);
509
+                    }
510
+                }
511
+            }
512
+        } else {
513
+            if ($this->verifyQuotaValue($valueFromLDAP)) {
514
+                $quota = $valueFromLDAP;
515
+            } else {
516
+                $this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', \OCP\Util::WARN);
517
+            }
518
+        }
519
+
520
+        if ($quota === false) {
521
+            // quota not found using the LDAP attribute (or not parseable). Try the default quota
522
+            $defaultQuota = $this->connection->ldapQuotaDefault;
523
+            if ($this->verifyQuotaValue($defaultQuota)) {
524
+                $quota = $defaultQuota;
525
+            }
526
+        }
527
+
528
+        $targetUser = $this->userManager->get($this->uid);
529
+        if ($targetUser) {
530
+            if($quota !== false) {
531
+                $targetUser->setQuota($quota);
532
+            } else {
533
+                $this->log->log('not suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', \OCP\Util::WARN);
534
+            }
535
+        } else {
536
+            $this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', \OCP\Util::ERROR);
537
+        }
538
+    }
539
+
540
+    private function verifyQuotaValue($quotaValue) {
541
+        return $quotaValue === 'none' || $quotaValue === 'default' || \OC_Helper::computerFileSize($quotaValue) !== false;
542
+    }
543
+
544
+    /**
545
+     * called by a post_login hook to save the avatar picture
546
+     *
547
+     * @param array $params
548
+     */
549
+    public function updateAvatarPostLogin($params) {
550
+        if(isset($params['uid']) && $params['uid'] === $this->getUsername()) {
551
+            $this->updateAvatar();
552
+        }
553
+    }
554
+
555
+    /**
556
+     * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
557
+     * @return null
558
+     */
559
+    public function updateAvatar() {
560
+        if($this->wasRefreshed('avatar')) {
561
+            return;
562
+        }
563
+        $avatarImage = $this->getAvatarImage();
564
+        if($avatarImage === false) {
565
+            //not set, nothing left to do;
566
+            return;
567
+        }
568
+        $this->image->loadFromBase64(base64_encode($avatarImage));
569
+        $this->setOwnCloudAvatar();
570
+    }
571
+
572
+    /**
573
+     * @brief sets an image as Nextcloud avatar
574
+     * @return null
575
+     */
576
+    private function setOwnCloudAvatar() {
577
+        if(!$this->image->valid()) {
578
+            $this->log->log('jpegPhoto data invalid for '.$this->dn, \OCP\Util::ERROR);
579
+            return;
580
+        }
581
+        //make sure it is a square and not bigger than 128x128
582
+        $size = min(array($this->image->width(), $this->image->height(), 128));
583
+        if(!$this->image->centerCrop($size)) {
584
+            $this->log->log('croping image for avatar failed for '.$this->dn, \OCP\Util::ERROR);
585
+            return;
586
+        }
587
+
588
+        if(!$this->fs->isLoaded()) {
589
+            $this->fs->setup($this->uid);
590
+        }
591
+
592
+        try {
593
+            $avatar = $this->avatarManager->getAvatar($this->uid);
594
+            $avatar->set($this->image);
595
+        } catch (\Exception $e) {
596
+            \OC::$server->getLogger()->notice(
597
+                'Could not set avatar for ' . $this->dn	. ', because: ' . $e->getMessage(),
598
+                ['app' => 'user_ldap']);
599
+        }
600
+    }
601
+
602
+    /**
603
+     * called by a post_login hook to handle password expiry
604
+     *
605
+     * @param array $params
606
+     */
607
+    public function handlePasswordExpiry($params) {
608
+        $ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
609
+        if (empty($ppolicyDN) || (intval($this->connection->turnOnPasswordChange) !== 1)) {
610
+            return;//password expiry handling disabled
611
+        }
612
+        $uid = $params['uid'];
613
+        if(isset($uid) && $uid === $this->getUsername()) {
614
+            //retrieve relevant user attributes
615
+            $result = $this->access->search('objectclass=*', $this->dn, ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
616 616
 			
617
-			if(array_key_exists('pwdpolicysubentry', $result[0])) {
618
-				$pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
619
-				if($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)){
620
-					$ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
621
-				}
622
-			}
617
+            if(array_key_exists('pwdpolicysubentry', $result[0])) {
618
+                $pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
619
+                if($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)){
620
+                    $ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
621
+                }
622
+            }
623 623
 			
624
-			$pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : null;
625
-			$pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : null;
626
-			$pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : null;
624
+            $pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : null;
625
+            $pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : null;
626
+            $pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : null;
627 627
 			
628
-			//retrieve relevant password policy attributes
629
-			$cacheKey = 'ppolicyAttributes' . $ppolicyDN;
630
-			$result = $this->connection->getFromCache($cacheKey);
631
-			if(is_null($result)) {
632
-				$result = $this->access->search('objectclass=*', $ppolicyDN, ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
633
-				$this->connection->writeToCache($cacheKey, $result);
634
-			}
628
+            //retrieve relevant password policy attributes
629
+            $cacheKey = 'ppolicyAttributes' . $ppolicyDN;
630
+            $result = $this->connection->getFromCache($cacheKey);
631
+            if(is_null($result)) {
632
+                $result = $this->access->search('objectclass=*', $ppolicyDN, ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
633
+                $this->connection->writeToCache($cacheKey, $result);
634
+            }
635 635
 			
636
-			$pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : null;
637
-			$pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : null;
638
-			$pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : null;
636
+            $pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : null;
637
+            $pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : null;
638
+            $pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : null;
639 639
 			
640
-			//handle grace login
641
-			$pwdGraceUseTimeCount = count($pwdGraceUseTime);
642
-			if($pwdGraceUseTime && $pwdGraceUseTimeCount > 0) { //was this a grace login?
643
-				if($pwdGraceAuthNLimit 
644
-					&& (count($pwdGraceAuthNLimit) > 0)
645
-					&&($pwdGraceUseTimeCount < intval($pwdGraceAuthNLimit[0]))) { //at least one more grace login available?
646
-					$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
647
-					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
648
-					'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
649
-				} else { //no more grace login available
650
-					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
651
-					'user_ldap.renewPassword.showLoginFormInvalidPassword', array('user' => $uid)));
652
-				}
653
-				exit();
654
-			}
655
-			//handle pwdReset attribute
656
-			if($pwdReset && (count($pwdReset) > 0) && $pwdReset[0] === 'TRUE') { //user must change his password
657
-				$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
658
-				header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
659
-				'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
660
-				exit();
661
-			}
662
-			//handle password expiry warning
663
-			if($pwdChangedTime && (count($pwdChangedTime) > 0)) {
664
-				if($pwdMaxAge && (count($pwdMaxAge) > 0)
665
-					&& $pwdExpireWarning && (count($pwdExpireWarning) > 0)) {
666
-					$pwdMaxAgeInt = intval($pwdMaxAge[0]);
667
-					$pwdExpireWarningInt = intval($pwdExpireWarning[0]);
668
-					if($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0){
669
-						$pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
670
-						$pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
671
-						$currentDateTime = new \DateTime();
672
-						$secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
673
-						if($secondsToExpiry <= $pwdExpireWarningInt) {
674
-							//remove last password expiry warning if any
675
-							$notification = $this->notificationManager->createNotification();
676
-							$notification->setApp('user_ldap')
677
-								->setUser($uid)
678
-								->setObject('pwd_exp_warn', $uid)
679
-							;
680
-							$this->notificationManager->markProcessed($notification);
681
-							//create new password expiry warning
682
-							$notification = $this->notificationManager->createNotification();
683
-							$notification->setApp('user_ldap')
684
-								->setUser($uid)
685
-								->setDateTime($currentDateTime)
686
-								->setObject('pwd_exp_warn', $uid) 
687
-								->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)])
688
-							;
689
-							$this->notificationManager->notify($notification);
690
-						}
691
-					}
692
-				}
693
-			}
694
-		}
695
-	}
640
+            //handle grace login
641
+            $pwdGraceUseTimeCount = count($pwdGraceUseTime);
642
+            if($pwdGraceUseTime && $pwdGraceUseTimeCount > 0) { //was this a grace login?
643
+                if($pwdGraceAuthNLimit 
644
+                    && (count($pwdGraceAuthNLimit) > 0)
645
+                    &&($pwdGraceUseTimeCount < intval($pwdGraceAuthNLimit[0]))) { //at least one more grace login available?
646
+                    $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
647
+                    header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
648
+                    'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
649
+                } else { //no more grace login available
650
+                    header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
651
+                    'user_ldap.renewPassword.showLoginFormInvalidPassword', array('user' => $uid)));
652
+                }
653
+                exit();
654
+            }
655
+            //handle pwdReset attribute
656
+            if($pwdReset && (count($pwdReset) > 0) && $pwdReset[0] === 'TRUE') { //user must change his password
657
+                $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
658
+                header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
659
+                'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
660
+                exit();
661
+            }
662
+            //handle password expiry warning
663
+            if($pwdChangedTime && (count($pwdChangedTime) > 0)) {
664
+                if($pwdMaxAge && (count($pwdMaxAge) > 0)
665
+                    && $pwdExpireWarning && (count($pwdExpireWarning) > 0)) {
666
+                    $pwdMaxAgeInt = intval($pwdMaxAge[0]);
667
+                    $pwdExpireWarningInt = intval($pwdExpireWarning[0]);
668
+                    if($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0){
669
+                        $pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
670
+                        $pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
671
+                        $currentDateTime = new \DateTime();
672
+                        $secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
673
+                        if($secondsToExpiry <= $pwdExpireWarningInt) {
674
+                            //remove last password expiry warning if any
675
+                            $notification = $this->notificationManager->createNotification();
676
+                            $notification->setApp('user_ldap')
677
+                                ->setUser($uid)
678
+                                ->setObject('pwd_exp_warn', $uid)
679
+                            ;
680
+                            $this->notificationManager->markProcessed($notification);
681
+                            //create new password expiry warning
682
+                            $notification = $this->notificationManager->createNotification();
683
+                            $notification->setApp('user_ldap')
684
+                                ->setUser($uid)
685
+                                ->setDateTime($currentDateTime)
686
+                                ->setObject('pwd_exp_warn', $uid) 
687
+                                ->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)])
688
+                            ;
689
+                            $this->notificationManager->notify($notification);
690
+                        }
691
+                    }
692
+                }
693
+            }
694
+        }
695
+    }
696 696
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Jobs/UpdateGroups.php 1 patch
Indentation   +164 added lines, -164 removed lines patch added patch discarded remove patch
@@ -41,183 +41,183 @@
 block discarded – undo
41 41
 use OCA\User_LDAP\User\Manager;
42 42
 
43 43
 class UpdateGroups extends \OC\BackgroundJob\TimedJob {
44
-	static private $groupsFromDB;
45
-
46
-	static private $groupBE;
47
-
48
-	public function __construct(){
49
-		$this->interval = self::getRefreshInterval();
50
-	}
51
-
52
-	/**
53
-	 * @param mixed $argument
54
-	 */
55
-	public function run($argument){
56
-		self::updateGroups();
57
-	}
58
-
59
-	static public function updateGroups() {
60
-		\OCP\Util::writeLog('user_ldap', 'Run background job "updateGroups"', \OCP\Util::DEBUG);
61
-
62
-		$knownGroups = array_keys(self::getKnownGroups());
63
-		$actualGroups = self::getGroupBE()->getGroups();
64
-
65
-		if(empty($actualGroups) && empty($knownGroups)) {
66
-			\OCP\Util::writeLog('user_ldap',
67
-				'bgJ "updateGroups" – groups do not seem to be configured properly, aborting.',
68
-				\OCP\Util::INFO);
69
-			return;
70
-		}
71
-
72
-		self::handleKnownGroups(array_intersect($actualGroups, $knownGroups));
73
-		self::handleCreatedGroups(array_diff($actualGroups, $knownGroups));
74
-		self::handleRemovedGroups(array_diff($knownGroups, $actualGroups));
75
-
76
-		\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Finished.', \OCP\Util::DEBUG);
77
-	}
78
-
79
-	/**
80
-	 * @return int
81
-	 */
82
-	static private function getRefreshInterval() {
83
-		//defaults to every hour
84
-		return \OCP\Config::getAppValue('user_ldap', 'bgjRefreshInterval', 3600);
85
-	}
86
-
87
-	/**
88
-	 * @param string[] $groups
89
-	 */
90
-	static private function handleKnownGroups($groups) {
91
-		\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Dealing with known Groups.', \OCP\Util::DEBUG);
92
-		$query = \OCP\DB::prepare('
44
+    static private $groupsFromDB;
45
+
46
+    static private $groupBE;
47
+
48
+    public function __construct(){
49
+        $this->interval = self::getRefreshInterval();
50
+    }
51
+
52
+    /**
53
+     * @param mixed $argument
54
+     */
55
+    public function run($argument){
56
+        self::updateGroups();
57
+    }
58
+
59
+    static public function updateGroups() {
60
+        \OCP\Util::writeLog('user_ldap', 'Run background job "updateGroups"', \OCP\Util::DEBUG);
61
+
62
+        $knownGroups = array_keys(self::getKnownGroups());
63
+        $actualGroups = self::getGroupBE()->getGroups();
64
+
65
+        if(empty($actualGroups) && empty($knownGroups)) {
66
+            \OCP\Util::writeLog('user_ldap',
67
+                'bgJ "updateGroups" – groups do not seem to be configured properly, aborting.',
68
+                \OCP\Util::INFO);
69
+            return;
70
+        }
71
+
72
+        self::handleKnownGroups(array_intersect($actualGroups, $knownGroups));
73
+        self::handleCreatedGroups(array_diff($actualGroups, $knownGroups));
74
+        self::handleRemovedGroups(array_diff($knownGroups, $actualGroups));
75
+
76
+        \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Finished.', \OCP\Util::DEBUG);
77
+    }
78
+
79
+    /**
80
+     * @return int
81
+     */
82
+    static private function getRefreshInterval() {
83
+        //defaults to every hour
84
+        return \OCP\Config::getAppValue('user_ldap', 'bgjRefreshInterval', 3600);
85
+    }
86
+
87
+    /**
88
+     * @param string[] $groups
89
+     */
90
+    static private function handleKnownGroups($groups) {
91
+        \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Dealing with known Groups.', \OCP\Util::DEBUG);
92
+        $query = \OCP\DB::prepare('
93 93
 			UPDATE `*PREFIX*ldap_group_members`
94 94
 			SET `owncloudusers` = ?
95 95
 			WHERE `owncloudname` = ?
96 96
 		');
97
-		foreach($groups as $group) {
98
-			//we assume, that self::$groupsFromDB has been retrieved already
99
-			$knownUsers = unserialize(self::$groupsFromDB[$group]['owncloudusers']);
100
-			$actualUsers = self::getGroupBE()->usersInGroup($group);
101
-			$hasChanged = false;
102
-			foreach(array_diff($knownUsers, $actualUsers) as $removedUser) {
103
-				\OCP\Util::emitHook('OC_User', 'post_removeFromGroup', array('uid' => $removedUser, 'gid' => $group));
104
-				\OCP\Util::writeLog('user_ldap',
105
-				'bgJ "updateGroups" – "'.$removedUser.'" removed from "'.$group.'".',
106
-				\OCP\Util::INFO);
107
-				$hasChanged = true;
108
-			}
109
-			foreach(array_diff($actualUsers, $knownUsers) as $addedUser) {
110
-				\OCP\Util::emitHook('OC_User', 'post_addToGroup', array('uid' => $addedUser, 'gid' => $group));
111
-				\OCP\Util::writeLog('user_ldap',
112
-				'bgJ "updateGroups" – "'.$addedUser.'" added to "'.$group.'".',
113
-				\OCP\Util::INFO);
114
-				$hasChanged = true;
115
-			}
116
-			if($hasChanged) {
117
-				$query->execute(array(serialize($actualUsers), $group));
118
-			}
119
-		}
120
-		\OCP\Util::writeLog('user_ldap',
121
-			'bgJ "updateGroups" – FINISHED dealing with known Groups.',
122
-			\OCP\Util::DEBUG);
123
-	}
124
-
125
-	/**
126
-	 * @param string[] $createdGroups
127
-	 */
128
-	static private function handleCreatedGroups($createdGroups) {
129
-		\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – dealing with created Groups.', \OCP\Util::DEBUG);
130
-		$query = \OCP\DB::prepare('
97
+        foreach($groups as $group) {
98
+            //we assume, that self::$groupsFromDB has been retrieved already
99
+            $knownUsers = unserialize(self::$groupsFromDB[$group]['owncloudusers']);
100
+            $actualUsers = self::getGroupBE()->usersInGroup($group);
101
+            $hasChanged = false;
102
+            foreach(array_diff($knownUsers, $actualUsers) as $removedUser) {
103
+                \OCP\Util::emitHook('OC_User', 'post_removeFromGroup', array('uid' => $removedUser, 'gid' => $group));
104
+                \OCP\Util::writeLog('user_ldap',
105
+                'bgJ "updateGroups" – "'.$removedUser.'" removed from "'.$group.'".',
106
+                \OCP\Util::INFO);
107
+                $hasChanged = true;
108
+            }
109
+            foreach(array_diff($actualUsers, $knownUsers) as $addedUser) {
110
+                \OCP\Util::emitHook('OC_User', 'post_addToGroup', array('uid' => $addedUser, 'gid' => $group));
111
+                \OCP\Util::writeLog('user_ldap',
112
+                'bgJ "updateGroups" – "'.$addedUser.'" added to "'.$group.'".',
113
+                \OCP\Util::INFO);
114
+                $hasChanged = true;
115
+            }
116
+            if($hasChanged) {
117
+                $query->execute(array(serialize($actualUsers), $group));
118
+            }
119
+        }
120
+        \OCP\Util::writeLog('user_ldap',
121
+            'bgJ "updateGroups" – FINISHED dealing with known Groups.',
122
+            \OCP\Util::DEBUG);
123
+    }
124
+
125
+    /**
126
+     * @param string[] $createdGroups
127
+     */
128
+    static private function handleCreatedGroups($createdGroups) {
129
+        \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – dealing with created Groups.', \OCP\Util::DEBUG);
130
+        $query = \OCP\DB::prepare('
131 131
 			INSERT
132 132
 			INTO `*PREFIX*ldap_group_members` (`owncloudname`, `owncloudusers`)
133 133
 			VALUES (?, ?)
134 134
 		');
135
-		foreach($createdGroups as $createdGroup) {
136
-			\OCP\Util::writeLog('user_ldap',
137
-				'bgJ "updateGroups" – new group "'.$createdGroup.'" found.',
138
-				\OCP\Util::INFO);
139
-			$users = serialize(self::getGroupBE()->usersInGroup($createdGroup));
140
-			$query->execute(array($createdGroup, $users));
141
-		}
142
-		\OCP\Util::writeLog('user_ldap',
143
-			'bgJ "updateGroups" – FINISHED dealing with created Groups.',
144
-			\OCP\Util::DEBUG);
145
-	}
146
-
147
-	/**
148
-	 * @param string[] $removedGroups
149
-	 */
150
-	static private function handleRemovedGroups($removedGroups) {
151
-		\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – dealing with removed groups.', \OCP\Util::DEBUG);
152
-		$query = \OCP\DB::prepare('
135
+        foreach($createdGroups as $createdGroup) {
136
+            \OCP\Util::writeLog('user_ldap',
137
+                'bgJ "updateGroups" – new group "'.$createdGroup.'" found.',
138
+                \OCP\Util::INFO);
139
+            $users = serialize(self::getGroupBE()->usersInGroup($createdGroup));
140
+            $query->execute(array($createdGroup, $users));
141
+        }
142
+        \OCP\Util::writeLog('user_ldap',
143
+            'bgJ "updateGroups" – FINISHED dealing with created Groups.',
144
+            \OCP\Util::DEBUG);
145
+    }
146
+
147
+    /**
148
+     * @param string[] $removedGroups
149
+     */
150
+    static private function handleRemovedGroups($removedGroups) {
151
+        \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – dealing with removed groups.', \OCP\Util::DEBUG);
152
+        $query = \OCP\DB::prepare('
153 153
 			DELETE
154 154
 			FROM `*PREFIX*ldap_group_members`
155 155
 			WHERE `owncloudname` = ?
156 156
 		');
157
-		foreach($removedGroups as $removedGroup) {
158
-			\OCP\Util::writeLog('user_ldap',
159
-				'bgJ "updateGroups" – group "'.$removedGroup.'" was removed.',
160
-				\OCP\Util::INFO);
161
-			$query->execute(array($removedGroup));
162
-		}
163
-		\OCP\Util::writeLog('user_ldap',
164
-			'bgJ "updateGroups" – FINISHED dealing with removed groups.',
165
-			\OCP\Util::DEBUG);
166
-	}
167
-
168
-	/**
169
-	 * @return \OCA\User_LDAP\Group_LDAP|\OCA\User_LDAP\Group_Proxy
170
-	 */
171
-	static private function getGroupBE() {
172
-		if(!is_null(self::$groupBE)) {
173
-			return self::$groupBE;
174
-		}
175
-		$helper = new Helper(\OC::$server->getConfig());
176
-		$configPrefixes = $helper->getServerConfigurationPrefixes(true);
177
-		$ldapWrapper = new LDAP();
178
-		if(count($configPrefixes) === 1) {
179
-			//avoid the proxy when there is only one LDAP server configured
180
-			$dbc = \OC::$server->getDatabaseConnection();
181
-			$userManager = new Manager(
182
-				\OC::$server->getConfig(),
183
-				new FilesystemHelper(),
184
-				new LogWrapper(),
185
-				\OC::$server->getAvatarManager(),
186
-				new \OCP\Image(),
187
-				$dbc,
188
-				\OC::$server->getUserManager(),
189
-				\OC::$server->getNotificationManager());
190
-			$connector = new Connection($ldapWrapper, $configPrefixes[0]);
191
-			$ldapAccess = new Access($connector, $ldapWrapper, $userManager, $helper, \OC::$server);
192
-			$groupMapper = new GroupMapping($dbc);
193
-			$userMapper  = new UserMapping($dbc);
194
-			$ldapAccess->setGroupMapper($groupMapper);
195
-			$ldapAccess->setUserMapper($userMapper);
196
-			self::$groupBE = new \OCA\User_LDAP\Group_LDAP($ldapAccess);
197
-		} else {
198
-			self::$groupBE = new \OCA\User_LDAP\Group_Proxy($configPrefixes, $ldapWrapper);
199
-		}
200
-
201
-		return self::$groupBE;
202
-	}
203
-
204
-	/**
205
-	 * @return array
206
-	 */
207
-	static private function getKnownGroups() {
208
-		if(is_array(self::$groupsFromDB)) {
209
-			return self::$groupsFromDB;
210
-		}
211
-		$query = \OCP\DB::prepare('
157
+        foreach($removedGroups as $removedGroup) {
158
+            \OCP\Util::writeLog('user_ldap',
159
+                'bgJ "updateGroups" – group "'.$removedGroup.'" was removed.',
160
+                \OCP\Util::INFO);
161
+            $query->execute(array($removedGroup));
162
+        }
163
+        \OCP\Util::writeLog('user_ldap',
164
+            'bgJ "updateGroups" – FINISHED dealing with removed groups.',
165
+            \OCP\Util::DEBUG);
166
+    }
167
+
168
+    /**
169
+     * @return \OCA\User_LDAP\Group_LDAP|\OCA\User_LDAP\Group_Proxy
170
+     */
171
+    static private function getGroupBE() {
172
+        if(!is_null(self::$groupBE)) {
173
+            return self::$groupBE;
174
+        }
175
+        $helper = new Helper(\OC::$server->getConfig());
176
+        $configPrefixes = $helper->getServerConfigurationPrefixes(true);
177
+        $ldapWrapper = new LDAP();
178
+        if(count($configPrefixes) === 1) {
179
+            //avoid the proxy when there is only one LDAP server configured
180
+            $dbc = \OC::$server->getDatabaseConnection();
181
+            $userManager = new Manager(
182
+                \OC::$server->getConfig(),
183
+                new FilesystemHelper(),
184
+                new LogWrapper(),
185
+                \OC::$server->getAvatarManager(),
186
+                new \OCP\Image(),
187
+                $dbc,
188
+                \OC::$server->getUserManager(),
189
+                \OC::$server->getNotificationManager());
190
+            $connector = new Connection($ldapWrapper, $configPrefixes[0]);
191
+            $ldapAccess = new Access($connector, $ldapWrapper, $userManager, $helper, \OC::$server);
192
+            $groupMapper = new GroupMapping($dbc);
193
+            $userMapper  = new UserMapping($dbc);
194
+            $ldapAccess->setGroupMapper($groupMapper);
195
+            $ldapAccess->setUserMapper($userMapper);
196
+            self::$groupBE = new \OCA\User_LDAP\Group_LDAP($ldapAccess);
197
+        } else {
198
+            self::$groupBE = new \OCA\User_LDAP\Group_Proxy($configPrefixes, $ldapWrapper);
199
+        }
200
+
201
+        return self::$groupBE;
202
+    }
203
+
204
+    /**
205
+     * @return array
206
+     */
207
+    static private function getKnownGroups() {
208
+        if(is_array(self::$groupsFromDB)) {
209
+            return self::$groupsFromDB;
210
+        }
211
+        $query = \OCP\DB::prepare('
212 212
 			SELECT `owncloudname`, `owncloudusers`
213 213
 			FROM `*PREFIX*ldap_group_members`
214 214
 		');
215
-		$result = $query->execute()->fetchAll();
216
-		self::$groupsFromDB = array();
217
-		foreach($result as $dataset) {
218
-			self::$groupsFromDB[$dataset['owncloudname']] = $dataset;
219
-		}
220
-
221
-		return self::$groupsFromDB;
222
-	}
215
+        $result = $query->execute()->fetchAll();
216
+        self::$groupsFromDB = array();
217
+        foreach($result as $dataset) {
218
+            self::$groupsFromDB[$dataset['owncloudname']] = $dataset;
219
+        }
220
+
221
+        return self::$groupsFromDB;
222
+    }
223 223
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Configuration.php 1 patch
Indentation   +464 added lines, -464 removed lines patch added patch discarded remove patch
@@ -36,493 +36,493 @@
 block discarded – undo
36 36
  */
37 37
 class Configuration {
38 38
 
39
-	protected $configPrefix = null;
40
-	protected $configRead = false;
39
+    protected $configPrefix = null;
40
+    protected $configRead = false;
41 41
 
42
-	//settings
43
-	protected $config = array(
44
-		'ldapHost' => null,
45
-		'ldapPort' => null,
46
-		'ldapBackupHost' => null,
47
-		'ldapBackupPort' => null,
48
-		'ldapBase' => null,
49
-		'ldapBaseUsers' => null,
50
-		'ldapBaseGroups' => null,
51
-		'ldapAgentName' => null,
52
-		'ldapAgentPassword' => null,
53
-		'ldapTLS' => null,
54
-		'turnOffCertCheck' => null,
55
-		'ldapIgnoreNamingRules' => null,
56
-		'ldapUserDisplayName' => null,
57
-		'ldapUserDisplayName2' => null,
58
-		'ldapGidNumber' => null,
59
-		'ldapUserFilterObjectclass' => null,
60
-		'ldapUserFilterGroups' => null,
61
-		'ldapUserFilter' => null,
62
-		'ldapUserFilterMode' => null,
63
-		'ldapGroupFilter' => null,
64
-		'ldapGroupFilterMode' => null,
65
-		'ldapGroupFilterObjectclass' => null,
66
-		'ldapGroupFilterGroups' => null,
67
-		'ldapGroupDisplayName' => null,
68
-		'ldapGroupMemberAssocAttr' => null,
69
-		'ldapLoginFilter' => null,
70
-		'ldapLoginFilterMode' => null,
71
-		'ldapLoginFilterEmail' => null,
72
-		'ldapLoginFilterUsername' => null,
73
-		'ldapLoginFilterAttributes' => null,
74
-		'ldapQuotaAttribute' => null,
75
-		'ldapQuotaDefault' => null,
76
-		'ldapEmailAttribute' => null,
77
-		'ldapCacheTTL' => null,
78
-		'ldapUuidUserAttribute' => 'auto',
79
-		'ldapUuidGroupAttribute' => 'auto',
80
-		'ldapOverrideMainServer' => false,
81
-		'ldapConfigurationActive' => false,
82
-		'ldapAttributesForUserSearch' => null,
83
-		'ldapAttributesForGroupSearch' => null,
84
-		'ldapExperiencedAdmin' => false,
85
-		'homeFolderNamingRule' => null,
86
-		'hasPagedResultSupport' => false,
87
-		'hasMemberOfFilterSupport' => false,
88
-		'useMemberOfToDetectMembership' => true,
89
-		'ldapExpertUsernameAttr' => null,
90
-		'ldapExpertUUIDUserAttr' => null,
91
-		'ldapExpertUUIDGroupAttr' => null,
92
-		'lastJpegPhotoLookup' => null,
93
-		'ldapNestedGroups' => false,
94
-		'ldapPagingSize' => null,
95
-		'turnOnPasswordChange' => false,
96
-		'ldapDynamicGroupMemberURL' => null,
97
-		'ldapDefaultPPolicyDN' => null,
98
-	);
42
+    //settings
43
+    protected $config = array(
44
+        'ldapHost' => null,
45
+        'ldapPort' => null,
46
+        'ldapBackupHost' => null,
47
+        'ldapBackupPort' => null,
48
+        'ldapBase' => null,
49
+        'ldapBaseUsers' => null,
50
+        'ldapBaseGroups' => null,
51
+        'ldapAgentName' => null,
52
+        'ldapAgentPassword' => null,
53
+        'ldapTLS' => null,
54
+        'turnOffCertCheck' => null,
55
+        'ldapIgnoreNamingRules' => null,
56
+        'ldapUserDisplayName' => null,
57
+        'ldapUserDisplayName2' => null,
58
+        'ldapGidNumber' => null,
59
+        'ldapUserFilterObjectclass' => null,
60
+        'ldapUserFilterGroups' => null,
61
+        'ldapUserFilter' => null,
62
+        'ldapUserFilterMode' => null,
63
+        'ldapGroupFilter' => null,
64
+        'ldapGroupFilterMode' => null,
65
+        'ldapGroupFilterObjectclass' => null,
66
+        'ldapGroupFilterGroups' => null,
67
+        'ldapGroupDisplayName' => null,
68
+        'ldapGroupMemberAssocAttr' => null,
69
+        'ldapLoginFilter' => null,
70
+        'ldapLoginFilterMode' => null,
71
+        'ldapLoginFilterEmail' => null,
72
+        'ldapLoginFilterUsername' => null,
73
+        'ldapLoginFilterAttributes' => null,
74
+        'ldapQuotaAttribute' => null,
75
+        'ldapQuotaDefault' => null,
76
+        'ldapEmailAttribute' => null,
77
+        'ldapCacheTTL' => null,
78
+        'ldapUuidUserAttribute' => 'auto',
79
+        'ldapUuidGroupAttribute' => 'auto',
80
+        'ldapOverrideMainServer' => false,
81
+        'ldapConfigurationActive' => false,
82
+        'ldapAttributesForUserSearch' => null,
83
+        'ldapAttributesForGroupSearch' => null,
84
+        'ldapExperiencedAdmin' => false,
85
+        'homeFolderNamingRule' => null,
86
+        'hasPagedResultSupport' => false,
87
+        'hasMemberOfFilterSupport' => false,
88
+        'useMemberOfToDetectMembership' => true,
89
+        'ldapExpertUsernameAttr' => null,
90
+        'ldapExpertUUIDUserAttr' => null,
91
+        'ldapExpertUUIDGroupAttr' => null,
92
+        'lastJpegPhotoLookup' => null,
93
+        'ldapNestedGroups' => false,
94
+        'ldapPagingSize' => null,
95
+        'turnOnPasswordChange' => false,
96
+        'ldapDynamicGroupMemberURL' => null,
97
+        'ldapDefaultPPolicyDN' => null,
98
+    );
99 99
 
100
-	/**
101
-	 * @param string $configPrefix
102
-	 * @param bool $autoRead
103
-	 */
104
-	public function __construct($configPrefix, $autoRead = true) {
105
-		$this->configPrefix = $configPrefix;
106
-		if($autoRead) {
107
-			$this->readConfiguration();
108
-		}
109
-	}
100
+    /**
101
+     * @param string $configPrefix
102
+     * @param bool $autoRead
103
+     */
104
+    public function __construct($configPrefix, $autoRead = true) {
105
+        $this->configPrefix = $configPrefix;
106
+        if($autoRead) {
107
+            $this->readConfiguration();
108
+        }
109
+    }
110 110
 
111
-	/**
112
-	 * @param string $name
113
-	 * @return mixed|null
114
-	 */
115
-	public function __get($name) {
116
-		if(isset($this->config[$name])) {
117
-			return $this->config[$name];
118
-		}
119
-		return null;
120
-	}
111
+    /**
112
+     * @param string $name
113
+     * @return mixed|null
114
+     */
115
+    public function __get($name) {
116
+        if(isset($this->config[$name])) {
117
+            return $this->config[$name];
118
+        }
119
+        return null;
120
+    }
121 121
 
122
-	/**
123
-	 * @param string $name
124
-	 * @param mixed $value
125
-	 */
126
-	public function __set($name, $value) {
127
-		$this->setConfiguration(array($name => $value));
128
-	}
122
+    /**
123
+     * @param string $name
124
+     * @param mixed $value
125
+     */
126
+    public function __set($name, $value) {
127
+        $this->setConfiguration(array($name => $value));
128
+    }
129 129
 
130
-	/**
131
-	 * @return array
132
-	 */
133
-	public function getConfiguration() {
134
-		return $this->config;
135
-	}
130
+    /**
131
+     * @return array
132
+     */
133
+    public function getConfiguration() {
134
+        return $this->config;
135
+    }
136 136
 
137
-	/**
138
-	 * set LDAP configuration with values delivered by an array, not read
139
-	 * from configuration. It does not save the configuration! To do so, you
140
-	 * must call saveConfiguration afterwards.
141
-	 * @param array $config array that holds the config parameters in an associated
142
-	 * array
143
-	 * @param array &$applied optional; array where the set fields will be given to
144
-	 * @return false|null
145
-	 */
146
-	public function setConfiguration($config, &$applied = null) {
147
-		if(!is_array($config)) {
148
-			return false;
149
-		}
137
+    /**
138
+     * set LDAP configuration with values delivered by an array, not read
139
+     * from configuration. It does not save the configuration! To do so, you
140
+     * must call saveConfiguration afterwards.
141
+     * @param array $config array that holds the config parameters in an associated
142
+     * array
143
+     * @param array &$applied optional; array where the set fields will be given to
144
+     * @return false|null
145
+     */
146
+    public function setConfiguration($config, &$applied = null) {
147
+        if(!is_array($config)) {
148
+            return false;
149
+        }
150 150
 
151
-		$cta = $this->getConfigTranslationArray();
152
-		foreach($config as $inputKey => $val) {
153
-			if(strpos($inputKey, '_') !== false && array_key_exists($inputKey, $cta)) {
154
-				$key = $cta[$inputKey];
155
-			} elseif(array_key_exists($inputKey, $this->config)) {
156
-				$key = $inputKey;
157
-			} else {
158
-				continue;
159
-			}
151
+        $cta = $this->getConfigTranslationArray();
152
+        foreach($config as $inputKey => $val) {
153
+            if(strpos($inputKey, '_') !== false && array_key_exists($inputKey, $cta)) {
154
+                $key = $cta[$inputKey];
155
+            } elseif(array_key_exists($inputKey, $this->config)) {
156
+                $key = $inputKey;
157
+            } else {
158
+                continue;
159
+            }
160 160
 
161
-			$setMethod = 'setValue';
162
-			switch($key) {
163
-				case 'ldapAgentPassword':
164
-					$setMethod = 'setRawValue';
165
-					break;
166
-				case 'homeFolderNamingRule':
167
-					$trimmedVal = trim($val);
168
-					if ($trimmedVal !== '' && strpos($val, 'attr:') === false) {
169
-						$val = 'attr:'.$trimmedVal;
170
-					}
171
-					break;
172
-				case 'ldapBase':
173
-				case 'ldapBaseUsers':
174
-				case 'ldapBaseGroups':
175
-				case 'ldapAttributesForUserSearch':
176
-				case 'ldapAttributesForGroupSearch':
177
-				case 'ldapUserFilterObjectclass':
178
-				case 'ldapUserFilterGroups':
179
-				case 'ldapGroupFilterObjectclass':
180
-				case 'ldapGroupFilterGroups':
181
-				case 'ldapLoginFilterAttributes':
182
-					$setMethod = 'setMultiLine';
183
-					break;
184
-			}
185
-			$this->$setMethod($key, $val);
186
-			if(is_array($applied)) {
187
-				$applied[] = $inputKey;
188
-			}
189
-		}
190
-		return null;
191
-	}
161
+            $setMethod = 'setValue';
162
+            switch($key) {
163
+                case 'ldapAgentPassword':
164
+                    $setMethod = 'setRawValue';
165
+                    break;
166
+                case 'homeFolderNamingRule':
167
+                    $trimmedVal = trim($val);
168
+                    if ($trimmedVal !== '' && strpos($val, 'attr:') === false) {
169
+                        $val = 'attr:'.$trimmedVal;
170
+                    }
171
+                    break;
172
+                case 'ldapBase':
173
+                case 'ldapBaseUsers':
174
+                case 'ldapBaseGroups':
175
+                case 'ldapAttributesForUserSearch':
176
+                case 'ldapAttributesForGroupSearch':
177
+                case 'ldapUserFilterObjectclass':
178
+                case 'ldapUserFilterGroups':
179
+                case 'ldapGroupFilterObjectclass':
180
+                case 'ldapGroupFilterGroups':
181
+                case 'ldapLoginFilterAttributes':
182
+                    $setMethod = 'setMultiLine';
183
+                    break;
184
+            }
185
+            $this->$setMethod($key, $val);
186
+            if(is_array($applied)) {
187
+                $applied[] = $inputKey;
188
+            }
189
+        }
190
+        return null;
191
+    }
192 192
 
193
-	public function readConfiguration() {
194
-		if(!$this->configRead && !is_null($this->configPrefix)) {
195
-			$cta = array_flip($this->getConfigTranslationArray());
196
-			foreach($this->config as $key => $val) {
197
-				if(!isset($cta[$key])) {
198
-					//some are determined
199
-					continue;
200
-				}
201
-				$dbKey = $cta[$key];
202
-				switch($key) {
203
-					case 'ldapBase':
204
-					case 'ldapBaseUsers':
205
-					case 'ldapBaseGroups':
206
-					case 'ldapAttributesForUserSearch':
207
-					case 'ldapAttributesForGroupSearch':
208
-					case 'ldapUserFilterObjectclass':
209
-					case 'ldapUserFilterGroups':
210
-					case 'ldapGroupFilterObjectclass':
211
-					case 'ldapGroupFilterGroups':
212
-					case 'ldapLoginFilterAttributes':
213
-						$readMethod = 'getMultiLine';
214
-						break;
215
-					case 'ldapIgnoreNamingRules':
216
-						$readMethod = 'getSystemValue';
217
-						$dbKey = $key;
218
-						break;
219
-					case 'ldapAgentPassword':
220
-						$readMethod = 'getPwd';
221
-						break;
222
-					case 'ldapUserDisplayName2':
223
-					case 'ldapGroupDisplayName':
224
-						$readMethod = 'getLcValue';
225
-						break;
226
-					case 'ldapUserDisplayName':
227
-					default:
228
-						// user display name does not lower case because
229
-						// we rely on an upper case N as indicator whether to
230
-						// auto-detect it or not. FIXME
231
-						$readMethod = 'getValue';
232
-						break;
233
-				}
234
-				$this->config[$key] = $this->$readMethod($dbKey);
235
-			}
236
-			$this->configRead = true;
237
-		}
238
-	}
193
+    public function readConfiguration() {
194
+        if(!$this->configRead && !is_null($this->configPrefix)) {
195
+            $cta = array_flip($this->getConfigTranslationArray());
196
+            foreach($this->config as $key => $val) {
197
+                if(!isset($cta[$key])) {
198
+                    //some are determined
199
+                    continue;
200
+                }
201
+                $dbKey = $cta[$key];
202
+                switch($key) {
203
+                    case 'ldapBase':
204
+                    case 'ldapBaseUsers':
205
+                    case 'ldapBaseGroups':
206
+                    case 'ldapAttributesForUserSearch':
207
+                    case 'ldapAttributesForGroupSearch':
208
+                    case 'ldapUserFilterObjectclass':
209
+                    case 'ldapUserFilterGroups':
210
+                    case 'ldapGroupFilterObjectclass':
211
+                    case 'ldapGroupFilterGroups':
212
+                    case 'ldapLoginFilterAttributes':
213
+                        $readMethod = 'getMultiLine';
214
+                        break;
215
+                    case 'ldapIgnoreNamingRules':
216
+                        $readMethod = 'getSystemValue';
217
+                        $dbKey = $key;
218
+                        break;
219
+                    case 'ldapAgentPassword':
220
+                        $readMethod = 'getPwd';
221
+                        break;
222
+                    case 'ldapUserDisplayName2':
223
+                    case 'ldapGroupDisplayName':
224
+                        $readMethod = 'getLcValue';
225
+                        break;
226
+                    case 'ldapUserDisplayName':
227
+                    default:
228
+                        // user display name does not lower case because
229
+                        // we rely on an upper case N as indicator whether to
230
+                        // auto-detect it or not. FIXME
231
+                        $readMethod = 'getValue';
232
+                        break;
233
+                }
234
+                $this->config[$key] = $this->$readMethod($dbKey);
235
+            }
236
+            $this->configRead = true;
237
+        }
238
+    }
239 239
 
240
-	/**
241
-	 * saves the current Configuration in the database
242
-	 */
243
-	public function saveConfiguration() {
244
-		$cta = array_flip($this->getConfigTranslationArray());
245
-		foreach($this->config as $key => $value) {
246
-			switch ($key) {
247
-				case 'ldapAgentPassword':
248
-					$value = base64_encode($value);
249
-					break;
250
-				case 'ldapBase':
251
-				case 'ldapBaseUsers':
252
-				case 'ldapBaseGroups':
253
-				case 'ldapAttributesForUserSearch':
254
-				case 'ldapAttributesForGroupSearch':
255
-				case 'ldapUserFilterObjectclass':
256
-				case 'ldapUserFilterGroups':
257
-				case 'ldapGroupFilterObjectclass':
258
-				case 'ldapGroupFilterGroups':
259
-				case 'ldapLoginFilterAttributes':
260
-					if(is_array($value)) {
261
-						$value = implode("\n", $value);
262
-					}
263
-					break;
264
-				//following options are not stored but detected, skip them
265
-				case 'ldapIgnoreNamingRules':
266
-				case 'hasPagedResultSupport':
267
-				case 'ldapUuidUserAttribute':
268
-				case 'ldapUuidGroupAttribute':
269
-					continue 2;
270
-			}
271
-			if(is_null($value)) {
272
-				$value = '';
273
-			}
274
-			$this->saveValue($cta[$key], $value);
275
-		}
276
-		$this->saveValue('_lastChange', time());
277
-	}
240
+    /**
241
+     * saves the current Configuration in the database
242
+     */
243
+    public function saveConfiguration() {
244
+        $cta = array_flip($this->getConfigTranslationArray());
245
+        foreach($this->config as $key => $value) {
246
+            switch ($key) {
247
+                case 'ldapAgentPassword':
248
+                    $value = base64_encode($value);
249
+                    break;
250
+                case 'ldapBase':
251
+                case 'ldapBaseUsers':
252
+                case 'ldapBaseGroups':
253
+                case 'ldapAttributesForUserSearch':
254
+                case 'ldapAttributesForGroupSearch':
255
+                case 'ldapUserFilterObjectclass':
256
+                case 'ldapUserFilterGroups':
257
+                case 'ldapGroupFilterObjectclass':
258
+                case 'ldapGroupFilterGroups':
259
+                case 'ldapLoginFilterAttributes':
260
+                    if(is_array($value)) {
261
+                        $value = implode("\n", $value);
262
+                    }
263
+                    break;
264
+                //following options are not stored but detected, skip them
265
+                case 'ldapIgnoreNamingRules':
266
+                case 'hasPagedResultSupport':
267
+                case 'ldapUuidUserAttribute':
268
+                case 'ldapUuidGroupAttribute':
269
+                    continue 2;
270
+            }
271
+            if(is_null($value)) {
272
+                $value = '';
273
+            }
274
+            $this->saveValue($cta[$key], $value);
275
+        }
276
+        $this->saveValue('_lastChange', time());
277
+    }
278 278
 
279
-	/**
280
-	 * @param string $varName
281
-	 * @return array|string
282
-	 */
283
-	protected function getMultiLine($varName) {
284
-		$value = $this->getValue($varName);
285
-		if(empty($value)) {
286
-			$value = '';
287
-		} else {
288
-			$value = preg_split('/\r\n|\r|\n/', $value);
289
-		}
279
+    /**
280
+     * @param string $varName
281
+     * @return array|string
282
+     */
283
+    protected function getMultiLine($varName) {
284
+        $value = $this->getValue($varName);
285
+        if(empty($value)) {
286
+            $value = '';
287
+        } else {
288
+            $value = preg_split('/\r\n|\r|\n/', $value);
289
+        }
290 290
 
291
-		return $value;
292
-	}
291
+        return $value;
292
+    }
293 293
 
294
-	/**
295
-	 * Sets multi-line values as arrays
296
-	 * 
297
-	 * @param string $varName name of config-key
298
-	 * @param array|string $value to set
299
-	 */
300
-	protected function setMultiLine($varName, $value) {
301
-		if(empty($value)) {
302
-			$value = '';
303
-		} else if (!is_array($value)) {
304
-			$value = preg_split('/\r\n|\r|\n|;/', $value);
305
-			if($value === false) {
306
-				$value = '';
307
-			}
308
-		}
294
+    /**
295
+     * Sets multi-line values as arrays
296
+     * 
297
+     * @param string $varName name of config-key
298
+     * @param array|string $value to set
299
+     */
300
+    protected function setMultiLine($varName, $value) {
301
+        if(empty($value)) {
302
+            $value = '';
303
+        } else if (!is_array($value)) {
304
+            $value = preg_split('/\r\n|\r|\n|;/', $value);
305
+            if($value === false) {
306
+                $value = '';
307
+            }
308
+        }
309 309
 
310
-		if(!is_array($value)) {
311
-			$finalValue = trim($value);
312
-		} else {
313
-			$finalValue = [];
314
-			foreach($value as $key => $val) {
315
-				if(is_string($val)) {
316
-					$val = trim($val);
317
-					if ($val !== '') {
318
-						//accidental line breaks are not wanted and can cause
319
-						// odd behaviour. Thus, away with them.
320
-						$finalValue[] = $val;
321
-					}
322
-				} else {
323
-					$finalValue[] = $val;
324
-				}
325
-			}
326
-		}
310
+        if(!is_array($value)) {
311
+            $finalValue = trim($value);
312
+        } else {
313
+            $finalValue = [];
314
+            foreach($value as $key => $val) {
315
+                if(is_string($val)) {
316
+                    $val = trim($val);
317
+                    if ($val !== '') {
318
+                        //accidental line breaks are not wanted and can cause
319
+                        // odd behaviour. Thus, away with them.
320
+                        $finalValue[] = $val;
321
+                    }
322
+                } else {
323
+                    $finalValue[] = $val;
324
+                }
325
+            }
326
+        }
327 327
 
328
-		$this->setRawValue($varName, $finalValue);
329
-	}
328
+        $this->setRawValue($varName, $finalValue);
329
+    }
330 330
 
331
-	/**
332
-	 * @param string $varName
333
-	 * @return string
334
-	 */
335
-	protected function getPwd($varName) {
336
-		return base64_decode($this->getValue($varName));
337
-	}
331
+    /**
332
+     * @param string $varName
333
+     * @return string
334
+     */
335
+    protected function getPwd($varName) {
336
+        return base64_decode($this->getValue($varName));
337
+    }
338 338
 
339
-	/**
340
-	 * @param string $varName
341
-	 * @return string
342
-	 */
343
-	protected function getLcValue($varName) {
344
-		return mb_strtolower($this->getValue($varName), 'UTF-8');
345
-	}
339
+    /**
340
+     * @param string $varName
341
+     * @return string
342
+     */
343
+    protected function getLcValue($varName) {
344
+        return mb_strtolower($this->getValue($varName), 'UTF-8');
345
+    }
346 346
 
347
-	/**
348
-	 * @param string $varName
349
-	 * @return string
350
-	 */
351
-	protected function getSystemValue($varName) {
352
-		//FIXME: if another system value is added, softcode the default value
353
-		return \OC::$server->getConfig()->getSystemValue($varName, false);
354
-	}
347
+    /**
348
+     * @param string $varName
349
+     * @return string
350
+     */
351
+    protected function getSystemValue($varName) {
352
+        //FIXME: if another system value is added, softcode the default value
353
+        return \OC::$server->getConfig()->getSystemValue($varName, false);
354
+    }
355 355
 
356
-	/**
357
-	 * @param string $varName
358
-	 * @return string
359
-	 */
360
-	protected function getValue($varName) {
361
-		static $defaults;
362
-		if(is_null($defaults)) {
363
-			$defaults = $this->getDefaults();
364
-		}
365
-		return \OCP\Config::getAppValue('user_ldap',
366
-										$this->configPrefix.$varName,
367
-										$defaults[$varName]);
368
-	}
356
+    /**
357
+     * @param string $varName
358
+     * @return string
359
+     */
360
+    protected function getValue($varName) {
361
+        static $defaults;
362
+        if(is_null($defaults)) {
363
+            $defaults = $this->getDefaults();
364
+        }
365
+        return \OCP\Config::getAppValue('user_ldap',
366
+                                        $this->configPrefix.$varName,
367
+                                        $defaults[$varName]);
368
+    }
369 369
 
370
-	/**
371
-	 * Sets a scalar value.
372
-	 * 
373
-	 * @param string $varName name of config key
374
-	 * @param mixed $value to set
375
-	 */
376
-	protected function setValue($varName, $value) {
377
-		if(is_string($value)) {
378
-			$value = trim($value);
379
-		}
380
-		$this->config[$varName] = $value;
381
-	}
370
+    /**
371
+     * Sets a scalar value.
372
+     * 
373
+     * @param string $varName name of config key
374
+     * @param mixed $value to set
375
+     */
376
+    protected function setValue($varName, $value) {
377
+        if(is_string($value)) {
378
+            $value = trim($value);
379
+        }
380
+        $this->config[$varName] = $value;
381
+    }
382 382
 
383
-	/**
384
-	 * Sets a scalar value without trimming.
385
-	 *
386
-	 * @param string $varName name of config key
387
-	 * @param mixed $value to set
388
-	 */
389
-	protected function setRawValue($varName, $value) {
390
-		$this->config[$varName] = $value;
391
-	}
383
+    /**
384
+     * Sets a scalar value without trimming.
385
+     *
386
+     * @param string $varName name of config key
387
+     * @param mixed $value to set
388
+     */
389
+    protected function setRawValue($varName, $value) {
390
+        $this->config[$varName] = $value;
391
+    }
392 392
 
393
-	/**
394
-	 * @param string $varName
395
-	 * @param string $value
396
-	 * @return bool
397
-	 */
398
-	protected function saveValue($varName, $value) {
399
-		\OC::$server->getConfig()->setAppValue(
400
-			'user_ldap',
401
-			$this->configPrefix.$varName,
402
-			$value
403
-		);
404
-		return true;
405
-	}
393
+    /**
394
+     * @param string $varName
395
+     * @param string $value
396
+     * @return bool
397
+     */
398
+    protected function saveValue($varName, $value) {
399
+        \OC::$server->getConfig()->setAppValue(
400
+            'user_ldap',
401
+            $this->configPrefix.$varName,
402
+            $value
403
+        );
404
+        return true;
405
+    }
406 406
 
407
-	/**
408
-	 * @return array an associative array with the default values. Keys are correspond
409
-	 * to config-value entries in the database table
410
-	 */
411
-	public function getDefaults() {
412
-		return array(
413
-			'ldap_host'                         => '',
414
-			'ldap_port'                         => '',
415
-			'ldap_backup_host'                  => '',
416
-			'ldap_backup_port'                  => '',
417
-			'ldap_override_main_server'         => '',
418
-			'ldap_dn'                           => '',
419
-			'ldap_agent_password'               => '',
420
-			'ldap_base'                         => '',
421
-			'ldap_base_users'                   => '',
422
-			'ldap_base_groups'                  => '',
423
-			'ldap_userlist_filter'              => '',
424
-			'ldap_user_filter_mode'             => 0,
425
-			'ldap_userfilter_objectclass'       => '',
426
-			'ldap_userfilter_groups'            => '',
427
-			'ldap_login_filter'                 => '',
428
-			'ldap_login_filter_mode'            => 0,
429
-			'ldap_loginfilter_email'            => 0,
430
-			'ldap_loginfilter_username'         => 1,
431
-			'ldap_loginfilter_attributes'       => '',
432
-			'ldap_group_filter'                 => '',
433
-			'ldap_group_filter_mode'            => 0,
434
-			'ldap_groupfilter_objectclass'      => '',
435
-			'ldap_groupfilter_groups'           => '',
436
-			'ldap_gid_number'                   => 'gidNumber',
437
-			'ldap_display_name'                 => 'displayName',
438
-			'ldap_user_display_name_2'			=> '',
439
-			'ldap_group_display_name'           => 'cn',
440
-			'ldap_tls'                          => 0,
441
-			'ldap_quota_def'                    => '',
442
-			'ldap_quota_attr'                   => '',
443
-			'ldap_email_attr'                   => '',
444
-			'ldap_group_member_assoc_attribute' => 'uniqueMember',
445
-			'ldap_cache_ttl'                    => 600,
446
-			'ldap_uuid_user_attribute'          => 'auto',
447
-			'ldap_uuid_group_attribute'         => 'auto',
448
-			'home_folder_naming_rule'           => '',
449
-			'ldap_turn_off_cert_check'          => 0,
450
-			'ldap_configuration_active'         => 0,
451
-			'ldap_attributes_for_user_search'   => '',
452
-			'ldap_attributes_for_group_search'  => '',
453
-			'ldap_expert_username_attr'         => '',
454
-			'ldap_expert_uuid_user_attr'        => '',
455
-			'ldap_expert_uuid_group_attr'       => '',
456
-			'has_memberof_filter_support'       => 0,
457
-			'use_memberof_to_detect_membership' => 1,
458
-			'last_jpegPhoto_lookup'             => 0,
459
-			'ldap_nested_groups'                => 0,
460
-			'ldap_paging_size'                  => 500,
461
-			'ldap_turn_on_pwd_change'           => 0,
462
-			'ldap_experienced_admin'            => 0,
463
-			'ldap_dynamic_group_member_url'     => '',
464
-			'ldap_default_ppolicy_dn'           => '',
465
-		);
466
-	}
407
+    /**
408
+     * @return array an associative array with the default values. Keys are correspond
409
+     * to config-value entries in the database table
410
+     */
411
+    public function getDefaults() {
412
+        return array(
413
+            'ldap_host'                         => '',
414
+            'ldap_port'                         => '',
415
+            'ldap_backup_host'                  => '',
416
+            'ldap_backup_port'                  => '',
417
+            'ldap_override_main_server'         => '',
418
+            'ldap_dn'                           => '',
419
+            'ldap_agent_password'               => '',
420
+            'ldap_base'                         => '',
421
+            'ldap_base_users'                   => '',
422
+            'ldap_base_groups'                  => '',
423
+            'ldap_userlist_filter'              => '',
424
+            'ldap_user_filter_mode'             => 0,
425
+            'ldap_userfilter_objectclass'       => '',
426
+            'ldap_userfilter_groups'            => '',
427
+            'ldap_login_filter'                 => '',
428
+            'ldap_login_filter_mode'            => 0,
429
+            'ldap_loginfilter_email'            => 0,
430
+            'ldap_loginfilter_username'         => 1,
431
+            'ldap_loginfilter_attributes'       => '',
432
+            'ldap_group_filter'                 => '',
433
+            'ldap_group_filter_mode'            => 0,
434
+            'ldap_groupfilter_objectclass'      => '',
435
+            'ldap_groupfilter_groups'           => '',
436
+            'ldap_gid_number'                   => 'gidNumber',
437
+            'ldap_display_name'                 => 'displayName',
438
+            'ldap_user_display_name_2'			=> '',
439
+            'ldap_group_display_name'           => 'cn',
440
+            'ldap_tls'                          => 0,
441
+            'ldap_quota_def'                    => '',
442
+            'ldap_quota_attr'                   => '',
443
+            'ldap_email_attr'                   => '',
444
+            'ldap_group_member_assoc_attribute' => 'uniqueMember',
445
+            'ldap_cache_ttl'                    => 600,
446
+            'ldap_uuid_user_attribute'          => 'auto',
447
+            'ldap_uuid_group_attribute'         => 'auto',
448
+            'home_folder_naming_rule'           => '',
449
+            'ldap_turn_off_cert_check'          => 0,
450
+            'ldap_configuration_active'         => 0,
451
+            'ldap_attributes_for_user_search'   => '',
452
+            'ldap_attributes_for_group_search'  => '',
453
+            'ldap_expert_username_attr'         => '',
454
+            'ldap_expert_uuid_user_attr'        => '',
455
+            'ldap_expert_uuid_group_attr'       => '',
456
+            'has_memberof_filter_support'       => 0,
457
+            'use_memberof_to_detect_membership' => 1,
458
+            'last_jpegPhoto_lookup'             => 0,
459
+            'ldap_nested_groups'                => 0,
460
+            'ldap_paging_size'                  => 500,
461
+            'ldap_turn_on_pwd_change'           => 0,
462
+            'ldap_experienced_admin'            => 0,
463
+            'ldap_dynamic_group_member_url'     => '',
464
+            'ldap_default_ppolicy_dn'           => '',
465
+        );
466
+    }
467 467
 
468
-	/**
469
-	 * @return array that maps internal variable names to database fields
470
-	 */
471
-	public function getConfigTranslationArray() {
472
-		//TODO: merge them into one representation
473
-		static $array = array(
474
-			'ldap_host'                         => 'ldapHost',
475
-			'ldap_port'                         => 'ldapPort',
476
-			'ldap_backup_host'                  => 'ldapBackupHost',
477
-			'ldap_backup_port'                  => 'ldapBackupPort',
478
-			'ldap_override_main_server'         => 'ldapOverrideMainServer',
479
-			'ldap_dn'                           => 'ldapAgentName',
480
-			'ldap_agent_password'               => 'ldapAgentPassword',
481
-			'ldap_base'                         => 'ldapBase',
482
-			'ldap_base_users'                   => 'ldapBaseUsers',
483
-			'ldap_base_groups'                  => 'ldapBaseGroups',
484
-			'ldap_userfilter_objectclass'       => 'ldapUserFilterObjectclass',
485
-			'ldap_userfilter_groups'            => 'ldapUserFilterGroups',
486
-			'ldap_userlist_filter'              => 'ldapUserFilter',
487
-			'ldap_user_filter_mode'             => 'ldapUserFilterMode',
488
-			'ldap_login_filter'                 => 'ldapLoginFilter',
489
-			'ldap_login_filter_mode'            => 'ldapLoginFilterMode',
490
-			'ldap_loginfilter_email'            => 'ldapLoginFilterEmail',
491
-			'ldap_loginfilter_username'         => 'ldapLoginFilterUsername',
492
-			'ldap_loginfilter_attributes'       => 'ldapLoginFilterAttributes',
493
-			'ldap_group_filter'                 => 'ldapGroupFilter',
494
-			'ldap_group_filter_mode'            => 'ldapGroupFilterMode',
495
-			'ldap_groupfilter_objectclass'      => 'ldapGroupFilterObjectclass',
496
-			'ldap_groupfilter_groups'           => 'ldapGroupFilterGroups',
497
-			'ldap_gid_number'                   => 'ldapGidNumber',
498
-			'ldap_display_name'                 => 'ldapUserDisplayName',
499
-			'ldap_user_display_name_2'			=> 'ldapUserDisplayName2',
500
-			'ldap_group_display_name'           => 'ldapGroupDisplayName',
501
-			'ldap_tls'                          => 'ldapTLS',
502
-			'ldap_quota_def'                    => 'ldapQuotaDefault',
503
-			'ldap_quota_attr'                   => 'ldapQuotaAttribute',
504
-			'ldap_email_attr'                   => 'ldapEmailAttribute',
505
-			'ldap_group_member_assoc_attribute' => 'ldapGroupMemberAssocAttr',
506
-			'ldap_cache_ttl'                    => 'ldapCacheTTL',
507
-			'home_folder_naming_rule'           => 'homeFolderNamingRule',
508
-			'ldap_turn_off_cert_check'          => 'turnOffCertCheck',
509
-			'ldap_configuration_active'         => 'ldapConfigurationActive',
510
-			'ldap_attributes_for_user_search'   => 'ldapAttributesForUserSearch',
511
-			'ldap_attributes_for_group_search'  => 'ldapAttributesForGroupSearch',
512
-			'ldap_expert_username_attr'         => 'ldapExpertUsernameAttr',
513
-			'ldap_expert_uuid_user_attr'        => 'ldapExpertUUIDUserAttr',
514
-			'ldap_expert_uuid_group_attr'       => 'ldapExpertUUIDGroupAttr',
515
-			'has_memberof_filter_support'       => 'hasMemberOfFilterSupport',
516
-			'use_memberof_to_detect_membership' => 'useMemberOfToDetectMembership',
517
-			'last_jpegPhoto_lookup'             => 'lastJpegPhotoLookup',
518
-			'ldap_nested_groups'                => 'ldapNestedGroups',
519
-			'ldap_paging_size'                  => 'ldapPagingSize',
520
-			'ldap_turn_on_pwd_change'           => 'turnOnPasswordChange',
521
-			'ldap_experienced_admin'            => 'ldapExperiencedAdmin',
522
-			'ldap_dynamic_group_member_url'     => 'ldapDynamicGroupMemberURL',
523
-			'ldap_default_ppolicy_dn'           => 'ldapDefaultPPolicyDN',
524
-		);
525
-		return $array;
526
-	}
468
+    /**
469
+     * @return array that maps internal variable names to database fields
470
+     */
471
+    public function getConfigTranslationArray() {
472
+        //TODO: merge them into one representation
473
+        static $array = array(
474
+            'ldap_host'                         => 'ldapHost',
475
+            'ldap_port'                         => 'ldapPort',
476
+            'ldap_backup_host'                  => 'ldapBackupHost',
477
+            'ldap_backup_port'                  => 'ldapBackupPort',
478
+            'ldap_override_main_server'         => 'ldapOverrideMainServer',
479
+            'ldap_dn'                           => 'ldapAgentName',
480
+            'ldap_agent_password'               => 'ldapAgentPassword',
481
+            'ldap_base'                         => 'ldapBase',
482
+            'ldap_base_users'                   => 'ldapBaseUsers',
483
+            'ldap_base_groups'                  => 'ldapBaseGroups',
484
+            'ldap_userfilter_objectclass'       => 'ldapUserFilterObjectclass',
485
+            'ldap_userfilter_groups'            => 'ldapUserFilterGroups',
486
+            'ldap_userlist_filter'              => 'ldapUserFilter',
487
+            'ldap_user_filter_mode'             => 'ldapUserFilterMode',
488
+            'ldap_login_filter'                 => 'ldapLoginFilter',
489
+            'ldap_login_filter_mode'            => 'ldapLoginFilterMode',
490
+            'ldap_loginfilter_email'            => 'ldapLoginFilterEmail',
491
+            'ldap_loginfilter_username'         => 'ldapLoginFilterUsername',
492
+            'ldap_loginfilter_attributes'       => 'ldapLoginFilterAttributes',
493
+            'ldap_group_filter'                 => 'ldapGroupFilter',
494
+            'ldap_group_filter_mode'            => 'ldapGroupFilterMode',
495
+            'ldap_groupfilter_objectclass'      => 'ldapGroupFilterObjectclass',
496
+            'ldap_groupfilter_groups'           => 'ldapGroupFilterGroups',
497
+            'ldap_gid_number'                   => 'ldapGidNumber',
498
+            'ldap_display_name'                 => 'ldapUserDisplayName',
499
+            'ldap_user_display_name_2'			=> 'ldapUserDisplayName2',
500
+            'ldap_group_display_name'           => 'ldapGroupDisplayName',
501
+            'ldap_tls'                          => 'ldapTLS',
502
+            'ldap_quota_def'                    => 'ldapQuotaDefault',
503
+            'ldap_quota_attr'                   => 'ldapQuotaAttribute',
504
+            'ldap_email_attr'                   => 'ldapEmailAttribute',
505
+            'ldap_group_member_assoc_attribute' => 'ldapGroupMemberAssocAttr',
506
+            'ldap_cache_ttl'                    => 'ldapCacheTTL',
507
+            'home_folder_naming_rule'           => 'homeFolderNamingRule',
508
+            'ldap_turn_off_cert_check'          => 'turnOffCertCheck',
509
+            'ldap_configuration_active'         => 'ldapConfigurationActive',
510
+            'ldap_attributes_for_user_search'   => 'ldapAttributesForUserSearch',
511
+            'ldap_attributes_for_group_search'  => 'ldapAttributesForGroupSearch',
512
+            'ldap_expert_username_attr'         => 'ldapExpertUsernameAttr',
513
+            'ldap_expert_uuid_user_attr'        => 'ldapExpertUUIDUserAttr',
514
+            'ldap_expert_uuid_group_attr'       => 'ldapExpertUUIDGroupAttr',
515
+            'has_memberof_filter_support'       => 'hasMemberOfFilterSupport',
516
+            'use_memberof_to_detect_membership' => 'useMemberOfToDetectMembership',
517
+            'last_jpegPhoto_lookup'             => 'lastJpegPhotoLookup',
518
+            'ldap_nested_groups'                => 'ldapNestedGroups',
519
+            'ldap_paging_size'                  => 'ldapPagingSize',
520
+            'ldap_turn_on_pwd_change'           => 'turnOnPasswordChange',
521
+            'ldap_experienced_admin'            => 'ldapExperiencedAdmin',
522
+            'ldap_dynamic_group_member_url'     => 'ldapDynamicGroupMemberURL',
523
+            'ldap_default_ppolicy_dn'           => 'ldapDefaultPPolicyDN',
524
+        );
525
+        return $array;
526
+    }
527 527
 
528 528
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Mapping/AbstractMapping.php 1 patch
Indentation   +214 added lines, -214 removed lines patch added patch discarded remove patch
@@ -29,267 +29,267 @@
 block discarded – undo
29 29
 * @package OCA\User_LDAP\Mapping
30 30
 */
31 31
 abstract class AbstractMapping {
32
-	/**
33
-	 * @var \OCP\IDBConnection $dbc
34
-	 */
35
-	protected $dbc;
32
+    /**
33
+     * @var \OCP\IDBConnection $dbc
34
+     */
35
+    protected $dbc;
36 36
 
37
-	/**
38
-	 * returns the DB table name which holds the mappings
39
-	 * @return string
40
-	 */
41
-	abstract protected function getTableName();
37
+    /**
38
+     * returns the DB table name which holds the mappings
39
+     * @return string
40
+     */
41
+    abstract protected function getTableName();
42 42
 
43
-	/**
44
-	 * @param \OCP\IDBConnection $dbc
45
-	 */
46
-	public function __construct(\OCP\IDBConnection $dbc) {
47
-		$this->dbc = $dbc;
48
-	}
43
+    /**
44
+     * @param \OCP\IDBConnection $dbc
45
+     */
46
+    public function __construct(\OCP\IDBConnection $dbc) {
47
+        $this->dbc = $dbc;
48
+    }
49 49
 
50
-	/**
51
-	 * checks whether a provided string represents an existing table col
52
-	 * @param string $col
53
-	 * @return bool
54
-	 */
55
-	public function isColNameValid($col) {
56
-		switch($col) {
57
-			case 'ldap_dn':
58
-			case 'owncloud_name':
59
-			case 'directory_uuid':
60
-				return true;
61
-			default:
62
-				return false;
63
-		}
64
-	}
50
+    /**
51
+     * checks whether a provided string represents an existing table col
52
+     * @param string $col
53
+     * @return bool
54
+     */
55
+    public function isColNameValid($col) {
56
+        switch($col) {
57
+            case 'ldap_dn':
58
+            case 'owncloud_name':
59
+            case 'directory_uuid':
60
+                return true;
61
+            default:
62
+                return false;
63
+        }
64
+    }
65 65
 
66
-	/**
67
-	 * Gets the value of one column based on a provided value of another column
68
-	 * @param string $fetchCol
69
-	 * @param string $compareCol
70
-	 * @param string $search
71
-	 * @throws \Exception
72
-	 * @return string|false
73
-	 */
74
-	protected function getXbyY($fetchCol, $compareCol, $search) {
75
-		if(!$this->isColNameValid($fetchCol)) {
76
-			//this is used internally only, but we don't want to risk
77
-			//having SQL injection at all.
78
-			throw new \Exception('Invalid Column Name');
79
-		}
80
-		$query = $this->dbc->prepare('
66
+    /**
67
+     * Gets the value of one column based on a provided value of another column
68
+     * @param string $fetchCol
69
+     * @param string $compareCol
70
+     * @param string $search
71
+     * @throws \Exception
72
+     * @return string|false
73
+     */
74
+    protected function getXbyY($fetchCol, $compareCol, $search) {
75
+        if(!$this->isColNameValid($fetchCol)) {
76
+            //this is used internally only, but we don't want to risk
77
+            //having SQL injection at all.
78
+            throw new \Exception('Invalid Column Name');
79
+        }
80
+        $query = $this->dbc->prepare('
81 81
 			SELECT `' . $fetchCol . '`
82 82
 			FROM `'. $this->getTableName() .'`
83 83
 			WHERE `' . $compareCol . '` = ?
84 84
 		');
85 85
 
86
-		$res = $query->execute(array($search));
87
-		if($res !== false) {
88
-			return $query->fetchColumn();
89
-		}
86
+        $res = $query->execute(array($search));
87
+        if($res !== false) {
88
+            return $query->fetchColumn();
89
+        }
90 90
 
91
-		return false;
92
-	}
91
+        return false;
92
+    }
93 93
 
94
-	/**
95
-	 * Performs a DELETE or UPDATE query to the database.
96
-	 * @param \Doctrine\DBAL\Driver\Statement $query
97
-	 * @param array $parameters
98
-	 * @return bool true if at least one row was modified, false otherwise
99
-	 */
100
-	protected function modify($query, $parameters) {
101
-		$result = $query->execute($parameters);
102
-		return ($result === true && $query->rowCount() > 0);
103
-	}
94
+    /**
95
+     * Performs a DELETE or UPDATE query to the database.
96
+     * @param \Doctrine\DBAL\Driver\Statement $query
97
+     * @param array $parameters
98
+     * @return bool true if at least one row was modified, false otherwise
99
+     */
100
+    protected function modify($query, $parameters) {
101
+        $result = $query->execute($parameters);
102
+        return ($result === true && $query->rowCount() > 0);
103
+    }
104 104
 
105
-	/**
106
-	 * Gets the LDAP DN based on the provided name.
107
-	 * Replaces Access::ocname2dn
108
-	 * @param string $name
109
-	 * @return string|false
110
-	 */
111
-	public function getDNByName($name) {
112
-		return $this->getXbyY('ldap_dn', 'owncloud_name', $name);
113
-	}
105
+    /**
106
+     * Gets the LDAP DN based on the provided name.
107
+     * Replaces Access::ocname2dn
108
+     * @param string $name
109
+     * @return string|false
110
+     */
111
+    public function getDNByName($name) {
112
+        return $this->getXbyY('ldap_dn', 'owncloud_name', $name);
113
+    }
114 114
 
115
-	/**
116
-	 * Updates the DN based on the given UUID
117
-	 * @param string $fdn
118
-	 * @param string $uuid
119
-	 * @return bool
120
-	 */
121
-	public function setDNbyUUID($fdn, $uuid) {
122
-		$query = $this->dbc->prepare('
115
+    /**
116
+     * Updates the DN based on the given UUID
117
+     * @param string $fdn
118
+     * @param string $uuid
119
+     * @return bool
120
+     */
121
+    public function setDNbyUUID($fdn, $uuid) {
122
+        $query = $this->dbc->prepare('
123 123
 			UPDATE `' . $this->getTableName() . '`
124 124
 			SET `ldap_dn` = ?
125 125
 			WHERE `directory_uuid` = ?
126 126
 		');
127 127
 
128
-		return $this->modify($query, array($fdn, $uuid));
129
-	}
128
+        return $this->modify($query, array($fdn, $uuid));
129
+    }
130 130
 
131
-	/**
132
-	 * Updates the UUID based on the given DN
133
-	 *
134
-	 * required by Migration/UUIDFix
135
-	 *
136
-	 * @param $uuid
137
-	 * @param $fdn
138
-	 * @return bool
139
-	 */
140
-	public function setUUIDbyDN($uuid, $fdn) {
141
-		$query = $this->dbc->prepare('
131
+    /**
132
+     * Updates the UUID based on the given DN
133
+     *
134
+     * required by Migration/UUIDFix
135
+     *
136
+     * @param $uuid
137
+     * @param $fdn
138
+     * @return bool
139
+     */
140
+    public function setUUIDbyDN($uuid, $fdn) {
141
+        $query = $this->dbc->prepare('
142 142
 			UPDATE `' . $this->getTableName() . '`
143 143
 			SET `directory_uuid` = ?
144 144
 			WHERE `ldap_dn` = ?
145 145
 		');
146 146
 
147
-		return $this->modify($query, [$uuid, $fdn]);
148
-	}
147
+        return $this->modify($query, [$uuid, $fdn]);
148
+    }
149 149
 
150
-	/**
151
-	 * Gets the name based on the provided LDAP DN.
152
-	 * @param string $fdn
153
-	 * @return string|false
154
-	 */
155
-	public function getNameByDN($fdn) {
156
-		return $this->getXbyY('owncloud_name', 'ldap_dn', $fdn);
157
-	}
150
+    /**
151
+     * Gets the name based on the provided LDAP DN.
152
+     * @param string $fdn
153
+     * @return string|false
154
+     */
155
+    public function getNameByDN($fdn) {
156
+        return $this->getXbyY('owncloud_name', 'ldap_dn', $fdn);
157
+    }
158 158
 
159
-	/**
160
-	 * Searches mapped names by the giving string in the name column
161
-	 * @param string $search
162
-	 * @param string $prefixMatch
163
-	 * @param string $postfixMatch
164
-	 * @return string[]
165
-	 */
166
-	public function getNamesBySearch($search, $prefixMatch = "", $postfixMatch = "") {
167
-		$query = $this->dbc->prepare('
159
+    /**
160
+     * Searches mapped names by the giving string in the name column
161
+     * @param string $search
162
+     * @param string $prefixMatch
163
+     * @param string $postfixMatch
164
+     * @return string[]
165
+     */
166
+    public function getNamesBySearch($search, $prefixMatch = "", $postfixMatch = "") {
167
+        $query = $this->dbc->prepare('
168 168
 			SELECT `owncloud_name`
169 169
 			FROM `'. $this->getTableName() .'`
170 170
 			WHERE `owncloud_name` LIKE ?
171 171
 		');
172 172
 
173
-		$res = $query->execute(array($prefixMatch.$this->dbc->escapeLikeParameter($search).$postfixMatch));
174
-		$names = array();
175
-		if($res !== false) {
176
-			while($row = $query->fetch()) {
177
-				$names[] = $row['owncloud_name'];
178
-			}
179
-		}
180
-		return $names;
181
-	}
173
+        $res = $query->execute(array($prefixMatch.$this->dbc->escapeLikeParameter($search).$postfixMatch));
174
+        $names = array();
175
+        if($res !== false) {
176
+            while($row = $query->fetch()) {
177
+                $names[] = $row['owncloud_name'];
178
+            }
179
+        }
180
+        return $names;
181
+    }
182 182
 
183
-	/**
184
-	 * Gets the name based on the provided LDAP UUID.
185
-	 * @param string $uuid
186
-	 * @return string|false
187
-	 */
188
-	public function getNameByUUID($uuid) {
189
-		return $this->getXbyY('owncloud_name', 'directory_uuid', $uuid);
190
-	}
183
+    /**
184
+     * Gets the name based on the provided LDAP UUID.
185
+     * @param string $uuid
186
+     * @return string|false
187
+     */
188
+    public function getNameByUUID($uuid) {
189
+        return $this->getXbyY('owncloud_name', 'directory_uuid', $uuid);
190
+    }
191 191
 
192
-	/**
193
-	 * Gets the UUID based on the provided LDAP DN
194
-	 * @param string $dn
195
-	 * @return false|string
196
-	 * @throws \Exception
197
-	 */
198
-	public function getUUIDByDN($dn) {
199
-		return $this->getXbyY('directory_uuid', 'ldap_dn', $dn);
200
-	}
192
+    /**
193
+     * Gets the UUID based on the provided LDAP DN
194
+     * @param string $dn
195
+     * @return false|string
196
+     * @throws \Exception
197
+     */
198
+    public function getUUIDByDN($dn) {
199
+        return $this->getXbyY('directory_uuid', 'ldap_dn', $dn);
200
+    }
201 201
 
202
-	/**
203
-	 * gets a piece of the mapping list
204
-	 * @param int $offset
205
-	 * @param int $limit
206
-	 * @return array
207
-	 */
208
-	public function getList($offset = null, $limit = null) {
209
-		$query = $this->dbc->prepare('
202
+    /**
203
+     * gets a piece of the mapping list
204
+     * @param int $offset
205
+     * @param int $limit
206
+     * @return array
207
+     */
208
+    public function getList($offset = null, $limit = null) {
209
+        $query = $this->dbc->prepare('
210 210
 			SELECT
211 211
 				`ldap_dn` AS `dn`,
212 212
 				`owncloud_name` AS `name`,
213 213
 				`directory_uuid` AS `uuid`
214 214
 			FROM `' . $this->getTableName() . '`',
215
-			$limit,
216
-			$offset
217
-		);
215
+            $limit,
216
+            $offset
217
+        );
218 218
 
219
-		$query->execute();
220
-		return $query->fetchAll();
221
-	}
219
+        $query->execute();
220
+        return $query->fetchAll();
221
+    }
222 222
 
223
-	/**
224
-	 * attempts to map the given entry
225
-	 * @param string $fdn fully distinguished name (from LDAP)
226
-	 * @param string $name
227
-	 * @param string $uuid a unique identifier as used in LDAP
228
-	 * @return bool
229
-	 */
230
-	public function map($fdn, $name, $uuid) {
231
-		if(mb_strlen($fdn) > 255) {
232
-			\OC::$server->getLogger()->error(
233
-				'Cannot map, because the DN exceeds 255 characters: {dn}',
234
-				[
235
-					'app' => 'user_ldap',
236
-					'dn' => $fdn,
237
-				]
238
-			);
239
-			return false;
240
-		}
223
+    /**
224
+     * attempts to map the given entry
225
+     * @param string $fdn fully distinguished name (from LDAP)
226
+     * @param string $name
227
+     * @param string $uuid a unique identifier as used in LDAP
228
+     * @return bool
229
+     */
230
+    public function map($fdn, $name, $uuid) {
231
+        if(mb_strlen($fdn) > 255) {
232
+            \OC::$server->getLogger()->error(
233
+                'Cannot map, because the DN exceeds 255 characters: {dn}',
234
+                [
235
+                    'app' => 'user_ldap',
236
+                    'dn' => $fdn,
237
+                ]
238
+            );
239
+            return false;
240
+        }
241 241
 
242
-		$row = array(
243
-			'ldap_dn'        => $fdn,
244
-			'owncloud_name'  => $name,
245
-			'directory_uuid' => $uuid
246
-		);
242
+        $row = array(
243
+            'ldap_dn'        => $fdn,
244
+            'owncloud_name'  => $name,
245
+            'directory_uuid' => $uuid
246
+        );
247 247
 
248
-		try {
249
-			$result = $this->dbc->insertIfNotExist($this->getTableName(), $row);
250
-			// insertIfNotExist returns values as int
251
-			return (bool)$result;
252
-		} catch (\Exception $e) {
253
-			return false;
254
-		}
255
-	}
248
+        try {
249
+            $result = $this->dbc->insertIfNotExist($this->getTableName(), $row);
250
+            // insertIfNotExist returns values as int
251
+            return (bool)$result;
252
+        } catch (\Exception $e) {
253
+            return false;
254
+        }
255
+    }
256 256
 
257
-	/**
258
-	 * removes a mapping based on the owncloud_name of the entry
259
-	 * @param string $name
260
-	 * @return bool
261
-	 */
262
-	public function unmap($name) {
263
-		$query = $this->dbc->prepare('
257
+    /**
258
+     * removes a mapping based on the owncloud_name of the entry
259
+     * @param string $name
260
+     * @return bool
261
+     */
262
+    public function unmap($name) {
263
+        $query = $this->dbc->prepare('
264 264
 			DELETE FROM `'. $this->getTableName() .'`
265 265
 			WHERE `owncloud_name` = ?');
266 266
 
267
-		return $this->modify($query, array($name));
268
-	}
267
+        return $this->modify($query, array($name));
268
+    }
269 269
 
270
-	/**
271
-	 * Truncate's the mapping table
272
-	 * @return bool
273
-	 */
274
-	public function clear() {
275
-		$sql = $this->dbc
276
-			->getDatabasePlatform()
277
-			->getTruncateTableSQL('`' . $this->getTableName() . '`');
278
-		return $this->dbc->prepare($sql)->execute();
279
-	}
270
+    /**
271
+     * Truncate's the mapping table
272
+     * @return bool
273
+     */
274
+    public function clear() {
275
+        $sql = $this->dbc
276
+            ->getDatabasePlatform()
277
+            ->getTruncateTableSQL('`' . $this->getTableName() . '`');
278
+        return $this->dbc->prepare($sql)->execute();
279
+    }
280 280
 
281
-	/**
282
-	 * returns the number of entries in the mappings table
283
-	 *
284
-	 * @return int
285
-	 */
286
-	public function count() {
287
-		$qb = $this->dbc->getQueryBuilder();
288
-		$query = $qb->select($qb->createFunction('COUNT(`ldap_dn`)'))
289
-			->from($this->getTableName());
290
-		$res = $query->execute();
291
-		$count = $res->fetchColumn();
292
-		$res->closeCursor();
293
-		return (int)$count;
294
-	}
281
+    /**
282
+     * returns the number of entries in the mappings table
283
+     *
284
+     * @return int
285
+     */
286
+    public function count() {
287
+        $qb = $this->dbc->getQueryBuilder();
288
+        $query = $qb->select($qb->createFunction('COUNT(`ldap_dn`)'))
289
+            ->from($this->getTableName());
290
+        $res = $query->execute();
291
+        $count = $res->fetchColumn();
292
+        $res->closeCursor();
293
+        return (int)$count;
294
+    }
295 295
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Connection.php 1 patch
Indentation   +577 added lines, -577 removed lines patch added patch discarded remove patch
@@ -54,582 +54,582 @@
 block discarded – undo
54 54
  * @property string ldapExpertUUIDGroupAttr
55 55
  */
56 56
 class Connection extends LDAPUtility {
57
-	private $ldapConnectionRes = null;
58
-	private $configPrefix;
59
-	private $configID;
60
-	private $configured = false;
61
-	private $hasPagedResultSupport = true;
62
-	//whether connection should be kept on __destruct
63
-	private $dontDestruct = false;
64
-
65
-	/**
66
-	 * @var bool runtime flag that indicates whether supported primary groups are available
67
-	 */
68
-	public $hasPrimaryGroups = true;
69
-
70
-	/**
71
-	 * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
72
-	 */
73
-	public $hasGidNumber = true;
74
-
75
-	//cache handler
76
-	protected $cache;
77
-
78
-	/** @var Configuration settings handler **/
79
-	protected $configuration;
80
-
81
-	protected $doNotValidate = false;
82
-
83
-	protected $ignoreValidation = false;
84
-
85
-	/**
86
-	 * Constructor
87
-	 * @param ILDAPWrapper $ldap
88
-	 * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
89
-	 * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
90
-	 */
91
-	public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
92
-		parent::__construct($ldap);
93
-		$this->configPrefix = $configPrefix;
94
-		$this->configID = $configID;
95
-		$this->configuration = new Configuration($configPrefix,
96
-												 !is_null($configID));
97
-		$memcache = \OC::$server->getMemCacheFactory();
98
-		if($memcache->isAvailable()) {
99
-			$this->cache = $memcache->create();
100
-		}
101
-		$helper = new Helper(\OC::$server->getConfig());
102
-		$this->doNotValidate = !in_array($this->configPrefix,
103
-			$helper->getServerConfigurationPrefixes());
104
-		$this->hasPagedResultSupport =
105
-			intval($this->configuration->ldapPagingSize) !== 0
106
-			|| $this->ldap->hasPagedResultSupport();
107
-	}
108
-
109
-	public function __destruct() {
110
-		if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
111
-			@$this->ldap->unbind($this->ldapConnectionRes);
112
-		};
113
-	}
114
-
115
-	/**
116
-	 * defines behaviour when the instance is cloned
117
-	 */
118
-	public function __clone() {
119
-		$this->configuration = new Configuration($this->configPrefix,
120
-												 !is_null($this->configID));
121
-		$this->ldapConnectionRes = null;
122
-		$this->dontDestruct = true;
123
-	}
124
-
125
-	/**
126
-	 * @param string $name
127
-	 * @return bool|mixed
128
-	 */
129
-	public function __get($name) {
130
-		if(!$this->configured) {
131
-			$this->readConfiguration();
132
-		}
133
-
134
-		if($name === 'hasPagedResultSupport') {
135
-			return $this->hasPagedResultSupport;
136
-		}
137
-
138
-		return $this->configuration->$name;
139
-	}
140
-
141
-	/**
142
-	 * @param string $name
143
-	 * @param mixed $value
144
-	 */
145
-	public function __set($name, $value) {
146
-		$this->doNotValidate = false;
147
-		$before = $this->configuration->$name;
148
-		$this->configuration->$name = $value;
149
-		$after = $this->configuration->$name;
150
-		if($before !== $after) {
151
-			if ($this->configID !== '') {
152
-				$this->configuration->saveConfiguration();
153
-			}
154
-			$this->validateConfiguration();
155
-		}
156
-	}
157
-
158
-	/**
159
-	 * sets whether the result of the configuration validation shall
160
-	 * be ignored when establishing the connection. Used by the Wizard
161
-	 * in early configuration state.
162
-	 * @param bool $state
163
-	 */
164
-	public function setIgnoreValidation($state) {
165
-		$this->ignoreValidation = (bool)$state;
166
-	}
167
-
168
-	/**
169
-	 * initializes the LDAP backend
170
-	 * @param bool $force read the config settings no matter what
171
-	 */
172
-	public function init($force = false) {
173
-		$this->readConfiguration($force);
174
-		$this->establishConnection();
175
-	}
176
-
177
-	/**
178
-	 * Returns the LDAP handler
179
-	 */
180
-	public function getConnectionResource() {
181
-		if(!$this->ldapConnectionRes) {
182
-			$this->init();
183
-		} else if(!$this->ldap->isResource($this->ldapConnectionRes)) {
184
-			$this->ldapConnectionRes = null;
185
-			$this->establishConnection();
186
-		}
187
-		if(is_null($this->ldapConnectionRes)) {
188
-			\OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, \OCP\Util::ERROR);
189
-			throw new ServerNotAvailableException('Connection to LDAP server could not be established');
190
-		}
191
-		return $this->ldapConnectionRes;
192
-	}
193
-
194
-	/**
195
-	 * resets the connection resource
196
-	 */
197
-	public function resetConnectionResource() {
198
-		if(!is_null($this->ldapConnectionRes)) {
199
-			@$this->ldap->unbind($this->ldapConnectionRes);
200
-			$this->ldapConnectionRes = null;
201
-		}
202
-	}
203
-
204
-	/**
205
-	 * @param string|null $key
206
-	 * @return string
207
-	 */
208
-	private function getCacheKey($key) {
209
-		$prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
210
-		if(is_null($key)) {
211
-			return $prefix;
212
-		}
213
-		return $prefix.md5($key);
214
-	}
215
-
216
-	/**
217
-	 * @param string $key
218
-	 * @return mixed|null
219
-	 */
220
-	public function getFromCache($key) {
221
-		if(!$this->configured) {
222
-			$this->readConfiguration();
223
-		}
224
-		if(is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
225
-			return null;
226
-		}
227
-		$key = $this->getCacheKey($key);
228
-
229
-		return json_decode(base64_decode($this->cache->get($key)), true);
230
-	}
231
-
232
-	/**
233
-	 * @param string $key
234
-	 * @param mixed $value
235
-	 *
236
-	 * @return string
237
-	 */
238
-	public function writeToCache($key, $value) {
239
-		if(!$this->configured) {
240
-			$this->readConfiguration();
241
-		}
242
-		if(is_null($this->cache)
243
-			|| !$this->configuration->ldapCacheTTL
244
-			|| !$this->configuration->ldapConfigurationActive) {
245
-			return null;
246
-		}
247
-		$key   = $this->getCacheKey($key);
248
-		$value = base64_encode(json_encode($value));
249
-		$this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
250
-	}
251
-
252
-	public function clearCache() {
253
-		if(!is_null($this->cache)) {
254
-			$this->cache->clear($this->getCacheKey(null));
255
-		}
256
-	}
257
-
258
-	/**
259
-	 * Caches the general LDAP configuration.
260
-	 * @param bool $force optional. true, if the re-read should be forced. defaults
261
-	 * to false.
262
-	 * @return null
263
-	 */
264
-	private function readConfiguration($force = false) {
265
-		if((!$this->configured || $force) && !is_null($this->configID)) {
266
-			$this->configuration->readConfiguration();
267
-			$this->configured = $this->validateConfiguration();
268
-		}
269
-	}
270
-
271
-	/**
272
-	 * set LDAP configuration with values delivered by an array, not read from configuration
273
-	 * @param array $config array that holds the config parameters in an associated array
274
-	 * @param array &$setParameters optional; array where the set fields will be given to
275
-	 * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
276
-	 */
277
-	public function setConfiguration($config, &$setParameters = null) {
278
-		if(is_null($setParameters)) {
279
-			$setParameters = array();
280
-		}
281
-		$this->doNotValidate = false;
282
-		$this->configuration->setConfiguration($config, $setParameters);
283
-		if(count($setParameters) > 0) {
284
-			$this->configured = $this->validateConfiguration();
285
-		}
286
-
287
-
288
-		return $this->configured;
289
-	}
290
-
291
-	/**
292
-	 * saves the current Configuration in the database and empties the
293
-	 * cache
294
-	 * @return null
295
-	 */
296
-	public function saveConfiguration() {
297
-		$this->configuration->saveConfiguration();
298
-		$this->clearCache();
299
-	}
300
-
301
-	/**
302
-	 * get the current LDAP configuration
303
-	 * @return array
304
-	 */
305
-	public function getConfiguration() {
306
-		$this->readConfiguration();
307
-		$config = $this->configuration->getConfiguration();
308
-		$cta = $this->configuration->getConfigTranslationArray();
309
-		$result = array();
310
-		foreach($cta as $dbkey => $configkey) {
311
-			switch($configkey) {
312
-				case 'homeFolderNamingRule':
313
-					if(strpos($config[$configkey], 'attr:') === 0) {
314
-						$result[$dbkey] = substr($config[$configkey], 5);
315
-					} else {
316
-						$result[$dbkey] = '';
317
-					}
318
-					break;
319
-				case 'ldapBase':
320
-				case 'ldapBaseUsers':
321
-				case 'ldapBaseGroups':
322
-				case 'ldapAttributesForUserSearch':
323
-				case 'ldapAttributesForGroupSearch':
324
-					if(is_array($config[$configkey])) {
325
-						$result[$dbkey] = implode("\n", $config[$configkey]);
326
-						break;
327
-					} //else follows default
328
-				default:
329
-					$result[$dbkey] = $config[$configkey];
330
-			}
331
-		}
332
-		return $result;
333
-	}
334
-
335
-	private function doSoftValidation() {
336
-		//if User or Group Base are not set, take over Base DN setting
337
-		foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
338
-			$val = $this->configuration->$keyBase;
339
-			if(empty($val)) {
340
-				$this->configuration->$keyBase = $this->configuration->ldapBase;
341
-			}
342
-		}
343
-
344
-		foreach(array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
345
-					  'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute')
346
-				as $expertSetting => $effectiveSetting) {
347
-			$uuidOverride = $this->configuration->$expertSetting;
348
-			if(!empty($uuidOverride)) {
349
-				$this->configuration->$effectiveSetting = $uuidOverride;
350
-			} else {
351
-				$uuidAttributes = Access::UUID_ATTRIBUTES;
352
-				array_unshift($uuidAttributes, 'auto');
353
-				if(!in_array($this->configuration->$effectiveSetting,
354
-							$uuidAttributes)
355
-					&& (!is_null($this->configID))) {
356
-					$this->configuration->$effectiveSetting = 'auto';
357
-					$this->configuration->saveConfiguration();
358
-					\OCP\Util::writeLog('user_ldap',
359
-										'Illegal value for the '.
360
-										$effectiveSetting.', '.'reset to '.
361
-										'autodetect.', \OCP\Util::INFO);
362
-				}
363
-
364
-			}
365
-		}
366
-
367
-		$backupPort = intval($this->configuration->ldapBackupPort);
368
-		if ($backupPort <= 0) {
369
-			$this->configuration->backupPort = $this->configuration->ldapPort;
370
-		}
371
-
372
-		//make sure empty search attributes are saved as simple, empty array
373
-		$saKeys = array('ldapAttributesForUserSearch',
374
-						'ldapAttributesForGroupSearch');
375
-		foreach($saKeys as $key) {
376
-			$val = $this->configuration->$key;
377
-			if(is_array($val) && count($val) === 1 && empty($val[0])) {
378
-				$this->configuration->$key = array();
379
-			}
380
-		}
381
-
382
-		if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
383
-			&& $this->configuration->ldapTLS) {
384
-			$this->configuration->ldapTLS = false;
385
-			\OCP\Util::writeLog('user_ldap',
386
-								'LDAPS (already using secure connection) and '.
387
-								'TLS do not work together. Switched off TLS.',
388
-								\OCP\Util::INFO);
389
-		}
390
-	}
391
-
392
-	/**
393
-	 * @return bool
394
-	 */
395
-	private function doCriticalValidation() {
396
-		$configurationOK = true;
397
-		$errorStr = 'Configuration Error (prefix '.
398
-					strval($this->configPrefix).'): ';
399
-
400
-		//options that shall not be empty
401
-		$options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
402
-						 'ldapGroupDisplayName', 'ldapLoginFilter');
403
-		foreach($options as $key) {
404
-			$val = $this->configuration->$key;
405
-			if(empty($val)) {
406
-				switch($key) {
407
-					case 'ldapHost':
408
-						$subj = 'LDAP Host';
409
-						break;
410
-					case 'ldapPort':
411
-						$subj = 'LDAP Port';
412
-						break;
413
-					case 'ldapUserDisplayName':
414
-						$subj = 'LDAP User Display Name';
415
-						break;
416
-					case 'ldapGroupDisplayName':
417
-						$subj = 'LDAP Group Display Name';
418
-						break;
419
-					case 'ldapLoginFilter':
420
-						$subj = 'LDAP Login Filter';
421
-						break;
422
-					default:
423
-						$subj = $key;
424
-						break;
425
-				}
426
-				$configurationOK = false;
427
-				\OCP\Util::writeLog('user_ldap',
428
-									$errorStr.'No '.$subj.' given!',
429
-									\OCP\Util::WARN);
430
-			}
431
-		}
432
-
433
-		//combinations
434
-		$agent = $this->configuration->ldapAgentName;
435
-		$pwd = $this->configuration->ldapAgentPassword;
436
-		if (
437
-			($agent === ''  && $pwd !== '')
438
-			|| ($agent !== '' && $pwd === '')
439
-		) {
440
-			\OCP\Util::writeLog('user_ldap',
441
-								$errorStr.'either no password is given for the '.
442
-								'user agent or a password is given, but not an '.
443
-								'LDAP agent.',
444
-				\OCP\Util::WARN);
445
-			$configurationOK = false;
446
-		}
447
-
448
-		$base = $this->configuration->ldapBase;
449
-		$baseUsers = $this->configuration->ldapBaseUsers;
450
-		$baseGroups = $this->configuration->ldapBaseGroups;
451
-
452
-		if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
453
-			\OCP\Util::writeLog('user_ldap',
454
-								$errorStr.'Not a single Base DN given.',
455
-								\OCP\Util::WARN);
456
-			$configurationOK = false;
457
-		}
458
-
459
-		if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
460
-		   === false) {
461
-			\OCP\Util::writeLog('user_ldap',
462
-								$errorStr.'login filter does not contain %uid '.
463
-								'place holder.',
464
-								\OCP\Util::WARN);
465
-			$configurationOK = false;
466
-		}
467
-
468
-		return $configurationOK;
469
-	}
470
-
471
-	/**
472
-	 * Validates the user specified configuration
473
-	 * @return bool true if configuration seems OK, false otherwise
474
-	 */
475
-	private function validateConfiguration() {
476
-
477
-		if($this->doNotValidate) {
478
-			//don't do a validation if it is a new configuration with pure
479
-			//default values. Will be allowed on changes via __set or
480
-			//setConfiguration
481
-			return false;
482
-		}
483
-
484
-		// first step: "soft" checks: settings that are not really
485
-		// necessary, but advisable. If left empty, give an info message
486
-		$this->doSoftValidation();
487
-
488
-		//second step: critical checks. If left empty or filled wrong, mark as
489
-		//not configured and give a warning.
490
-		return $this->doCriticalValidation();
491
-	}
492
-
493
-
494
-	/**
495
-	 * Connects and Binds to LDAP
496
-	 */
497
-	private function establishConnection() {
498
-		if(!$this->configuration->ldapConfigurationActive) {
499
-			return null;
500
-		}
501
-		static $phpLDAPinstalled = true;
502
-		if(!$phpLDAPinstalled) {
503
-			return false;
504
-		}
505
-		if(!$this->ignoreValidation && !$this->configured) {
506
-			\OCP\Util::writeLog('user_ldap',
507
-								'Configuration is invalid, cannot connect',
508
-								\OCP\Util::WARN);
509
-			return false;
510
-		}
511
-		if(!$this->ldapConnectionRes) {
512
-			if(!$this->ldap->areLDAPFunctionsAvailable()) {
513
-				$phpLDAPinstalled = false;
514
-				\OCP\Util::writeLog('user_ldap',
515
-									'function ldap_connect is not available. Make '.
516
-									'sure that the PHP ldap module is installed.',
517
-									\OCP\Util::ERROR);
518
-
519
-				return false;
520
-			}
521
-			if($this->configuration->turnOffCertCheck) {
522
-				if(putenv('LDAPTLS_REQCERT=never')) {
523
-					\OCP\Util::writeLog('user_ldap',
524
-						'Turned off SSL certificate validation successfully.',
525
-						\OCP\Util::DEBUG);
526
-				} else {
527
-					\OCP\Util::writeLog('user_ldap',
528
-										'Could not turn off SSL certificate validation.',
529
-										\OCP\Util::WARN);
530
-				}
531
-			}
532
-
533
-			$isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
534
-				|| $this->getFromCache('overrideMainServer'));
535
-			$isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
536
-			$bindStatus = false;
537
-			$error = -1;
538
-			try {
539
-				if (!$isOverrideMainServer) {
540
-					$this->doConnect($this->configuration->ldapHost,
541
-						$this->configuration->ldapPort);
542
-					$bindStatus = $this->bind();
543
-					$error = $this->ldap->isResource($this->ldapConnectionRes) ?
544
-						$this->ldap->errno($this->ldapConnectionRes) : -1;
545
-				}
546
-				if($bindStatus === true) {
547
-					return $bindStatus;
548
-				}
549
-			} catch (ServerNotAvailableException $e) {
550
-				if(!$isBackupHost) {
551
-					throw $e;
552
-				}
553
-			}
554
-
555
-			//if LDAP server is not reachable, try the Backup (Replica!) Server
556
-			if($isBackupHost && ($error !== 0 || $isOverrideMainServer)) {
557
-				$this->doConnect($this->configuration->ldapBackupHost,
558
-								 $this->configuration->ldapBackupPort);
559
-				$bindStatus = $this->bind();
560
-				$error = $this->ldap->isResource($this->ldapConnectionRes) ?
561
-					$this->ldap->errno($this->ldapConnectionRes) : -1;
562
-				if($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
563
-					//when bind to backup server succeeded and failed to main server,
564
-					//skip contacting him until next cache refresh
565
-					$this->writeToCache('overrideMainServer', true);
566
-				}
567
-			}
568
-
569
-			return $bindStatus;
570
-		}
571
-		return null;
572
-	}
573
-
574
-	/**
575
-	 * @param string $host
576
-	 * @param string $port
577
-	 * @return bool
578
-	 * @throws \OC\ServerNotAvailableException
579
-	 */
580
-	private function doConnect($host, $port) {
581
-		if ($host === '') {
582
-			return false;
583
-		}
584
-
585
-		$this->ldapConnectionRes = $this->ldap->connect($host, $port);
586
-
587
-		if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
588
-			throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
589
-		}
590
-
591
-		if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
592
-			throw new ServerNotAvailableException('Could not disable LDAP referrals.');
593
-		}
594
-
595
-		if($this->configuration->ldapTLS) {
596
-			if(!$this->ldap->startTls($this->ldapConnectionRes)) {
597
-				throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
598
-			}
599
-		}
600
-
601
-		return true;
602
-	}
603
-
604
-	/**
605
-	 * Binds to LDAP
606
-	 */
607
-	public function bind() {
608
-		if(!$this->configuration->ldapConfigurationActive) {
609
-			return false;
610
-		}
611
-		$cr = $this->getConnectionResource();
612
-		if(!$this->ldap->isResource($cr)) {
613
-			return false;
614
-		}
615
-		$ldapLogin = @$this->ldap->bind($cr,
616
-										$this->configuration->ldapAgentName,
617
-										$this->configuration->ldapAgentPassword);
618
-		if(!$ldapLogin) {
619
-			$errno = $this->ldap->errno($cr);
620
-
621
-			\OCP\Util::writeLog('user_ldap',
622
-				'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
623
-				\OCP\Util::WARN);
624
-
625
-			// Set to failure mode, if LDAP error code is not LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
626
-			if($errno !== 0x00 && $errno !== 0x31) {
627
-				$this->ldapConnectionRes = null;
628
-			}
629
-
630
-			return false;
631
-		}
632
-		return true;
633
-	}
57
+    private $ldapConnectionRes = null;
58
+    private $configPrefix;
59
+    private $configID;
60
+    private $configured = false;
61
+    private $hasPagedResultSupport = true;
62
+    //whether connection should be kept on __destruct
63
+    private $dontDestruct = false;
64
+
65
+    /**
66
+     * @var bool runtime flag that indicates whether supported primary groups are available
67
+     */
68
+    public $hasPrimaryGroups = true;
69
+
70
+    /**
71
+     * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
72
+     */
73
+    public $hasGidNumber = true;
74
+
75
+    //cache handler
76
+    protected $cache;
77
+
78
+    /** @var Configuration settings handler **/
79
+    protected $configuration;
80
+
81
+    protected $doNotValidate = false;
82
+
83
+    protected $ignoreValidation = false;
84
+
85
+    /**
86
+     * Constructor
87
+     * @param ILDAPWrapper $ldap
88
+     * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
89
+     * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
90
+     */
91
+    public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
92
+        parent::__construct($ldap);
93
+        $this->configPrefix = $configPrefix;
94
+        $this->configID = $configID;
95
+        $this->configuration = new Configuration($configPrefix,
96
+                                                    !is_null($configID));
97
+        $memcache = \OC::$server->getMemCacheFactory();
98
+        if($memcache->isAvailable()) {
99
+            $this->cache = $memcache->create();
100
+        }
101
+        $helper = new Helper(\OC::$server->getConfig());
102
+        $this->doNotValidate = !in_array($this->configPrefix,
103
+            $helper->getServerConfigurationPrefixes());
104
+        $this->hasPagedResultSupport =
105
+            intval($this->configuration->ldapPagingSize) !== 0
106
+            || $this->ldap->hasPagedResultSupport();
107
+    }
108
+
109
+    public function __destruct() {
110
+        if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
111
+            @$this->ldap->unbind($this->ldapConnectionRes);
112
+        };
113
+    }
114
+
115
+    /**
116
+     * defines behaviour when the instance is cloned
117
+     */
118
+    public function __clone() {
119
+        $this->configuration = new Configuration($this->configPrefix,
120
+                                                    !is_null($this->configID));
121
+        $this->ldapConnectionRes = null;
122
+        $this->dontDestruct = true;
123
+    }
124
+
125
+    /**
126
+     * @param string $name
127
+     * @return bool|mixed
128
+     */
129
+    public function __get($name) {
130
+        if(!$this->configured) {
131
+            $this->readConfiguration();
132
+        }
133
+
134
+        if($name === 'hasPagedResultSupport') {
135
+            return $this->hasPagedResultSupport;
136
+        }
137
+
138
+        return $this->configuration->$name;
139
+    }
140
+
141
+    /**
142
+     * @param string $name
143
+     * @param mixed $value
144
+     */
145
+    public function __set($name, $value) {
146
+        $this->doNotValidate = false;
147
+        $before = $this->configuration->$name;
148
+        $this->configuration->$name = $value;
149
+        $after = $this->configuration->$name;
150
+        if($before !== $after) {
151
+            if ($this->configID !== '') {
152
+                $this->configuration->saveConfiguration();
153
+            }
154
+            $this->validateConfiguration();
155
+        }
156
+    }
157
+
158
+    /**
159
+     * sets whether the result of the configuration validation shall
160
+     * be ignored when establishing the connection. Used by the Wizard
161
+     * in early configuration state.
162
+     * @param bool $state
163
+     */
164
+    public function setIgnoreValidation($state) {
165
+        $this->ignoreValidation = (bool)$state;
166
+    }
167
+
168
+    /**
169
+     * initializes the LDAP backend
170
+     * @param bool $force read the config settings no matter what
171
+     */
172
+    public function init($force = false) {
173
+        $this->readConfiguration($force);
174
+        $this->establishConnection();
175
+    }
176
+
177
+    /**
178
+     * Returns the LDAP handler
179
+     */
180
+    public function getConnectionResource() {
181
+        if(!$this->ldapConnectionRes) {
182
+            $this->init();
183
+        } else if(!$this->ldap->isResource($this->ldapConnectionRes)) {
184
+            $this->ldapConnectionRes = null;
185
+            $this->establishConnection();
186
+        }
187
+        if(is_null($this->ldapConnectionRes)) {
188
+            \OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, \OCP\Util::ERROR);
189
+            throw new ServerNotAvailableException('Connection to LDAP server could not be established');
190
+        }
191
+        return $this->ldapConnectionRes;
192
+    }
193
+
194
+    /**
195
+     * resets the connection resource
196
+     */
197
+    public function resetConnectionResource() {
198
+        if(!is_null($this->ldapConnectionRes)) {
199
+            @$this->ldap->unbind($this->ldapConnectionRes);
200
+            $this->ldapConnectionRes = null;
201
+        }
202
+    }
203
+
204
+    /**
205
+     * @param string|null $key
206
+     * @return string
207
+     */
208
+    private function getCacheKey($key) {
209
+        $prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
210
+        if(is_null($key)) {
211
+            return $prefix;
212
+        }
213
+        return $prefix.md5($key);
214
+    }
215
+
216
+    /**
217
+     * @param string $key
218
+     * @return mixed|null
219
+     */
220
+    public function getFromCache($key) {
221
+        if(!$this->configured) {
222
+            $this->readConfiguration();
223
+        }
224
+        if(is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
225
+            return null;
226
+        }
227
+        $key = $this->getCacheKey($key);
228
+
229
+        return json_decode(base64_decode($this->cache->get($key)), true);
230
+    }
231
+
232
+    /**
233
+     * @param string $key
234
+     * @param mixed $value
235
+     *
236
+     * @return string
237
+     */
238
+    public function writeToCache($key, $value) {
239
+        if(!$this->configured) {
240
+            $this->readConfiguration();
241
+        }
242
+        if(is_null($this->cache)
243
+            || !$this->configuration->ldapCacheTTL
244
+            || !$this->configuration->ldapConfigurationActive) {
245
+            return null;
246
+        }
247
+        $key   = $this->getCacheKey($key);
248
+        $value = base64_encode(json_encode($value));
249
+        $this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
250
+    }
251
+
252
+    public function clearCache() {
253
+        if(!is_null($this->cache)) {
254
+            $this->cache->clear($this->getCacheKey(null));
255
+        }
256
+    }
257
+
258
+    /**
259
+     * Caches the general LDAP configuration.
260
+     * @param bool $force optional. true, if the re-read should be forced. defaults
261
+     * to false.
262
+     * @return null
263
+     */
264
+    private function readConfiguration($force = false) {
265
+        if((!$this->configured || $force) && !is_null($this->configID)) {
266
+            $this->configuration->readConfiguration();
267
+            $this->configured = $this->validateConfiguration();
268
+        }
269
+    }
270
+
271
+    /**
272
+     * set LDAP configuration with values delivered by an array, not read from configuration
273
+     * @param array $config array that holds the config parameters in an associated array
274
+     * @param array &$setParameters optional; array where the set fields will be given to
275
+     * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
276
+     */
277
+    public function setConfiguration($config, &$setParameters = null) {
278
+        if(is_null($setParameters)) {
279
+            $setParameters = array();
280
+        }
281
+        $this->doNotValidate = false;
282
+        $this->configuration->setConfiguration($config, $setParameters);
283
+        if(count($setParameters) > 0) {
284
+            $this->configured = $this->validateConfiguration();
285
+        }
286
+
287
+
288
+        return $this->configured;
289
+    }
290
+
291
+    /**
292
+     * saves the current Configuration in the database and empties the
293
+     * cache
294
+     * @return null
295
+     */
296
+    public function saveConfiguration() {
297
+        $this->configuration->saveConfiguration();
298
+        $this->clearCache();
299
+    }
300
+
301
+    /**
302
+     * get the current LDAP configuration
303
+     * @return array
304
+     */
305
+    public function getConfiguration() {
306
+        $this->readConfiguration();
307
+        $config = $this->configuration->getConfiguration();
308
+        $cta = $this->configuration->getConfigTranslationArray();
309
+        $result = array();
310
+        foreach($cta as $dbkey => $configkey) {
311
+            switch($configkey) {
312
+                case 'homeFolderNamingRule':
313
+                    if(strpos($config[$configkey], 'attr:') === 0) {
314
+                        $result[$dbkey] = substr($config[$configkey], 5);
315
+                    } else {
316
+                        $result[$dbkey] = '';
317
+                    }
318
+                    break;
319
+                case 'ldapBase':
320
+                case 'ldapBaseUsers':
321
+                case 'ldapBaseGroups':
322
+                case 'ldapAttributesForUserSearch':
323
+                case 'ldapAttributesForGroupSearch':
324
+                    if(is_array($config[$configkey])) {
325
+                        $result[$dbkey] = implode("\n", $config[$configkey]);
326
+                        break;
327
+                    } //else follows default
328
+                default:
329
+                    $result[$dbkey] = $config[$configkey];
330
+            }
331
+        }
332
+        return $result;
333
+    }
334
+
335
+    private function doSoftValidation() {
336
+        //if User or Group Base are not set, take over Base DN setting
337
+        foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
338
+            $val = $this->configuration->$keyBase;
339
+            if(empty($val)) {
340
+                $this->configuration->$keyBase = $this->configuration->ldapBase;
341
+            }
342
+        }
343
+
344
+        foreach(array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
345
+                        'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute')
346
+                as $expertSetting => $effectiveSetting) {
347
+            $uuidOverride = $this->configuration->$expertSetting;
348
+            if(!empty($uuidOverride)) {
349
+                $this->configuration->$effectiveSetting = $uuidOverride;
350
+            } else {
351
+                $uuidAttributes = Access::UUID_ATTRIBUTES;
352
+                array_unshift($uuidAttributes, 'auto');
353
+                if(!in_array($this->configuration->$effectiveSetting,
354
+                            $uuidAttributes)
355
+                    && (!is_null($this->configID))) {
356
+                    $this->configuration->$effectiveSetting = 'auto';
357
+                    $this->configuration->saveConfiguration();
358
+                    \OCP\Util::writeLog('user_ldap',
359
+                                        'Illegal value for the '.
360
+                                        $effectiveSetting.', '.'reset to '.
361
+                                        'autodetect.', \OCP\Util::INFO);
362
+                }
363
+
364
+            }
365
+        }
366
+
367
+        $backupPort = intval($this->configuration->ldapBackupPort);
368
+        if ($backupPort <= 0) {
369
+            $this->configuration->backupPort = $this->configuration->ldapPort;
370
+        }
371
+
372
+        //make sure empty search attributes are saved as simple, empty array
373
+        $saKeys = array('ldapAttributesForUserSearch',
374
+                        'ldapAttributesForGroupSearch');
375
+        foreach($saKeys as $key) {
376
+            $val = $this->configuration->$key;
377
+            if(is_array($val) && count($val) === 1 && empty($val[0])) {
378
+                $this->configuration->$key = array();
379
+            }
380
+        }
381
+
382
+        if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
383
+            && $this->configuration->ldapTLS) {
384
+            $this->configuration->ldapTLS = false;
385
+            \OCP\Util::writeLog('user_ldap',
386
+                                'LDAPS (already using secure connection) and '.
387
+                                'TLS do not work together. Switched off TLS.',
388
+                                \OCP\Util::INFO);
389
+        }
390
+    }
391
+
392
+    /**
393
+     * @return bool
394
+     */
395
+    private function doCriticalValidation() {
396
+        $configurationOK = true;
397
+        $errorStr = 'Configuration Error (prefix '.
398
+                    strval($this->configPrefix).'): ';
399
+
400
+        //options that shall not be empty
401
+        $options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
402
+                            'ldapGroupDisplayName', 'ldapLoginFilter');
403
+        foreach($options as $key) {
404
+            $val = $this->configuration->$key;
405
+            if(empty($val)) {
406
+                switch($key) {
407
+                    case 'ldapHost':
408
+                        $subj = 'LDAP Host';
409
+                        break;
410
+                    case 'ldapPort':
411
+                        $subj = 'LDAP Port';
412
+                        break;
413
+                    case 'ldapUserDisplayName':
414
+                        $subj = 'LDAP User Display Name';
415
+                        break;
416
+                    case 'ldapGroupDisplayName':
417
+                        $subj = 'LDAP Group Display Name';
418
+                        break;
419
+                    case 'ldapLoginFilter':
420
+                        $subj = 'LDAP Login Filter';
421
+                        break;
422
+                    default:
423
+                        $subj = $key;
424
+                        break;
425
+                }
426
+                $configurationOK = false;
427
+                \OCP\Util::writeLog('user_ldap',
428
+                                    $errorStr.'No '.$subj.' given!',
429
+                                    \OCP\Util::WARN);
430
+            }
431
+        }
432
+
433
+        //combinations
434
+        $agent = $this->configuration->ldapAgentName;
435
+        $pwd = $this->configuration->ldapAgentPassword;
436
+        if (
437
+            ($agent === ''  && $pwd !== '')
438
+            || ($agent !== '' && $pwd === '')
439
+        ) {
440
+            \OCP\Util::writeLog('user_ldap',
441
+                                $errorStr.'either no password is given for the '.
442
+                                'user agent or a password is given, but not an '.
443
+                                'LDAP agent.',
444
+                \OCP\Util::WARN);
445
+            $configurationOK = false;
446
+        }
447
+
448
+        $base = $this->configuration->ldapBase;
449
+        $baseUsers = $this->configuration->ldapBaseUsers;
450
+        $baseGroups = $this->configuration->ldapBaseGroups;
451
+
452
+        if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
453
+            \OCP\Util::writeLog('user_ldap',
454
+                                $errorStr.'Not a single Base DN given.',
455
+                                \OCP\Util::WARN);
456
+            $configurationOK = false;
457
+        }
458
+
459
+        if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
460
+            === false) {
461
+            \OCP\Util::writeLog('user_ldap',
462
+                                $errorStr.'login filter does not contain %uid '.
463
+                                'place holder.',
464
+                                \OCP\Util::WARN);
465
+            $configurationOK = false;
466
+        }
467
+
468
+        return $configurationOK;
469
+    }
470
+
471
+    /**
472
+     * Validates the user specified configuration
473
+     * @return bool true if configuration seems OK, false otherwise
474
+     */
475
+    private function validateConfiguration() {
476
+
477
+        if($this->doNotValidate) {
478
+            //don't do a validation if it is a new configuration with pure
479
+            //default values. Will be allowed on changes via __set or
480
+            //setConfiguration
481
+            return false;
482
+        }
483
+
484
+        // first step: "soft" checks: settings that are not really
485
+        // necessary, but advisable. If left empty, give an info message
486
+        $this->doSoftValidation();
487
+
488
+        //second step: critical checks. If left empty or filled wrong, mark as
489
+        //not configured and give a warning.
490
+        return $this->doCriticalValidation();
491
+    }
492
+
493
+
494
+    /**
495
+     * Connects and Binds to LDAP
496
+     */
497
+    private function establishConnection() {
498
+        if(!$this->configuration->ldapConfigurationActive) {
499
+            return null;
500
+        }
501
+        static $phpLDAPinstalled = true;
502
+        if(!$phpLDAPinstalled) {
503
+            return false;
504
+        }
505
+        if(!$this->ignoreValidation && !$this->configured) {
506
+            \OCP\Util::writeLog('user_ldap',
507
+                                'Configuration is invalid, cannot connect',
508
+                                \OCP\Util::WARN);
509
+            return false;
510
+        }
511
+        if(!$this->ldapConnectionRes) {
512
+            if(!$this->ldap->areLDAPFunctionsAvailable()) {
513
+                $phpLDAPinstalled = false;
514
+                \OCP\Util::writeLog('user_ldap',
515
+                                    'function ldap_connect is not available. Make '.
516
+                                    'sure that the PHP ldap module is installed.',
517
+                                    \OCP\Util::ERROR);
518
+
519
+                return false;
520
+            }
521
+            if($this->configuration->turnOffCertCheck) {
522
+                if(putenv('LDAPTLS_REQCERT=never')) {
523
+                    \OCP\Util::writeLog('user_ldap',
524
+                        'Turned off SSL certificate validation successfully.',
525
+                        \OCP\Util::DEBUG);
526
+                } else {
527
+                    \OCP\Util::writeLog('user_ldap',
528
+                                        'Could not turn off SSL certificate validation.',
529
+                                        \OCP\Util::WARN);
530
+                }
531
+            }
532
+
533
+            $isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
534
+                || $this->getFromCache('overrideMainServer'));
535
+            $isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
536
+            $bindStatus = false;
537
+            $error = -1;
538
+            try {
539
+                if (!$isOverrideMainServer) {
540
+                    $this->doConnect($this->configuration->ldapHost,
541
+                        $this->configuration->ldapPort);
542
+                    $bindStatus = $this->bind();
543
+                    $error = $this->ldap->isResource($this->ldapConnectionRes) ?
544
+                        $this->ldap->errno($this->ldapConnectionRes) : -1;
545
+                }
546
+                if($bindStatus === true) {
547
+                    return $bindStatus;
548
+                }
549
+            } catch (ServerNotAvailableException $e) {
550
+                if(!$isBackupHost) {
551
+                    throw $e;
552
+                }
553
+            }
554
+
555
+            //if LDAP server is not reachable, try the Backup (Replica!) Server
556
+            if($isBackupHost && ($error !== 0 || $isOverrideMainServer)) {
557
+                $this->doConnect($this->configuration->ldapBackupHost,
558
+                                    $this->configuration->ldapBackupPort);
559
+                $bindStatus = $this->bind();
560
+                $error = $this->ldap->isResource($this->ldapConnectionRes) ?
561
+                    $this->ldap->errno($this->ldapConnectionRes) : -1;
562
+                if($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
563
+                    //when bind to backup server succeeded and failed to main server,
564
+                    //skip contacting him until next cache refresh
565
+                    $this->writeToCache('overrideMainServer', true);
566
+                }
567
+            }
568
+
569
+            return $bindStatus;
570
+        }
571
+        return null;
572
+    }
573
+
574
+    /**
575
+     * @param string $host
576
+     * @param string $port
577
+     * @return bool
578
+     * @throws \OC\ServerNotAvailableException
579
+     */
580
+    private function doConnect($host, $port) {
581
+        if ($host === '') {
582
+            return false;
583
+        }
584
+
585
+        $this->ldapConnectionRes = $this->ldap->connect($host, $port);
586
+
587
+        if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
588
+            throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
589
+        }
590
+
591
+        if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
592
+            throw new ServerNotAvailableException('Could not disable LDAP referrals.');
593
+        }
594
+
595
+        if($this->configuration->ldapTLS) {
596
+            if(!$this->ldap->startTls($this->ldapConnectionRes)) {
597
+                throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
598
+            }
599
+        }
600
+
601
+        return true;
602
+    }
603
+
604
+    /**
605
+     * Binds to LDAP
606
+     */
607
+    public function bind() {
608
+        if(!$this->configuration->ldapConfigurationActive) {
609
+            return false;
610
+        }
611
+        $cr = $this->getConnectionResource();
612
+        if(!$this->ldap->isResource($cr)) {
613
+            return false;
614
+        }
615
+        $ldapLogin = @$this->ldap->bind($cr,
616
+                                        $this->configuration->ldapAgentName,
617
+                                        $this->configuration->ldapAgentPassword);
618
+        if(!$ldapLogin) {
619
+            $errno = $this->ldap->errno($cr);
620
+
621
+            \OCP\Util::writeLog('user_ldap',
622
+                'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
623
+                \OCP\Util::WARN);
624
+
625
+            // Set to failure mode, if LDAP error code is not LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
626
+            if($errno !== 0x00 && $errno !== 0x31) {
627
+                $this->ldapConnectionRes = null;
628
+            }
629
+
630
+            return false;
631
+        }
632
+        return true;
633
+    }
634 634
 
635 635
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Access.php 1 patch
Indentation   +1859 added lines, -1859 removed lines patch added patch discarded remove patch
@@ -55,1620 +55,1620 @@  discard block
 block discarded – undo
55 55
  * @package OCA\User_LDAP
56 56
  */
57 57
 class Access extends LDAPUtility implements IUserTools {
58
-	const UUID_ATTRIBUTES = ['entryuuid', 'nsuniqueid', 'objectguid', 'guid', 'ipauniqueid'];
59
-
60
-	/** @var \OCA\User_LDAP\Connection */
61
-	public $connection;
62
-	/** @var Manager */
63
-	public $userManager;
64
-	//never ever check this var directly, always use getPagedSearchResultState
65
-	protected $pagedSearchedSuccessful;
66
-
67
-	/**
68
-	 * @var string[] $cookies an array of returned Paged Result cookies
69
-	 */
70
-	protected $cookies = array();
71
-
72
-	/**
73
-	 * @var string $lastCookie the last cookie returned from a Paged Results
74
-	 * operation, defaults to an empty string
75
-	 */
76
-	protected $lastCookie = '';
77
-
78
-	/**
79
-	 * @var AbstractMapping $userMapper
80
-	 */
81
-	protected $userMapper;
82
-
83
-	/**
84
-	* @var AbstractMapping $userMapper
85
-	*/
86
-	protected $groupMapper;
87
-
88
-	/**
89
-	 * @var \OCA\User_LDAP\Helper
90
-	 */
91
-	private $helper;
92
-	/** @var IServerContainer */
93
-	private $c;
94
-
95
-	public function __construct(
96
-		Connection $connection,
97
-		ILDAPWrapper $ldap,
98
-		Manager $userManager,
99
-		Helper $helper,
100
-		IServerContainer $c
101
-	) {
102
-		parent::__construct($ldap);
103
-		$this->connection = $connection;
104
-		$this->userManager = $userManager;
105
-		$this->userManager->setLdapAccess($this);
106
-		$this->helper = $helper;
107
-		$this->c = $c;
108
-	}
109
-
110
-	/**
111
-	 * sets the User Mapper
112
-	 * @param AbstractMapping $mapper
113
-	 */
114
-	public function setUserMapper(AbstractMapping $mapper) {
115
-		$this->userMapper = $mapper;
116
-	}
117
-
118
-	/**
119
-	 * returns the User Mapper
120
-	 * @throws \Exception
121
-	 * @return AbstractMapping
122
-	 */
123
-	public function getUserMapper() {
124
-		if(is_null($this->userMapper)) {
125
-			throw new \Exception('UserMapper was not assigned to this Access instance.');
126
-		}
127
-		return $this->userMapper;
128
-	}
129
-
130
-	/**
131
-	 * sets the Group Mapper
132
-	 * @param AbstractMapping $mapper
133
-	 */
134
-	public function setGroupMapper(AbstractMapping $mapper) {
135
-		$this->groupMapper = $mapper;
136
-	}
137
-
138
-	/**
139
-	 * returns the Group Mapper
140
-	 * @throws \Exception
141
-	 * @return AbstractMapping
142
-	 */
143
-	public function getGroupMapper() {
144
-		if(is_null($this->groupMapper)) {
145
-			throw new \Exception('GroupMapper was not assigned to this Access instance.');
146
-		}
147
-		return $this->groupMapper;
148
-	}
149
-
150
-	/**
151
-	 * @return bool
152
-	 */
153
-	private function checkConnection() {
154
-		return ($this->connection instanceof Connection);
155
-	}
156
-
157
-	/**
158
-	 * returns the Connection instance
159
-	 * @return \OCA\User_LDAP\Connection
160
-	 */
161
-	public function getConnection() {
162
-		return $this->connection;
163
-	}
164
-
165
-	/**
166
-	 * reads a given attribute for an LDAP record identified by a DN
167
-	 * @param string $dn the record in question
168
-	 * @param string $attr the attribute that shall be retrieved
169
-	 *        if empty, just check the record's existence
170
-	 * @param string $filter
171
-	 * @return array|false an array of values on success or an empty
172
-	 *          array if $attr is empty, false otherwise
173
-	 */
174
-	public function readAttribute($dn, $attr, $filter = 'objectClass=*') {
175
-		if(!$this->checkConnection()) {
176
-			\OCP\Util::writeLog('user_ldap',
177
-				'No LDAP Connector assigned, access impossible for readAttribute.',
178
-				\OCP\Util::WARN);
179
-			return false;
180
-		}
181
-		$cr = $this->connection->getConnectionResource();
182
-		if(!$this->ldap->isResource($cr)) {
183
-			//LDAP not available
184
-			\OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', \OCP\Util::DEBUG);
185
-			return false;
186
-		}
187
-		//Cancel possibly running Paged Results operation, otherwise we run in
188
-		//LDAP protocol errors
189
-		$this->abandonPagedSearch();
190
-		// openLDAP requires that we init a new Paged Search. Not needed by AD,
191
-		// but does not hurt either.
192
-		$pagingSize = intval($this->connection->ldapPagingSize);
193
-		// 0 won't result in replies, small numbers may leave out groups
194
-		// (cf. #12306), 500 is default for paging and should work everywhere.
195
-		$maxResults = $pagingSize > 20 ? $pagingSize : 500;
196
-		$attr = mb_strtolower($attr, 'UTF-8');
197
-		// the actual read attribute later may contain parameters on a ranged
198
-		// request, e.g. member;range=99-199. Depends on server reply.
199
-		$attrToRead = $attr;
200
-
201
-		$values = [];
202
-		$isRangeRequest = false;
203
-		do {
204
-			$result = $this->executeRead($cr, $dn, $attrToRead, $filter, $maxResults);
205
-			if(is_bool($result)) {
206
-				// when an exists request was run and it was successful, an empty
207
-				// array must be returned
208
-				return $result ? [] : false;
209
-			}
210
-
211
-			if (!$isRangeRequest) {
212
-				$values = $this->extractAttributeValuesFromResult($result, $attr);
213
-				if (!empty($values)) {
214
-					return $values;
215
-				}
216
-			}
217
-
218
-			$isRangeRequest = false;
219
-			$result = $this->extractRangeData($result, $attr);
220
-			if (!empty($result)) {
221
-				$normalizedResult = $this->extractAttributeValuesFromResult(
222
-					[ $attr => $result['values'] ],
223
-					$attr
224
-				);
225
-				$values = array_merge($values, $normalizedResult);
226
-
227
-				if($result['rangeHigh'] === '*') {
228
-					// when server replies with * as high range value, there are
229
-					// no more results left
230
-					return $values;
231
-				} else {
232
-					$low  = $result['rangeHigh'] + 1;
233
-					$attrToRead = $result['attributeName'] . ';range=' . $low . '-*';
234
-					$isRangeRequest = true;
235
-				}
236
-			}
237
-		} while($isRangeRequest);
238
-
239
-		\OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, \OCP\Util::DEBUG);
240
-		return false;
241
-	}
242
-
243
-	/**
244
-	 * Runs an read operation against LDAP
245
-	 *
246
-	 * @param resource $cr the LDAP connection
247
-	 * @param string $dn
248
-	 * @param string $attribute
249
-	 * @param string $filter
250
-	 * @param int $maxResults
251
-	 * @return array|bool false if there was any error, true if an exists check
252
-	 *                    was performed and the requested DN found, array with the
253
-	 *                    returned data on a successful usual operation
254
-	 */
255
-	public function executeRead($cr, $dn, $attribute, $filter, $maxResults) {
256
-		$this->initPagedSearch($filter, array($dn), array($attribute), $maxResults, 0);
257
-		$dn = $this->helper->DNasBaseParameter($dn);
258
-		$rr = @$this->invokeLDAPMethod('read', $cr, $dn, $filter, array($attribute));
259
-		if (!$this->ldap->isResource($rr)) {
260
-			if ($attribute !== '') {
261
-				//do not throw this message on userExists check, irritates
262
-				\OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN ' . $dn, \OCP\Util::DEBUG);
263
-			}
264
-			//in case an error occurs , e.g. object does not exist
265
-			return false;
266
-		}
267
-		if ($attribute === '' && ($filter === 'objectclass=*' || $this->invokeLDAPMethod('countEntries', $cr, $rr) === 1)) {
268
-			\OCP\Util::writeLog('user_ldap', 'readAttribute: ' . $dn . ' found', \OCP\Util::DEBUG);
269
-			return true;
270
-		}
271
-		$er = $this->invokeLDAPMethod('firstEntry', $cr, $rr);
272
-		if (!$this->ldap->isResource($er)) {
273
-			//did not match the filter, return false
274
-			return false;
275
-		}
276
-		//LDAP attributes are not case sensitive
277
-		$result = \OCP\Util::mb_array_change_key_case(
278
-			$this->invokeLDAPMethod('getAttributes', $cr, $er), MB_CASE_LOWER, 'UTF-8');
279
-
280
-		return $result;
281
-	}
282
-
283
-	/**
284
-	 * Normalizes a result grom getAttributes(), i.e. handles DNs and binary
285
-	 * data if present.
286
-	 *
287
-	 * @param array $result from ILDAPWrapper::getAttributes()
288
-	 * @param string $attribute the attribute name that was read
289
-	 * @return string[]
290
-	 */
291
-	public function extractAttributeValuesFromResult($result, $attribute) {
292
-		$values = [];
293
-		if(isset($result[$attribute]) && $result[$attribute]['count'] > 0) {
294
-			$lowercaseAttribute = strtolower($attribute);
295
-			for($i=0;$i<$result[$attribute]['count'];$i++) {
296
-				if($this->resemblesDN($attribute)) {
297
-					$values[] = $this->helper->sanitizeDN($result[$attribute][$i]);
298
-				} elseif($lowercaseAttribute === 'objectguid' || $lowercaseAttribute === 'guid') {
299
-					$values[] = $this->convertObjectGUID2Str($result[$attribute][$i]);
300
-				} else {
301
-					$values[] = $result[$attribute][$i];
302
-				}
303
-			}
304
-		}
305
-		return $values;
306
-	}
307
-
308
-	/**
309
-	 * Attempts to find ranged data in a getAttribute results and extracts the
310
-	 * returned values as well as information on the range and full attribute
311
-	 * name for further processing.
312
-	 *
313
-	 * @param array $result from ILDAPWrapper::getAttributes()
314
-	 * @param string $attribute the attribute name that was read. Without ";range=…"
315
-	 * @return array If a range was detected with keys 'values', 'attributeName',
316
-	 *               'attributeFull' and 'rangeHigh', otherwise empty.
317
-	 */
318
-	public function extractRangeData($result, $attribute) {
319
-		$keys = array_keys($result);
320
-		foreach($keys as $key) {
321
-			if($key !== $attribute && strpos($key, $attribute) === 0) {
322
-				$queryData = explode(';', $key);
323
-				if(strpos($queryData[1], 'range=') === 0) {
324
-					$high = substr($queryData[1], 1 + strpos($queryData[1], '-'));
325
-					$data = [
326
-						'values' => $result[$key],
327
-						'attributeName' => $queryData[0],
328
-						'attributeFull' => $key,
329
-						'rangeHigh' => $high,
330
-					];
331
-					return $data;
332
-				}
333
-			}
334
-		}
335
-		return [];
336
-	}
58
+    const UUID_ATTRIBUTES = ['entryuuid', 'nsuniqueid', 'objectguid', 'guid', 'ipauniqueid'];
59
+
60
+    /** @var \OCA\User_LDAP\Connection */
61
+    public $connection;
62
+    /** @var Manager */
63
+    public $userManager;
64
+    //never ever check this var directly, always use getPagedSearchResultState
65
+    protected $pagedSearchedSuccessful;
66
+
67
+    /**
68
+     * @var string[] $cookies an array of returned Paged Result cookies
69
+     */
70
+    protected $cookies = array();
71
+
72
+    /**
73
+     * @var string $lastCookie the last cookie returned from a Paged Results
74
+     * operation, defaults to an empty string
75
+     */
76
+    protected $lastCookie = '';
77
+
78
+    /**
79
+     * @var AbstractMapping $userMapper
80
+     */
81
+    protected $userMapper;
82
+
83
+    /**
84
+     * @var AbstractMapping $userMapper
85
+     */
86
+    protected $groupMapper;
87
+
88
+    /**
89
+     * @var \OCA\User_LDAP\Helper
90
+     */
91
+    private $helper;
92
+    /** @var IServerContainer */
93
+    private $c;
94
+
95
+    public function __construct(
96
+        Connection $connection,
97
+        ILDAPWrapper $ldap,
98
+        Manager $userManager,
99
+        Helper $helper,
100
+        IServerContainer $c
101
+    ) {
102
+        parent::__construct($ldap);
103
+        $this->connection = $connection;
104
+        $this->userManager = $userManager;
105
+        $this->userManager->setLdapAccess($this);
106
+        $this->helper = $helper;
107
+        $this->c = $c;
108
+    }
109
+
110
+    /**
111
+     * sets the User Mapper
112
+     * @param AbstractMapping $mapper
113
+     */
114
+    public function setUserMapper(AbstractMapping $mapper) {
115
+        $this->userMapper = $mapper;
116
+    }
117
+
118
+    /**
119
+     * returns the User Mapper
120
+     * @throws \Exception
121
+     * @return AbstractMapping
122
+     */
123
+    public function getUserMapper() {
124
+        if(is_null($this->userMapper)) {
125
+            throw new \Exception('UserMapper was not assigned to this Access instance.');
126
+        }
127
+        return $this->userMapper;
128
+    }
129
+
130
+    /**
131
+     * sets the Group Mapper
132
+     * @param AbstractMapping $mapper
133
+     */
134
+    public function setGroupMapper(AbstractMapping $mapper) {
135
+        $this->groupMapper = $mapper;
136
+    }
137
+
138
+    /**
139
+     * returns the Group Mapper
140
+     * @throws \Exception
141
+     * @return AbstractMapping
142
+     */
143
+    public function getGroupMapper() {
144
+        if(is_null($this->groupMapper)) {
145
+            throw new \Exception('GroupMapper was not assigned to this Access instance.');
146
+        }
147
+        return $this->groupMapper;
148
+    }
149
+
150
+    /**
151
+     * @return bool
152
+     */
153
+    private function checkConnection() {
154
+        return ($this->connection instanceof Connection);
155
+    }
156
+
157
+    /**
158
+     * returns the Connection instance
159
+     * @return \OCA\User_LDAP\Connection
160
+     */
161
+    public function getConnection() {
162
+        return $this->connection;
163
+    }
164
+
165
+    /**
166
+     * reads a given attribute for an LDAP record identified by a DN
167
+     * @param string $dn the record in question
168
+     * @param string $attr the attribute that shall be retrieved
169
+     *        if empty, just check the record's existence
170
+     * @param string $filter
171
+     * @return array|false an array of values on success or an empty
172
+     *          array if $attr is empty, false otherwise
173
+     */
174
+    public function readAttribute($dn, $attr, $filter = 'objectClass=*') {
175
+        if(!$this->checkConnection()) {
176
+            \OCP\Util::writeLog('user_ldap',
177
+                'No LDAP Connector assigned, access impossible for readAttribute.',
178
+                \OCP\Util::WARN);
179
+            return false;
180
+        }
181
+        $cr = $this->connection->getConnectionResource();
182
+        if(!$this->ldap->isResource($cr)) {
183
+            //LDAP not available
184
+            \OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', \OCP\Util::DEBUG);
185
+            return false;
186
+        }
187
+        //Cancel possibly running Paged Results operation, otherwise we run in
188
+        //LDAP protocol errors
189
+        $this->abandonPagedSearch();
190
+        // openLDAP requires that we init a new Paged Search. Not needed by AD,
191
+        // but does not hurt either.
192
+        $pagingSize = intval($this->connection->ldapPagingSize);
193
+        // 0 won't result in replies, small numbers may leave out groups
194
+        // (cf. #12306), 500 is default for paging and should work everywhere.
195
+        $maxResults = $pagingSize > 20 ? $pagingSize : 500;
196
+        $attr = mb_strtolower($attr, 'UTF-8');
197
+        // the actual read attribute later may contain parameters on a ranged
198
+        // request, e.g. member;range=99-199. Depends on server reply.
199
+        $attrToRead = $attr;
200
+
201
+        $values = [];
202
+        $isRangeRequest = false;
203
+        do {
204
+            $result = $this->executeRead($cr, $dn, $attrToRead, $filter, $maxResults);
205
+            if(is_bool($result)) {
206
+                // when an exists request was run and it was successful, an empty
207
+                // array must be returned
208
+                return $result ? [] : false;
209
+            }
210
+
211
+            if (!$isRangeRequest) {
212
+                $values = $this->extractAttributeValuesFromResult($result, $attr);
213
+                if (!empty($values)) {
214
+                    return $values;
215
+                }
216
+            }
217
+
218
+            $isRangeRequest = false;
219
+            $result = $this->extractRangeData($result, $attr);
220
+            if (!empty($result)) {
221
+                $normalizedResult = $this->extractAttributeValuesFromResult(
222
+                    [ $attr => $result['values'] ],
223
+                    $attr
224
+                );
225
+                $values = array_merge($values, $normalizedResult);
226
+
227
+                if($result['rangeHigh'] === '*') {
228
+                    // when server replies with * as high range value, there are
229
+                    // no more results left
230
+                    return $values;
231
+                } else {
232
+                    $low  = $result['rangeHigh'] + 1;
233
+                    $attrToRead = $result['attributeName'] . ';range=' . $low . '-*';
234
+                    $isRangeRequest = true;
235
+                }
236
+            }
237
+        } while($isRangeRequest);
238
+
239
+        \OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, \OCP\Util::DEBUG);
240
+        return false;
241
+    }
242
+
243
+    /**
244
+     * Runs an read operation against LDAP
245
+     *
246
+     * @param resource $cr the LDAP connection
247
+     * @param string $dn
248
+     * @param string $attribute
249
+     * @param string $filter
250
+     * @param int $maxResults
251
+     * @return array|bool false if there was any error, true if an exists check
252
+     *                    was performed and the requested DN found, array with the
253
+     *                    returned data on a successful usual operation
254
+     */
255
+    public function executeRead($cr, $dn, $attribute, $filter, $maxResults) {
256
+        $this->initPagedSearch($filter, array($dn), array($attribute), $maxResults, 0);
257
+        $dn = $this->helper->DNasBaseParameter($dn);
258
+        $rr = @$this->invokeLDAPMethod('read', $cr, $dn, $filter, array($attribute));
259
+        if (!$this->ldap->isResource($rr)) {
260
+            if ($attribute !== '') {
261
+                //do not throw this message on userExists check, irritates
262
+                \OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN ' . $dn, \OCP\Util::DEBUG);
263
+            }
264
+            //in case an error occurs , e.g. object does not exist
265
+            return false;
266
+        }
267
+        if ($attribute === '' && ($filter === 'objectclass=*' || $this->invokeLDAPMethod('countEntries', $cr, $rr) === 1)) {
268
+            \OCP\Util::writeLog('user_ldap', 'readAttribute: ' . $dn . ' found', \OCP\Util::DEBUG);
269
+            return true;
270
+        }
271
+        $er = $this->invokeLDAPMethod('firstEntry', $cr, $rr);
272
+        if (!$this->ldap->isResource($er)) {
273
+            //did not match the filter, return false
274
+            return false;
275
+        }
276
+        //LDAP attributes are not case sensitive
277
+        $result = \OCP\Util::mb_array_change_key_case(
278
+            $this->invokeLDAPMethod('getAttributes', $cr, $er), MB_CASE_LOWER, 'UTF-8');
279
+
280
+        return $result;
281
+    }
282
+
283
+    /**
284
+     * Normalizes a result grom getAttributes(), i.e. handles DNs and binary
285
+     * data if present.
286
+     *
287
+     * @param array $result from ILDAPWrapper::getAttributes()
288
+     * @param string $attribute the attribute name that was read
289
+     * @return string[]
290
+     */
291
+    public function extractAttributeValuesFromResult($result, $attribute) {
292
+        $values = [];
293
+        if(isset($result[$attribute]) && $result[$attribute]['count'] > 0) {
294
+            $lowercaseAttribute = strtolower($attribute);
295
+            for($i=0;$i<$result[$attribute]['count'];$i++) {
296
+                if($this->resemblesDN($attribute)) {
297
+                    $values[] = $this->helper->sanitizeDN($result[$attribute][$i]);
298
+                } elseif($lowercaseAttribute === 'objectguid' || $lowercaseAttribute === 'guid') {
299
+                    $values[] = $this->convertObjectGUID2Str($result[$attribute][$i]);
300
+                } else {
301
+                    $values[] = $result[$attribute][$i];
302
+                }
303
+            }
304
+        }
305
+        return $values;
306
+    }
307
+
308
+    /**
309
+     * Attempts to find ranged data in a getAttribute results and extracts the
310
+     * returned values as well as information on the range and full attribute
311
+     * name for further processing.
312
+     *
313
+     * @param array $result from ILDAPWrapper::getAttributes()
314
+     * @param string $attribute the attribute name that was read. Without ";range=…"
315
+     * @return array If a range was detected with keys 'values', 'attributeName',
316
+     *               'attributeFull' and 'rangeHigh', otherwise empty.
317
+     */
318
+    public function extractRangeData($result, $attribute) {
319
+        $keys = array_keys($result);
320
+        foreach($keys as $key) {
321
+            if($key !== $attribute && strpos($key, $attribute) === 0) {
322
+                $queryData = explode(';', $key);
323
+                if(strpos($queryData[1], 'range=') === 0) {
324
+                    $high = substr($queryData[1], 1 + strpos($queryData[1], '-'));
325
+                    $data = [
326
+                        'values' => $result[$key],
327
+                        'attributeName' => $queryData[0],
328
+                        'attributeFull' => $key,
329
+                        'rangeHigh' => $high,
330
+                    ];
331
+                    return $data;
332
+                }
333
+            }
334
+        }
335
+        return [];
336
+    }
337 337
 	
338
-	/**
339
-	 * Set password for an LDAP user identified by a DN
340
-	 *
341
-	 * @param string $userDN the user in question
342
-	 * @param string $password the new password
343
-	 * @return bool
344
-	 * @throws HintException
345
-	 * @throws \Exception
346
-	 */
347
-	public function setPassword($userDN, $password) {
348
-		if(intval($this->connection->turnOnPasswordChange) !== 1) {
349
-			throw new \Exception('LDAP password changes are disabled.');
350
-		}
351
-		$cr = $this->connection->getConnectionResource();
352
-		if(!$this->ldap->isResource($cr)) {
353
-			//LDAP not available
354
-			\OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', \OCP\Util::DEBUG);
355
-			return false;
356
-		}
357
-		try {
358
-			return @$this->invokeLDAPMethod('modReplace', $cr, $userDN, $password);
359
-		} catch(ConstraintViolationException $e) {
360
-			throw new HintException('Password change rejected.', \OC::$server->getL10N('user_ldap')->t('Password change rejected. Hint: ').$e->getMessage(), $e->getCode());
361
-		}
362
-	}
363
-
364
-	/**
365
-	 * checks whether the given attributes value is probably a DN
366
-	 * @param string $attr the attribute in question
367
-	 * @return boolean if so true, otherwise false
368
-	 */
369
-	private function resemblesDN($attr) {
370
-		$resemblingAttributes = array(
371
-			'dn',
372
-			'uniquemember',
373
-			'member',
374
-			// memberOf is an "operational" attribute, without a definition in any RFC
375
-			'memberof'
376
-		);
377
-		return in_array($attr, $resemblingAttributes);
378
-	}
379
-
380
-	/**
381
-	 * checks whether the given string is probably a DN
382
-	 * @param string $string
383
-	 * @return boolean
384
-	 */
385
-	public function stringResemblesDN($string) {
386
-		$r = $this->ldap->explodeDN($string, 0);
387
-		// if exploding a DN succeeds and does not end up in
388
-		// an empty array except for $r[count] being 0.
389
-		return (is_array($r) && count($r) > 1);
390
-	}
391
-
392
-	/**
393
-	 * returns a DN-string that is cleaned from not domain parts, e.g.
394
-	 * cn=foo,cn=bar,dc=foobar,dc=server,dc=org
395
-	 * becomes dc=foobar,dc=server,dc=org
396
-	 * @param string $dn
397
-	 * @return string
398
-	 */
399
-	public function getDomainDNFromDN($dn) {
400
-		$allParts = $this->ldap->explodeDN($dn, 0);
401
-		if($allParts === false) {
402
-			//not a valid DN
403
-			return '';
404
-		}
405
-		$domainParts = array();
406
-		$dcFound = false;
407
-		foreach($allParts as $part) {
408
-			if(!$dcFound && strpos($part, 'dc=') === 0) {
409
-				$dcFound = true;
410
-			}
411
-			if($dcFound) {
412
-				$domainParts[] = $part;
413
-			}
414
-		}
415
-		$domainDN = implode(',', $domainParts);
416
-		return $domainDN;
417
-	}
418
-
419
-	/**
420
-	 * returns the LDAP DN for the given internal Nextcloud name of the group
421
-	 * @param string $name the Nextcloud name in question
422
-	 * @return string|false LDAP DN on success, otherwise false
423
-	 */
424
-	public function groupname2dn($name) {
425
-		return $this->groupMapper->getDNByName($name);
426
-	}
427
-
428
-	/**
429
-	 * returns the LDAP DN for the given internal Nextcloud name of the user
430
-	 * @param string $name the Nextcloud name in question
431
-	 * @return string|false with the LDAP DN on success, otherwise false
432
-	 */
433
-	public function username2dn($name) {
434
-		$fdn = $this->userMapper->getDNByName($name);
435
-
436
-		//Check whether the DN belongs to the Base, to avoid issues on multi-
437
-		//server setups
438
-		if(is_string($fdn) && $this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
439
-			return $fdn;
440
-		}
441
-
442
-		return false;
443
-	}
444
-
445
-	/**
446
-	 * returns the internal Nextcloud name for the given LDAP DN of the group, false on DN outside of search DN or failure
447
-	 * @param string $fdn the dn of the group object
448
-	 * @param string $ldapName optional, the display name of the object
449
-	 * @return string|false with the name to use in Nextcloud, false on DN outside of search DN
450
-	 */
451
-	public function dn2groupname($fdn, $ldapName = null) {
452
-		//To avoid bypassing the base DN settings under certain circumstances
453
-		//with the group support, check whether the provided DN matches one of
454
-		//the given Bases
455
-		if(!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseGroups)) {
456
-			return false;
457
-		}
458
-
459
-		return $this->dn2ocname($fdn, $ldapName, false);
460
-	}
461
-
462
-	/**
463
-	 * accepts an array of group DNs and tests whether they match the user
464
-	 * filter by doing read operations against the group entries. Returns an
465
-	 * array of DNs that match the filter.
466
-	 *
467
-	 * @param string[] $groupDNs
468
-	 * @return string[]
469
-	 */
470
-	public function groupsMatchFilter($groupDNs) {
471
-		$validGroupDNs = [];
472
-		foreach($groupDNs as $dn) {
473
-			$cacheKey = 'groupsMatchFilter-'.$dn;
474
-			$groupMatchFilter = $this->connection->getFromCache($cacheKey);
475
-			if(!is_null($groupMatchFilter)) {
476
-				if($groupMatchFilter) {
477
-					$validGroupDNs[] = $dn;
478
-				}
479
-				continue;
480
-			}
481
-
482
-			// Check the base DN first. If this is not met already, we don't
483
-			// need to ask the server at all.
484
-			if(!$this->isDNPartOfBase($dn, $this->connection->ldapBaseGroups)) {
485
-				$this->connection->writeToCache($cacheKey, false);
486
-				continue;
487
-			}
488
-
489
-			$result = $this->readAttribute($dn, 'cn', $this->connection->ldapGroupFilter);
490
-			if(is_array($result)) {
491
-				$this->connection->writeToCache($cacheKey, true);
492
-				$validGroupDNs[] = $dn;
493
-			} else {
494
-				$this->connection->writeToCache($cacheKey, false);
495
-			}
496
-
497
-		}
498
-		return $validGroupDNs;
499
-	}
500
-
501
-	/**
502
-	 * returns the internal Nextcloud name for the given LDAP DN of the user, false on DN outside of search DN or failure
503
-	 * @param string $dn the dn of the user object
504
-	 * @param string $ldapName optional, the display name of the object
505
-	 * @return string|false with with the name to use in Nextcloud
506
-	 */
507
-	public function dn2username($fdn, $ldapName = null) {
508
-		//To avoid bypassing the base DN settings under certain circumstances
509
-		//with the group support, check whether the provided DN matches one of
510
-		//the given Bases
511
-		if(!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
512
-			return false;
513
-		}
514
-
515
-		return $this->dn2ocname($fdn, $ldapName, true);
516
-	}
517
-
518
-	/**
519
-	 * returns an internal Nextcloud name for the given LDAP DN, false on DN outside of search DN
520
-	 *
521
-	 * @param string $fdn the dn of the user object
522
-	 * @param string $ldapName optional, the display name of the object
523
-	 * @param bool $isUser optional, whether it is a user object (otherwise group assumed)
524
-	 * @param bool|null $newlyMapped
525
-	 * @param array|null $record
526
-	 * @return false|string with with the name to use in Nextcloud
527
-	 */
528
-	public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped = null, array $record = null) {
529
-		$newlyMapped = false;
530
-		if($isUser) {
531
-			$mapper = $this->getUserMapper();
532
-			$nameAttribute = $this->connection->ldapUserDisplayName;
533
-		} else {
534
-			$mapper = $this->getGroupMapper();
535
-			$nameAttribute = $this->connection->ldapGroupDisplayName;
536
-		}
537
-
538
-		//let's try to retrieve the Nextcloud name from the mappings table
539
-		$ncName = $mapper->getNameByDN($fdn);
540
-		if(is_string($ncName)) {
541
-			return $ncName;
542
-		}
543
-
544
-		//second try: get the UUID and check if it is known. Then, update the DN and return the name.
545
-		$uuid = $this->getUUID($fdn, $isUser, $record);
546
-		if(is_string($uuid)) {
547
-			$ncName = $mapper->getNameByUUID($uuid);
548
-			if(is_string($ncName)) {
549
-				$mapper->setDNbyUUID($fdn, $uuid);
550
-				return $ncName;
551
-			}
552
-		} else {
553
-			//If the UUID can't be detected something is foul.
554
-			\OCP\Util::writeLog('user_ldap', 'Cannot determine UUID for '.$fdn.'. Skipping.', \OCP\Util::INFO);
555
-			return false;
556
-		}
557
-
558
-		if(is_null($ldapName)) {
559
-			$ldapName = $this->readAttribute($fdn, $nameAttribute);
560
-			if(!isset($ldapName[0]) && empty($ldapName[0])) {
561
-				\OCP\Util::writeLog('user_ldap', 'No or empty name for '.$fdn.'.', \OCP\Util::INFO);
562
-				return false;
563
-			}
564
-			$ldapName = $ldapName[0];
565
-		}
566
-
567
-		if($isUser) {
568
-			$usernameAttribute = strval($this->connection->ldapExpertUsernameAttr);
569
-			if ($usernameAttribute !== '') {
570
-				$username = $this->readAttribute($fdn, $usernameAttribute);
571
-				$username = $username[0];
572
-			} else {
573
-				$username = $uuid;
574
-			}
575
-			$intName = $this->sanitizeUsername($username);
576
-		} else {
577
-			$intName = $ldapName;
578
-		}
579
-
580
-		//a new user/group! Add it only if it doesn't conflict with other backend's users or existing groups
581
-		//disabling Cache is required to avoid that the new user is cached as not-existing in fooExists check
582
-		//NOTE: mind, disabling cache affects only this instance! Using it
583
-		// outside of core user management will still cache the user as non-existing.
584
-		$originalTTL = $this->connection->ldapCacheTTL;
585
-		$this->connection->setConfiguration(array('ldapCacheTTL' => 0));
586
-		if(($isUser && !\OCP\User::userExists($intName))
587
-			|| (!$isUser && !\OC::$server->getGroupManager()->groupExists($intName))) {
588
-			if($mapper->map($fdn, $intName, $uuid)) {
589
-				$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
590
-				$newlyMapped = true;
591
-				return $intName;
592
-			}
593
-		}
594
-		$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
595
-
596
-		$altName = $this->createAltInternalOwnCloudName($intName, $isUser);
597
-		if(is_string($altName) && $mapper->map($fdn, $altName, $uuid)) {
598
-			$newlyMapped = true;
599
-			return $altName;
600
-		}
601
-
602
-		//if everything else did not help..
603
-		\OCP\Util::writeLog('user_ldap', 'Could not create unique name for '.$fdn.'.', \OCP\Util::INFO);
604
-		return false;
605
-	}
606
-
607
-	/**
608
-	 * gives back the user names as they are used ownClod internally
609
-	 * @param array $ldapUsers as returned by fetchList()
610
-	 * @return array an array with the user names to use in Nextcloud
611
-	 *
612
-	 * gives back the user names as they are used ownClod internally
613
-	 */
614
-	public function nextcloudUserNames($ldapUsers) {
615
-		return $this->ldap2NextcloudNames($ldapUsers, true);
616
-	}
617
-
618
-	/**
619
-	 * gives back the group names as they are used ownClod internally
620
-	 * @param array $ldapGroups as returned by fetchList()
621
-	 * @return array an array with the group names to use in Nextcloud
622
-	 *
623
-	 * gives back the group names as they are used ownClod internally
624
-	 */
625
-	public function nextcloudGroupNames($ldapGroups) {
626
-		return $this->ldap2NextcloudNames($ldapGroups, false);
627
-	}
628
-
629
-	/**
630
-	 * @param array $ldapObjects as returned by fetchList()
631
-	 * @param bool $isUsers
632
-	 * @return array
633
-	 */
634
-	private function ldap2NextcloudNames($ldapObjects, $isUsers) {
635
-		if($isUsers) {
636
-			$nameAttribute = $this->connection->ldapUserDisplayName;
637
-			$sndAttribute  = $this->connection->ldapUserDisplayName2;
638
-		} else {
639
-			$nameAttribute = $this->connection->ldapGroupDisplayName;
640
-		}
641
-		$nextcloudNames = array();
642
-
643
-		foreach($ldapObjects as $ldapObject) {
644
-			$nameByLDAP = null;
645
-			if(    isset($ldapObject[$nameAttribute])
646
-				&& is_array($ldapObject[$nameAttribute])
647
-				&& isset($ldapObject[$nameAttribute][0])
648
-			) {
649
-				// might be set, but not necessarily. if so, we use it.
650
-				$nameByLDAP = $ldapObject[$nameAttribute][0];
651
-			}
652
-
653
-			$ncName = $this->dn2ocname($ldapObject['dn'][0], $nameByLDAP, $isUsers);
654
-			if($ncName) {
655
-				$nextcloudNames[] = $ncName;
656
-				if($isUsers) {
657
-					//cache the user names so it does not need to be retrieved
658
-					//again later (e.g. sharing dialogue).
659
-					if(is_null($nameByLDAP)) {
660
-						continue;
661
-					}
662
-					$sndName = isset($ldapObject[$sndAttribute][0])
663
-						? $ldapObject[$sndAttribute][0] : '';
664
-					$this->cacheUserDisplayName($ncName, $nameByLDAP, $sndName);
665
-				}
666
-			}
667
-		}
668
-		return $nextcloudNames;
669
-	}
670
-
671
-	/**
672
-	 * caches the user display name
673
-	 * @param string $ocName the internal Nextcloud username
674
-	 * @param string|false $home the home directory path
675
-	 */
676
-	public function cacheUserHome($ocName, $home) {
677
-		$cacheKey = 'getHome'.$ocName;
678
-		$this->connection->writeToCache($cacheKey, $home);
679
-	}
680
-
681
-	/**
682
-	 * caches a user as existing
683
-	 * @param string $ocName the internal Nextcloud username
684
-	 */
685
-	public function cacheUserExists($ocName) {
686
-		$this->connection->writeToCache('userExists'.$ocName, true);
687
-	}
688
-
689
-	/**
690
-	 * caches the user display name
691
-	 * @param string $ocName the internal Nextcloud username
692
-	 * @param string $displayName the display name
693
-	 * @param string $displayName2 the second display name
694
-	 */
695
-	public function cacheUserDisplayName($ocName, $displayName, $displayName2 = '') {
696
-		$user = $this->userManager->get($ocName);
697
-		if($user === null) {
698
-			return;
699
-		}
700
-		$displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
701
-		$cacheKeyTrunk = 'getDisplayName';
702
-		$this->connection->writeToCache($cacheKeyTrunk.$ocName, $displayName);
703
-	}
704
-
705
-	/**
706
-	 * creates a unique name for internal Nextcloud use for users. Don't call it directly.
707
-	 * @param string $name the display name of the object
708
-	 * @return string|false with with the name to use in Nextcloud or false if unsuccessful
709
-	 *
710
-	 * Instead of using this method directly, call
711
-	 * createAltInternalOwnCloudName($name, true)
712
-	 */
713
-	private function _createAltInternalOwnCloudNameForUsers($name) {
714
-		$attempts = 0;
715
-		//while loop is just a precaution. If a name is not generated within
716
-		//20 attempts, something else is very wrong. Avoids infinite loop.
717
-		while($attempts < 20){
718
-			$altName = $name . '_' . rand(1000,9999);
719
-			if(!\OCP\User::userExists($altName)) {
720
-				return $altName;
721
-			}
722
-			$attempts++;
723
-		}
724
-		return false;
725
-	}
726
-
727
-	/**
728
-	 * creates a unique name for internal Nextcloud use for groups. Don't call it directly.
729
-	 * @param string $name the display name of the object
730
-	 * @return string|false with with the name to use in Nextcloud or false if unsuccessful.
731
-	 *
732
-	 * Instead of using this method directly, call
733
-	 * createAltInternalOwnCloudName($name, false)
734
-	 *
735
-	 * Group names are also used as display names, so we do a sequential
736
-	 * numbering, e.g. Developers_42 when there are 41 other groups called
737
-	 * "Developers"
738
-	 */
739
-	private function _createAltInternalOwnCloudNameForGroups($name) {
740
-		$usedNames = $this->groupMapper->getNamesBySearch($name, "", '_%');
741
-		if(!($usedNames) || count($usedNames) === 0) {
742
-			$lastNo = 1; //will become name_2
743
-		} else {
744
-			natsort($usedNames);
745
-			$lastName = array_pop($usedNames);
746
-			$lastNo = intval(substr($lastName, strrpos($lastName, '_') + 1));
747
-		}
748
-		$altName = $name.'_'.strval($lastNo+1);
749
-		unset($usedNames);
750
-
751
-		$attempts = 1;
752
-		while($attempts < 21){
753
-			// Check to be really sure it is unique
754
-			// while loop is just a precaution. If a name is not generated within
755
-			// 20 attempts, something else is very wrong. Avoids infinite loop.
756
-			if(!\OC::$server->getGroupManager()->groupExists($altName)) {
757
-				return $altName;
758
-			}
759
-			$altName = $name . '_' . ($lastNo + $attempts);
760
-			$attempts++;
761
-		}
762
-		return false;
763
-	}
764
-
765
-	/**
766
-	 * creates a unique name for internal Nextcloud use.
767
-	 * @param string $name the display name of the object
768
-	 * @param boolean $isUser whether name should be created for a user (true) or a group (false)
769
-	 * @return string|false with with the name to use in Nextcloud or false if unsuccessful
770
-	 */
771
-	private function createAltInternalOwnCloudName($name, $isUser) {
772
-		$originalTTL = $this->connection->ldapCacheTTL;
773
-		$this->connection->setConfiguration(array('ldapCacheTTL' => 0));
774
-		if($isUser) {
775
-			$altName = $this->_createAltInternalOwnCloudNameForUsers($name);
776
-		} else {
777
-			$altName = $this->_createAltInternalOwnCloudNameForGroups($name);
778
-		}
779
-		$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
780
-
781
-		return $altName;
782
-	}
783
-
784
-	/**
785
-	 * fetches a list of users according to a provided loginName and utilizing
786
-	 * the login filter.
787
-	 *
788
-	 * @param string $loginName
789
-	 * @param array $attributes optional, list of attributes to read
790
-	 * @return array
791
-	 */
792
-	public function fetchUsersByLoginName($loginName, $attributes = array('dn')) {
793
-		$loginName = $this->escapeFilterPart($loginName);
794
-		$filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
795
-		$users = $this->fetchListOfUsers($filter, $attributes);
796
-		return $users;
797
-	}
798
-
799
-	/**
800
-	 * counts the number of users according to a provided loginName and
801
-	 * utilizing the login filter.
802
-	 *
803
-	 * @param string $loginName
804
-	 * @return int
805
-	 */
806
-	public function countUsersByLoginName($loginName) {
807
-		$loginName = $this->escapeFilterPart($loginName);
808
-		$filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
809
-		$users = $this->countUsers($filter);
810
-		return $users;
811
-	}
812
-
813
-	/**
814
-	 * @param string $filter
815
-	 * @param string|string[] $attr
816
-	 * @param int $limit
817
-	 * @param int $offset
818
-	 * @param bool $forceApplyAttributes
819
-	 * @return array
820
-	 */
821
-	public function fetchListOfUsers($filter, $attr, $limit = null, $offset = null, $forceApplyAttributes = false) {
822
-		$ldapRecords = $this->searchUsers($filter, $attr, $limit, $offset);
823
-		$recordsToUpdate = $ldapRecords;
824
-		if(!$forceApplyAttributes) {
825
-			$isBackgroundJobModeAjax = $this->c->getConfig()
826
-					->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'ajax';
827
-			$recordsToUpdate = array_filter($ldapRecords, function($record) use ($isBackgroundJobModeAjax) {
828
-				$newlyMapped = false;
829
-				$uid = $this->dn2ocname($record['dn'][0], null, true, $newlyMapped, $record);
830
-				return ($uid !== false) && ($newlyMapped || $isBackgroundJobModeAjax);
831
-			});
832
-		}
833
-		$this->batchApplyUserAttributes($recordsToUpdate);
834
-		return $this->fetchList($ldapRecords, (count($attr) > 1));
835
-	}
836
-
837
-	/**
838
-	 * provided with an array of LDAP user records the method will fetch the
839
-	 * user object and requests it to process the freshly fetched attributes and
840
-	 * and their values
841
-	 * @param array $ldapRecords
842
-	 */
843
-	public function batchApplyUserAttributes(array $ldapRecords){
844
-		$displayNameAttribute = strtolower($this->connection->ldapUserDisplayName);
845
-		foreach($ldapRecords as $userRecord) {
846
-			if(!isset($userRecord[$displayNameAttribute])) {
847
-				// displayName is obligatory
848
-				continue;
849
-			}
850
-			$ocName  = $this->dn2ocname($userRecord['dn'][0], null, true);
851
-			if($ocName === false) {
852
-				continue;
853
-			}
854
-			$this->cacheUserExists($ocName);
855
-			$user = $this->userManager->get($ocName);
856
-			if($user instanceof OfflineUser) {
857
-				$user->unmark();
858
-				$user = $this->userManager->get($ocName);
859
-			}
860
-			if ($user !== null) {
861
-				$user->processAttributes($userRecord);
862
-			} else {
863
-				\OC::$server->getLogger()->debug(
864
-					"The ldap user manager returned null for $ocName",
865
-					['app'=>'user_ldap']
866
-				);
867
-			}
868
-		}
869
-	}
870
-
871
-	/**
872
-	 * @param string $filter
873
-	 * @param string|string[] $attr
874
-	 * @param int $limit
875
-	 * @param int $offset
876
-	 * @return array
877
-	 */
878
-	public function fetchListOfGroups($filter, $attr, $limit = null, $offset = null) {
879
-		return $this->fetchList($this->searchGroups($filter, $attr, $limit, $offset), (count($attr) > 1));
880
-	}
881
-
882
-	/**
883
-	 * @param array $list
884
-	 * @param bool $manyAttributes
885
-	 * @return array
886
-	 */
887
-	private function fetchList($list, $manyAttributes) {
888
-		if(is_array($list)) {
889
-			if($manyAttributes) {
890
-				return $list;
891
-			} else {
892
-				$list = array_reduce($list, function($carry, $item) {
893
-					$attribute = array_keys($item)[0];
894
-					$carry[] = $item[$attribute][0];
895
-					return $carry;
896
-				}, array());
897
-				return array_unique($list, SORT_LOCALE_STRING);
898
-			}
899
-		}
900
-
901
-		//error cause actually, maybe throw an exception in future.
902
-		return array();
903
-	}
904
-
905
-	/**
906
-	 * executes an LDAP search, optimized for Users
907
-	 * @param string $filter the LDAP filter for the search
908
-	 * @param string|string[] $attr optional, when a certain attribute shall be filtered out
909
-	 * @param integer $limit
910
-	 * @param integer $offset
911
-	 * @return array with the search result
912
-	 *
913
-	 * Executes an LDAP search
914
-	 */
915
-	public function searchUsers($filter, $attr = null, $limit = null, $offset = null) {
916
-		return $this->search($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
917
-	}
918
-
919
-	/**
920
-	 * @param string $filter
921
-	 * @param string|string[] $attr
922
-	 * @param int $limit
923
-	 * @param int $offset
924
-	 * @return false|int
925
-	 */
926
-	public function countUsers($filter, $attr = array('dn'), $limit = null, $offset = null) {
927
-		return $this->count($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
928
-	}
929
-
930
-	/**
931
-	 * executes an LDAP search, optimized for Groups
932
-	 * @param string $filter the LDAP filter for the search
933
-	 * @param string|string[] $attr optional, when a certain attribute shall be filtered out
934
-	 * @param integer $limit
935
-	 * @param integer $offset
936
-	 * @return array with the search result
937
-	 *
938
-	 * Executes an LDAP search
939
-	 */
940
-	public function searchGroups($filter, $attr = null, $limit = null, $offset = null) {
941
-		return $this->search($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
942
-	}
943
-
944
-	/**
945
-	 * returns the number of available groups
946
-	 * @param string $filter the LDAP search filter
947
-	 * @param string[] $attr optional
948
-	 * @param int|null $limit
949
-	 * @param int|null $offset
950
-	 * @return int|bool
951
-	 */
952
-	public function countGroups($filter, $attr = array('dn'), $limit = null, $offset = null) {
953
-		return $this->count($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
954
-	}
955
-
956
-	/**
957
-	 * returns the number of available objects on the base DN
958
-	 *
959
-	 * @param int|null $limit
960
-	 * @param int|null $offset
961
-	 * @return int|bool
962
-	 */
963
-	public function countObjects($limit = null, $offset = null) {
964
-		return $this->count('objectclass=*', $this->connection->ldapBase, array('dn'), $limit, $offset);
965
-	}
966
-
967
-	/**
968
-	 * Returns the LDAP handler
969
-	 * @throws \OC\ServerNotAvailableException
970
-	 */
971
-
972
-	/**
973
-	 * @return mixed
974
-	 * @throws \OC\ServerNotAvailableException
975
-	 */
976
-	private function invokeLDAPMethod() {
977
-		$arguments = func_get_args();
978
-		$command = array_shift($arguments);
979
-		$cr = array_shift($arguments);
980
-		if (!method_exists($this->ldap, $command)) {
981
-			return null;
982
-		}
983
-		array_unshift($arguments, $cr);
984
-		// php no longer supports call-time pass-by-reference
985
-		// thus cannot support controlPagedResultResponse as the third argument
986
-		// is a reference
987
-		$doMethod = function () use ($command, &$arguments) {
988
-			if ($command == 'controlPagedResultResponse') {
989
-				throw new \InvalidArgumentException('Invoker does not support controlPagedResultResponse, call LDAP Wrapper directly instead.');
990
-			} else {
991
-				return call_user_func_array(array($this->ldap, $command), $arguments);
992
-			}
993
-		};
994
-		try {
995
-			$ret = $doMethod();
996
-		} catch (ServerNotAvailableException $e) {
997
-			/* Server connection lost, attempt to reestablish it
338
+    /**
339
+     * Set password for an LDAP user identified by a DN
340
+     *
341
+     * @param string $userDN the user in question
342
+     * @param string $password the new password
343
+     * @return bool
344
+     * @throws HintException
345
+     * @throws \Exception
346
+     */
347
+    public function setPassword($userDN, $password) {
348
+        if(intval($this->connection->turnOnPasswordChange) !== 1) {
349
+            throw new \Exception('LDAP password changes are disabled.');
350
+        }
351
+        $cr = $this->connection->getConnectionResource();
352
+        if(!$this->ldap->isResource($cr)) {
353
+            //LDAP not available
354
+            \OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', \OCP\Util::DEBUG);
355
+            return false;
356
+        }
357
+        try {
358
+            return @$this->invokeLDAPMethod('modReplace', $cr, $userDN, $password);
359
+        } catch(ConstraintViolationException $e) {
360
+            throw new HintException('Password change rejected.', \OC::$server->getL10N('user_ldap')->t('Password change rejected. Hint: ').$e->getMessage(), $e->getCode());
361
+        }
362
+    }
363
+
364
+    /**
365
+     * checks whether the given attributes value is probably a DN
366
+     * @param string $attr the attribute in question
367
+     * @return boolean if so true, otherwise false
368
+     */
369
+    private function resemblesDN($attr) {
370
+        $resemblingAttributes = array(
371
+            'dn',
372
+            'uniquemember',
373
+            'member',
374
+            // memberOf is an "operational" attribute, without a definition in any RFC
375
+            'memberof'
376
+        );
377
+        return in_array($attr, $resemblingAttributes);
378
+    }
379
+
380
+    /**
381
+     * checks whether the given string is probably a DN
382
+     * @param string $string
383
+     * @return boolean
384
+     */
385
+    public function stringResemblesDN($string) {
386
+        $r = $this->ldap->explodeDN($string, 0);
387
+        // if exploding a DN succeeds and does not end up in
388
+        // an empty array except for $r[count] being 0.
389
+        return (is_array($r) && count($r) > 1);
390
+    }
391
+
392
+    /**
393
+     * returns a DN-string that is cleaned from not domain parts, e.g.
394
+     * cn=foo,cn=bar,dc=foobar,dc=server,dc=org
395
+     * becomes dc=foobar,dc=server,dc=org
396
+     * @param string $dn
397
+     * @return string
398
+     */
399
+    public function getDomainDNFromDN($dn) {
400
+        $allParts = $this->ldap->explodeDN($dn, 0);
401
+        if($allParts === false) {
402
+            //not a valid DN
403
+            return '';
404
+        }
405
+        $domainParts = array();
406
+        $dcFound = false;
407
+        foreach($allParts as $part) {
408
+            if(!$dcFound && strpos($part, 'dc=') === 0) {
409
+                $dcFound = true;
410
+            }
411
+            if($dcFound) {
412
+                $domainParts[] = $part;
413
+            }
414
+        }
415
+        $domainDN = implode(',', $domainParts);
416
+        return $domainDN;
417
+    }
418
+
419
+    /**
420
+     * returns the LDAP DN for the given internal Nextcloud name of the group
421
+     * @param string $name the Nextcloud name in question
422
+     * @return string|false LDAP DN on success, otherwise false
423
+     */
424
+    public function groupname2dn($name) {
425
+        return $this->groupMapper->getDNByName($name);
426
+    }
427
+
428
+    /**
429
+     * returns the LDAP DN for the given internal Nextcloud name of the user
430
+     * @param string $name the Nextcloud name in question
431
+     * @return string|false with the LDAP DN on success, otherwise false
432
+     */
433
+    public function username2dn($name) {
434
+        $fdn = $this->userMapper->getDNByName($name);
435
+
436
+        //Check whether the DN belongs to the Base, to avoid issues on multi-
437
+        //server setups
438
+        if(is_string($fdn) && $this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
439
+            return $fdn;
440
+        }
441
+
442
+        return false;
443
+    }
444
+
445
+    /**
446
+     * returns the internal Nextcloud name for the given LDAP DN of the group, false on DN outside of search DN or failure
447
+     * @param string $fdn the dn of the group object
448
+     * @param string $ldapName optional, the display name of the object
449
+     * @return string|false with the name to use in Nextcloud, false on DN outside of search DN
450
+     */
451
+    public function dn2groupname($fdn, $ldapName = null) {
452
+        //To avoid bypassing the base DN settings under certain circumstances
453
+        //with the group support, check whether the provided DN matches one of
454
+        //the given Bases
455
+        if(!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseGroups)) {
456
+            return false;
457
+        }
458
+
459
+        return $this->dn2ocname($fdn, $ldapName, false);
460
+    }
461
+
462
+    /**
463
+     * accepts an array of group DNs and tests whether they match the user
464
+     * filter by doing read operations against the group entries. Returns an
465
+     * array of DNs that match the filter.
466
+     *
467
+     * @param string[] $groupDNs
468
+     * @return string[]
469
+     */
470
+    public function groupsMatchFilter($groupDNs) {
471
+        $validGroupDNs = [];
472
+        foreach($groupDNs as $dn) {
473
+            $cacheKey = 'groupsMatchFilter-'.$dn;
474
+            $groupMatchFilter = $this->connection->getFromCache($cacheKey);
475
+            if(!is_null($groupMatchFilter)) {
476
+                if($groupMatchFilter) {
477
+                    $validGroupDNs[] = $dn;
478
+                }
479
+                continue;
480
+            }
481
+
482
+            // Check the base DN first. If this is not met already, we don't
483
+            // need to ask the server at all.
484
+            if(!$this->isDNPartOfBase($dn, $this->connection->ldapBaseGroups)) {
485
+                $this->connection->writeToCache($cacheKey, false);
486
+                continue;
487
+            }
488
+
489
+            $result = $this->readAttribute($dn, 'cn', $this->connection->ldapGroupFilter);
490
+            if(is_array($result)) {
491
+                $this->connection->writeToCache($cacheKey, true);
492
+                $validGroupDNs[] = $dn;
493
+            } else {
494
+                $this->connection->writeToCache($cacheKey, false);
495
+            }
496
+
497
+        }
498
+        return $validGroupDNs;
499
+    }
500
+
501
+    /**
502
+     * returns the internal Nextcloud name for the given LDAP DN of the user, false on DN outside of search DN or failure
503
+     * @param string $dn the dn of the user object
504
+     * @param string $ldapName optional, the display name of the object
505
+     * @return string|false with with the name to use in Nextcloud
506
+     */
507
+    public function dn2username($fdn, $ldapName = null) {
508
+        //To avoid bypassing the base DN settings under certain circumstances
509
+        //with the group support, check whether the provided DN matches one of
510
+        //the given Bases
511
+        if(!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
512
+            return false;
513
+        }
514
+
515
+        return $this->dn2ocname($fdn, $ldapName, true);
516
+    }
517
+
518
+    /**
519
+     * returns an internal Nextcloud name for the given LDAP DN, false on DN outside of search DN
520
+     *
521
+     * @param string $fdn the dn of the user object
522
+     * @param string $ldapName optional, the display name of the object
523
+     * @param bool $isUser optional, whether it is a user object (otherwise group assumed)
524
+     * @param bool|null $newlyMapped
525
+     * @param array|null $record
526
+     * @return false|string with with the name to use in Nextcloud
527
+     */
528
+    public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped = null, array $record = null) {
529
+        $newlyMapped = false;
530
+        if($isUser) {
531
+            $mapper = $this->getUserMapper();
532
+            $nameAttribute = $this->connection->ldapUserDisplayName;
533
+        } else {
534
+            $mapper = $this->getGroupMapper();
535
+            $nameAttribute = $this->connection->ldapGroupDisplayName;
536
+        }
537
+
538
+        //let's try to retrieve the Nextcloud name from the mappings table
539
+        $ncName = $mapper->getNameByDN($fdn);
540
+        if(is_string($ncName)) {
541
+            return $ncName;
542
+        }
543
+
544
+        //second try: get the UUID and check if it is known. Then, update the DN and return the name.
545
+        $uuid = $this->getUUID($fdn, $isUser, $record);
546
+        if(is_string($uuid)) {
547
+            $ncName = $mapper->getNameByUUID($uuid);
548
+            if(is_string($ncName)) {
549
+                $mapper->setDNbyUUID($fdn, $uuid);
550
+                return $ncName;
551
+            }
552
+        } else {
553
+            //If the UUID can't be detected something is foul.
554
+            \OCP\Util::writeLog('user_ldap', 'Cannot determine UUID for '.$fdn.'. Skipping.', \OCP\Util::INFO);
555
+            return false;
556
+        }
557
+
558
+        if(is_null($ldapName)) {
559
+            $ldapName = $this->readAttribute($fdn, $nameAttribute);
560
+            if(!isset($ldapName[0]) && empty($ldapName[0])) {
561
+                \OCP\Util::writeLog('user_ldap', 'No or empty name for '.$fdn.'.', \OCP\Util::INFO);
562
+                return false;
563
+            }
564
+            $ldapName = $ldapName[0];
565
+        }
566
+
567
+        if($isUser) {
568
+            $usernameAttribute = strval($this->connection->ldapExpertUsernameAttr);
569
+            if ($usernameAttribute !== '') {
570
+                $username = $this->readAttribute($fdn, $usernameAttribute);
571
+                $username = $username[0];
572
+            } else {
573
+                $username = $uuid;
574
+            }
575
+            $intName = $this->sanitizeUsername($username);
576
+        } else {
577
+            $intName = $ldapName;
578
+        }
579
+
580
+        //a new user/group! Add it only if it doesn't conflict with other backend's users or existing groups
581
+        //disabling Cache is required to avoid that the new user is cached as not-existing in fooExists check
582
+        //NOTE: mind, disabling cache affects only this instance! Using it
583
+        // outside of core user management will still cache the user as non-existing.
584
+        $originalTTL = $this->connection->ldapCacheTTL;
585
+        $this->connection->setConfiguration(array('ldapCacheTTL' => 0));
586
+        if(($isUser && !\OCP\User::userExists($intName))
587
+            || (!$isUser && !\OC::$server->getGroupManager()->groupExists($intName))) {
588
+            if($mapper->map($fdn, $intName, $uuid)) {
589
+                $this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
590
+                $newlyMapped = true;
591
+                return $intName;
592
+            }
593
+        }
594
+        $this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
595
+
596
+        $altName = $this->createAltInternalOwnCloudName($intName, $isUser);
597
+        if(is_string($altName) && $mapper->map($fdn, $altName, $uuid)) {
598
+            $newlyMapped = true;
599
+            return $altName;
600
+        }
601
+
602
+        //if everything else did not help..
603
+        \OCP\Util::writeLog('user_ldap', 'Could not create unique name for '.$fdn.'.', \OCP\Util::INFO);
604
+        return false;
605
+    }
606
+
607
+    /**
608
+     * gives back the user names as they are used ownClod internally
609
+     * @param array $ldapUsers as returned by fetchList()
610
+     * @return array an array with the user names to use in Nextcloud
611
+     *
612
+     * gives back the user names as they are used ownClod internally
613
+     */
614
+    public function nextcloudUserNames($ldapUsers) {
615
+        return $this->ldap2NextcloudNames($ldapUsers, true);
616
+    }
617
+
618
+    /**
619
+     * gives back the group names as they are used ownClod internally
620
+     * @param array $ldapGroups as returned by fetchList()
621
+     * @return array an array with the group names to use in Nextcloud
622
+     *
623
+     * gives back the group names as they are used ownClod internally
624
+     */
625
+    public function nextcloudGroupNames($ldapGroups) {
626
+        return $this->ldap2NextcloudNames($ldapGroups, false);
627
+    }
628
+
629
+    /**
630
+     * @param array $ldapObjects as returned by fetchList()
631
+     * @param bool $isUsers
632
+     * @return array
633
+     */
634
+    private function ldap2NextcloudNames($ldapObjects, $isUsers) {
635
+        if($isUsers) {
636
+            $nameAttribute = $this->connection->ldapUserDisplayName;
637
+            $sndAttribute  = $this->connection->ldapUserDisplayName2;
638
+        } else {
639
+            $nameAttribute = $this->connection->ldapGroupDisplayName;
640
+        }
641
+        $nextcloudNames = array();
642
+
643
+        foreach($ldapObjects as $ldapObject) {
644
+            $nameByLDAP = null;
645
+            if(    isset($ldapObject[$nameAttribute])
646
+                && is_array($ldapObject[$nameAttribute])
647
+                && isset($ldapObject[$nameAttribute][0])
648
+            ) {
649
+                // might be set, but not necessarily. if so, we use it.
650
+                $nameByLDAP = $ldapObject[$nameAttribute][0];
651
+            }
652
+
653
+            $ncName = $this->dn2ocname($ldapObject['dn'][0], $nameByLDAP, $isUsers);
654
+            if($ncName) {
655
+                $nextcloudNames[] = $ncName;
656
+                if($isUsers) {
657
+                    //cache the user names so it does not need to be retrieved
658
+                    //again later (e.g. sharing dialogue).
659
+                    if(is_null($nameByLDAP)) {
660
+                        continue;
661
+                    }
662
+                    $sndName = isset($ldapObject[$sndAttribute][0])
663
+                        ? $ldapObject[$sndAttribute][0] : '';
664
+                    $this->cacheUserDisplayName($ncName, $nameByLDAP, $sndName);
665
+                }
666
+            }
667
+        }
668
+        return $nextcloudNames;
669
+    }
670
+
671
+    /**
672
+     * caches the user display name
673
+     * @param string $ocName the internal Nextcloud username
674
+     * @param string|false $home the home directory path
675
+     */
676
+    public function cacheUserHome($ocName, $home) {
677
+        $cacheKey = 'getHome'.$ocName;
678
+        $this->connection->writeToCache($cacheKey, $home);
679
+    }
680
+
681
+    /**
682
+     * caches a user as existing
683
+     * @param string $ocName the internal Nextcloud username
684
+     */
685
+    public function cacheUserExists($ocName) {
686
+        $this->connection->writeToCache('userExists'.$ocName, true);
687
+    }
688
+
689
+    /**
690
+     * caches the user display name
691
+     * @param string $ocName the internal Nextcloud username
692
+     * @param string $displayName the display name
693
+     * @param string $displayName2 the second display name
694
+     */
695
+    public function cacheUserDisplayName($ocName, $displayName, $displayName2 = '') {
696
+        $user = $this->userManager->get($ocName);
697
+        if($user === null) {
698
+            return;
699
+        }
700
+        $displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
701
+        $cacheKeyTrunk = 'getDisplayName';
702
+        $this->connection->writeToCache($cacheKeyTrunk.$ocName, $displayName);
703
+    }
704
+
705
+    /**
706
+     * creates a unique name for internal Nextcloud use for users. Don't call it directly.
707
+     * @param string $name the display name of the object
708
+     * @return string|false with with the name to use in Nextcloud or false if unsuccessful
709
+     *
710
+     * Instead of using this method directly, call
711
+     * createAltInternalOwnCloudName($name, true)
712
+     */
713
+    private function _createAltInternalOwnCloudNameForUsers($name) {
714
+        $attempts = 0;
715
+        //while loop is just a precaution. If a name is not generated within
716
+        //20 attempts, something else is very wrong. Avoids infinite loop.
717
+        while($attempts < 20){
718
+            $altName = $name . '_' . rand(1000,9999);
719
+            if(!\OCP\User::userExists($altName)) {
720
+                return $altName;
721
+            }
722
+            $attempts++;
723
+        }
724
+        return false;
725
+    }
726
+
727
+    /**
728
+     * creates a unique name for internal Nextcloud use for groups. Don't call it directly.
729
+     * @param string $name the display name of the object
730
+     * @return string|false with with the name to use in Nextcloud or false if unsuccessful.
731
+     *
732
+     * Instead of using this method directly, call
733
+     * createAltInternalOwnCloudName($name, false)
734
+     *
735
+     * Group names are also used as display names, so we do a sequential
736
+     * numbering, e.g. Developers_42 when there are 41 other groups called
737
+     * "Developers"
738
+     */
739
+    private function _createAltInternalOwnCloudNameForGroups($name) {
740
+        $usedNames = $this->groupMapper->getNamesBySearch($name, "", '_%');
741
+        if(!($usedNames) || count($usedNames) === 0) {
742
+            $lastNo = 1; //will become name_2
743
+        } else {
744
+            natsort($usedNames);
745
+            $lastName = array_pop($usedNames);
746
+            $lastNo = intval(substr($lastName, strrpos($lastName, '_') + 1));
747
+        }
748
+        $altName = $name.'_'.strval($lastNo+1);
749
+        unset($usedNames);
750
+
751
+        $attempts = 1;
752
+        while($attempts < 21){
753
+            // Check to be really sure it is unique
754
+            // while loop is just a precaution. If a name is not generated within
755
+            // 20 attempts, something else is very wrong. Avoids infinite loop.
756
+            if(!\OC::$server->getGroupManager()->groupExists($altName)) {
757
+                return $altName;
758
+            }
759
+            $altName = $name . '_' . ($lastNo + $attempts);
760
+            $attempts++;
761
+        }
762
+        return false;
763
+    }
764
+
765
+    /**
766
+     * creates a unique name for internal Nextcloud use.
767
+     * @param string $name the display name of the object
768
+     * @param boolean $isUser whether name should be created for a user (true) or a group (false)
769
+     * @return string|false with with the name to use in Nextcloud or false if unsuccessful
770
+     */
771
+    private function createAltInternalOwnCloudName($name, $isUser) {
772
+        $originalTTL = $this->connection->ldapCacheTTL;
773
+        $this->connection->setConfiguration(array('ldapCacheTTL' => 0));
774
+        if($isUser) {
775
+            $altName = $this->_createAltInternalOwnCloudNameForUsers($name);
776
+        } else {
777
+            $altName = $this->_createAltInternalOwnCloudNameForGroups($name);
778
+        }
779
+        $this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
780
+
781
+        return $altName;
782
+    }
783
+
784
+    /**
785
+     * fetches a list of users according to a provided loginName and utilizing
786
+     * the login filter.
787
+     *
788
+     * @param string $loginName
789
+     * @param array $attributes optional, list of attributes to read
790
+     * @return array
791
+     */
792
+    public function fetchUsersByLoginName($loginName, $attributes = array('dn')) {
793
+        $loginName = $this->escapeFilterPart($loginName);
794
+        $filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
795
+        $users = $this->fetchListOfUsers($filter, $attributes);
796
+        return $users;
797
+    }
798
+
799
+    /**
800
+     * counts the number of users according to a provided loginName and
801
+     * utilizing the login filter.
802
+     *
803
+     * @param string $loginName
804
+     * @return int
805
+     */
806
+    public function countUsersByLoginName($loginName) {
807
+        $loginName = $this->escapeFilterPart($loginName);
808
+        $filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
809
+        $users = $this->countUsers($filter);
810
+        return $users;
811
+    }
812
+
813
+    /**
814
+     * @param string $filter
815
+     * @param string|string[] $attr
816
+     * @param int $limit
817
+     * @param int $offset
818
+     * @param bool $forceApplyAttributes
819
+     * @return array
820
+     */
821
+    public function fetchListOfUsers($filter, $attr, $limit = null, $offset = null, $forceApplyAttributes = false) {
822
+        $ldapRecords = $this->searchUsers($filter, $attr, $limit, $offset);
823
+        $recordsToUpdate = $ldapRecords;
824
+        if(!$forceApplyAttributes) {
825
+            $isBackgroundJobModeAjax = $this->c->getConfig()
826
+                    ->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'ajax';
827
+            $recordsToUpdate = array_filter($ldapRecords, function($record) use ($isBackgroundJobModeAjax) {
828
+                $newlyMapped = false;
829
+                $uid = $this->dn2ocname($record['dn'][0], null, true, $newlyMapped, $record);
830
+                return ($uid !== false) && ($newlyMapped || $isBackgroundJobModeAjax);
831
+            });
832
+        }
833
+        $this->batchApplyUserAttributes($recordsToUpdate);
834
+        return $this->fetchList($ldapRecords, (count($attr) > 1));
835
+    }
836
+
837
+    /**
838
+     * provided with an array of LDAP user records the method will fetch the
839
+     * user object and requests it to process the freshly fetched attributes and
840
+     * and their values
841
+     * @param array $ldapRecords
842
+     */
843
+    public function batchApplyUserAttributes(array $ldapRecords){
844
+        $displayNameAttribute = strtolower($this->connection->ldapUserDisplayName);
845
+        foreach($ldapRecords as $userRecord) {
846
+            if(!isset($userRecord[$displayNameAttribute])) {
847
+                // displayName is obligatory
848
+                continue;
849
+            }
850
+            $ocName  = $this->dn2ocname($userRecord['dn'][0], null, true);
851
+            if($ocName === false) {
852
+                continue;
853
+            }
854
+            $this->cacheUserExists($ocName);
855
+            $user = $this->userManager->get($ocName);
856
+            if($user instanceof OfflineUser) {
857
+                $user->unmark();
858
+                $user = $this->userManager->get($ocName);
859
+            }
860
+            if ($user !== null) {
861
+                $user->processAttributes($userRecord);
862
+            } else {
863
+                \OC::$server->getLogger()->debug(
864
+                    "The ldap user manager returned null for $ocName",
865
+                    ['app'=>'user_ldap']
866
+                );
867
+            }
868
+        }
869
+    }
870
+
871
+    /**
872
+     * @param string $filter
873
+     * @param string|string[] $attr
874
+     * @param int $limit
875
+     * @param int $offset
876
+     * @return array
877
+     */
878
+    public function fetchListOfGroups($filter, $attr, $limit = null, $offset = null) {
879
+        return $this->fetchList($this->searchGroups($filter, $attr, $limit, $offset), (count($attr) > 1));
880
+    }
881
+
882
+    /**
883
+     * @param array $list
884
+     * @param bool $manyAttributes
885
+     * @return array
886
+     */
887
+    private function fetchList($list, $manyAttributes) {
888
+        if(is_array($list)) {
889
+            if($manyAttributes) {
890
+                return $list;
891
+            } else {
892
+                $list = array_reduce($list, function($carry, $item) {
893
+                    $attribute = array_keys($item)[0];
894
+                    $carry[] = $item[$attribute][0];
895
+                    return $carry;
896
+                }, array());
897
+                return array_unique($list, SORT_LOCALE_STRING);
898
+            }
899
+        }
900
+
901
+        //error cause actually, maybe throw an exception in future.
902
+        return array();
903
+    }
904
+
905
+    /**
906
+     * executes an LDAP search, optimized for Users
907
+     * @param string $filter the LDAP filter for the search
908
+     * @param string|string[] $attr optional, when a certain attribute shall be filtered out
909
+     * @param integer $limit
910
+     * @param integer $offset
911
+     * @return array with the search result
912
+     *
913
+     * Executes an LDAP search
914
+     */
915
+    public function searchUsers($filter, $attr = null, $limit = null, $offset = null) {
916
+        return $this->search($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
917
+    }
918
+
919
+    /**
920
+     * @param string $filter
921
+     * @param string|string[] $attr
922
+     * @param int $limit
923
+     * @param int $offset
924
+     * @return false|int
925
+     */
926
+    public function countUsers($filter, $attr = array('dn'), $limit = null, $offset = null) {
927
+        return $this->count($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
928
+    }
929
+
930
+    /**
931
+     * executes an LDAP search, optimized for Groups
932
+     * @param string $filter the LDAP filter for the search
933
+     * @param string|string[] $attr optional, when a certain attribute shall be filtered out
934
+     * @param integer $limit
935
+     * @param integer $offset
936
+     * @return array with the search result
937
+     *
938
+     * Executes an LDAP search
939
+     */
940
+    public function searchGroups($filter, $attr = null, $limit = null, $offset = null) {
941
+        return $this->search($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
942
+    }
943
+
944
+    /**
945
+     * returns the number of available groups
946
+     * @param string $filter the LDAP search filter
947
+     * @param string[] $attr optional
948
+     * @param int|null $limit
949
+     * @param int|null $offset
950
+     * @return int|bool
951
+     */
952
+    public function countGroups($filter, $attr = array('dn'), $limit = null, $offset = null) {
953
+        return $this->count($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
954
+    }
955
+
956
+    /**
957
+     * returns the number of available objects on the base DN
958
+     *
959
+     * @param int|null $limit
960
+     * @param int|null $offset
961
+     * @return int|bool
962
+     */
963
+    public function countObjects($limit = null, $offset = null) {
964
+        return $this->count('objectclass=*', $this->connection->ldapBase, array('dn'), $limit, $offset);
965
+    }
966
+
967
+    /**
968
+     * Returns the LDAP handler
969
+     * @throws \OC\ServerNotAvailableException
970
+     */
971
+
972
+    /**
973
+     * @return mixed
974
+     * @throws \OC\ServerNotAvailableException
975
+     */
976
+    private function invokeLDAPMethod() {
977
+        $arguments = func_get_args();
978
+        $command = array_shift($arguments);
979
+        $cr = array_shift($arguments);
980
+        if (!method_exists($this->ldap, $command)) {
981
+            return null;
982
+        }
983
+        array_unshift($arguments, $cr);
984
+        // php no longer supports call-time pass-by-reference
985
+        // thus cannot support controlPagedResultResponse as the third argument
986
+        // is a reference
987
+        $doMethod = function () use ($command, &$arguments) {
988
+            if ($command == 'controlPagedResultResponse') {
989
+                throw new \InvalidArgumentException('Invoker does not support controlPagedResultResponse, call LDAP Wrapper directly instead.');
990
+            } else {
991
+                return call_user_func_array(array($this->ldap, $command), $arguments);
992
+            }
993
+        };
994
+        try {
995
+            $ret = $doMethod();
996
+        } catch (ServerNotAvailableException $e) {
997
+            /* Server connection lost, attempt to reestablish it
998 998
 			 * Maybe implement exponential backoff?
999 999
 			 * This was enough to get solr indexer working which has large delays between LDAP fetches.
1000 1000
 			 */
1001
-			\OCP\Util::writeLog('user_ldap', "Connection lost on $command, attempting to reestablish.", \OCP\Util::DEBUG);
1002
-			$this->connection->resetConnectionResource();
1003
-			$cr = $this->connection->getConnectionResource();
1004
-
1005
-			if(!$this->ldap->isResource($cr)) {
1006
-				// Seems like we didn't find any resource.
1007
-				\OCP\Util::writeLog('user_ldap', "Could not $command, because resource is missing.", \OCP\Util::DEBUG);
1008
-				throw $e;
1009
-			}
1010
-
1011
-			$arguments[0] = array_pad([], count($arguments[0]), $cr);
1012
-			$ret = $doMethod();
1013
-		}
1014
-		return $ret;
1015
-	}
1016
-
1017
-	/**
1018
-	 * retrieved. Results will according to the order in the array.
1019
-	 * @param int $limit optional, maximum results to be counted
1020
-	 * @param int $offset optional, a starting point
1021
-	 * @return array|false array with the search result as first value and pagedSearchOK as
1022
-	 * second | false if not successful
1023
-	 * @throws \OC\ServerNotAvailableException
1024
-	 */
1025
-	private function executeSearch($filter, $base, &$attr = null, $limit = null, $offset = null) {
1026
-		if(!is_null($attr) && !is_array($attr)) {
1027
-			$attr = array(mb_strtolower($attr, 'UTF-8'));
1028
-		}
1029
-
1030
-		// See if we have a resource, in case not cancel with message
1031
-		$cr = $this->connection->getConnectionResource();
1032
-		if(!$this->ldap->isResource($cr)) {
1033
-			// Seems like we didn't find any resource.
1034
-			// Return an empty array just like before.
1035
-			\OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', \OCP\Util::DEBUG);
1036
-			return false;
1037
-		}
1038
-
1039
-		//check whether paged search should be attempted
1040
-		$pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, intval($limit), $offset);
1041
-
1042
-		$linkResources = array_pad(array(), count($base), $cr);
1043
-		$sr = $this->invokeLDAPMethod('search', $linkResources, $base, $filter, $attr);
1044
-		// cannot use $cr anymore, might have changed in the previous call!
1045
-		$error = $this->ldap->errno($this->connection->getConnectionResource());
1046
-		if(!is_array($sr) || $error !== 0) {
1047
-			\OCP\Util::writeLog('user_ldap', 'Attempt for Paging?  '.print_r($pagedSearchOK, true), \OCP\Util::ERROR);
1048
-			return false;
1049
-		}
1050
-
1051
-		return array($sr, $pagedSearchOK);
1052
-	}
1053
-
1054
-	/**
1055
-	 * processes an LDAP paged search operation
1056
-	 * @param array $sr the array containing the LDAP search resources
1057
-	 * @param string $filter the LDAP filter for the search
1058
-	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
1059
-	 * @param int $iFoundItems number of results in the single search operation
1060
-	 * @param int $limit maximum results to be counted
1061
-	 * @param int $offset a starting point
1062
-	 * @param bool $pagedSearchOK whether a paged search has been executed
1063
-	 * @param bool $skipHandling required for paged search when cookies to
1064
-	 * prior results need to be gained
1065
-	 * @return bool cookie validity, true if we have more pages, false otherwise.
1066
-	 */
1067
-	private function processPagedSearchStatus($sr, $filter, $base, $iFoundItems, $limit, $offset, $pagedSearchOK, $skipHandling) {
1068
-		$cookie = null;
1069
-		if($pagedSearchOK) {
1070
-			$cr = $this->connection->getConnectionResource();
1071
-			foreach($sr as $key => $res) {
1072
-				if($this->ldap->controlPagedResultResponse($cr, $res, $cookie)) {
1073
-					$this->setPagedResultCookie($base[$key], $filter, $limit, $offset, $cookie);
1074
-				}
1075
-			}
1076
-
1077
-			//browsing through prior pages to get the cookie for the new one
1078
-			if($skipHandling) {
1079
-				return false;
1080
-			}
1081
-			// if count is bigger, then the server does not support
1082
-			// paged search. Instead, he did a normal search. We set a
1083
-			// flag here, so the callee knows how to deal with it.
1084
-			if($iFoundItems <= $limit) {
1085
-				$this->pagedSearchedSuccessful = true;
1086
-			}
1087
-		} else {
1088
-			if(!is_null($limit)) {
1089
-				\OCP\Util::writeLog('user_ldap', 'Paged search was not available', \OCP\Util::INFO);
1090
-			}
1091
-		}
1092
-		/* ++ Fixing RHDS searches with pages with zero results ++
1001
+            \OCP\Util::writeLog('user_ldap', "Connection lost on $command, attempting to reestablish.", \OCP\Util::DEBUG);
1002
+            $this->connection->resetConnectionResource();
1003
+            $cr = $this->connection->getConnectionResource();
1004
+
1005
+            if(!$this->ldap->isResource($cr)) {
1006
+                // Seems like we didn't find any resource.
1007
+                \OCP\Util::writeLog('user_ldap', "Could not $command, because resource is missing.", \OCP\Util::DEBUG);
1008
+                throw $e;
1009
+            }
1010
+
1011
+            $arguments[0] = array_pad([], count($arguments[0]), $cr);
1012
+            $ret = $doMethod();
1013
+        }
1014
+        return $ret;
1015
+    }
1016
+
1017
+    /**
1018
+     * retrieved. Results will according to the order in the array.
1019
+     * @param int $limit optional, maximum results to be counted
1020
+     * @param int $offset optional, a starting point
1021
+     * @return array|false array with the search result as first value and pagedSearchOK as
1022
+     * second | false if not successful
1023
+     * @throws \OC\ServerNotAvailableException
1024
+     */
1025
+    private function executeSearch($filter, $base, &$attr = null, $limit = null, $offset = null) {
1026
+        if(!is_null($attr) && !is_array($attr)) {
1027
+            $attr = array(mb_strtolower($attr, 'UTF-8'));
1028
+        }
1029
+
1030
+        // See if we have a resource, in case not cancel with message
1031
+        $cr = $this->connection->getConnectionResource();
1032
+        if(!$this->ldap->isResource($cr)) {
1033
+            // Seems like we didn't find any resource.
1034
+            // Return an empty array just like before.
1035
+            \OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', \OCP\Util::DEBUG);
1036
+            return false;
1037
+        }
1038
+
1039
+        //check whether paged search should be attempted
1040
+        $pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, intval($limit), $offset);
1041
+
1042
+        $linkResources = array_pad(array(), count($base), $cr);
1043
+        $sr = $this->invokeLDAPMethod('search', $linkResources, $base, $filter, $attr);
1044
+        // cannot use $cr anymore, might have changed in the previous call!
1045
+        $error = $this->ldap->errno($this->connection->getConnectionResource());
1046
+        if(!is_array($sr) || $error !== 0) {
1047
+            \OCP\Util::writeLog('user_ldap', 'Attempt for Paging?  '.print_r($pagedSearchOK, true), \OCP\Util::ERROR);
1048
+            return false;
1049
+        }
1050
+
1051
+        return array($sr, $pagedSearchOK);
1052
+    }
1053
+
1054
+    /**
1055
+     * processes an LDAP paged search operation
1056
+     * @param array $sr the array containing the LDAP search resources
1057
+     * @param string $filter the LDAP filter for the search
1058
+     * @param array $base an array containing the LDAP subtree(s) that shall be searched
1059
+     * @param int $iFoundItems number of results in the single search operation
1060
+     * @param int $limit maximum results to be counted
1061
+     * @param int $offset a starting point
1062
+     * @param bool $pagedSearchOK whether a paged search has been executed
1063
+     * @param bool $skipHandling required for paged search when cookies to
1064
+     * prior results need to be gained
1065
+     * @return bool cookie validity, true if we have more pages, false otherwise.
1066
+     */
1067
+    private function processPagedSearchStatus($sr, $filter, $base, $iFoundItems, $limit, $offset, $pagedSearchOK, $skipHandling) {
1068
+        $cookie = null;
1069
+        if($pagedSearchOK) {
1070
+            $cr = $this->connection->getConnectionResource();
1071
+            foreach($sr as $key => $res) {
1072
+                if($this->ldap->controlPagedResultResponse($cr, $res, $cookie)) {
1073
+                    $this->setPagedResultCookie($base[$key], $filter, $limit, $offset, $cookie);
1074
+                }
1075
+            }
1076
+
1077
+            //browsing through prior pages to get the cookie for the new one
1078
+            if($skipHandling) {
1079
+                return false;
1080
+            }
1081
+            // if count is bigger, then the server does not support
1082
+            // paged search. Instead, he did a normal search. We set a
1083
+            // flag here, so the callee knows how to deal with it.
1084
+            if($iFoundItems <= $limit) {
1085
+                $this->pagedSearchedSuccessful = true;
1086
+            }
1087
+        } else {
1088
+            if(!is_null($limit)) {
1089
+                \OCP\Util::writeLog('user_ldap', 'Paged search was not available', \OCP\Util::INFO);
1090
+            }
1091
+        }
1092
+        /* ++ Fixing RHDS searches with pages with zero results ++
1093 1093
 		 * Return cookie status. If we don't have more pages, with RHDS
1094 1094
 		 * cookie is null, with openldap cookie is an empty string and
1095 1095
 		 * to 386ds '0' is a valid cookie. Even if $iFoundItems == 0
1096 1096
 		 */
1097
-		return !empty($cookie) || $cookie === '0';
1098
-	}
1099
-
1100
-	/**
1101
-	 * executes an LDAP search, but counts the results only
1102
-	 * @param string $filter the LDAP filter for the search
1103
-	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
1104
-	 * @param string|string[] $attr optional, array, one or more attributes that shall be
1105
-	 * retrieved. Results will according to the order in the array.
1106
-	 * @param int $limit optional, maximum results to be counted
1107
-	 * @param int $offset optional, a starting point
1108
-	 * @param bool $skipHandling indicates whether the pages search operation is
1109
-	 * completed
1110
-	 * @return int|false Integer or false if the search could not be initialized
1111
-	 *
1112
-	 */
1113
-	private function count($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
1114
-		\OCP\Util::writeLog('user_ldap', 'Count filter:  '.print_r($filter, true), \OCP\Util::DEBUG);
1115
-
1116
-		$limitPerPage = intval($this->connection->ldapPagingSize);
1117
-		if(!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1118
-			$limitPerPage = $limit;
1119
-		}
1120
-
1121
-		$counter = 0;
1122
-		$count = null;
1123
-		$this->connection->getConnectionResource();
1124
-
1125
-		do {
1126
-			$search = $this->executeSearch($filter, $base, $attr,
1127
-										   $limitPerPage, $offset);
1128
-			if($search === false) {
1129
-				return $counter > 0 ? $counter : false;
1130
-			}
1131
-			list($sr, $pagedSearchOK) = $search;
1132
-
1133
-			/* ++ Fixing RHDS searches with pages with zero results ++
1097
+        return !empty($cookie) || $cookie === '0';
1098
+    }
1099
+
1100
+    /**
1101
+     * executes an LDAP search, but counts the results only
1102
+     * @param string $filter the LDAP filter for the search
1103
+     * @param array $base an array containing the LDAP subtree(s) that shall be searched
1104
+     * @param string|string[] $attr optional, array, one or more attributes that shall be
1105
+     * retrieved. Results will according to the order in the array.
1106
+     * @param int $limit optional, maximum results to be counted
1107
+     * @param int $offset optional, a starting point
1108
+     * @param bool $skipHandling indicates whether the pages search operation is
1109
+     * completed
1110
+     * @return int|false Integer or false if the search could not be initialized
1111
+     *
1112
+     */
1113
+    private function count($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
1114
+        \OCP\Util::writeLog('user_ldap', 'Count filter:  '.print_r($filter, true), \OCP\Util::DEBUG);
1115
+
1116
+        $limitPerPage = intval($this->connection->ldapPagingSize);
1117
+        if(!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1118
+            $limitPerPage = $limit;
1119
+        }
1120
+
1121
+        $counter = 0;
1122
+        $count = null;
1123
+        $this->connection->getConnectionResource();
1124
+
1125
+        do {
1126
+            $search = $this->executeSearch($filter, $base, $attr,
1127
+                                            $limitPerPage, $offset);
1128
+            if($search === false) {
1129
+                return $counter > 0 ? $counter : false;
1130
+            }
1131
+            list($sr, $pagedSearchOK) = $search;
1132
+
1133
+            /* ++ Fixing RHDS searches with pages with zero results ++
1134 1134
 			 * countEntriesInSearchResults() method signature changed
1135 1135
 			 * by removing $limit and &$hasHitLimit parameters
1136 1136
 			 */
1137
-			$count = $this->countEntriesInSearchResults($sr);
1138
-			$counter += $count;
1137
+            $count = $this->countEntriesInSearchResults($sr);
1138
+            $counter += $count;
1139 1139
 
1140
-			$hasMorePages = $this->processPagedSearchStatus($sr, $filter, $base, $count, $limitPerPage,
1141
-										$offset, $pagedSearchOK, $skipHandling);
1142
-			$offset += $limitPerPage;
1143
-			/* ++ Fixing RHDS searches with pages with zero results ++
1140
+            $hasMorePages = $this->processPagedSearchStatus($sr, $filter, $base, $count, $limitPerPage,
1141
+                                        $offset, $pagedSearchOK, $skipHandling);
1142
+            $offset += $limitPerPage;
1143
+            /* ++ Fixing RHDS searches with pages with zero results ++
1144 1144
 			 * Continue now depends on $hasMorePages value
1145 1145
 			 */
1146
-			$continue = $pagedSearchOK && $hasMorePages;
1147
-		} while($continue && (is_null($limit) || $limit <= 0 || $limit > $counter));
1148
-
1149
-		return $counter;
1150
-	}
1151
-
1152
-	/**
1153
-	 * @param array $searchResults
1154
-	 * @return int
1155
-	 */
1156
-	private function countEntriesInSearchResults($searchResults) {
1157
-		$counter = 0;
1158
-
1159
-		foreach($searchResults as $res) {
1160
-			$count = intval($this->invokeLDAPMethod('countEntries', $this->connection->getConnectionResource(), $res));
1161
-			$counter += $count;
1162
-		}
1163
-
1164
-		return $counter;
1165
-	}
1166
-
1167
-	/**
1168
-	 * Executes an LDAP search
1169
-	 * @param string $filter the LDAP filter for the search
1170
-	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
1171
-	 * @param string|string[] $attr optional, array, one or more attributes that shall be
1172
-	 * @param int $limit
1173
-	 * @param int $offset
1174
-	 * @param bool $skipHandling
1175
-	 * @return array with the search result
1176
-	 */
1177
-	public function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
1178
-		$limitPerPage = intval($this->connection->ldapPagingSize);
1179
-		if(!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1180
-			$limitPerPage = $limit;
1181
-		}
1182
-
1183
-		/* ++ Fixing RHDS searches with pages with zero results ++
1146
+            $continue = $pagedSearchOK && $hasMorePages;
1147
+        } while($continue && (is_null($limit) || $limit <= 0 || $limit > $counter));
1148
+
1149
+        return $counter;
1150
+    }
1151
+
1152
+    /**
1153
+     * @param array $searchResults
1154
+     * @return int
1155
+     */
1156
+    private function countEntriesInSearchResults($searchResults) {
1157
+        $counter = 0;
1158
+
1159
+        foreach($searchResults as $res) {
1160
+            $count = intval($this->invokeLDAPMethod('countEntries', $this->connection->getConnectionResource(), $res));
1161
+            $counter += $count;
1162
+        }
1163
+
1164
+        return $counter;
1165
+    }
1166
+
1167
+    /**
1168
+     * Executes an LDAP search
1169
+     * @param string $filter the LDAP filter for the search
1170
+     * @param array $base an array containing the LDAP subtree(s) that shall be searched
1171
+     * @param string|string[] $attr optional, array, one or more attributes that shall be
1172
+     * @param int $limit
1173
+     * @param int $offset
1174
+     * @param bool $skipHandling
1175
+     * @return array with the search result
1176
+     */
1177
+    public function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
1178
+        $limitPerPage = intval($this->connection->ldapPagingSize);
1179
+        if(!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1180
+            $limitPerPage = $limit;
1181
+        }
1182
+
1183
+        /* ++ Fixing RHDS searches with pages with zero results ++
1184 1184
 		 * As we can have pages with zero results and/or pages with less
1185 1185
 		 * than $limit results but with a still valid server 'cookie',
1186 1186
 		 * loops through until we get $continue equals true and
1187 1187
 		 * $findings['count'] < $limit
1188 1188
 		 */
1189
-		$findings = array();
1190
-		$savedoffset = $offset;
1191
-		do {
1192
-			$search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
1193
-			if($search === false) {
1194
-				return array();
1195
-			}
1196
-			list($sr, $pagedSearchOK) = $search;
1197
-			$cr = $this->connection->getConnectionResource();
1198
-
1199
-			if($skipHandling) {
1200
-				//i.e. result do not need to be fetched, we just need the cookie
1201
-				//thus pass 1 or any other value as $iFoundItems because it is not
1202
-				//used
1203
-				$this->processPagedSearchStatus($sr, $filter, $base, 1, $limitPerPage,
1204
-								$offset, $pagedSearchOK,
1205
-								$skipHandling);
1206
-				return array();
1207
-			}
1208
-
1209
-			$iFoundItems = 0;
1210
-			foreach($sr as $res) {
1211
-				$findings = array_merge($findings, $this->invokeLDAPMethod('getEntries', $cr, $res));
1212
-				$iFoundItems = max($iFoundItems, $findings['count']);
1213
-				unset($findings['count']);
1214
-			}
1215
-
1216
-			$continue = $this->processPagedSearchStatus($sr, $filter, $base, $iFoundItems,
1217
-				$limitPerPage, $offset, $pagedSearchOK,
1218
-										$skipHandling);
1219
-			$offset += $limitPerPage;
1220
-		} while ($continue && $pagedSearchOK && ($limit === null || count($findings) < $limit));
1221
-		// reseting offset
1222
-		$offset = $savedoffset;
1223
-
1224
-		// if we're here, probably no connection resource is returned.
1225
-		// to make Nextcloud behave nicely, we simply give back an empty array.
1226
-		if(is_null($findings)) {
1227
-			return array();
1228
-		}
1229
-
1230
-		if(!is_null($attr)) {
1231
-			$selection = array();
1232
-			$i = 0;
1233
-			foreach($findings as $item) {
1234
-				if(!is_array($item)) {
1235
-					continue;
1236
-				}
1237
-				$item = \OCP\Util::mb_array_change_key_case($item, MB_CASE_LOWER, 'UTF-8');
1238
-				foreach($attr as $key) {
1239
-					$key = mb_strtolower($key, 'UTF-8');
1240
-					if(isset($item[$key])) {
1241
-						if(is_array($item[$key]) && isset($item[$key]['count'])) {
1242
-							unset($item[$key]['count']);
1243
-						}
1244
-						if($key !== 'dn') {
1245
-							$selection[$i][$key] = $this->resemblesDN($key) ?
1246
-								$this->helper->sanitizeDN($item[$key])
1247
-								: $key === 'objectguid' || $key === 'guid' ?
1248
-									$selection[$i][$key] = $this->convertObjectGUID2Str($item[$key])
1249
-									: $item[$key];
1250
-						} else {
1251
-							$selection[$i][$key] = [$this->helper->sanitizeDN($item[$key])];
1252
-						}
1253
-					}
1254
-
1255
-				}
1256
-				$i++;
1257
-			}
1258
-			$findings = $selection;
1259
-		}
1260
-		//we slice the findings, when
1261
-		//a) paged search unsuccessful, though attempted
1262
-		//b) no paged search, but limit set
1263
-		if((!$this->getPagedSearchResultState()
1264
-			&& $pagedSearchOK)
1265
-			|| (
1266
-				!$pagedSearchOK
1267
-				&& !is_null($limit)
1268
-			)
1269
-		) {
1270
-			$findings = array_slice($findings, intval($offset), $limit);
1271
-		}
1272
-		return $findings;
1273
-	}
1274
-
1275
-	/**
1276
-	 * @param string $name
1277
-	 * @return bool|mixed|string
1278
-	 */
1279
-	public function sanitizeUsername($name) {
1280
-		if($this->connection->ldapIgnoreNamingRules) {
1281
-			return $name;
1282
-		}
1283
-
1284
-		// Transliteration
1285
-		// latin characters to ASCII
1286
-		$name = iconv('UTF-8', 'ASCII//TRANSLIT', $name);
1287
-
1288
-		// Replacements
1289
-		$name = str_replace(' ', '_', $name);
1290
-
1291
-		// Every remaining disallowed characters will be removed
1292
-		$name = preg_replace('/[^a-zA-Z0-9_.@-]/u', '', $name);
1293
-
1294
-		return $name;
1295
-	}
1296
-
1297
-	/**
1298
-	* escapes (user provided) parts for LDAP filter
1299
-	* @param string $input, the provided value
1300
-	* @param bool $allowAsterisk whether in * at the beginning should be preserved
1301
-	* @return string the escaped string
1302
-	*/
1303
-	public function escapeFilterPart($input, $allowAsterisk = false) {
1304
-		$asterisk = '';
1305
-		if($allowAsterisk && strlen($input) > 0 && $input[0] === '*') {
1306
-			$asterisk = '*';
1307
-			$input = mb_substr($input, 1, null, 'UTF-8');
1308
-		}
1309
-		$search  = array('*', '\\', '(', ')');
1310
-		$replace = array('\\*', '\\\\', '\\(', '\\)');
1311
-		return $asterisk . str_replace($search, $replace, $input);
1312
-	}
1313
-
1314
-	/**
1315
-	 * combines the input filters with AND
1316
-	 * @param string[] $filters the filters to connect
1317
-	 * @return string the combined filter
1318
-	 */
1319
-	public function combineFilterWithAnd($filters) {
1320
-		return $this->combineFilter($filters, '&');
1321
-	}
1322
-
1323
-	/**
1324
-	 * combines the input filters with OR
1325
-	 * @param string[] $filters the filters to connect
1326
-	 * @return string the combined filter
1327
-	 * Combines Filter arguments with OR
1328
-	 */
1329
-	public function combineFilterWithOr($filters) {
1330
-		return $this->combineFilter($filters, '|');
1331
-	}
1332
-
1333
-	/**
1334
-	 * combines the input filters with given operator
1335
-	 * @param string[] $filters the filters to connect
1336
-	 * @param string $operator either & or |
1337
-	 * @return string the combined filter
1338
-	 */
1339
-	private function combineFilter($filters, $operator) {
1340
-		$combinedFilter = '('.$operator;
1341
-		foreach($filters as $filter) {
1342
-			if ($filter !== '' && $filter[0] !== '(') {
1343
-				$filter = '('.$filter.')';
1344
-			}
1345
-			$combinedFilter.=$filter;
1346
-		}
1347
-		$combinedFilter.=')';
1348
-		return $combinedFilter;
1349
-	}
1350
-
1351
-	/**
1352
-	 * creates a filter part for to perform search for users
1353
-	 * @param string $search the search term
1354
-	 * @return string the final filter part to use in LDAP searches
1355
-	 */
1356
-	public function getFilterPartForUserSearch($search) {
1357
-		return $this->getFilterPartForSearch($search,
1358
-			$this->connection->ldapAttributesForUserSearch,
1359
-			$this->connection->ldapUserDisplayName);
1360
-	}
1361
-
1362
-	/**
1363
-	 * creates a filter part for to perform search for groups
1364
-	 * @param string $search the search term
1365
-	 * @return string the final filter part to use in LDAP searches
1366
-	 */
1367
-	public function getFilterPartForGroupSearch($search) {
1368
-		return $this->getFilterPartForSearch($search,
1369
-			$this->connection->ldapAttributesForGroupSearch,
1370
-			$this->connection->ldapGroupDisplayName);
1371
-	}
1372
-
1373
-	/**
1374
-	 * creates a filter part for searches by splitting up the given search
1375
-	 * string into single words
1376
-	 * @param string $search the search term
1377
-	 * @param string[] $searchAttributes needs to have at least two attributes,
1378
-	 * otherwise it does not make sense :)
1379
-	 * @return string the final filter part to use in LDAP searches
1380
-	 * @throws \Exception
1381
-	 */
1382
-	private function getAdvancedFilterPartForSearch($search, $searchAttributes) {
1383
-		if(!is_array($searchAttributes) || count($searchAttributes) < 2) {
1384
-			throw new \Exception('searchAttributes must be an array with at least two string');
1385
-		}
1386
-		$searchWords = explode(' ', trim($search));
1387
-		$wordFilters = array();
1388
-		foreach($searchWords as $word) {
1389
-			$word = $this->prepareSearchTerm($word);
1390
-			//every word needs to appear at least once
1391
-			$wordMatchOneAttrFilters = array();
1392
-			foreach($searchAttributes as $attr) {
1393
-				$wordMatchOneAttrFilters[] = $attr . '=' . $word;
1394
-			}
1395
-			$wordFilters[] = $this->combineFilterWithOr($wordMatchOneAttrFilters);
1396
-		}
1397
-		return $this->combineFilterWithAnd($wordFilters);
1398
-	}
1399
-
1400
-	/**
1401
-	 * creates a filter part for searches
1402
-	 * @param string $search the search term
1403
-	 * @param string[]|null $searchAttributes
1404
-	 * @param string $fallbackAttribute a fallback attribute in case the user
1405
-	 * did not define search attributes. Typically the display name attribute.
1406
-	 * @return string the final filter part to use in LDAP searches
1407
-	 */
1408
-	private function getFilterPartForSearch($search, $searchAttributes, $fallbackAttribute) {
1409
-		$filter = array();
1410
-		$haveMultiSearchAttributes = (is_array($searchAttributes) && count($searchAttributes) > 0);
1411
-		if($haveMultiSearchAttributes && strpos(trim($search), ' ') !== false) {
1412
-			try {
1413
-				return $this->getAdvancedFilterPartForSearch($search, $searchAttributes);
1414
-			} catch(\Exception $e) {
1415
-				\OCP\Util::writeLog(
1416
-					'user_ldap',
1417
-					'Creating advanced filter for search failed, falling back to simple method.',
1418
-					\OCP\Util::INFO
1419
-				);
1420
-			}
1421
-		}
1422
-
1423
-		$search = $this->prepareSearchTerm($search);
1424
-		if(!is_array($searchAttributes) || count($searchAttributes) === 0) {
1425
-			if ($fallbackAttribute === '') {
1426
-				return '';
1427
-			}
1428
-			$filter[] = $fallbackAttribute . '=' . $search;
1429
-		} else {
1430
-			foreach($searchAttributes as $attribute) {
1431
-				$filter[] = $attribute . '=' . $search;
1432
-			}
1433
-		}
1434
-		if(count($filter) === 1) {
1435
-			return '('.$filter[0].')';
1436
-		}
1437
-		return $this->combineFilterWithOr($filter);
1438
-	}
1439
-
1440
-	/**
1441
-	 * returns the search term depending on whether we are allowed
1442
-	 * list users found by ldap with the current input appended by
1443
-	 * a *
1444
-	 * @return string
1445
-	 */
1446
-	private function prepareSearchTerm($term) {
1447
-		$config = \OC::$server->getConfig();
1448
-
1449
-		$allowEnum = $config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes');
1450
-
1451
-		$result = $term;
1452
-		if ($term === '') {
1453
-			$result = '*';
1454
-		} else if ($allowEnum !== 'no') {
1455
-			$result = $term . '*';
1456
-		}
1457
-		return $result;
1458
-	}
1459
-
1460
-	/**
1461
-	 * returns the filter used for counting users
1462
-	 * @return string
1463
-	 */
1464
-	public function getFilterForUserCount() {
1465
-		$filter = $this->combineFilterWithAnd(array(
1466
-			$this->connection->ldapUserFilter,
1467
-			$this->connection->ldapUserDisplayName . '=*'
1468
-		));
1469
-
1470
-		return $filter;
1471
-	}
1472
-
1473
-	/**
1474
-	 * @param string $name
1475
-	 * @param string $password
1476
-	 * @return bool
1477
-	 */
1478
-	public function areCredentialsValid($name, $password) {
1479
-		$name = $this->helper->DNasBaseParameter($name);
1480
-		$testConnection = clone $this->connection;
1481
-		$credentials = array(
1482
-			'ldapAgentName' => $name,
1483
-			'ldapAgentPassword' => $password
1484
-		);
1485
-		if(!$testConnection->setConfiguration($credentials)) {
1486
-			return false;
1487
-		}
1488
-		return $testConnection->bind();
1489
-	}
1490
-
1491
-	/**
1492
-	 * reverse lookup of a DN given a known UUID
1493
-	 *
1494
-	 * @param string $uuid
1495
-	 * @return string
1496
-	 * @throws \Exception
1497
-	 */
1498
-	public function getUserDnByUuid($uuid) {
1499
-		$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1500
-		$filter       = $this->connection->ldapUserFilter;
1501
-		$base         = $this->connection->ldapBaseUsers;
1502
-
1503
-		if ($this->connection->ldapUuidUserAttribute === 'auto' && $uuidOverride === '') {
1504
-			// Sacrebleu! The UUID attribute is unknown :( We need first an
1505
-			// existing DN to be able to reliably detect it.
1506
-			$result = $this->search($filter, $base, ['dn'], 1);
1507
-			if(!isset($result[0]) || !isset($result[0]['dn'])) {
1508
-				throw new \Exception('Cannot determine UUID attribute');
1509
-			}
1510
-			$dn = $result[0]['dn'][0];
1511
-			if(!$this->detectUuidAttribute($dn, true)) {
1512
-				throw new \Exception('Cannot determine UUID attribute');
1513
-			}
1514
-		} else {
1515
-			// The UUID attribute is either known or an override is given.
1516
-			// By calling this method we ensure that $this->connection->$uuidAttr
1517
-			// is definitely set
1518
-			if(!$this->detectUuidAttribute('', true)) {
1519
-				throw new \Exception('Cannot determine UUID attribute');
1520
-			}
1521
-		}
1522
-
1523
-		$uuidAttr = $this->connection->ldapUuidUserAttribute;
1524
-		if($uuidAttr === 'guid' || $uuidAttr === 'objectguid') {
1525
-			$uuid = $this->formatGuid2ForFilterUser($uuid);
1526
-		}
1527
-
1528
-		$filter = $uuidAttr . '=' . $uuid;
1529
-		$result = $this->searchUsers($filter, ['dn'], 2);
1530
-		if(is_array($result) && isset($result[0]) && isset($result[0]['dn']) && count($result) === 1) {
1531
-			// we put the count into account to make sure that this is
1532
-			// really unique
1533
-			return $result[0]['dn'][0];
1534
-		}
1535
-
1536
-		throw new \Exception('Cannot determine UUID attribute');
1537
-	}
1538
-
1539
-	/**
1540
-	 * auto-detects the directory's UUID attribute
1541
-	 *
1542
-	 * @param string $dn a known DN used to check against
1543
-	 * @param bool $isUser
1544
-	 * @param bool $force the detection should be run, even if it is not set to auto
1545
-	 * @param array|null $ldapRecord
1546
-	 * @return bool true on success, false otherwise
1547
-	 */
1548
-	private function detectUuidAttribute($dn, $isUser = true, $force = false, array $ldapRecord = null) {
1549
-		if($isUser) {
1550
-			$uuidAttr     = 'ldapUuidUserAttribute';
1551
-			$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1552
-		} else {
1553
-			$uuidAttr     = 'ldapUuidGroupAttribute';
1554
-			$uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
1555
-		}
1556
-
1557
-		if(($this->connection->$uuidAttr !== 'auto') && !$force) {
1558
-			return true;
1559
-		}
1560
-
1561
-		if (is_string($uuidOverride) && trim($uuidOverride) !== '' && !$force) {
1562
-			$this->connection->$uuidAttr = $uuidOverride;
1563
-			return true;
1564
-		}
1565
-
1566
-		foreach(self::UUID_ATTRIBUTES as $attribute) {
1567
-			if($ldapRecord !== null) {
1568
-				// we have the info from LDAP already, we don't need to talk to the server again
1569
-				if(isset($ldapRecord[$attribute])) {
1570
-					$this->connection->$uuidAttr = $attribute;
1571
-					return true;
1572
-				} else {
1573
-					continue;
1574
-				}
1575
-			}
1576
-
1577
-			$value = $this->readAttribute($dn, $attribute);
1578
-			if(is_array($value) && isset($value[0]) && !empty($value[0])) {
1579
-				\OCP\Util::writeLog('user_ldap',
1580
-									'Setting '.$attribute.' as '.$uuidAttr,
1581
-									\OCP\Util::DEBUG);
1582
-				$this->connection->$uuidAttr = $attribute;
1583
-				return true;
1584
-			}
1585
-		}
1586
-		\OCP\Util::writeLog('user_ldap',
1587
-							'Could not autodetect the UUID attribute',
1588
-							\OCP\Util::ERROR);
1589
-
1590
-		return false;
1591
-	}
1592
-
1593
-	/**
1594
-	 * @param string $dn
1595
-	 * @param bool $isUser
1596
-	 * @param null $ldapRecord
1597
-	 * @return bool|string
1598
-	 */
1599
-	public function getUUID($dn, $isUser = true, $ldapRecord = null) {
1600
-		if($isUser) {
1601
-			$uuidAttr     = 'ldapUuidUserAttribute';
1602
-			$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1603
-		} else {
1604
-			$uuidAttr     = 'ldapUuidGroupAttribute';
1605
-			$uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
1606
-		}
1607
-
1608
-		$uuid = false;
1609
-		if($this->detectUuidAttribute($dn, $isUser, false, $ldapRecord)) {
1610
-			$attr = $this->connection->$uuidAttr;
1611
-			$uuid = isset($ldapRecord[$attr]) ? $ldapRecord[$attr] : $this->readAttribute($dn, $attr);
1612
-			if( !is_array($uuid)
1613
-				&& $uuidOverride !== ''
1614
-				&& $this->detectUuidAttribute($dn, $isUser, true, $ldapRecord))
1615
-			{
1616
-				$uuid = isset($ldapRecord[$this->connection->$uuidAttr])
1617
-					? $ldapRecord[$this->connection->$uuidAttr]
1618
-					: $this->readAttribute($dn, $this->connection->$uuidAttr);
1619
-			}
1620
-			if(is_array($uuid) && isset($uuid[0]) && !empty($uuid[0])) {
1621
-				$uuid = $uuid[0];
1622
-			}
1623
-		}
1624
-
1625
-		return $uuid;
1626
-	}
1627
-
1628
-	/**
1629
-	 * converts a binary ObjectGUID into a string representation
1630
-	 * @param string $oguid the ObjectGUID in it's binary form as retrieved from AD
1631
-	 * @return string
1632
-	 * @link http://www.php.net/manual/en/function.ldap-get-values-len.php#73198
1633
-	 */
1634
-	private function convertObjectGUID2Str($oguid) {
1635
-		$hex_guid = bin2hex($oguid);
1636
-		$hex_guid_to_guid_str = '';
1637
-		for($k = 1; $k <= 4; ++$k) {
1638
-			$hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2);
1639
-		}
1640
-		$hex_guid_to_guid_str .= '-';
1641
-		for($k = 1; $k <= 2; ++$k) {
1642
-			$hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2);
1643
-		}
1644
-		$hex_guid_to_guid_str .= '-';
1645
-		for($k = 1; $k <= 2; ++$k) {
1646
-			$hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
1647
-		}
1648
-		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
1649
-		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
1650
-
1651
-		return strtoupper($hex_guid_to_guid_str);
1652
-	}
1653
-
1654
-	/**
1655
-	 * the first three blocks of the string-converted GUID happen to be in
1656
-	 * reverse order. In order to use it in a filter, this needs to be
1657
-	 * corrected. Furthermore the dashes need to be replaced and \\ preprended
1658
-	 * to every two hax figures.
1659
-	 *
1660
-	 * If an invalid string is passed, it will be returned without change.
1661
-	 *
1662
-	 * @param string $guid
1663
-	 * @return string
1664
-	 */
1665
-	public function formatGuid2ForFilterUser($guid) {
1666
-		if(!is_string($guid)) {
1667
-			throw new \InvalidArgumentException('String expected');
1668
-		}
1669
-		$blocks = explode('-', $guid);
1670
-		if(count($blocks) !== 5) {
1671
-			/*
1189
+        $findings = array();
1190
+        $savedoffset = $offset;
1191
+        do {
1192
+            $search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
1193
+            if($search === false) {
1194
+                return array();
1195
+            }
1196
+            list($sr, $pagedSearchOK) = $search;
1197
+            $cr = $this->connection->getConnectionResource();
1198
+
1199
+            if($skipHandling) {
1200
+                //i.e. result do not need to be fetched, we just need the cookie
1201
+                //thus pass 1 or any other value as $iFoundItems because it is not
1202
+                //used
1203
+                $this->processPagedSearchStatus($sr, $filter, $base, 1, $limitPerPage,
1204
+                                $offset, $pagedSearchOK,
1205
+                                $skipHandling);
1206
+                return array();
1207
+            }
1208
+
1209
+            $iFoundItems = 0;
1210
+            foreach($sr as $res) {
1211
+                $findings = array_merge($findings, $this->invokeLDAPMethod('getEntries', $cr, $res));
1212
+                $iFoundItems = max($iFoundItems, $findings['count']);
1213
+                unset($findings['count']);
1214
+            }
1215
+
1216
+            $continue = $this->processPagedSearchStatus($sr, $filter, $base, $iFoundItems,
1217
+                $limitPerPage, $offset, $pagedSearchOK,
1218
+                                        $skipHandling);
1219
+            $offset += $limitPerPage;
1220
+        } while ($continue && $pagedSearchOK && ($limit === null || count($findings) < $limit));
1221
+        // reseting offset
1222
+        $offset = $savedoffset;
1223
+
1224
+        // if we're here, probably no connection resource is returned.
1225
+        // to make Nextcloud behave nicely, we simply give back an empty array.
1226
+        if(is_null($findings)) {
1227
+            return array();
1228
+        }
1229
+
1230
+        if(!is_null($attr)) {
1231
+            $selection = array();
1232
+            $i = 0;
1233
+            foreach($findings as $item) {
1234
+                if(!is_array($item)) {
1235
+                    continue;
1236
+                }
1237
+                $item = \OCP\Util::mb_array_change_key_case($item, MB_CASE_LOWER, 'UTF-8');
1238
+                foreach($attr as $key) {
1239
+                    $key = mb_strtolower($key, 'UTF-8');
1240
+                    if(isset($item[$key])) {
1241
+                        if(is_array($item[$key]) && isset($item[$key]['count'])) {
1242
+                            unset($item[$key]['count']);
1243
+                        }
1244
+                        if($key !== 'dn') {
1245
+                            $selection[$i][$key] = $this->resemblesDN($key) ?
1246
+                                $this->helper->sanitizeDN($item[$key])
1247
+                                : $key === 'objectguid' || $key === 'guid' ?
1248
+                                    $selection[$i][$key] = $this->convertObjectGUID2Str($item[$key])
1249
+                                    : $item[$key];
1250
+                        } else {
1251
+                            $selection[$i][$key] = [$this->helper->sanitizeDN($item[$key])];
1252
+                        }
1253
+                    }
1254
+
1255
+                }
1256
+                $i++;
1257
+            }
1258
+            $findings = $selection;
1259
+        }
1260
+        //we slice the findings, when
1261
+        //a) paged search unsuccessful, though attempted
1262
+        //b) no paged search, but limit set
1263
+        if((!$this->getPagedSearchResultState()
1264
+            && $pagedSearchOK)
1265
+            || (
1266
+                !$pagedSearchOK
1267
+                && !is_null($limit)
1268
+            )
1269
+        ) {
1270
+            $findings = array_slice($findings, intval($offset), $limit);
1271
+        }
1272
+        return $findings;
1273
+    }
1274
+
1275
+    /**
1276
+     * @param string $name
1277
+     * @return bool|mixed|string
1278
+     */
1279
+    public function sanitizeUsername($name) {
1280
+        if($this->connection->ldapIgnoreNamingRules) {
1281
+            return $name;
1282
+        }
1283
+
1284
+        // Transliteration
1285
+        // latin characters to ASCII
1286
+        $name = iconv('UTF-8', 'ASCII//TRANSLIT', $name);
1287
+
1288
+        // Replacements
1289
+        $name = str_replace(' ', '_', $name);
1290
+
1291
+        // Every remaining disallowed characters will be removed
1292
+        $name = preg_replace('/[^a-zA-Z0-9_.@-]/u', '', $name);
1293
+
1294
+        return $name;
1295
+    }
1296
+
1297
+    /**
1298
+     * escapes (user provided) parts for LDAP filter
1299
+     * @param string $input, the provided value
1300
+     * @param bool $allowAsterisk whether in * at the beginning should be preserved
1301
+     * @return string the escaped string
1302
+     */
1303
+    public function escapeFilterPart($input, $allowAsterisk = false) {
1304
+        $asterisk = '';
1305
+        if($allowAsterisk && strlen($input) > 0 && $input[0] === '*') {
1306
+            $asterisk = '*';
1307
+            $input = mb_substr($input, 1, null, 'UTF-8');
1308
+        }
1309
+        $search  = array('*', '\\', '(', ')');
1310
+        $replace = array('\\*', '\\\\', '\\(', '\\)');
1311
+        return $asterisk . str_replace($search, $replace, $input);
1312
+    }
1313
+
1314
+    /**
1315
+     * combines the input filters with AND
1316
+     * @param string[] $filters the filters to connect
1317
+     * @return string the combined filter
1318
+     */
1319
+    public function combineFilterWithAnd($filters) {
1320
+        return $this->combineFilter($filters, '&');
1321
+    }
1322
+
1323
+    /**
1324
+     * combines the input filters with OR
1325
+     * @param string[] $filters the filters to connect
1326
+     * @return string the combined filter
1327
+     * Combines Filter arguments with OR
1328
+     */
1329
+    public function combineFilterWithOr($filters) {
1330
+        return $this->combineFilter($filters, '|');
1331
+    }
1332
+
1333
+    /**
1334
+     * combines the input filters with given operator
1335
+     * @param string[] $filters the filters to connect
1336
+     * @param string $operator either & or |
1337
+     * @return string the combined filter
1338
+     */
1339
+    private function combineFilter($filters, $operator) {
1340
+        $combinedFilter = '('.$operator;
1341
+        foreach($filters as $filter) {
1342
+            if ($filter !== '' && $filter[0] !== '(') {
1343
+                $filter = '('.$filter.')';
1344
+            }
1345
+            $combinedFilter.=$filter;
1346
+        }
1347
+        $combinedFilter.=')';
1348
+        return $combinedFilter;
1349
+    }
1350
+
1351
+    /**
1352
+     * creates a filter part for to perform search for users
1353
+     * @param string $search the search term
1354
+     * @return string the final filter part to use in LDAP searches
1355
+     */
1356
+    public function getFilterPartForUserSearch($search) {
1357
+        return $this->getFilterPartForSearch($search,
1358
+            $this->connection->ldapAttributesForUserSearch,
1359
+            $this->connection->ldapUserDisplayName);
1360
+    }
1361
+
1362
+    /**
1363
+     * creates a filter part for to perform search for groups
1364
+     * @param string $search the search term
1365
+     * @return string the final filter part to use in LDAP searches
1366
+     */
1367
+    public function getFilterPartForGroupSearch($search) {
1368
+        return $this->getFilterPartForSearch($search,
1369
+            $this->connection->ldapAttributesForGroupSearch,
1370
+            $this->connection->ldapGroupDisplayName);
1371
+    }
1372
+
1373
+    /**
1374
+     * creates a filter part for searches by splitting up the given search
1375
+     * string into single words
1376
+     * @param string $search the search term
1377
+     * @param string[] $searchAttributes needs to have at least two attributes,
1378
+     * otherwise it does not make sense :)
1379
+     * @return string the final filter part to use in LDAP searches
1380
+     * @throws \Exception
1381
+     */
1382
+    private function getAdvancedFilterPartForSearch($search, $searchAttributes) {
1383
+        if(!is_array($searchAttributes) || count($searchAttributes) < 2) {
1384
+            throw new \Exception('searchAttributes must be an array with at least two string');
1385
+        }
1386
+        $searchWords = explode(' ', trim($search));
1387
+        $wordFilters = array();
1388
+        foreach($searchWords as $word) {
1389
+            $word = $this->prepareSearchTerm($word);
1390
+            //every word needs to appear at least once
1391
+            $wordMatchOneAttrFilters = array();
1392
+            foreach($searchAttributes as $attr) {
1393
+                $wordMatchOneAttrFilters[] = $attr . '=' . $word;
1394
+            }
1395
+            $wordFilters[] = $this->combineFilterWithOr($wordMatchOneAttrFilters);
1396
+        }
1397
+        return $this->combineFilterWithAnd($wordFilters);
1398
+    }
1399
+
1400
+    /**
1401
+     * creates a filter part for searches
1402
+     * @param string $search the search term
1403
+     * @param string[]|null $searchAttributes
1404
+     * @param string $fallbackAttribute a fallback attribute in case the user
1405
+     * did not define search attributes. Typically the display name attribute.
1406
+     * @return string the final filter part to use in LDAP searches
1407
+     */
1408
+    private function getFilterPartForSearch($search, $searchAttributes, $fallbackAttribute) {
1409
+        $filter = array();
1410
+        $haveMultiSearchAttributes = (is_array($searchAttributes) && count($searchAttributes) > 0);
1411
+        if($haveMultiSearchAttributes && strpos(trim($search), ' ') !== false) {
1412
+            try {
1413
+                return $this->getAdvancedFilterPartForSearch($search, $searchAttributes);
1414
+            } catch(\Exception $e) {
1415
+                \OCP\Util::writeLog(
1416
+                    'user_ldap',
1417
+                    'Creating advanced filter for search failed, falling back to simple method.',
1418
+                    \OCP\Util::INFO
1419
+                );
1420
+            }
1421
+        }
1422
+
1423
+        $search = $this->prepareSearchTerm($search);
1424
+        if(!is_array($searchAttributes) || count($searchAttributes) === 0) {
1425
+            if ($fallbackAttribute === '') {
1426
+                return '';
1427
+            }
1428
+            $filter[] = $fallbackAttribute . '=' . $search;
1429
+        } else {
1430
+            foreach($searchAttributes as $attribute) {
1431
+                $filter[] = $attribute . '=' . $search;
1432
+            }
1433
+        }
1434
+        if(count($filter) === 1) {
1435
+            return '('.$filter[0].')';
1436
+        }
1437
+        return $this->combineFilterWithOr($filter);
1438
+    }
1439
+
1440
+    /**
1441
+     * returns the search term depending on whether we are allowed
1442
+     * list users found by ldap with the current input appended by
1443
+     * a *
1444
+     * @return string
1445
+     */
1446
+    private function prepareSearchTerm($term) {
1447
+        $config = \OC::$server->getConfig();
1448
+
1449
+        $allowEnum = $config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes');
1450
+
1451
+        $result = $term;
1452
+        if ($term === '') {
1453
+            $result = '*';
1454
+        } else if ($allowEnum !== 'no') {
1455
+            $result = $term . '*';
1456
+        }
1457
+        return $result;
1458
+    }
1459
+
1460
+    /**
1461
+     * returns the filter used for counting users
1462
+     * @return string
1463
+     */
1464
+    public function getFilterForUserCount() {
1465
+        $filter = $this->combineFilterWithAnd(array(
1466
+            $this->connection->ldapUserFilter,
1467
+            $this->connection->ldapUserDisplayName . '=*'
1468
+        ));
1469
+
1470
+        return $filter;
1471
+    }
1472
+
1473
+    /**
1474
+     * @param string $name
1475
+     * @param string $password
1476
+     * @return bool
1477
+     */
1478
+    public function areCredentialsValid($name, $password) {
1479
+        $name = $this->helper->DNasBaseParameter($name);
1480
+        $testConnection = clone $this->connection;
1481
+        $credentials = array(
1482
+            'ldapAgentName' => $name,
1483
+            'ldapAgentPassword' => $password
1484
+        );
1485
+        if(!$testConnection->setConfiguration($credentials)) {
1486
+            return false;
1487
+        }
1488
+        return $testConnection->bind();
1489
+    }
1490
+
1491
+    /**
1492
+     * reverse lookup of a DN given a known UUID
1493
+     *
1494
+     * @param string $uuid
1495
+     * @return string
1496
+     * @throws \Exception
1497
+     */
1498
+    public function getUserDnByUuid($uuid) {
1499
+        $uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1500
+        $filter       = $this->connection->ldapUserFilter;
1501
+        $base         = $this->connection->ldapBaseUsers;
1502
+
1503
+        if ($this->connection->ldapUuidUserAttribute === 'auto' && $uuidOverride === '') {
1504
+            // Sacrebleu! The UUID attribute is unknown :( We need first an
1505
+            // existing DN to be able to reliably detect it.
1506
+            $result = $this->search($filter, $base, ['dn'], 1);
1507
+            if(!isset($result[0]) || !isset($result[0]['dn'])) {
1508
+                throw new \Exception('Cannot determine UUID attribute');
1509
+            }
1510
+            $dn = $result[0]['dn'][0];
1511
+            if(!$this->detectUuidAttribute($dn, true)) {
1512
+                throw new \Exception('Cannot determine UUID attribute');
1513
+            }
1514
+        } else {
1515
+            // The UUID attribute is either known or an override is given.
1516
+            // By calling this method we ensure that $this->connection->$uuidAttr
1517
+            // is definitely set
1518
+            if(!$this->detectUuidAttribute('', true)) {
1519
+                throw new \Exception('Cannot determine UUID attribute');
1520
+            }
1521
+        }
1522
+
1523
+        $uuidAttr = $this->connection->ldapUuidUserAttribute;
1524
+        if($uuidAttr === 'guid' || $uuidAttr === 'objectguid') {
1525
+            $uuid = $this->formatGuid2ForFilterUser($uuid);
1526
+        }
1527
+
1528
+        $filter = $uuidAttr . '=' . $uuid;
1529
+        $result = $this->searchUsers($filter, ['dn'], 2);
1530
+        if(is_array($result) && isset($result[0]) && isset($result[0]['dn']) && count($result) === 1) {
1531
+            // we put the count into account to make sure that this is
1532
+            // really unique
1533
+            return $result[0]['dn'][0];
1534
+        }
1535
+
1536
+        throw new \Exception('Cannot determine UUID attribute');
1537
+    }
1538
+
1539
+    /**
1540
+     * auto-detects the directory's UUID attribute
1541
+     *
1542
+     * @param string $dn a known DN used to check against
1543
+     * @param bool $isUser
1544
+     * @param bool $force the detection should be run, even if it is not set to auto
1545
+     * @param array|null $ldapRecord
1546
+     * @return bool true on success, false otherwise
1547
+     */
1548
+    private function detectUuidAttribute($dn, $isUser = true, $force = false, array $ldapRecord = null) {
1549
+        if($isUser) {
1550
+            $uuidAttr     = 'ldapUuidUserAttribute';
1551
+            $uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1552
+        } else {
1553
+            $uuidAttr     = 'ldapUuidGroupAttribute';
1554
+            $uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
1555
+        }
1556
+
1557
+        if(($this->connection->$uuidAttr !== 'auto') && !$force) {
1558
+            return true;
1559
+        }
1560
+
1561
+        if (is_string($uuidOverride) && trim($uuidOverride) !== '' && !$force) {
1562
+            $this->connection->$uuidAttr = $uuidOverride;
1563
+            return true;
1564
+        }
1565
+
1566
+        foreach(self::UUID_ATTRIBUTES as $attribute) {
1567
+            if($ldapRecord !== null) {
1568
+                // we have the info from LDAP already, we don't need to talk to the server again
1569
+                if(isset($ldapRecord[$attribute])) {
1570
+                    $this->connection->$uuidAttr = $attribute;
1571
+                    return true;
1572
+                } else {
1573
+                    continue;
1574
+                }
1575
+            }
1576
+
1577
+            $value = $this->readAttribute($dn, $attribute);
1578
+            if(is_array($value) && isset($value[0]) && !empty($value[0])) {
1579
+                \OCP\Util::writeLog('user_ldap',
1580
+                                    'Setting '.$attribute.' as '.$uuidAttr,
1581
+                                    \OCP\Util::DEBUG);
1582
+                $this->connection->$uuidAttr = $attribute;
1583
+                return true;
1584
+            }
1585
+        }
1586
+        \OCP\Util::writeLog('user_ldap',
1587
+                            'Could not autodetect the UUID attribute',
1588
+                            \OCP\Util::ERROR);
1589
+
1590
+        return false;
1591
+    }
1592
+
1593
+    /**
1594
+     * @param string $dn
1595
+     * @param bool $isUser
1596
+     * @param null $ldapRecord
1597
+     * @return bool|string
1598
+     */
1599
+    public function getUUID($dn, $isUser = true, $ldapRecord = null) {
1600
+        if($isUser) {
1601
+            $uuidAttr     = 'ldapUuidUserAttribute';
1602
+            $uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1603
+        } else {
1604
+            $uuidAttr     = 'ldapUuidGroupAttribute';
1605
+            $uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
1606
+        }
1607
+
1608
+        $uuid = false;
1609
+        if($this->detectUuidAttribute($dn, $isUser, false, $ldapRecord)) {
1610
+            $attr = $this->connection->$uuidAttr;
1611
+            $uuid = isset($ldapRecord[$attr]) ? $ldapRecord[$attr] : $this->readAttribute($dn, $attr);
1612
+            if( !is_array($uuid)
1613
+                && $uuidOverride !== ''
1614
+                && $this->detectUuidAttribute($dn, $isUser, true, $ldapRecord))
1615
+            {
1616
+                $uuid = isset($ldapRecord[$this->connection->$uuidAttr])
1617
+                    ? $ldapRecord[$this->connection->$uuidAttr]
1618
+                    : $this->readAttribute($dn, $this->connection->$uuidAttr);
1619
+            }
1620
+            if(is_array($uuid) && isset($uuid[0]) && !empty($uuid[0])) {
1621
+                $uuid = $uuid[0];
1622
+            }
1623
+        }
1624
+
1625
+        return $uuid;
1626
+    }
1627
+
1628
+    /**
1629
+     * converts a binary ObjectGUID into a string representation
1630
+     * @param string $oguid the ObjectGUID in it's binary form as retrieved from AD
1631
+     * @return string
1632
+     * @link http://www.php.net/manual/en/function.ldap-get-values-len.php#73198
1633
+     */
1634
+    private function convertObjectGUID2Str($oguid) {
1635
+        $hex_guid = bin2hex($oguid);
1636
+        $hex_guid_to_guid_str = '';
1637
+        for($k = 1; $k <= 4; ++$k) {
1638
+            $hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2);
1639
+        }
1640
+        $hex_guid_to_guid_str .= '-';
1641
+        for($k = 1; $k <= 2; ++$k) {
1642
+            $hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2);
1643
+        }
1644
+        $hex_guid_to_guid_str .= '-';
1645
+        for($k = 1; $k <= 2; ++$k) {
1646
+            $hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
1647
+        }
1648
+        $hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
1649
+        $hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
1650
+
1651
+        return strtoupper($hex_guid_to_guid_str);
1652
+    }
1653
+
1654
+    /**
1655
+     * the first three blocks of the string-converted GUID happen to be in
1656
+     * reverse order. In order to use it in a filter, this needs to be
1657
+     * corrected. Furthermore the dashes need to be replaced and \\ preprended
1658
+     * to every two hax figures.
1659
+     *
1660
+     * If an invalid string is passed, it will be returned without change.
1661
+     *
1662
+     * @param string $guid
1663
+     * @return string
1664
+     */
1665
+    public function formatGuid2ForFilterUser($guid) {
1666
+        if(!is_string($guid)) {
1667
+            throw new \InvalidArgumentException('String expected');
1668
+        }
1669
+        $blocks = explode('-', $guid);
1670
+        if(count($blocks) !== 5) {
1671
+            /*
1672 1672
 			 * Why not throw an Exception instead? This method is a utility
1673 1673
 			 * called only when trying to figure out whether a "missing" known
1674 1674
 			 * LDAP user was or was not renamed on the LDAP server. And this
@@ -1679,274 +1679,274 @@  discard block
 block discarded – undo
1679 1679
 			 * an exception here would kill the experience for a valid, acting
1680 1680
 			 * user. Instead we write a log message.
1681 1681
 			 */
1682
-			\OC::$server->getLogger()->info(
1683
-				'Passed string does not resemble a valid GUID. Known UUID ' .
1684
-				'({uuid}) probably does not match UUID configuration.',
1685
-				[ 'app' => 'user_ldap', 'uuid' => $guid ]
1686
-			);
1687
-			return $guid;
1688
-		}
1689
-		for($i=0; $i < 3; $i++) {
1690
-			$pairs = str_split($blocks[$i], 2);
1691
-			$pairs = array_reverse($pairs);
1692
-			$blocks[$i] = implode('', $pairs);
1693
-		}
1694
-		for($i=0; $i < 5; $i++) {
1695
-			$pairs = str_split($blocks[$i], 2);
1696
-			$blocks[$i] = '\\' . implode('\\', $pairs);
1697
-		}
1698
-		return implode('', $blocks);
1699
-	}
1700
-
1701
-	/**
1702
-	 * gets a SID of the domain of the given dn
1703
-	 * @param string $dn
1704
-	 * @return string|bool
1705
-	 */
1706
-	public function getSID($dn) {
1707
-		$domainDN = $this->getDomainDNFromDN($dn);
1708
-		$cacheKey = 'getSID-'.$domainDN;
1709
-		$sid = $this->connection->getFromCache($cacheKey);
1710
-		if(!is_null($sid)) {
1711
-			return $sid;
1712
-		}
1713
-
1714
-		$objectSid = $this->readAttribute($domainDN, 'objectsid');
1715
-		if(!is_array($objectSid) || empty($objectSid)) {
1716
-			$this->connection->writeToCache($cacheKey, false);
1717
-			return false;
1718
-		}
1719
-		$domainObjectSid = $this->convertSID2Str($objectSid[0]);
1720
-		$this->connection->writeToCache($cacheKey, $domainObjectSid);
1721
-
1722
-		return $domainObjectSid;
1723
-	}
1724
-
1725
-	/**
1726
-	 * converts a binary SID into a string representation
1727
-	 * @param string $sid
1728
-	 * @return string
1729
-	 */
1730
-	public function convertSID2Str($sid) {
1731
-		// The format of a SID binary string is as follows:
1732
-		// 1 byte for the revision level
1733
-		// 1 byte for the number n of variable sub-ids
1734
-		// 6 bytes for identifier authority value
1735
-		// n*4 bytes for n sub-ids
1736
-		//
1737
-		// Example: 010400000000000515000000a681e50e4d6c6c2bca32055f
1738
-		//  Legend: RRNNAAAAAAAAAAAA11111111222222223333333344444444
1739
-		$revision = ord($sid[0]);
1740
-		$numberSubID = ord($sid[1]);
1741
-
1742
-		$subIdStart = 8; // 1 + 1 + 6
1743
-		$subIdLength = 4;
1744
-		if (strlen($sid) !== $subIdStart + $subIdLength * $numberSubID) {
1745
-			// Incorrect number of bytes present.
1746
-			return '';
1747
-		}
1748
-
1749
-		// 6 bytes = 48 bits can be represented using floats without loss of
1750
-		// precision (see https://gist.github.com/bantu/886ac680b0aef5812f71)
1751
-		$iav = number_format(hexdec(bin2hex(substr($sid, 2, 6))), 0, '', '');
1752
-
1753
-		$subIDs = array();
1754
-		for ($i = 0; $i < $numberSubID; $i++) {
1755
-			$subID = unpack('V', substr($sid, $subIdStart + $subIdLength * $i, $subIdLength));
1756
-			$subIDs[] = sprintf('%u', $subID[1]);
1757
-		}
1758
-
1759
-		// Result for example above: S-1-5-21-249921958-728525901-1594176202
1760
-		return sprintf('S-%d-%s-%s', $revision, $iav, implode('-', $subIDs));
1761
-	}
1762
-
1763
-	/**
1764
-	 * checks if the given DN is part of the given base DN(s)
1765
-	 * @param string $dn the DN
1766
-	 * @param string[] $bases array containing the allowed base DN or DNs
1767
-	 * @return bool
1768
-	 */
1769
-	public function isDNPartOfBase($dn, $bases) {
1770
-		$belongsToBase = false;
1771
-		$bases = $this->helper->sanitizeDN($bases);
1772
-
1773
-		foreach($bases as $base) {
1774
-			$belongsToBase = true;
1775
-			if(mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($base, 'UTF-8'))) {
1776
-				$belongsToBase = false;
1777
-			}
1778
-			if($belongsToBase) {
1779
-				break;
1780
-			}
1781
-		}
1782
-		return $belongsToBase;
1783
-	}
1784
-
1785
-	/**
1786
-	 * resets a running Paged Search operation
1787
-	 */
1788
-	private function abandonPagedSearch() {
1789
-		if($this->connection->hasPagedResultSupport) {
1790
-			$cr = $this->connection->getConnectionResource();
1791
-			$this->invokeLDAPMethod('controlPagedResult', $cr, 0, false, $this->lastCookie);
1792
-			$this->getPagedSearchResultState();
1793
-			$this->lastCookie = '';
1794
-			$this->cookies = array();
1795
-		}
1796
-	}
1797
-
1798
-	/**
1799
-	 * get a cookie for the next LDAP paged search
1800
-	 * @param string $base a string with the base DN for the search
1801
-	 * @param string $filter the search filter to identify the correct search
1802
-	 * @param int $limit the limit (or 'pageSize'), to identify the correct search well
1803
-	 * @param int $offset the offset for the new search to identify the correct search really good
1804
-	 * @return string containing the key or empty if none is cached
1805
-	 */
1806
-	private function getPagedResultCookie($base, $filter, $limit, $offset) {
1807
-		if($offset === 0) {
1808
-			return '';
1809
-		}
1810
-		$offset -= $limit;
1811
-		//we work with cache here
1812
-		$cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . intval($limit) . '-' . intval($offset);
1813
-		$cookie = '';
1814
-		if(isset($this->cookies[$cacheKey])) {
1815
-			$cookie = $this->cookies[$cacheKey];
1816
-			if(is_null($cookie)) {
1817
-				$cookie = '';
1818
-			}
1819
-		}
1820
-		return $cookie;
1821
-	}
1822
-
1823
-	/**
1824
-	 * checks whether an LDAP paged search operation has more pages that can be
1825
-	 * retrieved, typically when offset and limit are provided.
1826
-	 *
1827
-	 * Be very careful to use it: the last cookie value, which is inspected, can
1828
-	 * be reset by other operations. Best, call it immediately after a search(),
1829
-	 * searchUsers() or searchGroups() call. count-methods are probably safe as
1830
-	 * well. Don't rely on it with any fetchList-method.
1831
-	 * @return bool
1832
-	 */
1833
-	public function hasMoreResults() {
1834
-		if(!$this->connection->hasPagedResultSupport) {
1835
-			return false;
1836
-		}
1837
-
1838
-		if(empty($this->lastCookie) && $this->lastCookie !== '0') {
1839
-			// as in RFC 2696, when all results are returned, the cookie will
1840
-			// be empty.
1841
-			return false;
1842
-		}
1843
-
1844
-		return true;
1845
-	}
1846
-
1847
-	/**
1848
-	 * set a cookie for LDAP paged search run
1849
-	 * @param string $base a string with the base DN for the search
1850
-	 * @param string $filter the search filter to identify the correct search
1851
-	 * @param int $limit the limit (or 'pageSize'), to identify the correct search well
1852
-	 * @param int $offset the offset for the run search to identify the correct search really good
1853
-	 * @param string $cookie string containing the cookie returned by ldap_control_paged_result_response
1854
-	 * @return void
1855
-	 */
1856
-	private function setPagedResultCookie($base, $filter, $limit, $offset, $cookie) {
1857
-		// allow '0' for 389ds
1858
-		if(!empty($cookie) || $cookie === '0') {
1859
-			$cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' .intval($limit) . '-' . intval($offset);
1860
-			$this->cookies[$cacheKey] = $cookie;
1861
-			$this->lastCookie = $cookie;
1862
-		}
1863
-	}
1864
-
1865
-	/**
1866
-	 * Check whether the most recent paged search was successful. It flushed the state var. Use it always after a possible paged search.
1867
-	 * @return boolean|null true on success, null or false otherwise
1868
-	 */
1869
-	public function getPagedSearchResultState() {
1870
-		$result = $this->pagedSearchedSuccessful;
1871
-		$this->pagedSearchedSuccessful = null;
1872
-		return $result;
1873
-	}
1874
-
1875
-	/**
1876
-	 * Prepares a paged search, if possible
1877
-	 * @param string $filter the LDAP filter for the search
1878
-	 * @param string[] $bases an array containing the LDAP subtree(s) that shall be searched
1879
-	 * @param string[] $attr optional, when a certain attribute shall be filtered outside
1880
-	 * @param int $limit
1881
-	 * @param int $offset
1882
-	 * @return bool|true
1883
-	 */
1884
-	private function initPagedSearch($filter, $bases, $attr, $limit, $offset) {
1885
-		$pagedSearchOK = false;
1886
-		if($this->connection->hasPagedResultSupport && ($limit !== 0)) {
1887
-			$offset = intval($offset); //can be null
1888
-			\OCP\Util::writeLog('user_ldap',
1889
-				'initializing paged search for  Filter '.$filter.' base '.print_r($bases, true)
1890
-				.' attr '.print_r($attr, true). ' limit ' .$limit.' offset '.$offset,
1891
-				\OCP\Util::DEBUG);
1892
-			//get the cookie from the search for the previous search, required by LDAP
1893
-			foreach($bases as $base) {
1894
-
1895
-				$cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
1896
-				if(empty($cookie) && $cookie !== "0" && ($offset > 0)) {
1897
-					// no cookie known from a potential previous search. We need
1898
-					// to start from 0 to come to the desired page. cookie value
1899
-					// of '0' is valid, because 389ds
1900
-					$reOffset = 0;
1901
-					while($reOffset < $offset) {
1902
-						$this->search($filter, array($base), $attr, $limit, $reOffset, true);
1903
-						$reOffset += $limit;
1904
-					}
1905
-					$cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
1906
-					//still no cookie? obviously, the server does not like us. Let's skip paging efforts.
1907
-					// '0' is valid, because 389ds
1908
-					//TODO: remember this, probably does not change in the next request...
1909
-					if(empty($cookie) && $cookie !== '0') {
1910
-						$cookie = null;
1911
-					}
1912
-				}
1913
-				if(!is_null($cookie)) {
1914
-					//since offset = 0, this is a new search. We abandon other searches that might be ongoing.
1915
-					$this->abandonPagedSearch();
1916
-					$pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
1917
-						$this->connection->getConnectionResource(), $limit,
1918
-						false, $cookie);
1919
-					if(!$pagedSearchOK) {
1920
-						return false;
1921
-					}
1922
-					\OCP\Util::writeLog('user_ldap', 'Ready for a paged search', \OCP\Util::DEBUG);
1923
-				} else {
1924
-					\OCP\Util::writeLog('user_ldap',
1925
-						'No paged search for us, Cpt., Limit '.$limit.' Offset '.$offset,
1926
-						\OCP\Util::INFO);
1927
-				}
1928
-
1929
-			}
1930
-		/* ++ Fixing RHDS searches with pages with zero results ++
1682
+            \OC::$server->getLogger()->info(
1683
+                'Passed string does not resemble a valid GUID. Known UUID ' .
1684
+                '({uuid}) probably does not match UUID configuration.',
1685
+                [ 'app' => 'user_ldap', 'uuid' => $guid ]
1686
+            );
1687
+            return $guid;
1688
+        }
1689
+        for($i=0; $i < 3; $i++) {
1690
+            $pairs = str_split($blocks[$i], 2);
1691
+            $pairs = array_reverse($pairs);
1692
+            $blocks[$i] = implode('', $pairs);
1693
+        }
1694
+        for($i=0; $i < 5; $i++) {
1695
+            $pairs = str_split($blocks[$i], 2);
1696
+            $blocks[$i] = '\\' . implode('\\', $pairs);
1697
+        }
1698
+        return implode('', $blocks);
1699
+    }
1700
+
1701
+    /**
1702
+     * gets a SID of the domain of the given dn
1703
+     * @param string $dn
1704
+     * @return string|bool
1705
+     */
1706
+    public function getSID($dn) {
1707
+        $domainDN = $this->getDomainDNFromDN($dn);
1708
+        $cacheKey = 'getSID-'.$domainDN;
1709
+        $sid = $this->connection->getFromCache($cacheKey);
1710
+        if(!is_null($sid)) {
1711
+            return $sid;
1712
+        }
1713
+
1714
+        $objectSid = $this->readAttribute($domainDN, 'objectsid');
1715
+        if(!is_array($objectSid) || empty($objectSid)) {
1716
+            $this->connection->writeToCache($cacheKey, false);
1717
+            return false;
1718
+        }
1719
+        $domainObjectSid = $this->convertSID2Str($objectSid[0]);
1720
+        $this->connection->writeToCache($cacheKey, $domainObjectSid);
1721
+
1722
+        return $domainObjectSid;
1723
+    }
1724
+
1725
+    /**
1726
+     * converts a binary SID into a string representation
1727
+     * @param string $sid
1728
+     * @return string
1729
+     */
1730
+    public function convertSID2Str($sid) {
1731
+        // The format of a SID binary string is as follows:
1732
+        // 1 byte for the revision level
1733
+        // 1 byte for the number n of variable sub-ids
1734
+        // 6 bytes for identifier authority value
1735
+        // n*4 bytes for n sub-ids
1736
+        //
1737
+        // Example: 010400000000000515000000a681e50e4d6c6c2bca32055f
1738
+        //  Legend: RRNNAAAAAAAAAAAA11111111222222223333333344444444
1739
+        $revision = ord($sid[0]);
1740
+        $numberSubID = ord($sid[1]);
1741
+
1742
+        $subIdStart = 8; // 1 + 1 + 6
1743
+        $subIdLength = 4;
1744
+        if (strlen($sid) !== $subIdStart + $subIdLength * $numberSubID) {
1745
+            // Incorrect number of bytes present.
1746
+            return '';
1747
+        }
1748
+
1749
+        // 6 bytes = 48 bits can be represented using floats without loss of
1750
+        // precision (see https://gist.github.com/bantu/886ac680b0aef5812f71)
1751
+        $iav = number_format(hexdec(bin2hex(substr($sid, 2, 6))), 0, '', '');
1752
+
1753
+        $subIDs = array();
1754
+        for ($i = 0; $i < $numberSubID; $i++) {
1755
+            $subID = unpack('V', substr($sid, $subIdStart + $subIdLength * $i, $subIdLength));
1756
+            $subIDs[] = sprintf('%u', $subID[1]);
1757
+        }
1758
+
1759
+        // Result for example above: S-1-5-21-249921958-728525901-1594176202
1760
+        return sprintf('S-%d-%s-%s', $revision, $iav, implode('-', $subIDs));
1761
+    }
1762
+
1763
+    /**
1764
+     * checks if the given DN is part of the given base DN(s)
1765
+     * @param string $dn the DN
1766
+     * @param string[] $bases array containing the allowed base DN or DNs
1767
+     * @return bool
1768
+     */
1769
+    public function isDNPartOfBase($dn, $bases) {
1770
+        $belongsToBase = false;
1771
+        $bases = $this->helper->sanitizeDN($bases);
1772
+
1773
+        foreach($bases as $base) {
1774
+            $belongsToBase = true;
1775
+            if(mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($base, 'UTF-8'))) {
1776
+                $belongsToBase = false;
1777
+            }
1778
+            if($belongsToBase) {
1779
+                break;
1780
+            }
1781
+        }
1782
+        return $belongsToBase;
1783
+    }
1784
+
1785
+    /**
1786
+     * resets a running Paged Search operation
1787
+     */
1788
+    private function abandonPagedSearch() {
1789
+        if($this->connection->hasPagedResultSupport) {
1790
+            $cr = $this->connection->getConnectionResource();
1791
+            $this->invokeLDAPMethod('controlPagedResult', $cr, 0, false, $this->lastCookie);
1792
+            $this->getPagedSearchResultState();
1793
+            $this->lastCookie = '';
1794
+            $this->cookies = array();
1795
+        }
1796
+    }
1797
+
1798
+    /**
1799
+     * get a cookie for the next LDAP paged search
1800
+     * @param string $base a string with the base DN for the search
1801
+     * @param string $filter the search filter to identify the correct search
1802
+     * @param int $limit the limit (or 'pageSize'), to identify the correct search well
1803
+     * @param int $offset the offset for the new search to identify the correct search really good
1804
+     * @return string containing the key or empty if none is cached
1805
+     */
1806
+    private function getPagedResultCookie($base, $filter, $limit, $offset) {
1807
+        if($offset === 0) {
1808
+            return '';
1809
+        }
1810
+        $offset -= $limit;
1811
+        //we work with cache here
1812
+        $cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . intval($limit) . '-' . intval($offset);
1813
+        $cookie = '';
1814
+        if(isset($this->cookies[$cacheKey])) {
1815
+            $cookie = $this->cookies[$cacheKey];
1816
+            if(is_null($cookie)) {
1817
+                $cookie = '';
1818
+            }
1819
+        }
1820
+        return $cookie;
1821
+    }
1822
+
1823
+    /**
1824
+     * checks whether an LDAP paged search operation has more pages that can be
1825
+     * retrieved, typically when offset and limit are provided.
1826
+     *
1827
+     * Be very careful to use it: the last cookie value, which is inspected, can
1828
+     * be reset by other operations. Best, call it immediately after a search(),
1829
+     * searchUsers() or searchGroups() call. count-methods are probably safe as
1830
+     * well. Don't rely on it with any fetchList-method.
1831
+     * @return bool
1832
+     */
1833
+    public function hasMoreResults() {
1834
+        if(!$this->connection->hasPagedResultSupport) {
1835
+            return false;
1836
+        }
1837
+
1838
+        if(empty($this->lastCookie) && $this->lastCookie !== '0') {
1839
+            // as in RFC 2696, when all results are returned, the cookie will
1840
+            // be empty.
1841
+            return false;
1842
+        }
1843
+
1844
+        return true;
1845
+    }
1846
+
1847
+    /**
1848
+     * set a cookie for LDAP paged search run
1849
+     * @param string $base a string with the base DN for the search
1850
+     * @param string $filter the search filter to identify the correct search
1851
+     * @param int $limit the limit (or 'pageSize'), to identify the correct search well
1852
+     * @param int $offset the offset for the run search to identify the correct search really good
1853
+     * @param string $cookie string containing the cookie returned by ldap_control_paged_result_response
1854
+     * @return void
1855
+     */
1856
+    private function setPagedResultCookie($base, $filter, $limit, $offset, $cookie) {
1857
+        // allow '0' for 389ds
1858
+        if(!empty($cookie) || $cookie === '0') {
1859
+            $cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' .intval($limit) . '-' . intval($offset);
1860
+            $this->cookies[$cacheKey] = $cookie;
1861
+            $this->lastCookie = $cookie;
1862
+        }
1863
+    }
1864
+
1865
+    /**
1866
+     * Check whether the most recent paged search was successful. It flushed the state var. Use it always after a possible paged search.
1867
+     * @return boolean|null true on success, null or false otherwise
1868
+     */
1869
+    public function getPagedSearchResultState() {
1870
+        $result = $this->pagedSearchedSuccessful;
1871
+        $this->pagedSearchedSuccessful = null;
1872
+        return $result;
1873
+    }
1874
+
1875
+    /**
1876
+     * Prepares a paged search, if possible
1877
+     * @param string $filter the LDAP filter for the search
1878
+     * @param string[] $bases an array containing the LDAP subtree(s) that shall be searched
1879
+     * @param string[] $attr optional, when a certain attribute shall be filtered outside
1880
+     * @param int $limit
1881
+     * @param int $offset
1882
+     * @return bool|true
1883
+     */
1884
+    private function initPagedSearch($filter, $bases, $attr, $limit, $offset) {
1885
+        $pagedSearchOK = false;
1886
+        if($this->connection->hasPagedResultSupport && ($limit !== 0)) {
1887
+            $offset = intval($offset); //can be null
1888
+            \OCP\Util::writeLog('user_ldap',
1889
+                'initializing paged search for  Filter '.$filter.' base '.print_r($bases, true)
1890
+                .' attr '.print_r($attr, true). ' limit ' .$limit.' offset '.$offset,
1891
+                \OCP\Util::DEBUG);
1892
+            //get the cookie from the search for the previous search, required by LDAP
1893
+            foreach($bases as $base) {
1894
+
1895
+                $cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
1896
+                if(empty($cookie) && $cookie !== "0" && ($offset > 0)) {
1897
+                    // no cookie known from a potential previous search. We need
1898
+                    // to start from 0 to come to the desired page. cookie value
1899
+                    // of '0' is valid, because 389ds
1900
+                    $reOffset = 0;
1901
+                    while($reOffset < $offset) {
1902
+                        $this->search($filter, array($base), $attr, $limit, $reOffset, true);
1903
+                        $reOffset += $limit;
1904
+                    }
1905
+                    $cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
1906
+                    //still no cookie? obviously, the server does not like us. Let's skip paging efforts.
1907
+                    // '0' is valid, because 389ds
1908
+                    //TODO: remember this, probably does not change in the next request...
1909
+                    if(empty($cookie) && $cookie !== '0') {
1910
+                        $cookie = null;
1911
+                    }
1912
+                }
1913
+                if(!is_null($cookie)) {
1914
+                    //since offset = 0, this is a new search. We abandon other searches that might be ongoing.
1915
+                    $this->abandonPagedSearch();
1916
+                    $pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
1917
+                        $this->connection->getConnectionResource(), $limit,
1918
+                        false, $cookie);
1919
+                    if(!$pagedSearchOK) {
1920
+                        return false;
1921
+                    }
1922
+                    \OCP\Util::writeLog('user_ldap', 'Ready for a paged search', \OCP\Util::DEBUG);
1923
+                } else {
1924
+                    \OCP\Util::writeLog('user_ldap',
1925
+                        'No paged search for us, Cpt., Limit '.$limit.' Offset '.$offset,
1926
+                        \OCP\Util::INFO);
1927
+                }
1928
+
1929
+            }
1930
+        /* ++ Fixing RHDS searches with pages with zero results ++
1931 1931
 		 * We coudn't get paged searches working with our RHDS for login ($limit = 0),
1932 1932
 		 * due to pages with zero results.
1933 1933
 		 * So we added "&& !empty($this->lastCookie)" to this test to ignore pagination
1934 1934
 		 * if we don't have a previous paged search.
1935 1935
 		 */
1936
-		} else if($this->connection->hasPagedResultSupport && $limit === 0 && !empty($this->lastCookie)) {
1937
-			// a search without limit was requested. However, if we do use
1938
-			// Paged Search once, we always must do it. This requires us to
1939
-			// initialize it with the configured page size.
1940
-			$this->abandonPagedSearch();
1941
-			// in case someone set it to 0 … use 500, otherwise no results will
1942
-			// be returned.
1943
-			$pageSize = intval($this->connection->ldapPagingSize) > 0 ? intval($this->connection->ldapPagingSize) : 500;
1944
-			$pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
1945
-				$this->connection->getConnectionResource(),
1946
-				$pageSize, false, '');
1947
-		}
1948
-
1949
-		return $pagedSearchOK;
1950
-	}
1936
+        } else if($this->connection->hasPagedResultSupport && $limit === 0 && !empty($this->lastCookie)) {
1937
+            // a search without limit was requested. However, if we do use
1938
+            // Paged Search once, we always must do it. This requires us to
1939
+            // initialize it with the configured page size.
1940
+            $this->abandonPagedSearch();
1941
+            // in case someone set it to 0 … use 500, otherwise no results will
1942
+            // be returned.
1943
+            $pageSize = intval($this->connection->ldapPagingSize) > 0 ? intval($this->connection->ldapPagingSize) : 500;
1944
+            $pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
1945
+                $this->connection->getConnectionResource(),
1946
+                $pageSize, false, '');
1947
+        }
1948
+
1949
+        return $pagedSearchOK;
1950
+    }
1951 1951
 
1952 1952
 }
Please login to merge, or discard this patch.