ldap.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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. $error = 'LDAP bind failed';
  112. error_log("LDAP Connection Test Error: $error - Host: {$this->config['host']}, Port: {$this->config['port']}, Base DN: {$this->config['base_dn']}");
  113. throw new Exception($error);
  114. }
  115. // Test search
  116. $search = ldap_search($this->connection, $this->config['base_dn'], '(objectClass=*)', ['dn']);
  117. if (!$search) {
  118. $error = 'LDAP search failed';
  119. error_log("LDAP Connection Test Error: $error - Host: {$this->config['host']}, Port: {$this->config['port']}, Base DN: {$this->config['base_dn']}");
  120. throw new Exception($error);
  121. }
  122. return true;
  123. } catch (Exception $e) {
  124. $error = 'Connection failed: ' . $e->getMessage();
  125. error_log("LDAP Connection Test Error: $error - Host: {$this->config['host']}, Port: {$this->config['port']}, Base DN: {$this->config['base_dn']}");
  126. return false;
  127. } finally {
  128. $this->disconnect();
  129. }
  130. }
  131. private function connect() {
  132. $connection_string = $this->config['host'] . ':' . $this->config['port'];
  133. // Suppress LDAP warnings for cleaner error handling
  134. $this->connection = @ldap_connect($connection_string);
  135. if (!$this->connection) {
  136. $error = 'Failed to connect to LDAP server';
  137. error_log("LDAP Connection Error: $error - Host: {$this->config['host']}, Port: {$this->config['port']}, Base DN: {$this->config['base_dn']}");
  138. throw new Exception($error);
  139. }
  140. // Set LDAP options
  141. ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3);
  142. ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0);
  143. // Enable TLS if using port 636
  144. if ($this->config['port'] == 636) {
  145. if (!ldap_start_tls($this->connection)) {
  146. throw new Exception('Failed to start TLS');
  147. }
  148. }
  149. }
  150. private function disconnect() {
  151. if ($this->connection) {
  152. ldap_close($this->connection);
  153. $this->connection = null;
  154. }
  155. }
  156. private function getLdapAttribute($entry, $attribute) {
  157. if (isset($entry[$attribute])) {
  158. if ($entry[$attribute]['count'] > 1) {
  159. return $entry[$attribute][0]; // Return first value for multi-valued attributes
  160. }
  161. return $entry[$attribute][0];
  162. }
  163. return null;
  164. }
  165. public function searchUsers($query = '', $limit = 50) {
  166. try {
  167. $this->connect();
  168. // Bind with service account if configured
  169. if (!empty($this->config['bind_dn'])) {
  170. if (!ldap_bind($this->connection, $this->config['bind_dn'], $this->config['bind_password'])) {
  171. throw new Exception('LDAP bind failed with service account');
  172. }
  173. }
  174. // Build search filter
  175. if ($query) {
  176. $escaped_query = ldap_escape($query, '', LDAP_ESCAPE_FILTER);
  177. $filter = "(&(|(uid=*$escaped_query*)(cn=*$escaped_query*)(mail=*$escaped_query*)(givenName=*$escaped_query*)(sn=*$escaped_query*))(!(objectClass=computer)))";
  178. } else {
  179. $filter = "(&(objectClass=person)(!(objectClass=computer)))";
  180. }
  181. $search = ldap_search($this->connection, $this->config['base_dn'], $filter, [
  182. 'uid',
  183. 'cn',
  184. 'givenName',
  185. 'sn',
  186. 'mail',
  187. 'dn'
  188. ]);
  189. if (!$search) {
  190. throw new Exception('LDAP search failed');
  191. }
  192. ldap_control_paged_result($this->connection, $limit);
  193. $entries = ldap_get_entries($this->connection, $search);
  194. $users = [];
  195. for ($i = 0; $i < $entries['count']; $i++) {
  196. $user_entry = $entries[$i];
  197. $users[] = [
  198. 'username' => $this->getLdapAttribute($user_entry, 'uid'),
  199. 'name' => $this->getLdapAttribute($user_entry, 'cn'),
  200. 'first_name' => $this->getLdapAttribute($user_entry, 'givenName'),
  201. 'last_name' => $this->getLdapAttribute($user_entry, 'sn'),
  202. 'email' => $this->getLdapAttribute($user_entry, 'mail'),
  203. 'ldap_dn' => $user_entry['dn']
  204. ];
  205. }
  206. return $users;
  207. } catch (Exception $e) {
  208. error_log('LDAP User Search Error: ' . $e->getMessage());
  209. return [];
  210. } finally {
  211. $this->disconnect();
  212. }
  213. }
  214. }