/** * Set a requested theme and template as default for the site. */ public function setdefault() { $request = $this->getPageRequest(); $view = $this->getView(); $themename = $this->getPageRequest()->getParameter(0); $template = $this->getPageRequest()->getParameter('template'); // If the browser prefers JSON data, send that. if ($request->prefersContentType(View::CTYPE_JSON)) { $view->contenttype = View::CTYPE_JSON; } // Validate the theme name if (!\Theme\validate_theme_name($themename)) { \Core\set_message('Invalid theme requested', 'error'); \Core\go_back(); } $theme = ThemeHandler::GetTheme($themename); if ($template) { // The template itself can be ignored. if (!\Theme\validate_template_name($themename, $template)) { \Core\set_message('Invalid template requested', 'error'); \Core\go_back(); } } else { // and the default one is used otherwise. $allskins = $theme->getSkins(); $template = $allskins[0]['file']; } if (Core::IsComponentAvailable('multisite') && MultiSiteHelper::GetCurrentSiteID()) { $config_default = ConfigHandler::GetConfig('/theme/default_template'); $config_selected = ConfigHandler::GetConfig('/theme/selected'); if ($config_default->get('overrideable') == 0) { // It's a child site and the admin never gave them permission to change default themes! \Core\set_message('Unable to set the default template on a child site, please ensure that the "/theme/default_template" config is set to be overrideable!', 'error'); \Core\go_back(); } if ($config_selected->get('overrideable') == 0) { // It's a child site and the admin never gave them permission to change default themes! \Core\set_message('Unable to set the selected theme on a child site, please ensure that the "/theme/selected" config is set to be overrideable!', 'error'); \Core\go_back(); } } if ($request->isPost()) { if ($themename != ConfigHandler::Get('/theme/selected')) { // The theme changed, change the admin skin too! ConfigHandler::Set('/theme/default_admin_template', $template); // And the email skin. ConfigHandler::Set('/theme/default_email_template', ''); } ConfigHandler::Set('/theme/default_template', $template); ConfigHandler::Set('/theme/selected', $themename); // reinstall theme and zee assets $t = ThemeHandler::GetTheme(); if (($change = $t->reinstall(0)) !== false) { SystemLogModel::LogInfoEvent('/updater/theme/reinstall', 'Theme ' . $t->getName() . ' reinstalled successfully', implode("\n", $change)); } \Core\set_message('Updated default theme', 'success'); \Core\redirect('/theme'); } $view->assign('theme', $themename); $view->assign('template', $template); }
/** * Execute the actual cron for the requested type. * * @param string $cron Cron type to execute. * * @return CronLogModel * @throws Exception */ private function _performcron($cron) { switch ($cron) { case '1-minute': case '5-minute': case '15-minute': case '30-minute': case 'hourly': case '2-hour': case '3-hour': case '6-hour': case '12-hour': case 'daily': case 'weekly': case 'monthly': break; default: throw new Exception('Unsupported cron type: [' . $cron . ']'); } if (!ConfigHandler::Get('/cron/enabled')) { $msg = 'Cron execution is globally disabled via the site configuration, not executing cron!'; SystemLogModel::LogInfoEvent('/cron/' . $cron, $msg); // It needs to return something. $log = new CronLogModel(); $log->set('status', 'fail'); return $log; } // First, check and see if there's one that's still running. $runninglogs = CronLogModel::Find(array('cron' => $cron, 'status' => 'running')); if (sizeof($runninglogs)) { foreach ($runninglogs as $log) { /** @var $log CronLogModel */ $log->set('status', 'fail'); $log->set('log', $log->get('log') . "\n------------\nTIMED OUT!"); $log->save(); } } // Start recording. $log = new CronLogModel(); $log->set('cron', $cron); $log->set('status', 'running'); $log->set('memory', memory_get_usage()); $log->set('ip', REMOTE_IP); $log->save(); $start = microtime(true) * 1000; $sep = '==========================================' . "\n"; $contents = "Starting cron execution for {$cron}\n{$sep}"; // This uses the hook system, but will be slightly different than most things. $overallresult = true; $hook = HookHandler::GetHook('/cron/' . $cron); $hookcount = 0; $hooksuccesses = 0; if ($hook) { if ($hook->getBindingCount()) { $hookcount = $hook->getBindingCount(); $bindings = $hook->getBindings(); foreach ($bindings as $b) { $contents .= sprintf("\nExecuting Binding %s...\n", $b['call']); // Since these systems will just be writing to STDOUT, I'll need to capture that. try { ob_start(); $execution = $hook->callBinding($b, array()); $executiondata = ob_get_clean(); } catch (Exception $e) { $execution = false; $executiondata = 'EXCEPTION: ' . $e->getMessage() . ob_get_clean(); } if ($executiondata == '' && $execution) { $contents .= "Cron executed successfully with no output\n"; ++$hooksuccesses; } elseif ($execution) { $contents .= $executiondata . "\n"; ++$hooksuccesses; } else { $contents .= $executiondata . "\n!!FAILED\n"; $overallresult = false; } } } else { $contents = 'No bindings located for requested cron'; $overallresult = true; } } else { $contents = 'Invalid hook requested: ' . $cron; $overallresult = false; } // Just in case the contents are returning html... (they should be plain text). // Replace the most common line endings with things that make sense for plain text. // This is to ensure that all the available scenarios are met and saved/displayed without extra whitespace. // // Since some systems will provide plain text (easy!), windows/os9 line endings, // HTML (br and br/), and formatted HTML (br + \n). $contents = str_ireplace(["\r\n<br>", "\r\n<br/>", "\r\n<br />", "\n<br>", "\n<br/>", "\n<br />", "<br>", "<br/>", "<br />", "\r\n", "\r"], "\n", $contents); // Save the results. $log->set('completed', Time::GetCurrentGMT()); $log->set('duration', microtime(true) * 1000 - $start); $log->set('log', $contents); $log->set('status', $overallresult ? 'pass' : 'fail'); $log->save(); // Make a copy of this in the system log too if applicable. // This time is listed in ms $time = microtime(true) * 1000 - $start; // 0.01 = 10 ns // 1 = 1 ms // 1000 = 1 second if ($time < 1) { // TIME is less than 1, which means it executed faster than 1ms, display in nanoseconds. $time = round($time, 4) * 1000 . ' ns'; } elseif ($time < 1000) { // TIME is less than 1000, which means it executed faster than 1 second, display in milliseconds. $time = round($time, 0) . ' ms'; } else { // TIME is at least 1 second or longer... Display in minutes:seconds, (no need to display 1.453 seconds!) // First, convert the milliseconds to seconds; they are more manageable for what I need to do. // This will change time from 12345(ms) to 13(seconds) $time = ceil($time / 1000); $minutes = floor($time / 60); $seconds = $time - $minutes * 60; if ($minutes > 0) { $time = $minutes . 'm ' . str_pad($seconds, 2, '0', STR_PAD_LEFT) . 's'; } else { $time = $seconds . ' seconds'; } } if ($hookcount > 0) { $msg = 'Cron ' . $cron . ' completed in ' . $time . '. ' . $hooksuccesses . ' out of ' . $hookcount . ' hooks called successfully.'; SystemLogModel::LogInfoEvent('/cron/' . $cron, $msg, $contents); } // Just to notify the calling function. return $log; }
<?php /** * Upgrade file for Core 2.8.0 to 3.0.0 (Security 1.2.0 to 1.3.0) * * The security model has been migrated into Core as of 3.0, so this script * migrates the data from the security component into Core. * * @author Charlie Powell <*****@*****.**> * @date 20131229.2033 */ // Handle conversion of the security entries to system log entries. // These are now contained in that table. $secfac = new ModelFactory('SecurityLogModel'); $stream = new \Core\Datamodel\DatasetStream($secfac->getDataset()); while ($row = $stream->getRecord()) { //$model = SystemLogModel::Construct($row['id']); $model = new SystemLogModel($row['id']); // Standard keys $model->setFromArray(['datetime' => $row['datetime'], 'session_id' => $row['session_id'], 'user_id' => $row['user_id'], 'ip_addr' => $row['ip_addr'], 'useragent' => $row['useragent'], 'code' => $row['action'], 'affected_user_id' => $row['affected_user_id'], 'message' => $row['details']]); $model->set('type', $row['status'] == 'fail' ? 'security' : 'info'); $model->save(); }
/** * Set this component as enabled in the database. */ public function enable(){ // If it's not installed already, it can't be disabled! if($this->isEnabled()) return false; $c = new ComponentModel($this->_name); $c->set('enabled', true); $c->save(); $this->_enabled = true; $changed = array(); $change = $this->_parseUserConfigs(); if ($change !== false) $changed = array_merge($changed, $change); $change = $this->_parsePages(); if ($change !== false) $changed = array_merge($changed, $change); // Do this when I actually have widgets to test. //$change = $this->_parseWidgets(); //if ($change !== false) $changed = array_merge($changed, $change); if(sizeof($changed)){ SystemLogModel::LogInfoEvent('/updater/component/enable', 'Component ' . $this->getName() . ' enabled successfully!', implode("\n", $changed)); } // Ensure that the core component cache is purged too! \Core\Cache::Delete('core-components'); return (sizeof($changed)) ? $changed : false; }
/** * Helper function called by the *_install views. * * @param $type * @param $name * @param $version */ private function _performInstall($type, $name, $version){ $view = $this->getView(); $req = $this->getPageRequest(); $dryrun = $req->getParameter('dryrun'); $verbose = $req->getParameter('verbose'); $nl = "<br/>\n"; // For standard calls, this is a json-only page. // verbose runs are html however. if($verbose){ $view->contenttype = View::CTYPE_HTML; $view->mode = View::MODE_NOOUTPUT; } else{ $view->contenttype = View::CTYPE_JSON; } // This is a post-only page! if(!$req->isPost()){ $view->error = View::ERROR_BADREQUEST; return; } $return = UpdaterHelper::PerformInstall($type, $name, $version, $dryrun, $verbose); // If it's not a dry run, record a log of this action! if(!$dryrun){ if($return['status']){ $logmsg = 'Installation of ' . $type . ' ' . $name . ' ' . $version . ' succeeded!' . "\n" . $return['message']; if(isset($return['changes'])){ foreach($return['changes'] as $change){ $logmsg .= "\n" . $change; } } $logstatus = 'success'; } else{ $logmsg = 'Installation of ' . $type . ' ' . $name . ' ' . $version . ' failed due to' . "\n" . $return['message']; $logstatus = 'fail'; } SystemLogModel::LogSecurityEvent( '/updater/installation', $logmsg ); } if($verbose){ if(!$return['status']){ echo $nl . '[=========== RESULTS ===========]' . $nl; echo '[ERROR] - ' . $return['message'] . $nl; } else{ echo $nl . '[=========== RESULTS ===========]' . $nl; if(isset($return['changes'])){ foreach($return['changes'] as $change){ echo '[INFO] - ' . $change . $nl; } } echo '[SUCCESS] - Performed all operations successfully!' . $nl; } echo '<div id="results" style="display:none;" status="' . $return['status'] . '">' . $return['message'] . '</div>'; } else{ $view->jsondata = $return; } }
/** * "Upgrade" (aka) Install this theme and its assets. * * Alias of install() * * @return false | array * @throws \InstallerException */ public function upgrade() { //if(!$this->isInstalled()) return false; $changes = $this->_performInstall(); if (is_array($changes) && sizeof($changes)) { \SystemLogModel::LogInfoEvent('/updater/theme/upgrade', 'Theme ' . $this->getName() . ' installed successfully!', implode("\n", $changes)); } return $changes; }
/** * Function that is fired off on page load. * This checks if a form was submitted and that form was present in the SESSION. * * @return null */ public static function CheckSavedSessionData() { // This needs to ignore the /form/savetemporary.ajax page! // This is a custom page that's meant to intercept all POST submissions. if(preg_match('#^/form/(.*)\.ajax$#', REL_REQUEST_PATH)) return; // There has to be data in the session. $forms = \Core\Session::Get('FormData/*'); $formid = (isset($_REQUEST['___formid'])) ? $_REQUEST['___formid'] : false; $form = false; foreach ($forms as $k => $v) { // If the object isn't a valid object after unserializing... if (!($el = unserialize($v))) { \Core\Session::UnsetKey('FormData/' . $k); continue; } // Check the expires time if ($el->get('expires') <= Time::GetCurrent()) { \Core\Session::UnsetKey('FormData/' . $k); continue; } if ($k == $formid) { // Remember this for after all the checks have finished. $form = $el; } } // No form found... simple enough if (!$form) return; // Otherwise /** @var $form Form */ // Ensure the submission types match up. if (strtoupper($form->get('method')) != $_SERVER['REQUEST_METHOD']) { \Core\set_message('t:MESSAGE_ERROR_FORM_SUBMISSION_TYPE_DOES_NOT_MATCH'); return; } // Ensure the REFERRER and original URL match up. if($_SERVER['HTTP_REFERER'] != $form->originalurl){ // @todo This is reported to be causing issues with production sites. // If found true, this check may need to be removed / refactored. //\Core\set_message('Form submission referrer does not match, please try your submission again.', 'error'); SystemLogModel::LogInfoEvent( 'Form Referrer Mismatch', 'Form referrer does not match! Submitted: [' . $_SERVER['HTTP_REFERER'] . '] Expected: [' . $form->originalurl . ']' ); //return; } // Run though each element submitted and try to validate it. if (strtoupper($form->get('method')) == 'POST') $src =& $_POST; else $src =& $_GET; $form->loadFrom($src); // Try to load the form from that form. That will call all of the model's validation logic // and will throw exceptions if it doesn't. try{ $form->getModel(); // Still good? if (!$form->hasError()){ $status = call_user_func($form->get('callsmethod'), $form); } else{ $status = false; } } catch(ModelValidationException $e){ \Core\set_message($e->getMessage(), 'error'); $status = false; } catch(GeneralValidationException $e){ \Core\set_message($e->getMessage(), 'error'); $status = false; } catch(Exception $e){ if(DEVELOPMENT_MODE){ // Developers get the full message \Core\set_message($e->getMessage(), 'error'); } else{ // While users of production-enabled sites get a friendlier message. \Core\set_message('t:MESSAGE_ERROR_FORM_SUBMISSION_UNHANDLED_EXCEPTION'); } Core\ErrorManagement\exception_handler($e); $status = false; } // The form was submitted. Set its persistent flag to true so that whatever may be listening for it can retrieve the user's values. $form->persistent = true; // Regardless, bundle this form back into the session so the controller can use it if needed. \Core\Session::Set('FormData/' . $formid, serialize($form)); // Fail statuses. if ($status === false) return; if ($status === null) return; // Guess it's not false and not null... must be good then. // @todo Handle an internal save procedure for "special" groups such as pageinsertables and what not. // Cleanup \Core\Session::UnsetKey('FormData/' . $formid); if ($status === 'die'){ // If it's set to die, simply exit the script without outputting anything. exit; } elseif($status === 'back'){ if($form->referrer && $form->referrer != REL_REQUEST_PATH){ // Go back to the original form's referrer. \Core\redirect($form->referrer); } else{ // Use Core to guess which page to redirect back to, (not as reliable). \Core\go_back(); } } elseif ($status === true){ // If the return code is boolean true, it's a reload. \Core\reload(); } elseif($status === REL_REQUEST_PATH || $status === CUR_CALL){ // If the page returned the same page as the current url, force a reload, (as redirect will ignore it) \Core\reload(); } else{ // Anything else gets sent to the redirect system. \core\redirect($status); } }
/** * Check the user's IP in the blacklist and see if it's found. * * If it is and has a high enough submission rate, (in a 24 hour period), then block the user completely and immediately. */ public static function CheckIP() { $record = \sfsBlacklistModel::Construct(REMOTE_IP); // It's not in there, YAY! if (!$record->exists()) { return; } // Is the submission score high enough? $highscore = 100; if ($record->get('submissions') > $highscore) { // YOU can haz good party tiem nau \SystemLogModel::LogSecurityEvent('/security/blocked', 'Blocking IP due to over ' . $highscore . ' submissions to sfs in a 24 hour period.'); die('IP Blocked due to high spam score'); } // Submissions listed, but not exceedingly high? $warnlevel = 5; if ($record->get('submissions') > $warnlevel) { if (\Core\Session::Get('security_antispam_allowed') === null) { $html = '<html><body>'; $html .= '<!-- You smell of spam.... are you sure you didn\'t come from a can?-->'; if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['happyfuntime']) && \Core\Session::Get('happyfuntimecheck')) { // It's an attempt! if ($_POST['happyfuntime'] == \Core\Session::Get('happyfuntimecheck')) { \SystemLogModel::LogSecurityEvent('/security/unblocked', 'User successfully answered an anti-bot math question, unblocking.'); \Core\Session::Set('security_antispam_allowed', true); } else { \SystemLogModel::LogSecurityEvent('/security/captchafailed', 'User attempted, but failed in answering an anti-bot math question.'); $html .= '<b>NOPE!</b>'; } } \SystemLogModel::LogSecurityEvent('/security/blocked', 'Blocking IP due to over ' . $warnlevel . ' submissions to sfs in a 24 hour period.'); $random1 = rand(4, 6) * 2; $random2 = rand(1, 3) * 2; $random3 = rand(1, 2); switch ($random3) { case 1: $result = $random1 / $random2; $operation = 'divided by'; break; case 2: $result = $random1 * $random2; $operation = 'multiplied by'; break; } \Core\Session::Set('happyfuntimecheck', $result); switch ($random2) { case 1: $random2 = 'oNe'; break; case 2: $random2 = 'Tw0'; break; case 3: $random2 = 'ThRe'; break; case 4: $random2 = 'Foor'; break; case 5: $random2 = 'fIve'; break; case 6: $random2 = 'Siix'; break; } $html .= '<form method="POST"><p>What is ' . $random1 . ' ' . $operation . ' ' . $random2 . '?</p><input type="text" name="happyfuntime" size="3"/><input type="submit" value="GO"/></form></body></html>'; die($html); } } }
/** * The actual Core registration page. * * This renders all the user's configurable options at registration. */ public function register2(){ $view = $this->getView(); $request = $this->getPageRequest(); $manager = \Core\user()->checkAccess('p:/user/users/manage'); // Current user an admin? // Anonymous users should have access to this if it's allow public. if(!\Core\user()->exists() && !ConfigHandler::Get('/user/register/allowpublic')){ return View::ERROR_BADREQUEST; } // Authenticated users must check the permission to manage users. if(\Core\user()->exists() && !$manager){ return View::ERROR_ACCESSDENIED; } /** @var NonceModel $nonce */ $nonce = NonceModel::Construct($request->getParameter(0)); if(!$nonce->isValid()){ \Core\set_message('Invalid nonce token, please try again.', 'error'); \Core\go_back(); } $nonce->decryptData(); $data = $nonce->get('data'); if(!isset($data['user']) || !($data['user'] instanceof UserModel)){ if(DEVELOPMENT_MODE){ \Core\set_message('Your nonce does not include a "user" key. Please ensure that this is set to a non-existent UserModel object!', 'error'); } else{ \Core\set_message('Invalid login type, please try again later.', 'error'); } \Core\go_back(); } /** @var UserModel $user */ $user = $data['user']; $form = \Core\User\Helper::GetForm($user); // If the total number of form elements here are only 2, then only the user object and submit button are present. // Instead of showing the form, auto-submit to that destination. if(sizeof($form->getElements()) <= 2){ $user->setDefaultGroups(); $user->setDefaultMetaFields(); $user->setDefaultActiveStatuses(); $user->generateNewApiKey(); $user->save(); // User created... make a log of this! \SystemLogModel::LogSecurityEvent('/user/register', 'User registration successful', null, $user->get('id')); // Send a thank you for registering email to the user. try{ $user->sendWelcomeEmail(); } catch(\Exception $e){ \Core\ErrorManagement\exception_handler($e); \Core\set_message('t:MESSAGE_ERROR_CANNOT_SEND_WELCOME_EMAIL'); } // "login" this user if not already logged in. if(!\Core\user()->exists()){ if($user->get('active')){ $user->set('last_login', \CoreDateTime::Now('U', \Time::TIMEZONE_GMT)); $user->save(); \Core\Session::SetUser($user); } \Core\set_message('t:MESSAGE_SUCCESS_CREATED_USER_ACCOUNT'); if(($overrideurl = \HookHandler::DispatchHook('/user/postlogin/getredirecturl'))){ // Allow an external script to override the redirecting URL. $url = $overrideurl; } elseif($form->getElementValue('redirect')){ // The preferred default redirect method. // This is set from /user/register2, which is in turn passed in, (hopefully), by the original callee registration page. $url = $form->getElementValue('redirect'); } elseif(strpos(REL_REQUEST_PATH, '/user/register') === 0){ // If the user came from the registration page, get the page before that. $url = '/'; } else{ // else the registration link is now on the same page as the 403 handler. $url = REL_REQUEST_PATH; } \Core\redirect($url); } // It was created administratively; redirect there instead. else{ \Core\set_message('t:MESSAGE_SUCCESS_CREATED_USER_ACCOUNT'); \Core\redirect('/user/admin'); } } $form->addElement('hidden', ['name' => 'redirect', 'value' => $data['redirect']]); $view->title = 'Complete Registration'; $view->assign('form', $form); }
/** * View to accept and process the FB login post. * * This will redirect to the registration page if the user doesn't exist, * will throw an error and display a link to enable FB if it's not enabled already, * or will simply log the user in via Facebook and sync his/her settings. */ public function login() { $view = $this->getView(); $request = $this->getPageRequest(); $view->ssl = true; $view->record = false; $auths = \Core\User\Helper::GetEnabledAuthDrivers(); if (!isset($auths['facebook'])) { // Facebook isn't enabled, simply redirect to the home page. \Core\redirect('/'); } if (!FACEBOOK_APP_ID) { \Core\redirect('/'); } if (!FACEBOOK_APP_SECRET) { \Core\redirect('/'); } if (!$request->isPost()) { return View::ERROR_BADREQUEST; } $facebook = new Facebook(['appId' => FACEBOOK_APP_ID, 'secret' => FACEBOOK_APP_SECRET]); // Did the user submit the facebook login request? if (isset($_POST['login-method']) && $_POST['login-method'] == 'facebook' && $_POST['access-token']) { try { $facebook->setAccessToken($_POST['access-token']); /** @var int $fbid The user ID from facebook */ $fbid = $facebook->getUser(); /** @var array $user_profile The array of user data from Facebook */ $user_profile = $facebook->api('/me'); } catch (Exception $e) { \Core\set_message($e->getMessage(), 'error'); \Core\go_back(); return null; } /** @var \UserModel|null $user */ $user = UserModel::Find(['email' => $user_profile['email']], 1); if (!$user) { if (ConfigHandler::Get('/user/register/allowpublic')) { // If public registration is enabled, then redirect the user to the registration page to complete their registration. $user = new UserModel(); $user->set('email', $user_profile['email']); $user->enableAuthDriver('facebook'); $user->disableAuthDriver('datastore'); /** @var \Facebook\UserAuth $auth */ $auth = $user->getAuthDriver('facebook'); $auth->syncUser($_POST['access-token']); // Otherwise, w00t! Record this user into a nonce and forward to step 2 of registration. $nonce = NonceModel::Generate('20 minutes', null, ['user' => $user, 'redirect' => $_POST['redirect']]); \Core\redirect('/user/register2/' . $nonce); } else { // Log this as a login attempt! $logmsg = 'Failed Login (Facebook). Email not registered' . "\n" . 'Email: ' . $user_profile['email'] . "\n"; \SystemLogModel::LogSecurityEvent('/user/login', $logmsg); \Core\set_message('Your Facebook email (' . $user_profile['email'] . ') does not appear to be registered on this site.', 'error'); \Core\go_back(); return null; } } elseif (!$user->get('active')) { // The model provides a quick cut-off for active/inactive users. // This is the control managed with in the admin. $logmsg = 'Failed Login. User tried to login before account activation' . "\n" . 'User: '******'email') . "\n"; \SystemLogModel::LogSecurityEvent('/user/login', $logmsg, null, $user->get('id')); \Core\set_message('Your account is not active yet.', 'error'); \Core\go_back(); return null; } try { /** @var \Facebook\UserAuth $auth */ $auth = $user->getAuthDriver('facebook'); } catch (Exception $e) { \Core\set_message('Your account does not have Facebook logins enabled! <a href="' . \Core\resolve_link('/facebook/enable') . '">Do you want to enable Facebook?</a>', 'error'); \Core\go_back(); return null; } if (!$user->isActive()) { \Core\set_message('Your account is not active!', 'error'); \Core\go_back(); return null; } // Well yay the user is available and authencation driver is ready! $auth->syncUser($_POST['access-token']); if ($_POST['redirect']) { // The page was set via client-side javascript on the login page. // This is the most reliable option. $url = $_POST['redirect']; } elseif (REL_REQUEST_PATH == '/facebook/login') { // If the user came from the registration page, get the page before that. $url = '/'; } else { // else the registration link is now on the same page as the 403 handler. $url = REL_REQUEST_PATH; } // Well, record this too! \SystemLogModel::LogSecurityEvent('/user/login', 'Login successful (via Facebook)', null, $user->get('id')); // yay... $user->set('last_login', \CoreDateTime::Now('U', \Time::TIMEZONE_GMT)); $user->save(); \Core\Session::SetUser($user); // Allow an external script to override the redirecting URL. $overrideurl = \HookHandler::DispatchHook('/user/postlogin/getredirecturl'); if ($overrideurl) { $url = $overrideurl; } \Core\redirect($url); } else { \Core\go_back(); } }
/** * Validate the verification email, part 2 of confirmation. * * @param string $nonce * @param string $signature * * @return bool|string */ public static function ValidateVerificationResponse($nonce, $signature) { /** @var \NonceModel $nonce */ $nonce = \NonceModel::Construct($nonce); if(!$nonce->isValid()){ \SystemLogModel::LogSecurityEvent('/user/gpg/verified', 'FAILED to verify key (Invalid NONCE)', null); return 'Invalid nonce provided!'; } // Now is where the real fun begins. $nonce->decryptData(); $data = $nonce->get('data'); /** @var \UserModel $user */ $user = \UserModel::Construct($data['user']); $gpg = new \Core\GPG\GPG(); $key = $data['key']; $pubKey = $gpg->getKey($key); try{ $sig = $gpg->verifyDataSignature($signature, $data['sentence']); } catch(\Exception $e){ \SystemLogModel::LogSecurityEvent('/user/gpg/verified', 'FAILED to verify key ' . $key, null, $user->get('id')); return 'Invalid signature'; } $fpr = str_replace(' ', '', $sig->fingerprint); // Trim spaces. if($key != $fpr && $key != $sig->keyID){ // They must match! \SystemLogModel::LogSecurityEvent('/user/gpg/verified', 'FAILED to verify key ' . $key, null, $user->get('id')); return 'Invalid signature'; } // Otherwise? $user->enableAuthDriver('gpg'); $user->set('gpgauth_pubkey', $fpr); // Was there a photo attached to this public key? if(sizeof($pubKey->getPhotos()) > 0){ $p = $pubKey->getPhotos(); // I just want the first. /** @var \Core\Filestore\File $p */ $p = $p[0]; $localFile = \Core\Filestore\Factory::File('public/user/avatar/' . $pubKey->fingerprint . '.' . $p->getExtension()); $p->copyTo($localFile); $user->set('avatar', $localFile->getFilename(false)); } $user->save(); $nonce->markUsed(); \SystemLogModel::LogSecurityEvent('/user/gpg/verified', 'Verified key ' . $fpr, null, $user->get('id')); return true; }
/** * Get the current user model that is logged in. * * To support legacy systems, this will also return the User object if it's available instead. * This support is for < 2.8.x Core installations and will be removed after some amount of time TBD. * * If no user systems are currently available, null is returned. * * @return \UserModel */ function user(){ static $_CurrentUserAccount = null; if(!class_exists('\\UserModel')){ return null; } if($_CurrentUserAccount !== null){ // Cache this for the page load. return $_CurrentUserAccount; } if(isset($_SERVER['HTTP_X_CORE_AUTH_KEY'])){ // Allow an auth key to be used to authentication the requested user instead! $user = \UserModel::Find(['apikey = ' . $_SERVER['HTTP_X_CORE_AUTH_KEY']], 1); if($user){ $_CurrentUserAccount = $user; } } elseif(Session::Get('user') instanceof \UserModel){ // There is a valid user account in the session! // But check if this user is forced to be resynced first. if(isset(Session::$Externals['user_forcesync'])){ // A force sync was requested by something that modified the original UserModel object. // Keep the user logged in, but reload the data from the database. $_CurrentUserAccount = \UserModel::Construct(Session::Get('user')->get('id')); // And cache this updated user model back to the session. Session::Set('user', $_CurrentUserAccount); unset(Session::$Externals['user_forcesync']); } else{ $_CurrentUserAccount = Session::Get('user'); } } if($_CurrentUserAccount === null){ // No valid user found. $_CurrentUserAccount = new \UserModel(); } // If this is in multisite mode, blank out the access string cache too! // This is because siteA may have some groups, while siteB may have another. // We don't want a user going to a site they have full access to, hopping to another and having cached permissions! if(\Core::IsComponentAvailable('multisite') && class_exists('MultiSiteHelper') && \MultiSiteHelper::IsEnabled()){ $_CurrentUserAccount->clearAccessStringCache(); } // Did this user request sudo access for another user? if(Session::Get('user_sudo') !== null){ $sudo = Session::Get('user_sudo'); if($sudo instanceof \UserModel){ // It's a valid user! if($_CurrentUserAccount->checkAccess('p:/user/users/sudo')){ // This user can SUDO! // (only if the other user is < SA or current == SA). if($sudo->checkAccess('g:admin') && !$_CurrentUserAccount->checkAccess('g:admin')){ Session::UnsetKey('user_sudo'); \SystemLogModel::LogSecurityEvent('/user/sudo', 'Authorized but non-SA user requested sudo access to a system admin!', null, $sudo->get('id')); } else{ // Ok, everything is good. // Remap the current user over to this sudo'd account! $_CurrentUserAccount = $sudo; } } else{ // This user can NOT sudo!!! Session::UnsetKey('user_sudo'); \SystemLogModel::LogSecurityEvent('/user/sudo', 'Unauthorized user requested sudo access to another user!', null, $sudo->get('id')); } } else{ Session::UnsetKey('user_sudo'); } } return $_CurrentUserAccount; }
/** * Handle the upload as a series of binary streams. * @return array */ private function _doStream() { // Read INPUT and write it directly to a temporary file based on the requested filename. //var_dump($_SERVER); die(); $name = substr($_SERVER['HTTP_CONTENT_DISPOSITION'], 22, -1); $file = array('name' => $name, 'size' => 0, 'remaining' => 0, 'type' => null, 'url' => '', 'thumbnail_url' => '', 'error' => ''); //$finalsize = $_SERVER['HTTP_X_FILE_SIZE']; if (isset($_SERVER['HTTP_CONTENT_RANGE'])) { $contentrange = explode('/', $_SERVER['HTTP_CONTENT_RANGE']); $finalsize = $contentrange[1]; } else { $finalsize = $_SERVER['CONTENT_LENGTH']; } $incomingsize = $_SERVER['CONTENT_LENGTH']; // Just used to prevent multiple pageloads from appending to the same file should something happen. $datestamp = isset($_SERVER['HTTP_X_UPLOAD_TIME']) ? $_SERVER['HTTP_X_UPLOAD_TIME'] : 0; $tmpfile = TMP_DIR . md5($this->_formelement->get('key') . $file['name'] . $datestamp) . '.part.dat'; // Record the filesize before and after so I can confirm I got all the data the client sent. if (file_exists($tmpfile)) { $file['size'] = filesize($tmpfile); } else { $file['size'] = 0; } if (!is_writable(TMP_DIR)) { $file['error'] = 'Unable to write to temporary directory.'; return array('files' => [$file]); } file_put_contents($tmpfile, file_get_contents('php://input'), FILE_APPEND); // And update the size. clearstatcache(); $newsize = filesize($tmpfile); if ($newsize - $file['size'] != $incomingsize) { $file['error'] = 'Did not receive all data, unable to process upload'; unlink($tmpfile); return array('files' => [$file]); } $file['size'] = $newsize; $file['remaining'] = $finalsize - $file['size']; // Is the file upload complete? if ($file['size'] == $finalsize) { // Source $f = \Core\Filestore\Factory::File($tmpfile); // Destination // Make sure the filename is sanitized. $newbasename = \Core\str_to_url(urldecode($file['name']), true); $nf = \Core\Filestore\Factory::File($this->_formelement->get('basedir') . $newbasename); if (!$nf->isWritable()) { $file['error'] = 'File destination is not writable.'; \SystemLogModel::LogErrorEvent('warning', dirname($nf->getFilename()) . ' does not appear to be writable!'); return array('files' => [$file]); } $file['type'] = $f->getMimetype(); // do NOT copy the contents over until the accept check has been ran! // Now that I have a file object, (in the temp filesystem still), I should validate the filetype // to see if the developer wanted a strict "accept" type to be requested. // If present, I'll have something to run through and see if the file matches. // I need the destination now because I need to full filename if an extension is requested in the accept. if ($this->_formelement->get('accept')) { $acceptcheck = \Core\check_file_mimetype($this->_formelement->get('accept'), $f->getMimetype(), $nf->getExtension()); // Now that all the mimetypes have run through, I can see if one matched. if ($acceptcheck != '') { $file['error'] = $acceptcheck; unlink($tmpfile); return array('files' => [$file]); } } // Now all the checks should be completed and I can safely copy the file away from the temporary filesystem. $f->copyTo($nf); unlink($tmpfile); // And now all the file's attributes will be visible. $file['name'] = $nf->getBaseFilename(); $file['url'] = $nf->getURL(); $file['thumbnail_url'] = $nf->getPreviewURL('50x50'); } return array('files' => [$file]); }
/** * Form Handler for logging in. * * @static * * @param \Form $form * * @return bool|null|string */ public static function LoginHandler(\Form $form){ /** @var \FormElement $e */ $e = $form->getElement('email'); /** @var \FormElement $p */ $p = $form->getElement('pass'); /** @var \UserModel $u */ $u = \UserModel::Find(array('email' => $e->get('value')), 1); if(!$u){ // Log this as a login attempt! $logmsg = 'Failed Login. Email not registered' . "\n" . 'Email: ' . $e->get('value') . "\n"; \SystemLogModel::LogSecurityEvent('/user/login', $logmsg); $e->setError('t:MESSAGE_ERROR_USER_LOGIN_EMAIL_NOT_FOUND'); return false; } if($u->get('active') == 0){ // The model provides a quick cut-off for active/inactive users. // This is the control managed with in the admin. $logmsg = 'Failed Login. User tried to login before account activation' . "\n" . 'User: '******'email') . "\n"; \SystemLogModel::LogSecurityEvent('/user/login', $logmsg, null, $u->get('id')); $e->setError('t:MESSAGE_ERROR_USER_LOGIN_ACCOUNT_NOT_ACTIVE'); return false; } elseif($u->get('active') == -1){ // The model provides a quick cut-off for active/inactive users. // This is the control managed with in the admin. $logmsg = 'Failed Login. User tried to login after account deactivation.' . "\n" . 'User: '******'email') . "\n"; \SystemLogModel::LogSecurityEvent('/user/login', $logmsg, null, $u->get('id')); $e->setError('t:MESSAGE_ERROR_USER_LOGIN_ACCOUNT_DEACTIVATED'); return false; } try{ /** @var \Core\User\AuthDrivers\datastore $auth */ $auth = $u->getAuthDriver('datastore'); } catch(Exception $e){ $e->setError('t:MESSAGE_ERROR_USER_LOGIN_PASSWORD_AUTH_DISABLED'); return false; } // This is a special case if the password isn't set yet. // It can happen with imported users or if a password is invalidated. if($u->get('password') == ''){ // Use the Nonce system to generate a one-time key with this user's data. $nonce = \NonceModel::Generate( '20 minutes', ['type' => 'password-reset', 'user' => $u->get('id')] ); $link = '/datastoreauth/forgotpassword?e=' . urlencode($u->get('email')) . '&n=' . $nonce; $email = new \Email(); $email->setSubject('Initial Password Request'); $email->to($u->get('email')); $email->assign('link', \Core\resolve_link($link)); $email->assign('ip', REMOTE_IP); $email->templatename = 'emails/user/initialpassword.tpl'; try{ $email->send(); \SystemLogModel::LogSecurityEvent('/user/initialpassword/send', 'Initial password request sent successfully', null, $u->get('id')); \Core\set_message('t:MESSAGE_INFO_USER_LOGIN_MUST_SET_NEW_PASSWORD_INSTRUCTIONS_HAVE_BEEN_EMAILED'); return true; } catch(\Exception $e){ \Core\ErrorManagement\exception_handler($e); \Core\set_message('t:MESSAGE_ERROR_USER_LOGIN_MUST_SET_NEW_PASSWORD_UNABLE_TO_SEND_EMAIL'); return false; } } if(!$auth->checkPassword($p->get('value'))){ // Log this as a login attempt! $logmsg = 'Failed Login. Invalid password' . "\n" . 'Email: ' . $e->get('value') . "\n"; \SystemLogModel::LogSecurityEvent('/user/login/failed_password', $logmsg, null, $u->get('id')); // Also, I want to look up and see how many login attempts there have been in the past couple minutes. // If there are too many, I need to start slowing the attempts. $time = new \CoreDateTime(); $time->modify('-5 minutes'); $securityfactory = new \ModelFactory('SystemLogModel'); $securityfactory->where('code = /user/login/failed_password'); $securityfactory->where('datetime > ' . $time->getFormatted(\Time::FORMAT_EPOCH, \Time::TIMEZONE_GMT)); $securityfactory->where('ip_addr = ' . REMOTE_IP); $attempts = $securityfactory->count(); if($attempts > 4){ // Start slowing down the response. This should help deter brute force attempts. // (x+((x-7)/4)^3)-4 sleep( ($attempts+(($attempts-7)/4)^3)-4 ); // This makes a nice little curve with the following delays: // 5th attempt: 0.85 // 6th attempt: 2.05 // 7th attempt: 3.02 // 8th attempt: 4.05 // 9th attempt: 5.15 // 10th attempt: 6.52 // 11th attempt: 8.10 // 12th attempt: 10.05 } $e->setError('t:MESSAGE_ERROR_USER_LOGIN_INCORRECT_PASSWORD'); $p->set('value', ''); return false; } if($form->getElementValue('redirect')){ // The page was set via client-side javascript on the login page. // This is the most reliable option. $url = $form->getElementValue('redirect'); } elseif(REL_REQUEST_PATH == '/user/login'){ // If the user came from the registration page, get the page before that. $url = $form->referrer; } else{ // else the registration link is now on the same page as the 403 handler. $url = REL_REQUEST_PATH; } // Well, record this too! \SystemLogModel::LogSecurityEvent('/user/login', 'Login successful (via password)', null, $u->get('id')); // yay... $u->set('last_login', \CoreDateTime::Now('U', \Time::TIMEZONE_GMT)); $u->save(); \Core\Session::SetUser($u); // Allow an external script to override the redirecting URL. $overrideurl = \HookHandler::DispatchHook('/user/postlogin/getredirecturl'); if($overrideurl){ $url = $overrideurl; } return $url; }
/** * Send the message * * @throws phpmailerException * @return bool */ public function send() { $m = $this->getMailer(); if(!\ConfigHandler::Get('/core/email/enable_sending')){ // Allow a config option to disable sending entirely. SystemLogModel::LogInfoEvent('/email/disabled', 'Email sending is disabled, not sending email ' . $m->Subject . '!'); return false; } if(\ConfigHandler::Get('/core/email/sandbox_to')){ $to = $m->getToAddresses(); $cc = $m->getCCAddresses(); $bcc = $m->getBCCAddresses(); $all = []; if(sizeof($to)){ foreach($to as $e){ $all[] = ['type' => 'To', 'email' => $e[0], 'name' => $e[1]]; } } if(sizeof($cc)){ foreach($cc as $e){ $all[] = ['type' => 'CC', 'email' => $e[0], 'name' => $e[1]]; } } if(sizeof($bcc)){ foreach($bcc as $e){ $all[] = ['type' => 'BCC', 'email' => $e[0], 'name' => $e[1]]; } } foreach($all as $e){ $m->AddCustomHeader('X-Original-' . $e['type'], ($e['name'] ? $e['name'] . ' <' . $e['email'] . '>' : $e['email'])); } // Allow a config option to override the "To" address, useful for testing with production data. $m->ClearAllRecipients(); $m->AddAddress(\ConfigHandler::Get('/core/email/sandbox_to')); } // Render out the body. Will be either HTML or text... $body = $this->renderBody(); // Wrap this body with the main email template if it's set. if($this->templatename && $this->_view){ // This version includes HTML tags and all that. $m->Body = $body; $m->IsHTML(true); // Use markdown for conversion. // It produces better results that phpMailer's built-in system! $converter = new \HTMLToMD\Converter(); // Manually strip out the head content. // This was throwing the converters for a loop and injecting weird characters! $body = preg_replace('#<head[^>]*?>.*</head>#ms', '', $body); $m->AltBody = $converter->convert($body); } elseif (strpos($body, '<html>') === false) { // Ensuring that the body is wrapped with <html> tags helps with spam checks with spamassassin. $m->MsgHTML('<html><body>' . $body . '</body></html>'); } else{ $m->MsgHTML($body); } if($this->_encryption){ // Encrypt this message, (both HTML and Alt), and all attachments. // I need to request the full EML from phpMailer so I can encrypt everything. // Then, the body will be recreated after Send is called. $m->PreSend(); $header = $m->CreateHeader(); $body = $m->CreateBody(); $gpg = new \Core\GPG\GPG(); if($this->_encryption === true){ // This is allowed for mutliple recipients! // This requires a little more overhead, as I need to lookup each recipient's user account // to retrieve their GPG key. $recipients = $m->getToAddresses(); foreach($recipients as $dat){ $email = $dat[0]; $user = UserModel::Find(['email = ' . $email], 1); if(!$user){ SystemLogModel::LogErrorEvent('/core/email/failed', 'Unable to locate GPG key for ' . $email . ', cannot send encrypted email to recipient!'); } else{ $key = $user->get('gpgauth_pubkey'); if(!$key){ SystemLogModel::LogErrorEvent('/core/email/failed', 'No GPG key uploaded for ' . $email . ', cannot send encrypted email to recipient!'); } else{ $enc = $gpg->encryptData($header . $body, $key); // Create a clone of the email object to send this data. /** @var PHPMailer $clone */ $clone = clone $m; $clone->ClearAddresses(); $clone->AddAddress($email); $clone->Body = $enc; $clone->AltBody = ''; $clone->Send(); } } } return true; } else{ // Single recipient! $enc = $gpg->encryptData($header . $body, $this->_encryption); $m->Body = $enc; $m->AltBody = ''; return $m->Send(); } } return $m->Send(); }
/** * This will check and see how many 404 requests there have been recently. * * @return bool */ public static function Check404Pages() { // How long back do I want to check the logs? $time = new DateTime(); $time->modify('-30 seconds'); $ds = Dataset::Init()->table('user_activity')->where(['status = 404', 'ip_addr = ' . REMOTE_IP, 'datetime > ' . $time->format('U')])->count()->execute(); if ($ds->num_rows > 30) { // CHILL THAR F****R! $time->modify('+6 minutes'); $blacklist = new \IpBlacklistModel(); $blacklist->setFromArray(['ip_addr' => REMOTE_IP . '/24', 'expires' => $time->format('U'), 'message' => 'You have requested too many "404" pages recently, please go get some coffee and wait for a short bit. If you are a bot and/or spammer, please bugger off.', 'comment' => '5-minute auto-ban for too many 404 requests in 30 seconds']); $blacklist->save(); \SystemLogModel::LogSecurityEvent('/security/blocked', 'Blocking IP due to too many 404 requests in 30 seconds.'); die($blacklist->get('message')); } }
/** * Page to display full details of a system log, usually opened in an ajax dialog. * * @return int * @throws DMI_Exception */ public function log_details(){ $view = $this->getView(); $request = $this->getPageRequest(); $view->mode = View::MODE_PAGEORAJAX; if(!\Core\user()->checkAccess('p:/core/systemlog/view')){ return View::ERROR_ACCESSDENIED; } $log = SystemLogModel::Construct($request->getParameter(0)); if(!$log->exists()){ return View::ERROR_NOTFOUND; } $view->mastertemplate = 'admin'; $view->addBreadcrumb('t:STRING_SYSTEM_LOG', '/admin/log'); $view->title = 't:STRING_SYSTEM_LOG_DETAILS'; $view->assign('entry', $log); }
/** * Check permissions on the user and system and return either blank or a string containing the error. * * @param string $step * * @return array|null */ private function _checkPermissions($step) { $error = null; if (!\ConfigHandler::Get('/package_repository/base_directory')) { // Check if the config is even set, can't proceed if it's not. trigger_error('The package repository does not appear to be setup yet. Please browse to Configuration and the appropriate options.'); return ['status' => View::ERROR_SERVERERROR, 'message' => 'The package repository is not setup on this server.']; } $dir = Factory::Directory(\ConfigHandler::Get('/package_repository/base_directory')); if (!$dir->exists()) { trigger_error($dir->getPath() . ' does not appear to exist! Unable to browse repo.xml without it.'); return ['status' => View::ERROR_SERVERERROR, 'message' => $dir->getPath() . ' does not seem to exist!']; } elseif (!$dir->isReadable()) { trigger_error($dir->getPath() . ' does not appear to be readable! Unable to browse repo.xml without it.'); return ['status' => View::ERROR_SERVERERROR, 'message' => $dir->getPath() . ' does not seem to be readable!']; } if (ConfigHandler::Get('/package_repository/is_private')) { // Lookup this license key, (or request one if not present). $valid = false; $autherror = 'Access to ' . SITENAME . ' (Package Repository) requires a license key and password.'; if (isset($_SERVER['PHP_AUTH_PW']) && isset($_SERVER['PHP_AUTH_USER'])) { $user = $_SERVER['PHP_AUTH_USER']; $pw = $_SERVER['PHP_AUTH_PW']; } else { $user = $pw = null; } if ($user && $pw) { /** @var PackageRepositoryLicenseModel $license */ $license = PackageRepositoryLicenseModel::Construct($user); $licvalid = $license->isValid($pw); if ($licvalid == 0) { // Lock this license to the remote IP, if requested by the admin. if (ConfigHandler::Get('/package_repository/auto_ip_restrict') && !$license->get('ip_restriction')) { $license->set('ip_restriction', REMOTE_IP); $license->save(); } SystemLogModel::LogInfoEvent('/packagerepository/' . $step, '[' . $user . '] accessed repository successfully'); return null; } else { if (($licvalid & PackageRepositoryLicenseModel::VALID_PASSWORD) == PackageRepositoryLicenseModel::VALID_PASSWORD) { $autherror = '[' . $user . '] Invalid license password'; $status = View::ERROR_ACCESSDENIED; SystemLogModel::LogSecurityEvent('/packagerepository/password_failure', $autherror); } if (($licvalid & PackageRepositoryLicenseModel::VALID_ACCESS) == PackageRepositoryLicenseModel::VALID_ACCESS) { $autherror = '[' . $user . '] IP address not authorized'; $status = View::ERROR_ACCESSDENIED; SystemLogModel::LogSecurityEvent('/packagerepository/ip_restriction', $autherror); } if (($licvalid & PackageRepositoryLicenseModel::VALID_EXPIRED) == PackageRepositoryLicenseModel::VALID_EXPIRED) { $autherror = '[' . $user . '] License provided has expired, please request a new one.'; $status = View::ERROR_GONE; SystemLogModel::LogSecurityEvent('/packagerepository/expired_license', $autherror); } if (($licvalid & PackageRepositoryLicenseModel::VALID_INVALID) == PackageRepositoryLicenseModel::VALID_INVALID) { $autherror = '[' . $user . '] License does not exist'; $status = View::ERROR_EXPECTATIONFAILED; SystemLogModel::LogSecurityEvent('/packagerepository/invalid_license', $autherror); } return ['status' => $status, 'message' => $autherror]; } } if (!$valid) { header('WWW-Authenticate: Basic realm="' . SITENAME . ' (Package Repository)"'); header('HTTP/1.0 401 Unauthorized'); echo $autherror; exit; } } else { SystemLogModel::LogInfoEvent('/packagerepository/' . $step, '[anonymous connection] accessed repository successfully'); return null; } }
/** * Load all the components in the system, replacement for the Core. * @throws CoreException */ private function _loadComponents() { // cannot reload components. if ($this->_components) return null; $this->_components = array(); $this->_libraries = array(); $tempcomponents = false; Core\Utilities\Logger\write_debug('Starting loading of component metadata'); // If the site is in DEVELOPMENT mode, component caching would probably be a bad idea; ie: the developer probably wants // those component files loaded everytime. if(DEVELOPMENT_MODE){ $enablecache = false; } else{ $enablecache = true; } // Is there a cache of elements available? This is a primary system cache that greatly increases performance, // since it will no longer have to run through each component.xml file to register each one. if($enablecache){ Core\Utilities\Logger\write_debug('Checking core-components cache'); // Try to load up the cached components and check them first. $tempcomponents = \Core\Cache::Get('core-components', (3600 * 24)); if($tempcomponents !== false){ // Cached components only need to be loaded. foreach ($tempcomponents as $c) { try { $c->load(); } catch (Exception $e) { // Don't completely bail out here, just invalidate the cache and continue on. \Core\Cache::Delete('core-components'); $tempcomponents = false; } } } } if(!$enablecache || $tempcomponents == false){ \Core\Utilities\Profiler\Profiler::GetDefaultProfiler()->record('Scanning for component.xml files manually'); Core\Utilities\Logger\write_debug('Scanning for component.xml files manually'); // Core is first, (obviously) $tempcomponents['core'] = ComponentFactory::Load(ROOT_PDIR . 'core/component.xml'); Core\Utilities\Logger\write_debug('Core component loaded'); // First, build my cache of components, regardless if the component is installed or not. $dh = opendir(ROOT_PDIR . 'components'); if (!$dh) throw new CoreException('Unable to open directory [' . ROOT_PDIR . 'components/] for reading.'); // This will read through every directory in 'components', which is // where all components in the system are installed to. while (($file = readdir($dh)) !== false) { // skip hidden directories. if ($file{0} == '.') continue; // skip non-directories if (!is_dir(ROOT_PDIR . 'components/' . $file)) continue; // Skip directories that do not have a readable component.xml file. if (!is_readable(ROOT_PDIR . 'components/' . $file . '/component.xml')) continue; //Core\Utilities\Logger\write_debug(' * Loading component ' . $file); $c = ComponentFactory::Load(ROOT_PDIR . 'components/' . $file . '/component.xml'); Core\Utilities\Logger\write_debug('Opened component ' . $file); // All further operations are case insensitive. // The original call to Component needs to be case sensitive because it sets the filename to pull. $file = strtolower($file); // If the component was flagged as invalid.. just skip to the next one. if (!$c->isValid()) { if (DEVELOPMENT_MODE) { \Core\set_message('Component ' . $c->getName() . ' appears to be invalid.'); } continue; } $tempcomponents[$file] = $c; unset($c); } closedir($dh); \Core\Utilities\Profiler\Profiler::GetDefaultProfiler()->record('Component XML files scanned'); // Now I probably could actually load the components! foreach ($tempcomponents as $c) { /** @var Component_2_1 $c */ try { // Load some of the data in the class so that it's available in the cached version. // This is because the component 2.1 has built-in caching for many of the XML requests. // by calling them once, that lookup data is cached in that component, which in turn gets // copied to the cache version here! $c->load(); $c->getClassList(); $c->getViewSearchDir(); $c->getSmartyPluginDirectory(); $c->getWidgetList(); } catch (Exception $e) { var_dump($e); die(); } } // Cache this list! if($enablecache){ Core\Utilities\Logger\write_debug(' * Caching core-components for next pass'); \Core\Cache::Set('core-components', $tempcomponents, (3600 * 24)); } } $list = $tempcomponents; \Core\Utilities\Profiler\Profiler::GetDefaultProfiler()->record('Component metadata loaded, starting registration'); Core\Utilities\Logger\write_debug(' * Component metadata loaded, starting registration'); // The core component at a minimum needs to be loaded and registered. // $this->_registerComponent($list['core']); // $this->_components['core']->loadFiles(); // unset($list['core']); // Now that I have a list of components available, copy them into a list of // components that are installed. do { $size = sizeof($list); foreach ($list as $n => $c) { /** @var $c Component_2_1 */ // Disabled components don't get recognized. if($c->isInstalled() && !$c->isEnabled()){ // But they do get sent to the disabled list! $this->_componentsDisabled[$n] = $c; unset($list[$n]); continue; } // Clear out the temporary class list $this->_tmpclasses = []; // If it's loaded, register it and remove it from the list! if ($c->isInstalled() && $c->isLoadable() && $c->loadFiles()) { try{ // Allow for on-the-fly package upgrading regardless of DEV mode or not. if ($c->needsUpdated()) { // Load this component's classes in case an upgrade operation requires one. // This allows a component to be loaded partially without completely being loaded. $this->_tmpclasses = $c->getClassList(); // Lock the site first! // This is because some upgrade procedures take a long time to upgrade. file_put_contents(TMP_DIR . 'lock.message', 'Core Plus is being upgraded, please try again in a minute. '); $c->upgrade(); unlink(TMP_DIR . 'lock.message'); } } catch(Exception $e){ SystemLogModel::LogErrorEvent('/core/component/failedupgrade', 'Ignoring component [' . $n . '] due to an error during upgrading!', $e->getMessage()); unlink(TMP_DIR . 'lock.message'); //$c->disable(); $this->_componentsDisabled[$n] = $c; unset($list[$n]); continue; } try{ $this->_components[$n] = $c; $this->_registerComponent($c); $c->loadSupplementalModels(); } catch(Exception $e){ SystemLogModel::LogErrorEvent('/core/component/failedregister', 'Ignoring component [' . $n . '] due to an error during registration!', $e->getMessage()); //$c->disable(); $this->_componentsDisabled[$n] = $c; unset($list[$n]); continue; } unset($list[$n]); continue; } // Allow for on-the-fly package upgrading regardless of DEV mode or not. // Guess this is needed for the loadFiles part... if ($c->isInstalled() && $c->needsUpdated() && $c->isLoadable()) { // Lock the site first! // This is because some upgrade procedures take a long time to upgrade. file_put_contents(TMP_DIR . 'lock.message', 'Core Plus is being upgraded, please try again in a minute. '); $c->upgrade(); $c->loadFiles(); $this->_components[$n] = $c; $this->_registerComponent($c); unlink(TMP_DIR . 'lock.message'); unset($list[$n]); continue; } // Allow packages to be auto-installed if in DEV mode. // If DEV mode is not enabled, just install the new component, do not enable it. if (!$c->isInstalled() && $c->isLoadable()) { // Load this component's classes in case an install operation requires one. // This allows a component to be loaded partially without completely being loaded. $this->_tmpclasses = $c->getClassList(); // w00t $c->install(); // BLAH, until I fix the disabled-packages-not-viewable bug... $c->enable(); $c->loadFiles(); $this->_components[$n] = $c; $this->_registerComponent($c); /* if(!DEVELOPMENT_MODE){ $c->disable(); } else{ $c->enable(); $c->loadFiles(); $this->_components[$n] = $c; $this->_registerComponent($c); } */ unset($list[$n]); continue; } } } while ($size > 0 && ($size != sizeof($list))); // If dev mode is enabled, display a list of components installed but not loadable. foreach ($list as $n => $c) { //$this->_components[$n] = $c; $this->_componentsDisabled[$n] = $c; // Ignore anything with the execmode different, those should be minor notices for debugging if anything. if ($c->error & Component_2_1::ERROR_WRONGEXECMODE) continue; if (DEVELOPMENT_MODE) { SystemLogModel::LogErrorEvent('/core/component/missingrequirement', 'Could not load installed component ' . $n . ' due to requirement failed.', $c->getErrors()); } } // Don't forget to load the themes too! if(class_exists('ThemeHandler')){ foreach(ThemeHandler::GetAllThemes() as $theme){ /** @var $theme Theme */ $theme->load(); } } // Lastly, make sure that the template path cache is updated! if(class_exists('\\Core\\Templates\\Template')){ \Core\Templates\Template::RequeryPaths(); } }
/** * Form handler for the rest of the user system, (auth handler has already been executed). * * @param \Form $form * * @return bool|string */ public static function RegisterHandler(\Form $form){ /////// VALIDATION \\\\\\\\ // All other validation can be done from the model. // All set calls will throw a ModelValidationException if the validation fails. try{ /** @var \UserModel $user */ $user = $form->getElement('user')->get('value'); // setFromForm will handle all attributes and custom values. $user->setFromForm($form); } catch(\ModelValidationException $e){ // Make a note of this! \SystemLogModel::LogSecurityEvent('/user/register', $e->getMessage()); \Core\set_message($e->getMessage(), 'error'); return false; } catch(\Exception $e){ // Make a note of this! \SystemLogModel::LogSecurityEvent('/user/register', $e->getMessage()); if(DEVELOPMENT_MODE){ \Core\set_message($e->getMessage(), 'error'); } else{ \Core\set_message('t:MESSAGE_ERROR_FORM_SUBMISSION_UNHANDLED_EXCEPTION'); } return false; } if( \Core\user()->checkAccess('g:admin') ) { $active = ($form->getElementValue('active') === "on" ? 1 : 0); $user->set('active', $active); } else { $user->setDefaultActiveStatuses(); } $user->setDefaultGroups(); $user->setDefaultMetaFields(); $user->generateNewApiKey(); $user->save(); // User created... make a log of this! \SystemLogModel::LogSecurityEvent('/user/register', 'User registration successful', null, $user->get('id')); // Send a thank you for registering email to the user. try{ $user->sendWelcomeEmail(); } catch(\Exception $e){ \Core\ErrorManagement\exception_handler($e); \Core\set_message('t:MESSAGE_ERROR_CANNOT_SEND_WELCOME_EMAIL'); } // "login" this user if not already logged in. if(!\Core\user()->exists()){ if($user->get('active')){ $user->set('last_login', \CoreDateTime::Now('U', \Time::TIMEZONE_GMT)); $user->save(); Session::SetUser($user); } \Core\set_message('t:MESSAGE_SUCCESS_CREATED_USER_ACCOUNT'); if(($overrideurl = \HookHandler::DispatchHook('/user/postlogin/getredirecturl'))){ // Allow an external script to override the redirecting URL. $url = $overrideurl; } elseif($form->getElementValue('redirect')){ // The preferred default redirect method. // This is set from /user/register2, which is in turn passed in, (hopefully), by the original callee registration page. $url = $form->getElementValue('redirect'); } elseif(strpos(REL_REQUEST_PATH, '/user/register') === 0){ // If the user came from the registration page, get the page before that. $url = '/'; } else{ // else the registration link is now on the same page as the 403 handler. $url = REL_REQUEST_PATH; } return $url; } // It was created administratively; redirect there instead. else{ \Core\set_message('t:MESSAGE_SUCCESS_CREATED_USER_ACCOUNT'); return '/user/admin'; } }
public function preview() { $view = $this->getView(); $request = $this->getPageRequest(); // This is designed to only return data! $view->mode = View::MODE_NOOUTPUT; // And it's going to be an image of some sorts. // This will get overridden by the actual image displaying method. $view->contenttype = 'image/png'; // And it shouldn't be recorded in navigation. $view->record = false; // The filename should be something like files/public/blah... or files/assets/foo... // This will get fed into the core system and checked internally. $filename = $request->getParameter('f'); // Was there a resize-to dimension requested? $d = $request->getParameter('d'); // The inbound filename must be in the format of base64:[b64data]. if (strpos($filename, 'base64:') !== 0) { if(DEVELOPMENT_MODE){ error_log('Invalid request made for /file/preview! Expecting: [base64:*b64data*], Received: [' . $filename . ']'); } $file = \Core\Filestore\Factory::File('assets/images/mimetypes/notfound.png'); $file->displayPreview($d); return; } // This preview ONLY supports assets and public files! // This is a security precaution. $base = base64_decode(substr($filename, 7)); if(!(strpos($base, 'public/') === 0 || strpos($base, 'assets/') === 0 || strpos($base, 'asset/') === 0)){ SystemLogModel::LogSecurityEvent('/file/preview', 'Invalid file requested: ' . $base); if(DEVELOPMENT_MODE){ error_log('Invalid request made for /file/preview! Expecting: [public/* or asset[s]/*], Received: [' . $base . ']'); } $file = \Core\Filestore\Factory::File('assets/images/mimetypes/notfound.png'); $file->displayPreview($d); return; } $file = \Core\Filestore\Factory::File($filename); if(!$file->exists()){ if(DEVELOPMENT_MODE){ error_log('File not found for /file/preview! Looking For: [' . $file->getFilename('') . ' (' . $filename . ') ]'); } $file = \Core\Filestore\Factory::File('assets/images/mimetypes/notfound.png'); $file->displayPreview($d); return; } // And this will render it to the browser. $file->displayPreview($d); }
public function finalize(){ $subject = 'Import completed in ' . $this->_profiler->getTimeFormatted(); $bits = []; if($this->success > 0){ $bits[] = 'Success: ' . $this->success; } if($this->skipped > 0){ $bits[] = 'Skipped: ' . $this->skipped; } if($this->error > 0){ $bits[] = 'Errors: ' . $this->error; } if($this->duplicate > 0){ $bits[] = 'Duplicates: ' . $this->duplicate; } $subject .= ' ' . implode(', ', $bits); \SystemLogModel::LogInfoEvent($this->_title, $subject, $this->_profiler->getEventTimesFormatted()); }
/** * Shortcut function to record a new security log entry. * * Return the log entry that's created. * * @param string $action * @param null|string $status * @param null|int $affecteduser * @param null|string $details * * @return null */ public static function Log($action, $status = null, $affecteduser = null, $details = null) { // The security log has been removed! trigger_error('SecurityLogModel::Log has been deprecated, please use SystemLogModel::LogSecurityEvent() instead.', E_USER_DEPRECATED); SystemLogModel::LogSecurityEvent($action, $details, null, $affecteduser); }
public function wpadmin() { $view = $this->getView(); $request = $this->getPageRequest(); if ($request->isPost()) { // Did they actually try to submit this form?...... silly bot ;) SystemLogModel::LogSecurityEvent('/wp-admin Honeypot POST', 'POST submission to /wp-admin detected!', print_r($_POST, true)); $expireback = new CoreDateTime(); $expireback->modify('+2 days'); $block = IpBlacklistModel::Find(['ip_addr = ' . REMOTE_IP . '/32'], 1); if (!$block) { $block = new IpBlacklistModel(); $block->set('ip_addr', REMOTE_IP . '/32'); } $block->setFromArray(['expires' => $expireback->getFormatted('U', Time::TIMEZONE_GMT), 'message' => 'You tried to submit a wp-admin page.... this is not a WP site!', 'comment' => 'Bot or user submitted to wp-admin']); $block->save(); } else { // Just record the hit. SystemLogModel::LogSecurityEvent('/wp-admin Honeypot GET', 'GET request to /wp-admin detected!'); } $view->templatename = 'pages/wphoneypot/wpadmin.phtml'; $view->mastertemplate = false; }
/** * Handle an error and report it to the Core system log. * * @param $errno * @param $errstr * @param $errfile * @param $errline * @param null $errcontext */ function error_handler($errno, $errstr, $errfile, $errline, $errcontext = null){ $type = null; $fatal = false; $code = null; $class = ''; // The exception to this is when error_reporting is explictly set to 0. // This happens when a function is called with the "@" error suppressor. // In this event, I still want to log the error, but simply do not display it on the screen. // Damn f*****g "@" operator..... $suppressed = (error_reporting() === 0); switch($errno){ case E_ERROR: case E_USER_ERROR: $fatal = true; $type = 'error'; $class = 'error'; $code = 'PHP Error'; break; case E_WARNING: case E_USER_WARNING: $type = 'error'; $class = 'warning'; $code = 'PHP Warning'; break; case E_NOTICE: case E_USER_NOTICE: $type = 'info'; $class = 'info'; $code = 'PHP Notice'; break; case E_DEPRECATED: case E_USER_DEPRECATED: $type = 'info'; $class = 'deprecated'; $code = 'PHP Deprecated Notice'; break; case E_STRICT: $type = 'info'; $class = 'warning'; $code = 'PHP Strict Warning'; $suppressed = true; break; default: $type = 'info'; $class = 'unknown'; $code = 'Unknown PHP Error [' . $errno . ']'; break; } if($suppressed){ // Ignore suppressed errors when on production. // This is required because PHP < 7.0 has some functions that can only be called with the '@' operator. // Such as LDAP binding or many things in Smarty. if(!DEVELOPMENT_MODE){ return; } $code .= ' @SUPPRESSED'; } // All errors/warnings/notices get logged! if($errfile && strpos($errfile, ROOT_PDIR) === 0){ $details = '[src: ' . '/' . substr($errfile, strlen(ROOT_PDIR)) . ':' . $errline . '] '; } elseif($errfile){ $details = '[src: ' . $errfile . ':' . $errline . '] '; } else{ $details = ''; } try{ if(!\Core::GetComponent()){ // SQUAK! Core isn't even loaded yet! return; } // Allow external systems to hook into this event. \HookHandler::DispatchHook('/core/error_handler', $code, $errstr); $log = \SystemLogModel::Factory(); $log->setFromArray([ 'type' => $type, 'code' => $code, 'message' => $details . $errstr ]); $log->save(); } catch(\Exception $e){ // meh, try a traditional log. try{ if(class_exists('Core\\Utilities\\Logger\\LogFile')){ $log = new LogFile($type); $log->write($details . $errstr, $code); } else{ error_log($details . $errstr); } } catch(\Exception $e){ // Really meh now! } } // Display all errors when in development mode. if(DEVELOPMENT_MODE && !$suppressed){ // The correct way to handle output is via EXEC_MODE. // HOWEVER, since the unit tests emulate a WEB mode so that the scripts behave as they would in the web browser, // this is not a reliable test here. if(isset($_SERVER['TERM']) || isset($_SERVER['SHELL'])){ print_error_as_text($class, $code, $errstr); } elseif(EXEC_MODE == 'WEB'){ print_error_as_html($class, $code, $errstr); } else{ print_error_as_text($class, $code, $errstr); } } // If it's a fatal error and it's not in development mode, simply display a friendly error page instead. if($fatal){ if(EXEC_MODE == 'WEB'){ require(ROOT_PDIR . 'core/templates/halt_pages/fatal_error.inc.html'); } exit(); } }