still working at OpenID
[living-lab-site.git] / application / models / users_model.php
1 <?php
2
3 /**
4  * Class Users_model models user information from DB
5  * 
6  * @category    Model
7  * @author              calinburloiu
8  *
9  */
10 class Users_model extends CI_Model {
11         public $db = NULL;
12
13         public function __construct()
14         {
15                 parent::__construct();
16
17                 if ($this->db === NULL)
18                 {
19                         $this->load->library('singleton_db');
20                         $this->db = $this->singleton_db->connect();
21                 }
22         }
23
24         /**
25          * Check authentication credentials. $username can be username or e-mail.
26          * 
27          * @param string $username
28          * @param string $password
29          * @return mixed can return FALSE if authentication failed, a DB row as an
30          * associative array if authentication was succesful or an associative
31          * array with LDAP user information if authentication with LDAP was
32          * successful but the user logged in for the first time and it does not
33          * have an entry in `users` table yet. The key 'auth_src' distinguishes
34          * which associative array was returned:
35          * <ul>
36          *   <li>'internal' or 'ldap': a DB row</li>
37          *   <li>'ldap_first_time': LDAP user information</li>
38          * </ul>
39          */
40         public function login($username, $password)
41         {
42                 $this->load->helper('email');
43                 
44                 // User logs with e-mail address.
45                 if (! valid_email($username))
46                         $cond_user = "username = '$username'";
47                 else
48                         $cond_user = "email = '$username'";
49                 
50                 $enc_password = sha1($password);
51                 
52                 // TODO select only required fields.
53                 $query = $this->db->query("SELECT u.*, a.activation_code
54                         FROM `users` u LEFT JOIN `users_unactivated` a ON (u.id = a.user_id)
55                         WHERE $cond_user
56                                 AND (auth_src = 'ldap' OR password = '$enc_password')");
57                 
58                 // It is possible that the user has a LDAP account but he's
59                 // authenticating here for the first time so it does not have an entry
60                 // in `users` table.
61                 if ($query->num_rows() === 0)
62                 {
63                         $ldap_userdata = $this->ldap_login($username, $password);
64                         if ($ldap_userdata === FALSE)
65                                 return FALSE;
66                         $userdata = $this->convert_ldap_userdata($ldap_userdata);
67                         $this->register($userdata);
68                         
69                         $user = $this->login($username, $password);
70                         $user['import'] = TRUE;
71                         return $user;
72                 }
73                 
74                 $user = $query->row_array();
75                 
76                 // Authenticate with LDAP.
77                 if ($user['auth_src'] == 'ldap'
78                                 && ! $this->ldap_login($username, $password))
79                         return FALSE; 
80                 
81                 if (empty($user['email']) || empty($user['first_name'])
82                                 || empty($user['last_name']))
83                         $user['import'] = TRUE;
84                 
85                 // Update last login time.
86                 $this->db->query("UPDATE `users`
87                         SET last_login = UTC_TIMESTAMP()
88                         WHERE username = '$username'");
89                 
90                 // If we are here internal authentication has successful.
91                 return $user;
92         }
93         
94         /**
95          * Converts an array returned by LDAP login to an array which contains
96          * user data ready to be used in `users` DB.
97          * 
98          * @param array $ldap_userdata
99          * @return array
100          */
101         public function convert_ldap_userdata($ldap_userdata)
102         {
103                 $userdata['username'] = $ldap_userdata['uid'][0];
104                 $userdata['email'] = $ldap_userdata['mail'][0];
105                 $userdata['first_name'] = $ldap_userdata['givenname'][0];
106                 $userdata['last_name'] = $ldap_userdata['sn'][0];
107                 
108                 $userdata['auth_src'] = 'ldap';
109                 
110                 return $userdata;
111         }
112         
113         /**
114         * Login with LDAP.
115         *
116         * @param string $username
117         * @param string $password
118         * @return boolean
119         * @author  Alex Herișanu, Răzvan Deaconescu, Călin-Andrei Burloiu
120         */
121         public function ldap_login($username, $password)
122         {
123                 $this->config->load('ldap');
124                 
125                 // First connection: binding.
126                 // TODO exception
127                 $ds = ldap_connect($this->config->item('ldap_server')) or die("Can't connect to ldap server.\n");
128                 if (!@ldap_bind($ds, $this->config->item('ldap_bind_user'),
129                         $this->config->item('ldap_bind_password'))) 
130                 {
131                         ldap_close($ds);
132                         die("Can't connect to ".$this->config->item('ldap_server')."\n");
133                         return FALSE;
134                 }
135                 $sr = ldap_search($ds, "dc=cs,dc=curs,dc=pub,dc=ro", "(uid=" . $username . ")");
136                 if (ldap_count_entries($ds, $sr) > 1)
137                 die("Multiple entries with the same uid in LDAP database??");
138                 if (ldap_count_entries($ds, $sr) < 1) {
139                         ldap_close($ds);
140                         return FALSE;
141                 }
142                 
143                 $info = ldap_get_entries($ds, $sr);
144                 $dn = $info[0]["dn"];
145                 ldap_close($ds);
146                 
147                 // Second connection: connect with user's credentials.
148                 $ds = ldap_connect($this->config->item('ldap_server')) or die("Can't connect to ldap server\n");
149                 if (!@ldap_bind($ds, $dn, $password) or $password == '') {
150                         ldap_close($ds);
151                         return FALSE;
152                 }
153                 
154                 // Verifify if DN belongs to the requested OU.
155                 $info[0]['ou_ok'] = $this->ldap_dn_belongs_ou( $dn, $this->config->item('ldap_req_ou') );
156                 
157                 // Set authentication source.
158                 $info[0]['auth_src'] = 'ldap_first_time';
159                 
160                 return $info[0];
161         }
162         
163         /**
164         * Verify if a user belongs to a group.
165         * 
166         * @param string $dn = "ou=Student,ou=People..."
167         * @param array $ou = array ("Student", etc
168         * @return TRUE or FALSE
169         * @author  Răzvan Herișanu, Răzvan Deaconescu, Călin-Andrei Burloiu
170         */
171         public function ldap_dn_belongs_ou($dn, $ou)
172         {
173                 if (!is_array($ou))
174                         $ou = array ($ou);
175                 
176                 $founded = FALSE;
177                 $words = explode(',', $dn);
178                 foreach ($words as $c) {
179                         $parts = explode("=", $c);
180                         $key = $parts[0];
181                         $value = $parts[1];
182                 
183                         if (strtolower($key) == "ou" && in_array($value, $ou) )
184                                 $founded = TRUE;
185                 }
186                 
187                 return $founded;
188         }
189         
190         /**
191          * Adds a new user to DB.
192          * Do not add join_date and last_login column, they will be automatically
193          * added.
194          * 
195          * @param array $data   corresponds to DB columns
196          */
197         public function register($data)
198         {
199                 $this->load->helper('array');
200                 
201                 // TODO verify mandatory data existance
202                 
203                 // Process data.
204                 if (isset($data['password']))
205                         $data['password'] = sha1($data['password']);
206                 // TODO picture data: save, convert, make it thumbnail
207                 
208                 if (empty($data['birth_date']))
209                         $data['birth_date'] = NULL;
210                 
211                 $cols = '';
212                 $vals = '';
213                 foreach ($data as $col=> $val)
214                 {
215                         if ($val === NULL)
216                         {
217                                 $cols .= "$col, ";
218                                 $vals .= "NULL, ";
219                                 continue;
220                         }
221                                 
222                         $cols .= "$col, ";
223                         if (is_int($val))
224                                 $vals .= "$val, ";
225                         else if (is_string($val))
226                                 $vals .= "'$val', ";
227                 }
228                 $cols = substr($cols, 0, -2);
229                 $vals = substr($vals, 0, -2);
230                 
231                 $query = $this->db->query("INSERT INTO `users`
232                         ($cols, registration_date, last_login)
233                         VALUES ($vals, utc_timestamp(), utc_timestamp())");
234                 if ($query === FALSE)
235                         return FALSE;
236                 
237                 // If registered with internal authentication it needs to activate
238                 // the account.
239                 if ($data['auth_src'] == 'internal')
240                 {
241                         $activation_code = Users_model::gen_activation_code($data['username']);
242                         $user_id = $this->get_user_id($data['username']);
243                         $query = $this->db->query("INSERT INTO `users_unactivated`
244                                 (user_id, activation_code)
245                                 VALUES ($user_id, '$activation_code')");
246                         $this->send_activation_email($user_id, $data['email'],
247                                 $activation_code, $data['username']);
248                 }
249                 
250                 // TODO exception on failure
251                 return $query;
252         }
253         
254         public function get_user_id($username)
255         {
256                 $query = $this->db->query("SELECT id FROM `users`
257                         WHERE username = '$username'");
258                 
259                 if ($query->num_rows() === 0)
260                         return FALSE;
261                 
262                 return $query->row()->id;
263         }
264         
265         // TODO cleanup account activation
266         public function cleanup_account_activation()
267         {
268                 
269         }
270         
271         /**
272          * Activated an account for an user having $user_id with $activation_code.
273          * 
274          * @param int $user_id
275          * @param string $activation_code       hexa 16 characters string
276          * @return returns TRUE if activation was successful and FALSE otherwise
277          */
278         public function activate_account($user_id, $activation_code)
279         {
280                 $query = $this->db->query("SELECT * FROM `users_unactivated`
281                         WHERE user_id = $user_id
282                                 AND activation_code = '$activation_code'");
283                 
284                 if ($query->num_rows() === 0)
285                         return FALSE;
286                 
287                 $this->db->query("DELETE FROM `users_unactivated`
288                         WHERE user_id = $user_id");
289                 
290                 return TRUE;
291         }
292         
293         public function send_activation_email($user_id, $email = NULL,
294                         $activation_code = NULL, $username = NULL)
295         {
296                 if (!$activation_code || !$email || !$username)
297                 {
298                         if (!$email)
299                                 $cols = 'email, ';
300                         else
301                                 $cols = '';
302                         
303                         $userdata = $this->get_userdata($user_id,
304                                         $cols. "a.activation_code, username");
305                         $activation_code =& $userdata['activation_code'];
306                         
307                         if (!$email)
308                                 $email =& $userdata['email'];
309                         $username =& $userdata['username'];
310                 }
311                 
312                 if ($activation_code === NULL)
313                         return TRUE;
314                 
315                 $subject = '['. $this->config->item('site_name')
316                                 . '] Account Activation';
317                 $activation_url =
318                                 site_url("user/activate/$user_id/code/$activation_code"); 
319                 $msg = sprintf($this->lang->line('user_activation_email_content'),
320                         $username, $this->config->item('site_name'), site_url(),
321                         $activation_url, $activation_code);
322                 $headers = "From: ". $this->config->item('noreply_email');
323                 
324                 return mail($email, $subject, $msg, $headers);
325         }
326         
327         public function recover_password($username, $email)
328         {
329                 $userdata = $this->get_userdata($username, 'email, username, id');
330                 
331                 if (strcmp($userdata['email'], $email) !== 0)
332                         return FALSE;
333                 
334                 $recovered_password = Users_model::gen_password();
335                 
336                 $this->set_userdata(intval($userdata['id']), array('password'=> 
337                                 $recovered_password));
338                 
339                 $subject = '['. $this->config->item('site_name')
340                 . '] Password Recovery';
341                 $msg = sprintf($this->lang->line('user_password_recovery_email_content'),
342                         $username, $this->config->item('site_name'), site_url(),
343                         $recovered_password);
344                 $headers = "From: ". $this->config->item('noreply_email');
345                 
346                 mail($email, $subject, $msg, $headers);
347                 
348                 return TRUE;
349         }
350         
351         /**
352          * Returns data from `users` table. If $user is int it is used as an
353          * id, if it is string it is used as an username.
354          * 
355          * @param mixed $user
356          * @param string $table_cols    (optional) string with comma separated
357          * `users` table column names. Use a.activation_code to check user's
358          * account activation_code. If this value is NULL than the account is
359          * active.
360          * @return array        associative array with userdata from DB
361          */
362         public function get_userdata($user, $table_cols = '*')
363         {
364                 if (is_int($user))
365                         $cond = "id = $user";
366                 else
367                         $cond = "username = '$user'";
368                 
369                 $query = $this->db->query("SELECT $table_cols
370                         FROM `users` u LEFT JOIN `users_unactivated` a
371                                 ON (u.id = a.user_id)
372                         WHERE $cond");
373                 
374                 if ($query->num_rows() === 0)
375                         return FALSE;
376                 
377                 $userdata = $query->row_array();
378                 
379                 // Post process userdata.
380                 if (isset($userdata['picture']))
381                 {
382                         $userdata['picture_thumb'] = site_url(
383                                 "data/user_pictures/{$userdata['picture']}-thumb.jpg");
384                         $userdata['picture'] = site_url(
385                                 "data/user_pictures/{$userdata['picture']}");
386                 } 
387                 
388                 return $userdata;
389         }
390         
391         /**
392          * Modifies data from `users` table for user with $user_id.
393          * 
394          * @param int $user_id
395          * @param array $data   key-value pairs with columns and new values to be
396          * modified
397          * @return boolean      returns TRUE on success and FALSE otherwise
398          */
399         public function set_userdata($user_id, $data)
400         {
401                 // TODO verify mandatory data existance
402                 
403                 // Process data.
404                 if (isset($data['password']))
405                         $data['password'] = sha1($data['password']);
406                 // TODO picture data: save, convert, make it thumbnail
407                 
408                 if (empty($data['birth_date']))
409                         $data['birth_date'] = NULL;
410                 
411                 $set = '';
412                 foreach ($data as $col => $val)
413                 {
414                         if ($val === NULL)
415                         {
416                                 $set .= "$col = NULL, ";
417                                 continue;
418                         }
419                         
420                         if (is_int($val))
421                                 $set .= "$col = $val, ";
422                         else if (is_string($val))
423                                 $set .= "$col = '$val', ";
424                 }
425                 $set = substr($set, 0, -2);
426                 
427                 $query_str = "UPDATE `users`
428                         SET $set WHERE id = $user_id";
429                 //echo "<p>$query_str</p>";
430                 $query = $this->db->query($query_str);
431                 
432                 // TODO exception
433                 return $query;
434         }
435         
436         public static function gen_activation_code($str = '')
437         {
438                 $ci =& get_instance();
439                 
440                 $activation_code = substr(
441                         sha1(''. $str. $ci->config->item('encryption_key')
442                                 . mt_rand()),
443                         0,
444                         16);
445                 
446                 return $activation_code;
447         }
448         
449         public static function gen_password()
450         {
451                 $ci =& get_instance();
452                 $length = 16;
453                 $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.?!_-';
454                 $len_chars = strlen($chars);
455                 $enc_key = $ci->config->item('encryption_key');
456                 $len_enc_key = strlen($enc_key);
457                 $password = '';
458                 
459                 for ($p = 0; $p < $length; $p++) 
460                 {
461                         $i = (mt_rand(1, 100) * ord($enc_key[ mt_rand(0, $len_enc_key-1) ]))
462                                 % $len_chars;
463                         $password .= $chars[$i];
464                 }
465                 
466                 return $password;
467         } 
468         
469         public static function roles_to_string($roles)
470         {
471                 $ci =& get_instance();
472                 $ci->lang->load('user');
473                 
474                 if ($roles == USER_ROLE_STANDARD)
475                         return $ci->lang->line('user_role_standard');
476                 else
477                 {
478                         $str_roles = '';
479                         
480                         if ($roles & USER_ROLE_ADMIN)
481                                 $str_roles .= $ci->lang->line('user_role_admin') . '; ';
482                 }
483                 
484                 return $str_roles;
485         }
486 }
487
488 /* End of file users_model.php */
489 /* Location: ./application/models/users_model.php */