Skip to content

Commit 6001f71

Browse files
committed
fix for relationship retrieval
1 parent 76bc252 commit 6001f71

File tree

2 files changed

+140
-6
lines changed

2 files changed

+140
-6
lines changed

src/Encryptable.php

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ protected static function bootEncryptable()
3131
static::retrieved(function ($model) {
3232
$model->decryptAttributes();
3333
});
34+
35+
// Also decrypt when models are created from arrays (like from relationships)
36+
static::creating(function ($model) {
37+
// Don't decrypt on creating, just mark that we need to check later
38+
});
3439
}
3540

3641
/**
@@ -209,13 +214,23 @@ public function getAttribute($key)
209214
$encryptableAttributes = $this->getEncryptableAttributes();
210215

211216
if (in_array($key, $encryptableAttributes) && isset($this->attributes[$key])) {
212-
try {
213-
// Ensure the attribute is decrypted
214-
return $this->decryptValue($this->attributes[$key]);
215-
} catch (\Exception $e) {
216-
// If decryption fails, return the attribute as-is
217-
return $this->attributes[$key];
217+
// Check if the value appears to be encrypted
218+
if (is_string($this->attributes[$key]) && !empty($this->attributes[$key])) {
219+
if ($this->isValueEncrypted($this->attributes[$key])) {
220+
try {
221+
$decrypted = $this->decryptValue($this->attributes[$key]);
222+
// Store the decrypted value back to avoid repeated decryption
223+
$this->attributes[$key] = $decrypted;
224+
return $decrypted;
225+
} catch (\Exception $e) {
226+
// If decryption fails, return the attribute as-is
227+
return $this->attributes[$key];
228+
}
229+
}
218230
}
231+
232+
// If not encrypted, return as-is
233+
return $this->attributes[$key];
219234
}
220235

221236
return parent::getAttribute($key);

tests/Unit/EncryptableTest.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,115 @@ public function testToArrayDecryptsAllAttributes()
282282
$this->assertEquals('123-456-7890', $array['phone']);
283283
$this->assertEquals('123 Main St', $array['address']);
284284
}
285+
286+
public function testCompactDecryptionFromRawDatabase()
287+
{
288+
// Create a test table
289+
$this->app['db']->connection()->getSchemaBuilder()->create('test_models', function ($table) {
290+
$table->increments('id');
291+
$table->text('address')->nullable();
292+
$table->timestamps();
293+
});
294+
295+
$encryptionService = $this->app->make(EncryptionService::class);
296+
297+
// Use reflection to access compact encryption
298+
$reflection = new \ReflectionClass($encryptionService);
299+
$compactEncryptMethod = $reflection->getMethod('compactEncrypt');
300+
$compactEncryptMethod->setAccessible(true);
301+
302+
// Create a compact encrypted value similar to the user's issue
303+
$originalValue = '123 Main Street';
304+
$compactEncrypted = $compactEncryptMethod->invokeArgs($encryptionService, [$originalValue]);
305+
306+
// Manually insert into database with compact encryption
307+
$id = $this->app['db']->connection()->table('test_models')->insertGetId([
308+
'address' => $compactEncrypted,
309+
'created_at' => now(),
310+
'updated_at' => now(),
311+
]);
312+
313+
// Retrieve using Eloquent - this should trigger decryption
314+
$model = TestEncryptableModel::find($id);
315+
316+
// Check that the value is properly decrypted when accessed
317+
$this->assertEquals($originalValue, $model->address);
318+
319+
// Check that accessing via __get also works
320+
$this->assertEquals($originalValue, $model->__get('address'));
321+
322+
// Check that toArray() also works
323+
$array = $model->toArray();
324+
$this->assertEquals($originalValue, $array['address']);
325+
326+
// Verify the raw database value is still compact encrypted
327+
$rawData = $this->app['db']->connection()->table('test_models')->where('id', $id)->first();
328+
$this->assertTrue(strpos($rawData->address, 'c:') === 0);
329+
$this->assertNotEquals($originalValue, $rawData->address);
330+
}
331+
332+
public function testDecryptionWorksForRelationshipLoadedModels()
333+
{
334+
// Create tables for user and addresses
335+
$this->app['db']->connection()->getSchemaBuilder()->create('users', function ($table) {
336+
$table->increments('id');
337+
$table->string('name');
338+
$table->timestamps();
339+
});
340+
341+
$this->app['db']->connection()->getSchemaBuilder()->create('user_addresses', function ($table) {
342+
$table->increments('id');
343+
$table->integer('user_id');
344+
$table->text('street_1')->nullable();
345+
$table->text('street_2')->nullable();
346+
$table->timestamps();
347+
});
348+
349+
$encryptionService = $this->app->make(EncryptionService::class);
350+
$reflection = new \ReflectionClass($encryptionService);
351+
$compactEncryptMethod = $reflection->getMethod('compactEncrypt');
352+
$compactEncryptMethod->setAccessible(true);
353+
354+
// Create test data
355+
$userId = $this->app['db']->connection()->table('users')->insertGetId([
356+
'name' => 'Test User',
357+
'created_at' => now(),
358+
'updated_at' => now(),
359+
]);
360+
361+
// Insert address with compact encrypted street_1 and street_2
362+
$street1Encrypted = $compactEncryptMethod->invokeArgs($encryptionService, ['123 Main Street']);
363+
$street2Encrypted = $encryptionService->encrypt('Apt 4B'); // Regular encryption
364+
365+
$addressId = $this->app['db']->connection()->table('user_addresses')->insertGetId([
366+
'user_id' => $userId,
367+
'street_1' => $street1Encrypted,
368+
'street_2' => $street2Encrypted,
369+
'created_at' => now(),
370+
'updated_at' => now(),
371+
]);
372+
373+
// Now simulate loading through a collection/relationship query
374+
// (which doesn't trigger the retrieved event)
375+
$addresses = $this->app['db']->connection()->table('user_addresses')
376+
->where('user_id', $userId)
377+
->get();
378+
379+
// Create model instances manually from the raw data (simulating relationship loading)
380+
$addressData = $addresses->first();
381+
$address = new TestAddressModel();
382+
$address->setRawAttributes((array) $addressData);
383+
$address->exists = true;
384+
385+
// Test that decryption works even when retrieved event didn't fire
386+
$this->assertEquals('123 Main Street', $address->street_1);
387+
$this->assertEquals('Apt 4B', $address->street_2);
388+
389+
// Test toArray() also works
390+
$array = $address->toArray();
391+
$this->assertEquals('123 Main Street', $array['street_1']);
392+
$this->assertEquals('Apt 4B', $array['street_2']);
393+
}
285394
}
286395

287396
class TestEncryptableModel extends Model
@@ -292,4 +401,14 @@ class TestEncryptableModel extends Model
292401
protected $guarded = [];
293402

294403
protected $encryptable = ['email', 'phone', 'address', 'company'];
404+
}
405+
406+
class TestAddressModel extends Model
407+
{
408+
use Encryptable;
409+
410+
protected $table = 'user_addresses';
411+
protected $guarded = [];
412+
413+
protected $encryptable = ['street_1', 'street_2'];
295414
}

0 commit comments

Comments
 (0)