From 19f0aab3221dd7760387cbec745c1eca9b215af7 Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Sat, 9 Sep 2017 16:08:00 +0200 Subject: WIP: CI3 migration Signed-off-by: Florian Pritz --- application/models/Mfile.php | 275 ++++++++++++++++++++++++++ application/models/Mmultipaste.php | 184 +++++++++++++++++ application/models/Muser.php | 390 +++++++++++++++++++++++++++++++++++++ application/models/mfile.php | 275 -------------------------- application/models/mmultipaste.php | 184 ----------------- application/models/muser.php | 390 ------------------------------------- 6 files changed, 849 insertions(+), 849 deletions(-) create mode 100644 application/models/Mfile.php create mode 100644 application/models/Mmultipaste.php create mode 100644 application/models/Muser.php delete mode 100644 application/models/mfile.php delete mode 100644 application/models/mmultipaste.php delete mode 100644 application/models/muser.php (limited to 'application/models') diff --git a/application/models/Mfile.php b/application/models/Mfile.php new file mode 100644 index 000000000..977240c89 --- /dev/null +++ b/application/models/Mfile.php @@ -0,0 +1,275 @@ + + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Mfile extends CI_Model { + + private $upload_path; + + function __construct() + { + parent::__construct(); + $this->load->model("muser"); + + $this->upload_path = $this->config->item('upload_path'); + $this->id_validation_config = array( + "upload_max_age" => $this->config->item("upload_max_age"), + "small_upload_size" => $this->config->item("small_upload_size"), + "sess_expiration" => $this->config->item("sess_expiration"), + ); + } + + // Returns an unused ID + function new_id($min = 3, $max = 6) + { + static $id_blacklist = NULL; + + if ($id_blacklist == NULL) { + // This prevents people from being unable to access their uploads + // because of URL rewriting + $id_blacklist = scandir(FCPATH); + $id_blacklist[] = "file"; + $id_blacklist[] = "user"; + } + + $max_tries = 100; + + for ($try = 0; $try < $max_tries; $try++) { + $id = random_alphanum($min, $max); + + if ($this->id_exists($id) || in_array($id, $id_blacklist)) { + continue; + } + + return $id; + } + + throw new \exceptions\PublicApiException("file/new_id-try-limit", "Failed to find unused ID after $max_tries tries"); + } + + function id_exists($id) + { + if (!$id) { + return false; + } + + $query = $this->db->select('id') + ->from('files') + ->where('id', $id) + ->limit(1) + ->get(); + + return $query->num_rows() == 1; + } + + function get_filedata($id) + { + return cache_function("filedatav2-$id", 300, function() use ($id) { + $query = $this->db + ->select('files.id, hash, file_storage.id storage_id, filename, mimetype, files.date, user, filesize') + ->from('files') + ->join('file_storage', 'file_storage.id = files.file_storage_id') + ->where('files.id', $id) + ->limit(1) + ->get(); + + if ($query->num_rows() > 0) { + $data = $query->row_array(); + $data["data_id"] = $data["hash"]."-".$data["storage_id"]; + return $data; + } else { + return false; + } + }); + } + + // return the folder in which the file with $data_id is stored + function folder($data_id) { + return $this->upload_path.'/'.substr($data_id, 0, 3); + } + + // Returns the full path to the file with $data_id + function file($data_id) { + return $this->folder($data_id).'/'.$data_id; + } + + // Add a file to the DB + function add_file($userid, $id, $filename, $storage_id) + { + $this->db->insert("files", array( + "id" => $id, + "filename" => $filename, + "date" => time(), + "user" => $userid, + "file_storage_id" => $storage_id, + )); + } + + function adopt($id) + { + $userid = $this->muser->get_userid(); + + $this->db->set(array('user' => $userid )) + ->where('id', $id) + ->where('user', 0) + ->update('files'); + return $this->db->affected_rows(); + } + + // remove old/invalid/broken IDs + public function valid_id($id) + { + $filedata = $this->get_filedata($id); + + if (!$filedata) { + return false; + } + + return $this->valid_filedata($filedata); + } + + public function valid_filedata($filedata) + { + return \service\files::valid_id($filedata, $this->id_validation_config, $this, time()); + } + + public function file_exists($file) + { + return file_exists($file); + } + + public function filemtime($file) + { + return filemtime($file); + } + + public function filesize($file) + { + return filesize($file); + } + + public function get_timeout($id) + { + $filedata = $this->get_filedata($id); + $file = $this->file($filedata["data_id"]); + + if ($this->config->item("upload_max_age") == 0) { + return -1; + } + + if (filesize($file) > $this->config->item("small_upload_size")) { + return $filedata["date"] + $this->config->item("upload_max_age"); + } else { + return -1; + } + } + + public function get_timeout_string($id) + { + $timeout = $this->get_timeout($id); + + if ($timeout >= 0) { + return date("r", $timeout); + } else { + return "unknown"; + } + } + + private function unused_file($data_id) + { + list ($hash, $storage_id) = explode("-", $data_id); + $query = $this->db->select('files.id') + ->from('files') + ->join('file_storage', 'file_storage.id = files.file_storage_id') + ->where('hash', $hash) + ->where('file_storage.id', $storage_id) + ->limit(1) + ->get(); + + return $query->num_rows() == 0; + } + + public function delete_by_user($userid) + { + $query = $this->db->select("id") + ->where("user", $userid) + ->get("files")->result_array(); + $ids = array_map(function ($a) {return $a['id'];}, $query); + + foreach ($ids as $id) { + $this->delete_id($id); + } + } + + public function delete_id($id) + { + $filedata = $this->get_filedata($id); + + // Delete the file and all multipastes using it + // Note that this does not delete all relations in multipaste_file_map + // which is actually done by an SQL contraint. + // TODO: make it work properly without the constraint + $map = $this->db->select('url_id') + ->distinct() + ->from('multipaste_file_map') + ->join("multipaste", "multipaste.multipaste_id = multipaste_file_map.multipaste_id") + ->where('file_url_id', $id) + ->get()->result_array(); + + $this->db->where('id', $id) + ->delete('files'); + delete_cache("filedata-$id"); + + foreach ($map as $entry) { + assert(!empty($entry['url_id'])); + $this->mmultipaste->delete_id($entry["url_id"]); + } + + if ($this->id_exists($id)) { + return false; + } + + if ($filedata !== false) { + assert(isset($filedata["data_id"])); + if ($this->unused_file($filedata['data_id'])) { + $this->delete_data_id($filedata['data_id']); + } + } + return true; + } + + public function delete_data_id($data_id) + { + list ($hash, $storage_id) = explode("-", $data_id); + + $this->db->where('id', $storage_id) + ->delete('file_storage'); + if (file_exists($this->file($data_id))) { + unlink($this->file($data_id)); + } + $dir = $this->folder($data_id); + if (file_exists($dir)) { + if (count(scandir($dir)) == 2) { + rmdir($dir); + } + } + delete_cache("${data_id}_thumb_150"); + } + + public function get_owner($id) + { + return $this->db->select('user') + ->from('files') + ->where('id', $id) + ->get()->row_array() + ['user']; + } + +} + +# vim: set noet: diff --git a/application/models/Mmultipaste.php b/application/models/Mmultipaste.php new file mode 100644 index 000000000..52ea4dfb4 --- /dev/null +++ b/application/models/Mmultipaste.php @@ -0,0 +1,184 @@ + + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Mmultipaste extends CI_Model { + + function __construct() + { + parent::__construct(); + $this->load->model("muser"); + $this->load->model("mfile"); + } + + /** + * Returns an unused ID + * + * @param min minimal length of the resulting ID + * @param max maximum length of the resulting ID + */ + public function new_id($min = 3, $max = 6) + { + static $id_blacklist = NULL; + + if ($id_blacklist == NULL) { + // This prevents people from being unable to access their uploads + // because of URL rewriting + $id_blacklist = scandir(FCPATH); + $id_blacklist[] = "file"; + $id_blacklist[] = "user"; + } + + $max_tries = 100; + + for ($try = 0; $try < $max_tries; $try++) { + $id = "m-".random_alphanum($min, $max); + + // TODO: try to insert the id into file_groups instead of checking with + // id_exists (prevents race conditio) + if ($this->id_exists($id) || in_array($id, $id_blacklist)) { + continue; + } + + $this->db->insert("multipaste", array( + "url_id" => $id, + "user_id" => $this->muser->get_userid(), + "date" => time(), + )); + + return $id; + } + + throw new \exceptions\PublicApiException("file/new_id-try-limit", "Failed to find unused ID after $max_tries tries"); + } + + public function id_exists($id) + { + if (!$id) { + return false; + } + + $sql = ' + SELECT url_id + FROM '.$this->db->dbprefix.'multipaste + WHERE url_id = ? + LIMIT 1'; + $query = $this->db->query($sql, array($id)); + + return $query->num_rows() == 1; + } + + public function valid_id($id) + { + $files = $this->get_files($id); + if (count($files) === 0) { + return false; + } + + foreach ($files as $file) { + if (!$this->mfile->valid_id($file["id"])) { + return false; + } + } + return true; + } + + function adopt($id) + { + $userid = $this->muser->get_userid(); + + $this->db->set(array('user_id' => $userid )) + ->where('url_id', $id) + ->where('user_id', 0) + ->update('multipaste'); + return $this->db->affected_rows(); + } + + public function get_tarball_path($id) + { + return $this->config->item("upload_path")."/special/multipaste-tarballs/".substr(md5($id), 0, 3)."/$id.tar.gz"; + } + + public function delete_by_user($userid) + { + $query = $this->db->select("url_id") + ->where("user_id", $userid) + ->get("multipaste")->result_array(); + $ids = array_map(function ($a) {return $a['url_id'];}, $query); + + foreach ($ids as $id) { + $this->delete_id($id); + } + } + + public function delete_id($id) + { + $this->db->where('url_id', $id) + ->delete('multipaste'); + + $path = $this->get_tarball_path($id); + $f = new \service\storage($this->get_tarball_path($id)); + $f->unlink(); + + return !$this->id_exists($id); + } + + public function get_owner($id) + { + return $this->db->query(" + SELECT user_id + FROM ".$this->db->dbprefix."multipaste + WHERE url_id = ? + ", array($id))->row_array()["user_id"]; + } + + public function get_multipaste($id) + { + return $this->db->query(" + SELECT url_id, user_id, date + FROM ".$this->db->dbprefix."multipaste + WHERE url_id = ? + ", array($id))->row_array(); + } + + public function get_files($url_id) + { + $ret = array(); + + $query = $this->db->query(" + SELECT mfm.file_url_id + FROM ".$this->db->dbprefix."multipaste_file_map mfm + JOIN ".$this->db->dbprefix."multipaste m ON m.multipaste_id = mfm.multipaste_id + WHERE m.url_id = ? + ORDER BY mfm.sort_order + ", array($url_id))->result_array(); + + foreach ($query as $row) { + $filedata = $this->mfile->get_filedata($row["file_url_id"]); + $ret[] = $filedata; + } + + return $ret; + } + + public function get_multipaste_id($url_id) + { + $query = $this->db->query(" + SELECT multipaste_id + FROM ".$this->db->dbprefix."multipaste + WHERE url_id = ? + ", array($url_id)); + + if ($query->num_rows() > 0) { + return $query->row_array()["multipaste_id"]; + } + + return false; + } + +} diff --git a/application/models/Muser.php b/application/models/Muser.php new file mode 100644 index 000000000..1ee6c259a --- /dev/null +++ b/application/models/Muser.php @@ -0,0 +1,390 @@ + + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Muser extends CI_Model { + + private $default_upload_id_limits = "3-6"; + + // last level has the most access + private $access_levels = array("basic", "apikey", "full"); + + private $hashalgo; + private $hashoptions = array(); + + function __construct() + { + parent::__construct(); + + $this->load->helper("filebin"); + $this->load->driver("duser"); + $this->hashalgo = $this->config->item('auth_db')['hashing_algorithm']; + $this->hashoptions = $this->config->item('auth_db')['hashing_options']; + } + + function has_session() + { + // checking $this doesn't work + $CI =& get_instance(); + if (property_exists($CI, "session")) { + return true; + } + + // Only load the session class if we already have a cookie that might need to be renewed. + // Otherwise we just create lots of stale sessions. + if (isset($_COOKIE[$this->config->item("sess_cookie_name")])) { + $this->load->library("session"); + return true; + } + + return false; + } + + function require_session() + { + if (!$this->has_session()) { + $this->load->library("session"); + } + } + + function logged_in() + { + if ($this->has_session()) { + return $this->session->userdata('logged_in') === true; + } + + return false; + } + + function login($username, $password) + { + $this->require_session(); + return $this->duser->login($username, $password); + } + + private function login_api_client() + { + $username = $this->input->post("username"); + $password = $this->input->post("password"); + + // TODO keep for now. might be useful if adapted to apikeys instead of passwords + // prefer post parameters if either (username or password) is set + //if ($username === false && $password === false) { + //if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { + //$username = $_SERVER['PHP_AUTH_USER']; + //$password = $_SERVER['PHP_AUTH_PW']; + //} + //} + + if ($username !== false && $password !== false) { + if ($this->login($username, $password)) { + return true; + } else { + throw new \exceptions\NotAuthenticatedException("user/login-failed", "Login failed"); + } + } + + return null; + } + + function apilogin($apikey) + { + $this->require_session(); + + // get rid of spaces and newlines + $apikey = trim($apikey); + + $query = $this->db->select('user, access_level') + ->from('apikeys') + ->where('key', $apikey) + ->get()->row_array(); + + if (isset($query["user"])) { + $this->session->set_userdata(array( + 'logged_in' => true, + 'username' => '', + 'userid' => $query["user"], + 'access_level' => $query["access_level"], + )); + return true; + } + + throw new \exceptions\NotAuthenticatedException("user/api-login-failed", "API key login failed"); + } + + function logout() + { + $this->require_session(); + $this->session->unset_userdata('logged_in'); + $this->session->unset_userdata('username'); + $this->session->unset_userdata('userid'); + $this->session->sess_destroy(); + } + + function get_username() + { + if (!$this->logged_in()) { + return ""; + } + + return $this->session->userdata('username'); + } + + /* + * Check if a given username is valid. + * + * Valid usernames contain only lowercase characters and numbers. They are + * also <= 32 characters in length. + * + * @return boolean + */ + public function valid_username($username) + { + return strlen($username) <= 32 && preg_match("/^[a-z0-9]+$/", $username); + } + + /** + * Check if a given email is valid. Only perform minimal checking since + * verifying emails is very very difficuly. + * + * @return boolean + */ + public function valid_email($email) + { + $this->load->helper("email"); + return valid_email($email); + } + + public function add_user($username, $password, $email, $referrer) + { + if (!$this->valid_username($username)) { + throw new \exceptions\PublicApiException("user/invalid-username", "Invalid username (only up to 32 chars of a-z0-9 are allowed)"); + } else { + if ($this->muser->username_exists($username)) { + throw new \exceptions\PublicApiException("user/username-already-exists", "Username already exists"); + } + } + + if (!$this->valid_email($email)) { + throw new \exceptions\PublicApiException("user/invalid-email", "Invalid email"); + } + + $this->db->set(array( + 'username' => $username, + 'password' => $this->hash_password($password), + 'email' => $email, + 'referrer' => $referrer + )) + ->insert('users'); + } + + /** + * Delete a user. + * + * @param username + * @param password + * @return true on sucess, false otherwise + */ + public function delete_user($username, $password) + { + $this->duser->require_implemented("can_delete_account"); + + if ($this->duser->test_login_credentials($username, $password)) { + $userid = $this->get_userid_by_name($username); + assert($userid !== null); + + $this->db->delete('profiles', array('user' => $userid)); + + $this->load->model("mfile"); + $this->load->model("mmultipaste"); + $this->mfile->delete_by_user($userid); + $this->mmultipaste->delete_by_user($userid); + + # null out user data to keep referer information traceable + # If referer information was relinked, one user could create many + # accounts, delete the account that was used to invite them and + # then cause trouble so that the account that invited him gets + # banned because the admin thinks that account invited abusers + $this->db->set(array( + 'username' => null, + 'password' => null, + 'email' => null, + )) + ->where(array('username' => $username)) + ->update('users'); + + return true; + } + + return false; + } + + function get_userid() + { + if (!$this->logged_in()) { + return 0; + } + + return $this->session->userdata("userid"); + } + + public function get_userid_by_name($username) + { + $query = $this->db->select('id') + ->from('users') + ->where('username', $username) + ->get()->row_array(); + if ($query) { + return $query['id']; + } + + return null; + } + + function get_email($userid) + { + return $this->duser->get_email($userid); + } + + public function get_access_levels() + { + return $this->access_levels; + } + + private function check_access_level($wanted_level) + { + $session_level = $this->session->userdata("access_level"); + + $wanted = array_search($wanted_level, $this->get_access_levels()); + $have = array_search($session_level, $this->get_access_levels()); + + if ($wanted === false || $have === false) { + throw new \exceptions\PublicApiException("api/invalid-accesslevel", "Failed to determine access level"); + } + + if ($have >= $wanted) { + return; + } + + throw new \exceptions\InsufficientPermissionsException("api/insufficient-permissions", "Access denied: Access level too low. Required: $wanted_level; Have: $session_level"); + } + + function require_access($wanted_level = "full") + { + if ($this->input->post("apikey") !== false) { + $this->apilogin($this->input->post("apikey")); + } + + //if (is_api_client()) { + //$this->login_api_client(); + //} + + if ($this->logged_in()) { + return $this->check_access_level($wanted_level); + } + + throw new \exceptions\NotAuthenticatedException("api/not-authenticated", "Not authenticated. FileBin requires you to have an account, please go to the homepage at ".site_url()." for more information."); + } + + function username_exists($username) + { + return $this->duser->username_exists($username); + } + + function get_action($action, $key) + { + $query = $this->db->from('actions') + ->where('key', $key) + ->where('action', $action) + ->get()->row_array(); + + if (!isset($query["key"]) || $key !== $query["key"]) { + throw new \exceptions\UserInputException("user/get_action/invalid-action", "Invalid action key. Has the key been used already?"); + } + + return $query; + } + + public function get_profile_data() + { + $userid = $this->get_userid(); + + $fields = array( + "user" => $userid, + "upload_id_limits" => $this->default_upload_id_limits, + ); + + $query = $this->db->select(implode(', ', array_keys($fields))) + ->from('profiles') + ->where('user', $userid) + ->get()->row_array(); + + $extra_fields = array( + "username" => $this->get_username(), + "email" => $this->get_email($userid), + ); + + return array_merge($fields, $query, $extra_fields); + } + + public function update_profile($data) + { + assert(is_array($data)); + + $data["user"] = $this->get_userid(); + + $exists_in_db = $this->db->get_where("profiles", array("user" => $data["user"]))->num_rows() > 0; + + if ($exists_in_db) { + $this->db->where("user", $data["user"]); + $this->db->update("profiles", $data); + } else { + $this->db->insert("profiles", $data); + } + } + + public function set_password($userid, $password) { + $this->db->where('id', $userid) + ->update('users', array( + 'password' => $this->hash_password($password) + )); + } + + public function rehash_password($userid, $password, $hash) { + if (password_needs_rehash($hash, $this->hashalgo, $this->hashoptions)) { + $this->set_password($userid, $password); + } + } + + public function get_upload_id_limits() + { + $userid = $this->get_userid(); + + $query = $this->db->select('upload_id_limits') + ->from('profiles') + ->where('user', $userid) + ->get()->row_array(); + + if (empty($query)) { + return explode("-", $this->default_upload_id_limits); + } + + return explode("-", $query["upload_id_limits"]); + } + + function hash_password($password) + { + $hash = password_hash($password, $this->hashalgo, $this->hashoptions); + if ($hash === false) { + throw new \exceptions\ApiException('user/hash_password/failed', "Failed to hash password"); + } + return $hash; + } + +} + diff --git a/application/models/mfile.php b/application/models/mfile.php deleted file mode 100644 index 977240c89..000000000 --- a/application/models/mfile.php +++ /dev/null @@ -1,275 +0,0 @@ - - * - * Licensed under AGPLv3 - * (see COPYING for full license text) - * - */ - -class Mfile extends CI_Model { - - private $upload_path; - - function __construct() - { - parent::__construct(); - $this->load->model("muser"); - - $this->upload_path = $this->config->item('upload_path'); - $this->id_validation_config = array( - "upload_max_age" => $this->config->item("upload_max_age"), - "small_upload_size" => $this->config->item("small_upload_size"), - "sess_expiration" => $this->config->item("sess_expiration"), - ); - } - - // Returns an unused ID - function new_id($min = 3, $max = 6) - { - static $id_blacklist = NULL; - - if ($id_blacklist == NULL) { - // This prevents people from being unable to access their uploads - // because of URL rewriting - $id_blacklist = scandir(FCPATH); - $id_blacklist[] = "file"; - $id_blacklist[] = "user"; - } - - $max_tries = 100; - - for ($try = 0; $try < $max_tries; $try++) { - $id = random_alphanum($min, $max); - - if ($this->id_exists($id) || in_array($id, $id_blacklist)) { - continue; - } - - return $id; - } - - throw new \exceptions\PublicApiException("file/new_id-try-limit", "Failed to find unused ID after $max_tries tries"); - } - - function id_exists($id) - { - if (!$id) { - return false; - } - - $query = $this->db->select('id') - ->from('files') - ->where('id', $id) - ->limit(1) - ->get(); - - return $query->num_rows() == 1; - } - - function get_filedata($id) - { - return cache_function("filedatav2-$id", 300, function() use ($id) { - $query = $this->db - ->select('files.id, hash, file_storage.id storage_id, filename, mimetype, files.date, user, filesize') - ->from('files') - ->join('file_storage', 'file_storage.id = files.file_storage_id') - ->where('files.id', $id) - ->limit(1) - ->get(); - - if ($query->num_rows() > 0) { - $data = $query->row_array(); - $data["data_id"] = $data["hash"]."-".$data["storage_id"]; - return $data; - } else { - return false; - } - }); - } - - // return the folder in which the file with $data_id is stored - function folder($data_id) { - return $this->upload_path.'/'.substr($data_id, 0, 3); - } - - // Returns the full path to the file with $data_id - function file($data_id) { - return $this->folder($data_id).'/'.$data_id; - } - - // Add a file to the DB - function add_file($userid, $id, $filename, $storage_id) - { - $this->db->insert("files", array( - "id" => $id, - "filename" => $filename, - "date" => time(), - "user" => $userid, - "file_storage_id" => $storage_id, - )); - } - - function adopt($id) - { - $userid = $this->muser->get_userid(); - - $this->db->set(array('user' => $userid )) - ->where('id', $id) - ->where('user', 0) - ->update('files'); - return $this->db->affected_rows(); - } - - // remove old/invalid/broken IDs - public function valid_id($id) - { - $filedata = $this->get_filedata($id); - - if (!$filedata) { - return false; - } - - return $this->valid_filedata($filedata); - } - - public function valid_filedata($filedata) - { - return \service\files::valid_id($filedata, $this->id_validation_config, $this, time()); - } - - public function file_exists($file) - { - return file_exists($file); - } - - public function filemtime($file) - { - return filemtime($file); - } - - public function filesize($file) - { - return filesize($file); - } - - public function get_timeout($id) - { - $filedata = $this->get_filedata($id); - $file = $this->file($filedata["data_id"]); - - if ($this->config->item("upload_max_age") == 0) { - return -1; - } - - if (filesize($file) > $this->config->item("small_upload_size")) { - return $filedata["date"] + $this->config->item("upload_max_age"); - } else { - return -1; - } - } - - public function get_timeout_string($id) - { - $timeout = $this->get_timeout($id); - - if ($timeout >= 0) { - return date("r", $timeout); - } else { - return "unknown"; - } - } - - private function unused_file($data_id) - { - list ($hash, $storage_id) = explode("-", $data_id); - $query = $this->db->select('files.id') - ->from('files') - ->join('file_storage', 'file_storage.id = files.file_storage_id') - ->where('hash', $hash) - ->where('file_storage.id', $storage_id) - ->limit(1) - ->get(); - - return $query->num_rows() == 0; - } - - public function delete_by_user($userid) - { - $query = $this->db->select("id") - ->where("user", $userid) - ->get("files")->result_array(); - $ids = array_map(function ($a) {return $a['id'];}, $query); - - foreach ($ids as $id) { - $this->delete_id($id); - } - } - - public function delete_id($id) - { - $filedata = $this->get_filedata($id); - - // Delete the file and all multipastes using it - // Note that this does not delete all relations in multipaste_file_map - // which is actually done by an SQL contraint. - // TODO: make it work properly without the constraint - $map = $this->db->select('url_id') - ->distinct() - ->from('multipaste_file_map') - ->join("multipaste", "multipaste.multipaste_id = multipaste_file_map.multipaste_id") - ->where('file_url_id', $id) - ->get()->result_array(); - - $this->db->where('id', $id) - ->delete('files'); - delete_cache("filedata-$id"); - - foreach ($map as $entry) { - assert(!empty($entry['url_id'])); - $this->mmultipaste->delete_id($entry["url_id"]); - } - - if ($this->id_exists($id)) { - return false; - } - - if ($filedata !== false) { - assert(isset($filedata["data_id"])); - if ($this->unused_file($filedata['data_id'])) { - $this->delete_data_id($filedata['data_id']); - } - } - return true; - } - - public function delete_data_id($data_id) - { - list ($hash, $storage_id) = explode("-", $data_id); - - $this->db->where('id', $storage_id) - ->delete('file_storage'); - if (file_exists($this->file($data_id))) { - unlink($this->file($data_id)); - } - $dir = $this->folder($data_id); - if (file_exists($dir)) { - if (count(scandir($dir)) == 2) { - rmdir($dir); - } - } - delete_cache("${data_id}_thumb_150"); - } - - public function get_owner($id) - { - return $this->db->select('user') - ->from('files') - ->where('id', $id) - ->get()->row_array() - ['user']; - } - -} - -# vim: set noet: diff --git a/application/models/mmultipaste.php b/application/models/mmultipaste.php deleted file mode 100644 index 52ea4dfb4..000000000 --- a/application/models/mmultipaste.php +++ /dev/null @@ -1,184 +0,0 @@ - - * - * Licensed under AGPLv3 - * (see COPYING for full license text) - * - */ - -class Mmultipaste extends CI_Model { - - function __construct() - { - parent::__construct(); - $this->load->model("muser"); - $this->load->model("mfile"); - } - - /** - * Returns an unused ID - * - * @param min minimal length of the resulting ID - * @param max maximum length of the resulting ID - */ - public function new_id($min = 3, $max = 6) - { - static $id_blacklist = NULL; - - if ($id_blacklist == NULL) { - // This prevents people from being unable to access their uploads - // because of URL rewriting - $id_blacklist = scandir(FCPATH); - $id_blacklist[] = "file"; - $id_blacklist[] = "user"; - } - - $max_tries = 100; - - for ($try = 0; $try < $max_tries; $try++) { - $id = "m-".random_alphanum($min, $max); - - // TODO: try to insert the id into file_groups instead of checking with - // id_exists (prevents race conditio) - if ($this->id_exists($id) || in_array($id, $id_blacklist)) { - continue; - } - - $this->db->insert("multipaste", array( - "url_id" => $id, - "user_id" => $this->muser->get_userid(), - "date" => time(), - )); - - return $id; - } - - throw new \exceptions\PublicApiException("file/new_id-try-limit", "Failed to find unused ID after $max_tries tries"); - } - - public function id_exists($id) - { - if (!$id) { - return false; - } - - $sql = ' - SELECT url_id - FROM '.$this->db->dbprefix.'multipaste - WHERE url_id = ? - LIMIT 1'; - $query = $this->db->query($sql, array($id)); - - return $query->num_rows() == 1; - } - - public function valid_id($id) - { - $files = $this->get_files($id); - if (count($files) === 0) { - return false; - } - - foreach ($files as $file) { - if (!$this->mfile->valid_id($file["id"])) { - return false; - } - } - return true; - } - - function adopt($id) - { - $userid = $this->muser->get_userid(); - - $this->db->set(array('user_id' => $userid )) - ->where('url_id', $id) - ->where('user_id', 0) - ->update('multipaste'); - return $this->db->affected_rows(); - } - - public function get_tarball_path($id) - { - return $this->config->item("upload_path")."/special/multipaste-tarballs/".substr(md5($id), 0, 3)."/$id.tar.gz"; - } - - public function delete_by_user($userid) - { - $query = $this->db->select("url_id") - ->where("user_id", $userid) - ->get("multipaste")->result_array(); - $ids = array_map(function ($a) {return $a['url_id'];}, $query); - - foreach ($ids as $id) { - $this->delete_id($id); - } - } - - public function delete_id($id) - { - $this->db->where('url_id', $id) - ->delete('multipaste'); - - $path = $this->get_tarball_path($id); - $f = new \service\storage($this->get_tarball_path($id)); - $f->unlink(); - - return !$this->id_exists($id); - } - - public function get_owner($id) - { - return $this->db->query(" - SELECT user_id - FROM ".$this->db->dbprefix."multipaste - WHERE url_id = ? - ", array($id))->row_array()["user_id"]; - } - - public function get_multipaste($id) - { - return $this->db->query(" - SELECT url_id, user_id, date - FROM ".$this->db->dbprefix."multipaste - WHERE url_id = ? - ", array($id))->row_array(); - } - - public function get_files($url_id) - { - $ret = array(); - - $query = $this->db->query(" - SELECT mfm.file_url_id - FROM ".$this->db->dbprefix."multipaste_file_map mfm - JOIN ".$this->db->dbprefix."multipaste m ON m.multipaste_id = mfm.multipaste_id - WHERE m.url_id = ? - ORDER BY mfm.sort_order - ", array($url_id))->result_array(); - - foreach ($query as $row) { - $filedata = $this->mfile->get_filedata($row["file_url_id"]); - $ret[] = $filedata; - } - - return $ret; - } - - public function get_multipaste_id($url_id) - { - $query = $this->db->query(" - SELECT multipaste_id - FROM ".$this->db->dbprefix."multipaste - WHERE url_id = ? - ", array($url_id)); - - if ($query->num_rows() > 0) { - return $query->row_array()["multipaste_id"]; - } - - return false; - } - -} diff --git a/application/models/muser.php b/application/models/muser.php deleted file mode 100644 index 1ee6c259a..000000000 --- a/application/models/muser.php +++ /dev/null @@ -1,390 +0,0 @@ - - * - * Licensed under AGPLv3 - * (see COPYING for full license text) - * - */ - -class Muser extends CI_Model { - - private $default_upload_id_limits = "3-6"; - - // last level has the most access - private $access_levels = array("basic", "apikey", "full"); - - private $hashalgo; - private $hashoptions = array(); - - function __construct() - { - parent::__construct(); - - $this->load->helper("filebin"); - $this->load->driver("duser"); - $this->hashalgo = $this->config->item('auth_db')['hashing_algorithm']; - $this->hashoptions = $this->config->item('auth_db')['hashing_options']; - } - - function has_session() - { - // checking $this doesn't work - $CI =& get_instance(); - if (property_exists($CI, "session")) { - return true; - } - - // Only load the session class if we already have a cookie that might need to be renewed. - // Otherwise we just create lots of stale sessions. - if (isset($_COOKIE[$this->config->item("sess_cookie_name")])) { - $this->load->library("session"); - return true; - } - - return false; - } - - function require_session() - { - if (!$this->has_session()) { - $this->load->library("session"); - } - } - - function logged_in() - { - if ($this->has_session()) { - return $this->session->userdata('logged_in') === true; - } - - return false; - } - - function login($username, $password) - { - $this->require_session(); - return $this->duser->login($username, $password); - } - - private function login_api_client() - { - $username = $this->input->post("username"); - $password = $this->input->post("password"); - - // TODO keep for now. might be useful if adapted to apikeys instead of passwords - // prefer post parameters if either (username or password) is set - //if ($username === false && $password === false) { - //if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { - //$username = $_SERVER['PHP_AUTH_USER']; - //$password = $_SERVER['PHP_AUTH_PW']; - //} - //} - - if ($username !== false && $password !== false) { - if ($this->login($username, $password)) { - return true; - } else { - throw new \exceptions\NotAuthenticatedException("user/login-failed", "Login failed"); - } - } - - return null; - } - - function apilogin($apikey) - { - $this->require_session(); - - // get rid of spaces and newlines - $apikey = trim($apikey); - - $query = $this->db->select('user, access_level') - ->from('apikeys') - ->where('key', $apikey) - ->get()->row_array(); - - if (isset($query["user"])) { - $this->session->set_userdata(array( - 'logged_in' => true, - 'username' => '', - 'userid' => $query["user"], - 'access_level' => $query["access_level"], - )); - return true; - } - - throw new \exceptions\NotAuthenticatedException("user/api-login-failed", "API key login failed"); - } - - function logout() - { - $this->require_session(); - $this->session->unset_userdata('logged_in'); - $this->session->unset_userdata('username'); - $this->session->unset_userdata('userid'); - $this->session->sess_destroy(); - } - - function get_username() - { - if (!$this->logged_in()) { - return ""; - } - - return $this->session->userdata('username'); - } - - /* - * Check if a given username is valid. - * - * Valid usernames contain only lowercase characters and numbers. They are - * also <= 32 characters in length. - * - * @return boolean - */ - public function valid_username($username) - { - return strlen($username) <= 32 && preg_match("/^[a-z0-9]+$/", $username); - } - - /** - * Check if a given email is valid. Only perform minimal checking since - * verifying emails is very very difficuly. - * - * @return boolean - */ - public function valid_email($email) - { - $this->load->helper("email"); - return valid_email($email); - } - - public function add_user($username, $password, $email, $referrer) - { - if (!$this->valid_username($username)) { - throw new \exceptions\PublicApiException("user/invalid-username", "Invalid username (only up to 32 chars of a-z0-9 are allowed)"); - } else { - if ($this->muser->username_exists($username)) { - throw new \exceptions\PublicApiException("user/username-already-exists", "Username already exists"); - } - } - - if (!$this->valid_email($email)) { - throw new \exceptions\PublicApiException("user/invalid-email", "Invalid email"); - } - - $this->db->set(array( - 'username' => $username, - 'password' => $this->hash_password($password), - 'email' => $email, - 'referrer' => $referrer - )) - ->insert('users'); - } - - /** - * Delete a user. - * - * @param username - * @param password - * @return true on sucess, false otherwise - */ - public function delete_user($username, $password) - { - $this->duser->require_implemented("can_delete_account"); - - if ($this->duser->test_login_credentials($username, $password)) { - $userid = $this->get_userid_by_name($username); - assert($userid !== null); - - $this->db->delete('profiles', array('user' => $userid)); - - $this->load->model("mfile"); - $this->load->model("mmultipaste"); - $this->mfile->delete_by_user($userid); - $this->mmultipaste->delete_by_user($userid); - - # null out user data to keep referer information traceable - # If referer information was relinked, one user could create many - # accounts, delete the account that was used to invite them and - # then cause trouble so that the account that invited him gets - # banned because the admin thinks that account invited abusers - $this->db->set(array( - 'username' => null, - 'password' => null, - 'email' => null, - )) - ->where(array('username' => $username)) - ->update('users'); - - return true; - } - - return false; - } - - function get_userid() - { - if (!$this->logged_in()) { - return 0; - } - - return $this->session->userdata("userid"); - } - - public function get_userid_by_name($username) - { - $query = $this->db->select('id') - ->from('users') - ->where('username', $username) - ->get()->row_array(); - if ($query) { - return $query['id']; - } - - return null; - } - - function get_email($userid) - { - return $this->duser->get_email($userid); - } - - public function get_access_levels() - { - return $this->access_levels; - } - - private function check_access_level($wanted_level) - { - $session_level = $this->session->userdata("access_level"); - - $wanted = array_search($wanted_level, $this->get_access_levels()); - $have = array_search($session_level, $this->get_access_levels()); - - if ($wanted === false || $have === false) { - throw new \exceptions\PublicApiException("api/invalid-accesslevel", "Failed to determine access level"); - } - - if ($have >= $wanted) { - return; - } - - throw new \exceptions\InsufficientPermissionsException("api/insufficient-permissions", "Access denied: Access level too low. Required: $wanted_level; Have: $session_level"); - } - - function require_access($wanted_level = "full") - { - if ($this->input->post("apikey") !== false) { - $this->apilogin($this->input->post("apikey")); - } - - //if (is_api_client()) { - //$this->login_api_client(); - //} - - if ($this->logged_in()) { - return $this->check_access_level($wanted_level); - } - - throw new \exceptions\NotAuthenticatedException("api/not-authenticated", "Not authenticated. FileBin requires you to have an account, please go to the homepage at ".site_url()." for more information."); - } - - function username_exists($username) - { - return $this->duser->username_exists($username); - } - - function get_action($action, $key) - { - $query = $this->db->from('actions') - ->where('key', $key) - ->where('action', $action) - ->get()->row_array(); - - if (!isset($query["key"]) || $key !== $query["key"]) { - throw new \exceptions\UserInputException("user/get_action/invalid-action", "Invalid action key. Has the key been used already?"); - } - - return $query; - } - - public function get_profile_data() - { - $userid = $this->get_userid(); - - $fields = array( - "user" => $userid, - "upload_id_limits" => $this->default_upload_id_limits, - ); - - $query = $this->db->select(implode(', ', array_keys($fields))) - ->from('profiles') - ->where('user', $userid) - ->get()->row_array(); - - $extra_fields = array( - "username" => $this->get_username(), - "email" => $this->get_email($userid), - ); - - return array_merge($fields, $query, $extra_fields); - } - - public function update_profile($data) - { - assert(is_array($data)); - - $data["user"] = $this->get_userid(); - - $exists_in_db = $this->db->get_where("profiles", array("user" => $data["user"]))->num_rows() > 0; - - if ($exists_in_db) { - $this->db->where("user", $data["user"]); - $this->db->update("profiles", $data); - } else { - $this->db->insert("profiles", $data); - } - } - - public function set_password($userid, $password) { - $this->db->where('id', $userid) - ->update('users', array( - 'password' => $this->hash_password($password) - )); - } - - public function rehash_password($userid, $password, $hash) { - if (password_needs_rehash($hash, $this->hashalgo, $this->hashoptions)) { - $this->set_password($userid, $password); - } - } - - public function get_upload_id_limits() - { - $userid = $this->get_userid(); - - $query = $this->db->select('upload_id_limits') - ->from('profiles') - ->where('user', $userid) - ->get()->row_array(); - - if (empty($query)) { - return explode("-", $this->default_upload_id_limits); - } - - return explode("-", $query["upload_id_limits"]); - } - - function hash_password($password) - { - $hash = password_hash($password, $this->hashalgo, $this->hashoptions); - if ($hash === false) { - throw new \exceptions\ApiException('user/hash_password/failed', "Failed to hash password"); - } - return $hash; - } - -} - -- cgit v1.2.3-24-g4f1b