예제 #1
0
 /**
  * Method to cleanup expired IP addresses from the database.
  *
  * @return bool
  */
 public static function CleanupHook()
 {
     $factory = new \ModelFactory('IpBlacklistModel');
     $factory->where('expires > 0');
     // If they're set not to be deleted, don't purge them...
     $factory->where('expires <= ' . \CoreDateTime::Now('U', \Time::TIMEZONE_GMT));
     // DELETE!
     $count = $factory->count();
     if (!$count) {
         echo 'No records purged.';
         return true;
     }
     foreach ($factory->get() as $record) {
         /** @var $record \IpBlacklistModel */
         $record->delete();
     }
     echo "Purged " . $count . ' record' . ($count > 1 ? 's' : '') . ' successfully.';
     return true;
 }
예제 #2
0
	/**
	 * 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';
		}
	}
	/**
	 * 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;
	}
예제 #4
0
	/**
	 * Method to archive a given log file.
	 *
	 * Will rename it and optionally compress if configured to do so.
	 *
	 * @throws \Exception
	 */
	public function archive(){
		if($this->isArchived()){
			throw new \Exception($this->getFilename('') . ' is already archived, unable to re-archive!');
		}

		if(!$this->exists()){
			throw new \Exception($this->getFilename('') . ' does not appear to exist!');
		}

		if(!is_writable($this->getDirectoryName())){
			throw new \Exception('Unable to write to directory ' . $this->getDirectoryName() . ', archive unsuccessful!');
		}

		$this->archivedate = \CoreDateTime::Now('YmdHi');

		$newfilename = $this->base . '.log.' . $this->archivedate;
		if(!$this->rename($newfilename)){
			throw new \Exception('Unable to move log to ' . $newfilename);
		}

		if(\ConfigHandler::Get('/core/logs/rotate/compress')){
			// Compression is requested too.
			$arg = escapeshellarg($this->getFilename());
			$output = null; // I don't actually care about this.
			exec('gzip ' . $arg, $output, $ret);

			if($ret != 0){
				throw new \Exception('Unable to compress archived file!');
			}

			// Gzip appends the gz extension to the file, so follow suit!
			$this->_filename .= '.gz';
		}
	}
예제 #5
0
 /**
  * Check if this nonce has been used, (does NOT check validity)
  * 
  * @return true
  */
 public function isUsed()
 {
     // Nonces are not valid if they're not recorded!
     if (!$this->exists()) {
         return false;
     }
     // Expired ones aren't valid either!
     if ($this->get('expires') < CoreDateTime::Now('U', Time::TIMEZONE_GMT)) {
         return false;
     }
     // Is this marked as used?
     return $this->get('status') == 'used';
 }
예제 #6
0
 /**
  * Page to enable Facebook logins for user accounts.
  *
  * @return int|null|string
  */
 public function enable()
 {
     $request = $this->getPageRequest();
     $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 it was a POST, then it should be the first page.
     if ($request->isPost()) {
         $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;
             }
             // If the user is logged in, then the verification logic is slightly different.
             if (\Core\user()->exists()) {
                 // Logged in users, the email must match.
                 if (\Core\user()->get('email') != $user_profile['email']) {
                     \Core\set_message('Your Facebook email is ' . $user_profile['email'] . ', which does not match your account email!  Unable to link accounts.', 'error');
                     \Core\go_back();
                     return null;
                 }
                 $user = \Core\user();
             } else {
                 /** @var \UserModel|null $user */
                 $user = UserModel::Find(['email' => $user_profile['email']], 1);
                 if (!$user) {
                     \Core\set_message('No local account found with the email ' . $user_profile['email'] . ', please <a href="' . \Core\resolve_link('/user/register') . '"create an account</a> instead.', 'error');
                     \Core\go_back();
                     return null;
                 }
             }
             // Send an email with a nonce link that will do the actual activation.
             // This is a security feature so just anyone can't link another user's account.
             $nonce = NonceModel::Generate('20 minutes', null, ['user' => $user, 'access_token' => $_POST['access-token']]);
             $email = new Email();
             $email->to($user->get('email'));
             $email->setSubject('Facebook Activation Request');
             $email->templatename = 'emails/facebook/enable_confirmation.tpl';
             $email->assign('link', \Core\resolve_link('/facebook/enable/' . $nonce));
             if ($email->send()) {
                 \Core\set_message('An email has been sent to your account with a link enclosed.  Please click on that to complete activation within twenty minutes.', 'success');
                 \Core\go_back();
                 return null;
             } else {
                 \Core\set_message('Unable to send a confirmation email, please try again later.', 'error');
                 \Core\go_back();
                 return null;
             }
         }
     }
     // If there is a nonce enclosed, then it should be the second confirmation page.
     // This is the one that actually performs the action.
     if ($request->getParameter(0)) {
         /** @var NonceModel $nonce */
         $nonce = NonceModel::Construct($request->getParameter(0));
         if (!$nonce->isValid()) {
             \Core\set_message('Invalid key requested.', 'error');
             \Core\redirect('/');
             return null;
         }
         $nonce->decryptData();
         $data = $nonce->get('data');
         /** @var UserModel $user */
         $user = $data['user'];
         try {
             $facebook = new Facebook(['appId' => FACEBOOK_APP_ID, 'secret' => FACEBOOK_APP_SECRET]);
             $facebook->setAccessToken($data['access_token']);
             $facebook->getUser();
             $facebook->api('/me');
         } catch (Exception $e) {
             \Core\set_message($e->getMessage(), 'error');
             \Core\redirect('/');
             return null;
         }
         $user->enableAuthDriver('facebook');
         /** @var \Facebook\UserAuth $auth */
         $auth = $user->getAuthDriver('facebook');
         $auth->syncUser($data['access_token']);
         \Core\set_message('Linked Facebook successfully!', 'success');
         // And log the user in!
         if (!\Core\user()->exists()) {
             $user->set('last_login', \CoreDateTime::Now('U', \Time::TIMEZONE_GMT));
             $user->save();
             \Core\Session::SetUser($user);
         }
         \Core\redirect('/');
         return null;
     }
 }
