PageRenderTime 39ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/add-ons/password_generator/Password.php

https://github.com/jcplat/console-seolan
PHP | 381 lines | 142 code | 38 blank | 201 comment | 31 complexity | 3a9dd6b78f56b5e9037039a679231b3c MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-2.1, GPL-3.0, Apache-2.0, BSD-3-Clause
  1. <?php
  2. /**
  3. * Password Generator
  4. *
  5. * LICENSE
  6. *
  7. * This source file is licensed under the Creative Commons Attribution
  8. * 2.5 License that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * {@link http://creativecommons.org/licenses/by/2.5/}
  11. *
  12. * @category Password Utilities
  13. * @package Password
  14. * @author Aleksey V. Zapparov A.K.A. iXTi <ixti.ru@gmail.com>
  15. * @license http://creativecommons.org/licenses/by/2.5/
  16. */
  17. /**
  18. * Password generating class
  19. *
  20. * @category Password Utilities
  21. * @package Password
  22. * @author Aleksey V. Zapparov A.K.A. iXTi <ixti.ru@gmail.com>
  23. * @license http://creativecommons.org/licenses/by/2.5/
  24. */
  25. class Password
  26. {
  27. /**
  28. * Used in Password::generate(), as third option.
  29. * Set letters in generated password(s) to lower register.
  30. *
  31. * @see Password::generate()
  32. */
  33. const LOWER = -1;
  34. /**
  35. * Used in Password::generate(), as third option.
  36. * Set letters in generated password(s) to random register.
  37. *
  38. * @see Password::generate()
  39. */
  40. const RANDOM = 0;
  41. /**
  42. * Used in Password::generate(), as third option.
  43. * Set letters in generated password(s) to upper register.
  44. *
  45. * @see Password::generate()
  46. */
  47. const UPPER = 1;
  48. /**
  49. * Singletone pattern
  50. *
  51. * @var object Password
  52. */
  53. static private $_instance;
  54. /**
  55. * Alphabet two-dimension array.
  56. *
  57. * @see Password::setAlphabet()
  58. * @var array $_alphabet
  59. */
  60. private $_alphabet = array();
  61. /**
  62. * Dictionary array
  63. *
  64. * @see Password::setDictionary()
  65. * @var array $_dictionary
  66. */
  67. private $_dictionary = array();
  68. /**
  69. * Singletone pattern
  70. */
  71. private function __construct()
  72. {
  73. $this->setAlphabet();
  74. }
  75. /**
  76. * Singletone pattern
  77. */
  78. private function __clone()
  79. {}
  80. /**
  81. * Singletone pattern
  82. *
  83. * @return object Password
  84. */
  85. static public function getInstance()
  86. {
  87. if (!self::$_instance instanceof self)
  88. {
  89. self::$_instance = new self();
  90. }
  91. return self::$_instance;
  92. }
  93. /**
  94. * Tells Password class to use password dictionary when creating passwords.
  95. *
  96. * If $dictionary is a string, then it tries to open file with a specified
  97. * name. Each line of that file will be treaten as a sample password.
  98. * If specified file cannot be opened then Exception thowed.
  99. *
  100. * If $dictionary is an array then each element of it will be treaten as a
  101. * sample password.
  102. *
  103. * To unset ditionary, call setDictionary() without $dictionary specified,
  104. * or specify it as array().
  105. *
  106. * @throws Exception If password dictionary cannot being found
  107. * @param string|array $dictionary (optional) Dictionary to use
  108. * @return object Password
  109. */
  110. public function setDictionary($dictionary = array())
  111. {
  112. if (!is_array($dictionary)) {
  113. $dictionary = @file($dictionary);
  114. if (!$dictionary) {
  115. throw new Exception('Password dictionary file not found!');
  116. }
  117. }
  118. $this->_dictionary = array_values($dictionary);
  119. return $this;
  120. }
  121. /**
  122. * Set custom alphabet. This can be usefull for creating easy-pronouncing
  123. * passwords without using of dictionary, for example:
  124. *
  125. * <code>
  126. * $alphabetEven = array('b', 'd', 'f', 'g', 'h', 'k', 'l', 'm', 'n', 'p',
  127. * 'r', 's', 't', 'v', 'w', 'z');
  128. * $alphabetOdd = array('a', 'e', 'i', 'o', 'u', 'y');
  129. * </code>
  130. *
  131. * So when you'll be generating new password without dictionary (or even
  132. * with dictionary) letters will be chosen from one then from another array.
  133. *
  134. * To clearly understand why, here you some sources of Password::_generate()
  135. * method:
  136. *
  137. * <code>
  138. * for ($char_id = count($password); $char_id < array_sum($passLength);
  139. * $char_id++) {
  140. * $chars_array = $this-{>}_alphabet[$char_id % 2];
  141. * $password[] = $chars_array[mt_rand(0, count($chars_array) - 1)];
  142. * }
  143. * </code>
  144. *
  145. * If $alphabetEven is not specified, or an empty array is given, then it
  146. * will be treaten as range('a', 'z')
  147. *
  148. * If $alphabetOdd is not specified, or an empty array is given, then it
  149. * will be equal to $alphabetEven
  150. *
  151. * $alphabetEven and $alphabetOdd must be a single dimenson numeric arrays
  152. * with single char as values. Valid examples:
  153. *
  154. * <code>
  155. * Password::getInstance()->setAlphabet(range('a', 'd'));
  156. * Password::getInstance()->setAlphabet(range('a', 'd'), range('e', 'z'));
  157. * Password::getInstance()->setAlphabet(array('a', 'b', 'c', 'd'));
  158. * Password::getInstance()->setAlphabet(array('a', 'b', 'c', 'd'),
  159. * array('e', 'f', 'g'));
  160. * </code>
  161. *
  162. * Note that if you'll specify both $alphabetEven and $alphabetOdd as
  163. * arrays with only one value, then you'll get very-very unsecure password.
  164. * IMHO it will be anything but not a password:
  165. *
  166. * <code>
  167. * Password::getInstance()->setAlphabet(array('a'))
  168. * ->generate();
  169. * // Result is: 6aaAaa3A (and so on)
  170. * Password::getInstance()->setAlphabet(array('a'), array('b'))
  171. * ->generate();
  172. * // Result is: abA48bAb (and so on)
  173. * Password::getInstance()->setAlphabet(array('a'))
  174. * ->generate(array(8,0));
  175. * // Result is: aaaaaaaa
  176. * </code>
  177. *
  178. * @throws Exception If $alphabetEven or $alphabetOdd is invalid.
  179. * @param array $alphabetEven (optional)
  180. * @param array $alphabetOdd (optional)
  181. * @return object Password
  182. */
  183. public function setAlphabet(array $alphabetEven = array(),
  184. array $alphabetOdd = array())
  185. {
  186. // Check validity of $alphabetEven
  187. if (0 === count($alphabetEven)) {
  188. $alphabetEven = range('a', 'z');
  189. } else {
  190. $alphabetEven = array_values($alphabetEven);
  191. for ($char_id = 0; $char_id < count($alphabetEven); $char_id++) {
  192. if (1 != strlen($alphabetEven[$char_id])) {
  193. $error = sprintf('$alphabetEven is not valid. Only single '
  194. . 'dimension numeric arrays with single '
  195. . 'char as values allowed. There is at '
  196. . 'least one value that is not single '
  197. . 'char. $alphabetEven[%d] = "%s".',
  198. $char_id, $alphabetEven[$char_id]);
  199. throw new Exception($error);
  200. }
  201. }
  202. }
  203. // Check validity of $alphabetOdd
  204. if (0 === count($alphabetOdd)) {
  205. $alphabetOdd = $alphabetEven;
  206. } else {
  207. $alphabetOdd = array_values($alphabetOdd);
  208. for ($char_id = 0; $char_id < count($alphabetOdd); $char_id++) {
  209. if (1 != strlen($alphabetOdd[$char_id])) {
  210. $error = sprintf('$alphabetOdd is not valid. Only single '
  211. . 'dimension numeric arrays with single '
  212. . 'char as values allowed. There is at '
  213. . 'least one value that is not single '
  214. . 'char. $alphabetOdd[%d] = "%s".',
  215. $char_id, $alphabetOdd[$char_id]);
  216. throw new Exception($error);
  217. }
  218. }
  219. }
  220. // Set alphabet and return $this object
  221. $this->_alphabet = array($alphabetEven, $alphabetOdd);
  222. return $this;
  223. }
  224. /**
  225. * Generate password(s)
  226. *
  227. * The default representation of $passLength is 'array => (letters, digits)'
  228. * but it can be given as an integer. If so, then it will be converted into
  229. * array with letters = 75% of $passLength and left 25% as digits.
  230. * Default is 6 letters and 2 digits. Valid examples are:
  231. *
  232. * <code>
  233. * $passLength = array(16, 2); // letters:16, digits:2, total length:18
  234. * $passLength = array(6, 0); // letters:6, digits:0, total length:6
  235. * $passLength = '15'; // letters:11, digits:4, total length:15
  236. * $passLength = '9'; // letters:7, digits:2, total length:9
  237. * </code>
  238. *
  239. * If $amountOfPasswords is more then one, then array with passwords of
  240. * $passLength and $lettersCase will be returned.
  241. *
  242. * $lettersCase let you choose which letter case to use in generated
  243. * password(s). Available values are: Password::LOWER, Password::UPPER and
  244. * Password::RANDOM. Password::RANDOM is default option.
  245. *
  246. * @uses Password::_generate()
  247. * @param array|integer $passLength (optional) Generated password's length
  248. * @param integer $amountOfPasswords (optional) Amount of passwords
  249. * @param integer $lettersCase (optional) Letter's case in passwords
  250. * @return string|array Generated password(s)
  251. */
  252. public function generate($passLength = array(6, 2), $amountOfPasswords = 1,
  253. $lettersCase = self::RANDOM)
  254. {
  255. // Getting amount of letters and digits in password which must be
  256. // present in generated password(s). Doing the same operation as in
  257. // $this->_generate, just to prevent of conbverting from
  258. if (!is_array($passLength)) {
  259. $letters = round($passLength*0.75);
  260. $pass_length = array($letters, $passLength-$letters);
  261. } else {
  262. $pass_length = array_values($passLength);
  263. if (0 == count($pass_length)) {
  264. $pass_length = array(6, 2);
  265. } elseif (1 == count($pass_length)) {
  266. $pass_length = array($pass_length[0], 0);
  267. } else {
  268. $pass_length = array($pass_length[0], $pass_length[1]);
  269. }
  270. }
  271. // If specified $amountOfPasswords greater then 1, then create an array
  272. // with generated passwords, else simply generate one password.
  273. if ($amountOfPasswords > 1) {
  274. $password = array();
  275. for ($pass_id = 0; $pass_id < $amountOfPasswords; $pass_id++) {
  276. $password[$pass_id] = $this->_generate($pass_length,
  277. $lettersCase);
  278. }
  279. } else {
  280. $password = $this->_generate($pass_length, $lettersCase);
  281. }
  282. return $password;
  283. }
  284. /**
  285. * Main method that generates a single password.
  286. *
  287. * @param array $passLength (optional) Generated password's length
  288. * @param integer $lettersCase (optional) Letters case in passwords
  289. * @return string
  290. */
  291. final private function _generate(array $passLength = array(6, 2),
  292. $lettersCase = self::RANDOM)
  293. {
  294. $password = array();
  295. // If password's dictionary not empty array then use it
  296. if (0 != count($this->_dictionary)) {
  297. // Take random password from password's dictionary array
  298. $password = $this->_dictionary[array_rand($this->_dictionary)];
  299. // Remove all 'new lines' and 'carriage returns' from password
  300. // and explode it into character's array
  301. $password = str_split(preg_replace("/[\r\n]+/", '', $password));
  302. }
  303. // If $password generated from dictionary is grater then maximum
  304. // password length, then trim it to maximum (amount of letters +
  305. // amount of digits)
  306. if (count($password) > array_sum($passLength)) {
  307. $password = array_splice($password, 0, array_sum($passLength));
  308. }
  309. // Fill $password with random letters from $this->_alphabet array
  310. for ($char_id = count($password); $char_id < array_sum($passLength);
  311. $char_id++) {
  312. $chars_array = $this->_alphabet[$char_id % 2];
  313. $password[] = $chars_array[mt_rand(0, count($chars_array) - 1)];
  314. }
  315. // Replace some letters with specified in $passLength amount of digits
  316. for ($digit_num = 0; $digit_num < $passLength[1]; $digit_num++) {
  317. $char_id = mt_rand(0, array_sum($passLength) - 1);
  318. while (is_numeric($password[$char_id])) {
  319. $char_id++;
  320. if ($char_id > array_sum($passLength) - 1) {
  321. $char_id = 0;
  322. }
  323. }
  324. $password[$char_id] = mt_rand(0, 9);
  325. }
  326. // Change password letter's case to one specified in $lettersCase
  327. for ($char_id = 0; $char_id < count($password); $char_id++) {
  328. switch ($lettersCase) {
  329. case self::LOWER :
  330. $password[$char_id] = strtolower($password[$char_id]);
  331. break;
  332. case self::UPPER :
  333. $password[$char_id] = strtoupper($password[$char_id]);
  334. break;
  335. default:
  336. $password[$char_id] = (mt_rand(0, 1))
  337. ? strtolower($password[$char_id])
  338. : strtoupper($password[$char_id]);
  339. }
  340. }
  341. return implode('', $password);
  342. }
  343. }