Password.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <?php
  2. namespace Illuminate\Validation\Rules;
  3. use ArrayIterator;
  4. use Illuminate\Container\Container;
  5. use Illuminate\Contracts\Validation\DataAwareRule;
  6. use Illuminate\Contracts\Validation\ImplicitRule;
  7. use Illuminate\Contracts\Validation\Rule;
  8. use Illuminate\Contracts\Validation\UncompromisedVerifier;
  9. use Illuminate\Contracts\Validation\ValidatorAwareRule;
  10. use Illuminate\Support\Arr;
  11. use Illuminate\Support\Facades\Validator;
  12. use Illuminate\Support\Traits\Conditionable;
  13. use InvalidArgumentException;
  14. use IteratorAggregate;
  15. use Traversable;
  16. class Password implements DataAwareRule, ImplicitRule, IteratorAggregate, Rule, ValidatorAwareRule
  17. {
  18. use Conditionable;
  19. /**
  20. * The validator performing the validation.
  21. *
  22. * @var \Illuminate\Contracts\Validation\Validator
  23. */
  24. protected $validator;
  25. /**
  26. * The data under validation.
  27. *
  28. * @var array
  29. */
  30. protected $data;
  31. /**
  32. * The minimum size of the password.
  33. *
  34. * @var int
  35. */
  36. protected $min = 8;
  37. /**
  38. * The maximum size of the password.
  39. *
  40. * @var int
  41. */
  42. protected $max;
  43. /**
  44. * If the password is required.
  45. *
  46. * @var bool
  47. */
  48. protected $required = false;
  49. /**
  50. * If the password should only be validated when present.
  51. *
  52. * @var bool
  53. */
  54. protected $sometimes = false;
  55. /**
  56. * If the password requires at least one uppercase and one lowercase letter.
  57. *
  58. * @var bool
  59. */
  60. protected $mixedCase = false;
  61. /**
  62. * If the password requires at least one letter.
  63. *
  64. * @var bool
  65. */
  66. protected $letters = false;
  67. /**
  68. * If the password requires at least one number.
  69. *
  70. * @var bool
  71. */
  72. protected $numbers = false;
  73. /**
  74. * If the password requires at least one symbol.
  75. *
  76. * @var bool
  77. */
  78. protected $symbols = false;
  79. /**
  80. * If the password should not have been compromised in data leaks.
  81. *
  82. * @var bool
  83. */
  84. protected $uncompromised = false;
  85. /**
  86. * The number of times a password can appear in data leaks before being considered compromised.
  87. *
  88. * @var int
  89. */
  90. protected $compromisedThreshold = 0;
  91. /**
  92. * Additional validation rules that should be merged into the default rules during validation.
  93. *
  94. * @var array
  95. */
  96. protected $customRules = [];
  97. /**
  98. * The failure messages, if any.
  99. *
  100. * @var array
  101. */
  102. protected $messages = [];
  103. /**
  104. * The callback that will generate the "default" version of the password rule.
  105. *
  106. * @var string|array|callable|null
  107. */
  108. public static $defaultCallback;
  109. /**
  110. * Create a new rule instance.
  111. *
  112. * @param int $min
  113. */
  114. public function __construct($min)
  115. {
  116. $this->min = max((int) $min, 1);
  117. }
  118. /**
  119. * Set the default callback to be used for determining a password's default rules.
  120. *
  121. * If no arguments are passed, the default password rule configuration will be returned.
  122. *
  123. * @param static|callable|null $callback
  124. * @return static|void
  125. *
  126. * @throws \InvalidArgumentException
  127. */
  128. public static function defaults($callback = null)
  129. {
  130. if (is_null($callback)) {
  131. return static::default();
  132. }
  133. if (! is_callable($callback) && ! $callback instanceof static) {
  134. throw new InvalidArgumentException('The given callback should be callable or an instance of '.static::class);
  135. }
  136. static::$defaultCallback = $callback;
  137. }
  138. /**
  139. * Get the default configuration of the password rule.
  140. *
  141. * @return static
  142. */
  143. public static function default()
  144. {
  145. $password = is_callable(static::$defaultCallback)
  146. ? call_user_func(static::$defaultCallback)
  147. : static::$defaultCallback;
  148. return $password instanceof Rule ? $password : static::min(8);
  149. }
  150. /**
  151. * Get the default configuration of the password rule and mark the field as required.
  152. *
  153. * @return static
  154. */
  155. public static function required()
  156. {
  157. $password = static::default();
  158. $password->required = true;
  159. return $password;
  160. }
  161. /**
  162. * Get the default configuration of the password rule and mark the field as sometimes being required.
  163. *
  164. * @return static
  165. */
  166. public static function sometimes()
  167. {
  168. $password = static::default();
  169. $password->sometimes = true;
  170. return $password;
  171. }
  172. /**
  173. * Set the performing validator.
  174. *
  175. * @param \Illuminate\Contracts\Validation\Validator $validator
  176. * @return $this
  177. */
  178. public function setValidator($validator)
  179. {
  180. $this->validator = $validator;
  181. return $this;
  182. }
  183. /**
  184. * Set the data under validation.
  185. *
  186. * @param array $data
  187. * @return $this
  188. */
  189. public function setData($data)
  190. {
  191. $this->data = $data;
  192. return $this;
  193. }
  194. /**
  195. * Set the minimum size of the password.
  196. *
  197. * @param int $size
  198. * @return $this
  199. */
  200. public static function min($size)
  201. {
  202. return new static($size);
  203. }
  204. /**
  205. * Set the maximum size of the password.
  206. *
  207. * @param int $size
  208. * @return $this
  209. */
  210. public function max($size)
  211. {
  212. $this->max = $size;
  213. return $this;
  214. }
  215. /**
  216. * Ensures the password has not been compromised in data leaks.
  217. *
  218. * @param int $threshold
  219. * @return $this
  220. */
  221. public function uncompromised($threshold = 0)
  222. {
  223. $this->uncompromised = true;
  224. $this->compromisedThreshold = $threshold;
  225. return $this;
  226. }
  227. /**
  228. * Makes the password require at least one uppercase and one lowercase letter.
  229. *
  230. * @return $this
  231. */
  232. public function mixedCase()
  233. {
  234. $this->mixedCase = true;
  235. return $this;
  236. }
  237. /**
  238. * Makes the password require at least one letter.
  239. *
  240. * @return $this
  241. */
  242. public function letters()
  243. {
  244. $this->letters = true;
  245. return $this;
  246. }
  247. /**
  248. * Makes the password require at least one number.
  249. *
  250. * @return $this
  251. */
  252. public function numbers()
  253. {
  254. $this->numbers = true;
  255. return $this;
  256. }
  257. /**
  258. * Makes the password require at least one symbol.
  259. *
  260. * @return $this
  261. */
  262. public function symbols()
  263. {
  264. $this->symbols = true;
  265. return $this;
  266. }
  267. /**
  268. * Specify additional validation rules that should be merged with the default rules during validation.
  269. *
  270. * @param \Closure|string|array $rules
  271. * @return $this
  272. */
  273. public function rules($rules)
  274. {
  275. $this->customRules = Arr::wrap($rules);
  276. return $this;
  277. }
  278. /**
  279. * Determine if the validation rule passes.
  280. *
  281. * @param string $attribute
  282. * @param mixed $value
  283. * @return bool
  284. */
  285. public function passes($attribute, $value)
  286. {
  287. $this->messages = [];
  288. if (! $this->required && ! $this->sometimes && ! Arr::has($this->data ?? [], $attribute)) {
  289. return true;
  290. }
  291. if (blank($value) && ! $this->required && $this->validator?->hasRule($attribute, ['Nullable'])) {
  292. return true;
  293. }
  294. $validator = Validator::make(
  295. $this->data,
  296. [$attribute => [...$this]],
  297. $this->validator->customMessages,
  298. $this->validator->customAttributes
  299. )->after(function ($validator) use ($attribute, $value) {
  300. if (! is_string($value)) {
  301. return;
  302. }
  303. if ($this->mixedCase && ! preg_match('/(\p{Ll}+.*\p{Lu})|(\p{Lu}+.*\p{Ll})/u', $value)) {
  304. $validator->addFailure($attribute, 'password.mixed');
  305. }
  306. if ($this->letters && ! preg_match('/\pL/u', $value)) {
  307. $validator->addFailure($attribute, 'password.letters');
  308. }
  309. if ($this->symbols && ! preg_match('/\p{Z}|\p{S}|\p{P}/u', $value)) {
  310. $validator->addFailure($attribute, 'password.symbols');
  311. }
  312. if ($this->numbers && ! preg_match('/\pN/u', $value)) {
  313. $validator->addFailure($attribute, 'password.numbers');
  314. }
  315. });
  316. if ($validator->fails()) {
  317. return $this->fail($validator->messages()->all());
  318. }
  319. if ($this->uncompromised && ! Container::getInstance()->make(UncompromisedVerifier::class)->verify([
  320. 'value' => $value,
  321. 'threshold' => $this->compromisedThreshold,
  322. ])) {
  323. $validator->addFailure($attribute, 'password.uncompromised');
  324. return $this->fail($validator->messages()->all());
  325. }
  326. return true;
  327. }
  328. /**
  329. * Get the validation error message.
  330. *
  331. * @return array
  332. */
  333. public function message()
  334. {
  335. return $this->messages;
  336. }
  337. /**
  338. * Adds the given failures, and return false.
  339. *
  340. * @param array|string $messages
  341. * @return bool
  342. */
  343. protected function fail($messages)
  344. {
  345. $this->messages = array_merge($this->messages, Arr::wrap($messages));
  346. return false;
  347. }
  348. /**
  349. * Get information about the current state of the password validation rules.
  350. *
  351. * @return array
  352. */
  353. public function appliedRules()
  354. {
  355. return [
  356. 'min' => $this->min,
  357. 'max' => $this->max,
  358. 'mixedCase' => $this->mixedCase,
  359. 'letters' => $this->letters,
  360. 'numbers' => $this->numbers,
  361. 'symbols' => $this->symbols,
  362. 'uncompromised' => $this->uncompromised,
  363. 'compromisedThreshold' => $this->compromisedThreshold,
  364. 'customRules' => $this->customRules,
  365. ];
  366. }
  367. /**
  368. * Get an iterator for the password validation rules.
  369. *
  370. * @return \ArrayIterator<TKey, TValue>
  371. */
  372. public function getIterator(): Traversable
  373. {
  374. return new ArrayIterator([
  375. ...($this->required ? ['required'] : []),
  376. ...($this->sometimes ? ['sometimes'] : []),
  377. 'string',
  378. 'min:'.$this->min,
  379. ...($this->max ? ['max:'.$this->max] : []),
  380. ...$this->customRules,
  381. ]);
  382. }
  383. }