Page MenuHomePhorge

D3785.1775265632.diff
No OneTemporary

Authored By
Unknown
Size
95 KB
Referenced Files
None
Subscribers
None

D3785.1775265632.diff

diff --git a/src/app/Backends/IMAP.php b/src/app/Backends/IMAP.php
--- a/src/app/Backends/IMAP.php
+++ b/src/app/Backends/IMAP.php
@@ -3,31 +3,369 @@
namespace App\Backends;
use App\Domain;
+use App\Group;
+use App\Resource;
+use App\SharedFolder;
use App\User;
class IMAP
{
+ /** @const array Maps Kolab permissions to IMAP permissions */
+ private const ACL_MAP = [
+ 'read-only' => 'lrs',
+ 'read-write' => 'lrswitedn',
+ 'full' => 'lrswipkxtecdn',
+ ];
+
/**
- * Check if an account is set up
+ * Delete a group (cleanup ACL).
*
- * @param string $username User login (email address)
+ * @param \App\Group $group Group
*
- * @return bool True if an account exists and is set up, False otherwise
+ * @return bool True if a group was deleted successfully, False otherwise
+ * @throws \Exception
*/
- public static function verifyAccount(string $username): bool
+ public static function deleteGroup(Group $group): bool
{
$config = self::getConfig();
- $imap = self::initIMAP($config, $username);
+ $imap = self::initIMAP($config);
- $folders = $imap->listMailboxes('', '*');
+ // TODO: Cleanup ACL
$imap->closeConnection();
- if (!is_array($folders)) {
- throw new \Exception("Failed to get IMAP folders");
+ return true;
+ }
+
+ /**
+ * Create a mailbox.
+ *
+ * @param \App\User $user User
+ *
+ * @return bool True if a mailbox was created successfully, False otherwise
+ * @throws \Exception
+ */
+ public static function createUser(User $user): bool
+ {
+ $config = self::getConfig();
+ $imap = self::initIMAP($config);
+
+ $mailbox = self::toUTF7('user/' . $user->email);
+
+ // Mailbox already exists
+ if (self::folderExists($imap, $mailbox)) {
+ $imap->closeConnection();
+ return true;
}
- return count($folders) > 0;
+ // Create the mailbox
+ if (!$imap->createFolder($mailbox)) {
+ \Log::error("Failed to create mailbox {$mailbox}");
+ $imap->closeConnection();
+ return false;
+ }
+
+ // Wait until it's propagated (for Cyrus Murder setup)
+ // FIXME: Do we still need this?
+ if (strpos($imap->conn->data['GREETING'] ?? '', 'Cyrus IMAP Murder') !== false) {
+ $tries = 30;
+ while ($tries-- > 0) {
+ $folders = $imap->listMailboxes('', $mailbox);
+ if (is_array($folders) && count($folders)) {
+ break;
+ }
+ sleep(1);
+ $imap->closeConnection();
+ $imap = self::initIMAP($config);
+ }
+ }
+
+ // Set quota
+ $quota = $user->countEntitlementsBySku('storage') * 1048576;
+ if ($quota) {
+ $imap->setQuota($mailbox, ['storage' => $quota]);
+ }
+
+ $imap->closeConnection();
+
+ return true;
+ }
+
+ /**
+ * Delete a mailbox.
+ *
+ * @param \App\User $user User
+ *
+ * @return bool True if a mailbox was deleted successfully, False otherwise
+ * @throws \Exception
+ */
+ public static function deleteUser(User $user): bool
+ {
+ $config = self::getConfig();
+ $imap = self::initIMAP($config);
+
+ $mailbox = self::toUTF7('user/' . $user->email);
+
+ // To delete the mailbox cyrus-admin needs extra permissions
+ $imap->setACL($mailbox, $config['user'], 'c');
+
+ // Delete the mailbox (no need to delete subfolders?)
+ $result = $imap->deleteFolder($mailbox);
+
+ // TODO: Cleanup ACL
+
+ $imap->closeConnection();
+
+ return $result;
+ }
+
+ /**
+ * Update a mailbox (quota).
+ *
+ * @param \App\User $user User
+ *
+ * @return bool True if a mailbox was updated successfully, False otherwise
+ * @throws \Exception
+ */
+ public static function updateUser(User $user): bool
+ {
+ $config = self::getConfig();
+ $imap = self::initIMAP($config);
+
+ $mailbox = self::toUTF7('user/' . $user->email);
+ $result = true;
+
+ // Set quota
+ $quota = $user->countEntitlementsBySku('storage') * 1048576;
+ if ($quota) {
+ $result = $imap->setQuota($mailbox, ['storage' => $quota]);
+ }
+
+ $imap->closeConnection();
+
+ return $result;
+ }
+
+ /**
+ * Create a resource.
+ *
+ * @param \App\Resource $resource Resource
+ *
+ * @return bool True if a resource was created successfully, False otherwise
+ * @throws \Exception
+ */
+ public static function createResource(Resource $resource): bool
+ {
+ $config = self::getConfig();
+ $imap = self::initIMAP($config);
+
+ $settings = $resource->getSettings(['invitation_policy', 'folder']);
+ $mailbox = self::toUTF7($settings['folder']);
+
+ // Mailbox already exists
+ if (self::folderExists($imap, $mailbox)) {
+ $imap->closeConnection();
+ return true;
+ }
+
+ // Create the shared folder
+ if (!$imap->createFolder($mailbox)) {
+ \Log::error("Failed to create mailbox {$mailbox}");
+ $imap->closeConnection();
+ return false;
+ }
+
+ // Set folder type
+ $imap->setMetadata($mailbox, ['/shared/vendor/kolab/folder-type' => 'event']);
+
+ // Set ACL
+ if (!empty($settings['invitation_policy'])) {
+ if (preg_match('/^manual:(\S+@\S+)$/', $settings['invitation_policy'], $m)) {
+ self::aclUpdate($imap, $mailbox, ["{$m[1]}, full"]);
+ }
+ }
+
+ $imap->closeConnection();
+
+ return true;
+ }
+
+ /**
+ * Update a resource.
+ *
+ * @param \App\Resource $resource Resource
+ * @param array $props Old resource properties
+ *
+ * @return bool True if a resource was updated successfully, False otherwise
+ * @throws \Exception
+ */
+ public static function updateResource(Resource $resource, array $props = []): bool
+ {
+ $config = self::getConfig();
+ $imap = self::initIMAP($config);
+
+ $settings = $resource->getSettings(['invitation_policy', 'folder']);
+ $folder = $settings['folder'];
+ $mailbox = self::toUTF7($folder);
+
+ // Rename the mailbox (only possible if we have the old folder)
+ if (!empty($props['folder']) && $props['folder'] != $folder) {
+ $oldMailbox = self::toUTF7($props['folder']);
+
+ if (!$imap->renameFolder($oldMailbox, $mailbox)) {
+ \Log::error("Failed to rename mailbox {$oldMailbox} to {$mailbox}");
+ $imap->closeConnection();
+ return false;
+ }
+ }
+
+ // ACL
+ $acl = [];
+ if (!empty($settings['invitation_policy'])) {
+ if (preg_match('/^manual:(\S+@\S+)$/', $settings['invitation_policy'], $m)) {
+ $acl = ["{$m[1]}, full"];
+ }
+ }
+ self::aclUpdate($imap, $mailbox, $acl);
+
+ $imap->closeConnection();
+
+ return true;
+ }
+
+ /**
+ * Delete a resource.
+ *
+ * @param \App\Resource $resource Resource
+ *
+ * @return bool True if a resource was deleted successfully, False otherwise
+ * @throws \Exception
+ */
+ public static function deleteResource(Resource $resource): bool
+ {
+ $config = self::getConfig();
+ $imap = self::initIMAP($config);
+
+ $settings = $resource->getSettings(['folder']);
+ $mailbox = self::toUTF7($settings['folder']);
+
+ // To delete the mailbox cyrus-admin needs extra permissions
+ $imap->setACL($mailbox, $config['user'], 'c');
+
+ // Delete the mailbox (no need to delete subfolders?)
+ $result = $imap->deleteFolder($mailbox);
+
+ $imap->closeConnection();
+
+ return $result;
+ }
+
+ /**
+ * Create a shared folder.
+ *
+ * @param \App\SharedFolder $folder Shared folder
+ *
+ * @return bool True if a falder was created successfully, False otherwise
+ * @throws \Exception
+ */
+ public static function createSharedFolder(SharedFolder $folder): bool
+ {
+ $config = self::getConfig();
+ $imap = self::initIMAP($config);
+
+ $settings = $folder->getSettings(['acl', 'folder']);
+ $acl = !empty($settings['acl']) ? json_decode($settings['acl'], true) : null;
+ $mailbox = self::toUTF7($settings['folder']);
+
+ // Mailbox already exists
+ if (self::folderExists($imap, $mailbox)) {
+ $imap->closeConnection();
+ return true;
+ }
+
+ // Create the mailbox
+ if (!$imap->createFolder($mailbox)) {
+ \Log::error("Failed to create mailbox {$mailbox}");
+ $imap->closeConnection();
+ return false;
+ }
+
+ // Set folder type
+ $imap->setMetadata($mailbox, ['/shared/vendor/kolab/folder-type' => $folder->type]);
+
+ // Set ACL
+ self::aclUpdate($imap, $mailbox, $acl);
+
+ $imap->closeConnection();
+
+ return true;
+ }
+
+ /**
+ * Update a shared folder.
+ *
+ * @param \App\SharedFolder $folder Shared folder
+ * @param array $props Old folder properties
+ *
+ * @return bool True if a falder was updated successfully, False otherwise
+ * @throws \Exception
+ */
+ public static function updateSharedFolder(SharedFolder $folder, array $props = []): bool
+ {
+ $config = self::getConfig();
+ $imap = self::initIMAP($config);
+
+ $settings = $folder->getSettings(['acl', 'folder']);
+ $acl = !empty($settings['acl']) ? json_decode($settings['acl'], true) : null;
+ $folder = $settings['folder'];
+ $mailbox = self::toUTF7($folder);
+
+ // Rename the mailbox
+ if (!empty($props['folder']) && $props['folder'] != $folder) {
+ $oldMailbox = self::toUTF7($props['folder']);
+
+ if (!$imap->renameFolder($oldMailbox, $mailbox)) {
+ \Log::error("Failed to rename mailbox {$oldMailbox} to {$mailbox}");
+ $imap->closeConnection();
+ return false;
+ }
+ }
+
+ // Note: Shared folder type does not change
+
+ // ACL
+ self::aclUpdate($imap, $mailbox, $acl);
+
+ $imap->closeConnection();
+
+ return true;
+ }
+
+ /**
+ * Delete a shared folder.
+ *
+ * @param \App\SharedFolder $folder Shared folder
+ *
+ * @return bool True if a falder was deleted successfully, False otherwise
+ * @throws \Exception
+ */
+ public static function deleteSharedFolder(SharedFolder $folder): bool
+ {
+ $config = self::getConfig();
+ $imap = self::initIMAP($config);
+
+ $settings = $folder->getSettings(['folder']);
+ $mailbox = self::toUTF7($settings['folder']);
+
+ // To delete the mailbox cyrus-admin needs extra permissions
+ $imap->setACL($mailbox, $config['user'], 'c');
+
+ // Delete the mailbox
+ $result = $imap->deleteFolder($mailbox);
+
+ $imap->closeConnection();
+
+ return $result;
}
/**
@@ -44,7 +382,7 @@
// Convert the folder from UTF8 to UTF7-IMAP
if (\preg_match('#^(shared/|shared/Resources/)(.+)(@[^@]+)$#', $folder, $matches)) {
- $folderName = \mb_convert_encoding($matches[2], 'UTF7-IMAP', 'UTF8');
+ $folderName = self::toUTF7($matches[2]);
$folder = $matches[1] . $folderName . $matches[3];
}
@@ -67,6 +405,91 @@
return true;
}
+ /**
+ * Convert UTF8 string to UTF7-IMAP encoding
+ */
+ public static function toUTF7(string $string): string
+ {
+ return \mb_convert_encoding($string, 'UTF7-IMAP', 'UTF8');
+ }
+
+ /**
+ * Check if an account is set up
+ *
+ * @param string $username User login (email address)
+ *
+ * @return bool True if an account exists and is set up, False otherwise
+ */
+ public static function verifyAccount(string $username): bool
+ {
+ $config = self::getConfig();
+ $imap = self::initIMAP($config, $username);
+
+ $folders = $imap->listMailboxes('', '*');
+
+ $imap->closeConnection();
+
+ if (!is_array($folders)) {
+ throw new \Exception("Failed to get IMAP folders");
+ }
+
+ return count($folders) > 0;
+ }
+
+ /**
+ * Convert Kolab ACL into IMAP user->rights array
+ */
+ private static function aclToImap($acl): array
+ {
+ if (empty($acl)) {
+ return [];
+ }
+
+ return \collect($acl)
+ ->mapWithKeys(function ($item, $key) {
+ list($user, $rights) = explode(',', $item, 2);
+ return [trim($user) => self::ACL_MAP[trim($rights)]];
+ })
+ ->all();
+ }
+
+ /**
+ * Update folder ACL
+ */
+ private static function aclUpdate($imap, $mailbox, $acl, bool $isNew = false)
+ {
+ $imapAcl = $isNew ? [] : $imap->getACL($mailbox);
+
+ if (is_array($imapAcl)) {
+ foreach (self::aclToImap($acl) as $user => $rights) {
+ if (empty($imapAcl[$user]) || implode('', $imapAcl[$user]) !== $rights) {
+ $imap->setACL($mailbox, $user, $rights);
+ }
+
+ unset($imapAcl[$user]);
+ }
+
+ foreach ($imapAcl as $user => $rights) {
+ $imap->deleteACL($mailbox, $user);
+ }
+ }
+ }
+
+ /**
+ * Check if an IMAP folder exists
+ */
+ private static function folderExists($imap, string $folder): bool
+ {
+ $folders = $imap->listMailboxes('', $folder);
+
+ if (!is_array($folders)) {
+ $imap->closeConnection();
+ throw new \Exception("Failed to get IMAP folders");
+ }
+
+ return count($folders) > 0;
+ }
+
/**
* Initialize connection to IMAP
*/
diff --git a/src/app/Console/Commands/Job/DomainCreate.php b/src/app/Console/Commands/Job/DomainCreate.php
--- a/src/app/Console/Commands/Job/DomainCreate.php
+++ b/src/app/Console/Commands/Job/DomainCreate.php
@@ -3,7 +3,6 @@
namespace App\Console\Commands\Job;
use App\Console\Command;
-use App\Domain;
class DomainCreate extends Command
{
@@ -19,7 +18,7 @@
*
* @var string
*/
- protected $description = "Execute the DomainCreate job (again).";
+ protected $description = "Execute the domain creation job (again).";
/**
* Execute the console command.
diff --git a/src/app/Console/Commands/Job/DomainUpdate.php b/src/app/Console/Commands/Job/DomainUpdate.php
--- a/src/app/Console/Commands/Job/DomainUpdate.php
+++ b/src/app/Console/Commands/Job/DomainUpdate.php
@@ -3,7 +3,6 @@
namespace App\Console\Commands\Job;
use App\Console\Command;
-use App\Domain;
class DomainUpdate extends Command
{
@@ -19,7 +18,7 @@
*
* @var string
*/
- protected $description = "Execute the DomainUpdate job (again).";
+ protected $description = "Execute the domain update job (again).";
/**
* Execute the console command.
diff --git a/src/app/Console/Commands/Job/UserUpdate.php b/src/app/Console/Commands/Job/ResourceCreate.php
copy from src/app/Console/Commands/Job/UserUpdate.php
copy to src/app/Console/Commands/Job/ResourceCreate.php
--- a/src/app/Console/Commands/Job/UserUpdate.php
+++ b/src/app/Console/Commands/Job/ResourceCreate.php
@@ -3,23 +3,22 @@
namespace App\Console\Commands\Job;
use App\Console\Command;
-use App\User;
-class UserUpdate extends Command
+class ResourceCreate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'job:userupdate {user}';
+ protected $signature = 'job:resourcecreate {resource}';
/**
* The console command description.
*
* @var string
*/
- protected $description = "Execute the UserUpdate job (again).";
+ protected $description = "Execute the resource creation job (again).";
/**
* Execute the console command.
@@ -28,13 +27,13 @@
*/
public function handle()
{
- $user = $this->getUser($this->argument('user'));
+ $resource = $this->getResource($this->argument('resource'));
- if (!$user) {
+ if (!$resource) {
return 1;
}
- $job = new \App\Jobs\User\UpdateJob($user->id);
+ $job = new \App\Jobs\Resource\CreateJob($resource->id);
$job->handle();
}
}
diff --git a/src/app/Console/Commands/Job/UserUpdate.php b/src/app/Console/Commands/Job/ResourceUpdate.php
copy from src/app/Console/Commands/Job/UserUpdate.php
copy to src/app/Console/Commands/Job/ResourceUpdate.php
--- a/src/app/Console/Commands/Job/UserUpdate.php
+++ b/src/app/Console/Commands/Job/ResourceUpdate.php
@@ -3,23 +3,22 @@
namespace App\Console\Commands\Job;
use App\Console\Command;
-use App\User;
-class UserUpdate extends Command
+class ResourceUpdate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'job:userupdate {user}';
+ protected $signature = 'job:resourceupdate {resource}';
/**
* The console command description.
*
* @var string
*/
- protected $description = "Execute the UserUpdate job (again).";
+ protected $description = "Execute the resource update job (again).";
/**
* Execute the console command.
@@ -28,13 +27,13 @@
*/
public function handle()
{
- $user = $this->getUser($this->argument('user'));
+ $resource = $this->getResource($this->argument('resource'));
- if (!$user) {
+ if (!$resource) {
return 1;
}
- $job = new \App\Jobs\User\UpdateJob($user->id);
+ $job = new \App\Jobs\Resource\UpdateJob($resource->id);
$job->handle();
}
}
diff --git a/src/app/Console/Commands/Job/UserUpdate.php b/src/app/Console/Commands/Job/SharedFolderCreate.php
copy from src/app/Console/Commands/Job/UserUpdate.php
copy to src/app/Console/Commands/Job/SharedFolderCreate.php
--- a/src/app/Console/Commands/Job/UserUpdate.php
+++ b/src/app/Console/Commands/Job/SharedFolderCreate.php
@@ -3,23 +3,22 @@
namespace App\Console\Commands\Job;
use App\Console\Command;
-use App\User;
-class UserUpdate extends Command
+class SharedFolderCreate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'job:userupdate {user}';
+ protected $signature = 'job:sharedfoldercreate {folder}';
/**
* The console command description.
*
* @var string
*/
- protected $description = "Execute the UserUpdate job (again).";
+ protected $description = "Execute the shared folder creation job (again).";
/**
* Execute the console command.
@@ -28,13 +27,13 @@
*/
public function handle()
{
- $user = $this->getUser($this->argument('user'));
+ $folder = $this->getSharedFolder($this->argument('folder'));
- if (!$user) {
+ if (!$folder) {
return 1;
}
- $job = new \App\Jobs\User\UpdateJob($user->id);
+ $job = new \App\Jobs\SharedFolder\CreateJob($folder->id);
$job->handle();
}
}
diff --git a/src/app/Console/Commands/Job/UserUpdate.php b/src/app/Console/Commands/Job/SharedFolderUpdate.php
copy from src/app/Console/Commands/Job/UserUpdate.php
copy to src/app/Console/Commands/Job/SharedFolderUpdate.php
--- a/src/app/Console/Commands/Job/UserUpdate.php
+++ b/src/app/Console/Commands/Job/SharedFolderUpdate.php
@@ -3,23 +3,22 @@
namespace App\Console\Commands\Job;
use App\Console\Command;
-use App\User;
-class UserUpdate extends Command
+class SharedFolderUpdate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'job:userupdate {user}';
+ protected $signature = 'job:sharedfolderupdate {folder}';
/**
* The console command description.
*
* @var string
*/
- protected $description = "Execute the UserUpdate job (again).";
+ protected $description = "Execute the shared folder update job (again).";
/**
* Execute the console command.
@@ -28,13 +27,13 @@
*/
public function handle()
{
- $user = $this->getUser($this->argument('user'));
+ $folder = $this->getSharedFolder($this->argument('folder'));
- if (!$user) {
+ if (!$folder) {
return 1;
}
- $job = new \App\Jobs\User\UpdateJob($user->id);
+ $job = new \App\Jobs\SharedFolder\UpdateJob($folder->id);
$job->handle();
}
}
diff --git a/src/app/Console/Commands/Job/UserCreate.php b/src/app/Console/Commands/Job/UserCreate.php
--- a/src/app/Console/Commands/Job/UserCreate.php
+++ b/src/app/Console/Commands/Job/UserCreate.php
@@ -3,7 +3,6 @@
namespace App\Console\Commands\Job;
use App\Console\Command;
-use App\User;
class UserCreate extends Command
{
@@ -19,7 +18,7 @@
*
* @var string
*/
- protected $description = "Execute the UserCreate job (again).";
+ protected $description = "Execute the user creation job (again).";
/**
* Execute the console command.
diff --git a/src/app/Console/Commands/Job/UserUpdate.php b/src/app/Console/Commands/Job/UserUpdate.php
--- a/src/app/Console/Commands/Job/UserUpdate.php
+++ b/src/app/Console/Commands/Job/UserUpdate.php
@@ -3,7 +3,6 @@
namespace App\Console\Commands\Job;
use App\Console\Command;
-use App\User;
class UserUpdate extends Command
{
@@ -19,7 +18,7 @@
*
* @var string
*/
- protected $description = "Execute the UserUpdate job (again).";
+ protected $description = "Execute the user update job (again).";
/**
* Execute the console command.
diff --git a/src/app/Http/Controllers/API/V4/DomainsController.php b/src/app/Http/Controllers/API/V4/DomainsController.php
--- a/src/app/Http/Controllers/API/V4/DomainsController.php
+++ b/src/app/Http/Controllers/API/V4/DomainsController.php
@@ -299,20 +299,17 @@
* @param \App\Domain $domain Domain object
* @param string $step Step identifier (as in self::statusInfo())
*
- * @return bool True if the execution succeeded, False otherwise
+ * @return bool|null True if the execution succeeded, False if not, Null when
+ * the job has been sent to the worker (result unknown)
*/
- public static function execProcessStep(Domain $domain, string $step): bool
+ public static function execProcessStep(Domain $domain, string $step): ?bool
{
try {
switch ($step) {
case 'domain-ldap-ready':
- // Domain not in LDAP, create it
- if (!$domain->isLdapReady()) {
- LDAP::createDomain($domain);
- $domain->status |= Domain::STATUS_LDAP_READY;
- $domain->save();
- }
- return $domain->isLdapReady();
+ // Use worker to do the job
+ \App\Jobs\Domain\CreateJob::dispatch($domain->id);
+ return null;
case 'domain-verified':
// Domain existence not verified
diff --git a/src/app/Http/Controllers/API/V4/ResourcesController.php b/src/app/Http/Controllers/API/V4/ResourcesController.php
--- a/src/app/Http/Controllers/API/V4/ResourcesController.php
+++ b/src/app/Http/Controllers/API/V4/ResourcesController.php
@@ -160,30 +160,10 @@
switch ($step) {
case 'resource-ldap-ready':
- // Resource not in LDAP, create it
- $job = new \App\Jobs\Resource\CreateJob($resource->id);
- $job->handle();
-
- $resource->refresh();
-
- return $resource->isLdapReady();
-
case 'resource-imap-ready':
- // Resource not in IMAP? Verify again
- // Do it synchronously if the imap admin credentials are available
- // otherwise let the worker do the job
- if (!\config('imap.admin_password')) {
- \App\Jobs\Resource\VerifyJob::dispatch($resource->id);
-
- return null;
- }
-
- $job = new \App\Jobs\Resource\VerifyJob($resource->id);
- $job->handle();
-
- $resource->refresh();
-
- return $resource->isImapReady();
+ // Use worker to do the job, frontend might not have the IMAP admin credentials
+ \App\Jobs\Resource\CreateJob::dispatch($resource->id);
+ return null;
}
} catch (\Exception $e) {
\Log::error($e);
diff --git a/src/app/Http/Controllers/API/V4/SharedFoldersController.php b/src/app/Http/Controllers/API/V4/SharedFoldersController.php
--- a/src/app/Http/Controllers/API/V4/SharedFoldersController.php
+++ b/src/app/Http/Controllers/API/V4/SharedFoldersController.php
@@ -156,30 +156,10 @@
switch ($step) {
case 'shared-folder-ldap-ready':
- // Shared folder not in LDAP, create it
- $job = new \App\Jobs\SharedFolder\CreateJob($folder->id);
- $job->handle();
-
- $folder->refresh();
-
- return $folder->isLdapReady();
-
case 'shared-folder-imap-ready':
- // Shared folder not in IMAP? Verify again
- // Do it synchronously if the imap admin credentials are available
- // otherwise let the worker do the job
- if (!\config('imap.admin_password')) {
- \App\Jobs\SharedFolder\VerifyJob::dispatch($folder->id);
-
- return null;
- }
-
- $job = new \App\Jobs\SharedFolder\VerifyJob($folder->id);
- $job->handle();
-
- $folder->refresh();
-
- return $folder->isImapReady();
+ // Use worker to do the job, frontend might not have the IMAP admin credentials
+ \App\Jobs\SharedFolder\CreateJob::dispatch($folder->id);
+ return null;
}
} catch (\Exception $e) {
\Log::error($e);
diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php
--- a/src/app/Http/Controllers/API/V4/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/UsersController.php
@@ -530,30 +530,10 @@
switch ($step) {
case 'user-ldap-ready':
- // User not in LDAP, create it
- $job = new \App\Jobs\User\CreateJob($user->id);
- $job->handle();
-
- $user->refresh();
-
- return $user->isLdapReady();
-
case 'user-imap-ready':
- // User not in IMAP? Verify again
- // Do it synchronously if the imap admin credentials are available
- // otherwise let the worker do the job
- if (!\config('imap.admin_password')) {
- \App\Jobs\User\VerifyJob::dispatch($user->id);
-
- return null;
- }
-
- $job = new \App\Jobs\User\VerifyJob($user->id);
- $job->handle();
-
- $user->refresh();
-
- return $user->isImapReady();
+ // Use worker to do the job, frontend might not have the IMAP admin credentials
+ \App\Jobs\User\CreateJob::dispatch($user->id);
+ return null;
}
} catch (\Exception $e) {
\Log::error($e);
diff --git a/src/app/Jobs/Group/CreateJob.php b/src/app/Jobs/Group/CreateJob.php
--- a/src/app/Jobs/Group/CreateJob.php
+++ b/src/app/Jobs/Group/CreateJob.php
@@ -23,7 +23,9 @@
\App\Backends\LDAP::createGroup($group);
$group->status |= \App\Group::STATUS_LDAP_READY;
- $group->save();
}
+
+ $group->status |= \App\Group::STATUS_ACTIVE;
+ $group->save();
}
}
diff --git a/src/app/Jobs/Group/DeleteJob.php b/src/app/Jobs/Group/DeleteJob.php
--- a/src/app/Jobs/Group/DeleteJob.php
+++ b/src/app/Jobs/Group/DeleteJob.php
@@ -25,14 +25,13 @@
return;
}
- \App\Backends\LDAP::deleteGroup($group);
-
- $group->status |= \App\Group::STATUS_DELETED;
-
if ($group->isLdapReady()) {
+ \App\Backends\LDAP::deleteGroup($group);
+
$group->status ^= \App\Group::STATUS_LDAP_READY;
}
+ $group->status |= \App\Group::STATUS_DELETED;
$group->save();
}
}
diff --git a/src/app/Jobs/Resource/CreateJob.php b/src/app/Jobs/Resource/CreateJob.php
--- a/src/app/Jobs/Resource/CreateJob.php
+++ b/src/app/Jobs/Resource/CreateJob.php
@@ -30,11 +30,6 @@
return;
}
- if ($resource->isLdapReady()) {
- $this->fail(new \Exception("Resource {$this->resourceId} is already marked as ldap-ready."));
- return;
- }
-
// see if the domain is ready
$domain = $resource->domain();
@@ -53,9 +48,22 @@
return;
}
- \App\Backends\LDAP::createResource($resource);
+ if (!$resource->isLdapReady()) {
+ \App\Backends\LDAP::createResource($resource);
+
+ $resource->status |= \App\Resource::STATUS_LDAP_READY;
+ $resource->save();
+ }
+
+ if (!$resource->isImapReady()) {
+ if (!\App\Backends\IMAP::createResource($resource)) {
+ throw new \Exception("Failed to create mailbox for resource {$this->resourceId}.");
+ }
+
+ $resource->status |= \App\Resource::STATUS_IMAP_READY;
+ }
- $resource->status |= \App\Resource::STATUS_LDAP_READY;
+ $resource->status |= \App\Resource::STATUS_ACTIVE;
$resource->save();
}
}
diff --git a/src/app/Jobs/Resource/DeleteJob.php b/src/app/Jobs/Resource/DeleteJob.php
--- a/src/app/Jobs/Resource/DeleteJob.php
+++ b/src/app/Jobs/Resource/DeleteJob.php
@@ -25,18 +25,22 @@
return;
}
- \App\Backends\LDAP::deleteResource($resource);
-
- $resource->status |= \App\Resource::STATUS_DELETED;
-
if ($resource->isLdapReady()) {
+ \App\Backends\LDAP::deleteResource($resource);
+
$resource->status ^= \App\Resource::STATUS_LDAP_READY;
+ $resource->save();
}
if ($resource->isImapReady()) {
+ if (!\App\Backends\IMAP::deleteResource($resource)) {
+ throw new \Exception("Failed to delete mailbox for resource {$this->resourceId}.");
+ }
+
$resource->status ^= \App\Resource::STATUS_IMAP_READY;
}
+ $resource->status |= \App\Resource::STATUS_DELETED;
$resource->save();
}
}
diff --git a/src/app/Jobs/Resource/UpdateJob.php b/src/app/Jobs/Resource/UpdateJob.php
--- a/src/app/Jobs/Resource/UpdateJob.php
+++ b/src/app/Jobs/Resource/UpdateJob.php
@@ -19,12 +19,20 @@
return;
}
- // Cancel the update if the resource is deleted or not yet in LDAP
- if (!$resource->isLdapReady() || $resource->isDeleted()) {
+ // Cancel the update if the resource is deleted
+ if ($resource->isDeleted()) {
$this->delete();
return;
}
- \App\Backends\LDAP::updateResource($resource);
+ if ($resource->isLdapReady()) {
+ \App\Backends\LDAP::updateResource($resource);
+ }
+
+ if ($resource->isImapReady()) {
+ if (!\App\Backends\IMAP::updateResource($resource, $this->properties)) {
+ throw new \Exception("Failed to update mailbox for resource {$this->resourceId}.");
+ }
+ }
}
}
diff --git a/src/app/Jobs/ResourceJob.php b/src/app/Jobs/ResourceJob.php
--- a/src/app/Jobs/ResourceJob.php
+++ b/src/app/Jobs/ResourceJob.php
@@ -13,6 +13,13 @@
*/
abstract class ResourceJob extends CommonJob
{
+ /**
+ * Old values of the resource properties on update (key -> value)
+ *
+ * @var array
+ */
+ protected $properties = [];
+
/**
* The ID for the \App\Resource. This is the shortest globally unique identifier and saves Redis space
* compared to a serialized version of the complete \App\Resource object.
@@ -31,13 +38,15 @@
/**
* Create a new job instance.
*
- * @param int $resourceId The ID for the resource to process.
+ * @param int $resourceId The ID for the resource to process.
+ * @param array $properties Old values of the resource properties on update
*
* @return void
*/
- public function __construct(int $resourceId)
+ public function __construct(int $resourceId, array $properties = [])
{
$this->resourceId = $resourceId;
+ $this->properties = $properties;
$resource = $this->getResource();
diff --git a/src/app/Jobs/SharedFolder/CreateJob.php b/src/app/Jobs/SharedFolder/CreateJob.php
--- a/src/app/Jobs/SharedFolder/CreateJob.php
+++ b/src/app/Jobs/SharedFolder/CreateJob.php
@@ -30,11 +30,6 @@
return;
}
- if ($folder->isLdapReady()) {
- $this->fail(new \Exception("Shared folder {$this->folderId} is already marked as ldap-ready."));
- return;
- }
-
// see if the domain is ready
$domain = $folder->domain();
@@ -53,9 +48,22 @@
return;
}
- \App\Backends\LDAP::createSharedFolder($folder);
+ if (!$folder->isLdapReady()) {
+ \App\Backends\LDAP::createSharedFolder($folder);
+
+ $folder->status |= \App\SharedFolder::STATUS_LDAP_READY;
+ $folder->save();
+ }
+
+ if (!$folder->isImapReady()) {
+ if (!\App\Backends\IMAP::createSharedFolder($folder)) {
+ throw new \Exception("Failed to create mailbox for shared folder {$this->folderId}.");
+ }
+
+ $folder->status |= \App\SharedFolder::STATUS_IMAP_READY;
+ }
- $folder->status |= \App\SharedFolder::STATUS_LDAP_READY;
+ $folder->status |= \App\SharedFolder::STATUS_ACTIVE;
$folder->save();
}
}
diff --git a/src/app/Jobs/SharedFolder/DeleteJob.php b/src/app/Jobs/SharedFolder/DeleteJob.php
--- a/src/app/Jobs/SharedFolder/DeleteJob.php
+++ b/src/app/Jobs/SharedFolder/DeleteJob.php
@@ -25,18 +25,22 @@
return;
}
- \App\Backends\LDAP::deleteSharedFolder($folder);
-
- $folder->status |= \App\SharedFolder::STATUS_DELETED;
-
if ($folder->isLdapReady()) {
+ \App\Backends\LDAP::deleteSharedFolder($folder);
+
$folder->status ^= \App\SharedFolder::STATUS_LDAP_READY;
+ $folder->save();
}
if ($folder->isImapReady()) {
+ if (!\App\Backends\IMAP::deleteSharedFolder($folder)) {
+ throw new \Exception("Failed to delete mailbox for shared folder {$this->folderId}.");
+ }
+
$folder->status ^= \App\SharedFolder::STATUS_IMAP_READY;
}
+ $folder->status |= \App\SharedFolder::STATUS_DELETED;
$folder->save();
}
}
diff --git a/src/app/Jobs/SharedFolder/UpdateJob.php b/src/app/Jobs/SharedFolder/UpdateJob.php
--- a/src/app/Jobs/SharedFolder/UpdateJob.php
+++ b/src/app/Jobs/SharedFolder/UpdateJob.php
@@ -19,12 +19,20 @@
return;
}
- // Cancel the update if the folder is deleted or not yet in LDAP
- if (!$folder->isLdapReady() || $folder->isDeleted()) {
+ // Cancel the update if the folder is deleted
+ if ($folder->isDeleted()) {
$this->delete();
return;
}
- \App\Backends\LDAP::updateSharedFolder($folder);
+ if ($folder->isLdapReady()) {
+ \App\Backends\LDAP::updateSharedFolder($folder);
+ }
+
+ if ($folder->isImapReady()) {
+ if (!\App\Backends\IMAP::updateSharedFolder($folder, $this->properties)) {
+ throw new \Exception("Failed to update mailbox for shared folder {$this->folderId}.");
+ }
+ }
}
}
diff --git a/src/app/Jobs/SharedFolderJob.php b/src/app/Jobs/SharedFolderJob.php
--- a/src/app/Jobs/SharedFolderJob.php
+++ b/src/app/Jobs/SharedFolderJob.php
@@ -27,16 +27,25 @@
*/
protected $folderEmail;
+ /**
+ * Old values of the shared folder properties on update (key -> value)
+ *
+ * @var array
+ */
+ protected $properties = [];
+
/**
* Create a new job instance.
*
- * @param int $folderId The ID for the shared folder to process.
+ * @param int $folderId The ID for the shared folder to process
+ * @param array $properties Old values of the shared folder properties on update (key -> value)
*
* @return void
*/
- public function __construct(int $folderId)
+ public function __construct(int $folderId, array $properties = [])
{
$this->folderId = $folderId;
+ $this->properties = $properties;
$folder = $this->getSharedFolder();
diff --git a/src/app/Jobs/User/CreateJob.php b/src/app/Jobs/User/CreateJob.php
--- a/src/app/Jobs/User/CreateJob.php
+++ b/src/app/Jobs/User/CreateJob.php
@@ -42,16 +42,11 @@
return;
}
- if ($user->deleted_at) {
+ if ($user->trashed()) {
$this->fail(new \Exception("User {$this->userId} is actually deleted."));
return;
}
- if ($user->isLdapReady()) {
- $this->fail(new \Exception("User {$this->userId} is already marked as ldap-ready."));
- return;
- }
-
// see if the domain is ready
$domain = $user->domain();
@@ -70,9 +65,22 @@
return;
}
- \App\Backends\LDAP::createUser($user);
+ if (!$user->isLdapReady()) {
+ \App\Backends\LDAP::createUser($user);
+
+ $user->status |= \App\User::STATUS_LDAP_READY;
+ $user->save();
+ }
+
+ if (!$user->isImapReady()) {
+ if (!\App\Backends\IMAP::createUser($user)) {
+ throw new \Exception("Failed to create mailbox for user {$this->userId}.");
+ }
+
+ $user->status |= \App\User::STATUS_IMAP_READY;
+ }
- $user->status |= \App\User::STATUS_LDAP_READY;
+ $user->status |= \App\User::STATUS_ACTIVE;
$user->save();
}
}
diff --git a/src/app/Jobs/User/DeleteJob.php b/src/app/Jobs/User/DeleteJob.php
--- a/src/app/Jobs/User/DeleteJob.php
+++ b/src/app/Jobs/User/DeleteJob.php
@@ -30,18 +30,22 @@
return;
}
- \App\Backends\LDAP::deleteUser($user);
-
- $user->status |= \App\User::STATUS_DELETED;
-
if ($user->isLdapReady()) {
+ \App\Backends\LDAP::deleteUser($user);
+
$user->status ^= \App\User::STATUS_LDAP_READY;
+ $user->save();
}
if ($user->isImapReady()) {
+ if (!\App\Backends\IMAP::deleteUser($user)) {
+ throw new \Exception("Failed to delete mailbox for user {$this->userId}.");
+ }
+
$user->status ^= \App\User::STATUS_IMAP_READY;
}
+ $user->status |= \App\User::STATUS_DELETED;
$user->save();
}
}
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
@@ -24,11 +24,14 @@
return;
}
- if (!$user->isLdapReady()) {
- $this->delete();
- return;
+ if ($user->isLdapReady()) {
+ \App\Backends\LDAP::updateUser($user);
}
- \App\Backends\LDAP::updateUser($user);
+ if ($user->isImapReady()) {
+ if (!\App\Backends\IMAP::updateUser($user)) {
+ throw new \Exception("Failed to update mailbox for user {$this->userId}.");
+ }
+ }
}
}
diff --git a/src/app/Observers/EntitlementObserver.php b/src/app/Observers/EntitlementObserver.php
--- a/src/app/Observers/EntitlementObserver.php
+++ b/src/app/Observers/EntitlementObserver.php
@@ -61,6 +61,11 @@
$entitlement->entitleable->save();
$entitlement->createTransaction(\App\Transaction::ENTITLEMENT_CREATED);
+
+ // Update the user IMAP mailbox quota
+ if ($entitlement->sku->title == 'storage') {
+ \App\Jobs\User\UpdateJob::dispatch($entitlement->entitleable_id);
+ }
}
/**
@@ -72,6 +77,13 @@
*/
public function deleted(Entitlement $entitlement)
{
+ if (!$entitlement->entitleable->trashed()) {
+ $entitlement->entitleable->updated_at = Carbon::now();
+ $entitlement->entitleable->save();
+
+ $entitlement->createTransaction(\App\Transaction::ENTITLEMENT_DELETED);
+ }
+
// Remove all configured 2FA methods from Roundcube database
if ($entitlement->sku->title == '2fa') {
// FIXME: Should that be an async job?
@@ -79,11 +91,9 @@
$sf->removeFactors();
}
- if (!$entitlement->entitleable->trashed()) {
- $entitlement->entitleable->updated_at = Carbon::now();
- $entitlement->entitleable->save();
-
- $entitlement->createTransaction(\App\Transaction::ENTITLEMENT_DELETED);
+ // Update the user IMAP mailbox quota
+ if ($entitlement->sku->title == 'storage') {
+ \App\Jobs\User\UpdateJob::dispatch($entitlement->entitleable_id);
}
}
diff --git a/src/app/Observers/GroupObserver.php b/src/app/Observers/GroupObserver.php
--- a/src/app/Observers/GroupObserver.php
+++ b/src/app/Observers/GroupObserver.php
@@ -16,7 +16,7 @@
*/
public function creating(Group $group): void
{
- $group->status |= Group::STATUS_NEW | Group::STATUS_ACTIVE;
+ $group->status |= Group::STATUS_NEW;
if (!isset($group->name) && isset($group->email)) {
$group->name = explode('@', $group->email)[0];
diff --git a/src/app/Observers/ResourceObserver.php b/src/app/Observers/ResourceObserver.php
--- a/src/app/Observers/ResourceObserver.php
+++ b/src/app/Observers/ResourceObserver.php
@@ -15,7 +15,7 @@
*/
public function creating(Resource $resource): void
{
- $resource->status |= Resource::STATUS_NEW | Resource::STATUS_ACTIVE;
+ $resource->status |= Resource::STATUS_NEW;
}
/**
@@ -45,12 +45,8 @@
// Note: This is a single multi-insert query
$resource->settings()->insert(array_values($settings));
- // Create resource record in LDAP, then check if it is created in IMAP
- $chain = [
- new \App\Jobs\Resource\VerifyJob($resource->id),
- ];
-
- \App\Jobs\Resource\CreateJob::withChain($chain)->dispatch($resource->id);
+ // Create the resource in the backend (LDAP and IMAP)
+ \App\Jobs\Resource\CreateJob::dispatch($resource->id);
}
/**
diff --git a/src/app/Observers/ResourceSettingObserver.php b/src/app/Observers/ResourceSettingObserver.php
--- a/src/app/Observers/ResourceSettingObserver.php
+++ b/src/app/Observers/ResourceSettingObserver.php
@@ -17,7 +17,8 @@
public function created(ResourceSetting $resourceSetting)
{
if (in_array($resourceSetting->key, LDAP::RESOURCE_SETTINGS)) {
- \App\Jobs\Resource\UpdateJob::dispatch($resourceSetting->resource_id);
+ $props = [$resourceSetting->key => null];
+ \App\Jobs\Resource\UpdateJob::dispatch($resourceSetting->resource_id, $props);
}
}
@@ -31,7 +32,8 @@
public function updated(ResourceSetting $resourceSetting)
{
if (in_array($resourceSetting->key, LDAP::RESOURCE_SETTINGS)) {
- \App\Jobs\Resource\UpdateJob::dispatch($resourceSetting->resource_id);
+ $props = [$resourceSetting->key => $resourceSetting->getOriginal('value')];
+ \App\Jobs\Resource\UpdateJob::dispatch($resourceSetting->resource_id, $props);
}
}
@@ -45,7 +47,8 @@
public function deleted(ResourceSetting $resourceSetting)
{
if (in_array($resourceSetting->key, LDAP::RESOURCE_SETTINGS)) {
- \App\Jobs\Resource\UpdateJob::dispatch($resourceSetting->resource_id);
+ $props = [$resourceSetting->key => $resourceSetting->getOriginal('value')];
+ \App\Jobs\Resource\UpdateJob::dispatch($resourceSetting->resource_id, $props);
}
}
}
diff --git a/src/app/Observers/SharedFolderObserver.php b/src/app/Observers/SharedFolderObserver.php
--- a/src/app/Observers/SharedFolderObserver.php
+++ b/src/app/Observers/SharedFolderObserver.php
@@ -19,7 +19,7 @@
$folder->type = 'mail';
}
- $folder->status |= SharedFolder::STATUS_NEW | SharedFolder::STATUS_ACTIVE;
+ $folder->status |= SharedFolder::STATUS_NEW;
}
/**
@@ -49,12 +49,8 @@
// Note: This is a single multi-insert query
$folder->settings()->insert(array_values($settings));
- // Create folder record in LDAP, then check if it is created in IMAP
- $chain = [
- new \App\Jobs\SharedFolder\VerifyJob($folder->id),
- ];
-
- \App\Jobs\SharedFolder\CreateJob::withChain($chain)->dispatch($folder->id);
+ // Create the shared folder in the backend (LDAP and IMAP)
+ \App\Jobs\SharedFolder\CreateJob::dispatch($folder->id);
}
/**
diff --git a/src/app/Observers/SharedFolderSettingObserver.php b/src/app/Observers/SharedFolderSettingObserver.php
--- a/src/app/Observers/SharedFolderSettingObserver.php
+++ b/src/app/Observers/SharedFolderSettingObserver.php
@@ -17,7 +17,8 @@
public function created(SharedFolderSetting $folderSetting)
{
if (in_array($folderSetting->key, LDAP::SHARED_FOLDER_SETTINGS)) {
- \App\Jobs\SharedFolder\UpdateJob::dispatch($folderSetting->shared_folder_id);
+ $props = [$folderSetting->key => null];
+ \App\Jobs\SharedFolder\UpdateJob::dispatch($folderSetting->shared_folder_id, $props);
}
}
@@ -31,7 +32,8 @@
public function updated(SharedFolderSetting $folderSetting)
{
if (in_array($folderSetting->key, LDAP::SHARED_FOLDER_SETTINGS)) {
- \App\Jobs\SharedFolder\UpdateJob::dispatch($folderSetting->shared_folder_id);
+ $props = [$folderSetting->key => $folderSetting->getOriginal('value')];
+ \App\Jobs\SharedFolder\UpdateJob::dispatch($folderSetting->shared_folder_id, $props);
}
}
@@ -45,7 +47,8 @@
public function deleted(SharedFolderSetting $folderSetting)
{
if (in_array($folderSetting->key, LDAP::SHARED_FOLDER_SETTINGS)) {
- \App\Jobs\SharedFolder\UpdateJob::dispatch($folderSetting->shared_folder_id);
+ $props = [$folderSetting->key => $folderSetting->getOriginal('value')];
+ \App\Jobs\SharedFolder\UpdateJob::dispatch($folderSetting->shared_folder_id, $props);
}
}
}
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
@@ -20,8 +20,7 @@
{
$user->email = \strtolower($user->email);
- // only users that are not imported get the benefit of the doubt.
- $user->status |= User::STATUS_NEW | User::STATUS_ACTIVE;
+ $user->status |= User::STATUS_NEW;
}
/**
@@ -64,12 +63,8 @@
$user->wallets()->create();
- // Create user record in LDAP, then check if the account is created in IMAP
- $chain = [
- new \App\Jobs\User\VerifyJob($user->id),
- ];
-
- \App\Jobs\User\CreateJob::withChain($chain)->dispatch($user->id);
+ // Create user record in the backend (LDAP and IMAP)
+ \App\Jobs\User\CreateJob::dispatch($user->id);
if (\App\Tenant::getConfig($user->tenant_id, 'pgp.enable')) {
\App\Jobs\PGP\KeyCreateJob::dispatch($user->id, $user->email);
@@ -183,12 +178,8 @@
// FIXME: Should we reset user aliases? or re-validate them in any way?
- // Create user record in LDAP, then run the verification process
- $chain = [
- new \App\Jobs\User\VerifyJob($user->id),
- ];
-
- \App\Jobs\User\CreateJob::withChain($chain)->dispatch($user->id);
+ // Create user record in the backend (LDAP and IMAP)
+ \App\Jobs\User\CreateJob::dispatch($user->id);
}
/**
diff --git a/src/app/Traits/EntitleableTrait.php b/src/app/Traits/EntitleableTrait.php
--- a/src/app/Traits/EntitleableTrait.php
+++ b/src/app/Traits/EntitleableTrait.php
@@ -156,6 +156,24 @@
});
}
+ /**
+ * Count entitlements for the specified SKU.
+ *
+ * @param string $title The SKU title
+ *
+ * @return int Numer of entitlements
+ */
+ public function countEntitlementsBySku(string $title): int
+ {
+ $sku = $this->skuByTitle($title);
+
+ if (!$sku) {
+ return 0;
+ }
+
+ return $this->entitlements()->where('sku_id', $sku->id)->count();
+ }
+
/**
* Entitlements for this object.
*
@@ -176,13 +194,7 @@
*/
public function hasSku(string $title): bool
{
- $sku = $this->skuByTitle($title);
-
- if (!$sku) {
- return false;
- }
-
- return $this->entitlements()->where('sku_id', $sku->id)->count() > 0;
+ return $this->countEntitlementsBySku($title) > 0;
}
/**
diff --git a/src/include/rcube_imap_generic.php b/src/include/rcube_imap_generic.php
--- a/src/include/rcube_imap_generic.php
+++ b/src/include/rcube_imap_generic.php
@@ -3180,6 +3180,28 @@
return $result;
}
+ /**
+ * Send the SETQUOTA command (RFC9208)
+ *
+ * @param string $root Quota root
+ * @param array $quota Quota limits e.g. ['storage' => 1024000']
+ *
+ * @return boolean True on success, False on failure
+ */
+ public function setQuota($root, $quota)
+ {
+ $fn = function ($key, $value) {
+ return strtoupper($key) . ' ' . $value;
+ };
+
+ $quota = implode(' ', array_map($fn, array_keys($quota), $quota));
+
+ $result = $this->execute('SETQUOTA', [$this->escape($root), "({$quota})"],
+ self::COMMAND_NORESPONSE);
+
+ return ($result == self::ERROR_OK);
+ }
+
/**
* Send the SETACL command (RFC4314)
*
diff --git a/src/tests/Browser/StatusTest.php b/src/tests/Browser/StatusTest.php
--- a/src/tests/Browser/StatusTest.php
+++ b/src/tests/Browser/StatusTest.php
@@ -112,7 +112,7 @@
$browser->waitUntilMissing('@status', 10);
});
- // Test the Refresh button
+ // Test the Refresh button
if ($domain->isConfirmed()) {
$domain->status ^= Domain::STATUS_CONFIRMED;
$domain->save();
@@ -134,17 +134,15 @@
->assertVisible('@refresh-button')
->assertVisible('@refresh-text');
- if ($john->refresh()->isImapReady()) {
- $john->status ^= User::STATUS_IMAP_READY;
- $john->save();
- }
+ $browser->click('@refresh-button')
+ ->assertToast(Toast::TYPE_SUCCESS, 'Setup process has been pushed. Please wait.');
+
+ $john->status |= User::STATUS_IMAP_READY;
+ $john->save();
$domain->status |= Domain::STATUS_CONFIRMED;
$domain->save();
-
- $browser->click('@refresh-button')
- ->assertToast(Toast::TYPE_SUCCESS, 'Setup process finished successfully.');
})
- ->assertMissing('@status');
+ ->waitUntilMissing('@status', 10);
});
}
diff --git a/src/tests/Feature/Backends/IMAPTest.php b/src/tests/Feature/Backends/IMAPTest.php
--- a/src/tests/Feature/Backends/IMAPTest.php
+++ b/src/tests/Feature/Backends/IMAPTest.php
@@ -7,6 +7,173 @@
class IMAPTest extends TestCase
{
+ private $imap;
+ private $user;
+ private $resource;
+ private $folder;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ if ($this->user) {
+ $this->deleteTestUser($this->user->email);
+ }
+ if ($this->resource) {
+ $this->deleteTestResource($this->resource->email);
+ }
+ if ($this->folder) {
+ $this->deleteTestSharedFolder($this->folder->email);
+ }
+
+ if ($this->imap) {
+ $this->imap->closeConnection();
+ $this->imap = null;
+ }
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test creating/updating/deleting an IMAP account
+ *
+ * @group imap
+ */
+ public function testUsers(): void
+ {
+ $this->user = $user = $this->getTestUser('test-' . time() . '@' . \config('app.domain'));
+ $storage = \App\Sku::withEnvTenantContext()->where('title', 'storage')->first();
+ $user->assignSku($storage, 1, $user->wallets->first());
+
+ $expectedQuota = [
+ 'user/' . $user->email => [
+ 'storage' => [
+ 'used' => 0,
+ 'total' => 1048576
+ ]
+ ]
+ ];
+
+ // Create the mailbox
+ $result = IMAP::createUser($user);
+ $this->assertTrue($result);
+ // $this->assertTrue(IMAP::verifyAccount($user->email));
+
+ $imap = $this->getImap();
+ $quota = $imap->getQuota('user/' . $user->email);
+ $this->assertSame($expectedQuota, $quota['all']);
+
+ // Update the mailbox (increase quota)
+ $user->assignSku($storage, 1, $user->wallets->first());
+ $expectedQuota['user/' . $user->email]['storage']['total'] = 1048576 * 2;
+
+ $result = IMAP::updateUser($user);
+ $this->assertTrue($result);
+
+ $quota = $imap->getQuota('user/' . $user->email);
+ $this->assertSame($expectedQuota, $quota['all']);
+
+ // Delete the mailbox
+ $result = IMAP::deleteUser($user);
+ $this->assertTrue($result);
+
+ $this->expectException(\Exception::class);
+ IMAP::verifyAccount($user->email);
+ }
+
+ /**
+ * Test creating/updating/deleting a resource
+ *
+ * @group imap
+ */
+ public function testResources(): void
+ {
+ $this->resource = $resource = $this->getTestResource(
+ 'test-resource-' . time() . '@kolab.org',
+ ['name' => 'Resource ©' . time()]
+ );
+
+ $resource->setSetting('invitation_policy', 'manual:john@kolab.org');
+
+ // Create the resource
+ $this->assertTrue(IMAP::createResource($resource));
+ $this->assertTrue(IMAP::verifySharedFolder($imapFolder = $resource->getSetting('folder')));
+
+ $imap = $this->getImap();
+ $expectedAcl = ['john@kolab.org' => str_split('lrswipkxtecdn')];
+ $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($imapFolder)));
+
+ // Update the resource (rename)
+ $resource->name = 'Resource1 ©'. time();
+ $resource->save();
+ $newImapFolder = $resource->getSetting('folder');
+
+ $this->assertTrue(IMAP::updateResource($resource, ['folder' => $imapFolder]));
+ $this->assertTrue($imapFolder != $newImapFolder);
+ $this->assertTrue(IMAP::verifySharedFolder($newImapFolder));
+ $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($newImapFolder)));
+
+ // Update the resource (acl change)
+ $resource->setSetting('invitation_policy', 'accept');
+ $this->assertTrue(IMAP::updateResource($resource));
+ $this->assertSame([], $imap->getACL(IMAP::toUTF7($newImapFolder)));
+
+ // Delete the resource
+ $this->assertTrue(IMAP::deleteResource($resource));
+ $this->assertFalse(IMAP::verifySharedFolder($newImapFolder));
+ }
+
+ /**
+ * Test creating/updating/deleting a shared folder
+ *
+ * @group imap
+ */
+ public function testSharedFolders(): void
+ {
+ $this->folder = $folder = $this->getTestSharedFolder(
+ 'test-folder-' . time() . '@kolab.org',
+ ['name' => 'SharedFolder ©' . time()]
+ );
+
+ $folder->setSetting('acl', json_encode(['john@kolab.org, full', 'jack@kolab.org, read-only']));
+
+ // Create the shared folder
+ $this->assertTrue(IMAP::createSharedFolder($folder));
+ $this->assertTrue(IMAP::verifySharedFolder($imapFolder = $folder->getSetting('folder')));
+
+ $imap = $this->getImap();
+ $expectedAcl = [
+ 'john@kolab.org' => str_split('lrswipkxtecdn'),
+ 'jack@kolab.org' => str_split('lrs')
+ ];
+
+ $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($imapFolder)));
+
+ // Update shared folder (acl)
+ $folder->setSetting('acl', json_encode(['jack@kolab.org, read-only']));
+
+ $this->assertTrue(IMAP::updateSharedFolder($folder));
+
+ $expectedAcl = ['jack@kolab.org' => str_split('lrs')];
+
+ $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($imapFolder)));
+
+ // Update the shared folder (rename)
+ $folder->name = 'SharedFolder1 ©'. time();
+ $folder->save();
+ $newImapFolder = $folder->getSetting('folder');
+
+ $this->assertTrue(IMAP::updateSharedFolder($folder, ['folder' => $imapFolder]));
+ $this->assertTrue($imapFolder != $newImapFolder);
+ $this->assertTrue(IMAP::verifySharedFolder($newImapFolder));
+ $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($newImapFolder)));
+
+ // Delete the shared folder
+ $this->assertTrue(IMAP::deleteSharedFolder($folder));
+ $this->assertFalse(IMAP::verifySharedFolder($newImapFolder));
+ }
+
/**
* Test verifying IMAP account existence (existing account)
*
@@ -38,4 +205,24 @@
$result = IMAP::verifySharedFolder('shared/Calendar@kolab.org');
$this->assertTrue($result);
}
+
+ /**
+ * Get configured/initialized rcube_imap_generic instance
+ */
+ private function getImap()
+ {
+ if ($this->imap) {
+ return $this->imap;
+ }
+
+ $class = new \ReflectionClass(IMAP::class);
+ $init = $class->getMethod('initIMAP');
+ $config = $class->getMethod('getConfig');
+ $init->setAccessible(true);
+ $config->setAccessible(true);
+
+ $config = $config->invoke(null);
+
+ return $this->imap = $init->invokeArgs(null, [$config]);
+ }
}
diff --git a/src/tests/Feature/Console/User/StatusTest.php b/src/tests/Feature/Console/User/StatusTest.php
--- a/src/tests/Feature/Console/User/StatusTest.php
+++ b/src/tests/Feature/Console/User/StatusTest.php
@@ -2,6 +2,7 @@
namespace Tests\Feature\Console\User;
+use App\User;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
@@ -41,14 +42,20 @@
$this->assertSame(1, $code);
$this->assertSame("User not found.", $output);
+ $user = $this->getTestUser(
+ 'user@force-delete.com',
+ ['status' => User::STATUS_NEW | User::STATUS_ACTIVE | User::STATUS_IMAP_READY | User::STATUS_LDAP_READY]
+ );
+
// Existing user
- $code = \Artisan::call("user:status john@kolab.org");
+ $code = \Artisan::call("user:status {$user->email}");
$output = trim(\Artisan::output());
$this->assertSame(0, $code);
$this->assertSame("Status (51): active (2), ldapReady (16), imapReady (32)", $output);
- $user = $this->getTestUser('user@force-delete.com');
+ $user->status = User::STATUS_ACTIVE;
+ $user->save();
$user->delete();
// Deleted user
@@ -56,6 +63,6 @@
$output = trim(\Artisan::output());
$this->assertSame(0, $code);
- $this->assertSame("Status (3): active (2), deleted (8)", $output);
+ $this->assertSame("Status (2): active (2), deleted (8)", $output);
}
}
diff --git a/src/tests/Feature/Controller/AuthTest.php b/src/tests/Feature/Controller/AuthTest.php
--- a/src/tests/Feature/Controller/AuthTest.php
+++ b/src/tests/Feature/Controller/AuthTest.php
@@ -69,7 +69,7 @@
*/
public function testInfo(): void
{
- $user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
+ $user = $this->getTestUser('UsersControllerTest1@userscontroller.com', ['status' => User::STATUS_NEW]);
$domain = $this->getTestDomain('userscontroller.com', [
'status' => Domain::STATUS_NEW,
'type' => Domain::TYPE_PUBLIC,
@@ -85,7 +85,7 @@
$this->assertEquals($user->id, $json['id']);
$this->assertEquals($user->email, $json['email']);
- $this->assertEquals(User::STATUS_NEW | User::STATUS_ACTIVE, $json['status']);
+ $this->assertEquals(User::STATUS_NEW, $json['status']);
$this->assertTrue(is_array($json['statusInfo']));
$this->assertTrue(is_array($json['settings']));
$this->assertTrue(!isset($json['access_token']));
diff --git a/src/tests/Feature/Controller/ResourcesTest.php b/src/tests/Feature/Controller/ResourcesTest.php
--- a/src/tests/Feature/Controller/ResourcesTest.php
+++ b/src/tests/Feature/Controller/ResourcesTest.php
@@ -339,25 +339,29 @@
$resource->status |= Resource::STATUS_IMAP_READY;
$resource->save();
- // Now "reboot" the process and get the resource status
+ // Now "reboot" the process
+ Queue::fake();
$response = $this->actingAs($john)->get("/api/v4/resources/{$resource->id}/status?refresh=1");
$response->assertStatus(200);
$json = $response->json();
- $this->assertTrue($json['isLdapReady']);
+ $this->assertFalse($json['isLdapReady']);
$this->assertTrue($json['isImapReady']);
- $this->assertTrue($json['isReady']);
+ $this->assertFalse($json['isReady']);
$this->assertCount(7, $json['process']);
$this->assertSame('resource-ldap-ready', $json['process'][1]['label']);
- $this->assertSame(true, $json['process'][1]['state']);
+ $this->assertSame(false, $json['process'][1]['state']);
$this->assertSame('resource-imap-ready', $json['process'][2]['label']);
$this->assertSame(true, $json['process'][2]['state']);
$this->assertSame('success', $json['status']);
- $this->assertSame('Setup process finished successfully.', $json['message']);
- $this->assertSame('done', $json['processState']);
+ $this->assertSame('Setup process has been pushed. Please wait.', $json['message']);
+ $this->assertSame('waiting', $json['processState']);
+
+ Queue::assertPushed(\App\Jobs\Resource\CreateJob::class, 1);
// Test a case when a domain is not ready
+ Queue::fake();
$domain->status ^= \App\Domain::STATUS_CONFIRMED;
$domain->save();
@@ -366,13 +370,16 @@
$json = $response->json();
- $this->assertTrue($json['isLdapReady']);
- $this->assertTrue($json['isReady']);
+ $this->assertFalse($json['isLdapReady']);
+ $this->assertFalse($json['isReady']);
$this->assertCount(7, $json['process']);
$this->assertSame('resource-ldap-ready', $json['process'][1]['label']);
- $this->assertSame(true, $json['process'][1]['state']);
+ $this->assertSame(false, $json['process'][1]['state']);
$this->assertSame('success', $json['status']);
- $this->assertSame('Setup process finished successfully.', $json['message']);
+ $this->assertSame('Setup process has been pushed. Please wait.', $json['message']);
+ $this->assertSame('waiting', $json['processState']);
+
+ Queue::assertPushed(\App\Jobs\Resource\CreateJob::class, 1);
}
/**
diff --git a/src/tests/Feature/Controller/SharedFoldersTest.php b/src/tests/Feature/Controller/SharedFoldersTest.php
--- a/src/tests/Feature/Controller/SharedFoldersTest.php
+++ b/src/tests/Feature/Controller/SharedFoldersTest.php
@@ -346,25 +346,29 @@
$folder->status |= SharedFolder::STATUS_IMAP_READY;
$folder->save();
- // Now "reboot" the process and get the folder status
+ // Now "reboot" the process
+ Queue::fake();
$response = $this->actingAs($john)->get("/api/v4/shared-folders/{$folder->id}/status?refresh=1");
$response->assertStatus(200);
$json = $response->json();
- $this->assertTrue($json['isLdapReady']);
+ $this->assertFalse($json['isLdapReady']);
$this->assertTrue($json['isImapReady']);
- $this->assertTrue($json['isReady']);
+ $this->assertFalse($json['isReady']);
$this->assertCount(7, $json['process']);
$this->assertSame('shared-folder-ldap-ready', $json['process'][1]['label']);
- $this->assertSame(true, $json['process'][1]['state']);
+ $this->assertSame(false, $json['process'][1]['state']);
$this->assertSame('shared-folder-imap-ready', $json['process'][2]['label']);
$this->assertSame(true, $json['process'][2]['state']);
$this->assertSame('success', $json['status']);
- $this->assertSame('Setup process finished successfully.', $json['message']);
- $this->assertSame('done', $json['processState']);
+ $this->assertSame('Setup process has been pushed. Please wait.', $json['message']);
+ $this->assertSame('waiting', $json['processState']);
+
+ Queue::assertPushed(\App\Jobs\SharedFolder\CreateJob::class, 1);
// Test a case when a domain is not ready
+ Queue::fake();
$domain->status ^= \App\Domain::STATUS_CONFIRMED;
$domain->save();
@@ -373,13 +377,16 @@
$json = $response->json();
- $this->assertTrue($json['isLdapReady']);
- $this->assertTrue($json['isReady']);
+ $this->assertFalse($json['isLdapReady']);
+ $this->assertFalse($json['isReady']);
$this->assertCount(7, $json['process']);
$this->assertSame('shared-folder-ldap-ready', $json['process'][1]['label']);
- $this->assertSame(true, $json['process'][1]['state']);
+ $this->assertSame(false, $json['process'][1]['state']);
$this->assertSame('success', $json['status']);
- $this->assertSame('Setup process finished successfully.', $json['message']);
+ $this->assertSame('Setup process has been pushed. Please wait.', $json['message']);
+ $this->assertSame('waiting', $json['processState']);
+
+ Queue::assertPushed(\App\Jobs\SharedFolder\CreateJob::class, 1);
}
/**
diff --git a/src/tests/Feature/Controller/UsersTest.php b/src/tests/Feature/Controller/UsersTest.php
--- a/src/tests/Feature/Controller/UsersTest.php
+++ b/src/tests/Feature/Controller/UsersTest.php
@@ -493,29 +493,8 @@
$domain->status |= Domain::STATUS_CONFIRMED;
$domain->save();
- // Now "reboot" the process and verify the user in imap synchronously
- $response = $this->actingAs($john)->get("/api/v4/users/{$john->id}/status?refresh=1");
- $response->assertStatus(200);
-
- $json = $response->json();
-
- $this->assertTrue($json['isImapReady']);
- $this->assertTrue($json['isReady']);
- $this->assertCount(7, $json['process']);
- $this->assertSame('user-imap-ready', $json['process'][2]['label']);
- $this->assertSame(true, $json['process'][2]['state']);
- $this->assertSame('success', $json['status']);
- $this->assertSame('Setup process finished successfully.', $json['message']);
-
- Queue::size(1);
-
- // Test case for when the verify job is dispatched to the worker
- $john->refresh();
- $john->status ^= User::STATUS_IMAP_READY;
- $john->save();
-
- \config(['imap.admin_password' => null]);
-
+ // Now "reboot" the process
+ Queue::fake();
$response = $this->actingAs($john)->get("/api/v4/users/{$john->id}/status?refresh=1");
$response->assertStatus(200);
@@ -523,11 +502,13 @@
$this->assertFalse($json['isImapReady']);
$this->assertFalse($json['isReady']);
+ $this->assertCount(7, $json['process']);
+ $this->assertSame('user-imap-ready', $json['process'][2]['label']);
+ $this->assertSame(false, $json['process'][2]['state']);
$this->assertSame('success', $json['status']);
- $this->assertSame('waiting', $json['processState']);
$this->assertSame('Setup process has been pushed. Please wait.', $json['message']);
- Queue::assertPushed(\App\Jobs\User\VerifyJob::class, 1);
+ Queue::assertPushed(\App\Jobs\User\CreateJob::class, 1);
}
/**
diff --git a/src/tests/Feature/EntitlementTest.php b/src/tests/Feature/EntitlementTest.php
--- a/src/tests/Feature/EntitlementTest.php
+++ b/src/tests/Feature/EntitlementTest.php
@@ -61,6 +61,48 @@
$this->assertTrue($costsPerDay > 16.12);
}
+ /**
+ * Tests for EntitlementObserver
+ */
+ public function testEntitlementObserver(): void
+ {
+ $skuStorage = Sku::withEnvTenantContext()->where('title', 'storage')->first();
+ $skuMailbox = Sku::withEnvTenantContext()->where('title', 'mailbox')->first();
+ $user = $this->getTestUser('entitlement-test@kolabnow.com');
+ $wallet = $user->wallets->first();
+
+ // Test dispatching update jobs for the user, on quota update
+ Queue::fake();
+ $user->assignSku($skuMailbox, 1, $wallet);
+ Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 0);
+
+ Queue::fake();
+ $user->assignSku($skuStorage, 1, $wallet);
+ Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 1);
+ Queue::assertPushed(
+ \App\Jobs\User\UpdateJob::class,
+ function ($job) use ($user) {
+ return $user->id === TestCase::getObjectProperty($job, 'userId');
+ }
+ );
+
+ Queue::fake();
+ $user->entitlements()->where('sku_id', $skuMailbox->id)->first()->delete();
+ Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 0);
+
+ Queue::fake();
+ $user->entitlements()->where('sku_id', $skuStorage->id)->first()->delete();
+ Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 1);
+ Queue::assertPushed(
+ \App\Jobs\User\UpdateJob::class,
+ function ($job) use ($user) {
+ return $user->id === TestCase::getObjectProperty($job, 'userId');
+ }
+ );
+
+ // TODO: Test all events in the observer in more detail
+ }
+
/**
* Tests for entitlements
* @todo This really should be in User or Wallet tests file
diff --git a/src/tests/Feature/GroupTest.php b/src/tests/Feature/GroupTest.php
--- a/src/tests/Feature/GroupTest.php
+++ b/src/tests/Feature/GroupTest.php
@@ -79,7 +79,7 @@
$this->assertMatchesRegularExpression('/^[0-9]{1,20}$/', $group->id);
$this->assertSame([], $group->members);
$this->assertTrue($group->isNew());
- $this->assertTrue($group->isActive());
+ $this->assertFalse($group->isActive());
Queue::assertPushed(
\App\Jobs\Group\CreateJob::class,
diff --git a/src/tests/Feature/Jobs/Resource/CreateTest.php b/src/tests/Feature/Jobs/Resource/CreateTest.php
--- a/src/tests/Feature/Jobs/Resource/CreateTest.php
+++ b/src/tests/Feature/Jobs/Resource/CreateTest.php
@@ -29,6 +29,7 @@
* Test job handle
*
* @group ldap
+ * @group imap
*/
public function testHandle(): void
{
@@ -41,24 +42,27 @@
$this->assertTrue($job->isReleased());
$this->assertFalse($job->hasFailed());
- $resource = $this->getTestResource('resource-test@' . \config('app.domain'));
+ $resource = $this->getTestResource(
+ 'resource-test@' . \config('app.domain'),
+ ['status' => Resource::STATUS_NEW]
+ );
$this->assertFalse($resource->isLdapReady());
+ $this->assertFalse($resource->isImapReady());
+ $this->assertFalse($resource->isActive());
// Test resource creation
$job = new \App\Jobs\Resource\CreateJob($resource->id);
$job->handle();
- $this->assertTrue($resource->fresh()->isLdapReady());
+ $resource->refresh();
+
$this->assertFalse($job->hasFailed());
+ $this->assertTrue($resource->isLdapReady());
+ $this->assertTrue($resource->isImapReady());
+ $this->assertTrue($resource->isActive());
// Test job failures
- $job = new \App\Jobs\Resource\CreateJob($resource->id);
- $job->handle();
-
- $this->assertTrue($job->hasFailed());
- $this->assertSame("Resource {$resource->id} is already marked as ldap-ready.", $job->failureMessage);
-
$resource->status |= Resource::STATUS_DELETED;
$resource->save();
@@ -79,5 +83,6 @@
$this->assertSame("Resource {$resource->id} is actually deleted.", $job->failureMessage);
// TODO: Test failures on domain sanity checks
+ // TODO: Test partial execution, i.e. only IMAP or only LDAP
}
}
diff --git a/src/tests/Feature/Jobs/Resource/DeleteTest.php b/src/tests/Feature/Jobs/Resource/DeleteTest.php
--- a/src/tests/Feature/Jobs/Resource/DeleteTest.php
+++ b/src/tests/Feature/Jobs/Resource/DeleteTest.php
@@ -29,6 +29,7 @@
* Test job handle
*
* @group ldap
+ * @group imap
*/
public function testHandle(): void
{
@@ -52,11 +53,10 @@
$resource->refresh();
$this->assertTrue($resource->isLdapReady());
+ $this->assertTrue($resource->isImapReady());
+ $this->assertFalse($resource->isDeleted());
// Test successful deletion
- $resource->status |= Resource::STATUS_IMAP_READY;
- $resource->save();
-
$job = new \App\Jobs\Resource\DeleteJob($resource->id);
$job->handle();
diff --git a/src/tests/Feature/Jobs/Resource/UpdateTest.php b/src/tests/Feature/Jobs/Resource/UpdateTest.php
--- a/src/tests/Feature/Jobs/Resource/UpdateTest.php
+++ b/src/tests/Feature/Jobs/Resource/UpdateTest.php
@@ -33,6 +33,7 @@
* Test job handle
*
* @group ldap
+ * @group imap
*/
public function testHandle(): void
{
@@ -45,12 +46,16 @@
$this->assertTrue($job->hasFailed());
$this->assertSame("Resource 123 could not be found in the database.", $job->failureMessage);
- $resource = $this->getTestResource('resource-test@' . \config('app.domain'));
+ $resource = $this->getTestResource(
+ 'resource-test@' . \config('app.domain'),
+ ['status' => Resource::STATUS_NEW]
+ );
// Create the resource in LDAP
$job = new \App\Jobs\Resource\CreateJob($resource->id);
$job->handle();
+ // Run the update with some new config
$resource->setConfig(['invitation_policy' => 'accept']);
$job = new \App\Jobs\Resource\UpdateJob($resource->id);
@@ -60,18 +65,11 @@
$this->assertSame('ACT_ACCEPT', $ldap_resource['kolabinvitationpolicy']);
+ // TODO: Assert IMAP change worked
+
// Test that the job is being deleted if the resource is not ldap ready or is deleted
$resource->refresh();
- $resource->status = Resource::STATUS_NEW | Resource::STATUS_ACTIVE;
- $resource->save();
-
- $job = new \App\Jobs\Resource\UpdateJob($resource->id);
- $job->handle();
-
- $this->assertTrue($job->isDeleted());
-
- $resource->status = Resource::STATUS_NEW | Resource::STATUS_ACTIVE
- | Resource::STATUS_LDAP_READY | Resource::STATUS_DELETED;
+ $resource->status |= Resource::STATUS_DELETED;
$resource->save();
$job = new \App\Jobs\Resource\UpdateJob($resource->id);
diff --git a/src/tests/Feature/Jobs/SharedFolder/CreateTest.php b/src/tests/Feature/Jobs/SharedFolder/CreateTest.php
--- a/src/tests/Feature/Jobs/SharedFolder/CreateTest.php
+++ b/src/tests/Feature/Jobs/SharedFolder/CreateTest.php
@@ -29,6 +29,7 @@
* Test job handle
*
* @group ldap
+ * @group imap
*/
public function testHandle(): void
{
@@ -41,24 +42,27 @@
$this->assertTrue($job->isReleased());
$this->assertFalse($job->hasFailed());
- $folder = $this->getTestSharedFolder('folder-test@' . \config('app.domain'));
+ $folder = $this->getTestSharedFolder(
+ 'folder-test@' . \config('app.domain'),
+ ['status' => SharedFolder::STATUS_NEW]
+ );
$this->assertFalse($folder->isLdapReady());
+ $this->assertFalse($folder->isImapReady());
+ $this->assertFalse($folder->isActive());
// Test shared folder creation
$job = new \App\Jobs\SharedFolder\CreateJob($folder->id);
$job->handle();
- $this->assertTrue($folder->fresh()->isLdapReady());
+ $folder->refresh();
+
$this->assertFalse($job->hasFailed());
+ $this->assertTrue($folder->isLdapReady());
+ $this->assertTrue($folder->isImapReady());
+ $this->assertTrue($folder->isActive());
// Test job failures
- $job = new \App\Jobs\SharedFolder\CreateJob($folder->id);
- $job->handle();
-
- $this->assertTrue($job->hasFailed());
- $this->assertSame("Shared folder {$folder->id} is already marked as ldap-ready.", $job->failureMessage);
-
$folder->status |= SharedFolder::STATUS_DELETED;
$folder->save();
@@ -79,5 +83,6 @@
$this->assertSame("Shared folder {$folder->id} is actually deleted.", $job->failureMessage);
// TODO: Test failures on domain sanity checks
+ // TODO: Test partial execution, i.e. only IMAP or only LDAP
}
}
diff --git a/src/tests/Feature/Jobs/SharedFolder/DeleteTest.php b/src/tests/Feature/Jobs/SharedFolder/DeleteTest.php
--- a/src/tests/Feature/Jobs/SharedFolder/DeleteTest.php
+++ b/src/tests/Feature/Jobs/SharedFolder/DeleteTest.php
@@ -29,6 +29,7 @@
* Test job handle
*
* @group ldap
+ * @group imap
*/
public function testHandle(): void
{
@@ -52,11 +53,10 @@
$folder->refresh();
$this->assertTrue($folder->isLdapReady());
+ $this->assertTrue($folder->isImapReady());
+ $this->assertFalse($folder->isDeleted());
// Test successful deletion
- $folder->status |= SharedFolder::STATUS_IMAP_READY;
- $folder->save();
-
$job = new \App\Jobs\SharedFolder\DeleteJob($folder->id);
$job->handle();
diff --git a/src/tests/Feature/Jobs/SharedFolder/UpdateTest.php b/src/tests/Feature/Jobs/SharedFolder/UpdateTest.php
--- a/src/tests/Feature/Jobs/SharedFolder/UpdateTest.php
+++ b/src/tests/Feature/Jobs/SharedFolder/UpdateTest.php
@@ -33,6 +33,7 @@
* Test job handle
*
* @group ldap
+ * @group imap
*/
public function testHandle(): void
{
@@ -45,29 +46,28 @@
$this->assertTrue($job->hasFailed());
$this->assertSame("Shared folder 123 could not be found in the database.", $job->failureMessage);
- $folder = $this->getTestSharedFolder('folder-test@' . \config('app.domain'));
+ $folder = $this->getTestSharedFolder(
+ 'folder-test@' . \config('app.domain'),
+ ['status' => SharedFolder::STATUS_NEW]
+ );
// Create the folder in LDAP
$job = new \App\Jobs\SharedFolder\CreateJob($folder->id);
$job->handle();
- $job = new \App\Jobs\SharedFolder\UpdateJob($folder->id);
- $job->handle();
-
- $this->assertTrue(is_array(LDAP::getSharedFolder($folder->email)));
-
- // Test that the job is being deleted if the folder is not ldap ready or is deleted
$folder->refresh();
- $folder->status = SharedFolder::STATUS_NEW | SharedFolder::STATUS_ACTIVE;
- $folder->save();
+ $this->assertTrue($folder->isLdapReady());
+ $this->assertTrue($folder->isImapReady());
+
+ // Run the update job
$job = new \App\Jobs\SharedFolder\UpdateJob($folder->id);
$job->handle();
- $this->assertTrue($job->isDeleted());
+ // TODO: Assert that it worked on both LDAP and IMAP side
- $folder->status = SharedFolder::STATUS_NEW | SharedFolder::STATUS_ACTIVE
- | SharedFolder::STATUS_LDAP_READY | SharedFolder::STATUS_DELETED;
+ // Test handling deleted folder
+ $folder->status |= SharedFolder::STATUS_DELETED;
$folder->save();
$job = new \App\Jobs\SharedFolder\UpdateJob($folder->id);
diff --git a/src/tests/Feature/Jobs/User/CreateTest.php b/src/tests/Feature/Jobs/User/CreateTest.php
--- a/src/tests/Feature/Jobs/User/CreateTest.php
+++ b/src/tests/Feature/Jobs/User/CreateTest.php
@@ -3,6 +3,7 @@
namespace Tests\Feature\Jobs\User;
use App\User;
+use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
class CreateTest extends TestCase
@@ -28,26 +29,28 @@
* Test job handle
*
* @group ldap
+ * @group imap
*/
public function testHandle(): void
{
- $user = $this->getTestUser('new-job-user@' . \config('app.domain'));
+ Queue::fake();
+ $user = $this->getTestUser('new-job-user@' . \config('app.domain'), ['status' => User::STATUS_NEW]);
$this->assertFalse($user->isLdapReady());
+ $this->assertFalse($user->isImapReady());
+ $this->assertFalse($user->isActive());
$job = new \App\Jobs\User\CreateJob($user->id);
$job->handle();
- $this->assertTrue($user->fresh()->isLdapReady());
- $this->assertFalse($job->hasFailed());
+ $user->refresh();
- // Test job failures
- $job = new \App\Jobs\User\CreateJob($user->id);
- $job->handle();
-
- $this->assertTrue($job->hasFailed());
- $this->assertSame("User {$user->id} is already marked as ldap-ready.", $job->failureMessage);
+ $this->assertTrue($user->isLdapReady());
+ $this->assertTrue($user->isImapReady());
+ $this->assertTrue($user->isActive());
+ $this->assertFalse($job->hasFailed());
+ // Test job failure (user deleted)
$user->status |= User::STATUS_DELETED;
$user->save();
@@ -57,6 +60,7 @@
$this->assertTrue($job->hasFailed());
$this->assertSame("User {$user->id} is marked as deleted.", $job->failureMessage);
+ // Test job failure (user removed)
$user->status ^= User::STATUS_DELETED;
$user->save();
$user->delete();
@@ -67,12 +71,14 @@
$this->assertTrue($job->hasFailed());
$this->assertSame("User {$user->id} is actually deleted.", $job->failureMessage);
- // TODO: Test failures on domain sanity checks
-
+ // Test job failure (user unknown)
$job = new \App\Jobs\User\CreateJob(123);
$job->handle();
$this->assertTrue($job->isReleased());
$this->assertFalse($job->hasFailed());
+
+ // TODO: Test failures on domain sanity checks
+ // TODO: Test partial execution, i.e. only IMAP or only LDAP
}
}
diff --git a/src/tests/Feature/Jobs/User/CreateTest.php b/src/tests/Feature/Jobs/User/DeleteTest.php
copy from src/tests/Feature/Jobs/User/CreateTest.php
copy to src/tests/Feature/Jobs/User/DeleteTest.php
--- a/src/tests/Feature/Jobs/User/CreateTest.php
+++ b/src/tests/Feature/Jobs/User/DeleteTest.php
@@ -3,9 +3,10 @@
namespace Tests\Feature\Jobs\User;
use App\User;
+use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
-class CreateTest extends TestCase
+class DeleteTest extends TestCase
{
/**
* {@inheritDoc}
@@ -17,6 +18,9 @@
$this->deleteTestUser('new-job-user@' . \config('app.domain'));
}
+ /**
+ * {@inheritDoc}
+ */
public function tearDown(): void
{
$this->deleteTestUser('new-job-user@' . \config('app.domain'));
@@ -28,51 +32,50 @@
* Test job handle
*
* @group ldap
+ * @group imap
*/
public function testHandle(): void
{
- $user = $this->getTestUser('new-job-user@' . \config('app.domain'));
+ Queue::fake();
- $this->assertFalse($user->isLdapReady());
+ $user = $this->getTestUser('new-job-user@' . \config('app.domain'));
+ // Create the user in LDAP+IMAP
$job = new \App\Jobs\User\CreateJob($user->id);
$job->handle();
- $this->assertTrue($user->fresh()->isLdapReady());
- $this->assertFalse($job->hasFailed());
+ $user->refresh();
- // Test job failures
- $job = new \App\Jobs\User\CreateJob($user->id);
- $job->handle();
-
- $this->assertTrue($job->hasFailed());
- $this->assertSame("User {$user->id} is already marked as ldap-ready.", $job->failureMessage);
+ $this->assertTrue($user->isLdapReady());
+ $this->assertTrue($user->isImapReady());
+ $this->assertFalse($user->isDeleted());
+ // Test job failure (user already deleted)
$user->status |= User::STATUS_DELETED;
$user->save();
- $job = new \App\Jobs\User\CreateJob($user->id);
+ $job = new \App\Jobs\User\DeleteJob($user->id);
$job->handle();
$this->assertTrue($job->hasFailed());
- $this->assertSame("User {$user->id} is marked as deleted.", $job->failureMessage);
+ $this->assertSame("User {$user->id} is already marked as deleted.", $job->failureMessage);
+ // Test success delete from LDAP and IMAP
$user->status ^= User::STATUS_DELETED;
$user->save();
- $user->delete();
- $job = new \App\Jobs\User\CreateJob($user->id);
+ $this->assertFalse($user->isDeleted());
+
+ $job = new \App\Jobs\User\DeleteJob($user->id);
$job->handle();
- $this->assertTrue($job->hasFailed());
- $this->assertSame("User {$user->id} is actually deleted.", $job->failureMessage);
-
- // TODO: Test failures on domain sanity checks
+ $user->refresh();
- $job = new \App\Jobs\User\CreateJob(123);
- $job->handle();
-
- $this->assertTrue($job->isReleased());
$this->assertFalse($job->hasFailed());
+ $this->assertFalse($user->isLdapReady());
+ $this->assertFalse($user->isImapReady());
+ $this->assertTrue($user->isDeleted());
+
+ // TODO: Test partial execution, i.e. only IMAP or only LDAP
}
}
diff --git a/src/tests/Feature/Jobs/User/UpdateTest.php b/src/tests/Feature/Jobs/User/UpdateTest.php
--- a/src/tests/Feature/Jobs/User/UpdateTest.php
+++ b/src/tests/Feature/Jobs/User/UpdateTest.php
@@ -32,6 +32,7 @@
* Test job handle
*
* @group ldap
+ * @group imap
*/
public function testHandle(): void
{
@@ -90,5 +91,7 @@
$this->assertTrue($job->hasFailed());
$this->assertSame("User 123 could not be found in the database.", $job->failureMessage);
+
+ // TODO: Test IMAP, e.g. quota change
}
}
diff --git a/src/tests/Feature/ResourceTest.php b/src/tests/Feature/ResourceTest.php
--- a/src/tests/Feature/ResourceTest.php
+++ b/src/tests/Feature/ResourceTest.php
@@ -113,7 +113,7 @@
$this->assertMatchesRegularExpression('/^resource-[0-9]{1,20}@kolabnow\.com$/', $resource->email);
$this->assertSame('Reśo', $resource->name);
$this->assertTrue($resource->isNew());
- $this->assertTrue($resource->isActive());
+ $this->assertFalse($resource->isActive());
$this->assertFalse($resource->isDeleted());
$this->assertFalse($resource->isLdapReady());
$this->assertFalse($resource->isImapReady());
@@ -133,13 +133,6 @@
&& $resourceId === $resource->id;
}
);
-
- Queue::assertPushedWithChain(
- \App\Jobs\Resource\CreateJob::class,
- [
- \App\Jobs\Resource\VerifyJob::class,
- ]
- );
}
/**
@@ -225,6 +218,13 @@
$resource->setSetting('invitation_policy', 'accept');
Queue::assertPushed(\App\Jobs\Resource\UpdateJob::class, 1);
+ Queue::assertPushed(
+ \App\Jobs\Resource\UpdateJob::class,
+ function ($job) use ($resource) {
+ return $resource->id === TestCase::getObjectProperty($job, 'resourceId')
+ && ['invitation_policy' => null] === TestCase::getObjectProperty($job, 'properties');
+ }
+ );
// Note: We test both current resource as well as fresh resource object
// to make sure cache works as expected
@@ -242,6 +242,13 @@
$resource->setSetting('invitation_policy', 'reject');
Queue::assertPushed(\App\Jobs\Resource\UpdateJob::class, 1);
+ Queue::assertPushed(
+ \App\Jobs\Resource\UpdateJob::class,
+ function ($job) use ($resource) {
+ return $resource->id === TestCase::getObjectProperty($job, 'resourceId')
+ && ['invitation_policy' => 'accept'] === TestCase::getObjectProperty($job, 'properties');
+ }
+ );
$this->assertSame('test1', $resource->getSetting('unknown'));
$this->assertSame('reject', $resource->fresh()->getSetting('invitation_policy'));
@@ -257,6 +264,13 @@
$resource->setSetting('invitation_policy', null);
Queue::assertPushed(\App\Jobs\Resource\UpdateJob::class, 1);
+ Queue::assertPushed(
+ \App\Jobs\Resource\UpdateJob::class,
+ function ($job) use ($resource) {
+ return $resource->id === TestCase::getObjectProperty($job, 'resourceId')
+ && ['invitation_policy' => 'reject'] === TestCase::getObjectProperty($job, 'properties');
+ }
+ );
$this->assertSame(null, $resource->getSetting('unknown'));
$this->assertSame(null, $resource->fresh()->getSetting('invitation_policy'));
diff --git a/src/tests/Feature/SharedFolderTest.php b/src/tests/Feature/SharedFolderTest.php
--- a/src/tests/Feature/SharedFolderTest.php
+++ b/src/tests/Feature/SharedFolderTest.php
@@ -177,7 +177,7 @@
$this->assertMatchesRegularExpression('/^mail-[0-9]{1,20}@kolabnow\.com$/', $folder->email);
$this->assertSame('Reśo', $folder->name);
$this->assertTrue($folder->isNew());
- $this->assertTrue($folder->isActive());
+ $this->assertFalse($folder->isActive());
$this->assertFalse($folder->isDeleted());
$this->assertFalse($folder->isLdapReady());
$this->assertFalse($folder->isImapReady());
@@ -197,13 +197,6 @@
&& $folderId === $folder->id;
}
);
-
- Queue::assertPushedWithChain(
- \App\Jobs\SharedFolder\CreateJob::class,
- [
- \App\Jobs\SharedFolder\VerifyJob::class,
- ]
- );
}
/**
@@ -289,6 +282,13 @@
$folder->setSetting('acl', 'test');
Queue::assertPushed(\App\Jobs\SharedFolder\UpdateJob::class, 1);
+ Queue::assertPushed(
+ \App\Jobs\SharedFolder\UpdateJob::class,
+ function ($job) use ($folder) {
+ return $folder->id === TestCase::getObjectProperty($job, 'folderId')
+ && ['acl' => null] === TestCase::getObjectProperty($job, 'properties');
+ }
+ );
// Note: We test both current folder as well as fresh folder object
// to make sure cache works as expected
@@ -306,6 +306,13 @@
$folder->setSetting('acl', 'test1');
Queue::assertPushed(\App\Jobs\SharedFolder\UpdateJob::class, 1);
+ Queue::assertPushed(
+ \App\Jobs\SharedFolder\UpdateJob::class,
+ function ($job) use ($folder) {
+ return $folder->id === TestCase::getObjectProperty($job, 'folderId')
+ && ['acl' => 'test'] === TestCase::getObjectProperty($job, 'properties');
+ }
+ );
$this->assertSame('test1', $folder->getSetting('unknown'));
$this->assertSame('test1', $folder->fresh()->getSetting('acl'));
@@ -321,6 +328,13 @@
$folder->setSetting('acl', null);
Queue::assertPushed(\App\Jobs\SharedFolder\UpdateJob::class, 1);
+ Queue::assertPushed(
+ \App\Jobs\SharedFolder\UpdateJob::class,
+ function ($job) use ($folder) {
+ return $folder->id === TestCase::getObjectProperty($job, 'folderId')
+ && ['acl' => 'test1'] === TestCase::getObjectProperty($job, 'properties');
+ }
+ );
$this->assertSame(null, $folder->getSetting('unknown'));
$this->assertSame(null, $folder->fresh()->getSetting('acl'));
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
@@ -321,7 +321,7 @@
$this->assertSame("user-test@$domain", $result->email);
$this->assertSame($user->id, $result->id);
- $this->assertSame(User::STATUS_NEW | User::STATUS_ACTIVE, $result->status);
+ $this->assertSame(User::STATUS_NEW, $result->status);
$this->assertSame(0, $user->passwords()->count());
Queue::assertPushed(\App\Jobs\User\CreateJob::class, 1);
@@ -338,28 +338,6 @@
}
);
- Queue::assertPushedWithChain(
- \App\Jobs\User\CreateJob::class,
- [
- \App\Jobs\User\VerifyJob::class,
- ]
- );
-/*
- FIXME: Looks like we can't really do detailed assertions on chained jobs
- Another thing to consider is if we maybe should run these jobs
- independently (not chained) and make sure there's no race-condition
- in status update
-
- Queue::assertPushed(\App\Jobs\User\VerifyJob::class, 1);
- Queue::assertPushed(\App\Jobs\User\VerifyJob::class, function ($job) use ($user) {
- $userEmail = TestCase::getObjectProperty($job, 'userEmail');
- $userId = TestCase::getObjectProperty($job, 'userId');
-
- return $userEmail === $user->email
- && $userId === $user->id;
- });
-*/
-
// Test invoking KeyCreateJob
$this->deleteTestUser("user-test@$domain");
@@ -957,7 +935,7 @@
}
/**
- * Test User::hasSku() method
+ * Test User::hasSku() and countEntitlementsBySku() methods
*/
public function testHasSku(): void
{
@@ -967,6 +945,11 @@
$this->assertTrue($john->hasSku('storage'));
$this->assertFalse($john->hasSku('beta'));
$this->assertFalse($john->hasSku('unknown'));
+
+ $this->assertSame(0, $john->countEntitlementsBySku('unknown'));
+ $this->assertSame(0, $john->countEntitlementsBySku('2fa'));
+ $this->assertSame(1, $john->countEntitlementsBySku('mailbox'));
+ $this->assertSame(5, $john->countEntitlementsBySku('storage'));
}
/**
@@ -1149,12 +1132,6 @@
return $userA->id === TestCase::getObjectProperty($job, 'userId');
}
);
- Queue::assertPushedWithChain(
- \App\Jobs\User\CreateJob::class,
- [
- \App\Jobs\User\VerifyJob::class,
- ]
- );
}
/**
diff --git a/src/tests/TestCaseTrait.php b/src/tests/TestCaseTrait.php
--- a/src/tests/TestCaseTrait.php
+++ b/src/tests/TestCaseTrait.php
@@ -563,7 +563,7 @@
// Note: we do not want to use user restore here
User::where('id', $user->id)->forceDelete();
$user = User::create(['email' => $email] + $attrib);
- }
+ }
return $user;
}

File Metadata

Mime Type
text/plain
Expires
Sat, Apr 4, 1:20 AM (17 h, 41 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18827557
Default Alt Text
D3785.1775265632.diff (95 KB)

Event Timeline