Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117914193
D3785.1775402883.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
102 KB
Referenced Files
None
Subscribers
None
D3785.1775402883.diff
View Options
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,372 @@
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.
*
- * @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
+ {
+ $domainName = explode('@', $group->email, 2)[1];
+
+ // Cleanup ACL
+ // FIXME: Since all groups in Kolab4 have email address,
+ // should we consider using it in ACL instead of the name?
+ // Also we need to decide what to do and configure IMAP appropriately,
+ // right now groups in ACL does not work for me at all.
+ \App\Jobs\IMAP\AclCleanupJob::dispatch($group->name, $domainName);
+
+ 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, $username);
+ $imap = self::initIMAP($config);
- $folders = $imap->listMailboxes('', '*');
+ $mailbox = self::toUTF7('user/' . $user->email);
+
+ // 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;
+ }
+
+ // 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();
- if (!is_array($folders)) {
- throw new \Exception("Failed to get IMAP folders");
+ 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);
+
+ $imap->closeConnection();
+
+ // Cleanup ACL
+ \App\Jobs\IMAP\AclCleanupJob::dispatch($user->email);
+
+ 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]);
}
- return count($folders) > 0;
+ $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 +385,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 +408,134 @@
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;
+ }
+
+ /**
+ * Remove ACL for a specified user/group anywhere in the IMAP
+ *
+ * @param string $ident ACL identifier (user email or e.g. group name)
+ * @param string $domain ACL domain
+ */
+ public static function aclCleanup(string $ident, string $domain = ''): void
+ {
+ $config = self::getConfig();
+ $imap = self::initIMAP($config);
+
+ if (strpos($ident, '@')) {
+ $domain = explode('@', $ident, 2)[1];
+ }
+
+ $callback = function ($folder) use ($imap, $ident) {
+ $acl = $imap->getACL($folder);
+ if (is_array($acl) && isset($acl[$ident])) {
+ $imap->deleteACL($folder, $ident);
+ }
+ };
+
+ $folders = $imap->listMailboxes('', "user/*@{$domain}");
+
+ if (!is_array($folders)) {
+ $imap->closeConnection();
+ throw new \Exception("Failed to get IMAP folders");
+ }
+
+ array_walk($folders, $callback);
+
+ $folders = $imap->listMailboxes('', "shared/*@{$domain}");
+
+ if (!is_array($folders)) {
+ $imap->closeConnection();
+ throw new \Exception("Failed to get IMAP folders");
+ }
+
+ array_walk($folders, $callback);
+
+ $imap->closeConnection();
+ }
+
+ /**
+ * 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/Backends/Roundcube.php b/src/app/Backends/Roundcube.php
--- a/src/app/Backends/Roundcube.php
+++ b/src/app/Backends/Roundcube.php
@@ -195,6 +195,18 @@
}
}
+ /**
+ * Delete a Roundcube user.
+ *
+ * @param string $email User email address
+ */
+ public static function deleteUser(string $email): void
+ {
+ $db = self::dbh();
+
+ $db->table(self::USERS_TABLE)->where('username', \strtolower($email))->delete();
+ }
+
/**
* Find the Roundcube user identifier for the specified user.
*
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/UserCreate.php b/src/app/Console/Commands/Job/ResourceCreate.php
copy from src/app/Console/Commands/Job/UserCreate.php
copy to src/app/Console/Commands/Job/ResourceCreate.php
--- a/src/app/Console/Commands/Job/UserCreate.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 UserCreate extends Command
+class ResourceCreate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'job:usercreate {user}';
+ protected $signature = 'job:resourcecreate {resource}';
/**
* The console command description.
*
* @var string
*/
- protected $description = "Execute the UserCreate 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\CreateJob($user->id);
+ $job = new \App\Jobs\Resource\CreateJob($resource->id);
$job->handle();
}
}
diff --git a/src/app/Console/Commands/Job/UserCreate.php b/src/app/Console/Commands/Job/ResourceUpdate.php
copy from src/app/Console/Commands/Job/UserCreate.php
copy to src/app/Console/Commands/Job/ResourceUpdate.php
--- a/src/app/Console/Commands/Job/UserCreate.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 UserCreate extends Command
+class ResourceUpdate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'job:usercreate {user}';
+ protected $signature = 'job:resourceupdate {resource}';
/**
* The console command description.
*
* @var string
*/
- protected $description = "Execute the UserCreate 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\CreateJob($user->id);
+ $job = new \App\Jobs\Resource\UpdateJob($resource->id);
$job->handle();
}
}
diff --git a/src/app/Console/Commands/Job/UserCreate.php b/src/app/Console/Commands/Job/SharedFolderCreate.php
copy from src/app/Console/Commands/Job/UserCreate.php
copy to src/app/Console/Commands/Job/SharedFolderCreate.php
--- a/src/app/Console/Commands/Job/UserCreate.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 UserCreate extends Command
+class SharedFolderCreate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'job:usercreate {user}';
+ protected $signature = 'job:sharedfoldercreate {folder}';
/**
* The console command description.
*
* @var string
*/
- protected $description = "Execute the UserCreate 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\CreateJob($user->id);
+ $job = new \App\Jobs\SharedFolder\CreateJob($folder->id);
$job->handle();
}
}
diff --git a/src/app/Console/Commands/Job/UserCreate.php b/src/app/Console/Commands/Job/SharedFolderUpdate.php
copy from src/app/Console/Commands/Job/UserCreate.php
copy to src/app/Console/Commands/Job/SharedFolderUpdate.php
--- a/src/app/Console/Commands/Job/UserCreate.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 UserCreate extends Command
+class SharedFolderUpdate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'job:usercreate {user}';
+ protected $signature = 'job:sharedfolderupdate {folder}';
/**
* The console command description.
*
* @var string
*/
- protected $description = "Execute the UserCreate 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\CreateJob($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
@@ -532,30 +532,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,17 @@
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;
}
-
+/*
+ if (!\App\Backends\IMAP::deleteGroup($group)) {
+ throw new \Exception("Failed to delete group {$this->groupId} from IMAP.");
+ }
+*/
+ $group->status |= \App\Group::STATUS_DELETED;
$group->save();
}
}
diff --git a/src/app/Jobs/IMAP/AclCleanupJob.php b/src/app/Jobs/IMAP/AclCleanupJob.php
new file mode 100644
--- /dev/null
+++ b/src/app/Jobs/IMAP/AclCleanupJob.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace App\Jobs\IMAP;
+
+use App\Jobs\CommonJob;
+
+/**
+ * Remove ACL for a specified user/group anywhere in IMAP
+ */
+class AclCleanupJob extends CommonJob
+{
+ /**
+ * The ACL identifier
+ *
+ * @var string
+ */
+ protected $ident;
+
+ /**
+ * The ACL subject domain
+ *
+ * @var string
+ */
+ protected $domain;
+
+ /**
+ * The number of seconds the job can run before timing out.
+ *
+ * @var int
+ */
+ public $timeout = 60 * 60;
+
+
+ /**
+ * Create a new job instance.
+ *
+ * @param string $ident ACL identifier
+ * @param string $domain ACL domain
+ *
+ * @return void
+ */
+ public function __construct(string $ident, string $domain = '')
+ {
+ $this->ident = $ident;
+ $this->domain = $domain;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ *
+ * @throws \Exception
+ */
+ public function handle()
+ {
+ \App\Backends\IMAP::aclCleanup($this->ident, $this->domain);
+ }
+}
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,26 @@
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;
}
+ if (\config('database.connections.roundcube')) {
+ \App\Backends\Roundcube::deleteUser($user->email);
+ }
+
+ $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
@@ -3,10 +3,225 @@
namespace Tests\Feature\Backends;
use App\Backends\IMAP;
+use App\Backends\LDAP;
use Tests\TestCase;
class IMAPTest extends TestCase
{
+ private $imap;
+ private $user;
+ private $group;
+ private $resource;
+ private $folder;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ if ($this->imap) {
+ $this->imap->closeConnection();
+ $this->imap = null;
+ }
+
+ if ($this->user) {
+ $this->deleteTestUser($this->user->email);
+ }
+ if ($this->group) {
+ $this->deleteTestGroup($this->group->email);
+ }
+ if ($this->resource) {
+ $this->deleteTestResource($this->resource->email);
+ }
+ if ($this->folder) {
+ $this->deleteTestSharedFolder($this->folder->email);
+ }
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test aclCleanup()
+ *
+ * @group imap
+ * @group ldap
+ */
+ public function testAclCleanup(): void
+ {
+ $this->user = $user = $this->getTestUser('test-' . time() . '@kolab.org');
+ $this->group = $group = $this->getTestGroup('test-group-' . time() . '@kolab.org');
+
+ // SETACL requires that the user/group exists in LDAP
+ LDAP::createUser($user);
+ // LDAP::createGroup($group);
+
+ // First, set some ACLs that we'll expect to be removed later
+ $imap = $this->getImap();
+
+ $this->assertTrue($imap->setACL('user/john@kolab.org', $user->email, 'lrs'));
+ $this->assertTrue($imap->setACL('shared/Resources/Conference Room #1@kolab.org', $user->email, 'lrs'));
+/*
+ $this->assertTrue($imap->setACL('user/john@kolab.org', $group->name, 'lrs'));
+ $this->assertTrue($imap->setACL('shared/Resources/Conference Room #1@kolab.org', $group->name, 'lrs'));
+*/
+ // Cleanup ACL of a user
+ IMAP::aclCleanup($user->email);
+
+ $acl = $imap->getACL('user/john@kolab.org');
+ $this->assertTrue(is_array($acl) && !isset($acl[$user->email]));
+ $acl = $imap->getACL('shared/Resources/Conference Room #1@kolab.org');
+ $this->assertTrue(is_array($acl) && !isset($acl[$user->email]));
+
+/*
+ // Cleanup ACL of a group
+ IMAP::aclCleanup($group->name, 'kolab.org');
+
+ $acl = $imap->getACL('user/john@kolab.org');
+ $this->assertTrue(is_array($acl) && !isset($acl[$user->email]));
+ $acl = $imap->getACL('shared/Resources/Conference Room #1@kolab.org');
+ $this->assertTrue(is_array($acl) && !isset($acl[$user->email]));
+*/
+ }
+
+ /**
+ * 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 +253,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/Backends/RoundcubeTest.php b/src/tests/Feature/Backends/RoundcubeTest.php
--- a/src/tests/Feature/Backends/RoundcubeTest.php
+++ b/src/tests/Feature/Backends/RoundcubeTest.php
@@ -32,7 +32,7 @@
*
* @group roundcube
*/
- public function testUserCreation(): void
+ public function testUserCreationAndDeletion(): void
{
$user = $this->getTestUser('roundcube@' . \config('app.domain'));
$user->setSetting('first_name', 'First');
@@ -57,5 +57,11 @@
$this->assertSame($user->email, $rcidentity->email);
$this->assertSame('First Last', $rcidentity->name);
$this->assertSame(1, $rcidentity->standard);
+
+ // Delete the user
+ Roundcube::deleteUser($user->email);
+
+ $this->assertNull($db->table('users')->where('username', $user->email)->first());
+ $this->assertNull($db->table('identities')->where('user_id', $rcuser->user_id)->first());
}
}
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
@@ -495,29 +495,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);
@@ -525,11 +504,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
@@ -39,6 +39,48 @@
parent::tearDown();
}
+ /**
+ * 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/Group/DeleteTest.php b/src/tests/Feature/Jobs/Group/DeleteTest.php
--- a/src/tests/Feature/Jobs/Group/DeleteTest.php
+++ b/src/tests/Feature/Jobs/Group/DeleteTest.php
@@ -3,6 +3,7 @@
namespace Tests\Feature\Jobs\Group;
use App\Group;
+use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
class DeleteTest extends TestCase
@@ -42,6 +43,8 @@
$this->assertTrue($group->fresh()->isLdapReady());
+ Queue::fake();
+
$job = new \App\Jobs\Group\DeleteJob($group->id);
$job->handle();
@@ -49,7 +52,17 @@
$this->assertFalse($group->isLdapReady());
$this->assertTrue($group->isDeleted());
-
+/*
+ Queue::assertPushed(\App\Jobs\IMAP\AclCleanupJob::class, 1);
+ Queue::assertPushed(
+ \App\Jobs\IMAP\AclCleanupJob::class,
+ function ($job) {
+ $ident = TestCase::getObjectProperty($job, 'ident');
+ $domain = TestCase::getObjectProperty($job, 'domain');
+ return $ident == 'group' && $domain === 'kolab.org';
+ }
+ );
+*/
// Test non-existing group ID
$job = new \App\Jobs\Group\DeleteJob(123);
$job->handle();
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
{
@@ -42,24 +43,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();
@@ -80,5 +84,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
{
@@ -42,24 +43,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();
@@ -80,5 +84,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,13 +71,14 @@
$this->assertTrue($job->hasFailed());
$this->assertSame("User {$user->id} is actually deleted.", $job->failureMessage);
- // TODO: Test failures on domain sanity checks
-
- $this->expectException(\Exception::class);
+ // 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/DeleteTest.php b/src/tests/Feature/Jobs/User/DeleteTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Jobs/User/DeleteTest.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Tests\Feature\Jobs\User;
+
+use App\Backends\Roundcube;
+use App\User;
+use Illuminate\Support\Facades\Queue;
+use Tests\TestCase;
+
+class DeleteTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->deleteTestUser('new-job-user@' . \config('app.domain'));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ $this->deleteTestUser('new-job-user@' . \config('app.domain'));
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test job handle
+ *
+ * @group ldap
+ * @group imap
+ * @group roundcube
+ */
+ public function testHandle(): void
+ {
+ Queue::fake();
+
+ $rcdb = Roundcube::dbh();
+
+ $user = $this->getTestUser('new-job-user@' . \config('app.domain'));
+ $rcuser = Roundcube::userId($user->email);
+
+ // Create the user in LDAP+IMAP
+ $job = new \App\Jobs\User\CreateJob($user->id);
+ $job->handle();
+
+ $user->refresh();
+
+ $this->assertTrue($user->isLdapReady());
+ $this->assertTrue($user->isImapReady());
+ $this->assertFalse($user->isDeleted());
+ $this->assertNotNull($rcdb->table('users')->where('username', $user->email)->first());
+
+ // Test job failure (user already deleted)
+ $user->status |= User::STATUS_DELETED;
+ $user->save();
+
+ $job = new \App\Jobs\User\DeleteJob($user->id);
+ $job->handle();
+
+ $this->assertTrue($job->hasFailed());
+ $this->assertSame("User {$user->id} is already marked as deleted.", $job->failureMessage);
+
+ // Test success delete from LDAP, IMAP and Roundcube
+ $user->status ^= User::STATUS_DELETED;
+ $user->save();
+
+ $this->assertFalse($user->isDeleted());
+
+ $job = new \App\Jobs\User\DeleteJob($user->id);
+ $job->handle();
+
+ $user->refresh();
+
+ $this->assertFalse($job->hasFailed());
+ $this->assertFalse($user->isLdapReady());
+ $this->assertFalse($user->isImapReady());
+ $this->assertTrue($user->isDeleted());
+ $this->assertNull($rcdb->table('users')->where('username', $user->email)->first());
+
+ Queue::assertPushed(\App\Jobs\IMAP\AclCleanupJob::class, 1);
+ Queue::assertPushed(
+ \App\Jobs\IMAP\AclCleanupJob::class,
+ function ($job) use ($user) {
+ $ident = TestCase::getObjectProperty($job, 'ident');
+ $domain = TestCase::getObjectProperty($job, 'domain');
+ return $ident == $user->email && $domain === '';
+ }
+ );
+
+ // 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");
@@ -973,7 +951,7 @@
}
/**
- * Test User::hasSku() method
+ * Test User::hasSku() and countEntitlementsBySku() methods
*/
public function testHasSku(): void
{
@@ -983,6 +961,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'));
}
/**
@@ -1165,12 +1148,6 @@
return $userA->id === TestCase::getObjectProperty($job, 'userId');
}
);
- Queue::assertPushedWithChain(
- \App\Jobs\User\CreateJob::class,
- [
- \App\Jobs\User\VerifyJob::class,
- ]
- );
}
/**
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Apr 5, 3:28 PM (8 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18833789
Default Alt Text
D3785.1775402883.diff (102 KB)
Attached To
Mode
D3785: [DRAFT] Direct IMAP backend
Attached
Detach File
Event Timeline