ldap.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. <?php
  2. /**
  3. * LDAP Authentication Class
  4. * Handles LDAP directory authentication and user information retrieval
  5. */
  6. class LDAPAuth {
  7. private $connection;
  8. private $config;
  9. public function __construct() {
  10. $this->config = [
  11. 'host' => LDAP_HOST,
  12. 'port' => LDAP_PORT,
  13. 'base_dn' => LDAP_BASE_DN,
  14. 'user_filter' => LDAP_USER_FILTER,
  15. 'bind_dn' => LDAP_BIND_DN,
  16. 'bind_password' => LDAP_BIND_PASSWORD,
  17. 'email_attribute' => LDAP_EMAIL_ATTRIBUTE,
  18. 'name_attribute' => LDAP_NAME_ATTRIBUTE
  19. ];
  20. }
  21. public function authenticate($username, $password) {
  22. try {
  23. // Connect to LDAP server
  24. $this->connect();
  25. // First bind with service account if configured
  26. if (!empty($this->config['bind_dn'])) {
  27. if (!ldap_bind($this->connection, $this->config['bind_dn'], $this->config['bind_password'])) {
  28. throw new Exception('LDAP bind failed with service account');
  29. }
  30. }
  31. // Search for user
  32. $filter = str_replace('{username}', ldap_escape($username, '', LDAP_ESCAPE_FILTER), $this->config['user_filter']);
  33. $search = ldap_search($this->connection, $this->config['base_dn'], $filter, [$this->config['email_attribute'], $this->config['name_attribute'], 'dn']);
  34. if (!$search) {
  35. throw new Exception('LDAP search failed');
  36. }
  37. $entries = ldap_get_entries($this->connection, $search);
  38. if ($entries['count'] === 0) {
  39. return false;
  40. }
  41. $user_dn = $entries[0]['dn'];
  42. // Try to bind with user credentials
  43. if (@ldap_bind($this->connection, $user_dn, $password)) {
  44. return true;
  45. }
  46. return false;
  47. } catch (Exception $e) {
  48. error_log('LDAP Authentication Error: ' . $e->getMessage());
  49. return false;
  50. } finally {
  51. $this->disconnect();
  52. }
  53. }
  54. public function getUserInfo($username) {
  55. try {
  56. $this->connect();
  57. // Bind with service account if configured
  58. if (!empty($this->config['bind_dn'])) {
  59. if (!ldap_bind($this->connection, $this->config['bind_dn'], $this->config['bind_password'])) {
  60. throw new Exception('LDAP bind failed with service account');
  61. }
  62. }
  63. // Search for user
  64. $filter = str_replace('{username}', ldap_escape($username, '', LDAP_ESCAPE_FILTER), $this->config['user_filter']);
  65. $search = ldap_search($this->connection, $this->config['base_dn'], $filter, [
  66. $this->config['email_attribute'],
  67. $this->config['name_attribute'],
  68. 'dn',
  69. 'cn',
  70. 'givenName',
  71. 'sn',
  72. 'mail',
  73. 'uid'
  74. ]);
  75. if (!$search) {
  76. throw new Exception('LDAP search failed');
  77. }
  78. $entries = ldap_get_entries($this->connection, $search);
  79. if ($entries['count'] === 0) {
  80. return null;
  81. }
  82. $user_entry = $entries[0];
  83. $userInfo = [
  84. 'ldap_dn' => $user_entry['dn'],
  85. 'username' => $username,
  86. 'email' => $this->getLdapAttribute($user_entry, $this->config['email_attribute']),
  87. 'name' => $this->getLdapAttribute($user_entry, $this->config['name_attribute']),
  88. 'first_name' => $this->getLdapAttribute($user_entry, 'givenName'),
  89. 'last_name' => $this->getLdapAttribute($user_entry, 'sn'),
  90. 'full_name' => $this->getLdapAttribute($user_entry, 'cn'),
  91. 'uid' => $this->getLdapAttribute($user_entry, 'uid')
  92. ];
  93. return $userInfo;
  94. } catch (Exception $e) {
  95. error_log('LDAP User Info Error: ' . $e->getMessage());
  96. return null;
  97. } finally {
  98. $this->disconnect();
  99. }
  100. }
  101. public function testConnection() {
  102. try {
  103. $this->connect();
  104. // Test bind
  105. if (!empty($this->config['bind_dn'])) {
  106. $bind_result = ldap_bind($this->connection, $this->config['bind_dn'], $this->config['bind_password']);
  107. } else {
  108. $bind_result = ldap_bind($this->connection);
  109. }
  110. if (!$bind_result) {
  111. throw new Exception('LDAP bind failed');
  112. }
  113. // Test search
  114. $search = ldap_search($this->connection, $this->config['base_dn'], '(objectclass=*)', ['dn']);
  115. if (!$search) {
  116. throw new Exception('LDAP search failed');
  117. }
  118. return true;
  119. } catch (Exception $e) {
  120. return false;
  121. } finally {
  122. $this->disconnect();
  123. }
  124. }
  125. private function connect() {
  126. $connection_string = $this->config['host'] . ':' . $this->config['port'];
  127. // Suppress LDAP warnings for cleaner error handling
  128. $this->connection = @ldap_connect($connection_string);
  129. if (!$this->connection) {
  130. throw new Exception('Failed to connect to LDAP server');
  131. }
  132. // Set LDAP options
  133. ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3);
  134. ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0);
  135. // Enable TLS if using port 636
  136. if ($this->config['port'] == 636) {
  137. if (!ldap_start_tls($this->connection)) {
  138. throw new Exception('Failed to start TLS');
  139. }
  140. }
  141. }
  142. private function disconnect() {
  143. if ($this->connection) {
  144. ldap_close($this->connection);
  145. $this->connection = null;
  146. }
  147. }
  148. private function getLdapAttribute($entry, $attribute) {
  149. if (isset($entry[$attribute])) {
  150. if ($entry[$attribute]['count'] > 1) {
  151. return $entry[$attribute][0]; // Return first value for multi-valued attributes
  152. }
  153. return $entry[$attribute][0];
  154. }
  155. return null;
  156. }
  157. public function searchUsers($query = '', $limit = 50) {
  158. try {
  159. $this->connect();
  160. // Bind with service account if configured
  161. if (!empty($this->config['bind_dn'])) {
  162. if (!ldap_bind($this->connection, $this->config['bind_dn'], $this->config['bind_password'])) {
  163. throw new Exception('LDAP bind failed with service account');
  164. }
  165. }
  166. // Build search filter - use zimbraAccount objectclass for Zimbra directory
  167. if ($query) {
  168. $escaped_query = ldap_escape($query, '', LDAP_ESCAPE_FILTER);
  169. $filter = "(&(objectclass=zimbraAccount)(|(uid=*$escaped_query*)(cn=*$escaped_query*)(mail=*$escaped_query*)(givenName=*$escaped_query*)(sn=*$escaped_query*)))";
  170. } else {
  171. $filter = "(&(objectclass=zimbraAccount))";
  172. }
  173. $search = ldap_search($this->connection, $this->config['base_dn'], $filter, [
  174. 'uid',
  175. 'cn',
  176. 'givenName',
  177. 'sn',
  178. 'mail',
  179. 'dn'
  180. ]);
  181. if (!$search) {
  182. throw new Exception('LDAP search failed');
  183. }
  184. $entries = ldap_get_entries($this->connection, $search);
  185. $users = [];
  186. for ($i = 0; $i < $entries['count']; $i++) {
  187. $user_entry = $entries[$i];
  188. $users[] = [
  189. 'username' => $this->getLdapAttribute($user_entry, 'uid'),
  190. 'name' => $this->getLdapAttribute($user_entry, 'cn'),
  191. 'first_name' => $this->getLdapAttribute($user_entry, 'givenName'),
  192. 'last_name' => $this->getLdapAttribute($user_entry, 'sn'),
  193. 'email' => $this->getLdapAttribute($user_entry, 'mail'),
  194. 'ldap_dn' => $user_entry['dn']
  195. ];
  196. }
  197. return $users;
  198. } catch (Exception $e) {
  199. error_log('LDAP User Search Error: ' . $e->getMessage());
  200. return [];
  201. } finally {
  202. $this->disconnect();
  203. }
  204. }
  205. }