Topic-icon marked as password changed even if action fails

Active Subscriptions:

None
13 years 9 months ago #4070 by alc0905
Hi! Thank you for this awesome plugin - installation was a breeze, and I already see it working in action.

I have a slight problem though. When users first login, they are brought to the standard edit user form as expected, where they can change their password. However, if the password update fails due to failing validation, the plugin still assumes the password change happened and updates the user's last visit date. The user can then browse the remainder of the website, despite not having really changed his or her password.

I have not modified any plugin code yet.

I have made a slight modification to the components\com_user\controller.php file to force a strong password using regular expressions. Here is the code I changed (approx line 81):
// do a password safety check
		if((preg_match("/^.*(?=.{6,})(?=.*[a-z])(?=.*[\d]).*$/", $post['password'])) || (preg_match("/^.*(?=.{6,})(?=.*[a-z])(?=.*[\d]).*$/", $post['password2']))) { // password must be strong
			if($post['password'] != $post['password2']) {
				$msg	= JText::_('PASSWORDS_DO_NOT_MATCH');
				// something is wrong. we are redirecting back to edit form.
				// TODO: HTTP_REFERER should be replaced with a base64 encoded form field in a later release
				$return = str_replace(array('"', '<', '>', "'"), '', @$_SERVER['HTTP_REFERER']);
				if (empty($return) || !JURI::isInternal($return)) {
					$return = JURI::base();
				}
				$this->setRedirect($return, $msg, 'error');
				return false;
			}
		} else {
			$msg	= JText::_('Password does not meet security requirements.');
			$return = str_replace(array('"', '<', '>', "'"), '', @$_SERVER['HTTP_REFERER']);
			if (empty($return) || !JURI::isInternal($return)) {
				$return = JURI::base();
			}
			$this->setRedirect($return, $msg, 'error');
			return false;
		}

With this code, the user remains on the edit user form page if the password change operation fails due to failing validation and error messages are displayed.

Thanks for your help in this issue.

Antonio
The topic has been locked.
Support Specialist
13 years 9 months ago #4085 by alzander
That's very cool. And thanks for your code snippet and contribution. We agree that Joomla should allow some better way to specify the security of the password and frequency of resets.

The Force Password Change plugin was never meant to be a foolproof method, so yes, it can be 'gotten around' as you mentioned. Unfortunately, I don't know of a good method to reset the lastvisitDate in the plugin as that won't get fired at the right time to do so. We may try to implement something similar in plugin form that keeps a user on the page using other means, but all we have to go off of now is the lastvisitDate.

Since you're already modifying the core (not something we recommend, but you seem comfortable with it and probably understand the ramifications), you could do something along the lines of:
$user = JFactory::getUser();
$user->lastvisitDate = "00-00-0000 00:00:00"; (check that format)
$user->store();

That would force the plugin to fire on the next pageload again... the code above was quick, so might be missing something, but you probably get the gist.

Thanks again, and we hope this helps others who want to implement this change for better security.
The topic has been locked.
Active Subscriptions:

None
13 years 9 months ago #4089 by alc0905
Hi there!

Just wanted to provide you with a follow-up. I managed to solve the issue but it's messy - some hacking of the core files, which I hate to do, but had no other options at this point. Here is what I did:

EDIT: To show the location of the changes I made, I highlighted any code modifications in bold, but the bold formatting doesn't show - you will see the
[b] [/b]
around the modified code though!

1) I modified the function onLogoutUser in the joomla core user plugin, located at plugins/user/joomla.php
function onLogoutUser($user, $options = array())
	{
		$my =& JFactory::getUser();
		//Make sure we're a valid user first
		if($user['id'] == 0 && !$my->get('tmp_user')) return true;

		//Check to see if we're deleting the current session
		if($my->get('id') == $user['id'])
		{
			[b]// check to see if last vist is 0
			if($my->lastvisitDate != "0000-00-00 00:00:00") :
				// Hit the user last visit field
				$my->setLastVisit();
			endif;[/b]
			
			// Destroy the php session for this user
			$session =& JFactory::getSession();
			$session->destroy();
		} else {
			// Force logout all users with that userid
			$table = & JTable::getInstance('session');
			$table->destroy($user['id'], $options['clientid']);
		}
		return true;
	}

I noticed that every time a user is logged out, by default, their last visit date is automatically updated, which proved troublesome if a user logs in for the first time, fails to change their password successfully, and then logs out. This conditional check makes it so that the last visit date is updated only if the last visit date is not set to zero.

