PasswordableBehaviorTest.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. <?php
  2. namespace Tools\Test\TestCase\Model\Behavior;
  3. use Cake\Auth\PasswordHasherFactory;
  4. use Cake\Core\Configure;
  5. use Cake\Network\Request;
  6. use Cake\ORM\TableRegistry;
  7. use Cake\Routing\Router;
  8. use Tools\TestSuite\TestCase;
  9. class PasswordableBehaviorTest extends TestCase {
  10. /**
  11. * @var array
  12. */
  13. public $fixtures = [
  14. 'plugin.tools.tools_users', 'plugin.tools.roles',
  15. ];
  16. /**
  17. * @var \TestApp\Model\Table\ToolsUsersTable
  18. */
  19. public $Users;
  20. /**
  21. * SetUp method
  22. *
  23. * @return void
  24. */
  25. public function setUp() {
  26. parent::setUp();
  27. Configure::write('App.namespace', 'TestApp');
  28. Configure::delete('Passwordable');
  29. Configure::write('Passwordable.auth', 'AuthTest');
  30. $this->Users = TableRegistry::get('ToolsUsers');
  31. $this->hasher = PasswordHasherFactory::build('Default');
  32. Router::setRequestInfo(new Request());
  33. }
  34. public function tearDown() {
  35. TableRegistry::clear();
  36. parent::tearDown();
  37. }
  38. /**
  39. * Make sure validation is triggered correctly
  40. *
  41. * @return void
  42. */
  43. public function testValidate() {
  44. $this->Users->addBehavior('Tools.Passwordable', []);
  45. $user = $this->Users->newEntity();
  46. $data = [
  47. 'pwd' => '123456',
  48. ];
  49. $this->Users->patchEntity($user, $data);
  50. $is = $this->Users->save($user);
  51. $this->assertFalse($is);
  52. $this->assertEquals(['pwd_repeat'], array_keys($user->errors()));
  53. $user = $this->Users->newEntity();
  54. $data = [
  55. 'pwd' => '1234ab',
  56. 'pwd_repeat' => '123456'
  57. ];
  58. $this->Users->patchEntity($user, $data);
  59. $is = $this->Users->save($user);
  60. $this->assertFalse($is);
  61. $this->assertEquals(['validateIdentical' => __d('tools', 'valErrPwdNotMatch')], $user->errors()['pwd_repeat']);
  62. $user = $this->Users->newEntity();
  63. $data = [
  64. 'pwd' => '123456',
  65. 'pwd_repeat' => '123456'
  66. ];
  67. $this->Users->patchEntity($user, $data);
  68. $is = $this->Users->save($user);
  69. $this->assertTrue(!empty($is));
  70. }
  71. /**
  72. * Test that confirm false does not require confirmation
  73. *
  74. * @return void
  75. */
  76. public function testValidateNoConfirm() {
  77. $this->Users->addBehavior('Tools.Passwordable', ['confirm' => false]);
  78. $user = $this->Users->newEntity();
  79. $data = [
  80. 'pwd' => '123456',
  81. ];
  82. $this->Users->patchEntity($user, $data);
  83. $is = $this->Users->save($user);
  84. //debug($is);
  85. $this->assertTrue(!empty($is));
  86. }
  87. /**
  88. * Trigger validation and update process if no values are entered but are required
  89. *
  90. * @return void
  91. */
  92. public function testValidateRequired() {
  93. $this->Users->addBehavior('Tools.Passwordable');
  94. $user = $this->Users->newEntity();
  95. $data = [
  96. 'pwd' => '',
  97. 'pwd_repeat' => ''
  98. ];
  99. $this->Users->patchEntity($user, $data);
  100. $is = $this->Users->save($user);
  101. $this->assertFalse($is);
  102. $this->assertEquals(['pwd', 'pwd_repeat'], array_keys($user->errors()));
  103. }
  104. /**
  105. * Validation and update process gets skipped if no values are entered
  106. *
  107. * @return void
  108. */
  109. public function testValidateNotRequired() {
  110. $this->Users->addBehavior('Tools.Passwordable', ['require' => false]);
  111. $user = $this->Users->newEntity();
  112. $data = [
  113. 'name' => 'foo', // we need at least one field besides the password on CREATE
  114. 'pwd' => '',
  115. 'pwd_repeat' => ''
  116. ];
  117. $this->Users->patchEntity($user, $data);
  118. $is = $this->Users->save($user);
  119. $this->assertTrue((bool)$is);
  120. $this->assertEquals(['name', 'id'], $is->visibleProperties());
  121. $id = $user->id;
  122. $user = $this->Users->newEntity();
  123. $data = [
  124. 'id' => $id,
  125. 'pwd' => '',
  126. 'pwd_repeat' => ''
  127. ];
  128. $this->Users->patchEntity($user, $data);
  129. $is = $this->Users->save($user);
  130. $this->assertTrue((bool)$is);
  131. $this->assertEquals(['id'], $is->visibleProperties());
  132. }
  133. /**
  134. * PasswordableBehaviorTest::testValidateEmptyWithCurrentPassword()
  135. *
  136. * @return void
  137. */
  138. public function testValidateEmptyWithCurrentPassword() {
  139. $this->Users->addBehavior('Tools.Passwordable', ['current' => true]);
  140. $user = $this->Users->newEntity();
  141. $data = [
  142. 'id' => '123',
  143. 'pwd' => '',
  144. 'pwd_repeat' => '',
  145. 'pwd_current' => '123456',
  146. ];
  147. $this->Users->patchEntity($user, $data);
  148. $is = $this->Users->save($user);
  149. //debug($user->errors());
  150. $this->assertFalse($is);
  151. $this->assertEquals(['pwd', 'pwd_repeat', 'pwd_current'], array_keys($user->errors()));
  152. $this->tearDown();
  153. $this->setUp();
  154. $this->Users->removeBehavior('Passwordable');
  155. $this->Users->addBehavior('Tools.Passwordable', ['require' => false, 'current' => true]);
  156. $user = $this->Users->newEntity();
  157. $data = [
  158. 'name' => 'foo',
  159. 'pwd' => '',
  160. 'pwd_repeat' => '',
  161. 'pwd_current' => '',
  162. ];
  163. $this->Users->patchEntity($user, $data);
  164. $is = $this->Users->save($user);
  165. $this->assertTrue(!empty($is));
  166. }
  167. /**
  168. * Test aliases for field names
  169. *
  170. * @return void
  171. */
  172. public function testDifferentFieldNames() {
  173. $this->Users->addBehavior('Tools.Passwordable', [
  174. 'formField' => 'passw',
  175. 'formFieldRepeat' => 'passw_repeat',
  176. 'formFieldCurrent' => 'passw_current',
  177. ]);
  178. $user = $this->Users->newEntity();
  179. $data = [
  180. 'passw' => '123456',
  181. 'passw_repeat' => '123456'
  182. ];
  183. $this->Users->patchEntity($user, $data);
  184. //debug($this->Users->data);
  185. $is = $this->Users->save($user);
  186. $this->assertTrue(!empty($is));
  187. }
  188. /**
  189. * Assert that allowSame false does not allow storing the same password as previously entered
  190. *
  191. * @return void
  192. */
  193. public function testNotSame() {
  194. $user = $this->Users->newEntity();
  195. $data = [
  196. 'name' => 'admin',
  197. 'password' => $this->hasher->hash('somepwd'),
  198. 'role_id' => '1'
  199. ];
  200. $this->Users->patchEntity($user, $data);
  201. $result = $this->Users->save($user);
  202. $this->assertTrue((bool)$result);
  203. $userCopy = clone($user);
  204. $this->Users->addBehavior('Tools.Passwordable', [
  205. 'formField' => 'passw',
  206. 'formFieldRepeat' => 'passw_repeat',
  207. 'formFieldCurrent' => 'passw_current',
  208. 'allowSame' => false,
  209. 'current' => true,
  210. ]);
  211. $user = clone($userCopy);
  212. $data = [
  213. 'passw_current' => 'something',
  214. 'passw' => 'somepwd',
  215. 'passw_repeat' => 'somepwd'
  216. ];
  217. $this->Users->patchEntity($user, $data);
  218. $is = $this->Users->save($user);
  219. $this->assertFalse($is);
  220. $user = clone($userCopy);
  221. $data = [
  222. 'passw_current' => 'somepwd',
  223. 'passw' => 'newpwd',
  224. 'passw_repeat' => 'newpwd'
  225. ];
  226. $this->Users->patchEntity($user, $data);
  227. $is = $this->Users->save($user);
  228. $this->assertTrue(!empty($is));
  229. }
  230. /**
  231. * Assert that allowSame false does not allow storing the same password as previously entered
  232. *
  233. * @return void
  234. */
  235. public function testNotSameWithoutCurrentField() {
  236. $this->Users->addBehavior('Tools.Passwordable', [
  237. 'formField' => 'passw',
  238. 'formFieldRepeat' => 'passw_repeat',
  239. 'allowSame' => false,
  240. 'current' => false
  241. ]);
  242. $user = $this->Users->newEntity();
  243. $data = [
  244. 'passw' => 'somepwd',
  245. 'passw_repeat' => 'somepwd'
  246. ];
  247. $this->Users->patchEntity($user, $data);
  248. $is = $this->Users->save($user);
  249. $this->assertTrue((bool)$is);
  250. $userCopy = clone($user);
  251. $uid = $user->id;
  252. $user = clone($userCopy);
  253. $data = [
  254. 'passw' => 'somepwd',
  255. 'passw_repeat' => 'somepwd'
  256. ];
  257. $this->Users->patchEntity($user, $data);
  258. $is = $this->Users->save($user);
  259. $this->assertFalse((bool)$is);
  260. $user = clone($userCopy);
  261. $data = [
  262. 'passw' => 'newpwd',
  263. 'passw_repeat' => 'newpwd'
  264. ];
  265. $this->Users->patchEntity($user, $data);
  266. $is = $this->Users->save($user);
  267. $this->assertTrue((bool)$is);
  268. }
  269. /**
  270. * Assert that on edit it does not wrongly pass validation (require => false)
  271. *
  272. * @return void
  273. */
  274. public function testRequireFalse() {
  275. $this->Users->addBehavior('Tools.Passwordable', [
  276. 'formField' => 'passw',
  277. 'formFieldRepeat' => 'passw_repeat',
  278. 'require' => false
  279. ]);
  280. $user = $this->Users->newEntity();
  281. $data = [
  282. 'passw' => 'somepwd',
  283. 'passw_repeat' => 'somepwd'
  284. ];
  285. $this->Users->patchEntity($user, $data);
  286. $is = $this->Users->save($user);
  287. $this->assertTrue((bool)$is);
  288. $userCopy = clone($user);
  289. $user = clone($userCopy);
  290. $data = [
  291. 'passw' => '',
  292. 'passw_repeat' => ''
  293. ];
  294. $this->Users->patchEntity($user, $data);
  295. $is = $this->Users->save($user);
  296. $this->assertTrue((bool)$is);
  297. //debug($user->errors());
  298. $user = clone($userCopy);
  299. $data = [
  300. 'passw' => 'somepwd2',
  301. 'passw_repeat' => ''
  302. ];
  303. $this->Users->patchEntity($user, $data);
  304. $is = $this->Users->save($user);
  305. $this->assertFalse((bool)$is);
  306. $user = clone($userCopy);
  307. $data = [
  308. 'passw' => 'somepwd2',
  309. 'passw_repeat' => 'somepwd2'
  310. ];
  311. $this->Users->patchEntity($user, $data);
  312. $is = $this->Users->save($user);
  313. $this->assertTrue((bool)$is);
  314. }
  315. /**
  316. * Needs faking of pwd check...
  317. *
  318. * @return void
  319. */
  320. public function testValidateCurrent() {
  321. $this->assertFalse($this->Users->behaviors()->has('Passwordable'));
  322. $user = $this->Users->newEntity();
  323. $data = [
  324. 'name' => 'xyz',
  325. 'password' => $this->hasher->hash('somepwd')];
  326. $this->Users->patchEntity($user, $data);
  327. $result = $this->Users->save($user);
  328. $this->assertTrue(!empty($result));
  329. $userCopy = clone($user);
  330. $uid = $user->id;
  331. $this->Users->removeBehavior('Passwordable');
  332. $this->Users->addBehavior('Tools.Passwordable', ['current' => true]);
  333. $user = clone($userCopy);
  334. $data = [
  335. 'pwd' => '123456',
  336. 'pwd_repeat' => '12345678',
  337. ];
  338. $this->Users->patchEntity($user, $data);
  339. $this->assertTrue($this->Users->behaviors()->has('Passwordable'));
  340. $is = $this->Users->save($user);
  341. $this->assertFalse($is);
  342. $user = clone($userCopy);
  343. $data = [
  344. 'pwd_current' => 'somepwdx',
  345. 'pwd' => '123456',
  346. 'pwd_repeat' => '123456'
  347. ];
  348. $this->Users->patchEntity($user, $data);
  349. $is = $this->Users->save($user);
  350. $this->assertFalse($is);
  351. $user = clone($userCopy);
  352. $data = [
  353. 'name' => 'Yeah',
  354. 'pwd_current' => 'somepwd',
  355. 'pwd' => '123456',
  356. 'pwd_repeat' => '123456'
  357. ];
  358. $user->accessible('*', false); // Mark all properties as protected
  359. $user->accessible(['id'], true); // Allow id to be accessible by default
  360. $user = $this->Users->patchEntity($user, $data, ['fieldList' => ['id']]);
  361. $this->assertNotSame($is['password'], $user['password']);
  362. $this->assertTrue($user->dirty('pwd'));
  363. $options = ['validate' => true];
  364. $is = $this->Users->save($user, $options);
  365. $this->assertTrue(!empty($is));
  366. $user = $this->Users->get($uid);
  367. // The password is updated, the name not
  368. $this->assertSame($is['password'], $user['password']);
  369. $this->assertSame('xyz', $user['name']);
  370. // Proof that we manually need to add pwd, pwd_repeat etc due to a bug in CakePHP<=2.4 allowing behaviors to only modify saving,
  371. // not validating of additional whitelist fields. Validation for those will be just skipped, no matter what the behavior tries
  372. // to set.
  373. $user = clone($userCopy);
  374. $data = [
  375. 'name' => 'Yeah',
  376. 'pwd_current' => '123', // Obviously wrong
  377. 'pwd' => 'some', // Too short
  378. 'pwd_repeat' => 'somex' // Don't match
  379. ];
  380. $user->accessible('*', false); // Mark all properties as protected
  381. $user->accessible(['id', 'name'], true);
  382. $this->Users->patchEntity($user, $data, ['fieldList' => ['id', 'name']]);
  383. // Test whitelist setting - only "password" gets auto-added, pwd, pwd_repeat etc need to be added manually
  384. // NOTE that I had to remove the code for adding those fields from the behavior (as it was not functional)
  385. // So of course, this won't work now as expected. But feel free to try to add them in the behavior. Results will be the same.
  386. $options = ['validate' => true];
  387. $is = $this->Users->save($user, $options);
  388. // Validation errors triggered - as expected
  389. $this->assertFalse($is);
  390. $this->assertSame(['pwd', 'pwd_repeat', 'pwd_current'], array_keys($user->errors()));
  391. }
  392. /**
  393. * @return void
  394. */
  395. public function testPatchWithFieldList() {
  396. $this->Users->addBehavior('Tools.Passwordable', [
  397. 'formField' => 'pwd',
  398. 'formFieldRepeat' => 'pwd_repeat',
  399. 'current' => false,
  400. 'passwordHasher' => 'Complex',
  401. ]);
  402. $user = $this->Users->newEntity();
  403. $data = [
  404. 'pwd' => 'somepwd',
  405. 'pwd_repeat' => 'somepwd'
  406. ];
  407. $user->accessible('*', false); // Mark all properties as protected
  408. $user->accessible(['id'], true);
  409. $this->Users->patchEntity($user, $data, ['fieldList' => ['id']]);
  410. $result = $this->Users->save($user);
  411. $this->assertTrue((bool)$result);
  412. }
  413. /**
  414. * @return void
  415. */
  416. public function testPatchWithoutFieldList() {
  417. $this->Users->addBehavior('Tools.Passwordable', [
  418. 'formField' => 'pwd',
  419. 'formFieldRepeat' => 'pwd_repeat',
  420. 'current' => false,
  421. 'passwordHasher' => 'Complex',
  422. 'forceFieldList' => true
  423. ]);
  424. $user = $this->Users->newEntity();
  425. $data = [
  426. 'name' => 'x',
  427. 'pwd' => 'somepwd',
  428. 'pwd_repeat' => 'somepwd'
  429. ];
  430. $user->accessible('*', false); // Mark all properties as protected
  431. $user->accessible(['id'], true);
  432. $user = $this->Users->patchEntity($user, $data);
  433. $result = $this->Users->save($user);
  434. $this->assertTrue((bool)$result);
  435. $savedUser = $this->Users->get($user->id);
  436. $this->assertSame($data['name'], $savedUser->name);
  437. $this->assertSame($user->password, $savedUser->password);
  438. }
  439. /**
  440. * PasswordableBehaviorTest::testBlowfish()
  441. *
  442. * @return void
  443. */
  444. public function testBlowfish() {
  445. $this->Users->addBehavior('Tools.Passwordable', [
  446. 'allowSame' => false,
  447. 'current' => false,
  448. 'authType' => 'Blowfish',
  449. ]);
  450. $user = $this->Users->newEntity();
  451. $data = [
  452. 'pwd' => 'somepwd',
  453. 'pwd_repeat' => 'somepwd'
  454. ];
  455. $this->Users->patchEntity($user, $data);
  456. $result = $this->Users->save($user);
  457. $this->assertTrue((bool)$result);
  458. $userCopy = clone($user);
  459. $uid = (string)$user->id;
  460. $this->Users->removeBehavior('Passwordable');
  461. $this->Users->addBehavior('Tools.Passwordable', ['current' => true]);
  462. // Without the current password it will not continue
  463. $data = [
  464. 'pwd' => '123456',
  465. 'pwd_repeat' => '12345678',
  466. ];
  467. $this->Users->patchEntity($user, $data);
  468. $this->assertTrue($this->Users->behaviors()->has('Passwordable'));
  469. $result = $this->Users->save($user);
  470. $this->assertFalse($result);
  471. // Without the correct current password it will not continue
  472. $user = clone($userCopy);
  473. $data = [
  474. 'pwd_current' => 'somepwdx',
  475. 'pwd' => '123456',
  476. 'pwd_repeat' => '123456'
  477. ];
  478. $this->Users->patchEntity($user, $data);
  479. $result = $this->Users->save($user);
  480. $this->assertFalse($result);
  481. // Now it will
  482. $user = clone($userCopy);
  483. $data = [
  484. 'pwd_current' => 'somepwd',
  485. 'pwd' => '123456',
  486. 'pwd_repeat' => '123456'
  487. ];
  488. $this->Users->patchEntity($user, $data);
  489. $result = $this->Users->save($user);
  490. $this->assertTrue((bool)$result);
  491. }
  492. /**
  493. * Tests needsPasswordRehash()
  494. *
  495. * @return void
  496. */
  497. public function testNeedsPasswordRehash() {
  498. $this->Users->addBehavior('Tools.Passwordable', [
  499. 'allowSame' => false,
  500. 'current' => false,
  501. 'authType' => 'Blowfish',
  502. 'passwordHasher' => 'Default'
  503. ]);
  504. $hash = password_hash('foobar', PASSWORD_BCRYPT);
  505. $result = $this->Users->needsPasswordRehash($hash);
  506. $this->assertFalse($result);
  507. $hash = sha1('foobar');
  508. $result = $this->Users->needsPasswordRehash($hash);
  509. $this->assertTrue($result);
  510. }
  511. /**
  512. * Tests needsPasswordRehash()
  513. *
  514. * @return void
  515. */
  516. public function testNeedsPasswordRehashWithNotSupportedHasher() {
  517. $this->Users->addBehavior('Tools.Passwordable', [
  518. 'allowSame' => false,
  519. 'current' => false,
  520. 'authType' => 'Blowfish',
  521. ]);
  522. $hash = password_hash('foobar', PASSWORD_BCRYPT);
  523. $result = $this->Users->needsPasswordRehash($hash);
  524. $this->assertFalse($result);
  525. $this->Users->removeBehavior('Passwordable');
  526. $this->Users->addBehavior('Tools.Passwordable', [
  527. 'allowSame' => false,
  528. 'current' => false,
  529. 'authType' => 'Blowfish',
  530. 'passwordHasher' => 'Default'
  531. ]);
  532. $hash = password_hash('foobar', PASSWORD_BCRYPT);
  533. $result = $this->Users->needsPasswordRehash($hash);
  534. $this->assertFalse($result);
  535. }
  536. /**
  537. * PasswordableBehaviorTest::testSettings()
  538. *
  539. * @return void
  540. */
  541. public function testSettings() {
  542. // Pwd min and max length
  543. $this->Users->addBehavior('Tools.Passwordable', [
  544. 'allowSame' => false,
  545. 'current' => false,
  546. 'minLength' => 3,
  547. 'maxLength' => 6,
  548. ]);
  549. $user = $this->Users->newEntity();
  550. $data = [
  551. 'pwd' => '123',
  552. 'pwd_repeat' => '123'
  553. ];
  554. $this->Users->patchEntity($user, $data);
  555. $result = $this->Users->save($user);
  556. $this->assertTrue((bool)$result);
  557. $user = $this->Users->newEntity();
  558. $data = [
  559. 'pwd' => '12345678',
  560. 'pwd_repeat' => '12345678'
  561. ];
  562. $this->Users->patchEntity($user, $data);
  563. $result = $this->Users->save($user);
  564. $this->assertFalse($result);
  565. $expected = [
  566. 'pwd' => ['between' => __d('tools', 'valErrBetweenCharacters {0} {1}', 3, 6)],
  567. ];
  568. $this->assertEquals($expected, $user->errors());
  569. }
  570. /**
  571. * Test that validate false also works.
  572. *
  573. * @return void
  574. */
  575. public function testSaveWithValidateFalse() {
  576. $this->Users->addBehavior('Tools.Passwordable');
  577. $user = $this->Users->newEntity();
  578. $data = [
  579. 'pwd' => '123',
  580. ];
  581. $this->Users->patchEntity($user, $data, ['validate' => false]);
  582. $result = $this->Users->save($user);
  583. $this->assertTrue((bool)$result);
  584. $uid = (string)$user->id;
  585. $hash = $user['password'];
  586. $data = [
  587. 'pwd' => '1234'
  588. ];
  589. $this->Users->patchEntity($user, $data, ['validate' => false]);
  590. $result2 = $this->Users->save($user);
  591. $this->assertTrue((bool)$result2);
  592. $hash2 = $user['password'];
  593. $this->assertTrue($hash !== $hash2);
  594. }
  595. /**
  596. * PasswordableBehaviorTest::testValidateCustomRule()
  597. *
  598. * @return void
  599. */
  600. public function testValidateCustomRule() {
  601. $rules = [
  602. 'validateCustom' => [
  603. 'rule' => ['custom', '#^[a-z0-9]+$#'], // Just a test example, never use this regexp!
  604. 'message' => 'Foo Bar',
  605. 'last' => true,
  606. ],
  607. 'validateCustomExt' => [
  608. 'rule' => ['custom', '#^[a-z]+$#'], // Just a test example, never use this regexp!
  609. 'message' => 'Foo Bar Ext',
  610. 'last' => true,
  611. ]
  612. ];
  613. $this->Users->addBehavior('Tools.Passwordable', [
  614. 'customValidation' => $rules]);
  615. $user = $this->Users->newEntity();
  616. $data = [
  617. 'pwd' => '%123456',
  618. 'pwd_repeat' => '%123456'
  619. ];
  620. $this->Users->patchEntity($user, $data);
  621. $is = $this->Users->save($user);
  622. $this->assertFalse($is);
  623. $result = $user->errors();
  624. $expected = ['pwd' => ['validateCustom' => 'Foo Bar']];
  625. $this->assertSame($expected, $result);
  626. $user = $this->Users->newEntity();
  627. $data = [
  628. 'pwd' => 'abc123',
  629. 'pwd_repeat' => 'abc123'
  630. ];
  631. $this->Users->patchEntity($user, $data);
  632. $is = $this->Users->save($user);
  633. $this->assertFalse($is);
  634. $result = $user->errors();
  635. $expected = ['pwd' => ['validateCustomExt' => 'Foo Bar Ext']];
  636. $this->assertSame($expected, $result);
  637. $user = $this->Users->newEntity();
  638. $data = [
  639. 'pwd' => 'abcdef',
  640. 'pwd_repeat' => 'abcdef'
  641. ];
  642. $this->Users->patchEntity($user, $data);
  643. $is = $this->Users->save($user);
  644. $this->assertTrue((bool)$is);
  645. }
  646. }