Force All Users To Change Password On Next Login

The following code will force users to change their passwords on their next login attempt and will set the lifetime of a password to 365 days (only if it is currently set to zero).



These changes only apply to users who are NOT admin users with the “is_root” property == 'Y'. I.e. the primary admin should be unaffected.



This same code will work on all versions but needs to be installed in different locations depending on whether you are V4 or earlier.



Follow the instructions in the comments.

It is reasonably well tested and should work in PRO, ULTIMATE and MVE environments.


```php


/*
* For V4, copy this file to app/addons/my_changes/controllers/backend/my_changes.php
* Make sure the My Changes addon is active
* Then run this url: [your_domain_admin.php]?dispatch=my_changes.reset_user_passwords
*
* For V2/V3, copy this file to addons/my_changes/controllers/admin/my_changes.php
* Make sure the My Changes addon is active
* Then run this url: [your_domain_admin.php]?dispatch=my_changes.reset_user_passwords
*
* What will it do?
* - Changes the last password change timestamp to 1969 for all "Non admin root" users.
* - Changes the required password change time to 1 year if it is set to zero. If set to some other
* value then it leaves this alone.
* - Any user (admin, supplier, vendor or customer other than a root-admin-user) will be required to
* change their password.
* - You will see a yellow warning message after this completes.
*
* You might see a message indicating that you need to change your password. You can close that message
* and ignore it if you are logged in as the root admin.
*/

if( !defined('PRODUCT_VERSION') ) die('Access denied');

if( version_compare(PRODUCT_VERSION, '4.0.0', '<') )
if( !defined('AREA') ) die('Access denied');
else
if( !defined('BOOTSTRAP') ) die('Access denied');

switch($mode) {
case 'reset_user_passwords':
$user_ids = db_get_fields("SELECT user_id FROM ?:users WHERE is_root != 'Y'");
$user_count = count($user_ids);
db_query("UPDATE ?:users SET password_change_timestamp=1 WHERE is_root != 'Y'");
db_query("UPDATE ?:users SET password_change_timestamp=?i WHERE is_root = 'Y'", TIME);
$msg = "
Your previous password expiration was set to zero. It has been reset to 365.";
if( version_compare(PRODUCT_VERSION, '3.0.0', '<') ) {
$cur_days = db_get_field("SELECT value FROM ?:settings WHERE option_name='admin_password_expiration_period'");
if( !$cur_days ) {
db_query("UPDATE ?:settings SET value = 365 WHERE option_name = 'admin_password_expiration_period' AND value = 0");
} else {
$msg = '';
}
} else {
$cur_days = db_get_field("SELECT value FROM ?:settings_objects WHERE name='admin_password_expiration_period'");
if( !$cur_days ) {
db_query("UPDATE ?:settings_objects SET value = 365 WHERE name = 'admin_password_expiration_period' AND value = 0");
} else {
$msg = '';
}
}

fn_set_notification('W', "Force password change", "'$user_count' users will be asked to change their passwords next time they login.$msg", true');
fn_redirect(empty($index_script) ? "/" : $index_script);
break;
}
return array(CONTROLLER_STATUS_OK);

?>

```

Thank you for this code! There is a small typo in the fn_set_notification, this is the correct code:


