diff --git a/README.md b/README.md index 8b25c2d..f46ef16 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ Lets administrators create user accounts from the ACP. ## Features - New **Add User** page under **ACP → Users and Groups**. -- Set username, email, password (left blank, one is generated), language and birthday. +- Set username, email, password (left blank, a random one is generated and never emailed), language and birthday. - Choose the user's group and optionally make it their default group; optionally add them to *Newly registered users*. -- Follows the board's activation setting: the new user gets their login details by email, or with admin activation you can activate the account on the spot. +- Follows the board's activation setting: the new user gets an email with a link to set their own password, or with admin activation you can activate the account on the spot. - Every account created is recorded in the admin log. ## Requirements diff --git a/acp/adduser_module.php b/acp/adduser_module.php index 96d02f6..7193f60 100644 --- a/acp/adduser_module.php +++ b/acp/adduser_module.php @@ -99,21 +99,35 @@ public function main($id, $mode) // Create a new password for our user only if there is nothing for a password already if (empty($data['new_password']) && empty($data['password_confirm'])) { - if ($this->config['pass_complex'] == 'PASS_TYPE_ANY' || $this->config['pass_complex'] == 'PASS_TYPE_CASE') + // The password is never emailed, the user sets their own via the reset + // link. Draw it from random_int rather than from the current time, and + // take one character from every class validate_password() demands. + $pools = ['abcdefghijkmnopqrstuvwxyz', 'ABCDEFGHJKLMNPQRSTUVWXYZ', '23456789']; + if (!in_array($this->config['pass_complex'], ['PASS_TYPE_ANY', 'PASS_TYPE_CASE', 'PASS_TYPE_ALPHA'], true)) { - $new_password = str_split(base64_encode(md5(time() . $data['username'])), $this->config['min_pass_chars'] + rand(3, 5)); - $data['new_password'] = $data['password_confirm'] = $new_password[0]; + // PASS_TYPE_SYMBOL, or an invalid setting, which phpBB treats as the strictest one + $pools[] = '{}[];:,./<>?_+~!@#'; } - else if ($this->config['pass_complex'] == 'PASS_TYPE_ALPHA') + $all = implode('', $pools); + $length = max(20, (int) $this->config['min_pass_chars']); + $new_password = ''; + foreach ($pools as $pool) { - $new_password = $this->generate_password($this->config['min_pass_chars'] + rand(3, 5), 'PASS_TYPE_ALPHA'); - $data['new_password'] = $data['password_confirm'] = $new_password; + $new_password .= $pool[random_int(0, strlen($pool) - 1)]; } - else + for ($i = strlen($new_password); $i < $length; $i++) + { + $new_password .= $all[random_int(0, strlen($all) - 1)]; + } + // str_shuffle() draws from the Mersenne Twister, so shuffle by hand + for ($i = strlen($new_password) - 1; $i > 0; $i--) { - $new_password = $this->generate_password($this->config['min_pass_chars'] + rand(3, 5), 'PASS_TYPE_SYMBOL'); - $data['new_password'] = $data['password_confirm'] = $new_password; + $j = random_int(0, $i); + $swap = $new_password[$i]; + $new_password[$i] = $new_password[$j]; + $new_password[$j] = $swap; } + $data['new_password'] = $data['password_confirm'] = $new_password; } // Validate entries @@ -281,12 +295,32 @@ public function main($id, $mode) $messenger->headers('X-AntiAbuse: Username - ' . $this->user->data['username']); $messenger->headers('X-AntiAbuse: User IP - ' . $this->user->ip); + // Send a reset link instead of the password: the user picks their own. + // route() ends in append_sid(), which would attach the session id of the + // admin creating the account. That must not travel by email. + $reset_expires = \phpbb\user::get_token_expiration(); + $reset_token = strtolower(gen_rand_string(32)); + $this->db->sql_query('UPDATE ' . USERS_TABLE . " + SET reset_token = '" . $this->db->sql_escape($reset_token) . "', + reset_token_expiration = " . (int) $reset_expires . ' + WHERE user_id = ' . (int) $this->user_id); + global $_SID; + $sid_backup = $_SID; + $_SID = ''; + $reset_link = generate_board_url(true) . $phpbb_container->get('controller.helper')->route( + 'phpbb_ucp_reset_password_controller', + ['u' => (int) $this->user_id, 'token' => $reset_token], + false + ); + $_SID = $sid_backup; + $messenger->assign_vars([ - 'WELCOME_MSG' => htmlspecialchars_decode(sprintf($this->user->lang['WELCOME_SUBJECT'], $this->config['sitename'])), - 'USERNAME' => htmlspecialchars_decode($data['username']), - 'PASSWORD' => htmlspecialchars_decode($data['new_password']), + 'WELCOME_MSG' => htmlspecialchars_decode(sprintf($this->user->lang['WELCOME_SUBJECT'], $this->config['sitename'])), + 'USERNAME' => htmlspecialchars_decode($data['username']), + 'U_RESET_PASSWORD' => $reset_link, + 'RESET_EXPIRES' => $this->user->format_date($reset_expires, str_replace('|', '', $this->user->lang['DATETIME_FORMAT'])), - 'U_ACTIVATE' => "$server_url/ucp.$phpEx?mode=activate&u=$this->user_id&k=$this->user_actkey", + 'U_ACTIVATE' => "$server_url/ucp.$phpEx?mode=activate&u=$this->user_id&k=$this->user_actkey", ]); $messenger->send(NOTIFY_EMAIL); @@ -401,49 +435,6 @@ public function main($id, $mode) $cp->generate_profile_fields('profile', $this->user->get_iso_lang_id()); } - // Function to generate passwords - private function generate_password($length, $type) - { - $lowercase = "abcdefghijklmnopqrstuvwxyz"; - $uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - $numbers = "1234567890"; - $specialcharacters = "{}[];:,./<>?_+~!@#"; - - $pword_string = ''; - - $max = strlen($lowercase) - 1; - - for ($x = 0; $x < abs($length/3); $x++) - { - $pword_string .= $lowercase[mt_rand(0, $max)]; - } - - $max = strlen($uppercase) - 1; - - for ($x = 0; $x < abs($length/3); $x++) - { - $pword_string .= $uppercase[mt_rand(0, $max)]; - } - - $max = strlen($numbers) - 1; - - for ($x = 0; $x < abs($length/3); $x++) - { - $pword_string .= $numbers[mt_rand(0, $max)]; - } - - if ($type == 'PASS_TYPE_SYMBOL') - { - $max = strlen($specialcharacters) - 1; - - for ($x = 0; $x < abs($length/3); $x++) - { - $pword_string .= $specialcharacters[mt_rand(0, $max)]; - } - } - - return str_shuffle($pword_string); - } // Function to return groups that are allowed private function get_groups($group_selected) diff --git a/language/en/email/user_added_admin_welcome_inactive.txt b/language/en/email/user_added_admin_welcome_inactive.txt index 90c32bc..ed622a2 100644 --- a/language/en/email/user_added_admin_welcome_inactive.txt +++ b/language/en/email/user_added_admin_welcome_inactive.txt @@ -2,21 +2,21 @@ Subject: You have been added to "{SITENAME}" {WELCOME_MSG} -Please keep this e-mail for your records. Your account information is as follows: +You need your username to log in. Keep it: ---------------------------- Username: {USERNAME} Board URL: {U_BOARD} - -Password: {PASSWORD} ---------------------------- Your account is currently inactive and will need to be approved by an administrator before you can log in. Another e-mail will be sent when this has occurred. -Your password has been securely stored in our database and cannot be retrieved. In the event that it is forgotten, you will be able to reset it using the e-mail address associated with your account. +Use this link to set your own password: + +{U_RESET_PASSWORD} -We strongly recommend that you change your password once you log into the forum. +The link is valid until {RESET_EXPIRES}. After that, use “Forgot password” at {U_BOARD}. {EMAIL_SIG} diff --git a/language/en/email/user_added_inactive.txt b/language/en/email/user_added_inactive.txt index 0e2487d..a732435 100644 --- a/language/en/email/user_added_inactive.txt +++ b/language/en/email/user_added_inactive.txt @@ -2,23 +2,23 @@ Subject: You have been added to "{SITENAME}" {WELCOME_MSG} -Please keep this e-mail for your records. Your account information is as follows: +You need your username to log in. Keep it: ---------------------------- Username: {USERNAME} Board URL: {U_BOARD} - -Password: {PASSWORD} ---------------------------- Please visit the following link in order to activate your account: {U_ACTIVATE} -Your password has been securely stored in our database and cannot be retrieved. In the event that it is forgotten, you will be able to reset it using the e-mail address associated with your account. +Use this link to set your own password: + +{U_RESET_PASSWORD} -We strongly recommend that you change your password once you log into the forum. +The link is valid until {RESET_EXPIRES}. After that, use “Forgot password” at {U_BOARD}. {EMAIL_SIG} diff --git a/language/en/email/user_added_welcome.txt b/language/en/email/user_added_welcome.txt index 3c8ffd4..1fc546e 100644 --- a/language/en/email/user_added_welcome.txt +++ b/language/en/email/user_added_welcome.txt @@ -2,19 +2,19 @@ Subject: You have been added to "{SITENAME}" {WELCOME_MSG} -Please keep this e-mail for your records. Your account information is as follows: +You need your username to log in. Keep it: ---------------------------- Username: {USERNAME} Board URL: {U_BOARD} - -Password: {PASSWORD} ---------------------------- -Your password has been securely stored in our database and cannot be retrieved. In the event that it is forgotten, you will be able to reset it using the e-mail address associated with your account. +Use this link to set your own password: + +{U_RESET_PASSWORD} -We strongly recommend that you change your password once you log into the forum. +The link is valid until {RESET_EXPIRES}. After that, use “Forgot password” at {U_BOARD}. {EMAIL_SIG} diff --git a/language/en/info_acp_adduser.php b/language/en/info_acp_adduser.php index bb0b841..8f45d47 100644 --- a/language/en/info_acp_adduser.php +++ b/language/en/info_acp_adduser.php @@ -41,12 +41,12 @@ // ACP Module 'ACP_ADD_USER' => 'Add User', 'DIR_NOT_EXIST' => 'The language you have chosen %s does not have the files needed for the extension. Please translate them and upload to the %s directory of the extension.', - 'ACP_ACCOUNT_ADDED' => 'The user account has been created. The user may now login with the username and password sent to the email address you provided.', + 'ACP_ACCOUNT_ADDED' => 'The user account has been created. A link was sent to the email address you provided; the user sets their own password with it. The email states how long the link is valid.', 'ACP_ACCOUNT_INACTIVE' => 'The user account has been created. However, the forum settings require the user to activate their account.
An activation key has been sent to the email address you provided for the user.', 'ACP_ACCOUNT_INACTIVE_ADMIN' => 'The user account has been created. However, the forum settings require account activation by an administrator.
An email has been sent to the Administrators and the user will be informed when their account has been activated', - 'ACP_ADMIN_ACTIVATE' => 'An email will be dispatched to an Administrator for account activation. Alternatively you may check the activate account box below to activate the account instantly once created. The user will receive an email containing account login details.', + 'ACP_ADMIN_ACTIVATE' => 'An email will be dispatched to an Administrator for account activation. Alternatively you may check the activate account box below to activate the account instantly once created. The user will receive a link to set their own password.', 'ACP_EMAIL_ACTIVATE' => 'Once the account has been created, the user will receive an email containing an activation link to activate the account.', - 'ACP_INSTANT_ACTIVATE' => 'The account will be activated instantly. The user will receive an email with account login details.', + 'ACP_INSTANT_ACTIVATE' => 'The account will be activated instantly. The user will receive a link to set their own password.', 'ADD_USER' => 'Add User', 'ADD_USER_EXPLAIN' => 'Create a new user account. If your activation settings are to Admin Activation only, you will have the option to activate the user instantly.', @@ -56,7 +56,7 @@ 'GROUP_ADD' => 'Making a selection here will add the user to the group selected as well as to the registered users group.', 'GROUP_DEFAULT_EXPLAIN' => 'Checking this box will set the group selected above to be the users default.', 'CONTINUE_EDIT_USER' => '%1$sClick here to manage %2$s’s profile%3$s', - 'PASSWORD_EXPLAIN' => 'If left blank a password will be auto generated.', + 'PASSWORD_EXPLAIN' => 'If left blank a password will be generated. It is never emailed — the user sets their own password with the link.', 'ENABLE_NEWUSER' => 'Enable new user', 'ENABLE_NEWUSER_EXPLAIN' => 'If set as yes the user will be added to the newly registered user group',