Page MenuHomePhorge

D5892.1779443027.diff
No OneTemporary

Authored By
Unknown
Size
38 KB
Referenced Files
None
Subscribers
None

D5892.1779443027.diff

diff --git a/src/app/Backends/PGP.php b/src/app/Backends/PGP.php
--- a/src/app/Backends/PGP.php
+++ b/src/app/Backends/PGP.php
@@ -44,19 +44,14 @@
* Generate a keypair.
* This will also initialize the user GPG homedir content.
*
- * @param User $user User object
- * @param string $email Email address to use for the key
- *
* @throws \Exception
*/
- public static function keypairCreate(User $user, string $email): void
+ public static function keyCreate(User $user): void
{
self::initGPG($user, true);
- if ($user->email === $email) {
- // Make sure the homedir is empty for a new user
- self::homedirCleanup($user);
- }
+ // Make sure the homedir is empty for a new user
+ self::homedirCleanup($user);
$keygen = new \Crypt_GPG_KeyGenerator(self::$config);
@@ -65,38 +60,110 @@
// ->setExpirationDate(0)
->setKeyParams(\Crypt_GPG_SubKey::ALGORITHM_RSA, \config('pgp.length'))
->setSubKeyParams(\Crypt_GPG_SubKey::ALGORITHM_RSA, \config('pgp.length'))
- ->generateKey('', $email);
+ ->generateKey('', $user->email);
+
+ // In case this is not a new user, there may be aliases
+ $aliases = $user->aliases()->pluck('alias')->all();
+ if (!empty($aliases)) {
+ $editor = self::$gpg->getKeyEditor()->edit((string) $key);
+
+ // Add missing aliases
+ foreach ($aliases as $alias) {
+ $editor->addUserId((new \Crypt_GPG_UserId())->setEmail($alias));
+ }
+
+ $editor->save();
+
+ $key = self::$gpg->getKeys((string) $key)[0];
+ }
// Store the keypair in Roundcube Enigma storage
self::dbSave(true);
// Register the public key in the system
- self::keyRegister($user, $email, $key);
+ self::storeCreate($user, $key);
// FIXME: Should we remove the files from the worker filesystem?
// They are still in database and Roundcube hosts' filesystem
}
/**
- * Delete a keypair from DNS and Enigma keyring.
+ * Delete a keypair from the user keyring.
*
- * @param User $user User object
- * @param string $email Email address of the key
+ * @throws \Exception
+ */
+ public static function keyDelete(User $user): void
+ {
+ // Start with the key store(s)
+ self::storeDelete($user);
+
+ // Remove the whole Enigma keyring
+ self::homedirCleanup($user);
+ }
+
+ /**
+ * Get a user (active) key
+ *
+ * @return \Crypt_GPG_Key|null
+ */
+ public static function keyGet(User $user)
+ {
+ self::initGPG($user);
+
+ $keys = collect(self::$gpg->getKeys($user->email))
+ ->filter(function (\Crypt_GPG_Key $key) use ($user) {
+ if ($key->canSign() && !$key->getPrimaryKey()?->isRevoked()) {
+ foreach ($key->getUserIds() as $userid) {
+ if ($userid->getEmail() == $user->email) {
+ return true;
+ }
+ }
+ }
+ return false;
+ })
+ ->values()
+ ->all();
+
+ return $keys[0] ?? null;
+ }
+
+ /**
+ * Update a key in a user keyring (add/remove aliases).
*
* @throws \Exception
*/
- public static function keyDelete(User $user, string $email): void
+ public static function keyUpdate(User $user): void
{
- // Start with the DNS, it's more important
- self::keyUnregister($user, $email);
+ $key = self::keyGet($user);
- // Remove the whole Enigma keyring if deleting a user
- if ($user->email === $email) {
- self::homedirCleanup($user);
- $user->aliases()->pluck('alias')->each(static fn ($alias) => self::keyUnregister($user, $alias));
+ if (!$key) {
+ return;
}
- // TODO: remove only the alias key from Enigma keyring
+ $emails = $user->aliases()->pluck('alias')->all();
+ $emails[] = $user->email;
+ $existing = [];
+
+ $editor = self::$gpg->getKeyEditor()->edit((string) $key);
+
+ // Remove email addresses that user doesn't have anymore
+ foreach ($key->getUserIds() as $userid) {
+ $email = $userid->getEmail();
+ if (!in_array($email, $emails)) {
+ $editor->deleteUserId($userid);
+ } else {
+ $xisting[] = $email;
+ }
+ }
+
+ // Add missing aliases
+ foreach (array_diff($emails, $existing) as $email) {
+ $editor->addUserId((new \Crypt_GPG_UserId())->setEmail($email));
+ }
+
+ $editor->save();
+
+ self::storeUpdate($user, self::$gpg->getKeys((string) $key)[0]);
}
/**
@@ -112,7 +179,7 @@
{
self::initGPG($user);
- return self::$gpg->getKeys('');
+ return self::$gpg->getKeys();
}
/**
@@ -147,10 +214,10 @@
/**
* Register a new key in the system (WOAT, HKP)
*/
- private static function keyRegister(User $user, string $email, \Crypt_GPG_Key $key): void
+ private static function storeCreate(User $user, \Crypt_GPG_Key $key): void
{
if (self::isEnabled('woat')) {
- PGP\WOAT::addKey($user, $email, $key);
+ PGP\WOAT::addKey($user, $key);
}
if (self::isEnabled('hkp')) {
@@ -161,14 +228,28 @@
/**
* Remove the key from the system (WOAT, HKP)
*/
- private static function keyUnregister(User $user, string $email): void
+ private static function storeDelete(User $user): void
+ {
+ if (self::isEnabled('woat')) {
+ PGP\WOAT::deleteKey($user);
+ }
+
+ if (self::isEnabled('hkp')) {
+ PGP\Keyserver::deleteKey($user);
+ }
+ }
+
+ /**
+ * Update the key in the system (WOAT, HKP)
+ */
+ private static function storeUpdate(User $user, \Crypt_GPG_Key $key): void
{
if (self::isEnabled('woat')) {
- PGP\WOAT::deleteKey($user, $email);
+ PGP\WOAT::updateKey($user, $key);
}
if (self::isEnabled('hkp')) {
- PGP\Keyserver::deleteKey($user, $email);
+ PGP\Keyserver::updateKey($user, $key);
}
}
diff --git a/src/app/Backends/PGP/Keyserver.php b/src/app/Backends/PGP/Keyserver.php
--- a/src/app/Backends/PGP/Keyserver.php
+++ b/src/app/Backends/PGP/Keyserver.php
@@ -4,6 +4,7 @@
use App\Backends\PGP;
use App\User;
+use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
/**
@@ -64,16 +65,66 @@
/**
* Remove a key from the server
*/
- public static function deleteKey(User $user, string $email): void
+ public static function deleteKey(User $user): void
{
- // TODO
+ Key::where('user_id', $user->id)
+ ->whereExists(function (Builder $query) use ($user) {
+ $query->selectRaw('1')
+ ->from('pgp_identities')
+ ->whereColumn('pgp_keys.id', 'pgp_identities.key_id')
+ ->where('email', $user->email);
+ })
+ ->delete();
}
/**
* Remove a key from the server
*/
- public static function updateKey(\Crypt_GPG_Key $key): void
+ public static function updateKey(User $user, \Crypt_GPG_Key $key): void
{
- // TODO
+ $pkey = $key->getPrimaryKey();
+ $key_id = Key::fingerprintId($pkey->getFingerprint());
+
+ $record = Key::where('id', $key_id)->where('user_id', $user->id)->first();
+
+ if (!$record) {
+ throw new \Exception("No key {$key_id} for user {$user->email}");
+ }
+
+ DB::beginTransaction();
+
+ $record->content = PGP::exportPublicKey($user, $key);
+ $record->is_revoked = $pkey->isRevoked();
+ $record->expires_at = $pkey->getExpirationDateTime();
+ $record->algorithm = $pkey->getAlgorithm();
+ $record->length = $pkey->getLength();
+ $record->save();
+
+ $identities = $record->identities()->pluck('identity', 'id')->all();
+ $new_identities = [];
+ foreach ($key->getUserIds() as $userid) {
+ if ($found = array_search((string) $userid, $identities)) {
+ KeyIdentity::find($found)->update(['is_revoked' => $userid->isRevoked()]);
+ unset($identities[$found]);
+ } else {
+ $new_identities[] = [
+ 'email' => $userid->getEmail(),
+ 'identity' => (string) $userid,
+ 'is_revoked' => $userid->isRevoked(),
+ ];
+ }
+ }
+
+ if (!empty($new_identities)) {
+ $record->identities()->createMany($new_identities);
+ }
+
+ if (!empty($identities)) {
+ $record->identities()->whereIn('id', $identities)->delete();
+ }
+
+ // FIXME: Do we have to update subkeys?
+
+ DB::commit();
}
}
diff --git a/src/app/Backends/PGP/WOAT.php b/src/app/Backends/PGP/WOAT.php
--- a/src/app/Backends/PGP/WOAT.php
+++ b/src/app/Backends/PGP/WOAT.php
@@ -4,6 +4,7 @@
use App\Backends\PGP;
use App\PowerDNS\Domain;
+use App\PowerDNS\Record;
use App\User;
use App\Utils;
use Illuminate\Support\Facades\DB;
@@ -11,44 +12,123 @@
class WOAT
{
/**
- * Register a new key in the system (WOAT, HKP)
+ * Register a new key in the system
*/
- public static function addKey(User $user, string $email, \Crypt_GPG_Key $key): void
+ public static function addKey(User $user, \Crypt_GPG_Key $key): void
{
// Get the ASCII armored data of the public key
$armor = PGP::exportPublicKey($user, $key);
- [$local, $domain] = Utils::normalizeAddress($email, true);
-
DB::beginTransaction();
- $domain = Domain::firstOrCreate([
- 'name' => '_woat.' . $domain,
- ]);
+ $domains = [];
+
+ foreach ($key->getUserIds() as $userid) {
+ [$local, $domain] = Utils::normalizeAddress($userid->getEmail(), true);
- $domain->records()->create([
- 'name' => sha1($local) . '.' . $domain->name,
- 'type' => 'TXT',
- 'content' => 'v=woat1,public_key=' . $armor,
- ]);
+ $wd = '_woat.' . $domain;
+ $domain = $domains[$wd] ?? Domain::firstOrCreate(['name' => $wd]);
+ $domains[$wd] = $domain;
+
+ $domain->records()->create([
+ 'name' => sha1($local) . '.' . $wd,
+ 'type' => 'TXT',
+ 'content' => 'v=woat1,public_key=' . $armor,
+ ]);
+ }
DB::commit();
}
/**
- * Remove the key from the system (WOAT, HKP)
+ * Remove the key from the system
*/
- public static function deleteKey(User $user, string $email): void
+ public static function deleteKey(User $user): void
{
- [$local, $domain] = Utils::normalizeAddress($email, true);
+ [$local, $domain] = Utils::normalizeAddress($user->email, true);
$domain = Domain::where('name', '_woat.' . $domain)->first();
- if ($domain) {
- $fqdn = sha1($local) . '.' . $domain->name;
+ if (!$domain) {
+ return;
+ }
+
+ // Getting old records belonging to the user (key) is a bit tricky, but we know
+ // they share the same key content, so we'll use that. This is going to be slow.
+ // TODO: Introduce some faster way to do this.
+ $primary = $domain->records()->where('type', 'TXT')
+ ->where('name', sha1($local) . '.' . $domain->name)
+ ->first();
+
+ if (!$primary) {
+ return;
+ }
+
+ Record::where('type', 'TXT')->where('content', $primary->content)->delete();
+ }
+
+ /**
+ * Update a key in the system
+ */
+ public static function updateKey(User $user, \Crypt_GPG_Key $key): void
+ {
+ [$local, $domain] = Utils::normalizeAddress($user->email, true);
- // For now we support only one WOAT key record
- $domain->records()->where('name', $fqdn)->delete();
+ $domain = Domain::where('name', '_woat.' . $domain)->first();
+
+ if (!$domain) {
+ return;
}
+
+ // Getting old records belonging to the user (key) is a bit tricky, but we know
+ // they share the same key content, so we'll use that. This is going to be slow.
+ // TODO: Introduce some faster way to do this.
+ $primary = $domain->records()->where('type', 'TXT')
+ ->where('name', sha1($local) . '.' . $domain->name)
+ ->first();
+
+ if (!$primary) {
+ return;
+ }
+
+ $existing = Record::where('type', 'TXT')->where('content', $primary->content)
+ ->pluck('name', 'id')->all();
+ $updated = [];
+ $domains = [$domain->name => $domain];
+
+ // Get the ASCII armored data of the public key
+ $armor = PGP::exportPublicKey($user, $key);
+
+ DB::beginTransaction();
+
+ foreach ($key->getUserIds() as $userid) {
+ [$local, $domain] = Utils::normalizeAddress($userid->getEmail(), true);
+
+ $wd = '_woat.' . $domain;
+ $name = sha1($local) . '.' . $wd;
+
+ if ($found = array_search($name, $existing)) {
+ $updated[] = $found;
+ unset($existing[$found]);
+ } else {
+ $domain = $domains[$wd] ?? Domain::firstOrCreate(['name' => $wd]);
+ $domains[$wd] = $domain;
+ $domain->records()->create([
+ 'name' => $name,
+ 'type' => 'TXT',
+ 'content' => 'v=woat1,public_key=' . $armor,
+ ]);
+ }
+ }
+
+ if (!empty($updated)) {
+ Record::whereIn('id', $updated)->update(['content' => 'v=woat1,public_key=' . $armor]);
+ }
+
+ if (!empty($existing)) {
+ Record::whereIn('id', array_keys($existing))->delete();
+ }
+
+ DB::commit();
}
}
diff --git a/src/app/Jobs/PGP/KeyCreateJob.php b/src/app/Jobs/PGP/KeyCreateJob.php
--- a/src/app/Jobs/PGP/KeyCreateJob.php
+++ b/src/app/Jobs/PGP/KeyCreateJob.php
@@ -6,29 +6,10 @@
use App\Support\Facades\PGP;
/**
- * Create a GPG keypair for a user (or alias).
- *
- * Throws exceptions for the following reasons:
- *
- * * The user is marked as deleted (`$user->isDeleted()`), or
- * * the user is actually deleted (`$user->deleted_at`)
- * * the alias is actually deleted
- * * there was an error in keypair generation process
+ * Create a GPG keypair for a user.
*/
class KeyCreateJob extends UserJob
{
- /**
- * Create a new job instance.
- *
- * @param int $userId user identifier
- * @param string $userEmail User email address for the key
- */
- public function __construct(int $userId, string $userEmail)
- {
- $this->userId = $userId;
- $this->userEmail = $userEmail;
- }
-
/**
* Execute the job.
*
@@ -47,14 +28,6 @@
return;
}
- if (
- $this->userEmail != $user->email
- && !$user->aliases()->where('alias', $this->userEmail)->exists()
- ) {
- $this->fail("Alias {$this->userEmail} is actually deleted.");
- return;
- }
-
- PGP::keypairCreate($user, $this->userEmail);
+ PGP::keyCreate($user);
}
}
diff --git a/src/app/Jobs/PGP/KeyDeleteJob.php b/src/app/Jobs/PGP/KeyDeleteJob.php
--- a/src/app/Jobs/PGP/KeyDeleteJob.php
+++ b/src/app/Jobs/PGP/KeyDeleteJob.php
@@ -10,18 +10,6 @@
*/
class KeyDeleteJob extends UserJob
{
- /**
- * Create a new job instance.
- *
- * @param int $userId user identifier
- * @param string $userEmail User email address of the key
- */
- public function __construct(int $userId, string $userEmail)
- {
- $this->userId = $userId;
- $this->userEmail = $userEmail;
- }
-
/**
* Execute the job.
*
@@ -35,6 +23,6 @@
return;
}
- PGP::keyDelete($user, $this->userEmail);
+ PGP::keyDelete($user);
}
}
diff --git a/src/app/Jobs/PGP/KeyUpdateJob.php b/src/app/Jobs/PGP/KeyUpdateJob.php
new file mode 100644
--- /dev/null
+++ b/src/app/Jobs/PGP/KeyUpdateJob.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Jobs\PGP;
+
+use App\Jobs\UserUniqueJob;
+use App\Support\Facades\PGP;
+
+/**
+ * Update a GPG key for a user (add/remove aliases).
+ */
+class KeyUpdateJob extends UserUniqueJob
+{
+ /**
+ * Execute the job.
+ *
+ * @throws \Exception
+ */
+ public function handle()
+ {
+ $user = $this->getUser();
+
+ if (!$user) {
+ return;
+ }
+
+ if ($user->trashed()) {
+ $this->fail("User {$user->email} is actually deleted.");
+ return;
+ }
+
+ PGP::keyUpdate($user);
+ }
+}
diff --git a/src/app/Jobs/User/UpdateJob.php b/src/app/Jobs/User/UpdateJob.php
--- a/src/app/Jobs/User/UpdateJob.php
+++ b/src/app/Jobs/User/UpdateJob.php
@@ -2,20 +2,16 @@
namespace App\Jobs\User;
-use App\Jobs\UserJob;
+use App\Jobs\UserUniqueJob;
use App\Support\Facades\IMAP;
use App\Support\Facades\LDAP;
use App\User;
-use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
-class UpdateJob extends UserJob implements ShouldBeUniqueUntilProcessing
+class UpdateJob extends UserUniqueJob
{
/** @var int Enable waiting for a user record to exist */
protected $waitForUser = 5;
- /** @var int The number of seconds after which the job's unique lock will be released. */
- public $uniqueFor = 60;
-
/**
* Execute the job.
*/
@@ -39,16 +35,4 @@
}
}
}
-
- /**
- * Get the unique ID for the job.
- *
- * This together with ShouldBeUniqueUntilProcessing makes sure there's only one update job
- * for the same user at the same time. E.g. if you delete 5 storage entitlements in one action,
- * we'll reach to LDAP/IMAP backend only once (instead of five).
- */
- public function uniqueId(): string
- {
- return (string) $this->userId;
- }
}
diff --git a/src/app/Jobs/UserJob.php b/src/app/Jobs/UserJob.php
--- a/src/app/Jobs/UserJob.php
+++ b/src/app/Jobs/UserJob.php
@@ -73,4 +73,12 @@
return $user;
}
+
+ /**
+ * Execute the job.
+ */
+ public function handle()
+ {
+ // To be overwritten
+ }
}
diff --git a/src/app/Jobs/UserUniqueJob.php b/src/app/Jobs/UserUniqueJob.php
new file mode 100644
--- /dev/null
+++ b/src/app/Jobs/UserUniqueJob.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Jobs;
+
+use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
+
+class UserUniqueJob extends UserJob implements ShouldBeUniqueUntilProcessing
+{
+ /** @var int The number of seconds after which the job's unique lock will be released. */
+ public $uniqueFor = 60;
+
+ /**
+ * Get the unique ID for the job.
+ *
+ * This together with ShouldBeUniqueUntilProcessing makes sure there's only one job of this type
+ * for the same user at the same time. E.g. if you delete 5 storage entitlements in one action,
+ * we'll reach to LDAP/IMAP backend only once (instead of five).
+ */
+ public function uniqueId(): string
+ {
+ return (string) $this->userId;
+ }
+}
diff --git a/src/app/Observers/UserAliasObserver.php b/src/app/Observers/UserAliasObserver.php
--- a/src/app/Observers/UserAliasObserver.php
+++ b/src/app/Observers/UserAliasObserver.php
@@ -3,8 +3,7 @@
namespace App\Observers;
use App\Domain;
-use App\Jobs\PGP\KeyCreateJob;
-use App\Jobs\PGP\KeyDeleteJob;
+use App\Jobs\PGP\KeyUpdateJob;
use App\Jobs\User\UpdateJob;
use App\Tenant;
use App\User;
@@ -51,7 +50,7 @@
UpdateJob::dispatch($alias->user_id);
if (Tenant::getConfig($alias->user->tenant_id, 'pgp.enable')) {
- KeyCreateJob::dispatch($alias->user_id, $alias->alias);
+ KeyUpdateJob::dispatch($alias->user_id);
}
}
}
@@ -84,7 +83,7 @@
});
if (Tenant::getConfig($alias->user->tenant_id, 'pgp.enable')) {
- KeyDeleteJob::dispatch($alias->user_id, $alias->alias);
+ KeyUpdateJob::dispatch($alias->user_id);
}
}
}
diff --git a/src/app/Observers/UserObserver.php b/src/app/Observers/UserObserver.php
--- a/src/app/Observers/UserObserver.php
+++ b/src/app/Observers/UserObserver.php
@@ -82,7 +82,7 @@
CreateJob::dispatch($user->id);
if (Tenant::getConfig($user->tenant_id, 'pgp.enable')) {
- KeyCreateJob::dispatch($user->id, $user->email);
+ KeyCreateJob::dispatch($user->id);
}
}
@@ -101,7 +101,7 @@
DeleteJob::dispatch($user->id);
if (Tenant::getConfig($user->tenant_id, 'pgp.enable')) {
- KeyDeleteJob::dispatch($user->id, $user->email);
+ KeyDeleteJob::dispatch($user->id);
}
}
diff --git a/src/composer.json b/src/composer.json
--- a/src/composer.json
+++ b/src/composer.json
@@ -32,7 +32,7 @@
"league/flysystem-aws-s3-v3": "^3.16",
"mlocati/spf-lib": "^3.1",
"mollie/laravel-mollie": "^3.0",
- "pear/crypt_gpg": "^1.6.6",
+ "pear/crypt_gpg": "dev-dev/key-editor",
"pear/mail_mime": "~1.10.11",
"predis/predis": "^2.0",
"sabre/dav": "dev-master#b22c020e",
diff --git a/src/tests/Feature/Backends/PGPTest.php b/src/tests/Feature/Backends/PGPTest.php
--- a/src/tests/Feature/Backends/PGPTest.php
+++ b/src/tests/Feature/Backends/PGPTest.php
@@ -6,6 +6,7 @@
use App\Backends\PGP\Key;
use App\Backends\Roundcube;
use App\PowerDNS\Domain;
+use App\PowerDNS\Record;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
@@ -16,30 +17,32 @@
parent::setUp();
$user = $this->getTestUser('john@kolab.org');
- $user->aliases()->where('alias', 'test-alias@kolab.org')->delete();
- PGP::homedirCleanup($user);
- Domain::where('name', '_woat.kolab.org')->delete();
+ $user->aliases()->where('alias', 'test-alias@kolabnow.com')->delete();
+ Domain::query()->delete();
Key::query()->delete();
+
+ PGP::homedirCleanup($user);
}
protected function tearDown(): void
{
$user = $this->getTestUser('john@kolab.org');
- $user->aliases()->where('alias', 'test-alias@kolab.org')->delete();
- PGP::homedirCleanup($user);
- Domain::where('name', '_woat.kolab.org')->delete();
+ $user->aliases()->where('alias', 'test-alias@kolabnow.com')->delete();
+ Domain::query()->delete();
Key::query()->delete();
+ PGP::homedirCleanup($user);
+
parent::tearDown();
}
/**
- * Test key pair creation, listing and deletion.
+ * Test key pair creation, update, delete and listing.
*
* @group pgp
* @group roundcube
*/
- public function testKeyCreateListDelete(): void
+ public function testKeyCreateListUpdateAndDelete(): void
{
\config(['pgp.services' => ['woat', 'hkp']]);
Queue::fake();
@@ -49,7 +52,7 @@
$this->assertCount(0, PGP::listKeys($user));
// Create a key pair
- PGP::keypairCreate($user, $user->email);
+ PGP::keyCreate($user);
// Assert the Enigma storage has been initialized and contains the key
$files = Roundcube::enigmaList($user->email);
@@ -62,12 +65,17 @@
$keyId = Key::fingerprintId((string) $keys[0]);
$userIds = $keys[0]->getUserIds();
- $this->assertCount(1, $userIds);
- $this->assertSame($user->email, $userIds[0]->getEmail());
+ $this->assertCount(2, $userIds);
+ $this->assertCount(1, array_filter($userIds, fn ($item) => $item->getEmail() == $user->email));
+ $this->assertCount(1, array_filter($userIds, fn ($item) => $item->getEmail() == 'john.doe@kolab.org'));
$this->assertSame('', $userIds[0]->getName());
$this->assertSame('', $userIds[0]->getComment());
$this->assertTrue($userIds[0]->isValid());
$this->assertFalse($userIds[0]->isRevoked());
+ $this->assertSame('', $userIds[1]->getName());
+ $this->assertSame('', $userIds[1]->getComment());
+ $this->assertTrue($userIds[1]->isValid());
+ $this->assertFalse($userIds[1]->isRevoked());
$key = $keys[0]->getPrimaryKey();
$this->assertSame(\Crypt_GPG_SubKey::ALGORITHM_RSA, $key->getAlgorithm());
@@ -86,57 +94,68 @@
$this->assertTrue($key->canEncrypt());
$this->assertFalse($key->isRevoked());
- // WOAT: Assert the public key in DNS
- $dns_domain = Domain::where('name', '_woat.kolab.org')->first();
- $this->assertNotNull($dns_domain);
- $dns_record = $dns_domain->records()->where('type', 'TXT')->first();
- $this->assertNotNull($dns_record);
- $this->assertSame('TXT', $dns_record->type);
- $this->assertSame(sha1('john') . '._woat.kolab.org', $dns_record->name);
$this->assertMatchesRegularExpression(
- '/^v=woat1,public_key='
- . '-----BEGIN PGP PUBLIC KEY BLOCK-----'
- . '[a-zA-Z0-9\n\/+=]+'
- . '-----END PGP PUBLIC KEY BLOCK-----'
- . '$/',
- $dns_record->content
+ '/^-----BEGIN PGP PUBLIC KEY BLOCK-----[a-zA-Z0-9\n\/+=]+-----END PGP PUBLIC KEY BLOCK-----$/',
+ $content = PGP::exportPublicKey($user, $keys[0])
);
+ // WOAT: Assert the public key in DNS
+ $dns_domain = Domain::where('name', '_woat.kolab.org')->first();
+ $records = $dns_domain->records()->where('type', 'TXT')->get()->keyBy('name')->all();
+ $this->assertCount(2, $records);
+ $this->assertSame('TXT', $records[sha1('john') . '._woat.kolab.org']->type);
+ $this->assertSame('TXT', $records[sha1('john.doe') . '._woat.kolab.org']->type);
+ $this->assertSame("v=woat1,public_key={$content}", $records[sha1('john') . '._woat.kolab.org']->content);
+ $this->assertSame("v=woat1,public_key={$content}", $records[sha1('john.doe') . '._woat.kolab.org']->content);
+
// HKP: Keyserver
$key = Key::find($keyId);
$this->assertInstanceOf(Key::class, $key);
- $this->assertMatchesRegularExpression(
- '/-----BEGIN PGP PUBLIC KEY BLOCK-----[a-zA-Z0-9\n\/+=]+-----END PGP PUBLIC KEY BLOCK-----$/',
- $key->content
- );
+ $this->assertSame($content, $key->content);
$this->assertFalse($key->is_revoked);
$this->assertNull($key->expires_at);
$this->assertSame(\config('pgp.length'), $key->length);
$this->assertSame(\Crypt_GPG_SubKey::ALGORITHM_RSA, $key->algorithm);
- $this->assertCount(1, $key_users = $key->identities()->get());
- $this->assertSame($user->email, $key_users[0]->email);
- $subkeys = $keys[0]->getSubKeys();
+ $this->assertCount(2, $key_users = $key->identities()->get()->keyBy('email')->all());
+ $this->assertArrayHasKey('john@kolab.org', $key_users);
+ $this->assertArrayHasKey('john.doe@kolab.org', $key_users);
$this->assertCount(1, $key_subkeys = $key->subkeys()->get());
$this->assertSame(Key::fingerprintId($keys[0]->getSubKeys()[1]->getFingerprint()), $key_subkeys[0]->id);
- // Test an alias
- $alias = $user->aliases()->create(['alias' => 'test-alias@kolab.org']);
- PGP::keypairCreate($user, $alias->alias);
+ // Test key update with adding another alias (different domain)
+ $alias = $user->aliases()->create(['alias' => 'test-alias@kolabnow.com']);
+ PGP::keyUpdate($user);
// Assert the created keypair parameters
$keys = PGP::listKeys($user);
- $this->assertCount(2, $keys);
+ $this->assertCount(1, $keys);
- $userIds = $keys[1]->getUserIds();
- $this->assertCount(1, $userIds);
- $this->assertSame('test-alias@kolab.org', $userIds[0]->getEmail());
- $this->assertSame('', $userIds[0]->getName());
- $this->assertSame('', $userIds[0]->getComment());
- $this->assertTrue($userIds[0]->isValid());
- $this->assertFalse($userIds[0]->isRevoked());
+ $this->assertMatchesRegularExpression(
+ '/^-----BEGIN PGP PUBLIC KEY BLOCK-----[a-zA-Z0-9\n\/+=]+-----END PGP PUBLIC KEY BLOCK-----$/',
+ $content = PGP::exportPublicKey($user, $keys[0])
+ );
+
+ $userIds = collect($keys[0]->getUserIds())
+ ->keyBy(function (\Crypt_GPG_UserId $item) {
+ return $item->getEmail();
+ })
+ ->all();
+ $this->assertCount(3, $userIds);
+ $this->assertSame('', $userIds['john@kolab.org']->getName());
+ $this->assertSame('', $userIds['john@kolab.org']->getComment());
+ $this->assertTrue($userIds['john@kolab.org']->isValid());
+ $this->assertFalse($userIds['john@kolab.org']->isRevoked());
+ $this->assertSame('', $userIds['john.doe@kolab.org']->getName());
+ $this->assertSame('', $userIds['john.doe@kolab.org']->getComment());
+ $this->assertTrue($userIds['john.doe@kolab.org']->isValid());
+ $this->assertFalse($userIds['john.doe@kolab.org']->isRevoked());
+ $this->assertSame('', $userIds['test-alias@kolabnow.com']->getName());
+ $this->assertSame('', $userIds['test-alias@kolabnow.com']->getComment());
+ $this->assertTrue($userIds['test-alias@kolabnow.com']->isValid());
+ $this->assertFalse($userIds['test-alias@kolabnow.com']->isRevoked());
- $key = $keys[1]->getPrimaryKey();
+ $key = $keys[0]->getPrimaryKey();
$this->assertSame(\Crypt_GPG_SubKey::ALGORITHM_RSA, $key->getAlgorithm());
$this->assertSame(0, $key->getExpirationDate());
$this->assertSame((int) \config('pgp.length'), $key->getLength());
@@ -145,7 +164,7 @@
$this->assertFalse($key->canEncrypt());
$this->assertFalse($key->isRevoked());
- $key = $keys[1]->getSubKeys()[1];
+ $key = $keys[0]->getSubKeys()[1];
$this->assertSame(\Crypt_GPG_SubKey::ALGORITHM_RSA, $key->getAlgorithm());
$this->assertSame(0, $key->getExpirationDate());
$this->assertSame((int) \config('pgp.length'), $key->getLength());
@@ -154,18 +173,40 @@
$this->assertFalse($key->isRevoked());
// WOAT
- $this->assertSame(2, $dns_domain->records()->where('type', 'TXT')->count());
+ $records = $dns_domain->records()->where('type', 'TXT')->get()->keyBy('name')->all();
+ $this->assertCount(2, $records);
+ $this->assertSame("v=woat1,public_key={$content}", $records[sha1('john') . '._woat.kolab.org']->content);
+ $this->assertSame("v=woat1,public_key={$content}", $records[sha1('john.doe') . '._woat.kolab.org']->content);
+ $dns_domain2 = Domain::where('name', '_woat.kolabnow.com')->first();
+ $records = $dns_domain2->records()->where('type', 'TXT')->get()->keyBy('name')->all();
+ $this->assertCount(1, $records);
+ $this->assertSame("v=woat1,public_key={$content}", $records[sha1('test-alias') . '._woat.kolabnow.com']->content);
+
+ // HKP
+ $this->assertCount(1, $keys = Key::where('user_id', $user->id)->get());
+ $key = $keys[0];
+ $this->assertSame($content, $key->content);
+ $this->assertFalse($key->is_revoked);
+ $this->assertNull($key->expires_at);
+ $this->assertSame(\config('pgp.length'), $key->length);
+ $this->assertSame(\Crypt_GPG_SubKey::ALGORITHM_RSA, $key->algorithm);
+
+ $this->assertCount(3, $key_users = $key->identities()->get()->keyBy('email')->all());
+ $this->assertFalse($key_users['john@kolab.org']->is_revoked);
+ $this->assertFalse($key_users['john.doe@kolab.org']->is_revoked);
+ $this->assertFalse($key_users['test-alias@kolabnow.com']->is_revoked);
- // TODO: HKP
+ // TODO: Test removing aliases
// Delete the key
- PGP::keyDelete($user, $user->email);
+ PGP::keyDelete($user);
$this->assertCount(0, PGP::listKeys($user));
// WOAT
- $this->assertSame(0, $dns_domain->records()->where('type', 'TXT')->count());
+ $this->assertSame(0, Record::where('type', 'TXT')->count());
- // TODO: HKP
+ // HKP
+ $this->assertSame(0, Key::where('user_id', $user->id)->count());
}
}
diff --git a/src/tests/Feature/Jobs/PGP/KeyCreateTest.php b/src/tests/Feature/Jobs/PGP/KeyCreateTest.php
--- a/src/tests/Feature/Jobs/PGP/KeyCreateTest.php
+++ b/src/tests/Feature/Jobs/PGP/KeyCreateTest.php
@@ -4,7 +4,6 @@
use App\Jobs\PGP\KeyCreateJob;
use App\Support\Facades\PGP;
-use App\UserAlias;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
@@ -14,12 +13,12 @@
{
parent::setUp();
- UserAlias::where('alias', 'test-alias@kolab.org')->delete();
+ $this->deleteTestUser('pgp@kolabnow.com');
}
protected function tearDown(): void
{
- UserAlias::where('alias', 'test-alias@kolab.org')->delete();
+ $this->deleteTestUser('pgp@kolabnow.com');
parent::tearDown();
}
@@ -31,21 +30,20 @@
{
Queue::fake();
- $user = $this->getTestUser('john@kolab.org');
+ $user = $this->getTestUser('pgp@kolabnow.com');
- // Test without an alias
- PGP::shouldReceive('keypairCreate')->once()->with($user, $user->email);
+ // Test success
+ PGP::shouldReceive('keyCreate')->once()->with($user);
- $job = (new KeyCreateJob($user->id, $user->email))->withFakeQueueInteractions();
+ $job = (new KeyCreateJob($user->id))->withFakeQueueInteractions();
$job->handle();
$job->assertNotFailed();
- // Test with an alias
- $alias = UserAlias::create(['user_id' => $user->id, 'alias' => 'test-alias@kolab.org']);
- PGP::shouldReceive('keypairCreate')->once()->with($user, $alias->alias);
+ // Test deleted user
+ $user->delete();
- $job = (new KeyCreateJob($user->id, 'test-alias@kolab.org'))->withFakeQueueInteractions();
+ $job = (new KeyCreateJob($user->id))->withFakeQueueInteractions();
$job->handle();
- $job->assertNotFailed();
+ $job->assertFailed();
}
}
diff --git a/src/tests/Feature/Jobs/PGP/KeyDeleteTest.php b/src/tests/Feature/Jobs/PGP/KeyDeleteTest.php
--- a/src/tests/Feature/Jobs/PGP/KeyDeleteTest.php
+++ b/src/tests/Feature/Jobs/PGP/KeyDeleteTest.php
@@ -15,9 +15,9 @@
{
$user = $this->getTestUser('john@kolab.org');
- PGP::shouldReceive('keyDelete')->once()->with($user, $user->email);
+ PGP::shouldReceive('keyDelete')->once()->with($user);
- $job = (new KeyDeleteJob($user->id, $user->email))->withFakeQueueInteractions();
+ $job = (new KeyDeleteJob($user->id))->withFakeQueueInteractions();
$job->handle();
$job->assertNotFailed();
}
diff --git a/src/tests/Feature/Jobs/PGP/KeyUpdateTest.php b/src/tests/Feature/Jobs/PGP/KeyUpdateTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Jobs/PGP/KeyUpdateTest.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Tests\Feature\Jobs\PGP;
+
+use App\Jobs\PGP\KeyUpdateJob;
+use App\Support\Facades\PGP;
+use Illuminate\Support\Facades\Queue;
+use Tests\TestCase;
+
+class KeyUpdateTest extends TestCase
+{
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->deleteTestUser('pgp@kolabnow.com');
+ }
+
+ protected function tearDown(): void
+ {
+ $this->deleteTestUser('pgp@kolabnow.com');
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test job handle
+ */
+ public function testHandle(): void
+ {
+ Queue::fake();
+
+ $user = $this->getTestUser('pgp@kolabnow.com');
+
+ // Test success case
+ PGP::shouldReceive('keyUpdate')->once()->with($user);
+
+ $job = (new KeyUpdateJob($user->id))->withFakeQueueInteractions();
+ $job->handle();
+ $job->assertNotFailed();
+
+ // Test deleted user
+ $user->delete();
+ $job = (new KeyUpdateJob($user->id))->withFakeQueueInteractions();
+ $job->handle();
+ $job->assertFailed();
+ }
+}
diff --git a/src/tests/Feature/UserTest.php b/src/tests/Feature/UserTest.php
--- a/src/tests/Feature/UserTest.php
+++ b/src/tests/Feature/UserTest.php
@@ -11,6 +11,7 @@
use App\Group;
use App\Jobs\PGP\KeyCreateJob;
use App\Jobs\PGP\KeyDeleteJob;
+use App\Jobs\PGP\KeyUpdateJob;
use App\Jobs\User\CreateJob;
use App\Jobs\User\DeleteJob;
use App\Jobs\User\UpdateJob;
@@ -520,11 +521,7 @@
Queue::assertPushed(
KeyCreateJob::class,
static function ($job) use ($user) {
- $userEmail = TestCase::getObjectProperty($job, 'userEmail');
- $userId = TestCase::getObjectProperty($job, 'userId');
-
- return $userEmail === $user->email
- && $userId === $user->id;
+ return $user->id === TestCase::getObjectProperty($job, 'userId');
}
);
@@ -1112,8 +1109,7 @@
KeyDeleteJob::class,
static function ($job) use ($user) {
$userId = TestCase::getObjectProperty($job, 'userId');
- $userEmail = TestCase::getObjectProperty($job, 'userEmail');
- return $userId == $user->id && $userEmail === $user->email;
+ return $userId == $user->id;
}
);
}
@@ -1480,7 +1476,7 @@
$user->setAliases(['UserAlias1@UserAccount.com']);
Queue::assertPushed(UpdateJob::class, 1);
- Queue::assertPushed(KeyCreateJob::class, 1);
+ Queue::assertPushed(KeyUpdateJob::class, 1);
$user->tenant->setSetting('pgp.enable', 0);
@@ -1493,7 +1489,7 @@
$user->setAliases(['UserAlias1@UserAccount.com', 'UserAlias2@UserAccount.com']);
Queue::assertPushed(UpdateJob::class, 1);
- Queue::assertPushed(KeyCreateJob::class, 0);
+ Queue::assertPushed(KeyUpdateJob::class, 1);
$aliases = $user->aliases()->orderBy('alias')->get();
$this->assertCount(2, $aliases);
@@ -1512,13 +1508,11 @@
$user->tenant->setSetting('pgp.enable', 0);
Queue::assertPushed(UpdateJob::class, 1);
- Queue::assertPushed(KeyDeleteJob::class, 1);
+ Queue::assertPushed(KeyUpdateJob::class, 1);
Queue::assertPushed(
- KeyDeleteJob::class,
+ KeyUpdateJob::class,
static function ($job) use ($user) {
- $userId = TestCase::getObjectProperty($job, 'userId');
- $userEmail = TestCase::getObjectProperty($job, 'userEmail');
- return $userId == $user->id && $userEmail === 'useralias2@useraccount.com';
+ return $user->id == TestCase::getObjectProperty($job, 'userId');
}
);
Queue::assertPushed(\App\Jobs\User\Delegation\UserRefreshJob::class, 1);

File Metadata

Mime Type
text/plain
Expires
Fri, May 22, 9:43 AM (15 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18929686
Default Alt Text
D5892.1779443027.diff (38 KB)

Event Timeline