diff options
Diffstat (limited to 'application/controllers/User.php')
-rw-r--r-- | application/controllers/User.php | 717 |
1 files changed, 717 insertions, 0 deletions
diff --git a/application/controllers/User.php b/application/controllers/User.php new file mode 100644 index 000000000..00d348240 --- /dev/null +++ b/application/controllers/User.php @@ -0,0 +1,717 @@ +<?php +/* + * Copyright 2012-2013 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class User extends MY_Controller { + + function __construct() + { + parent::__construct(); + } + + function index() + { + if (is_cli()) { + $this->load->library("../controllers/tools"); + return $this->tools->index(); + } + + $this->data["username"] = $this->muser->get_username(); + + $this->load->view('header', $this->data); + $this->load->view('user/index', $this->data); + $this->load->view('footer', $this->data); + } + + function test_login() + { + $username = $this->input->post('username'); + $password = $this->input->post('password'); + + if ($this->muser->login($username, $password)) { + $this->output->set_status_header(204); + } else { + $this->output->set_status_header(403); + } + } + + function login() + { + $redirect_uri = $this->input->get("redirect_uri"); + $this->muser->require_session(); + + if (!preg_match('/^[0-9a-zA-Z\/_-]*$/', $redirect_uri)) { + $redirect_uri = '/'; + } + + if ($this->muser->logged_in()) { + redirect($redirect_uri); + } + + $this->data['redirect_uri'] = $redirect_uri; + + if ($this->input->post('process') !== null) { + $username = $this->input->post('username'); + $password = $this->input->post('password'); + + $result = $this->muser->login($username, $password); + + if ($result !== true) { + $this->data['login_error'] = true; + $this->load->view('header', $this->data); + $this->load->view('user/login', $this->data); + $this->load->view('footer', $this->data); + } else { + redirect($redirect_uri); + } + } else { + $this->load->view('header', $this->data); + $this->load->view('user/login', $this->data); + $this->load->view('footer', $this->data); + } + } + + function create_apikey() + { + $this->muser->require_access(); + + $userid = $this->muser->get_userid(); + $comment = $this->input->post("comment"); + $comment = $comment === null ? "" : $comment; + $access_level = $this->input->post("access_level"); + + if ($access_level === null) { + $access_level = "apikey"; + } + + $key = \service\user::create_apikey($userid, $comment, $access_level); + + redirect("user/apikeys"); + } + + function delete_apikey() + { + $this->muser->require_access(); + + $userid = $this->muser->get_userid(); + $key = $this->input->post("key"); + + $this->db->where('user', $userid) + ->where('key', $key) + ->delete('apikeys'); + + redirect("user/apikeys"); + } + + function apikeys() + { + $this->muser->require_access(); + + $userid = $this->muser->get_userid(); + $apikeys = \service\user::apikeys($userid); + $this->data["query"] = $apikeys["apikeys"]; + + $this->load->view('header', $this->data); + $this->load->view('user/apikeys', $this->data); + $this->load->view('footer', $this->data); + } + + function create_invitation_key() + { + $this->duser->require_implemented("can_register_new_users"); + $this->muser->require_access(); + + $userid = $this->muser->get_userid(); + + \service\user::create_invitation_key($userid); + + redirect("user/invite"); + } + + function delete_invitation_key() + { + $this->duser->require_implemented("can_register_new_users"); + $this->muser->require_access(); + + $userid = $this->muser->get_userid(); + $key = $this->input->post("key"); + + \service\user::delete_invitation_key($userid, $key); + redirect("user/invite"); + } + + function invite() + { + $this->duser->require_implemented("can_register_new_users"); + $this->muser->require_access(); + + $userid = $this->muser->get_userid(); + + $query = $this->db->select('key, date') + ->from('actions') + ->where('user', $userid) + ->where('action', 'invitation') + ->get()->result_array(); + + $this->data["query"] = $query; + + $this->load->view('header', $this->data); + $this->load->view('user/invite', $this->data); + $this->load->view('footer', $this->data); + } + + function register() + { + $this->duser->require_implemented("can_register_new_users"); + $key = $this->uri->segment(3); + $process = $this->input->post("process"); + $values = array( + "username" => "", + "email" => "" + ); + $error = array(); + + $query = $this->muser->get_action("invitation", $key); + + $referrer = $query["user"]; + + $this->data['redirect_uri'] = "/"; + + if ($process !== null) { + $username = $this->input->post("username"); + $email = $this->input->post("email"); + $password = $this->input->post("password"); + $password_confirm = $this->input->post("password_confirm"); + + if (!$this->muser->valid_username($username)) { + $error[]= "Invalid username (only up to 32 chars of a-z0-9 are allowed)."; + } else { + if ($this->muser->username_exists($username)) { + $error[] = "Username already exists."; + } + } + + if (!$this->muser->valid_email($email)) { + $error[]= "Invalid email."; + } + + if (!$password || $password !== $password_confirm) { + $error[]= "No password or passwords don't match."; + } + + if (empty($error)) { + $this->muser->add_user($username, $password, $email, $referrer); + + $this->db->where('key', $key) + ->delete('actions'); + + $this->load->view('header', $this->data); + $this->load->view('user/registered', $this->data); + $this->load->view('footer', $this->data); + return; + } else { + $values["username"] = $username; + $values["email"] = $email; + } + } + + $this->data["key"] = $key; + $this->data["values"] = $values; + $this->data["error"] = $error; + + $this->load->view('header', $this->data); + $this->load->view('user/register', $this->data); + $this->load->view('footer', $this->data); + } + + public function delete_account() + { + $this->muser->require_access(); + $this->duser->require_implemented("can_delete_account"); + + if ($_SERVER["REQUEST_METHOD"] == "GET") { + return $this->_delete_account_form(); + } elseif ($_SERVER["REQUEST_METHOD"] == "POST") { + return $this->_delete_account_process(); + } + } + + public function _delete_account_form() + { + $this->data['username'] = $this->muser->get_username(); + + $this->load->view('header', $this->data); + $this->load->view('user/delete_account_form', $this->data); + $this->load->view('footer', $this->data); + } + + public function _delete_account_process() + { + $username = $this->muser->get_username(); + $password = $this->input->post("password"); + + $useremail = $this->muser->get_email($this->muser->get_userid()); + + if ($this->muser->delete_user($username, $password)) { + $this->muser->logout(); + + $this->load->library("email"); + $this->email->from($this->config->item("email_from")); + $this->email->to($useremail); + $this->email->subject("FileBin account deleted"); + $this->email->message("" + ."Your FileBin account '${username}' at ".site_url()."\n" + ."has been permemently deleted.\n" + ."\n" + ."The request has been sent from the IP address '${_SERVER["REMOTE_ADDR"]}'\n" + ."and was confirmed with your password.\n" + ."\n" + ."Thank you for using FileBin!\n" + ); + $this->email->send(); + unset($this->data['username']); + unset($this->data['user_logged_in']); + + $this->load->view('header', $this->data); + $this->load->view('user/delete_account_success', $this->data); + $this->load->view('footer', $this->data); + return; + } else { + $this->data['alerts'][] = array( + "type" => "danger", + "message" => "Your password was incorrect", + ); + return $this->_delete_account_form(); + } + } + + // This routes the different steps of a password reset + function reset_password() + { + $this->duser->require_implemented("can_reset_password"); + $key = $this->uri->segment(3); + + if ($_SERVER["REQUEST_METHOD"] == "GET" && $key === null) { + return $this->_reset_password_username_form(); + } + + if ($key === null) { + return $this->_reset_password_send_mail(); + } + + if ($key !== null) { + return $this->_reset_password_form(); + } + } + + // This simply queries the username + function _reset_password_username_form() + { + $this->data['username'] = $this->muser->get_username(); + + $this->load->view('header', $this->data); + $this->load->view('user/reset_password_username_form', $this->data); + $this->load->view('footer', $this->data); + } + + // This sends a mail to the user containing the reset link + function _reset_password_send_mail() + { + $key = random_alphanum(12, 16); + $username = $this->input->post("username"); + + if (!$this->muser->username_exists($username)) { + throw new \exceptions\UserInputException("user/reset_password/invalid-username", "Invalid username"); + } + + $userinfo = $this->db->select('id, email, username') + ->from('users') + ->where('username', $username) + ->get()->row_array(); + + $this->load->library("email"); + + $this->db->set(array( + 'key' => $key, + 'user' => $userinfo['id'], + 'date' => time(), + 'action' => 'passwordreset' + )) + ->insert('actions'); + + $this->email->from($this->config->item("email_from")); + $this->email->to($userinfo["email"]); + $this->email->subject("FileBin password reset"); + $this->email->message("" + ."Someone requested a password reset for the account '${userinfo["username"]}'\n" + ."from the IP address '${_SERVER["REMOTE_ADDR"]}'.\n" + ."\n" + ."Please follow this link to reset your password:\n" + .site_url("user/reset_password/$key") + ); + $this->email->send(); + + // don't disclose full email addresses + $this->data["email_domain"] = substr($userinfo["email"], strpos($userinfo["email"], "@") + 1); + + $this->load->view('header', $this->data); + $this->load->view('user/reset_password_link_sent', $this->data); + $this->load->view('footer', $this->data); + } + + // This displays a form and handles the reset if the form has been filled out correctly + function _reset_password_form() + { + $process = $this->input->post("process"); + $key = $this->uri->segment(3); + $error = array(); + + $query = $this->muser->get_action("passwordreset", $key); + + $userid = $query["user"]; + + if ($process !== null) { + $password = $this->input->post("password"); + $password_confirm = $this->input->post("password_confirm"); + + if (!$password || $password !== $password_confirm) { + $error[]= "No password or passwords don't match."; + } + + if (empty($error)) { + $this->muser->set_password($userid, $password); + + $this->db->where('key', $key) + ->delete('actions'); + + $this->load->view('header', $this->data); + $this->load->view('user/reset_password_success', $this->data); + $this->load->view('footer', $this->data); + return; + } + } + + $this->data["key"] = $key; + $this->data["error"] = $error; + + $this->load->view('header', $this->data); + $this->load->view('user/reset_password_form', $this->data); + $this->load->view('footer', $this->data); + } + + public function change_email() + { + $this->duser->require_implemented("can_change_email"); + $key = $this->uri->segment(3); + $action = $this->uri->segment(4); + + $alerts = array(); + + $query = $this->muser->get_action("change_email", $key); + + $userid = $query["user"]; + $data = json_decode($query['data'], true); + + switch ($action) { + case 'confirm': + $this->db->where('id', $userid) + ->update('users', array( + "email" => $data['new_email'], + )); + $alerts[] = array( + "type" => "success", + "message" => "Your email address has been updated", + ); + break; + case 'reject': + $this->db->where('id', $userid) + ->update('users', array( + "email" => $data['old_email'], + )); + foreach ($data['keys'] as $k) { + $this->db->where('key', $k) + ->delete('actions'); + } + $alerts[] = array( + "type" => "success", + "message" => "Your email change request has been canceled and/or your old email address has been restored", + ); + break; + default: + assert(false); + break; + } + + $this->data["alerts"] = $alerts; + + return $this->profile(); + } + + function profile() + { + $this->muser->require_access(); + + if ($this->input->post("process") !== null) { + $this->_save_profile(); + } + + $this->data["profile_data"] = $this->muser->get_profile_data(); + + $this->load->view('header', $this->data); + $this->load->view('user/profile', $this->data); + $this->load->view('footer', $this->data); + } + + private function _save_profile() + { + $this->muser->require_access(); + + $old = $this->muser->get_profile_data(); + + /* + * Key = name of the form field + * Value = function that sanatizes the value and returns it + * TODO: some kind of error handling that doesn't loose correctly filled out fields + */ + $value_processor = array(); + $alerts = array(); + + $value_processor["upload_id_limits"] = function($value) { + $values = explode("-", $value); + + if (!is_array($values) || count($values) != 2) { + throw new \exceptions\UserInputException("user/profile/invalid-upload-id-limit", "Invalid upload id limit value"); + } + + $lower = intval($values[0]); + $upper = intval($values[1]); + + if ($lower > $upper) { + throw new \exceptions\UserInputException("user/profile/lower-bigger-than-upper", "lower limit > upper limit"); + } + + if ($lower < 3 || $upper > 64) { + throw new \exceptions\UserInputException("user/profile/limit-out-of-bounds", "upper or lower limit out of bounds (3-64)"); + } + + return $lower."-".$upper; + }; + + $value_processor["email"] = function($value) use ($old, &$alerts) { + if (!$this->duser->is_implemented("can_change_email")) { + return null; + } + + if ($value === $old["email"]) { + return null; + } + + if (!$this->muser->valid_email($value)) { + throw new \exceptions\UserInputException("user/profile/invalid-email", "Invalid email"); + } + + $this->load->library("email"); + $keys = array( + "old" => random_alphanum(12,16), + "new" => random_alphanum(12,16), + ); + $emails = array( + array( + "key" => $keys['old'], + "email" => $old['email'], + "user" => $this->muser->get_userid(), + ), + array( + "key" => $keys['new'], + "email" => $value, + "user" => $this->muser->get_userid(), + ), + ); + + foreach ($emails as $email) { + $key = $email['key']; + + $this->db->set(array( + 'key' => $key, + 'user' => $this->muser->get_userid(), + 'date' => time(), + 'action' => 'change_email', + 'data' => json_encode(array( + 'old_email' => $old['email'], + 'new_email' => $value, + 'keys' => $keys, + )), + )) + ->insert('actions'); + + $this->email->from($this->config->item("email_from")); + $this->email->to($email['email']); + $this->email->subject("FileBin email change confirmation"); + $this->email->message("" + ."A request has been sent to change the email address of account '${old["username"]}'\n" + ."from ".$old['email']." to $value.\n" + ."\n" + ."Please follow this link to CONFIRM the change:\n" + .site_url("user/change_email/$key/confirm")."\n\n" + ."Please follow this link to REJECT the change:\n" + .site_url("user/change_email/$key/reject")."\n\n" + ); + $this->email->send(); + $this->email->clear(); + } + + $alerts[] = array( + "type" => "info", + "message" => "Reset and confirmation emails have been sent to your new and old address. Until your new address is confirmed the old one will be displayed and used.", + ); + + return null; + }; + + + $data = array(); + foreach (array_keys($value_processor) as $field) { + $value = $this->input->post($field); + + if ($value !== null) { + $new_value = $value_processor[$field]($value); + if ($new_value !== null) { + $data[$field] = $new_value; + } + } + } + + if (!empty($data)) { + $this->muser->update_profile($data); + } + + $alerts[] = array( + "type" => "success", + "message" => "Changes saved", + ); + $this->data["alerts"] = $alerts; + + return true; + } + + function logout() + { + $this->muser->logout(); + redirect('/'); + } + + function hash_password() + { + $process = $this->input->post("process"); + $password = $this->input->post("password"); + $password_confirm = $this->input->post("password_confirm"); + $this->data["hash"] = false; + $this->data["password"] = $password; + + if ($process !== null) { + if (!$password || $password !== $password_confirm) { + $error[]= "No password or passwords don't match."; + } else { + $this->data["hash"] = $this->muser->hash_password($password); + } + } + + $this->load->view('header', $this->data); + $this->load->view('user/hash_password', $this->data); + $this->load->view('footer', $this->data); + } + + function cron() + { + $this->_require_cli_request(); + + if ($this->config->item('actions_max_age') == 0) return; + + $oldest_time = (time() - $this->config->item('actions_max_age')); + + $this->db->where('date <', $oldest_time) + ->delete('actions'); + } + + private function _get_line_cli($message, $verification_func = NULL) + { + echo "$message: "; + + while ($line = fgets(STDIN)) { + $line = trim($line); + if ($verification_func === NULL) { + return $line; + } + + if ($verification_func($line)) { + return $line; + } else { + echo "$message: "; + } + } + } + + function add_user() + { + $this->_require_cli_request(); + $this->duser->require_implemented("can_register_new_users"); + + $error = array(); + + $username = $this->_get_line_cli("Username", function($username) { + if (!$this->muser->valid_username($username)) { + echo "Invalid username (only up to 32 chars of a-z0-9 are allowed).\n"; + return false; + } else { + if (get_instance()->muser->username_exists($username)) { + echo "Username already exists.\n"; + return false; + } + } + return true; + }); + + $email = $this->_get_line_cli("Email", function($email) { + if (!$this->muser->valid_email($email)) { + echo "Invalid email.\n"; + return false; + } + return true; + }); + + $password = $this->_get_line_cli("Password", function($password) { + if (!$password || $password === "") { + echo "No password supplied.\n"; + return false; + } + return true; + }); + + $this->muser->add_user($username, $password, $email, NULL); + + echo "User added\n"; + } + + function delete_user() + { + $this->_require_cli_request(); + $this->duser->require_implemented("can_delete_account"); + + echo "\nWARNING: Deleting a user will delete ALL their data permanently.\n\n"; + + $username = $this->_get_line_cli("Username", function($username) { + if (get_instance()->muser->username_exists($username)) { + return true; + } + return false; + }); + $this->muser->delete_user_real($username); + echo "User removed\n"; + } + +} |