EloquentModelEncryptedCastingTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. <?php
  2. namespace Illuminate\Tests\Integration\Database;
  3. use Illuminate\Contracts\Encryption\Encrypter;
  4. use Illuminate\Database\Eloquent\Casts\ArrayObject;
  5. use Illuminate\Database\Eloquent\Casts\AsEncryptedArrayObject;
  6. use Illuminate\Database\Eloquent\Casts\AsEncryptedCollection;
  7. use Illuminate\Database\Eloquent\Model;
  8. use Illuminate\Database\Schema\Blueprint;
  9. use Illuminate\Support\Collection;
  10. use Illuminate\Support\Facades\Crypt;
  11. use Illuminate\Support\Facades\Schema;
  12. use stdClass;
  13. class EloquentModelEncryptedCastingTest extends DatabaseTestCase
  14. {
  15. protected $encrypter;
  16. protected function setUp(): void
  17. {
  18. parent::setUp();
  19. $this->encrypter = $this->mock(Encrypter::class);
  20. Crypt::swap($this->encrypter);
  21. Model::$encrypter = null;
  22. }
  23. protected function defineDatabaseMigrationsAfterDatabaseRefreshed()
  24. {
  25. Schema::create('encrypted_casts', function (Blueprint $table) {
  26. $table->increments('id');
  27. $table->string('secret', 1000)->nullable();
  28. $table->text('secret_array')->nullable();
  29. $table->text('secret_json')->nullable();
  30. $table->text('secret_object')->nullable();
  31. $table->text('secret_collection')->nullable();
  32. });
  33. }
  34. public function testStringsAreCastable()
  35. {
  36. $this->encrypter->expects('encrypt')
  37. ->with('this is a secret string', false)
  38. ->andReturn('encrypted-secret-string');
  39. $this->encrypter->expects('decrypt')
  40. ->with('encrypted-secret-string', false)
  41. ->andReturn('this is a secret string');
  42. /** @var \Illuminate\Tests\Integration\Database\EncryptedCast $subject */
  43. $subject = EncryptedCast::create([
  44. 'secret' => 'this is a secret string',
  45. ]);
  46. $this->assertSame('this is a secret string', $subject->secret);
  47. $this->assertDatabaseHas('encrypted_casts', [
  48. 'id' => $subject->id,
  49. 'secret' => 'encrypted-secret-string',
  50. ]);
  51. }
  52. public function testArraysAreCastable()
  53. {
  54. $this->encrypter->expects('encrypt')
  55. ->with('{"key1":"value1"}', false)
  56. ->andReturn('encrypted-secret-array-string');
  57. $this->encrypter->expects('decrypt')
  58. ->with('encrypted-secret-array-string', false)
  59. ->andReturn('{"key1":"value1"}');
  60. /** @var \Illuminate\Tests\Integration\Database\EncryptedCast $subject */
  61. $subject = EncryptedCast::create([
  62. 'secret_array' => ['key1' => 'value1'],
  63. ]);
  64. $this->assertSame(['key1' => 'value1'], $subject->secret_array);
  65. $this->assertDatabaseHas('encrypted_casts', [
  66. 'id' => $subject->id,
  67. 'secret_array' => 'encrypted-secret-array-string',
  68. ]);
  69. }
  70. public function testJsonIsCastable()
  71. {
  72. $this->encrypter->expects('encrypt')
  73. ->with('{"key1":"value1"}', false)
  74. ->andReturn('encrypted-secret-json-string');
  75. $this->encrypter->expects('decrypt')
  76. ->with('encrypted-secret-json-string', false)
  77. ->andReturn('{"key1":"value1"}');
  78. /** @var \Illuminate\Tests\Integration\Database\EncryptedCast $subject */
  79. $subject = EncryptedCast::create([
  80. 'secret_json' => ['key1' => 'value1'],
  81. ]);
  82. $this->assertSame(['key1' => 'value1'], $subject->secret_json);
  83. $this->assertDatabaseHas('encrypted_casts', [
  84. 'id' => $subject->id,
  85. 'secret_json' => 'encrypted-secret-json-string',
  86. ]);
  87. }
  88. public function testJsonAttributeIsCastable()
  89. {
  90. $this->encrypter->expects('encrypt')
  91. ->with('{"key1":"value1"}', false)
  92. ->andReturn('encrypted-secret-json-string');
  93. $this->encrypter->expects('decrypt')
  94. ->with('encrypted-secret-json-string', false)
  95. ->andReturn('{"key1":"value1"}');
  96. $this->encrypter->expects('encrypt')
  97. ->with('{"key1":"value1","key2":"value2"}', false)
  98. ->andReturn('encrypted-secret-json-string2');
  99. $this->encrypter->expects('decrypt')
  100. ->with('encrypted-secret-json-string2', false)
  101. ->andReturn('{"key1":"value1","key2":"value2"}');
  102. $subject = new EncryptedCast([
  103. 'secret_json' => ['key1' => 'value1'],
  104. ]);
  105. $subject->fill([
  106. 'secret_json->key2' => 'value2',
  107. ]);
  108. $subject->save();
  109. $this->assertSame(['key1' => 'value1', 'key2' => 'value2'], $subject->secret_json);
  110. $this->assertDatabaseHas('encrypted_casts', [
  111. 'id' => $subject->id,
  112. 'secret_json' => 'encrypted-secret-json-string2',
  113. ]);
  114. }
  115. public function testObjectIsCastable()
  116. {
  117. $object = new stdClass;
  118. $object->key1 = 'value1';
  119. $this->encrypter->expects('encrypt')
  120. ->with('{"key1":"value1"}', false)
  121. ->andReturn('encrypted-secret-object-string');
  122. $this->encrypter->expects('decrypt')
  123. ->twice()
  124. ->with('encrypted-secret-object-string', false)
  125. ->andReturn('{"key1":"value1"}');
  126. /** @var \Illuminate\Tests\Integration\Database\EncryptedCast $object */
  127. $object = EncryptedCast::create([
  128. 'secret_object' => $object,
  129. ]);
  130. $this->assertInstanceOf(stdClass::class, $object->secret_object);
  131. $this->assertSame('value1', $object->secret_object->key1);
  132. $this->assertDatabaseHas('encrypted_casts', [
  133. 'id' => $object->id,
  134. 'secret_object' => 'encrypted-secret-object-string',
  135. ]);
  136. }
  137. public function testCollectionIsCastable()
  138. {
  139. $this->encrypter->expects('encrypt')
  140. ->with('{"key1":"value1"}', false)
  141. ->andReturn('encrypted-secret-collection-string');
  142. $this->encrypter->expects('decrypt')
  143. ->twice()
  144. ->with('encrypted-secret-collection-string', false)
  145. ->andReturn('{"key1":"value1"}');
  146. /** @var \Illuminate\Tests\Integration\Database\EncryptedCast $subject */
  147. $subject = EncryptedCast::create([
  148. 'secret_collection' => new Collection(['key1' => 'value1']),
  149. ]);
  150. $this->assertInstanceOf(Collection::class, $subject->secret_collection);
  151. $this->assertSame('value1', $subject->secret_collection->get('key1'));
  152. $this->assertDatabaseHas('encrypted_casts', [
  153. 'id' => $subject->id,
  154. 'secret_collection' => 'encrypted-secret-collection-string',
  155. ]);
  156. }
  157. public function testAsEncryptedCollection()
  158. {
  159. $this->encrypter->expects('encryptString')
  160. ->twice()
  161. ->with('{"key1":"value1"}')
  162. ->andReturn('encrypted-secret-collection-string-1');
  163. $this->encrypter->expects('encryptString')
  164. ->times(12)
  165. ->with('{"key1":"value1","key2":"value2"}')
  166. ->andReturn('encrypted-secret-collection-string-2');
  167. $this->encrypter->expects('decryptString')
  168. ->once()
  169. ->with('encrypted-secret-collection-string-2')
  170. ->andReturn('{"key1":"value1","key2":"value2"}');
  171. $subject = new EncryptedCast;
  172. $subject->mergeCasts(['secret_collection' => AsEncryptedCollection::class]);
  173. $subject->secret_collection = new Collection(['key1' => 'value1']);
  174. $subject->secret_collection->put('key2', 'value2');
  175. $subject->save();
  176. $this->assertInstanceOf(Collection::class, $subject->secret_collection);
  177. $this->assertSame('value1', $subject->secret_collection->get('key1'));
  178. $this->assertSame('value2', $subject->secret_collection->get('key2'));
  179. $this->assertDatabaseHas('encrypted_casts', [
  180. 'id' => $subject->id,
  181. 'secret_collection' => 'encrypted-secret-collection-string-2',
  182. ]);
  183. $subject = $subject->fresh();
  184. $this->assertInstanceOf(Collection::class, $subject->secret_collection);
  185. $this->assertSame('value1', $subject->secret_collection->get('key1'));
  186. $this->assertSame('value2', $subject->secret_collection->get('key2'));
  187. $subject->secret_collection = null;
  188. $subject->save();
  189. $this->assertNull($subject->secret_collection);
  190. $this->assertDatabaseHas('encrypted_casts', [
  191. 'id' => $subject->id,
  192. 'secret_collection' => null,
  193. ]);
  194. $this->assertNull($subject->fresh()->secret_collection);
  195. }
  196. public function testAsEncryptedArrayObject()
  197. {
  198. $this->encrypter->expects('encryptString')
  199. ->once()
  200. ->with('{"key1":"value1"}')
  201. ->andReturn('encrypted-secret-array-string-1');
  202. $this->encrypter->expects('decryptString')
  203. ->once()
  204. ->with('encrypted-secret-array-string-1')
  205. ->andReturn('{"key1":"value1"}');
  206. $this->encrypter->expects('encryptString')
  207. ->times(12)
  208. ->with('{"key1":"value1","key2":"value2"}')
  209. ->andReturn('encrypted-secret-array-string-2');
  210. $this->encrypter->expects('decryptString')
  211. ->once()
  212. ->with('encrypted-secret-array-string-2')
  213. ->andReturn('{"key1":"value1","key2":"value2"}');
  214. $subject = new EncryptedCast;
  215. $subject->mergeCasts(['secret_array' => AsEncryptedArrayObject::class]);
  216. $subject->secret_array = ['key1' => 'value1'];
  217. $subject->secret_array['key2'] = 'value2';
  218. $subject->save();
  219. $this->assertInstanceOf(ArrayObject::class, $subject->secret_array);
  220. $this->assertSame('value1', $subject->secret_array['key1']);
  221. $this->assertSame('value2', $subject->secret_array['key2']);
  222. $this->assertDatabaseHas('encrypted_casts', [
  223. 'id' => $subject->id,
  224. 'secret_array' => 'encrypted-secret-array-string-2',
  225. ]);
  226. $subject = $subject->fresh();
  227. $this->assertInstanceOf(ArrayObject::class, $subject->secret_array);
  228. $this->assertSame('value1', $subject->secret_array['key1']);
  229. $this->assertSame('value2', $subject->secret_array['key2']);
  230. $subject->secret_array = null;
  231. $subject->save();
  232. $this->assertNull($subject->secret_array);
  233. $this->assertDatabaseHas('encrypted_casts', [
  234. 'id' => $subject->id,
  235. 'secret_array' => null,
  236. ]);
  237. $this->assertNull($subject->fresh()->secret_array);
  238. }
  239. public function testCustomEncrypterCanBeSpecified()
  240. {
  241. $customEncrypter = $this->mock(Encrypter::class);
  242. $this->assertNull(Model::$encrypter);
  243. Model::encryptUsing($customEncrypter);
  244. $this->assertSame($customEncrypter, Model::$encrypter);
  245. $this->encrypter->expects('encrypt')
  246. ->never();
  247. $this->encrypter->expects('decrypt')
  248. ->never();
  249. $customEncrypter->expects('encrypt')
  250. ->with('this is a secret string', false)
  251. ->andReturn('encrypted-secret-string');
  252. $customEncrypter->expects('decrypt')
  253. ->with('encrypted-secret-string', false)
  254. ->andReturn('this is a secret string');
  255. /** @var \Illuminate\Tests\Integration\Database\EncryptedCast $subject */
  256. $subject = EncryptedCast::create([
  257. 'secret' => 'this is a secret string',
  258. ]);
  259. $this->assertSame('this is a secret string', $subject->secret);
  260. $this->assertDatabaseHas('encrypted_casts', [
  261. 'id' => $subject->id,
  262. 'secret' => 'encrypted-secret-string',
  263. ]);
  264. }
  265. }
  266. /**
  267. * @property $secret
  268. * @property $secret_array
  269. * @property $secret_json
  270. * @property $secret_object
  271. * @property $secret_collection
  272. */
  273. class EncryptedCast extends Model
  274. {
  275. public $timestamps = false;
  276. protected $guarded = [];
  277. public $casts = [
  278. 'secret' => 'encrypted',
  279. 'secret_array' => 'encrypted:array',
  280. 'secret_json' => 'encrypted:json',
  281. 'secret_object' => 'encrypted:object',
  282. 'secret_collection' => 'encrypted:collection',
  283. ];
  284. }