```php


/*
* For V4, copy this file to app/addons/my_changes/controllers/backend/my_changes.php
* Make sure the My Changes addon is active
* Then run this url: [your_domain_admin.php]?dispatch=my_changes.reset_user_passwords
*
* For V2/V3, copy this file to addons/my_changes/controllers/admin/my_changes.php
* Make sure the My Changes addon is active
* Then run this url: [your_domain_admin.php]?dispatch=my_changes.reset_user_passwords
*
* What will it do?
* - Changes the last password change timestamp to 1969 for all "Non admin root" users.
* - Changes the required password change time to 1 year if it is set to zero. If set to some other
* value then it leaves this alone.
* - Any user (admin, supplier, vendor or customer other than a root-admin-user) will be required to
* change their password.
* - You will see a yellow warning message after this completes.
*
* You might see a message indicating that you need to change your password. You can close that message
* and ignore it if you are logged in as the root admin.
*/

if( !defined('PRODUCT_VERSION') ) die('Access denied');

if( version_compare(PRODUCT_VERSION, '4.0.0', '<') )
if( !defined('AREA') ) die('Access denied');
else
if( !defined('BOOTSTRAP') ) die('Access denied');

switch($mode) {
case 'reset_user_passwords':
$user_ids = db_get_fields("SELECT user_id FROM ?:users WHERE is_root != 'Y'");
$user_count = count($user_ids);
db_query("UPDATE ?:users SET password_change_timestamp=1 WHERE is_root != 'Y'");
db_query("UPDATE ?:users SET password_change_timestamp=?i WHERE is_root = 'Y'", TIME);
$msg = "
Your previous password expiration was set to zero. It has been reset to 365.";
if( version_compare(PRODUCT_VERSION, '3.0.0', '<') ) {
$cur_days = db_get_field("SELECT value FROM ?:settings WHERE option_name='admin_password_expiration_period'");
if( !$cur_days ) {
db_query("UPDATE ?:settings SET value = 365 WHERE option_name = 'admin_password_expiration_period' AND value = 0");
} else {
$msg = '';
}
} else {
$cur_days = db_get_field("SELECT value FROM ?:settings_objects WHERE name='admin_password_expiration_period'");
if( !$cur_days ) {
db_query("UPDATE ?:settings_objects SET value = 365 WHERE name = 'admin_password_expiration_period' AND value = 0");
} else {
$msg = '';
}
}

fn_set_notification('W', "Force password change", "$user_count users will be asked to change their passwords next time they login. $msg", true);
fn_redirect(empty($index_script) ? "/" : $index_script);
break;
}
return array(CONTROLLER_STATUS_OK);
?>

```



Unfortunately after testing in version 4.2.3 I noticed it's not working. At least. After logging in with a customer account it doesn't tell me to change the password. So I searched the CS-Cart files for the lang var “error_password_expired” and it only appears in \app\controllers\backend\init.php and \design\backend\templates\views\auth\password_change.tpl.



So I'm guessing more code changes need to be made to make it work with customers in the front-end.

It should work in V4.x. Verify that the change_password_timestamp field in the cscart_users table has been correctly set to 1 for non-primary-admin users.

Thank you for your reply! Yes, all users except the admin are set to 1. But after logging in as a customer to the front end it only shows “logged in successfully”.

Then I think something else is wrong. The code clearly checks to see if the change_password_timestamp is less than TIME (current timestamp) and if so, should force a change. Maybe they broke it in this version. Might want to submit a bug in bugtracker.



And is the error you mentioned in fn_set_notification the missing space between the notice of the number of users changed and the $msg variable?

I checked with CS-Cart support and it seems to be a bug. As soon as I have a working solution, I'll post it here.



As for the code correction. I changed:

fn_set_notification('W', "Force password change", "'$user_count' users will be asked to change their passwords next time they login.$msg", true');

to

fn_set_notification('W', "Force password change", "$user_count users will be asked to change their passwords next time they login.$msg", true);



So I removed the ' after “true”. As you can see it's messing up the code coloring.

Okay, didn’t catch that. Old eyes don’t see single quotes very well here…:-)

:grin: no problem.


CS-Cart told me to change some code:

[quote]
The issue may be caused by a bug in the current version of Multi-Vendor.

In order to fix it, open the app/functions/fn.users.php file of your installation and replace this line:
```php $has_permissions = false; ```

with this part of code:
```php if ($auth['user_id'] != $user_data['user_id']) {
$has_permissions = false;
} ```

The [b]$has_permissions = false;[/b] line is used two times. You will need to replace the one which is after the following line:
```php } elseif ($auth['user_type'] == 'V' && $user_data['is_root'] == 'Y') { ```
[/quote]

Unfortunately after this, still only users in the back-end will have to change their password. I'm guessing they removed this function for front-end users, since I only see After logging in with a customer account it doesn't tell me to change the password. the lang var "error_password_expired" in \app\controllers\backend\init.php and \design\backend\templates\views\auth\password_change.tpl.

