/** main program for visitors * * this routine is called from /index.php. It is the main program for visitors. * * @return void page sent to the browser * @todo cleanup login/logout-code */ function main_index() { global $USER; global $CFG; global $LANGUAGE; /** initialise the program, setup database, read configuration, etc. */ require_once $CFG->progdir . '/init.php'; initialise(); was_version_check(); // this never returns if versions don't match // TODO: cleanup like in main_admin() // handle login/logout/continuation so we quickly find out which user is calling if (isset($_GET['logout'])) { /** loginlib.php contains both login- and logout-routines */ require_once $CFG->progdir . '/lib/loginlib.php'; was_logout(); // may or may not return here } elseif (isset($_GET['login'])) { /** loginlib.php contains both login- and logout-routines */ require_once $CFG->progdir . '/lib/loginlib.php'; was_login(magic_unquote($_GET['login'])); // may or may not return here } elseif (isset($_COOKIE[$CFG->session_name])) { /** dbsessionlib.php contains our own database based session handler */ require_once $CFG->progdir . '/lib/dbsessionlib.php'; dbsession_setup($CFG->session_name); if (dbsession_exists(magic_unquote($_COOKIE[$CFG->session_name]))) { session_start(); } } // At this point we either have a valid session with a logged-in user // (indicated via existence of $_SESSION) or we are dealing with an anonymous // visitor with non-existing $_SESSION. Keep track of the number of calls // this user makes (may be logged lateron on logout). if (isset($_SESSION)) { if (!isset($_SESSION['session_counter'])) { // first time after login, record start time of session $_SESSION['session_counter'] = 1; $_SESSION['session_start'] = strftime("%Y-%m-%d %T"); } else { $_SESSION['session_counter']++; } } // Now is the time to create a USER object, even when the visitor is just a passerby // because we can then determine easily if a visitor is allowed certain things, e.g. // view a protected area or something /** useraccount.class.php is used to define the USER object */ require_once $CFG->progdir . '/lib/useraccount.class.php'; if (isset($_SESSION) && isset($_SESSION['user_id'])) { $USER = new Useraccount($_SESSION['user_id']); $USER->is_logged_in = TRUE; $_SESSION['language_key'] = $LANGUAGE->get_current_language(); // remember language set via _GET or otherwise } else { $USER = new Useraccount(); $USER->is_logged_in = FALSE; } // Check for the special preview-mode // This allows a webmaster to preview a page in the correct environment (theme) // even when the page is under embargo. Note that the node_id and area_id are // retrieved from the session; the user only has a cryptic preview-code. // See pagemanagerlib.php for more information (function task_page_preview()). $in_preview_mode = FALSE; if ($USER->is_logged_in) { $preview_code_from_url = get_parameter_string('preview'); if (!is_null($preview_code_from_url) && isset($_SESSION['preview_salt']) && isset($_SESSION['preview_node'])) { $hash = md5($_SESSION['preview_salt'] . $_SESSION['preview_node']); if ($hash === $preview_code_from_url) { $node_id = intval($_SESSION['preview_node']); $area_id = intval($_SESSION['preview_area']); $area = db_select_single_record('areas', '*', array('area_id' => $area_id)); if ($area === FALSE) { logger("Fatal error 070: cannot preview node '{$node_id}' in area '{$area_id}'"); error_exit('070'); } else { $tree = tree_build($area_id); $in_preview_mode = TRUE; } } } } if ($in_preview_mode == FALSE) { $requested_area = get_requested_area(); $requested_node = get_requested_node(); $req_area_str = is_null($requested_area) ? "NULL" : strval($requested_area); $req_node_str = is_null($requested_node) ? "NULL" : strval($requested_node); if (($area = calculate_area($requested_area, $requested_node)) === FALSE) { logger("Fatal error 080: no valid area (request: area='{$req_area_str}', node='{$req_node_str}')"); error_exit('080'); // no such area } $area_id = intval($area['area_id']); // If $USER has no permission to view area $area_id, we simply bail out. // Rationale: if the user is genuine, she knows about logging in first. // If the user is NOT logged in and tries to view a protected area, I'd consider // it malicious, and in that case I won't even confirm the existence of // the requested area. (If a cracker simply tries areas 0,1,.. and sometimes is greeted // with 'please enter credentials' and sometimes with 'area does not exist', this // provides information to the cracker. I don't want that). Note that the error code // is the same as the one for non-existing area. In other words: for an unauthorised // visitor an existing private area is just as non-existent as a non-existing public area. if (db_bool_is(TRUE, $area['is_private']) && !$USER->has_intranet_permissions(ACL_ROLE_INTRANET_ACCESS, $area_id)) { logger(sprintf("Fatal error 080: no view permissions for area '%d' (request: area='%s', node='%s')", $area_id, $req_area_str, $req_node_str)); error_exit('080'); // no such area } // still here? // then we've got a valid $area_id and corresponding $area record. // now we need to figure out which $node_id to use $tree = tree_build($area_id); if (($node_id = calculate_node_id($tree, $area_id, $requested_node)) === FALSE) { logger(sprintf("Fatal error 080: no valid node within area '%d' (request: area='%s', node='%s')", $area_id, $req_area_str, $req_node_str)); error_exit('080'); // no such area } } // At this point we have the following in our hands // - a valid $area_id // - a valid $node_id // - the complete tree from area $area_id in $tree // - the area record from database in $area // - the node record from database in $tree[$node_id]['record'] // - a flag that signals preview mode in $in_preview_mode // We are on our way to generate a full page with content and all, // but otoh we MIGHT be in the middle of a redirect, so we may have to // leave without showing anything at all... if (!empty($tree[$node_id]['record']['link_href'])) { update_statistics($node_id); if (isset($_SESSION)) { session_write_close(); } redirect_and_exit(htmlspecialchars($tree[$node_id]['record']['link_href'])); // exit; redirect_and_exit() never returns } /** themelib contains the theme factory */ require_once $CFG->progdir . '/lib/themelib.php'; // And now we know about the $area, we can carry on determining which $theme to use. // $theme = theme_factory($area['theme_id'], $area_id, $node_id); if ($theme === FALSE) { logger("Fatal error 090: cannot setup theme '{$area['theme_id']}' in area '{$area_id}'"); error_exit('090'); } // Tell the theme about the preview mode $theme->set_preview_mode($in_preview_mode); // Now all we need to do is let the module connected to node $node_id generate output $module_id = $tree[$node_id]['record']['module_id']; module_view($theme, $area_id, $node_id, $module_id); // Remember this visitor update_statistics($node_id); // Finally, send output to user $theme->send_output(); if (isset($_SESSION)) { session_write_close(); } // done! exit; }
/** preview a page that is maybe still under embargo/already expired * * if the user has permissions to preview the specified page, she is * redirected to the regular site with a special one-time permission to view * a page, even if that page is under embargo or already expired (which * normally would prevent any user from viewing that page). * * There are several ways to implement such a one-off permit, e.g. * by setting a quasi-random string in the session and specifying that * string as a parameter to index.php. If (in index.php) the string provided matches * ths string in the session, the user is granted access. However, this * leaves room for the user to manually change the node id to _any_ * number, even a node that that user is not supposed to see. * * Another solution might have been to simply include index.php. * I decided against that; I don't want to have to deal with a mix * of admin.php and index.php-code in the same run. * * I took a slightly different approach, as follows. * First I generate a quasi-random string of N (N=32) characters. * (The lenght of 32 is an arbitrary choice.) * This string is stored in the session variable. * Then I store the requested node in the session variable, too. * After that I calculate the md5sum of the combination of the * random string and the node id. This yields a hash. * This hash is passed on to index.php as the sole parameter. * * Note that the quasi-random key never leaves the server: it is * only stored in the session variables. Also, the node id is not * one of the parameters of index.php, this too is only stored in * the session variables. * * Once index.php is processed, the specified md5sum is retrieved * and a check is performed on the node id and the quasi-random string * in the session variables in order to see if the hashes match. If * this is the case, index.php can proceed to show the page preview. * Note that there is no way for the user to manipulate the node id, * because that number never travels to the user's browser in plain * text. * * Making a bookmark for the preview will use the hash, but the hash * depends on a quasi-random string stored in the session. It means * that when the session is terminated, the bookmarked page will no * longer be visible, which is good. Also, whenever another page * preview is requested, a new quasi-random string is generated, * which also invalidates the bookmarked page. * * The only thing that CAN happen is that the user saves the preview in * a place where it can be seen by others. Also, the page will probably * be cached in the user's browser. * * With respect to permissions: I consider the preview privilege equivalent * with edit permission: if the user is able to edit the node she can see * the content of the node anyway. However, maybe we should look at different * permissions. Put it on the todo-list. * * @return void results are returned as output in $this->output * @uses $CFG * @todo the check on permissions can be improved (is PERMISSION_XXXX_EDIT_NODE enough?) * @todo there is an issue with redirecting to another site: * officially the url should be fully qualified (ie. $CFG->www). * I use the shorthand, possibly without scheme and hostname * ($CFG->www_short). This might pose a problem with picky browsers. * See {@link calculate_uri_shortcuts} for more information. */ function task_page_preview() { global $CFG; // 1A -- do we have a sane value for node_id? $node_id = get_parameter_int('node', 0); $anode = array('{NODE}' => strval($node_id)); if ($node_id == 0 || !isset($this->tree[$node_id])) { // are they trying to trick us, specifying a node from another area? logger(__FUNCTION__ . "(): weird: user previews node '{$node_id}' working in area '{$this->area_id}'?"); $this->output->add_message(t('invalid_node', 'admin', $anode)); $this->task_treeview(); return; } // 1B -- is it a page? if (!$this->tree[$node_id]['is_page']) { logger(__CLASS__ . ": weird: cannot preview content of a section (section '{$node_id}')"); $this->task_treeview(); return; } // 2 -- does the user have permission to edit and thus view this page at all? $user_has_permission = $this->permission_edit_node_content($node_id) || $this->permission_edit_node($node_id, $this->tree[$node_id]['is_page']); if ($user_has_permission) { $random_string = quasi_random_string(32); $_SESSION['preview_salt'] = $random_string; $_SESSION['preview_node'] = $node_id; $_SESSION['preview_area'] = $this->area_id; $hash = md5($_SESSION['preview_salt'] . $_SESSION['preview_node']); session_write_close(); redirect_and_exit($CFG->www_short . '/index.php?preview=' . $hash); // we never reach this point } else { $msg = t('access_denied', 'admin'); $this->output->add_message($msg); $this->output->add_popup_bottom($msg); $this->output->add_content('<h2>' . $msg . '</h2>'); $this->output->add_content(t('access_denied_preview', 'admin')); } }