public function post_test() { access::allow(identity::everybody(), "edit", item::root()); $request = new stdClass(); $request->params = new stdClass(); $request->params->name = "test tag"; $this->assert_equal(array("url" => url::site("rest/tag/1")), tags_rest::post($request)); }
public function post_test() { $tag = test::random_tag(); // Create an editable item to be tagged $album = test::random_album(); access::allow(identity::everybody(), "edit", $album); // Add the album to the tag $request->url = rest::url("tag", $tag); $request->params->url = rest::url("item", $album); $this->assert_equal_array(array("url" => rest::url("tag_item", $tag, $album)), tag_rest::post($request)); }
private function _get_proxy() { $album = test::random_album(); $photo = test::random_photo($album); access::deny(identity::everybody(), "view_full", $album); access::deny(identity::registered_users(), "view_full", $album); $proxy = ORM::factory("digibug_proxy"); $proxy->uuid = random::hash(); $proxy->item_id = $photo->id; return $proxy->save(); }
public function viewable_test() { $root = ORM::factory("item", 1); $album = album::create($root, rand(), rand(), rand()); $item = self::_create_random_item($album); identity::set_active_user(identity::guest()); // We can see the item when permissions are granted access::allow(identity::everybody(), "view", $album); $this->assert_equal(1, ORM::factory("item")->viewable()->where("id", "=", $item->id)->count_all()); // We can't see the item when permissions are denied access::deny(identity::everybody(), "view", $album); $this->assert_equal(0, ORM::factory("item")->viewable()->where("id", "=", $item->id)->count_all()); }
public function viewable_test() { $album = test::random_album(); $item = test::random_photo($album); $album->reload(); identity::set_active_user(identity::guest()); // We can see the item when permissions are granted access::allow(identity::everybody(), "view", $album); $this->assert_equal(1, ORM::factory("item")->viewable()->where("id", "=", $item->id)->count_all()); // We can't see the item when permissions are denied access::deny(identity::everybody(), "view", $album); $this->assert_equal(0, ORM::factory("item")->viewable()->where("id", "=", $item->id)->count_all()); }
public function cant_view_comments_for_unviewable_items_test() { $root = ORM::factory("item", 1); $album = album::create($root, rand(), rand(), rand()); $comment = comment::create($album, identity::guest(), "text", "name", "email", "url"); identity::set_active_user(identity::guest()); // We can see the comment when permissions are granted on the album access::allow(identity::everybody(), "view", $album); $this->assert_equal(1, ORM::factory("comment")->viewable()->where("comments.id", "=", $comment->id)->count_all()); // We can't see the comment when permissions are denied on the album access::deny(identity::everybody(), "view", $album); $this->assert_equal(0, ORM::factory("comment")->viewable()->where("comments.id", "=", $comment->id)->count_all()); }
public function post_fails_without_permissions_test() { access::deny(identity::everybody(), "edit", item::root()); identity::set_active_user(identity::guest()); try { $request->params->name = "test tag"; tags_rest::post($request); } catch (Exception $e) { $this->assert_equal(403, $e->getCode()); return; } $this->assert_true(false, "Shouldnt get here"); }
public function setup() { $this->_server = $_SERVER; $root = ORM::factory("item", 1); $this->_album = album::create($root, rand(), "test album"); access::deny(identity::everybody(), "view_full", $this->_album); access::deny(identity::registered_users(), "view_full", $this->_album); $rand = rand(); $this->_item = photo::create($this->_album, MODPATH . "gallery/tests/test.jpg", "{$rand}.jpg", $rand, $rand); $this->_proxy = ORM::factory("digibug_proxy"); $this->_proxy->uuid = md5(rand()); $this->_proxy->item_id = $this->_item->id; $this->_proxy->save(); }
public function change_album_no_csrf_fails_test() { $controller = new Albums_Controller(); $album = test::random_album(); $_POST["name"] = "new name"; $_POST["title"] = "new title"; $_POST["description"] = "new description"; access::allow(identity::everybody(), "edit", item::root()); try { $controller->update($album->id); $this->assert_true(false, "This should fail"); } catch (Exception $e) { // pass $this->assert_same("@todo FORBIDDEN", $e->getMessage()); } }
public function change_album_no_csrf_fails_test() { $controller = new Albums_Controller(); $root = ORM::factory("item", 1); $this->_album = album::create($root, "test", "test", "test"); $_POST["name"] = "new name"; $_POST["title"] = "new title"; $_POST["description"] = "new description"; access::allow(identity::everybody(), "edit", $root); try { $controller->_update($this->_album); $this->assert_true(false, "This should fail"); } catch (Exception $e) { // pass } }
public function cant_view_comments_for_unviewable_items_test() { $album = test::random_album(); $comment = ORM::factory("comment"); $comment->item_id = $album->id; $comment->author_id = identity::admin_user()->id; $comment->text = "text"; $comment->save(); identity::set_active_user(identity::guest()); // We can see the comment when permissions are granted on the album access::allow(identity::everybody(), "view", $album); $this->assert_true(ORM::factory("comment")->viewable()->where("comments.id", "=", $comment->id)->count_all()); // We can't see the comment when permissions are denied on the album access::deny(identity::everybody(), "view", $album); $this->assert_false(ORM::factory("comment")->viewable()->where("comments.id", "=", $comment->id)->count_all()); }
public function change_photo_no_csrf_fails_test() { $controller = new Photos_Controller(); $root = ORM::factory("item", 1); $photo = photo::create($root, MODPATH . "gallery/tests/test.jpg", "test.jpg", "test", "test"); $_POST["name"] = "new name"; $_POST["title"] = "new title"; $_POST["description"] = "new description"; access::allow(identity::everybody(), "edit", $root); try { $controller->_update($photo); $this->assert_true(false, "This should fail"); } catch (Exception $e) { // pass } }
public function illegal_access_test() { $album = test::random_album(); $photo = test::random_photo($album); $album->reload(); access::deny(identity::everybody(), "view", $album); identity::set_active_user(identity::guest()); $request = new stdClass(); $request->url = rest::url("data", $photo, "thumb"); $request->params = new stdClass(); $request->params->size = "thumb"; try { data_rest::get($request); $this->assert_true(false); } catch (Kohana_404_Exception $e) { // pass } }
public function print_photo($id) { access::verify_csrf(); $item = ORM::factory("item", $id); access::required("view", $item); if (access::group_can(identity::everybody(), "view_full", $item)) { $full_url = $item->file_url(true); $thumb_url = $item->thumb_url(true); } else { $proxy = ORM::factory("digibug_proxy"); $proxy->uuid = random::hash(); $proxy->item_id = $item->id; $proxy->save(); $full_url = url::abs_site("digibug/print_proxy/full/{$proxy->uuid}/{$item->id}"); $thumb_url = url::abs_site("digibug/print_proxy/thumb/{$proxy->uuid}/{$item->id}"); } $v = new View("digibug_form.html"); $v->order_params = array("digibug_api_version" => "100", "company_id" => module::get_var("digibug", "company_id"), "event_id" => module::get_var("digibug", "event_id"), "cmd" => "addimg", "partner_code" => "69", "return_url" => url::abs_site("digibug/close_window"), "num_images" => "1", "image_1" => $full_url, "thumb_1" => $thumb_url, "image_height_1" => $item->height, "image_width_1" => $item->width, "thumb_height_1" => $item->thumb_height, "thumb_width_1" => $item->thumb_width, "title_1" => html::purify($item->title)); print $v; }
private function _get_admin_view($form, $errors) { $v = new Admin_View("admin.html"); $v->page_title = t("User registration"); $v->content = new View("admin_register.html"); $v->content->action = "admin/register/update"; $v->content->policy_list = array("admin_only" => t("Only site administrators can create new user accounts."), "visitor" => t("Visitors can create accounts and no administrator approval is required."), "admin_approval" => t("Visitors can create accounts but administrator approval is required.")); $admin = identity::admin_user(); $v->content->disable_email = empty($admin->email) || $form["policy"] == "admin_only" ? "disabled" : ""; if (empty($admin->email)) { module::set_var("registration", "email_verification", false); } // below lines added Shad Laws, v2 $v->content->disable_admin_notify = empty($admin->email) || $form["policy"] !== "admin_approval" ? "disabled" : ""; if (empty($admin->email)) { module::set_var("registration", "admin_notify", false); } $v->content->group_list = array(); foreach (identity::groups() as $group) { if ($group->id != identity::everybody()->id && $group->id != identity::registered_users()->id) { $v->content->group_list[$group->id] = $group->name; } } $hidden = array("name" => "csrf", "value" => access::csrf_token()); if (count($v->content->group_list)) { $v->content->group_list = array("" => t("Choose the default group")) + $v->content->group_list; } else { $hidden["group"] = ""; } $v->content->hidden = $hidden; $v->content->pending = ORM::factory("pending_user")->find_all(); $v->content->activate = "admin/register/activate"; $v->content->form = $form; $v->content->errors = $errors; return $v; }
/** * Import a single group. */ static function import_group(&$queue) { $messages = array(); $g2_group_id = array_shift($queue); if (self::map($g2_group_id)) { return; } try { $g2_group = g2(GalleryCoreApi::loadEntitiesById($g2_group_id)); } catch (Exception $e) { throw new G2_Import_Exception(t("Failed to import Gallery 2 group with id: %id,", array("id" => $g2_group_id)), $e); } switch ($g2_group->getGroupType()) { case GROUP_NORMAL: try { $group = identity::create_group($g2_group->getGroupName()); $messages[] = t("Group '%name' was imported", array("name" => $g2_group->getGroupname())); } catch (Exception $e) { // Did it fail because of a duplicate group name? $group = identity::lookup_group_by_name($g2_group->getGroupname()); if ($group) { $messages[] = t("Group '%name' was mapped to the existing group group of the same name.", array("name" => $g2_group->getGroupname())); } else { throw new G2_Import_Exception(t("Failed to import group '%name'", array("name" => $g2_group->getGroupname())), $e); } } break; case GROUP_ALL_USERS: $group = identity::registered_users(); $messages[] = t("Group 'Registered' was converted to '%name'", array("name" => $group->name)); break; case GROUP_SITE_ADMINS: $messages[] = t("Group 'Admin' does not exist in Gallery 3, skipping"); break; // This is not a group in G3 // This is not a group in G3 case GROUP_EVERYBODY: $group = identity::everybody(); $messages[] = t("Group 'Everybody' was converted to '%name'", array("name" => $group->name)); break; } if (isset($group)) { self::set_map($g2_group->getId(), $group->id, "group"); } return $messages; }
public function moved_items_inherit_new_permissions_test() { identity::set_active_user(identity::lookup_user_by_name("admin")); $public_album = test::random_album(); $public_photo = test::random_photo($public_album); access::allow(identity::everybody(), "view", $public_album); access::allow(identity::everybody(), "edit", $public_album); item::root()->reload(); // Account for MPTT changes $private_album = test::random_album(); access::deny(identity::everybody(), "view", $private_album); access::deny(identity::everybody(), "edit", $private_album); $private_photo = test::random_photo($private_album); // Make sure that we now have a public photo and private photo. $this->assert_true(access::group_can(identity::everybody(), "view", $public_photo)); $this->assert_false(access::group_can(identity::everybody(), "view", $private_photo)); // Swap the photos item::move($public_photo, $private_album); $private_album->reload(); // Reload to get new MPTT pointers and cached perms. $public_album->reload(); $private_photo->reload(); $public_photo->reload(); item::move($private_photo, $public_album); $private_album->reload(); // Reload to get new MPTT pointers and cached perms. $public_album->reload(); $private_photo->reload(); $public_photo->reload(); // Make sure that the public_photo is now private, and the private_photo is now public. $this->assert_false(access::group_can(identity::everybody(), "view", $public_photo)); $this->assert_false(access::group_can(identity::everybody(), "edit", $public_photo)); $this->assert_true(access::group_can(identity::everybody(), "view", $private_photo)); $this->assert_true(access::group_can(identity::everybody(), "edit", $private_photo)); }
public function need_view_full_permission_to_view_original_test() { $album = test::random_album(); $photo = test::random_photo($album); $album = $album->reload(); // adding the photo changed the album in the db $_SERVER["REQUEST_URI"] = url::file("var/albums/{$album->name}/{$photo->name}"); $controller = new File_Proxy_Controller(); access::deny(identity::everybody(), "view_full", $album); identity::set_active_user(identity::guest()); try { $controller->__call("", array()); $this->assert_true(false); } catch (Kohana_404_Exception $e) { $this->assert_same(5, $e->test_fail_code); } }
/** * Import a single group. */ static function import_group(&$queue) { $g2_group_id = array_shift($queue); if (self::map($g2_group_id)) { return; } try { $g2_group = g2(GalleryCoreApi::loadEntitiesById($g2_group_id)); } catch (Exception $e) { return t("Failed to import Gallery 2 group with id: %id\n%exception", array("id" => $g2_group_id, "exception" => $e->__toString())); } switch ($g2_group->getGroupType()) { case GROUP_NORMAL: try { $group = identity::create_group($g2_group->getGroupName()); } catch (Exception $e) { // @todo For now we assume this is a "duplicate group" exception $group = identity::lookup_user_by_name($g2_group->getGroupname()); } $message = t("Group '%name' was imported", array("name" => $g2_group->getGroupname())); break; case GROUP_ALL_USERS: $group = identity::registered_users(); $message = t("Group 'Registered' was converted to '%name'", array("name" => $group->name)); break; case GROUP_SITE_ADMINS: $message = t("Group 'Admin' does not exist in Gallery 3, skipping"); break; // This is not a group in G3 // This is not a group in G3 case GROUP_EVERYBODY: $group = identity::everybody(); $message = t("Group 'Everybody' was converted to '%name'", array("name" => $group->name)); break; } if (isset($group)) { self::set_map($g2_group->getId(), $group->id); } return $message; }
public function as_restful_array_with_add_bit_test() { $response = item::root()->as_restful_array(); $this->assert_true($response["can_add"]); access::deny(identity::everybody(), "add", item::root()); identity::set_active_user(identity::guest()); $response = item::root()->as_restful_array(); $this->assert_false($response["can_add"]); }
public function moved_items_inherit_new_permissions_test() { identity::set_active_user(identity::lookup_user_by_name("admin")); $root = ORM::factory("item", 1); $public_album = album::create($root, rand(), "public album"); $public_photo = photo::create($public_album, MODPATH . "gallery/images/gallery.png", "", ""); access::allow(identity::everybody(), "view", $public_album); $root->reload(); // Account for MPTT changes $private_album = album::create($root, rand(), "private album"); access::deny(identity::everybody(), "view", $private_album); $private_photo = photo::create($private_album, MODPATH . "gallery/images/gallery.png", "", ""); // Make sure that we now have a public photo and private photo. $this->assert_true(access::group_can(identity::everybody(), "view", $public_photo)); $this->assert_false(access::group_can(identity::everybody(), "view", $private_photo)); // Swap the photos item::move($public_photo, $private_album); $private_album->reload(); // Reload to get new MPTT pointers and cached perms. $public_album->reload(); $private_photo->reload(); $public_photo->reload(); item::move($private_photo, $public_album); $private_album->reload(); // Reload to get new MPTT pointers and cached perms. $public_album->reload(); $private_photo->reload(); $public_photo->reload(); // Make sure that the public_photo is now private, and the private_photo is now public. $this->assert_false(access::group_can(identity::everybody(), "view", $public_photo)); $this->assert_true(access::group_can(identity::everybody(), "view", $private_photo)); }
static function fix($task) { $start = microtime(true); $total = $task->get("total"); if (empty($total)) { $item_count = db::build()->count_records("items"); $total = 0; // mptt: 2 operations for every item $total += 2 * $item_count; // album audit (permissions and bogus album covers): 1 operation for every album $total += db::build()->where("type", "=", "album")->count_records("items"); // one operation for each dupe slug, dupe name, dupe base name, and missing access cache foreach (array("find_dupe_slugs", "find_dupe_names", "find_dupe_base_names", "find_missing_access_caches") as $func) { foreach (self::$func() as $row) { $total++; } } // one operation to rebuild path and url caches; $total += 1 * $item_count; $task->set("total", $total); $task->set("state", $state = self::FIX_STATE_START_MPTT); $task->set("ptr", 1); $task->set("completed", 0); } $completed = $task->get("completed"); $state = $task->get("state"); if (!module::get_var("gallery", "maintenance_mode")) { module::set_var("gallery", "maintenance_mode", 1); } // This is a state machine that checks each item in the database. It verifies the following // attributes for an item. // 1. Left and right MPTT pointers are correct // 2. The .htaccess permission files for restricted items exist and are well formed. // 3. The relative_path_cache and relative_url_cache values are set to null. // 4. there are no album_cover_item_ids pointing to missing items // // We'll do a depth-first tree walk over our hierarchy using only the adjacency data because // we don't trust MPTT here (that might be what we're here to fix!). Avoid avoid using ORM // calls as much as possible since they're expensive. // // NOTE: the MPTT check will only traverse items that have valid parents. It's possible that // we have some tree corruption where there are items with parent ids to non-existent items. // We should probably do something about that. while ($state != self::FIX_STATE_DONE && microtime(true) - $start < 1.5) { switch ($state) { case self::FIX_STATE_START_MPTT: $task->set("ptr", $ptr = 1); $task->set("stack", item::root()->id . "L1"); $state = self::FIX_STATE_RUN_MPTT; break; case self::FIX_STATE_RUN_MPTT: $ptr = $task->get("ptr"); $stack = explode(" ", $task->get("stack")); preg_match("/([0-9]+)([A-Z])([0-9]+)/", array_pop($stack), $matches); // e.g. "12345L10" list(, $id, $ptr_mode, $level) = $matches; // Skip the 0th entry of matches. switch ($ptr_mode) { case "L": // Albums could be parent nodes. $stack[] = "{$id}R{$level}"; db::build()->update("items")->set("left_ptr", $ptr++)->where("id", "=", $id)->execute(); $level++; foreach (db::build()->select(array("id", "type"))->from("items")->where("parent_id", "=", $id)->order_by("left_ptr", "DESC")->execute() as $child) { $stack[] = $child->type == "album" ? "{$child->id}L{$level}" : "{$child->id}B{$level}"; } $completed++; break; case "B": // Non-albums must be leaf nodes. db::build()->update("items")->set("left_ptr", $ptr++)->set("right_ptr", $ptr++)->set("level", $level)->set("relative_path_cache", null)->set("relative_url_cache", null)->where("id", "=", $id)->execute(); $completed += 2; // we updated two pointers break; case "R": db::build()->update("items")->set("right_ptr", $ptr++)->set("level", $level)->set("relative_path_cache", null)->set("relative_url_cache", null)->where("id", "=", $id)->execute(); $completed++; } $task->set("ptr", $ptr); $task->set("stack", implode(" ", $stack)); if (empty($stack)) { $state = self::FIX_STATE_START_DUPE_SLUGS; } break; case self::FIX_STATE_START_DUPE_SLUGS: $stack = array(); foreach (self::find_dupe_slugs() as $row) { list($parent_id, $slug) = explode(":", $row->parent_slug, 2); $stack[] = join(":", array($parent_id, $slug)); } if ($stack) { $task->set("stack", implode(" ", $stack)); $state = self::FIX_STATE_RUN_DUPE_SLUGS; } else { $state = self::FIX_STATE_START_DUPE_NAMES; } break; case self::FIX_STATE_RUN_DUPE_SLUGS: $stack = explode(" ", $task->get("stack")); list($parent_id, $slug) = explode(":", array_pop($stack)); // We want to leave the first one alone and update all conflicts to be random values. $fixed = 0; $conflicts = ORM::factory("item")->where("parent_id", "=", $parent_id)->where("slug", "=", $slug)->find_all(1, 1); if ($conflicts->count() && ($conflict = $conflicts->current())) { $task->log("Fixing conflicting slug for item id {$conflict->id}"); db::build()->update("items")->set("slug", $slug . "-" . (string) rand(1000, 9999))->where("id", "=", $conflict->id)->execute(); // We fixed one conflict, but there might be more so put this parent back on the stack // and try again. We won't consider it completed when we don't fix a conflict. This // guarantees that we won't spend too long fixing one set of conflicts, and that we // won't stop before all are fixed. $stack[] = "{$parent_id}:{$slug}"; break; } $task->set("stack", implode(" ", $stack)); $completed++; if (empty($stack)) { $state = self::FIX_STATE_START_DUPE_NAMES; } break; case self::FIX_STATE_START_DUPE_NAMES: $stack = array(); foreach (self::find_dupe_names() as $row) { list($parent_id, $name) = explode(":", $row->parent_name, 2); $stack[] = join(":", array($parent_id, $name)); } if ($stack) { $task->set("stack", implode(" ", $stack)); $state = self::FIX_STATE_RUN_DUPE_NAMES; } else { $state = self::FIX_STATE_START_DUPE_BASE_NAMES; } break; case self::FIX_STATE_RUN_DUPE_NAMES: // NOTE: This does *not* attempt to fix the file system! $stack = explode(" ", $task->get("stack")); list($parent_id, $name) = explode(":", array_pop($stack)); $fixed = 0; // We want to leave the first one alone and update all conflicts to be random values. $conflicts = ORM::factory("item")->where("parent_id", "=", $parent_id)->where("name", "=", $name)->find_all(1, 1); if ($conflicts->count() && ($conflict = $conflicts->current())) { $task->log("Fixing conflicting name for item id {$conflict->id}"); if (!$conflict->is_album() && preg_match("/^(.*)(\\.[^\\.\\/]*?)\$/", $conflict->name, $matches)) { $item_base_name = $matches[1]; $item_extension = $matches[2]; // includes a leading dot } else { $item_base_name = $conflict->name; $item_extension = ""; } db::build()->update("items")->set("name", $item_base_name . "-" . (string) rand(1000, 9999) . $item_extension)->where("id", "=", $conflict->id)->execute(); // We fixed one conflict, but there might be more so put this parent back on the stack // and try again. We won't consider it completed when we don't fix a conflict. This // guarantees that we won't spend too long fixing one set of conflicts, and that we // won't stop before all are fixed. $stack[] = "{$parent_id}:{$name}"; break; } $task->set("stack", implode(" ", $stack)); $completed++; if (empty($stack)) { $state = self::FIX_STATE_START_DUPE_BASE_NAMES; } break; case self::FIX_STATE_START_DUPE_BASE_NAMES: $stack = array(); foreach (self::find_dupe_base_names() as $row) { list($parent_id, $base_name) = explode(":", $row->parent_base_name, 2); $stack[] = join(":", array($parent_id, $base_name)); } if ($stack) { $task->set("stack", implode(" ", $stack)); $state = self::FIX_STATE_RUN_DUPE_BASE_NAMES; } else { $state = self::FIX_STATE_START_ALBUMS; } break; case self::FIX_STATE_RUN_DUPE_BASE_NAMES: // NOTE: This *does* attempt to fix the file system! So, it must go *after* run_dupe_names. $stack = explode(" ", $task->get("stack")); list($parent_id, $base_name) = explode(":", array_pop($stack)); $base_name_escaped = Database::escape_for_like($base_name); $fixed = 0; // We want to leave the first one alone and update all conflicts to be random values. $conflicts = ORM::factory("item")->where("parent_id", "=", $parent_id)->where("name", "LIKE", "{$base_name_escaped}.%")->where("type", "<>", "album")->find_all(1, 1); if ($conflicts->count() && ($conflict = $conflicts->current())) { $task->log("Fixing conflicting name for item id {$conflict->id}"); if (preg_match("/^(.*)(\\.[^\\.\\/]*?)\$/", $conflict->name, $matches)) { $item_base_name = $matches[1]; // unlike $base_name, this always maintains capitalization $item_extension = $matches[2]; // includes a leading dot } else { $item_base_name = $conflict->name; $item_extension = ""; } // Unlike conflicts found in run_dupe_names, these items are likely to have an intact // file system. Let's use the item save logic to rebuild the paths and rename the files // if possible. try { $conflict->name = $item_base_name . "-" . (string) rand(1000, 9999) . $item_extension; $conflict->validate(); // If we get here, we're safe to proceed with save $conflict->save(); } catch (Exception $e) { // Didn't work. Edit database directly without fixing file system. db::build()->update("items")->set("name", $item_base_name . "-" . (string) rand(1000, 9999) . $item_extension)->where("id", "=", $conflict->id)->execute(); } // We fixed one conflict, but there might be more so put this parent back on the stack // and try again. We won't consider it completed when we don't fix a conflict. This // guarantees that we won't spend too long fixing one set of conflicts, and that we // won't stop before all are fixed. $stack[] = "{$parent_id}:{$base_name}"; break; } $task->set("stack", implode(" ", $stack)); $completed++; if (empty($stack)) { $state = self::FIX_STATE_START_ALBUMS; } break; case self::FIX_STATE_START_ALBUMS: $stack = array(); foreach (db::build()->select("id")->from("items")->where("type", "=", "album")->execute() as $row) { $stack[] = $row->id; } $task->set("stack", implode(" ", $stack)); $state = self::FIX_STATE_RUN_ALBUMS; break; case self::FIX_STATE_RUN_ALBUMS: $stack = explode(" ", $task->get("stack")); $id = array_pop($stack); $item = ORM::factory("item", $id); if ($item->album_cover_item_id) { $album_cover_item = ORM::factory("item", $item->album_cover_item_id); if (!$album_cover_item->loaded()) { $item->album_cover_item_id = null; $item->save(); } } $everybody = identity::everybody(); $view_col = "view_{$everybody->id}"; $view_full_col = "view_full_{$everybody->id}"; $intent = ORM::factory("access_intent")->where("item_id", "=", $id)->find(); if ($intent->{$view_col} === access::DENY) { access::update_htaccess_files($item, $everybody, "view", access::DENY); } if ($intent->{$view_full_col} === access::DENY) { access::update_htaccess_files($item, $everybody, "view_full", access::DENY); } $task->set("stack", implode(" ", $stack)); $completed++; if (empty($stack)) { $state = self::FIX_STATE_START_REBUILD_ITEM_CACHES; } break; case self::FIX_STATE_START_REBUILD_ITEM_CACHES: $stack = array(); foreach (self::find_empty_item_caches(500) as $row) { $stack[] = $row->id; } $task->set("stack", implode(" ", $stack)); $state = self::FIX_STATE_RUN_REBUILD_ITEM_CACHES; break; case self::FIX_STATE_RUN_REBUILD_ITEM_CACHES: $stack = explode(" ", $task->get("stack")); if (!empty($stack)) { $id = array_pop($stack); $item = ORM::factory("item", $id); $item->relative_path(); // this rebuilds the cache and saves the item as a side-effect $task->set("stack", implode(" ", $stack)); $completed++; } if (empty($stack)) { // Try refilling the stack foreach (self::find_empty_item_caches(500) as $row) { $stack[] = $row->id; } $task->set("stack", implode(" ", $stack)); if (empty($stack)) { $state = self::FIX_STATE_START_MISSING_ACCESS_CACHES; } } break; case self::FIX_STATE_START_MISSING_ACCESS_CACHES: $stack = array(); foreach (self::find_missing_access_caches_limited(500) as $row) { $stack[] = $row->id; } $task->set("stack", implode(" ", $stack)); $state = self::FIX_STATE_RUN_MISSING_ACCESS_CACHES; break; case self::FIX_STATE_RUN_MISSING_ACCESS_CACHES: $stack = array_filter(explode(" ", $task->get("stack"))); // filter removes empty/zero ids if (!empty($stack)) { $id = array_pop($stack); $access_cache = ORM::factory("access_cache"); $access_cache->item_id = $id; $access_cache->save(); $task->set("stack", implode(" ", $stack)); $completed++; } if (empty($stack)) { // Try refilling the stack foreach (self::find_missing_access_caches_limited(500) as $row) { $stack[] = $row->id; } $task->set("stack", implode(" ", $stack)); if (empty($stack)) { // The new cache rows are there, but they're incorrectly populated so we have to fix // them. If this turns out to be too slow, we'll have to refactor // access::recalculate_permissions to allow us to do it in slices. access::recalculate_album_permissions(item::root()); $state = self::FIX_STATE_DONE; } } break; } } $task->set("state", $state); $task->set("completed", $completed); if ($state == self::FIX_STATE_DONE) { $task->done = true; $task->state = "success"; $task->percent_complete = 100; module::set_var("gallery", "maintenance_mode", 0); } else { $task->percent_complete = round(100 * $completed / $total); } $task->status = t2("One operation complete", "%count / %total operations complete", $completed, array("total" => $total)); }
public function delete_album_fails_without_permission_test() { $album1 = test::random_album(); access::deny(identity::everybody(), "edit", $album1); identity::set_active_user(identity::guest()); $request->url = rest::url("item", $album1); try { item_rest::delete($request); } catch (Exception $e) { $this->assert_equal("@todo FORBIDDEN", $e->getMessage()); return; } $this->assert_true(false, "Shouldn't get here"); }
static function fix($task) { $start = microtime(true); $total = $task->get("total"); if (empty($total)) { // mptt: 2 operations for every item $total = 2 * db::build()->count_records("items"); // album audit (permissions and bogus album covers): 1 operation for every album $total += db::build()->where("type", "=", "album")->count_records("items"); // one operation for each missing slug, name and access cache foreach (array("find_dupe_slugs", "find_dupe_names", "find_missing_access_caches") as $func) { foreach (self::$func() as $row) { $total++; } } $task->set("total", $total); $task->set("state", $state = self::FIX_STATE_START_MPTT); $task->set("ptr", 1); $task->set("completed", 0); } $completed = $task->get("completed"); $state = $task->get("state"); if (!module::get_var("gallery", "maintenance_mode")) { module::set_var("gallery", "maintenance_mode", 1); } // This is a state machine that checks each item in the database. It verifies the following // attributes for an item. // 1. Left and right MPTT pointers are correct // 2. The .htaccess permission files for restricted items exist and are well formed. // 3. The relative_path_cache and relative_url_cache values are set to null. // 4. there are no album_cover_item_ids pointing to missing items // // We'll do a depth-first tree walk over our hierarchy using only the adjacency data because // we don't trust MPTT here (that might be what we're here to fix!). Avoid avoid using ORM // calls as much as possible since they're expensive. // // NOTE: the MPTT check will only traverse items that have valid parents. It's possible that // we have some tree corruption where there are items with parent ids to non-existent items. // We should probably do something about that. while ($state != self::FIX_STATE_DONE && microtime(true) - $start < 1.5) { switch ($state) { case self::FIX_STATE_START_MPTT: $task->set("ptr", $ptr = 1); $task->set("stack", item::root()->id . ":L"); $state = self::FIX_STATE_RUN_MPTT; break; case self::FIX_STATE_RUN_MPTT: $ptr = $task->get("ptr"); $stack = explode(" ", $task->get("stack")); list($id, $ptr_mode) = explode(":", array_pop($stack)); if ($ptr_mode == "L") { $stack[] = "{$id}:R"; db::build()->update("items")->set("left_ptr", $ptr++)->where("id", "=", $id)->execute(); foreach (db::build()->select(array("id"))->from("items")->where("parent_id", "=", $id)->order_by("left_ptr", "ASC")->execute() as $child) { array_push($stack, "{$child->id}:L"); } } else { if ($ptr_mode == "R") { db::build()->update("items")->set("right_ptr", $ptr++)->set("relative_path_cache", null)->set("relative_url_cache", null)->where("id", "=", $id)->execute(); } } $task->set("ptr", $ptr); $task->set("stack", implode(" ", $stack)); $completed++; if (empty($stack)) { $state = self::FIX_STATE_START_DUPE_SLUGS; } break; case self::FIX_STATE_START_DUPE_SLUGS: $stack = array(); foreach (self::find_dupe_slugs() as $row) { list($parent_id, $slug) = explode(":", $row->parent_slug, 2); $stack[] = join(":", array($parent_id, $slug)); } if ($stack) { $task->set("stack", implode(" ", $stack)); $state = self::FIX_STATE_RUN_DUPE_SLUGS; } else { $state = self::FIX_STATE_START_DUPE_NAMES; } break; case self::FIX_STATE_RUN_DUPE_SLUGS: $stack = explode(" ", $task->get("stack")); list($parent_id, $slug) = explode(":", array_pop($stack)); // We want to leave the first one alone and update all conflicts to be random values. $fixed = 0; $conflicts = ORM::factory("item")->where("parent_id", "=", $parent_id)->where("slug", "=", $slug)->find_all(1, 1); if ($conflicts->count() && ($conflict = $conflicts->current())) { $task->log("Fixing conflicting slug for item id {$conflict->id}"); db::build()->update("items")->set("slug", $slug . "-" . (string) rand(1000, 9999))->where("id", "=", $conflict->id)->execute(); // We fixed one conflict, but there might be more so put this parent back on the stack // and try again. We won't consider it completed when we don't fix a conflict. This // guarantees that we won't spend too long fixing one set of conflicts, and that we // won't stop before all are fixed. $stack[] = "{$parent_id}:{$slug}"; break; } $task->set("stack", implode(" ", $stack)); $completed++; if (empty($stack)) { $state = self::FIX_STATE_START_DUPE_NAMES; } break; case self::FIX_STATE_START_DUPE_NAMES: $stack = array(); foreach (self::find_dupe_names() as $row) { list($parent_id, $name) = explode(":", $row->parent_name, 2); $stack[] = join(":", array($parent_id, $name)); } if ($stack) { $task->set("stack", implode(" ", $stack)); $state = self::FIX_STATE_RUN_DUPE_NAMES; } else { $state = self::FIX_STATE_START_ALBUMS; } break; case self::FIX_STATE_RUN_DUPE_NAMES: $stack = explode(" ", $task->get("stack")); list($parent_id, $name) = explode(":", array_pop($stack)); $fixed = 0; // We want to leave the first one alone and update all conflicts to be random values. $conflicts = ORM::factory("item")->where("parent_id", "=", $parent_id)->where("name", "=", $name)->find_all(1, 1); if ($conflicts->count() && ($conflict = $conflicts->current())) { $task->log("Fixing conflicting name for item id {$conflict->id}"); db::build()->update("items")->set("name", $name . "-" . (string) rand(1000, 9999))->where("id", "=", $conflict->id)->execute(); // We fixed one conflict, but there might be more so put this parent back on the stack // and try again. We won't consider it completed when we don't fix a conflict. This // guarantees that we won't spend too long fixing one set of conflicts, and that we // won't stop before all are fixed. $stack[] = "{$parent_id}:{$name}"; break; } $task->set("stack", implode(" ", $stack)); $completed++; if (empty($stack)) { $state = self::FIX_STATE_START_ALBUMS; } break; case self::FIX_STATE_START_ALBUMS: $stack = array(); foreach (db::build()->select("id")->from("items")->where("type", "=", "album")->execute() as $row) { $stack[] = $row->id; } $task->set("stack", implode(" ", $stack)); $state = self::FIX_STATE_RUN_ALBUMS; break; case self::FIX_STATE_RUN_ALBUMS: $stack = explode(" ", $task->get("stack")); $id = array_pop($stack); $item = ORM::factory("item", $id); if ($item->album_cover_item_id) { $album_cover_item = ORM::factory("item", $item->album_cover_item_id); if (!$album_cover_item->loaded()) { $item->album_cover_item_id = null; $item->save(); } } $everybody = identity::everybody(); $view_col = "view_{$everybody->id}"; $view_full_col = "view_full_{$everybody->id}"; $intent = ORM::factory("access_intent")->where("item_id", "=", $id)->find(); if ($intent->{$view_col} === access::DENY) { access::update_htaccess_files($item, $everybody, "view", access::DENY); } if ($intent->{$view_full_col} === access::DENY) { access::update_htaccess_files($item, $everybody, "view_full", access::DENY); } $task->set("stack", implode(" ", $stack)); $completed++; if (empty($stack)) { $state = self::FIX_STATE_START_MISSING_ACCESS_CACHES; } break; case self::FIX_STATE_START_MISSING_ACCESS_CACHES: $stack = array(); foreach (self::find_missing_access_caches() as $row) { $stack[] = $row->id; } if ($stack) { $task->set("stack", implode(" ", $stack)); $state = self::FIX_STATE_RUN_MISSING_ACCESS_CACHES; } else { $state = self::FIX_STATE_DONE; } break; case self::FIX_STATE_RUN_MISSING_ACCESS_CACHES: $stack = explode(" ", $task->get("stack")); $id = array_pop($stack); $access_cache = ORM::factory("access_cache"); $access_cache->item_id = $id; $access_cache->save(); $task->set("stack", implode(" ", $stack)); $completed++; if (empty($stack)) { // The new cache rows are there, but they're incorrectly populated so we have to fix // them. If this turns out to be too slow, we'll have to refactor // access::recalculate_permissions to allow us to do it in slices. access::recalculate_album_permissions(item::root()); $state = self::FIX_STATE_DONE; } break; } } $task->set("state", $state); $task->set("completed", $completed); if ($state == self::FIX_STATE_DONE) { $task->done = true; $task->state = "success"; $task->percent_complete = 100; module::set_var("gallery", "maintenance_mode", 0); } else { $task->percent_complete = round(100 * $completed / $total); } $task->status = t2("One operation complete", "%count / %total operations complete", $completed, array("total" => $total)); }
/** * Rebuild the .htaccess files that prevent direct access to albums, resizes and thumbnails. We * call this internally any time we change the view or view_full permissions for guest users. * This function is only public because we use it in maintenance tasks. * * @param Item_Model the album * @param Group_Model the group whose permission is changing * @param string the permission name * @param string the new permission value (eg access::DENY) */ static function update_htaccess_files($album, $group, $perm_name, $value) { if ($group->id != identity::everybody()->id || !($perm_name == "view" || $perm_name == "view_full")) { return; } $dirs = array($album->file_path()); if ($perm_name == "view") { $dirs[] = dirname($album->resize_path()); $dirs[] = dirname($album->thumb_path()); } $base_url = url::base(true); $sep = "?"; if (strpos($base_url, "?") !== false) { $sep = "&"; } $base_url .= $sep . "kohana_uri=/file_proxy"; // Replace "/index.php/?kohana..." with "/index.php?koahan..." // Doesn't apply to "/?kohana..." or "/foo/?kohana..." // Can't check for "index.php" since the file might be renamed, and // there might be more Apache aliases / rewrites at work. $url_path = parse_url($base_url, PHP_URL_PATH); // Does the URL path have a file component? if (preg_match("#[^/]+\\.php#i", $url_path)) { $base_url = str_replace("/?", "?", $base_url); } foreach ($dirs as $dir) { if ($value === access::DENY) { $fp = fopen("{$dir}/.htaccess", "w+"); fwrite($fp, "<IfModule mod_rewrite.c>\n"); fwrite($fp, " RewriteEngine On\n"); fwrite($fp, " RewriteRule (.*) {$base_url}/\$1 [L]\n"); fwrite($fp, "</IfModule>\n"); fwrite($fp, "<IfModule !mod_rewrite.c>\n"); fwrite($fp, " Order Deny,Allow\n"); fwrite($fp, " Deny from All\n"); fwrite($fp, "</IfModule>\n"); fclose($fp); } else { @unlink($dir . "/.htaccess"); } } }
static function hotfix_all() { $messages = array(); $messages[] = t('Running Hotfix'); /* ON THE LAST RUN WE NEED TO RE-FIX ALL DAMAGED ALBUM THUMBS! */ $albumDir = self::$album_dir; if (substr($albumDir, -1) != DIRECTORY_SEPARATOR) { $albumDir .= DIRECTORY_SEPARATOR; } foreach (self::$albums_flat as $g1_album) { $album_id = self::map($g1_album, '', 'album'); if (!$album_id) { $messages[] = t('Album %name not found', array('name' => $g1_album)); continue; } $album = ORM::factory('item', $album_id); $importDir = $albumDir . $g1_album . DIRECTORY_SEPARATOR; try { require_once 'Gallery1DataParser.php'; list($result, $items) = Gallery1DataParser::getPhotos($importDir); if ($result == null) { foreach ($items as $object) { if (isset($object->highlight) && $object->highlight == 1 && isset($object->highlightImage) && is_a($object->highlightImage, 'G1Img')) { $g1_path = $importDir . $object->highlightImage->name . '.' . $object->highlightImage->type; if (is_file($g1_path) && @copy($g1_path, $album->thumb_path())) { $album->thumb_height = $object->highlightImage->height; $album->thumb_width = $object->highlightImage->width; $album->thumb_dirty = false; $album->save(); } } } } } catch (Exception $e) { $messages[] = (string) new G1_Import_Exception(t('Failed to copy thumb for album %name.', array('name' => $g1_album)), $e); } } /* ON THE LAST RUN WE NEED TO RE-FIX ALL ALBUM PERMISSIONS */ foreach (self::$albums_hidden as $g1_album => $dummy) { try { $album_id = self::map($g1_album, '', 'album'); $album = ORM::factory('item', $album_id); access::deny(identity::everybody(), 'view', $album); $messages[] = t('Denying access to %album', array('album' => $g1_album)); } catch (Exception $e) { $messages[] = (string) new G1_Import_Exception(t('Failed to set access permission for hidden album %name.', array('name' => $g1_album)), $e); } } return $messages; }