genpass.module 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. <?php
  2. define('GENPASS_REQUIRED', 0);
  3. define('GENPASS_OPTIONAL', 1);
  4. define('GENPASS_RESTRICTED', 2);
  5. define('GENPASS_DISPLAY_NONE', 0);
  6. define('GENPASS_DISPLAY_ADMIN', 1);
  7. define('GENPASS_DISPLAY_USER', 2);
  8. define('GENPASS_DISPLAY_BOTH', 3);
  9. /**
  10. * Implements of hook_init().
  11. */
  12. function genpass_init() {
  13. drupal_add_css(drupal_get_path('module', 'genpass') . '/genpass.css');
  14. }
  15. /**
  16. * Defines default characters allowed for passwords.
  17. */
  18. function _GENPASS_REQUIRED_entropy() {
  19. return 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!#$%&()*+,-./:;<=>?@[]^_{|}~';
  20. }
  21. /**
  22. * Generate a new password using the preferred password generation algorithm.
  23. *
  24. * @return a fresh password.
  25. */
  26. function genpass_generate() {
  27. return module_invoke(genpass_algorithm_module(), 'password');
  28. }
  29. /**
  30. * Generate a new password using genpass's internal password generation
  31. * algorithm.
  32. * Based on the original D6 user_password function (with more characters)
  33. *
  34. * @return a fresh password according to the settings made in /admin/user/settings
  35. *
  36. * @see genpass_form_alter()
  37. */
  38. function genpass_password() {
  39. $pass = '';
  40. $length = variable_get('genpass_length', 8);
  41. $allowable_characters = variable_get('genpass_entropy', _GENPASS_REQUIRED_entropy());
  42. // Zero-based count of characters in the allowable list:
  43. $len = strlen($allowable_characters) - 1;
  44. // Loop the number of times specified by $length.
  45. for ($i = 0; $i < $length; $i++) {
  46. // Each iteration, pick a random character from the
  47. // allowable string and append it to the password:
  48. $pass .= $allowable_characters[mt_rand(0, $len)];
  49. }
  50. return $pass;
  51. }
  52. /**
  53. * Helper function to find a item in the user form, since its position
  54. * within the form-array depends on the profile module (account-category).
  55. */
  56. function &_genpass_get_form_item(&$form, $field) {
  57. if (isset($form['account'][$field])) {
  58. return $form['account'][$field];
  59. }
  60. else {
  61. return $form[$field];
  62. }
  63. }
  64. /**
  65. * Implementation of hook_form_alter()
  66. */
  67. function genpass_form_alter(&$form, $form_state, $form_id) {
  68. switch ($form_id) {
  69. // User admin settings form at admin/config/people/accounts
  70. case 'user_admin_settings':
  71. $form['registration_cancellation']['genpass_mode'] = array(
  72. '#type' => 'radios',
  73. '#title' => t('Password handling'),
  74. '#default_value' => variable_get('genpass_mode', GENPASS_REQUIRED),
  75. '#options' => array(
  76. GENPASS_REQUIRED => t('Users <strong>must</strong> enter a password on registration. This is disabled if e-mail verification is enabled above.'),
  77. GENPASS_OPTIONAL => t('Users <strong>may</strong> enter a password on registration. If left empty, a random password will be generated. This always applies when an administer is creating the account.'),
  78. GENPASS_RESTRICTED => t('Users <strong>cannot</strong> enter a password on registration; a random password will be generated. This always applies for the regular user registration form if e-mail verification is enabled above.'),
  79. ),
  80. '#description' => t('Choose a password handling mode for new users.'),
  81. );
  82. $form['registration_cancellation']['genpass_length'] = array(
  83. '#type' => 'textfield',
  84. '#title' => t('Generated password length'),
  85. '#default_value' => variable_get('genpass_length', 8),
  86. '#size' => 2,
  87. '#maxlength' => 2,
  88. '#description' => t('Set the length of generated passwords here. Allowed range: 5 to 32.'),
  89. );
  90. $form['registration_cancellation']['genpass_entropy'] = array(
  91. '#type' => 'textfield',
  92. '#title' => t('Generated password entropy'),
  93. '#size' => 100,
  94. '#default_value' => variable_get('genpass_entropy', _GENPASS_REQUIRED_entropy()),
  95. '#description' => t('Give a list of possible characters for a generated password. Note that the list must contain at least X different characters where X is defined by the length you have given above.'),
  96. );
  97. // Provide a selection mechanism to choose the preferred algorithm for
  98. // generating passwords. Any module which implements hook_password() is
  99. // displayed here.
  100. $form['registration_cancellation']['genpass_algorithm'] = array(
  101. '#type' => 'radios',
  102. '#title' => t('Password generation algorithm'),
  103. '#default_value' => genpass_algorithm_module(),
  104. '#options' => genpass_add_samples(genpass_algorithm_modules()),
  105. '#description' => t('If third party modules define a password generation algorithm, you can select which one to use. Note that algorithms other than genpass will ignore the preferred entropy and password length. The currently selected algorithm produced the password @pw.', array('@pw' => genpass_generate())),
  106. );
  107. $form['registration_cancellation']['genpass_display'] = array(
  108. '#type' => 'radios',
  109. '#title' => t('Generated password display'),
  110. '#default_value' => variable_get('genpass_display', GENPASS_DISPLAY_BOTH),
  111. '#options' => array(
  112. GENPASS_DISPLAY_NONE => t('Do not display.'),
  113. GENPASS_DISPLAY_ADMIN => t('Display when site administrators create new user accounts.'),
  114. GENPASS_DISPLAY_USER => t('Display when users create their own accounts.'),
  115. GENPASS_DISPLAY_BOTH => t('Display to both site administrators and users.'),
  116. ),
  117. '#description' => t('Whether or not the generated password should display after a user account is created.'),
  118. );
  119. $form['#validate'][] = 'genpass_user_admin_settings_validate';
  120. // Move the "When cancelling a user account" field down.
  121. $form['registration_cancellation']['user_cancel_method']['#weight'] = 1;
  122. break;
  123. // User registration form at admin/people/create
  124. case 'user_register_form':
  125. $mode = variable_get('genpass_mode', GENPASS_REQUIRED);
  126. // Add validation function, where password may get set
  127. $form['#validate'][] = 'genpass_register_validate';
  128. // Administrator is creating the user
  129. if ($_GET['q'] == 'admin/user/user/create') {
  130. // Switch to optional mode
  131. $mode = GENPASS_OPTIONAL;
  132. // Help avoid obvious consequence of password being optional
  133. $notify_item =& _genpass_get_form_item($form, 'notify');
  134. $notify_item['#description'] = t('This is recommended when auto-generating the password; otherwise, neither you nor the new user will know the password.');
  135. }
  136. // Pass mode to validation function
  137. $form['genpass_mode'] = array(
  138. '#type' => 'value',
  139. '#value' => $mode,
  140. );
  141. $pass_item =& _genpass_get_form_item($form, 'pass');
  142. switch ($mode) {
  143. // If password is optional, don't require it, and give the user an
  144. // indication of what will happen if left blank
  145. case GENPASS_OPTIONAL:
  146. $pass_item['#required'] = FALSE;
  147. $pass_item['#description'] = (empty($pass_item['#description']) ? '' : $pass_item['#description'] . ' ') . t('If left blank, a password will be generated for you.');
  148. break;
  149. // If password is restricted, remove access
  150. case GENPASS_RESTRICTED:
  151. $pass_item['#access'] = FALSE;
  152. $pass_item['#required'] = FALSE;
  153. break;
  154. }
  155. break;
  156. }
  157. }
  158. /**
  159. * User settings validation.
  160. */
  161. function genpass_user_admin_settings_validate($form, &$form_state) {
  162. // Validate length of password
  163. $length = $form_state['values']['genpass_length'];
  164. if (!is_numeric($length) || $length < 5 || $length > 32) {
  165. form_set_error('genpass_length', t('The length of a generated password must be between 5 and 32.'));
  166. return;
  167. }
  168. // Validate allowed characters
  169. $chars = array_unique(preg_split('//', $form_state['values']['genpass_entropy'], -1, PREG_SPLIT_NO_EMPTY));
  170. if (count($chars) < $length) {
  171. form_set_error('genpass_entropy', t('The list of possible characters is not long or unique enough.'));
  172. return;
  173. }
  174. return $form;
  175. }
  176. /**
  177. * User registration validation.
  178. */
  179. function genpass_register_validate($form, &$form_state) {
  180. if (empty($form_state['values']['pass']) && !form_get_errors()) {
  181. // Generate and set password
  182. $pass = genpass_generate();
  183. $pass_item =& _genpass_get_form_item($form, 'pass');
  184. form_set_value($pass_item, $pass, $form_state);
  185. $display = variable_get('genpass_display', GENPASS_DISPLAY_BOTH);
  186. // Administrator created the user.
  187. if ($_GET['q'] == 'admin/people/create') {
  188. $message = t('Since you did not provide a password, it was generated automatically for this account.');
  189. if (in_array($display, array(GENPASS_DISPLAY_ADMIN, GENPASS_DISPLAY_BOTH))) {
  190. $message .= ' ' . t('The password is: <strong class="genpass-password">!password</strong>', array('!password' => $pass));
  191. }
  192. }
  193. // Optional - User did not provide password, so it was generated
  194. elseif ($form_state['values']['genpass_mode'] == GENPASS_OPTIONAL) {
  195. $message = t('Since you did not provide a password, it was generated for you.');
  196. if (in_array($display, array(GENPASS_DISPLAY_USER, GENPASS_DISPLAY_BOTH))) {
  197. $message .= ' ' . t('Your password is: <strong class="genpass-password">!password</strong>', array('!password' => $pass));
  198. }
  199. }
  200. // Restricted - User was forced to receive a generated password
  201. elseif ($form_state['values']['genpass_mode'] == GENPASS_RESTRICTED && in_array($display, array(GENPASS_DISPLAY_USER, GENPASS_DISPLAY_BOTH))) {
  202. $message = t('The following password was generated for you: <strong class="genpass-password">!password</strong>', array('!password' => $pass));
  203. }
  204. if (!empty($message)) {
  205. drupal_set_message($message);
  206. }
  207. }
  208. return $form;
  209. }
  210. /**
  211. * Return an array of all modules which implement hook_password.
  212. *
  213. * @return array of module names.
  214. */
  215. function genpass_algorithm_modules() {
  216. // Fetch a list of all modules which implement the password generation hook.
  217. // To be in this list, a module must implement a hook, and return random
  218. // passwords as strings.
  219. $return = array();
  220. foreach (module_implements('password') as $module) {
  221. $return[$module] = $module;
  222. }
  223. return $return;
  224. }
  225. /**
  226. * Return the currently activated module for generating passwords. Does some
  227. * validation to make sure the variable contains a valid module name.
  228. *
  229. * @return the name of the module whose implementation of hook_password is
  230. * currently the preferred implementation.
  231. */
  232. function genpass_algorithm_module() {
  233. $modules = genpass_algorithm_modules();
  234. $module = variable_get('genpass_algorithm', 'genpass');
  235. if (in_array($module, array_keys($modules))) {
  236. return $module;
  237. }
  238. else {
  239. return 'genpass';
  240. }
  241. }
  242. /**
  243. * Adds some sample passwords to each module in an array.
  244. */
  245. function genpass_add_samples($array) {
  246. $return = array();
  247. foreach ($array as $module => $name) {
  248. $return[$module] = $module . ' (' . t('examples') . ': <strong>' . htmlentities(module_invoke($module, 'password')) . '</strong>, <strong>' . htmlentities(module_invoke($module, 'password')) . '</strong>)';
  249. }
  250. return $return;
  251. }