LDAPProvider.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OCA\User_LDAP;
  8. use OCA\User_LDAP\User\DeletedUsersIndex;
  9. use OCP\IServerContainer;
  10. use OCP\LDAP\IDeletionFlagSupport;
  11. use OCP\LDAP\ILDAPProvider;
  12. /**
  13. * LDAP provider for pulic access to the LDAP backend.
  14. */
  15. class LDAPProvider implements ILDAPProvider, IDeletionFlagSupport {
  16. private $userBackend;
  17. private $groupBackend;
  18. private $logger;
  19. private $helper;
  20. private $deletedUsersIndex;
  21. /**
  22. * Create new LDAPProvider
  23. * @param \OCP\IServerContainer $serverContainer
  24. * @param Helper $helper
  25. * @param DeletedUsersIndex $deletedUsersIndex
  26. * @throws \Exception if user_ldap app was not enabled
  27. */
  28. public function __construct(IServerContainer $serverContainer, Helper $helper, DeletedUsersIndex $deletedUsersIndex) {
  29. $this->logger = $serverContainer->getLogger();
  30. $this->helper = $helper;
  31. $this->deletedUsersIndex = $deletedUsersIndex;
  32. $userBackendFound = false;
  33. $groupBackendFound = false;
  34. foreach ($serverContainer->getUserManager()->getBackends() as $backend) {
  35. $this->logger->debug('instance '.get_class($backend).' user backend.', ['app' => 'user_ldap']);
  36. if ($backend instanceof IUserLDAP) {
  37. $this->userBackend = $backend;
  38. $userBackendFound = true;
  39. break;
  40. }
  41. }
  42. foreach ($serverContainer->getGroupManager()->getBackends() as $backend) {
  43. $this->logger->debug('instance '.get_class($backend).' group backend.', ['app' => 'user_ldap']);
  44. if ($backend instanceof IGroupLDAP) {
  45. $this->groupBackend = $backend;
  46. $groupBackendFound = true;
  47. break;
  48. }
  49. }
  50. if (!$userBackendFound or !$groupBackendFound) {
  51. throw new \Exception('To use the LDAPProvider, user_ldap app must be enabled');
  52. }
  53. }
  54. /**
  55. * Translate an user id to LDAP DN
  56. * @param string $uid user id
  57. * @return string with the LDAP DN
  58. * @throws \Exception if translation was unsuccessful
  59. */
  60. public function getUserDN($uid) {
  61. if (!$this->userBackend->userExists($uid)) {
  62. throw new \Exception('User id not found in LDAP');
  63. }
  64. $result = $this->userBackend->getLDAPAccess($uid)->username2dn($uid);
  65. if (!$result) {
  66. throw new \Exception('Translation to LDAP DN unsuccessful');
  67. }
  68. return $result;
  69. }
  70. /**
  71. * Translate a group id to LDAP DN.
  72. * @param string $gid group id
  73. * @return string
  74. * @throws \Exception
  75. */
  76. public function getGroupDN($gid) {
  77. if (!$this->groupBackend->groupExists($gid)) {
  78. throw new \Exception('Group id not found in LDAP');
  79. }
  80. $result = $this->groupBackend->getLDAPAccess($gid)->groupname2dn($gid);
  81. if (!$result) {
  82. throw new \Exception('Translation to LDAP DN unsuccessful');
  83. }
  84. return $result;
  85. }
  86. /**
  87. * Translate a LDAP DN to an internal user name. If there is no mapping between
  88. * the DN and the user name, a new one will be created.
  89. * @param string $dn LDAP DN
  90. * @return string with the internal user name
  91. * @throws \Exception if translation was unsuccessful
  92. */
  93. public function getUserName($dn) {
  94. $result = $this->userBackend->dn2UserName($dn);
  95. if (!$result) {
  96. throw new \Exception('Translation to internal user name unsuccessful');
  97. }
  98. return $result;
  99. }
  100. /**
  101. * Convert a stored DN so it can be used as base parameter for LDAP queries.
  102. * @param string $dn the DN in question
  103. * @return string
  104. */
  105. public function DNasBaseParameter($dn) {
  106. return $this->helper->DNasBaseParameter($dn);
  107. }
  108. /**
  109. * Sanitize a DN received from the LDAP server.
  110. * @param array $dn the DN in question
  111. * @return array the sanitized DN
  112. */
  113. public function sanitizeDN($dn) {
  114. return $this->helper->sanitizeDN($dn);
  115. }
  116. /**
  117. * Return a new LDAP connection resource for the specified user.
  118. * The connection must be closed manually.
  119. * @param string $uid user id
  120. * @return \LDAP\Connection The LDAP connection
  121. * @throws \Exception if user id was not found in LDAP
  122. */
  123. public function getLDAPConnection($uid) {
  124. if (!$this->userBackend->userExists($uid)) {
  125. throw new \Exception('User id not found in LDAP');
  126. }
  127. return $this->userBackend->getNewLDAPConnection($uid);
  128. }
  129. /**
  130. * Return a new LDAP connection resource for the specified user.
  131. * The connection must be closed manually.
  132. * @param string $gid group id
  133. * @return \LDAP\Connection The LDAP connection
  134. * @throws \Exception if group id was not found in LDAP
  135. */
  136. public function getGroupLDAPConnection($gid) {
  137. if (!$this->groupBackend->groupExists($gid)) {
  138. throw new \Exception('Group id not found in LDAP');
  139. }
  140. return $this->groupBackend->getNewLDAPConnection($gid);
  141. }
  142. /**
  143. * Get the LDAP base for users.
  144. * @param string $uid user id
  145. * @return string the base for users
  146. * @throws \Exception if user id was not found in LDAP
  147. */
  148. public function getLDAPBaseUsers($uid) {
  149. if (!$this->userBackend->userExists($uid)) {
  150. throw new \Exception('User id not found in LDAP');
  151. }
  152. $access = $this->userBackend->getLDAPAccess($uid);
  153. $bases = $access->getConnection()->ldapBaseUsers;
  154. $dn = $this->getUserDN($uid);
  155. foreach ($bases as $base) {
  156. if ($access->isDNPartOfBase($dn, [$base])) {
  157. return $base;
  158. }
  159. }
  160. // should not occur, because the user does not qualify to use NC in this case
  161. $this->logger->info(
  162. 'No matching user base found for user {dn}, available: {bases}.',
  163. [
  164. 'app' => 'user_ldap',
  165. 'dn' => $dn,
  166. 'bases' => $bases,
  167. ]
  168. );
  169. return array_shift($bases);
  170. }
  171. /**
  172. * Get the LDAP base for groups.
  173. * @param string $uid user id
  174. * @return string the base for groups
  175. * @throws \Exception if user id was not found in LDAP
  176. */
  177. public function getLDAPBaseGroups($uid) {
  178. if (!$this->userBackend->userExists($uid)) {
  179. throw new \Exception('User id not found in LDAP');
  180. }
  181. $bases = $this->userBackend->getLDAPAccess($uid)->getConnection()->ldapBaseGroups;
  182. return array_shift($bases);
  183. }
  184. /**
  185. * Clear the cache if a cache is used, otherwise do nothing.
  186. * @param string $uid user id
  187. * @throws \Exception if user id was not found in LDAP
  188. */
  189. public function clearCache($uid) {
  190. if (!$this->userBackend->userExists($uid)) {
  191. throw new \Exception('User id not found in LDAP');
  192. }
  193. $this->userBackend->getLDAPAccess($uid)->getConnection()->clearCache();
  194. }
  195. /**
  196. * Clear the cache if a cache is used, otherwise do nothing.
  197. * Acts on the LDAP connection of a group
  198. * @param string $gid group id
  199. * @throws \Exception if user id was not found in LDAP
  200. */
  201. public function clearGroupCache($gid) {
  202. if (!$this->groupBackend->groupExists($gid)) {
  203. throw new \Exception('Group id not found in LDAP');
  204. }
  205. $this->groupBackend->getLDAPAccess($gid)->getConnection()->clearCache();
  206. }
  207. /**
  208. * Check whether a LDAP DN exists
  209. * @param string $dn LDAP DN
  210. * @return bool whether the DN exists
  211. */
  212. public function dnExists($dn) {
  213. $result = $this->userBackend->dn2UserName($dn);
  214. return !$result ? false : true;
  215. }
  216. /**
  217. * Flag record for deletion.
  218. * @param string $uid user id
  219. */
  220. public function flagRecord($uid) {
  221. $this->deletedUsersIndex->markUser($uid);
  222. }
  223. /**
  224. * Unflag record for deletion.
  225. * @param string $uid user id
  226. */
  227. public function unflagRecord($uid) {
  228. //do nothing
  229. }
  230. /**
  231. * Get the LDAP attribute name for the user's display name
  232. * @param string $uid user id
  233. * @return string the display name field
  234. * @throws \Exception if user id was not found in LDAP
  235. */
  236. public function getLDAPDisplayNameField($uid) {
  237. if (!$this->userBackend->userExists($uid)) {
  238. throw new \Exception('User id not found in LDAP');
  239. }
  240. return $this->userBackend->getLDAPAccess($uid)->getConnection()->getConfiguration()['ldap_display_name'];
  241. }
  242. /**
  243. * Get the LDAP attribute name for the email
  244. * @param string $uid user id
  245. * @return string the email field
  246. * @throws \Exception if user id was not found in LDAP
  247. */
  248. public function getLDAPEmailField($uid) {
  249. if (!$this->userBackend->userExists($uid)) {
  250. throw new \Exception('User id not found in LDAP');
  251. }
  252. return $this->userBackend->getLDAPAccess($uid)->getConnection()->getConfiguration()['ldap_email_attr'];
  253. }
  254. /**
  255. * Get the LDAP type of association between users and groups
  256. * @param string $gid group id
  257. * @return string the configuration, one of: 'memberUid', 'uniqueMember', 'member', 'gidNumber', ''
  258. * @throws \Exception if group id was not found in LDAP
  259. */
  260. public function getLDAPGroupMemberAssoc($gid) {
  261. if (!$this->groupBackend->groupExists($gid)) {
  262. throw new \Exception('Group id not found in LDAP');
  263. }
  264. return $this->groupBackend->getLDAPAccess($gid)->getConnection()->getConfiguration()['ldap_group_member_assoc_attribute'];
  265. }
  266. /**
  267. * Get an LDAP attribute for a nextcloud user
  268. *
  269. * @throws \Exception if user id was not found in LDAP
  270. */
  271. public function getUserAttribute(string $uid, string $attribute): ?string {
  272. $values = $this->getMultiValueUserAttribute($uid, $attribute);
  273. if (count($values) === 0) {
  274. return null;
  275. }
  276. return current($values);
  277. }
  278. /**
  279. * Get a multi-value LDAP attribute for a nextcloud user
  280. *
  281. * @throws \Exception if user id was not found in LDAP
  282. */
  283. public function getMultiValueUserAttribute(string $uid, string $attribute): array {
  284. if (!$this->userBackend->userExists($uid)) {
  285. throw new \Exception('User id not found in LDAP');
  286. }
  287. $access = $this->userBackend->getLDAPAccess($uid);
  288. $connection = $access->getConnection();
  289. $key = $uid . '-' . $attribute;
  290. $cached = $connection->getFromCache($key);
  291. if (is_array($cached)) {
  292. return $cached;
  293. }
  294. $values = $access->readAttribute($access->username2dn($uid), $attribute);
  295. if ($values === false) {
  296. $values = [];
  297. }
  298. $connection->writeToCache($key, $values);
  299. return $values;
  300. }
  301. }