예제 #7
0
 /**
  * Get if this article is published AND not set to a future published date.
  *
  * @return bool
  */
 public function isPublished()
 {
     return $this->get('status') == 'published' && $this->get('published') <= CoreDateTime::Now('U', Time::TIMEZONE_GMT);
 }
예제 #8
0
 /**
  * View controller for a blog article listing page.
  * This will only display articles under this same blog.
  *
  * @param BlogModel $blog
  */
 private function _viewBlog(BlogModel $blog)
 {
     $view = $this->getView();
     $page = $blog->getLink('Page');
     $request = $this->getPageRequest();
     $manager = \Core\user()->checkAccess('p:/blog/manage_all');
     $editor = \Core\user()->checkAccess($blog->get('manage_articles_permission ')) || $manager;
     $viewer = \Core\user()->checkAccess($blog->get('access')) || $editor;
     // Get the latest published article's update date.  This will be used for the blog updated timestamp.
     // (This doesn't have a whole lot of benefit above the ModelFactory, simply illustrating a different way to query data).
     $latest = \Core\Datamodel\Dataset::Init()->select('*')->table('page')->where('parenturl = ' . $blog->get('baseurl'))->where('published_status = published')->order('published DESC')->limit(1)->current();
     $filters = new FilterForm();
     $filters->haspagination = true;
     // Allow different type of requests to come in here.
     switch ($request->ctype) {
         case 'application/atom+xml':
             $view->templatename = 'pages/blog/view-blog.atom.tpl';
             $view->contenttype = $request->ctype;
             $view->mastertemplate = false;
             $filters->setLimit(200);
             break;
         case 'application/rss+xml':
             $view->templatename = 'pages/blog/view-blog.rss.tpl';
             $view->contenttype = $request->ctype;
             $view->mastertemplate = false;
             $filters->setLimit(200);
             break;
         default:
             $view->templatename = 'pages/blog/view-blog.tpl';
             $filters->setLimit(20);
             break;
     }
     $filters->load($this->getPageRequest());
     $factory = new ModelFactory('PageModel');
     if ($request->getParameter('q')) {
         $query = $request->getParameter('q');
         $factory->where(\Core\Search\Helper::GetWhereClause($request->getParameter('q')));
     } else {
         $query = null;
     }
     $factory->where('parenturl = ' . $blog->get('baseurl'));
     $factory->order('published DESC');
     if (!$editor) {
         // Limit these to published articles.
         $factory->where('published_status = published');
         // And where the published date is >= now.
         $factory->where('published <= ' . CoreDateTime::Now('U', Time::TIMEZONE_GMT));
     }
     $filters->applyToFactory($factory);
     $articles = $factory->get();
     $view->mode = View::MODE_PAGEORAJAX;
     $view->assign('blog', $blog);
     $view->assign('articles', $articles);
     $view->assign('page', $page);
     $view->assign('filters', $filters);
     $view->assign('canonical_url', \Core\resolve_link($blog->get('baseurl')));
     $view->assign('last_updated', $latest ? $latest['updated'] : 0);
     $view->assign('servername', SERVERNAME_NOSSL);
     $view->assign('editor', $editor);
     $view->assign('add_article_link', '/content/create?page_template=blog-article.tpl&parenturl=' . $blog->get('baseurl'));
     // Add the extra view types for this page
     $view->addHead('<link rel="alternate" type="application/atom+xml" title="' . $page->get('title') . ' Atom Feed" href="' . \Core\resolve_link($blog->get('baseurl')) . '.atom"/>');
     $view->addHead('<link rel="alternate" type="application/rss+xml" title="' . $page->get('title') . ' RSS Feed" href="' . \Core\resolve_link($blog->get('baseurl')) . '.rss"/>');
     if ($editor) {
         if ($blog->get('type') == 'remote') {
             $view->addControl('Import Feed', '/blog/import/' . $blog->get('id'), 'exchange');
         } else {
             $view->addControl('Add Article', '/content/create?page_template=blog-article.tpl&parenturl=' . $blog->get('baseurl'), 'add');
         }
     }
     if ($manager) {
         $view->addControl('Edit Blog', '/blog/update/' . $blog->get('id'), 'edit');
         $view->addControl('All Articles', '/admin/pages/?filter[parenturl]=' . $blog->get('baseurl'), 'tasks');
     }
     $view->addControl('RSS Feed', \Core\resolve_link($blog->get('baseurl')) . '.rss', 'rss');
     //$view->addControl('Atom Feed', \Core\resolve_link($blog->get('baseurl')) . '.atom', 'rss');
 }
예제 #9
0
	/**
	 * 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);
	}