]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/Users/authentication/LDAPAuthenticate/LDAPAuthenticateUser.php
Release 6.5.16
[Github/sugarcrm.git] / modules / Users / authentication / LDAPAuthenticate / LDAPAuthenticateUser.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38
39
40
41 /**
42  * This file is where the user authentication occurs. No redirection should happen in this file.
43  *
44  */
45 require_once('modules/Users/authentication/LDAPAuthenticate/LDAPConfigs/default.php');
46 require_once('modules/Users/authentication/SugarAuthenticate/SugarAuthenticateUser.php');
47
48 define('DEFAULT_PORT', 389);
49 class LDAPAuthenticateUser extends SugarAuthenticateUser{
50
51         /**
52          * Does the actual authentication of the user and returns an id that will be used
53          * to load the current user (loadUserOnSession)
54          *
55          * @param STRING $name
56          * @param STRING $password
57          * @return STRING id - used for loading the user
58          *
59          * Contributions by Erik Mitchell erikm@logicpd.com
60          */
61         function authenticateUser($name, $password) {
62
63                 $server = $GLOBALS['ldap_config']->settings['ldap_hostname'];
64                 $port = $GLOBALS['ldap_config']->settings['ldap_port'];
65                 if(!$port)
66                         $port = DEFAULT_PORT;
67                 $GLOBALS['log']->debug("ldapauth: Connecting to LDAP server: $server");
68                 $ldapconn = ldap_connect($server, $port);
69                  $error = ldap_errno($ldapconn);
70                 if($this->loginError($error)){
71                         return '';
72                 }
73                 @ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
74                 @ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0); // required for AD
75                 // If constant is defined, set the timeout (PHP >= 5.3)
76                 if (defined('LDAP_OPT_NETWORK_TIMEOUT'))
77                 {
78                         // Network timeout, lower than PHP and DB timeouts
79                         @ldap_set_option($ldapconn, LDAP_OPT_NETWORK_TIMEOUT, 60);
80                 }
81
82                 $bind_user = $this->ldap_rdn_lookup($name, $password);
83                 $GLOBALS['log']->debug("ldapauth.ldap_authenticate_user: ldap_rdn_lookup returned bind_user=" . $bind_user);
84                 if (!$bind_user) {
85                         $GLOBALS['log']->fatal("SECURITY: ldapauth: failed LDAP bind (login) by " .
86                                                                         $name . ", could not construct bind_user");
87                         return '';
88                 }
89
90                 // MRF - Bug #18578 - punctuation was being passed as HTML entities, i.e. &amp;
91                 $bind_password = html_entity_decode($password,ENT_QUOTES);
92
93         $GLOBALS['log']->info("ldapauth: Binding user " . $bind_user);
94         $bind = ldap_bind($ldapconn, $bind_user, $bind_password);
95         $error = ldap_errno($ldapconn);
96         if ($this->loginError($error)) {
97             $full_user = $GLOBALS['ldap_config']->settings['ldap_bind_attr'] . "=" . $bind_user
98                 . "," . $GLOBALS['ldap_config']->settings['ldap_base_dn'];
99
100             $GLOBALS['log']->info("ldapauth: Binding user " . $full_user);
101             $bind = ldap_bind($ldapconn, $full_user, $bind_password);
102             $error = ldap_errno($ldapconn);
103             if ($this->loginError($error)) {
104                 return '';
105             }
106         }
107
108                 $GLOBALS['log']->info("ldapauth: Bind attempt complete.");
109
110                 if ($bind) {
111                         // Authentication succeeded, get info from LDAP directory
112                         $attrs = array_keys($GLOBALS['ldapConfig']['users']['fields']);
113                         $base_dn = $GLOBALS['ldap_config']->settings['ldap_base_dn'];
114                         $name_filter = $this->getUserNameFilter($name);
115
116                         //add the group user attribute that we will compare to the group attribute for membership validation if group membership is turned on
117             if (!empty($GLOBALS['ldap_config']->settings['ldap_group'])
118                 && !empty($GLOBALS['ldap_config']->settings['ldap_group_user_attr'])
119                 && !empty($GLOBALS['ldap_config']->settings['ldap_group_attr'])) {
120
121                 if (!in_array($attrs, $GLOBALS['ldap_config']->settings['ldap_group_user_attr'])) {
122                     $attrs[] = $GLOBALS['ldap_config']->settings['ldap_group_user_attr'];
123                 }
124             }
125
126             $GLOBALS['log']->debug(
127                 "ldapauth: Fetching user info from Directory using base dn: "
128                 . $base_dn . ", name_filter: " . $name_filter . ", attrs: " . var_export($attrs, true)
129             );
130
131                         $result = @ldap_search($ldapconn, $base_dn, $name_filter, $attrs);
132                         $error = ldap_errno($ldapconn);
133             if ($this->loginError($error)) {
134                         return '';
135                         }
136                         $GLOBALS['log']->debug("ldapauth: ldap_search complete.");
137
138             $info = @ldap_get_entries($ldapconn, $result);
139             $error = ldap_errno($ldapconn);
140             if ($this->loginError($error)) {
141                 return '';
142             }
143
144
145
146                         $GLOBALS['log']->debug("ldapauth: User info from Directory fetched.");
147
148                         // some of these don't seem to work
149                         $this->ldapUserInfo = array();
150                         foreach($GLOBALS['ldapConfig']['users']['fields'] as $key=>$value){
151                                 //MRF - BUG:19765
152                                 $key = strtolower($key);
153                                 if(isset($info[0]) && isset($info[0][$key]) && isset($info[0][$key][0])){
154                                         $this->ldapUserInfo[$value] = $info[0][$key][0];
155                                 }
156                         }
157
158                         //we should check that a user is a member of a specific group
159                         if(!empty($GLOBALS['ldap_config']->settings['ldap_group'])){
160                                 $GLOBALS['log']->debug("LDAPAuth: scanning group for user membership");
161                                 $group_user_attr = $GLOBALS['ldap_config']->settings['ldap_group_user_attr'];
162                                 $group_attr = $GLOBALS['ldap_config']->settings['ldap_group_attr'];
163                                 if(!isset($info[0][$group_user_attr])){
164                                         $GLOBALS['log']->fatal("ldapauth: $group_user_attr not found for user $name cannot authenticate against an LDAP group");
165                                         ldap_close($ldapconn);
166                                         return '';
167                                 }else{
168                                         $user_uid = $info[0][$group_user_attr];
169                     if (is_array($user_uid)){
170                         $user_uid = $user_uid[0];
171                     }
172                     // If user_uid contains special characters (for LDAP) we need to escape them !
173                     $user_uid = str_replace(array("(", ")"), array("\(", "\)"), $user_uid);
174
175                                 }
176
177                                 // build search query and determine if we are searching for a bare id or the full dn path
178                 $group_name = $GLOBALS['ldap_config']->settings['ldap_group_name'] . ","
179                     . $GLOBALS['ldap_config']->settings['ldap_group_dn'];
180                                 $GLOBALS['log']->debug("ldapauth: Searching for group name: " . $group_name);
181                                 $user_search = "";
182                 if (!empty($GLOBALS['ldap_config']->settings['ldap_group_attr_req_dn'])
183                     && $GLOBALS['ldap_config']->settings['ldap_group_attr_req_dn'] == 1) {
184
185                                         $GLOBALS['log']->debug("ldapauth: Checking for group membership using full user dn");
186                                         $user_search = "($group_attr=" . $group_user_attr . "=" . $user_uid . "," . $base_dn . ")";
187                                 } else {
188                                         $user_search = "($group_attr=" . $user_uid . ")";
189                                 }
190                                 $GLOBALS['log']->debug("ldapauth: Searching for user: " . $user_search);
191
192                                 //user is not a member of the group if the count is zero get the logs and return no id so it fails login        
193                 if (!isset($user_uid)
194                     || ldap_count_entries($ldapconn, ldap_search($ldapconn, $group_name, $user_search)) ==  0) {
195
196                                         $GLOBALS['log']->fatal("ldapauth: User ($name) is not a member of the LDAP group");
197                                         $user_id = var_export($user_uid, true);
198                     $GLOBALS['log']->debug(
199                         "ldapauth: Group DN:{$GLOBALS['ldap_config']->settings['ldap_group_dn']}"
200                         . " Group Name: " . $GLOBALS['ldap_config']->settings['ldap_group_name']
201                         . " Group Attribute: $group_attr  User Attribute: $group_user_attr :(" . $user_uid . ")"
202                     );
203
204                                         ldap_close($ldapconn);
205                                         return '';
206                                 }
207                         }
208
209
210
211                         ldap_close($ldapconn);
212                         $dbresult = $GLOBALS['db']->query("SELECT id, status FROM users WHERE user_name='" . $name . "' AND deleted = 0");
213
214                         //user already exists use this one
215                         if($row = $GLOBALS['db']->fetchByAssoc($dbresult)){
216                                 if($row['status'] != 'Inactive')
217                                         return $row['id'];
218                                 else
219                                         return '';
220                         }
221
222                         //create a new user and return the user
223                         if($GLOBALS['ldap_config']->settings['ldap_auto_create_users']){
224                                 return $this->createUser($name);
225
226                         }
227                         return '';
228
229                 } else {
230                         $GLOBALS['log']->fatal("SECURITY: failed LDAP bind (login) by $this->user_name using bind_user=$bind_user");
231                         $GLOBALS['log']->fatal("ldapauth: failed LDAP bind (login) by $this->user_name using bind_user=$bind_user");
232                         ldap_close($ldapconn);
233                         return '';
234                 }
235         }
236
237         /**
238          * takes in a name and creates the appropriate search filter for that user name including any additional filters specified in the system settings page
239          * @param $name
240          * @return String
241          */
242         function getUserNameFilter($name){
243                         $name_filter = "(" . $GLOBALS['ldap_config']->settings['ldap_login_attr']. "=" . $name . ")";
244                         //add the additional user filter if it is specified
245                         if(!empty($GLOBALS['ldap_config']->settings['ldap_login_filter'])){
246                                 $add_filter = $GLOBALS['ldap_config']->settings['ldap_login_filter'];
247                                 if(substr($add_filter, 0, 1) !== "("){
248                                         $add_filter = "(" . $add_filter . ")";
249                                 }
250                                 $name_filter = "(&" . $name_filter . $add_filter . ")";
251                         }
252                         return $name_filter;
253         }
254
255         /**
256          * Creates a user with the given User Name and returns the id of that new user
257          * populates the user with what was set in ldapUserInfo
258          *
259          * @param STRING $name
260          * @return STRING $id
261          */
262         function createUser($name){
263
264                         $user = new User();
265                         $user->user_name = $name;
266                         foreach($this->ldapUserInfo as $key=>$value){
267                                 $user->$key = $value;
268                         }
269                         $user->employee_status = 'Active';
270                         $user->status = 'Active';
271                         $user->is_admin = 0;
272                         $user->external_auth_only = 1;
273                         $user->save();
274                         return $user->id;
275
276         }
277         /**
278          * this is called when a user logs in
279          *
280          * @param STRING $name
281          * @param STRING $password
282          * @return boolean
283          */
284         function loadUserOnLogin($name, $password) {
285
286             global $mod_strings;
287
288             // Check if the LDAP extensions are loaded
289             if(!function_exists('ldap_connect')) {
290                $error = $mod_strings['LBL_LDAP_EXTENSION_ERROR'];
291                $GLOBALS['log']->fatal($error);
292                $_SESSION['login_error'] = $error;
293                return false;
294             }
295
296                 global $login_error;
297                 $GLOBALS['ldap_config']  = new Administration();
298                 $GLOBALS['ldap_config']->retrieveSettings('ldap');
299                 $GLOBALS['log']->debug("Starting user load for ". $name);
300                 if(empty($name) || empty($password)) return false;
301                 checkAuthUserStatus();
302
303                 $user_id = $this->authenticateUser($name, $password);
304                 if(empty($user_id)) {
305                         //check if the user can login as a normal sugar user
306                         $GLOBALS['log']->fatal('SECURITY: User authentication for '.$name.' failed');
307                         return false;
308                 }
309                 $this->loadUserOnSession($user_id);
310                 return true;
311         }
312
313
314         /**
315          * Called with the error number of the last call if the error number is 0
316          * there was no error otherwise it converts the error to a string and logs it as fatal
317          *
318          * @param INT $error
319          * @return boolean
320          */
321         function loginError($error){
322                 if(empty($error)) return false;
323                 $errorstr = ldap_err2str($error);
324                 // BEGIN SUGAR INT
325                 $_SESSION['login_error'] = $errorstr;
326                 /*
327                 // END SUGAR INT
328                 $_SESSION['login_error'] = translate('ERR_INVALID_PASSWORD', 'Users');
329                 // BEGIN SUGAR INT
330                 */
331                 // END SUGAR INT
332                 $GLOBALS['log']->fatal('[LDAP ERROR]['. $error . ']'.$errorstr);
333                 return true;
334         }
335
336          /**
337     * @return string appropriate value for username when binding to directory server.
338     * @param string $user_name the value provided in login form
339     * @desc Take the login username and return either said username for AD or lookup
340      * distinguished name using anonymous credentials for OpenLDAP.
341      * Contributions by Erik Mitchell erikm@logicpd.com
342     */
343     function ldap_rdn_lookup($user_name, $password) {
344
345         // MFH BUG# 14547 - Added htmlspecialchars_decode()
346         $server = $GLOBALS['ldap_config']->settings['ldap_hostname'];
347         $base_dn = htmlspecialchars_decode($GLOBALS['ldap_config']->settings['ldap_base_dn']);
348                 if(!empty($GLOBALS['ldap_config']->settings['ldap_authentication'])){
349                 $admin_user = htmlspecialchars_decode($GLOBALS['ldap_config']->settings['ldap_admin_user']);
350                 $admin_password = htmlspecialchars_decode($GLOBALS['ldap_config']->settings['ldap_admin_password']);
351                 }else{
352                         $admin_user = '';
353                 $admin_password = '';
354                 }
355         $user_attr = $GLOBALS['ldap_config']->settings['ldap_login_attr'];
356         $bind_attr = $GLOBALS['ldap_config']->settings['ldap_bind_attr'];
357         $port = $GLOBALS['ldap_config']->settings['ldap_port'];
358                 if(!$port)
359                         $port = DEFAULT_PORT;
360         $ldapconn = ldap_connect($server, $port);
361         $error = ldap_errno($ldapconn);
362         if($this->loginError($error)){
363                 return false;
364                 }
365         ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
366         ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0); // required for AD
367         //if we are going to connect anonymously lets at least try to connect with the user connecting
368         if(empty($admin_user)){
369                         $bind = @ldap_bind($ldapconn, $user_name, $password);
370                 $error = ldap_errno($ldapconn);
371         }
372         if(empty($bind)){
373                 $bind = @ldap_bind($ldapconn, $admin_user, $admin_password);
374                 $error = ldap_errno($ldapconn);
375         }
376
377         if($this->loginError($error)){
378                 return false;
379                 }
380         if (!$bind) {
381                    $GLOBALS['log']->warn("ldapauth.ldap_rdn_lookup: Could not bind with admin user, trying to bind anonymously");
382             $bind = @ldap_bind($ldapconn);
383              $error = ldap_errno($ldapconn);
384
385                  if($this->loginError($error)){
386                         return false;
387                         }
388             if (!$bind) {
389                         $GLOBALS['log']->warn("ldapauth.ldap_rdn_lookup: Could not bind anonymously, returning username");
390                         return $user_name;
391             }
392         }
393
394                 // If we get here we were able to bind somehow
395         $search_filter = $this->getUserNameFilter($user_name);
396
397         $GLOBALS['log']->info("ldapauth.ldap_rdn_lookup: Bind succeeded, searching for $user_attr=$user_name");
398         $GLOBALS['log']->debug("ldapauth.ldap_rdn_lookup: base_dn:$base_dn , search_filter:$search_filter");
399
400         $result = @ldap_search($ldapconn, $base_dn , $search_filter, array("dn", $bind_attr));
401          $error = ldap_errno($ldapconn);
402          if($this->loginError($error)){
403                 return false;
404                 }
405         $info = ldap_get_entries($ldapconn, $result);
406          if($info['count'] == 0){
407
408                 return false;
409
410         }
411         ldap_unbind($ldapconn);
412
413         $GLOBALS['log']->info("ldapauth.ldap_rdn_lookup: Search result:\nldapauth.ldap_rdn_lookup: " . count($info));
414
415         if ($bind_attr == "dn") {
416                         $found_bind_user = $info[0]['dn'];
417         } else {
418                 $found_bind_user = $info[0][strtolower($bind_attr)][0];
419         }
420
421         $GLOBALS['log']->info("ldapauth.ldap_rdn_lookup: found_bind_user=" . $found_bind_user);
422
423         if (!empty($found_bind_user)) {
424             return $found_bind_user;
425         } elseif ($user_attr == $bind_attr) {
426             return $user_name;
427         } else {
428             return false;
429         }
430     }
431
432
433
434
435
436
437
438
439
440 }
441
442 ?>