From 09c54722a877352713d8cefdb6a0a92860633898 Mon Sep 17 00:00:00 2001
From: Arthur Schiwon <blizzz@owncloud.com>
Date: Fri, 11 Jan 2013 18:13:22 +0100
Subject: [PATCH] add LDAP User and Group proxies to suppoer multiple servers

---
 apps/user_ldap/group_proxy.php | 178 +++++++++++++++++++++++++++++++++
 apps/user_ldap/lib/proxy.php   | 104 +++++++++++++++++++
 apps/user_ldap/user_proxy.php  | 159 +++++++++++++++++++++++++++++
 3 files changed, 441 insertions(+)
 create mode 100644 apps/user_ldap/group_proxy.php
 create mode 100644 apps/user_ldap/lib/proxy.php
 create mode 100644 apps/user_ldap/user_proxy.php

diff --git a/apps/user_ldap/group_proxy.php b/apps/user_ldap/group_proxy.php
new file mode 100644
index 0000000000..5aa1aef0e0
--- /dev/null
+++ b/apps/user_ldap/group_proxy.php
@@ -0,0 +1,178 @@
+<?php
+
+/**
+ * ownCloud
+ *
+ * @author Artuhr Schiwon
+ * @copyright 2013 Arthur Schiwon blizzz@owncloud.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\user_ldap;
+
+class Group_Proxy extends lib\Proxy implements \OCP\GroupInterface {
+	private $backends = array();
+	private $refBackend = null;
+
+	/**
+	 * @brief Constructor
+	 * @param $serverConfigPrefixes array containing the config Prefixes
+	 */
+	public function __construct($serverConfigPrefixes) {
+		parent::__construct();
+		foreach($serverConfigPrefixes as $configPrefix) {
+		    $this->backends[$configPrefix] = new \OCA\user_ldap\GROUP_LDAP();
+		    $connector = $this->getConnector($configPrefix);
+			$this->backends[$configPrefix]->setConnector($connector);
+			if(is_null($this->refBackend)) {
+				$this->refBackend = &$this->backends[$configPrefix];
+			}
+		}
+	}
+
+	/**
+	 * @brief Tries the backends one after the other until a positive result is returned from the specified method
+	 * @param $gid string, the gid connected to the request
+	 * @param $method string, the method of the group backend that shall be called
+	 * @param $parameters an array of parameters to be passed
+	 * @return mixed, the result of the method or false
+	 */
+	protected function walkBackends($gid, $method, $parameters) {
+		$cacheKey = $this->getGroupCacheKey($gid);
+		foreach($this->backends as $configPrefix => $backend) {
+		    if($result = call_user_func_array(array($backend, $method), $parameters)) {
+				$this->writeToCache($cacheKey, $configPrefix);
+				return $result;
+		    }
+		}
+		return false;
+	}
+
+	/**
+	 * @brief Asks the backend connected to the server that supposely takes care of the gid from the request.
+	 * @param $gid string, the gid connected to the request
+	 * @param $method string, the method of the group backend that shall be called
+	 * @param $parameters an array of parameters to be passed
+	 * @return mixed, the result of the method or false
+	 */
+	protected function callOnLastSeenOn($gid, $method, $parameters) {
+		$cacheKey = $this->getGroupCacheKey($gid);;
+		$prefix = $this->getFromCache($cacheKey);
+		//in case the uid has been found in the past, try this stored connection first
+		if(!is_null($prefix)) {
+			if(isset($this->backends[$prefix])) {
+				$result = call_user_func_array(array($this->backends[$prefix], $method), $parameters);
+				if(!$result) {
+					//not found here, reset cache to null
+					$this->writeToCache($cacheKey, null);
+				}
+				return $result;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * @brief is user in group?
+	 * @param $uid uid of the user
+	 * @param $gid gid of the group
+	 * @returns true/false
+	 *
+	 * Checks whether the user is member of a group or not.
+	 */
+	public function inGroup($uid, $gid) {
+		return $this->handleRequest($gid, 'inGroup', array($uid, $gid));
+	}
+
+	/**
+	 * @brief Get all groups a user belongs to
+	 * @param $uid Name of the user
+	 * @returns array with group names
+	 *
+	 * This function fetches all groups a user belongs to. It does not check
+	 * if the user exists at all.
+	 */
+	public function getUserGroups($uid) {
+		$groups = array();
+
+		foreach($this->backends as $backend) {
+		    $backendGroups = $backend->getUserGroups($uid);
+			if (is_array($backendGroups)) {
+				$groups = array_merge($groups, $backendGroups);
+			}
+		}
+
+		return $groups;
+	}
+
+	/**
+	 * @brief get a list of all users in a group
+	 * @returns array with user ids
+	 */
+	public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
+		$users = array();
+
+		foreach($this->backends as $backend) {
+		    $backendUsers = $backend->usersInGroup($gid, $search, $limit, $offset);
+			if (is_array($backendUsers)) {
+				$users = array_merge($users, $backendUsers);
+			}
+		}
+
+		return $users;
+	}
+
+	/**
+	 * @brief get a list of all groups
+	 * @returns array with group names
+	 *
+	 * Returns a list with all groups
+	 */
+	public function getGroups($search = '', $limit = -1, $offset = 0) {
+		$groups = array();
+
+		foreach($this->backends as $backend) {
+		    $backendGroups = $backend->getGroups($search, $limit, $offset);
+			if (is_array($backendGroups)) {
+				$groups = array_merge($groups, $backendGroups);
+			}
+		}
+
+		return $groups;
+	}
+
+	/**
+	 * check if a group exists
+	 * @param string $gid
+	 * @return bool
+	 */
+	public function groupExists($gid) {
+		return $this->handleRequest($gid, 'groupExists', array($gid));
+	}
+
+	/**
+	 * @brief Check if backend implements actions
+	 * @param $actions bitwise-or'ed actions
+	 * @returns boolean
+	 *
+	 * Returns the supported actions as int to be
+	 * compared with OC_USER_BACKEND_CREATE_USER etc.
+	 */
+	public function implementsActions($actions) {
+		//it's the same across all our user backends obviously
+		return $this->refBackend->implementsActions($actions);
+	}
+}
\ No newline at end of file
diff --git a/apps/user_ldap/lib/proxy.php b/apps/user_ldap/lib/proxy.php
new file mode 100644
index 0000000000..c80e216347
--- /dev/null
+++ b/apps/user_ldap/lib/proxy.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * ownCloud – LDAP Backend Proxy
+ *
+ * @author Arthur Schiwon
+ * @copyright 2013 Arthur Schiwon blizzz@owncloud.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\user_ldap\lib;
+
+abstract class Proxy {
+	static private $connectors = array();
+
+	public function __construct() {
+		$this->cache = \OC_Cache::getGlobalCache();
+	}
+
+	private function addConnector($configPrefix) {
+		self::$connectors[$configPrefix] = new \OCA\user_ldap\lib\Connection($configPrefix);
+	}
+
+	protected function getConnector($configPrefix) {
+		if(!isset(self::$connectors[$configPrefix])) {
+			$this->addConnector($configPrefix);
+		}
+		return self::$connectors[$configPrefix];
+	}
+
+	protected function getConnectors() {
+		return self::$connectors;
+	}
+
+	protected function getUserCacheKey($uid) {
+		return 'user-'.$uid.'-lastSeenOn';
+	}
+
+	protected function getGroupCacheKey($gid) {
+		return 'group-'.$gid.'-lastSeenOn';
+	}
+
+	abstract protected function callOnLastSeenOn($id, $method, $parameters);
+	abstract protected function walkBackends($id, $method, $parameters);
+
+	/**
+	 * @brief Takes care of the request to the User backend
+	 * @param $uid string, the uid connected to the request
+	 * @param $method string, the method of the user backend that shall be called
+	 * @param $parameters an array of parameters to be passed
+	 * @return mixed, the result of the specified method
+	 */
+	protected function handleRequest($id, $method, $parameters) {
+		if(!$result = $this->callOnLastSeenOn($id,  $method, $parameters)) {
+			$result = $this->walkBackends($id, $method, $parameters);
+		}
+		return $result;
+	}
+
+	private function getCacheKey($key) {
+		$prefix = 'LDAP-Proxy-';
+		if(is_null($key)) {
+			return $prefix;
+		}
+		return $prefix.md5($key);
+	}
+
+	public function getFromCache($key) {
+		if(!$this->isCached($key)) {
+			return null;
+		}
+		$key = $this->getCacheKey($key);
+
+		return unserialize(base64_decode($this->cache->get($key)));
+	}
+
+	public function isCached($key) {
+		$key = $this->getCacheKey($key);
+		return $this->cache->hasKey($key);
+	}
+
+	public function writeToCache($key, $value) {
+		$key   = $this->getCacheKey($key);
+		$value = base64_encode(serialize($value));
+		$this->cache->set($key, $value, '2592000');
+	}
+
+	public function clearCache() {
+		$this->cache->clear($this->getCacheKey(null));
+	}
+}
\ No newline at end of file
diff --git a/apps/user_ldap/user_proxy.php b/apps/user_ldap/user_proxy.php
new file mode 100644
index 0000000000..47f901ddb5
--- /dev/null
+++ b/apps/user_ldap/user_proxy.php
@@ -0,0 +1,159 @@
+<?php
+
+/**
+ * ownCloud
+ *
+ * @author Artuhr Schiwon
+ * @copyright 2013 Arthur Schiwon blizzz@owncloud.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\user_ldap;
+
+class User_Proxy extends lib\Proxy implements \OCP\UserInterface {
+	private $backends = array();
+	private $refBackend = null;
+
+	/**
+	 * @brief Constructor
+	 * @param $serverConfigPrefixes array containing the config Prefixes
+	 */
+	public function __construct($serverConfigPrefixes) {
+		parent::__construct();
+		foreach($serverConfigPrefixes as $configPrefix) {
+		    $this->backends[$configPrefix] = new \OCA\user_ldap\USER_LDAP();
+		    $connector = $this->getConnector($configPrefix);
+			$this->backends[$configPrefix]->setConnector($connector);
+			if(is_null($this->refBackend)) {
+				$this->refBackend = &$this->backends[$configPrefix];
+			}
+		}
+	}
+
+	/**
+	 * @brief Tries the backends one after the other until a positive result is returned from the specified method
+	 * @param $uid string, the uid connected to the request
+	 * @param $method string, the method of the user backend that shall be called
+	 * @param $parameters an array of parameters to be passed
+	 * @return mixed, the result of the method or false
+	 */
+	protected  function walkBackends($uid, $method, $parameters) {
+		$cacheKey = $this->getUserCacheKey($uid);
+		foreach($this->backends as $configPrefix => $backend) {
+		    if($result = call_user_func_array(array($backend, $method), $parameters)) {
+				$this->writeToCache($cacheKey, $configPrefix);
+				return $result;
+		    }
+		}
+		return false;
+	}
+
+	/**
+	 * @brief Asks the backend connected to the server that supposely takes care of the uid from the request.
+	 * @param $uid string, the uid connected to the request
+	 * @param $method string, the method of the user backend that shall be called
+	 * @param $parameters an array of parameters to be passed
+	 * @return mixed, the result of the method or false
+	 */
+	protected  function callOnLastSeenOn($uid, $method, $parameters) {
+		$cacheKey = $this->getUserCacheKey($uid);
+		$prefix = $this->getFromCache($cacheKey);
+		//in case the uid has been found in the past, try this stored connection first
+		if(!is_null($prefix)) {
+			if(isset($this->backends[$prefix])) {
+				$result = call_user_func_array(array($this->backends[$prefix], $method), $parameters);
+				if(!$result) {
+					//not found here, reset cache to null
+					$this->writeToCache($cacheKey, null);
+				}
+				return $result;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * @brief Check if backend implements actions
+	 * @param $actions bitwise-or'ed actions
+	 * @returns boolean
+	 *
+	 * Returns the supported actions as int to be
+	 * compared with OC_USER_BACKEND_CREATE_USER etc.
+	 */
+	public function implementsActions($actions) {
+		//it's the same across all our user backends obviously
+		return $this->refBackend->implementsActions($actions);
+	}
+
+	/**
+	 * @brief Get a list of all users
+	 * @returns array with all uids
+	 *
+	 * Get a list of all users.
+	 */
+	public function getUsers($search = '', $limit = 10, $offset = 0) {
+		//we do it just as the /OC_User implementation: do not play around with limit and offset but ask all backends
+		$users = array();
+		foreach($this->backends as $backend) {
+			$backendUsers = $backend->getUsers($search, $limit, $offset);
+			if (is_array($backendUsers)) {
+				$users = array_merge($users, $backendUsers);
+			}
+		}
+		return $users;
+	}
+
+	/**
+	 * @brief check if a user exists
+	 * @param string $uid the username
+	 * @return boolean
+	 */
+	public function userExists($uid) {
+		return $this->handleRequest($uid, 'userExists', array($uid));
+	}
+
+	/**
+	 * @brief Check if the password is correct
+	 * @param $uid The username
+	 * @param $password The password
+	 * @returns true/false
+	 *
+	 * Check if the password is correct without logging in the user
+	 */
+	public function checkPassword($uid, $password) {
+		return $this->handleRequest($uid, 'checkPassword', array($uid, $password));
+	}
+
+	/**
+	 * @brief get the user's home directory
+	 * @param string $uid the username
+	 * @return boolean
+	 */
+	public function getHome($uid) {
+		return $this->handleRequest($uid, 'getHome', array($uid));
+	}
+
+	/**
+	 * @brief delete a user
+	 * @param $uid The username of the user to delete
+	 * @returns true/false
+	 *
+	 * Deletes a user
+	 */
+	public function deleteUser($uid) {
+		return false;
+	}
+}
\ No newline at end of file
-- 
GitLab