CIS: CIWorker works; now we need communication via web services
[living-lab-site.git] / application / models / users_model.php
index 6faa6fa..30e18d7 100644 (file)
@@ -4,7 +4,7 @@
  * Class Users_model models user information from DB
  * 
  * @category   Model
- * @author             calinburloiu
+ * @author             Călin-Andrei Burloiu
  *
  */
 class Users_model extends CI_Model {
@@ -26,16 +26,8 @@ class Users_model extends CI_Model {
         * 
         * @param string $username
         * @param string $password
-        * @return mixed can return FALSE if authentication failed, a DB row as an
-        * associative array if authentication was succesful or an associative
-        * array with LDAP user information if authentication with LDAP was
-        * successful but the user logged in for the first time and it does not
-        * have an entry in `users` table yet. The key 'auth_src' distinguishes
-        * which associative array was returned:
-        * <ul>
-        *   <li>'internal' or 'ldap': a DB row</li>
-        *   <li>'ldap_first_time': LDAP user information</li>
-        * </ul>
+        * @return mixed can return FALSE if authentication failed, a `users`DB row
+        * as an associative array if authentication was successful
         */
        public function login($username, $password)
        {
@@ -49,7 +41,6 @@ class Users_model extends CI_Model {
                
                $enc_password = sha1($password);
                
-               // TODO select only required fields.
                $query = $this->db->query("SELECT u.*, a.activation_code
                        FROM `users` u LEFT JOIN `users_unactivated` a ON (u.id = a.user_id)
                        WHERE $cond_user
@@ -78,6 +69,11 @@ class Users_model extends CI_Model {
                                && ! $this->ldap_login($username, $password))
                        return FALSE; 
                
+               if (empty($user['username']) || empty($user['email'])
+                               || empty($user['first_name']) || empty($user['last_name'])
+                               || empty($user['country']))
+                       $user['import'] = TRUE;
+               
                // Update last login time.
                $this->db->query("UPDATE `users`
                        SET last_login = UTC_TIMESTAMP()
@@ -87,6 +83,238 @@ class Users_model extends CI_Model {
                return $user;
        }
        
+       /**
+        * Begin the OpenID login by redirecting user to the OP to authenticate.
+        * 
+        * @param string $openid 
+        */
+       public function openid_begin_login($openid)
+       {
+               $this->lang->load('openid');
+               $this->load->library('openid');
+
+               $request_to = site_url('user/check_openid_login');
+
+               $req = array('nickname');
+               $opt = array('fullname', 'email', 'dob', 'country');
+               $policy = site_url('user/openid_policy');
+
+               $ax_attributes[] = Auth_OpenID_AX_AttrInfo::make(
+                               'http://axschema.org/contact/email', 1, TRUE);
+               $ax_attributes[] = Auth_OpenID_AX_AttrInfo::make(
+                               'http://axschema.org/namePerson/first', 1, TRUE);
+               $ax_attributes[] = Auth_OpenID_AX_AttrInfo::make(
+                               'http://axschema.org/namePerson/last', 1, TRUE);
+               $ax_attributes[] = Auth_OpenID_AX_AttrInfo::make(
+                               'http://axschema.org/contact/country', 1, TRUE);
+
+               $this->openid->set_request_to($request_to);
+               $this->openid->set_trust_root(base_url());
+               $this->openid->set_sreg(TRUE, $req, $opt, $policy);
+               $this->openid->set_ax(TRUE, $ax_attributes);
+
+               // Redirection to OP site will follow.
+               $this->openid->authenticate($openid);
+       }
+       
+       /**
+        * Finalize the OpenID login. Register user if is here for the first time.
+        * 
+        * @return mixed returns a `users` DB row as an associative array if
+        * authentication was successful or Auth_OpenID_CANCEL/_FAILURE if it was
+        * unsuccessful.
+        */
+       public function openid_complete_login()
+       {
+               $this->lang->load('openid');
+               $this->load->library('openid');
+               
+               $request_to = site_url('user/check_openid_login');
+               $this->openid->set_request_to($request_to);
+
+               $response = $this->openid->get_response();
+               
+               if ($response->status === Auth_OpenID_CANCEL
+                               || $response->status === Auth_OpenID_FAILURE)
+                       return $response->status;
+
+               // Auth_OpenID_SUCCESS
+               $openid = $response->getDisplayIdentifier();
+               //$esc_openid = htmlspecialchars($openid, ENT_QUOTES);
+
+               // Get user_id to see if it's the first time the user logs in with
+               // OpenID.
+               $query = $this->db->query("SELECT * from `users_openid`
+                       WHERE openid_url = '$openid'");
+               $import = FALSE;
+               
+               // First time with OpenID => register user
+               if ($query->num_rows() === 0)
+               {
+                       $user_id = $this->openid_register($response);
+                       $import = TRUE;
+               }
+               // Not first time with OpenID.
+               else
+                       $user_id = $query->row()->user_id;
+               
+               // Login
+               $query = $this->db->query("SELECT * FROM `users`
+                       WHERE id = $user_id");
+               $userdata = $query->row_array();
+               $userdata['import'] = $import;
+               
+               if (empty($userdata['username']) || empty($userdata['email'])
+                               || empty($userdata['first_name'])
+                               || empty($userdata['last_name'])
+                               || empty($userdata['country']))
+                       $userdata['import'] = TRUE;
+               
+               // Update last login time.
+               $this->db->query("UPDATE `users`
+                       SET last_login = UTC_TIMESTAMP()
+                       WHERE id = $user_id");
+
+               return $userdata;               
+       }
+       
+       /**
+        * Register an user that logged in with OpenID for the first time.
+        * 
+        * @param object $op_response object returned by Janrain 
+        * Consumer::complete method.
+        * @return mixed the user_id inserted or FALSE on error
+        */
+       public function openid_register($op_response)
+       {
+               $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($op_response);
+               $ax_resp = Auth_OpenID_AX_FetchResponse::fromSuccessResponse($op_response);
+               
+               if ($ax_resp)
+               {
+                       $ax_email = $ax_resp->get('http://axschema.org/contact/email');
+                       $ax_first_name = $ax_resp->get(
+                                       'http://axschema.org/namePerson/first');
+                       $ax_last_name = $ax_resp->get('http://axschema.org/namePerson/last');
+                       $ax_country = $ax_resp->get('http://axschema.org/contact/country');
+               }
+               else
+               {
+                       $ax_email = '';
+                       $ax_first_name = '';
+                       $ax_last_name = '';
+                       $ax_country = '';
+               }
+               
+               if ($sreg_resp)
+               {
+                       $sreg_email = $sreg_resp->get('email', '');
+                       $sreg_fullname = $sreg_resp->get('fullname', '');
+                       $sreg_nickname = $sreg_resp->get('nickname', '');
+                       $sreg_country = $sreg_resp->get('country', '');
+                       $sreg_dob = $sreg_resp->get('dob', NULL);
+               }
+               else
+               {
+                       $sreg_email = $sreg_fullname = $sreg_nickname = $sreg_country = '';
+                       $sreg_dob = NULL;
+               }
+
+               // E-mail
+               if (empty($ax_email) || is_a($ax_email, 'Auth_OpenID_AX_Error'))
+                       $data['email'] = $sreg_email;
+               else
+                       $data['email'] = $ax_email[0];
+               $data['email'] = strtolower($data['email']);
+               
+               // First Name
+               if (empty($ax_first_name)
+                               || is_a($ax_first_name, 'Auth_OpenID_AX_Error'))
+                       $data['first_name'] = '';
+               else
+                       $data['first_name'] = $ax_first_name[0];
+               
+               // Sur Name
+               if (empty($ax_last_name) || is_a($ax_last_name, 'Auth_OpenID_AX_Error'))
+                       $data['last_name'] = '';
+               else
+                       $data['last_name'] = $ax_last_name[0];
+               
+               // First Name and Last Name
+               if (empty($data['first_name']) || empty($data['last_name']))
+               {
+                       if ($sreg_fullname)
+                       {
+                               if (empty($data['first_name']))
+                                       $data['first_name'] = substr(
+                                                       $sreg_fullname, 0, strrpos($sreg_fullname, ' '));
+                               if (empty($data['last_name']))
+                                       $data['last_name'] = substr(
+                                                       $sreg_fullname, strrpos($sreg_fullname, ' ') + 1);
+                       }
+               }
+               
+               // Username
+               $data['username'] = $sreg_nickname;
+               if (!$data['username'])
+               {
+                       // Generate username from email
+                       if (!empty($data['email']))
+                       {
+                               $data['username'] = substr($data['email'],
+                                               0, strpos($data['email'], '@'));
+                               $data['username'] = preg_replace(array('/[^a-z0-9\._]*/'),
+                                               array(''), $data['username']);
+                       }
+                       // Generate username from first name and sur name
+                       else if(!empty($data['first_name']) || !empty($data['last_name']))
+                       {
+                               $data['username'] = $data['first_name'] . '_'
+                                               . $data['last_name'];
+                       }
+                       // Generate a random username
+                       else
+                               $data['username'] = $this->gen_username();
+               }
+               // Limit username to 24 characters because a prefix of 8 characters
+               // will be added: 'autogen_'.
+               $data['username'] = substr($data['username'], 0, 24);
+               // Append a random character to the username each time it still exists.
+               if ($this->get_userdata($data['username']))
+               {
+                       $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
+                       $len_chars = strlen($chars);
+                       $data['username'] .= '_';
+                       do
+                       {
+                               $data['username'] .= $chars[ mt_rand(0, $len_chars - 1) ];
+                       } while($this->get_userdata($data['username']));
+               }
+               // Usernames autogenerated have 'autogen_' prefix and can be changed
+               // by the user from the account section. After this process it cannot
+               // be changed anymore.
+               $data['username'] = 'autogen_' . $data['username'];
+               
+               // Country
+               if (empty($ax_country) || is_a($ax_country, 'Auth_OpenID_AX_Error'))
+                       $data['country'] = $sreg_country;
+               else
+                       $data['country'] = $ax_country[0];
+               
+               // Birth Date
+               $data['birth_date'] = $sreg_dob;
+               
+               // OpenID
+               $data['auth_src'] = 'openid';
+               
+               if (!$this->register($data, $op_response->getDisplayIdentifier()))
+                       return FALSE;
+               
+               $query = $this->db->query("SELECT id from `users`
+                       WHERE username = '{$data['username']}'");
+               return $query->row()->id;
+       }
+       
        /**
         * Converts an array returned by LDAP login to an array which contains
         * user data ready to be used in `users` DB.
@@ -187,10 +415,12 @@ class Users_model extends CI_Model {
         * Adds a new user to DB.
         * Do not add join_date and last_login column, they will be automatically
         * added.
+        * Provide an $openid with the OpenID as value in order to register users
+        * logging in this way.
         * 
         * @param array $data   corresponds to DB columns
         */
-       public function register($data)
+       public function register($data, $openid = NULL)
        {
                $this->load->helper('array');
                
@@ -199,16 +429,25 @@ class Users_model extends CI_Model {
                // Process data.
                if (isset($data['password']))
                        $data['password'] = sha1($data['password']);
-               // TODO picture data: save, convert, make it thumbnail
+               
+               if (empty($data['birth_date']))
+                       $data['birth_date'] = NULL;
                
                $cols = '';
                $vals = '';
                foreach ($data as $col=> $val)
                {
+                       if ($val === NULL)
+                       {
+                               $cols .= "$col, ";
+                               $vals .= "NULL, ";
+                               continue;
+                       }
+                               
                        $cols .= "$col, ";
                        if (is_int($val))
                                $vals .= "$val, ";
-                       else
+                       else if (is_string($val))
                                $vals .= "'$val', ";
                }
                $cols = substr($cols, 0, -2);
@@ -217,17 +456,39 @@ class Users_model extends CI_Model {
                $query = $this->db->query("INSERT INTO `users`
                        ($cols, registration_date, last_login)
                        VALUES ($vals, utc_timestamp(), utc_timestamp())");
-               
                if ($query === FALSE)
                        return FALSE;
                
-               // If the registered with internal authentication it needs to activate
+               // If registered with OpenID insert a row in `users_openid`.
+               if ($openid)
+               {
+                       // Find user_id.
+                       $query = $this->db->query("SELECT id from `users`
+                               WHERE username = '{$data['username']}'");
+                       if ($query->num_rows() === 0)
+                               return FALSE;
+                       $user_id = $query->row()->id;
+                       
+                       // Insert row in `users_openid`.
+                       $query = $this->db->query("INSERT INTO `users_openid`
+                               (openid_url, user_id)
+                               VALUES ('$openid', $user_id)");
+                       if (!$query)
+                               return FALSE;
+               }
+               
+               // If registered with internal authentication it needs to activate
                // the account.
-               $activation_code = Users_model::gen_activation_code();
-               $user_id = $this->get_user_id($data['username']);
-               $query = $this->db->query("INSERT INTO `users_unactivated`
-                       (user_id, activation_code)
-                       VALUES ($user_id, '$activation_code')");
+               if ($data['auth_src'] == 'internal')
+               {
+                       $activation_code = Users_model::gen_activation_code($data['username']);
+                       $user_id = $this->get_user_id($data['username']);
+                       $query = $this->db->query("INSERT INTO `users_unactivated`
+                               (user_id, activation_code)
+                               VALUES ($user_id, '$activation_code')");
+                       $this->send_activation_email($user_id, $data['email'],
+                               $activation_code, $data['username']);
+               }
                
                // TODO exception on failure
                return $query;
@@ -244,10 +505,45 @@ class Users_model extends CI_Model {
                return $query->row()->id;
        }
        
-       // TODO cleanup account activation
-       public function cleanup_account_activation()
+       /**
+        * Removes users that didn't activated their account within $days_to_expire
+        * days inclusively.
+        * 
+        * @param int $days_to_expire 
+        */
+       public function cleanup_unactivated_users($days_to_expire)
        {
+               // Get user_id-s with expired activation period.
+               $query = $this->db->query("SELECT u.id
+                       FROM `users` u, `users_unactivated` a
+                       WHERE u.id = a.user_id
+                               AND DATEDIFF(CURRENT_DATE(), u.registration_date) > $days_to_expire");
+               
+               if ($query->num_rows() > 0)
+               {
+                       $str_user_ids = '';
+                       $results = $query->result();
+                       foreach ($results as $result)
+                               $str_user_ids .= "{$result->id}, ";
+                       $str_user_ids = substr($str_user_ids, 0, -2);
+               }
+               else
+                       return FALSE;
+               
+               // Delete from `users` table.
+               $ret = $this->db->query("DELETE FROM `users`
+                       WHERE id IN ($str_user_ids)");
+               if (!$ret)
+                       return FALSE;
                
+               // Delete from `users_unactivated table.
+               $ret = $this->db->query("DELETE FROM `users_unactivated`
+                       WHERE user_id IN ($str_user_ids)");
+               if (!$ret)
+                       return FALSE;
+               
+               // Success
+               return TRUE;
        }
        
        /**
@@ -272,6 +568,64 @@ class Users_model extends CI_Model {
                return TRUE;
        }
        
+       public function send_activation_email($user_id, $email = NULL,
+                       $activation_code = NULL, $username = NULL)
+       {
+               if (!$activation_code || !$email || !$username)
+               {
+                       if (!$email)
+                               $cols = 'email, ';
+                       else
+                               $cols = '';
+                       
+                       $userdata = $this->get_userdata($user_id,
+                                       $cols. "a.activation_code, username");
+                       $activation_code =& $userdata['activation_code'];
+                       
+                       if (!$email)
+                               $email =& $userdata['email'];
+                       $username =& $userdata['username'];
+               }
+               
+               if ($activation_code === NULL)
+                       return TRUE;
+               
+               $subject = '['. $this->config->item('site_name')
+                               . '] Account Activation';
+               $activation_url =
+                               site_url("user/activate/$user_id/code/$activation_code"); 
+               $msg = sprintf($this->lang->line('user_activation_email_content'),
+                       $username, $this->config->item('site_name'), site_url(),
+                       $activation_url, $activation_code);
+               $headers = "From: ". $this->config->item('noreply_email');
+               
+               return mail($email, $subject, $msg, $headers);
+       }
+       
+       public function recover_password($username, $email)
+       {
+               $userdata = $this->get_userdata($username, 'email, username, id');
+               
+               if (strcmp($userdata['email'], $email) !== 0)
+                       return FALSE;
+               
+               $recovered_password = Users_model::gen_password();
+               
+               $this->set_userdata(intval($userdata['id']), array('password'=> 
+                               $recovered_password));
+               
+               $subject = '['. $this->config->item('site_name')
+               . '] Password Recovery';
+               $msg = sprintf($this->lang->line('user_password_recovery_email_content'),
+                       $username, $this->config->item('site_name'), site_url(),
+                       $recovered_password);
+               $headers = "From: ". $this->config->item('noreply_email');
+               
+               mail($email, $subject, $msg, $headers);
+               
+               return TRUE;
+       }
+       
        /**
         * Returns data from `users` table. If $user is int it is used as an
         * id, if it is string it is used as an username.
@@ -298,7 +652,18 @@ class Users_model extends CI_Model {
                if ($query->num_rows() === 0)
                        return FALSE;
                
-               return $query->row_array();
+               $userdata = $query->row_array();
+               
+               // Post process userdata.
+               if (isset($userdata['picture']))
+               {
+                       $userdata['picture_thumb'] = site_url(
+                               "data/user_pictures/{$userdata['picture']}-thumb.jpg");
+                       $userdata['picture'] = site_url(
+                               "data/user_pictures/{$userdata['picture']}");
+               } 
+               
+               return $userdata;
        }
        
        /**
@@ -307,6 +672,7 @@ class Users_model extends CI_Model {
         * @param int $user_id
         * @param array $data   key-value pairs with columns and new values to be
         * modified
+        * @return boolean      returns TRUE on success and FALSE otherwise
         */
        public function set_userdata($user_id, $data)
        {
@@ -317,12 +683,21 @@ class Users_model extends CI_Model {
                        $data['password'] = sha1($data['password']);
                // TODO picture data: save, convert, make it thumbnail
                
+               if (empty($data['birth_date']))
+                       $data['birth_date'] = NULL;
+               
                $set = '';
                foreach ($data as $col => $val)
                {
+                       if ($val === NULL)
+                       {
+                               $set .= "$col = NULL, ";
+                               continue;
+                       }
+                       
                        if (is_int($val))
                                $set .= "$col = $val, ";
-                       else
+                       else if (is_string($val))
                                $set .= "$col = '$val', ";
                }
                $set = substr($set, 0, -2);
@@ -349,6 +724,39 @@ class Users_model extends CI_Model {
                return $activation_code;
        }
        
+       public static function gen_password()
+       {
+               $ci =& get_instance();
+               $length = 16;
+               $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.?!_-';
+               $len_chars = strlen($chars);
+               $enc_key = $ci->config->item('encryption_key');
+               $len_enc_key = strlen($enc_key);
+               $password = '';
+               
+               for ($p = 0; $p < $length; $p++) 
+               {
+                       $i = (mt_rand(1, 100) * ord($enc_key[ mt_rand(0, $len_enc_key-1) ]))
+                               % $len_chars;
+                       $password .= $chars[$i];
+               }
+               
+               return $password;
+       }
+       
+       public static function gen_username()
+       {
+               $chars = 'abcdefghijklmnopqrstuvwxyz0123456789._';
+               $len_chars = strlen($chars);
+               $len = 8;
+               $username = '';
+               
+               for ($i = 0; $i < $len; $i++)
+                       $username .= $chars[ mt_rand(0, $len_chars - 1) ];
+               
+               return $username;
+       }
+       
        public static function roles_to_string($roles)
        {
                $ci =& get_instance();