2 // +----------------------------------------------------------------------+
3 // | Anuko Time Tracker
4 // +----------------------------------------------------------------------+
5 // | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
6 // +----------------------------------------------------------------------+
7 // | LIBERAL FREEWARE LICENSE: This source code document may be used
8 // | by anyone for any purpose, and freely redistributed alone or in
9 // | combination with other software, provided that the license is obeyed.
11 // | There are only two ways to violate the license:
13 // | 1. To redistribute this code in source form, with the copyright
14 // | notice or license removed or altered. (Distributing in compiled
15 // | forms without embedded copyright notices is permitted).
17 // | 2. To redistribute modified versions of this code in *any* form
18 // | that bears insufficient indications that the modifications are
19 // | not the work of the original author(s).
21 // | This license applies to this document only, not any other software
22 // | that it may be combined with.
24 // +----------------------------------------------------------------------+
26 // | https://www.anuko.com/content/time_tracker/open_source/credits.htm
27 // +----------------------------------------------------------------------+
31 // Auth_ldap.class.php was originally written for LDAP authentication with Windows Active Directory.
32 // It June 2011, it was extended to include support for OpenLDAP. The difference in the code is in the format
33 // of user identification that we pass to ldap_bind().
35 // Windows AD accepts username@domain.com while OpenLDAP needs something like "uid=username,ou=people,dc=domain,dc=com".
36 // Therefore, some branching in the code.
38 // In April 2012, a previously mandatory search for group membership was put in a conditional block (if ($member_of) -
39 // when mandatory membership in groups is actually defined in config.php).
40 // This made the module work with Sun Directory Server when NO GROUP MEMBERSHIP is specified.
41 // Note 1: search is likely to fail with Sun DS if 'member_of' => array()); is used in config.php.
42 // Note 2: search is likely to not work properly with OpenLDAP as well because of Windows specific filtering code in there
43 // (we are looking for matches for Windows-specific samaccountname property). Search needs to be redone during the next
44 // refactoring effort.
48 * Auth_ldap class to authenticate users against an LDAP server (Windows AD, OpenLDAP, and others).
49 * @package TimeTracker
51 class Auth_ldap extends Auth {
54 function __construct($params)
57 $this->params = $params;
58 $smarty->assign('Auth_ldap_params', $this->params);
61 function ldap_escape($str){
62 $illegal = array("(", ")", "#");
64 foreach ($illegal as $id => $char) {
65 $legal[$id] = "\\".$char;
67 $str = str_replace($illegal, $legal, $str); //replace them
72 * Authenticate user against LDAP server.
74 * @param string $login
75 * @param string $password
78 function authenticate($login, $password)
80 // Special handling for admin@localhost - authenticate against db, not ldap.
81 // It is a fallback mechanism when admin account in LDAP directory does not exist or is misconfigured.
82 if ($login == 'admin@localhost') {
83 import('auth.Auth_db');
84 return Auth_db::authenticate($login, $password);
87 if (!function_exists('ldap_bind')) {
88 die ('php_ldap extension not loaded!');
91 if (empty($this->params['server']) || empty($this->params['base_dn'])) {
92 die('You must set server and base_dn in AUTH_MODULE_PARAMS in config.php');
95 $member_of = @$this->params['member_of'];
97 $lc = ldap_connect($this->params['server']);
99 if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
101 echo '$lc='; var_dump($lc); echo '<br />';
102 echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
105 if (!$lc) return false;
107 ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, 3);
108 ldap_set_option($lc, LDAP_OPT_REFERRALS, 0);
109 if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
110 ldap_set_option($lc, LDAP_OPT_DEBUG_LEVEL, 7);
113 // We need to handle Windows AD and OpenLDAP differently.
114 if ($this->params['type'] == 'ad') {
116 // Check if user specified full login.
117 if (strpos($login, '@') === false) {
118 // Append default domain.
119 $login .= '@' . $this->params['default_domain'];
122 if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
123 echo '$login='; var_dump($login); echo '<br />';
126 $lb = @ldap_bind($lc, $login, $password);
128 if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
129 echo '$lb='; var_dump($lb); echo '<br />';
130 echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
139 // Get groups the user is a member of from AD LDAP server.
141 $filter = 'userPrincipalName='.Auth_ldap::ldap_escape($login);
142 $fields = array('memberof');
143 $sr = @ldap_search($lc, $this->params['base_dn'], $filter, $fields);
145 if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
146 echo '$sr='; var_dump($sr); echo '<br />';
147 echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
155 $entries = @ldap_get_entries($lc, $sr);
157 if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
158 echo '$entries='; var_dump($entries); echo '<br />';
159 echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
162 if ($entries === false) {
169 // Extract group names. Assume the groups are in format: CN=<group_name>,...
170 for ($i = 0; $i < @$entries[0]['memberof']['count']; $i++) {
171 $grp = $entries[0]['memberof'][$i];
172 $grp_fields = explode(',', $grp);
173 $groups[] = substr($grp_fields[0], 3);
176 if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
177 echo '$member_of'; var_dump($member_of); echo '<br />';
180 // Check for group membership.
181 foreach ($member_of as $check_grp) {
182 if (!in_array($check_grp, $groups)) {
190 return array('login' => $login, 'data' => $entries, 'member_of' => $groups);
193 if ($this->params['type'] == 'openldap') {
195 // Assuming OpenLDAP server.
196 $login_oldap = 'uid='.$login.','.$this->params['base_dn'];
198 if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
199 echo '$login_oldap='; var_dump($login_oldap); echo '<br />';
202 // check if the user specified full login
203 if (strpos($login, '@') === false) {
204 // append default domain
205 $login .= '@' . $this->params['default_domain'];
208 $lb = @ldap_bind($lc, $login_oldap, $password);
210 if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
211 echo '$lb='; var_dump($lb); echo '<br />';
212 echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
221 // TODO: Fix this for OpenLDAP, as samaccountname has nothing to do with it.
224 $filter = 'samaccountname='.Auth_ldap::ldap_escape($login_oldap);
225 $fields = array('samaccountname', 'mail', 'memberof', 'department', 'displayname', 'telephonenumber', 'primarygroupid');
226 $sr = @ldap_search($lc, $this->params['base_dn'], $filter, $fields);
228 if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
229 echo '$sr='; var_dump($sr); echo '<br />';
230 echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
233 // if search failed it's likely that account is disabled
239 $entries = @ldap_get_entries($lc, $sr);
241 if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
242 echo '$entries='; var_dump($entries); echo '<br />';
243 echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
246 if ($entries === false) {
253 // extract group names from
254 // assuming the groups are in format: CN=<group_name>,...
255 for ($i = 0; $i < @$entries[0]['memberof']['count']; $i++) {
256 $grp = $entries[0]['memberof'][$i];
257 $grp_fields = explode(',', $grp);
258 $groups[] = substr($grp_fields[0], 3);
261 if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
262 echo '$member_of'; var_dump($member_of); echo '<br />';
265 // check for group membership
266 foreach ($member_of as $check_grp) {
267 if (!in_array($check_grp, $groups)) {
276 return array('login' => $login, 'data' => $entries, 'member_of' => $groups);
279 // Server type is neither 'ad' or 'openldap'.
283 function isPasswordExternal() {