2) Updated the save function in the com_user controller file, located at components/com_user_controller.php
function save()
	{
		// Check for request forgeries
		JRequest::checkToken() or jexit( 'Invalid Token' );

		$user	 =& JFactory::getUser();
		$userid = JRequest::getVar( 'id', 0, 'post', 'int' );

		// preform security checks
		if ($user->get('id') == 0 || $userid == 0 || $userid <> $user->get('id')) {
			JError::raiseError( 403, JText::_('Access Forbidden') );
			return;
		}

		//clean request
		$post = JRequest::get( 'post' );
		$post['username']	= JRequest::getVar('username', '', 'post', 'username');
		$post['password']	= JRequest::getVar('password', '', 'post', 'string', JREQUEST_ALLOWRAW);
		$post['password2']	= JRequest::getVar('password2', '', 'post', 'string', JREQUEST_ALLOWRAW);
	
		// get the redirect
		$return = JURI::base();
		
		// do a password safety check
		if((preg_match("/^.*(?=.{6,})(?=.*[a-z])(?=.*[\d]).*$/", $post['password'])) || (preg_match("/^.*(?=.{6,})(?=.*[a-z])(?=.*[\d]).*$/", $post['password2']))) { // password must be strong
			if($post['password'] != $post['password2']) {
				$msg	= JText::_('PASSWORDS_DO_NOT_MATCH');
				// something is wrong. we are redirecting back to edit form.
				// TODO: HTTP_REFERER should be replaced with a base64 encoded form field in a later release
				$return = str_replace(array('"', '<', '>', "'"), '', @$_SERVER['HTTP_REFERER']);
				if (empty($return) || !JURI::isInternal($return)) {
					$return = JURI::base();
				}
				$this->setRedirect($return, $msg, 'error');
				return false;
			}
		} else {
			$msg	= JText::_('Password does not meet security requirements. Please try again.');
			$return = str_replace(array('"', '<', '>', "'"), '', @$_SERVER['HTTP_REFERER']);
			if (empty($return) || !JURI::isInternal($return)) {
				$return = JURI::base();
			}
			$this->setRedirect($return, $msg, 'error');
			return false;
		}

		// we don't want users to edit certain fields so we will unset them
		unset($post['gid']);
		unset($post['block']);
		unset($post['usertype']);
		unset($post['registerDate']);
		unset($post['activation']);

		// store data
		$model = $this->getModel('user');
		
		$return = str_replace(array('"', '<', '>', "'"), '', @$_SERVER['HTTP_REFERER']);
		if (empty($return) || !JURI::isInternal($return)) {
			$return = JURI::base();
		}

		if ($model->store($post)) {
			[b]$user->setLastVisit();
			$date = JFactory::getDate();
			$user->lastvisitDate = $date->toMySQL();[/b]
			$msg	= JText::_( 'Your account was successfully updated.' );
			$this->setRedirect( $return, $msg );
		} else {
			//$msg	= JText::_( 'Error saving your settings.' );
			$msg	= $model->getError();
			$this->setRedirect( $return, $msg, 'error' );
		}
	}

Besides the changes I made to enforce strong password validation, the code basically updates the user's last visit date when the user successfully updates his or her account. When using the force password change plugin, users are automatically brought to the edit user form, where they must change their password. So, when a user changes his or her password, this function is called, if the password validation passes -> the last visit date is updated and the changes are saved. (Now every time a user logs out, their last visit date is automatically updated as normal).

3) Changes to forcepasswordchange.php.
class plgSystemForcePasswordChange extends JPlugin
{
	function plgSystemForcePasswordChange(&$subject, $config)
	{
		parent::__construct($subject, $config);
	}

