Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117753076
D2554.1775192542.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
29 KB
Referenced Files
None
Subscribers
None
D2554.1775192542.diff
View Options
diff --git a/src/.env.example b/src/.env.example
--- a/src/.env.example
+++ b/src/.env.example
@@ -90,6 +90,12 @@
#OPENVIDU_WEBHOOK_EVENTS=[sessionCreated,sessionDestroyed,participantJoined,participantLeft,webrtcConnectionCreated,webrtcConnectionDestroyed,recordingStatusChanged,filterEventDispatched,mediaNodeStatusChanged]
#OPENVIDU_WEBHOOK_HEADERS=[\"Authorization:\ Basic\ SOMETHING\"]
+PGP_ENABLED=
+PGP_BINARY=
+PGP_AGENT=
+PGP_GPGCONF=
+PGP_LENGTH=
+
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
diff --git a/src/app/Auth/SecondFactor.php b/src/app/Auth/SecondFactor.php
--- a/src/app/Auth/SecondFactor.php
+++ b/src/app/Auth/SecondFactor.php
@@ -3,7 +3,6 @@
namespace App\Auth;
use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\DB;
use Kolab2FA\Storage\Base;
/**
@@ -307,16 +306,6 @@
*/
public static function dbh()
{
- $dsn = \config('2fa.dsn');
-
- if (empty($dsn)) {
- \Log::warning("2-FACTOR database not configured");
-
- return DB::connection(\config('database.default'));
- }
-
- \Config::set('database.connections.2fa', ['url' => $dsn]);
-
- return DB::connection('2fa');
+ return \App\Backends\Roundcube::dbh();
}
}
diff --git a/src/app/Backends/PGP.php b/src/app/Backends/PGP.php
new file mode 100644
--- /dev/null
+++ b/src/app/Backends/PGP.php
@@ -0,0 +1,208 @@
+<?php
+
+namespace App\Backends;
+
+use App\User;
+use Illuminate\Support\Facades\Storage;
+
+class PGP
+{
+ /** @var \Crypt_GPG GnuPG engine instance */
+ private static $gpg;
+
+ /** @var array Crypt_GPG configuration */
+ private static $config = [];
+
+
+ /**
+ * Remove all files from the user homedir
+ *
+ * @param \App\User $user User object
+ * @param bool $del Delete also the homedir itself
+ */
+ public static function homedirCleanup(User $user, bool $del = false): void
+ {
+ $homedir = self::setHomedir($user);
+
+ // Remove all files from the filesystem (and optionally the dir itself)
+ if ($del) {
+ Storage::disk('pgp')->deleteDirectory($homedir);
+ } else {
+ Storage::disk('pgp')->delete(Storage::disk('pgp')->files($homedir));
+
+ foreach (Storage::disk('pgp')->files($homedir) as $subdir) {
+ Storage::disk('pgp')->deleteDirectory($subdir);
+ }
+ }
+
+ // Remove all files from the Enigma database
+ // Note: This will cause existing files in the Roundcube filesystem
+ // to be removed, but only if the user used the Enigma functionality
+ Roundcube::enigmaCleanup($user->email);
+ }
+
+ /**
+ * Generate a keypair.
+ * This will also initialize the user GPG homedir content.
+ *
+ * @param \App\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
+ {
+ self::initGPG($user, true);
+
+ if ($user->email === $email) {
+ // Make sure the homedir is empty for a new user
+ self::homedirCleanup($user);
+ }
+
+ $keygen = new \Crypt_GPG_KeyGenerator(self::$config);
+
+ $key = $keygen
+ // ->setPassphrase()
+ // ->setExpirationDate(0)
+ ->setKeyParams(\Crypt_GPG_SubKey::ALGORITHM_RSA, \config('pgp.length'))
+ ->setSubKeyParams(\Crypt_GPG_SubKey::ALGORITHM_RSA, \config('pgp.length'))
+ ->generateKey(null, $email);
+
+ // Store the keypair in Roundcube Enigma storage
+ self::dbSave(true);
+
+ // Get the ASCII armored data of the public key
+ $armor = self::$gpg->exportPublicKey((string) $key, true);
+
+ // Register the public key in DNS
+ self::keyRegister($email, $armor);
+
+ // FIXME: Should we remove the files from the worker filesystem?
+ // They are still in database and Roundcube hosts' filesystem
+ }
+
+ /**
+ * List (public and private) keys from a user keyring.
+ *
+ * @param \App\User $user User object
+ *
+ * @returns \Crypt_GPG_Key[] List of keys
+ * @throws \Exception
+ */
+ public static function listKeys(User $user): array
+ {
+ self::initGPG($user);
+
+ return self::$gpg->getKeys('');
+ }
+
+ /**
+ * Debug logging callback
+ */
+ public static function logDebug($msg): void
+ {
+ \Log::debug("[GPG] $msg");
+ }
+
+ /**
+ * Register the key in the WOAT DNS system
+ *
+ * @param string $email Email address
+ * @param string $key The ASCII-armored key content
+ */
+ public static function keyRegister(string $email, string $key)
+ {
+ // TODO
+ }
+
+ /**
+ * Remove the key from the WOAT DNS system
+ *
+ * @param string $email Email address
+ */
+ public static function keyUnregister(string $email)
+ {
+ // TODO
+ }
+
+ /**
+ * Prepare Crypt_GPG configuration
+ */
+ private static function initConfig(User $user, $nosync = false): void
+ {
+ if (!empty(self::$config) && self::$config['email'] == $user->email) {
+ return;
+ }
+
+ $debug = \config('app.debug');
+ $binary = \config('pgp.binary');
+ $agent = \config('pgp.agent');
+ $gpgconf = \config('pgp.gpgconf');
+
+ $dir = self::setHomedir($user);
+ $options = [
+ 'email' => $user->email, // this one is not a Crypt_GPG option
+ 'dir' => $dir, // this one is not a Crypt_GPG option
+ 'homedir' => \config('filesystems.disks.pgp.root') . '/' . $dir,
+ 'debug' => $debug ? 'App\Backends\PGP::logDebug' : null,
+ ];
+
+ if ($binary) {
+ $options['binary'] = $binary;
+ }
+
+ if ($agent) {
+ $options['agent'] = $agent;
+ }
+
+ if ($gpgconf) {
+ $options['gpgconf'] = $gpgconf;
+ }
+
+ self::$config = $options;
+
+ // Sync the homedir directory content with the Enigma storage
+ if (!$nosync) {
+ self::dbSync();
+ }
+ }
+
+ /**
+ * Initialize Crypt_GPG
+ */
+ private static function initGPG(User $user, $nosync = false): void
+ {
+ self::initConfig($user, $nosync);
+
+ self::$gpg = new \Crypt_GPG(self::$config);
+ }
+
+ /**
+ * Prepare a homedir for the user
+ */
+ private static function setHomedir(User $user): string
+ {
+ // Create a subfolder using two first digits of the user ID
+ $dir = sprintf('%02d', substr((string) $user->id, 0, 2)) . '/' . $user->email;
+
+ Storage::disk('pgp')->makeDirectory($dir);
+
+ return $dir;
+ }
+
+ /**
+ * Synchronize keys database of a user
+ */
+ private static function dbSync(): void
+ {
+ Roundcube::enigmaSync(self::$config['email'], self::$config['dir']);
+ }
+
+ /**
+ * Save the keys database
+ */
+ private static function dbSave($is_empty = false): void
+ {
+ Roundcube::enigmaSave(self::$config['email'], self::$config['dir'], $is_empty);
+ }
+}
diff --git a/src/app/Backends/Roundcube.php b/src/app/Backends/Roundcube.php
new file mode 100644
--- /dev/null
+++ b/src/app/Backends/Roundcube.php
@@ -0,0 +1,256 @@
+<?php
+
+namespace App\Backends;
+
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Storage;
+
+class Roundcube
+{
+ private const FILESTORE_TABLE = 'filestore';
+ private const USERS_TABLE = 'users';
+
+ /** @var array List of GnuPG files to store */
+ private static $enigma_files = ['pubring.gpg', 'secring.gpg', 'pubring.kbx'];
+
+
+ /**
+ * Return connection to the Roundcube database
+ *
+ * @return \Illuminate\Database\ConnectionInterface
+ */
+ public static function dbh()
+ {
+ if (!\config('database.connections.roundcube')) {
+ \Log::warning("Roundcube database not configured");
+
+ return DB::connection(\config('database.default'));
+ }
+
+ return DB::connection('roundcube');
+ }
+
+ /**
+ * Remove all files from the Enigma filestore.
+ *
+ * @param string $email User email address
+ */
+ public static function enigmaCleanup(string $email): void
+ {
+ self::dbh()->table(self::FILESTORE_TABLE)
+ ->where('user_id', self::userId($email))
+ ->where('context', 'enigma')
+ ->delete();
+ }
+
+ /**
+ * List all files from the Enigma filestore.
+ *
+ * @param string $email User email address
+ *
+ * @return array List of Enigma filestore records
+ */
+ public static function enigmaList(string $email): array
+ {
+ return self::dbh()->table(self::FILESTORE_TABLE)
+ ->where('user_id', self::userId($email))
+ ->where('context', 'enigma')
+ ->orderBy('filename')
+ ->get()
+ ->all();
+ }
+
+ /**
+ * Synchronize Enigma filestore from/to specified directory
+ *
+ * @param string $email User email address
+ * @param string $homedir Directory location
+ */
+ public static function enigmaSync(string $email, string $homedir): void
+ {
+ $db = self::dbh();
+ $debug = \config('app.debug');
+ $user_id = self::userId($email);
+ $root = \config('filesystems.disks.pgp.root');
+ $fs = Storage::disk('pgp');
+ $files = [];
+
+ $result = $db->table(self::FILESTORE_TABLE)->select('file_id', 'filename', 'mtime')
+ ->where('user_id', $user_id)
+ ->where('context', 'enigma')
+ ->get();
+
+ foreach ($result as $record) {
+ $file = $homedir . '/' . $record->filename;
+ $mtime = $fs->exists($file) ? $fs->lastModified($file) : 0;
+ $files[] = $record->filename;
+
+ if ($mtime < $record->mtime) {
+ $record = $db->table(self::FILESTORE_TABLE)->select('file_id', 'data', 'mtime')
+ ->where('file_id', $record->file_id)
+ ->first();
+
+ $data = $record ? base64_decode($record->data) : false;
+
+ if ($data === false) {
+ \Log::error("Failed to sync $file ({$record->file_id}). Decode error.");
+ continue;
+ }
+
+ if ($fs->put($file, $data, true)) {
+ // Note: Laravel Filesystem API does not provide touch method
+ touch("$root/$file", $record->mtime);
+
+ if ($debug) {
+ \Log::debug("[SYNC] Fetched file: $file");
+ }
+ }
+ }
+ }
+
+ // Remove files not in database
+ foreach (array_diff(self::enigmaFilesList($homedir), $files) as $file) {
+ $file = $homedir . '/' . $file;
+
+ if ($fs->delete($file)) {
+ if ($debug) {
+ \Log::debug("[SYNC] Removed file: $file");
+ }
+ }
+ }
+
+ // No records found, do initial sync if already have the keyring
+ if (empty($file)) {
+ self::enigmaSave(true, $homedir);
+ }
+ }
+
+ /**
+ * Save the keys database
+ *
+ * @param string $email User email address
+ * @param string $homedir Directory location
+ * @param bool $is_empty Set to Tre if it is a initial save
+ */
+ public static function enigmaSave(string $email, string $homedir, bool $is_empty = false): void
+ {
+ $db = self::dbh();
+ $debug = \config('app.debug');
+ $user_id = self::userId($email);
+ $fs = Storage::disk('pgp');
+ $records = [];
+
+ if (!$is_empty) {
+ $records = $db->table(self::FILESTORE_TABLE)->select('file_id', 'filename', 'mtime')
+ ->where('user_id', $user_id)
+ ->where('context', 'enigma')
+ ->get()
+ ->keyBy('filename')
+ ->all();
+ }
+
+ foreach (self::enigmaFilesList($homedir) as $filename) {
+ $file = $homedir . '/' . $filename;
+ $mtime = $fs->exists($file) ? $fs->lastModified($file) : 0;
+
+ $existing = !empty($records[$filename]) ? $records[$filename] : null;
+ unset($records[$filename]);
+
+ if ($mtime && (empty($existing) || $mtime > $existing->mtime)) {
+ $data = base64_encode($fs->get($file));
+/*
+ if (empty($maxsize)) {
+ $maxsize = min($db->get_variable('max_allowed_packet', 1048500), 4*1024*1024) - 2000;
+ }
+
+ if (strlen($data) > $maxsize) {
+ \Log::error("Failed to save $file. Size exceeds max_allowed_packet.");
+ continue;
+ }
+*/
+ $result = $db->table(self::FILESTORE_TABLE)->updateOrInsert(
+ ['user_id' => $user_id, 'context' => 'enigma', 'filename' => $filename],
+ ['mtime' => $mtime, 'data' => $data]
+ );
+
+ if ($debug) {
+ \Log::debug("[SYNC] Pushed file: $file");
+ }
+ }
+ }
+
+ // Delete removed files from database
+ foreach (array_keys($records) as $filename) {
+ $file = $homedir . '/' . $filename;
+ $result = $db->table(self::FILESTORE_TABLE)
+ ->where('user_id', $user_id)
+ ->where('context', 'enigma')
+ ->where('filename', $filename)
+ ->delete();
+
+ if ($debug) {
+ \Log::debug("[SYNC] Removed file: $file");
+ }
+ }
+ }
+
+ /**
+ * Find the Roundcube user identifier for the specified user.
+ *
+ * @param string $email User email address
+ * @param bool $create Make sure the user record exists
+ *
+ * @returns ?int Roundcube user identifier
+ */
+ public static function userId(string $email, bool $create = true): ?int
+ {
+ $db = self::dbh();
+
+ $user = $db->table(self::USERS_TABLE)->select('user_id')
+ ->where('username', \strtolower($email))
+ ->first();
+
+ // Create a user record, without it we can't use the Roundcube storage
+ if (empty($user)) {
+ if (!$create) {
+ return null;
+ }
+
+ $uri = \parse_url(\config('imap.uri'));
+
+ return (int) $db->table(self::USERS_TABLE)->insertGetId(
+ [
+ 'username' => $email,
+ 'mail_host' => $uri['host'],
+ 'created' => now()->toDateTimeString(),
+ ],
+ 'user_id'
+ );
+ }
+
+ return (int) $user->user_id;
+ }
+
+ /**
+ * Returns list of Enigma user homedir files to backup/sync
+ */
+ private static function enigmaFilesList(string $homedir)
+ {
+ $files = [];
+ $fs = Storage::disk('pgp');
+
+ foreach (self::$enigma_files as $file) {
+ if ($fs->exists($homedir . '/' . $file)) {
+ $files[] = $file;
+ }
+ }
+
+ foreach ($fs->files($homedir . '/private-keys-v1.d') as $file) {
+ if (preg_match('/\.key$/', $file)) {
+ $files[] = substr($file, strlen($homedir . '/'));
+ }
+ }
+
+ return $files;
+ }
+}
diff --git a/src/app/Jobs/PGP/KeyCreateJob.php b/src/app/Jobs/PGP/KeyCreateJob.php
new file mode 100644
--- /dev/null
+++ b/src/app/Jobs/PGP/KeyCreateJob.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace App\Jobs\PGP;
+
+use App\Jobs\UserJob;
+
+/**
+ * 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
+ */
+class KeyCreateJob extends UserJob
+{
+ /**
+ * Create a new job instance.
+ *
+ * @param int $userId User identifier.
+ * @param string $userEmail User email address for the key
+ *
+ * @return void
+ */
+ public function __construct(int $userId, string $userEmail)
+ {
+ $this->userId = $userId;
+ $this->userEmail = $userEmail;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ *
+ * @throws \Exception
+ */
+ public function handle()
+ {
+ $user = $this->getUser();
+
+ if (!$user) {
+ return;
+ }
+
+ // sanity checks
+ if ($user->isDeleted()) {
+ $this->fail(new \Exception("User {$this->userId} is marked as deleted."));
+ return;
+ }
+
+ if ($user->trashed()) {
+ $this->fail(new \Exception("User {$this->userId} is actually deleted."));
+ return;
+ }
+
+ if (
+ $this->userEmail != $user->email
+ && !$user->aliases()->where('alias', $this->userEmail)->exists()
+ ) {
+ $this->fail(new \Exception("Alias {$this->userEmail} is actually deleted."));
+ return;
+ }
+
+ \App\Backends\PGP::keypairCreate($user, $this->userEmail);
+ }
+}
diff --git a/src/app/Jobs/PGP/KeyUnregisterJob.php b/src/app/Jobs/PGP/KeyUnregisterJob.php
new file mode 100644
--- /dev/null
+++ b/src/app/Jobs/PGP/KeyUnregisterJob.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Jobs\PGP;
+
+use App\Jobs\CommonJob;
+
+/**
+ * Remove the GPG key from the WOAT DNS system.
+ */
+class KeyUnregisterJob extends CommonJob
+{
+ /**
+ * The email property.
+ *
+ * @var string
+ */
+ protected $email;
+
+ /**
+ * Create a new job instance.
+ *
+ * @param string $email User email address for the key
+ *
+ * @return void
+ */
+ public function __construct(string $email)
+ {
+ $this->email = $email;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ *
+ * @throws \Exception
+ */
+ public function handle()
+ {
+ \App\Backends\PGP::keyUnregister($this->email);
+ }
+}
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
@@ -51,6 +51,10 @@
{
if ($alias->user) {
\App\Jobs\User\UpdateJob::dispatch($alias->user_id);
+
+ if (\config('pgp.enable')) {
+ \App\Jobs\PGP\KeyCreateJob::dispatch($alias->user_id, $alias->alias);
+ }
}
}
@@ -79,6 +83,10 @@
{
if ($alias->user) {
\App\Jobs\User\UpdateJob::dispatch($alias->user_id);
+
+ if (\config('pgp.enable')) {
+ \App\Jobs\PGP\KeyUnregisterJob::dispatch($alias->alias);
+ }
}
}
}
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
@@ -87,6 +87,10 @@
];
\App\Jobs\User\CreateJob::withChain($chain)->dispatch($user->id);
+
+ if (\config('pgp.enable')) {
+ \App\Jobs\PGP\KeyCreateJob::dispatch($user->id, $user->email);
+ }
}
/**
diff --git a/src/composer.json b/src/composer.json
--- a/src/composer.json
+++ b/src/composer.json
@@ -27,6 +27,7 @@
"mlocati/spf-lib": "^3.0",
"mollie/laravel-mollie": "^2.9",
"morrislaptop/laravel-queue-clear": "^1.2",
+ "pear/crypt_gpg": "dev-master",
"silviolleite/laravelpwa": "^2.0",
"spatie/laravel-translatable": "^4.2",
"spomky-labs/otphp": "~4.0.0",
diff --git a/src/config/2fa.php b/src/config/2fa.php
--- a/src/config/2fa.php
+++ b/src/config/2fa.php
@@ -9,6 +9,4 @@
'issuer' => env('APP_NAME', 'Laravel'),
],
- 'dsn' => env('MFA_DSN'),
-
];
diff --git a/src/config/database.php b/src/config/database.php
--- a/src/config/database.php
+++ b/src/config/database.php
@@ -96,6 +96,10 @@
'prefix' => '',
'prefix_indexes' => true,
],
+
+ 'roundcube' => [
+ 'url' => env('DB_ROUNDCUBE_URL', env('MFA_DSN')),
+ ],
],
/*
diff --git a/src/config/filesystems.php b/src/config/filesystems.php
--- a/src/config/filesystems.php
+++ b/src/config/filesystems.php
@@ -48,6 +48,11 @@
'root' => storage_path('app'),
],
+ 'pgp' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app/keys'),
+ ],
+
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
diff --git a/src/config/pgp.php b/src/config/pgp.php
new file mode 100644
--- /dev/null
+++ b/src/config/pgp.php
@@ -0,0 +1,20 @@
+<?php
+
+return [
+
+ // Enables PGP keypair generation on user creation
+ 'enable' => env('PGP_ENABLE', false),
+
+ // gpg binary location
+ 'binary' => env('PGP_BINARY'),
+
+ // gpg-agent location
+ 'agent' => env('PGP_AGENT'),
+
+ // gpgconf location
+ 'gpgconf' => env('PGP_GPGCONF'),
+
+ // Default size of the new RSA key
+ 'length' => (int) env('PGP_LENGTH', 3072),
+
+];
diff --git a/src/phpunit.xml b/src/phpunit.xml
--- a/src/phpunit.xml
+++ b/src/phpunit.xml
@@ -41,5 +41,6 @@
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="SWOOLE_HTTP_ACCESS_LOG" value="false"/>
+ <server name="PGP_LENGTH" value="1024"/>
</php>
</phpunit>
diff --git a/src/tests/Feature/Jobs/PGP/KeyCreateTest.php b/src/tests/Feature/Jobs/PGP/KeyCreateTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Jobs/PGP/KeyCreateTest.php
@@ -0,0 +1,123 @@
+<?php
+
+namespace Tests\Feature\Jobs\PGP;
+
+use App\Backends\PGP;
+use App\Backends\Roundcube;
+use App\User;
+use App\UserAlias;
+use Illuminate\Support\Facades\Queue;
+use Tests\TestCase;
+
+class KeyCreateTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $user = $this->getTestUser('john@kolab.org');
+ UserAlias::where('alias', 'test-alias@kolab.org')->delete();
+ PGP::homedirCleanup($user);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ $user = $this->getTestUser('john@kolab.org');
+ UserAlias::where('alias', 'test-alias@kolab.org')->delete();
+ PGP::homedirCleanup($user);
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test job handle
+ *
+ * @group pgp
+ */
+ public function testHandle(): void
+ {
+ $user = $this->getTestUser('john@kolab.org');
+
+ $job = new \App\Jobs\PGP\KeyCreateJob($user->id, $user->email);
+ $job->handle();
+
+ // Assert the Enigma storage has been initialized and contains the key
+ $files = Roundcube::enigmaList($user->email);
+ // TODO: More detailed asserts on the filestore content, but it's specific to GPG version
+ $this->assertTrue(count($files) > 1);
+
+ // Assert the created keypair parameters
+ $keys = PGP::listKeys($user);
+
+ $this->assertCount(1, $keys);
+
+ $userIds = $keys[0]->getUserIds();
+ $this->assertCount(1, $userIds);
+ $this->assertSame($user->email, $userIds[0]->getEmail());
+ $this->assertSame('', $userIds[0]->getName());
+ $this->assertSame('', $userIds[0]->getComment());
+ $this->assertSame(true, $userIds[0]->isValid());
+ $this->assertSame(false, $userIds[0]->isRevoked());
+
+ $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());
+ $this->assertSame(true, $key->hasPrivate());
+ $this->assertSame(true, $key->canSign());
+ $this->assertSame(false, $key->canEncrypt());
+ $this->assertSame(false, $key->isRevoked());
+
+ $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());
+ $this->assertSame(false, $key->canSign());
+ $this->assertSame(true, $key->canEncrypt());
+ $this->assertSame(false, $key->isRevoked());
+
+ // TODO: Assert the public key in DNS?
+
+ // Test an alias
+ Queue::fake();
+ UserAlias::create(['user_id' => $user->id, 'alias' => 'test-alias@kolab.org']);
+ $job = new \App\Jobs\PGP\KeyCreateJob($user->id, 'test-alias@kolab.org');
+ $job->handle();
+
+ // Assert the created keypair parameters
+ $keys = PGP::listKeys($user);
+
+ $this->assertCount(2, $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->assertSame(true, $userIds[0]->isValid());
+ $this->assertSame(false, $userIds[0]->isRevoked());
+
+ $key = $keys[1]->getPrimaryKey();
+ $this->assertSame(\Crypt_GPG_SubKey::ALGORITHM_RSA, $key->getAlgorithm());
+ $this->assertSame(0, $key->getExpirationDate());
+ $this->assertSame((int) \config('pgp.length'), $key->getLength());
+ $this->assertSame(true, $key->hasPrivate());
+ $this->assertSame(true, $key->canSign());
+ $this->assertSame(false, $key->canEncrypt());
+ $this->assertSame(false, $key->isRevoked());
+
+ $key = $keys[1]->getSubKeys()[1];
+ $this->assertSame(\Crypt_GPG_SubKey::ALGORITHM_RSA, $key->getAlgorithm());
+ $this->assertSame(0, $key->getExpirationDate());
+ $this->assertSame((int) \config('pgp.length'), $key->getLength());
+ $this->assertSame(false, $key->canSign());
+ $this->assertSame(true, $key->canEncrypt());
+ $this->assertSame(false, $key->isRevoked());
+ }
+}
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
@@ -25,6 +25,8 @@
public function tearDown(): void
{
+ \config(['pgp.enable' => false]);
+
$this->deleteTestUser('user-test@' . \config('app.domain'));
$this->deleteTestUser('UserAccountA@UserAccount.com');
$this->deleteTestUser('UserAccountB@UserAccount.com');
@@ -247,15 +249,14 @@
*/
public function testCreateJobs(): void
{
- // Fake the queue, assert that no jobs were pushed...
Queue::fake();
- Queue::assertNothingPushed();
$user = User::create([
'email' => 'user-test@' . \config('app.domain')
]);
Queue::assertPushed(\App\Jobs\User\CreateJob::class, 1);
+ Queue::assertPushed(\App\Jobs\PGP\KeyCreateJob::class, 0);
Queue::assertPushed(
\App\Jobs\User\CreateJob::class,
@@ -291,6 +292,35 @@
*/
}
+ /**
+ * Verify user creation process invokes the PGP keys creation job (if configured)
+ */
+ public function testCreatePGPJob(): void
+ {
+ Queue::fake();
+
+ \config(['pgp.enable' => true]);
+
+ $user = User::create([
+ 'email' => 'user-test@' . \config('app.domain')
+ ]);
+
+ Queue::assertPushed(\App\Jobs\PGP\KeyCreateJob::class, 1);
+
+ Queue::assertPushed(
+ \App\Jobs\PGP\KeyCreateJob::class,
+ function ($job) use ($user) {
+ $userEmail = TestCase::getObjectProperty($job, 'userEmail');
+ $userId = TestCase::getObjectProperty($job, 'userId');
+
+ return $userEmail === $user->email
+ && $userId === $user->id;
+ }
+ );
+
+ \config(['pgp.enable' => false]);
+ }
+
/**
* Tests for User::domains()
*/
@@ -576,6 +606,8 @@
$this->assertInstanceOf(User::class, $result);
$this->assertSame($user->id, $result->id);
+ Queue::fake();
+
// A case where two users have the same alias
$ned = $this->getTestUser('ned@kolab.org');
$ned->setAliases(['joe.monster@kolab.org']);
@@ -739,10 +771,15 @@
$this->assertCount(0, $user->aliases->all());
+ \config(['pgp.enable' => true]);
+
// Add an alias
$user->setAliases(['UserAlias1@UserAccount.com']);
Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 1);
+ Queue::assertPushed(\App\Jobs\PGP\KeyCreateJob::class, 1);
+
+ \config(['pgp.enable' => false]);
$aliases = $user->aliases()->get();
$this->assertCount(1, $aliases);
@@ -752,16 +789,22 @@
$user->setAliases(['UserAlias1@UserAccount.com', 'UserAlias2@UserAccount.com']);
Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 2);
+ Queue::assertPushed(\App\Jobs\PGP\KeyCreateJob::class, 1);
$aliases = $user->aliases()->orderBy('alias')->get();
$this->assertCount(2, $aliases);
$this->assertSame('useralias1@useraccount.com', $aliases[0]->alias);
$this->assertSame('useralias2@useraccount.com', $aliases[1]->alias);
+ \config(['pgp.enable' => true]);
+
// Remove an alias
$user->setAliases(['UserAlias1@UserAccount.com']);
+ \config(['pgp.enable' => false]);
+
Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 3);
+ Queue::assertPushed(\App\Jobs\PGP\KeyUnregisterJob::class, 1);
$aliases = $user->aliases()->get();
$this->assertCount(1, $aliases);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Apr 3, 5:02 AM (22 h, 26 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18822652
Default Alt Text
D2554.1775192542.diff (29 KB)
Attached To
Mode
D2554: Add PGP keys generator
Attached
Detach File
Event Timeline