Not sure why they would remove a functioning feature that lets an administrator tighten the security of their site by forcing users to change their passwords at some administrator defined frequency.



The change you list above seems to only applies to Vendors, not Customers.

As expected I got this answer from CS-Cart:


[quote]

My name is Timur Mestan-zade. I am a technical support engineer at CS-Cart. I will continue communicating with you in this ticket.



I should inform you that there was no such feature as forcing customers to change their passwords in default CS-Cart. This functionality requires an additional code modification.



I am afraid, it cannot be made as a part of our technical support service, so we can offer you the customization services offered by our affiliate company Simtech Development Ltd (reorganization took place in our company and now our Custom Development department acts as a separate legal entity. Their company name is Simtech Development Ltd and the website is http://www.simtechdev.com ).



For your convenience we can forward your request to Simtech Development Ltd for an estimation, please let me know if you are interested in it.

[/quote]



Do you have a working “demo” of the force password change for customers? Would like to show them.

Just tell them the password_change_timestamp was set to 1 in the DB and when a user logs in to your site it does not ask them to change the password. Obviously 1 is less than the current timestamp (TIME). I.e. the password has expired.

I did, but still… offering Simtech to make the changes.

I see no other option than having someone customize the code to make it work in Multi-Vendor 4.2.4, but not by Simtech. You can PM me if you are interrested. My client needs it next week.

You can request a quote by using the link in my signature. I.e. “get a quote”.

Hello. Have you found a solution to this? Also, will this work for the free version of cscart?

[quote name='Webrunner' timestamp='1426236456' post='207840']

I checked with CS-Cart support and it seems to be a bug. As soon as I have a working solution, I'll post it here.



As for the code correction. I changed:

fn_set_notification('W', "Force password change", "'$user_count' users will be asked to change their passwords next time they login.$msg", true');

to

fn_set_notification('W', "Force password change", "$user_count users will be asked to change their passwords next time they login.$msg", true);



So I removed the ' after “true”. As you can see it's messing up the code coloring.

[/quote]

Looks like you also removed the ' before and after $user_count in the code (not sure if that was intended)

Line should read:


fn_set_notification('W', "Force password change", "'$user_count' users will be asked to change their passwords next time they login.$msg", true);


This simply puts the number of affected users in single quotes. I.e.

[quote]'3929 users will be asked to change their passwords next time they login[/quote]

If the previous setting of the password expiration setting was zero (no reset of passwords), it is changed to 365 days and the notification has

[quote]Your previous password expiration was set to zero. It has been reset to 365.[/quote]

appended to the message. If it was not zero, it is left alone.

[quote name='scooterlord' timestamp='1429387651' post='211939']

Hello. Have you found a solution to this? Also, will this work for the free version of cscart?

[/quote]



Unfortunately, no. Still no solution.

My client decided not to spend too much time on this, because in the end it isn't that important for them.

How to check previous 4 passwords for both vendor and customers

The code below checks the previous 4 password for vendor account only I want to extend it to include the customers as well

// Check last 4 passwords
                if (!empty($user_id)) {
                        $current_user_data = db_get_row(
            "SELECT user_id, company_id, is_root, status, user_type, email, user_login, lang_code, password, salt, last_passwords"
            . " FROM ?:users WHERE user_id = ?i",
            $user_id
        );
                    $prev_passwords = !empty($current_user_data['last_passwords']) ? explode(',', $current_user_data['last_passwords']) : array();
                if (!empty(Tygh::$app['session']['auth']['forced_password_change'])) {
                    // if forced password change - new password can't be equal to current password.
                    $prev_passwords[] = $current_user_data['password'];
                }

                if (in_array(fn_generate_salted_password($user_data['password1'], $current_user_data['salt']), $prev_passwords)) {
                    $valid_passwords = false;
                    fn_set_notification('E', __('error'), __('error_password_was_used'));
                } else {
                    if (count($prev_passwords) >= 5) {
                        array_shift($prev_passwords);
                    }
                    $user_data['last_passwords'] = implode(',', $prev_passwords);
                }
            }