	function onAfterRoute()
	{
                global $mainframe;

                // Don't do anything if this is the administrator backend or debugging is on
                if($mainframe->isAdmin() || JDEBUG) {
                        return;
                }

		$user = &JFactory::getUser();

		$option = JRequest::getVar('option');
		$view = JRequest::getVar('view');
		$task = JRequest::getVar('task');
		$layout = JRequest::getVar('layout');
		// no_html is sent by Mighty Registration for ajax checks, so we need to ignore them
		$noHtml = JRequest::getVar('no_html');

		$editProfileOption = "com_user";
		$editProfileLayout = "form";
		$editProfileSaveTask = "save";
		$editProfileView = "user";
		[b]$logout = "logout";[/b]

		// Use these for Mighty Registration
		/*
		$editProfileOption = "com_juser";
		$editProfileLayout = "mydetails";
		$editProfileSaveTask = "user_update";
		*/

		[b]if(!$user->guest && $user->lastvisitDate == "0000-00-00 00:00:00" && $noHtml != "1")
		{
			// The user is not a guest and their lastvisitDate is zeros
			if($option == $editProfileOption && $task == $editProfileSaveTask)
			{
				// The user is saving their profile

				// Set the last visit date to a real value so we won't continue forcing them to update their profile
				//$user->setLastVisit();
				//$date = JFactory::getDate();
				//$user->lastvisitDate = $date->toMySQL();
			}
			else if($task == $logout)
			{
				// do nothing
			}[/b]
			else if(!($option == $editProfileOption && $view == $editProfileView && $layout == $editProfileLayout))
			{
				// The user is not on the edit profile form

				// Update lastvisitDate back to zero
				$dbo = &JFactory::getDBO();
				$query = "UPDATE #__users ".
					"SET lastvisitDate = ".$dbo->quote("0000-00-00 00:00:00")." ".
					"WHERE id = ".$dbo->quote($user->id);
				$dbo->setQuery($query);
				$dbo->query();
	
				// Redirect to edit profile
				$app = &JFactory::getApplication();

				$app->redirect(
					"index.php?option=".$editProfileOption."&view=".$editProfileView."&layout=".$editProfileLayout,
					$this->params->get("message", "You must update your password before continuing to use the site.")
				);
			}
		}

I created a variable called logout and assigned a string to it with the same name. I commented out the code in the first if statement, as I had moved that code into the save function in the controller.php file mentioned above. I added a second if statement to allow users to use the logout function, so they could logout of joomla, and log back in (and if they didnt not change their password, they will be brought back to the edit user form again automatically).

I think I covered most of what I changed. I am working off memory as I was recently away for vacation, but that is the gist of it :)

Messy hacks, but what can ya do!

Antonio
The topic has been locked.
Support Specialist
13 years 9 months ago #4116 by alzander
Yeah, it's unfortunate you have to modify core files. But it seems pretty clean from what you did. Looking through your code, I was trying to look for 'easy' ways to migrate some of the code to the forcepasswordchange plugin, but I unfortunately couldn't think of any ways.

We really, really appreciate your post and hope it helps others. If we come up with any ways to improve the plugin to do what you're trying to do here, which we agree is best (since it guarantees the password is updated), we'll post here and definitely let you know.

Thanks again,
Alex
The topic has been locked.
Active Subscriptions:

None
13 years 2 months ago #7632 by Mike
Great plugin and modification. Unfortunately it doesn't ensure that users change their passwords, all they have to do to get around the forced change is re-enter their old password.

Is it possible to add a current password field to the form so that it is checked as part of the routine? Then the two passwords (new and old) can be compared and rejected if they are identical.
The topic has been locked.
Support Specialist
13 years 2 months ago #7726 by alzander
Mike,
Unfortunately, what you ask for isn't easily done. Our plugin simply checks the user's last visit date, and if it's blank (meaning they're a new user), it will force them to the Edit User page. At this point, Joomla pretty much takes over, and the validation built into it is what occurs. Our plugin doesn't actually enforce password rules or even update the users password, it just tries to coax them into changing it.

Hope that helps explain, and sorry it doesn't do everything you're looking for. Best of luck to you!
The topic has been locked.
Active Subscriptions:

None
13 years 2 months ago #7730 by Mike
Thanks Alex

I realised after I posted that I was asking you to add fields to a Joomla form, but had a faint hope that something could be done.

I have two clients wanting regular forced password changes on Joomla sites. So I grabbed your plugin, modified the Joomla database to include a password reset date field in the users table and modified the plugin to use that field instead of lastvisitDate. I can reset that field to default (zeros) which fires the plugin for all the users - the same as zeros in the lastvistDate does. That means that I don't have to lose the last visit data to make the plugin work. That works fine and I can do that every 3 months or so to get the regular password change, but I don't have the php skills to create a facility to actually force a password change.

Thanks anyway and I'd like to hear if you do create an extension (commercial or free) that will actually force the change.
The topic has been locked.
Support Specialist
13 years 2 months ago #7762 by alzander
Mike,
Thanks for the suggestion, as we're always looking for new ideas. I don't think this is anything that we'll be working on immediately, but we agree, it'd be a great addition for Joomla.

Good luck to you, and thanks again for your feedback!
The topic has been locked.