LDAPProvider.php 9.6 KB

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