ldap.php 8.9 KB

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