Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F125430023
D5892.1779469097.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
38 KB
Referenced Files
None
Subscribers
None
D5892.1779469097.diff
View Options
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": "^1.8.0",
"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
Details
Attached
Mime Type
text/plain
Expires
Fri, May 22, 4:58 PM (22 h, 22 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18929999
Default Alt Text
D5892.1779469097.diff (38 KB)
Attached To
Mode
D5892: PGP keys with proper email aliases support
Attached
Detach File
Event Timeline