Page MenuHomePhorge

D3785.1775466532.diff
No OneTemporary

Authored By
Unknown
Size
126 KB
Referenced Files
None
Subscribers
None

D3785.1775466532.diff

diff --git a/src/.env.example b/src/.env.example
--- a/src/.env.example
+++ b/src/.env.example
@@ -17,6 +17,8 @@
APP_WITH_SERVICES=1
APP_WITH_FILES=1
+APP_LDAP=1
+
APP_HEADER_CSP="connect-src 'self'; child-src 'self'; font-src 'self'; form-action 'self' data:; frame-ancestors 'self'; img-src blob: data: 'self' *; media-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; default-src 'self';"
APP_HEADER_XFO=sameorigin
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,46 +3,393 @@
namespace App\Backends;
use App\Domain;
+use App\Group;
+use App\Resource;
+use App\SharedFolder;
use App\User;
class IMAP
{
+ /** @const array Group settings used by the backend */
+ public const GROUP_SETTINGS = [];
+
+ /** @const array Resource settings used by the backend */
+ public const RESOURCE_SETTINGS = [
+ 'folder',
+ 'invitation_policy',
+ ];
+
+ /** @const array Shared folder settings used by the backend */
+ public const SHARED_FOLDER_SETTINGS = [
+ 'folder',
+ 'acl',
+ ];
+
+ /** @const array User settings used by the backend */
+ public const USER_SETTINGS = [];
+
+
+ /** @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;
}
/**
- * Check if we can connect to the imap server
+ * Create a resource.
*
- * @return bool True on success
+ * @param \App\Resource $resource Resource
+ *
+ * @return bool True if a resource was created successfully, False otherwise
+ * @throws \Exception
*/
- public static function healthcheck(): bool
+ 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;
+ }
+
/**
* Check if a shared folder is set up.
*
@@ -57,7 +404,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];
}
@@ -80,6 +427,150 @@
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);
+
+ # List the mailbox so we don't verify if shared folders are existing.
+ $folders = $imap->listMailboxes('', "INBOX");
+
+ \Log::debug("Verify account output" . var_export($folders, true));
+
+ $imap->closeConnection();
+
+ if (!is_array($folders)) {
+ return false;
+ }
+
+ return count($folders) > 0;
+ }
+
+ /**
+ * Check if we can connect to the imap server
+ *
+ * @return bool True on success
+ */
+ public static function healthcheck(): bool
+ {
+ $config = self::getConfig();
+ $imap = self::initIMAP($config);
+ $imap->closeConnection();
+ return true;
+ }
+
+ /**
+ * 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/DomainUpdate.php b/src/app/Console/Commands/Job/ResourceCreate.php
copy from src/app/Console/Commands/Job/DomainUpdate.php
copy to src/app/Console/Commands/Job/ResourceCreate.php
--- a/src/app/Console/Commands/Job/DomainUpdate.php
+++ b/src/app/Console/Commands/Job/ResourceCreate.php
@@ -3,23 +3,22 @@
namespace App\Console\Commands\Job;
use App\Console\Command;
-use App\Domain;
-class DomainUpdate extends Command
+class ResourceCreate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'job:domainupdate {domain}';
+ protected $signature = 'job:resourcecreate {resource}';
/**
* The console command description.
*
* @var string
*/
- protected $description = "Execute the DomainUpdate job (again).";
+ protected $description = "Execute the resource creation job (again).";
/**
* Execute the console command.
@@ -28,13 +27,13 @@
*/
public function handle()
{
- $domain = $this->getDomain($this->argument('domain'));
+ $resource = $this->getResource($this->argument('resource'));
- if (!$domain) {
+ if (!$resource) {
return 1;
}
- $job = new \App\Jobs\Domain\UpdateJob($domain->id);
+ $job = new \App\Jobs\Resource\CreateJob($resource->id);
$job->handle();
}
}
diff --git a/src/app/Console/Commands/Job/DomainUpdate.php b/src/app/Console/Commands/Job/ResourceUpdate.php
copy from src/app/Console/Commands/Job/DomainUpdate.php
copy to src/app/Console/Commands/Job/ResourceUpdate.php
--- a/src/app/Console/Commands/Job/DomainUpdate.php
+++ b/src/app/Console/Commands/Job/ResourceUpdate.php
@@ -3,23 +3,22 @@
namespace App\Console\Commands\Job;
use App\Console\Command;
-use App\Domain;
-class DomainUpdate extends Command
+class ResourceUpdate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'job:domainupdate {domain}';
+ protected $signature = 'job:resourceupdate {resource}';
/**
* The console command description.
*
* @var string
*/
- protected $description = "Execute the DomainUpdate job (again).";
+ protected $description = "Execute the resource update job (again).";
/**
* Execute the console command.
@@ -28,13 +27,13 @@
*/
public function handle()
{
- $domain = $this->getDomain($this->argument('domain'));
+ $resource = $this->getResource($this->argument('resource'));
- if (!$domain) {
+ if (!$resource) {
return 1;
}
- $job = new \App\Jobs\Domain\UpdateJob($domain->id);
+ $job = new \App\Jobs\Resource\UpdateJob($resource->id);
$job->handle();
}
}
diff --git a/src/app/Console/Commands/Job/DomainUpdate.php b/src/app/Console/Commands/Job/SharedFolderCreate.php
copy from src/app/Console/Commands/Job/DomainUpdate.php
copy to src/app/Console/Commands/Job/SharedFolderCreate.php
--- a/src/app/Console/Commands/Job/DomainUpdate.php
+++ b/src/app/Console/Commands/Job/SharedFolderCreate.php
@@ -3,23 +3,22 @@
namespace App\Console\Commands\Job;
use App\Console\Command;
-use App\Domain;
-class DomainUpdate extends Command
+class SharedFolderCreate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'job:domainupdate {domain}';
+ protected $signature = 'job:sharedfoldercreate {folder}';
/**
* The console command description.
*
* @var string
*/
- protected $description = "Execute the DomainUpdate job (again).";
+ protected $description = "Execute the shared folder creation job (again).";
/**
* Execute the console command.
@@ -28,13 +27,13 @@
*/
public function handle()
{
- $domain = $this->getDomain($this->argument('domain'));
+ $folder = $this->getSharedFolder($this->argument('folder'));
- if (!$domain) {
+ if (!$folder) {
return 1;
}
- $job = new \App\Jobs\Domain\UpdateJob($domain->id);
+ $job = new \App\Jobs\SharedFolder\CreateJob($folder->id);
$job->handle();
}
}
diff --git a/src/app/Console/Commands/Job/DomainUpdate.php b/src/app/Console/Commands/Job/SharedFolderUpdate.php
copy from src/app/Console/Commands/Job/DomainUpdate.php
copy to src/app/Console/Commands/Job/SharedFolderUpdate.php
--- a/src/app/Console/Commands/Job/DomainUpdate.php
+++ b/src/app/Console/Commands/Job/SharedFolderUpdate.php
@@ -3,23 +3,22 @@
namespace App\Console\Commands\Job;
use App\Console\Command;
-use App\Domain;
-class DomainUpdate extends Command
+class SharedFolderUpdate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'job:domainupdate {domain}';
+ protected $signature = 'job:sharedfolderupdate {folder}';
/**
* The console command description.
*
* @var string
*/
- protected $description = "Execute the DomainUpdate job (again).";
+ protected $description = "Execute the shared folder update job (again).";
/**
* Execute the console command.
@@ -28,13 +27,13 @@
*/
public function handle()
{
- $domain = $this->getDomain($this->argument('domain'));
+ $folder = $this->getSharedFolder($this->argument('folder'));
- if (!$domain) {
+ if (!$folder) {
return 1;
}
- $job = new \App\Jobs\Domain\UpdateJob($domain->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
@@ -4,7 +4,6 @@
use App\Domain;
use App\Http\Controllers\RelationController;
-use App\Backends\LDAP;
use App\Rules\UserEmailDomain;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@@ -299,20 +298,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/NGINXController.php b/src/app/Http/Controllers/API/V4/NGINXController.php
--- a/src/app/Http/Controllers/API/V4/NGINXController.php
+++ b/src/app/Http/Controllers/API/V4/NGINXController.php
@@ -9,8 +9,6 @@
class NGINXController extends Controller
{
-
-
/**
* Authorize with the provided credentials.
*
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/Http/Controllers/RelationController.php b/src/app/Http/Controllers/RelationController.php
--- a/src/app/Http/Controllers/RelationController.php
+++ b/src/app/Http/Controllers/RelationController.php
@@ -151,9 +151,21 @@
protected static function processStateInfo($object, array $steps): array
{
$process = [];
+ $withLdap = \config('app.with_ldap');
+ $withImap = \config('app.with_imap');
// Create a process check list
foreach ($steps as $step_name => $state) {
+ // Remove LDAP related steps if the backend is disabled
+ if (!$withLdap && strpos($step_name, '-ldap-')) {
+ continue;
+ }
+
+ // Remove IMAP related steps if the backend is disabled
+ if (!$withImap && strpos($step_name, '-imap-')) {
+ continue;
+ }
+
$step = [
'label' => $step_name,
'title' => \trans("app.process-{$step_name}"),
diff --git a/src/app/Jobs/Domain/CreateJob.php b/src/app/Jobs/Domain/CreateJob.php
--- a/src/app/Jobs/Domain/CreateJob.php
+++ b/src/app/Jobs/Domain/CreateJob.php
@@ -19,13 +19,13 @@
return;
}
- if (!$domain->isLdapReady()) {
+ if (\config('app.with_ldap') && !$domain->isLdapReady()) {
\App\Backends\LDAP::createDomain($domain);
$domain->status |= \App\Domain::STATUS_LDAP_READY;
$domain->save();
-
- \App\Jobs\Domain\VerifyJob::dispatch($domain->id);
}
+
+ \App\Jobs\Domain\VerifyJob::dispatch($domain->id);
}
}
diff --git a/src/app/Jobs/Domain/DeleteJob.php b/src/app/Jobs/Domain/DeleteJob.php
--- a/src/app/Jobs/Domain/DeleteJob.php
+++ b/src/app/Jobs/Domain/DeleteJob.php
@@ -25,14 +25,13 @@
return;
}
- \App\Backends\LDAP::deleteDomain($domain);
+ if (\config('app.with_ldap') && $domain->isLdapReady()) {
+ \App\Backends\LDAP::deleteDomain($domain);
- $domain->status |= \App\Domain::STATUS_DELETED;
-
- if ($domain->isLdapReady()) {
$domain->status ^= \App\Domain::STATUS_LDAP_READY;
}
+ $domain->status |= \App\Domain::STATUS_DELETED;
$domain->save();
}
}
diff --git a/src/app/Jobs/Domain/UpdateJob.php b/src/app/Jobs/Domain/UpdateJob.php
--- a/src/app/Jobs/Domain/UpdateJob.php
+++ b/src/app/Jobs/Domain/UpdateJob.php
@@ -19,7 +19,7 @@
return;
}
- if (!$domain->isLdapReady()) {
+ if (!\config('app.with_ldap') || !$domain->isLdapReady()) {
$this->delete();
return;
}
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
@@ -19,11 +19,13 @@
return;
}
- if (!$group->isLdapReady()) {
+ if (\config('app.with_ldap') && !$group->isLdapReady()) {
\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,21 @@
return;
}
- \App\Backends\LDAP::deleteGroup($group);
+ if (\config('app.with_ldap') && $group->isLdapReady()) {
+ \App\Backends\LDAP::deleteGroup($group);
- $group->status |= \App\Group::STATUS_DELETED;
-
- if ($group->isLdapReady()) {
$group->status ^= \App\Group::STATUS_LDAP_READY;
}
+/*
+ if (\config('app.with_imap') && $group->isImapReady()) {
+ if (!\App\Backends\IMAP::deleteGroup($group)) {
+ throw new \Exception("Failed to delete group {$this->groupId} from IMAP.");
+ }
+ $group->status ^= \App\Group::STATUS_IMAP_READY;
+ }
+*/
+ $group->status |= \App\Group::STATUS_DELETED;
$group->save();
}
}
diff --git a/src/app/Jobs/Group/UpdateJob.php b/src/app/Jobs/Group/UpdateJob.php
--- a/src/app/Jobs/Group/UpdateJob.php
+++ b/src/app/Jobs/Group/UpdateJob.php
@@ -21,7 +21,7 @@
}
// Cancel the update if the group is deleted or not yet in LDAP
- if (!$group->isLdapReady() || $group->isDeleted()) {
+ if (!\config('app.with_ldap') || !$group->isLdapReady() || $group->isDeleted()) {
$this->delete();
return;
}
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,10 +30,7 @@
return;
}
- if ($resource->isLdapReady()) {
- $this->fail(new \Exception("Resource {$this->resourceId} is already marked as ldap-ready."));
- return;
- }
+ $withLdap = \config('app.with_ldap');
// see if the domain is ready
$domain = $resource->domain();
@@ -48,14 +45,27 @@
return;
}
- if (!$domain->isLdapReady()) {
+ if ($withLdap && !$domain->isLdapReady()) {
$this->release(60);
return;
}
- \App\Backends\LDAP::createResource($resource);
+ if ($withLdap && !$resource->isLdapReady()) {
+ \App\Backends\LDAP::createResource($resource);
+
+ $resource->status |= \App\Resource::STATUS_LDAP_READY;
+ $resource->save();
+ }
+
+ if (\config('app.with_imap') && !$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);
+ if (\config('app.with_ldap') && $resource->isLdapReady()) {
+ \App\Backends\LDAP::deleteResource($resource);
- $resource->status |= \App\Resource::STATUS_DELETED;
-
- if ($resource->isLdapReady()) {
$resource->status ^= \App\Resource::STATUS_LDAP_READY;
+ $resource->save();
}
- if ($resource->isImapReady()) {
+ if (\config('app.with_imap') && $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 (\config('app.with_ldap') && $resource->isLdapReady()) {
+ \App\Backends\LDAP::updateResource($resource);
+ }
+
+ if (\config('app.with_imap') && $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,10 +30,8 @@
return;
}
- if ($folder->isLdapReady()) {
- $this->fail(new \Exception("Shared folder {$this->folderId} is already marked as ldap-ready."));
- return;
- }
+ $withLdap = \config('app.with_ldap');
+ $withImap = \config('app.with_imap');
// see if the domain is ready
$domain = $folder->domain();
@@ -48,14 +46,27 @@
return;
}
- if (!$domain->isLdapReady()) {
+ if ($withLdap && !$domain->isLdapReady()) {
$this->release(60);
return;
}
- \App\Backends\LDAP::createSharedFolder($folder);
+ if ($withLdap && !$folder->isLdapReady()) {
+ \App\Backends\LDAP::createSharedFolder($folder);
+
+ $folder->status |= \App\SharedFolder::STATUS_LDAP_READY;
+ $folder->save();
+ }
+
+ if ($withImap && !$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,23 @@
return;
}
- \App\Backends\LDAP::deleteSharedFolder($folder);
+ if (\config('app.with_ldap') && $folder->isLdapReady()) {
+ \App\Backends\LDAP::deleteSharedFolder($folder);
- $folder->status |= \App\SharedFolder::STATUS_DELETED;
-
- if ($folder->isLdapReady()) {
$folder->status ^= \App\SharedFolder::STATUS_LDAP_READY;
+ // Already save in case of exception below
+ $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 (\config('app.with_ldap') && $folder->isLdapReady()) {
+ \App\Backends\LDAP::updateSharedFolder($folder);
+ }
+
+ if (\config('app.with_imap') && $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,15 +42,12 @@
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;
- }
+ $withLdap = \config('app.with_ldap');
// see if the domain is ready
$domain = $user->domain();
@@ -65,7 +62,7 @@
return;
}
- if (!$domain->isLdapReady()) {
+ if ($withLdap && !$domain->isLdapReady()) {
$this->release(60);
return;
}
@@ -78,9 +75,24 @@
}
}
- \App\Backends\LDAP::createUser($user);
+ if ($withLdap && !$user->isLdapReady()) {
+ \App\Backends\LDAP::createUser($user);
+
+ $user->status |= \App\User::STATUS_LDAP_READY;
+ $user->save();
+ }
+
+ if (!$user->isImapReady()) {
+ if (\config('app.with_imap')) {
+ 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,28 @@
return;
}
- \App\Backends\LDAP::deleteUser($user);
+ if (\config('app.with_ldap') && $user->isLdapReady()) {
+ \App\Backends\LDAP::deleteUser($user);
- $user->status |= \App\User::STATUS_DELETED;
-
- if ($user->isLdapReady()) {
$user->status ^= \App\User::STATUS_LDAP_READY;
+ $user->save();
}
if ($user->isImapReady()) {
+ if (\config('app.with_imap')) {
+ 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 (\config('app.with_ldap') && $user->isLdapReady()) {
+ \App\Backends\LDAP::updateUser($user);
}
- \App\Backends\LDAP::updateUser($user);
+ if (\config('app.with_imap') && $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/GroupSettingObserver.php b/src/app/Observers/GroupSettingObserver.php
--- a/src/app/Observers/GroupSettingObserver.php
+++ b/src/app/Observers/GroupSettingObserver.php
@@ -2,7 +2,6 @@
namespace App\Observers;
-use App\Backends\LDAP;
use App\GroupSetting;
class GroupSettingObserver
@@ -16,9 +15,7 @@
*/
public function created(GroupSetting $groupSetting)
{
- if (in_array($groupSetting->key, LDAP::GROUP_SETTINGS)) {
- \App\Jobs\Group\UpdateJob::dispatch($groupSetting->group_id);
- }
+ $this->dispatchUpdateJob($groupSetting);
}
/**
@@ -30,9 +27,7 @@
*/
public function updated(GroupSetting $groupSetting)
{
- if (in_array($groupSetting->key, LDAP::GROUP_SETTINGS)) {
- \App\Jobs\Group\UpdateJob::dispatch($groupSetting->group_id);
- }
+ $this->dispatchUpdateJob($groupSetting);
}
/**
@@ -44,7 +39,20 @@
*/
public function deleted(GroupSetting $groupSetting)
{
- if (in_array($groupSetting->key, LDAP::GROUP_SETTINGS)) {
+ $this->dispatchUpdateJob($groupSetting);
+ }
+
+ /**
+ * Dispatch group update job (if needed).
+ *
+ * @param \App\GroupSetting $groupSetting Settings object
+ */
+ private function dispatchUpdateJob(GroupSetting $groupSetting): void
+ {
+ if (
+ (\config('app.with_ldap') && in_array($groupSetting->key, \App\Backends\LDAP::GROUP_SETTINGS))
+ || (\config('app.with_imap') && in_array($groupSetting->key, \App\Backends\IMAP::GROUP_SETTINGS))
+ ) {
\App\Jobs\Group\UpdateJob::dispatch($groupSetting->group_id);
}
}
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
@@ -2,7 +2,6 @@
namespace App\Observers;
-use App\Backends\LDAP;
use App\ResourceSetting;
class ResourceSettingObserver
@@ -16,9 +15,7 @@
*/
public function created(ResourceSetting $resourceSetting)
{
- if (in_array($resourceSetting->key, LDAP::RESOURCE_SETTINGS)) {
- \App\Jobs\Resource\UpdateJob::dispatch($resourceSetting->resource_id);
- }
+ $this->dispatchUpdateJob($resourceSetting);
}
/**
@@ -30,9 +27,7 @@
*/
public function updated(ResourceSetting $resourceSetting)
{
- if (in_array($resourceSetting->key, LDAP::RESOURCE_SETTINGS)) {
- \App\Jobs\Resource\UpdateJob::dispatch($resourceSetting->resource_id);
- }
+ $this->dispatchUpdateJob($resourceSetting);
}
/**
@@ -44,8 +39,22 @@
*/
public function deleted(ResourceSetting $resourceSetting)
{
- if (in_array($resourceSetting->key, LDAP::RESOURCE_SETTINGS)) {
- \App\Jobs\Resource\UpdateJob::dispatch($resourceSetting->resource_id);
+ $this->dispatchUpdateJob($resourceSetting);
+ }
+
+ /**
+ * Dispatch resource update job (if needed)
+ *
+ * @param \App\ResourceSetting $resourceSetting Settings object
+ */
+ private function dispatchUpdateJob(ResourceSetting $resourceSetting): void
+ {
+ if (
+ (\config('app.with_ldap') && in_array($resourceSetting->key, \App\Backends\LDAP::RESOURCE_SETTINGS))
+ || (\config('app.with_imap') && in_array($resourceSetting->key, \App\Backends\IMAP::RESOURCE_SETTINGS))
+ ) {
+ $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
@@ -2,7 +2,6 @@
namespace App\Observers;
-use App\Backends\LDAP;
use App\SharedFolderSetting;
class SharedFolderSettingObserver
@@ -16,9 +15,7 @@
*/
public function created(SharedFolderSetting $folderSetting)
{
- if (in_array($folderSetting->key, LDAP::SHARED_FOLDER_SETTINGS)) {
- \App\Jobs\SharedFolder\UpdateJob::dispatch($folderSetting->shared_folder_id);
- }
+ $this->dispatchUpdateJob($folderSetting);
}
/**
@@ -30,9 +27,7 @@
*/
public function updated(SharedFolderSetting $folderSetting)
{
- if (in_array($folderSetting->key, LDAP::SHARED_FOLDER_SETTINGS)) {
- \App\Jobs\SharedFolder\UpdateJob::dispatch($folderSetting->shared_folder_id);
- }
+ $this->dispatchUpdateJob($folderSetting);
}
/**
@@ -44,8 +39,22 @@
*/
public function deleted(SharedFolderSetting $folderSetting)
{
- if (in_array($folderSetting->key, LDAP::SHARED_FOLDER_SETTINGS)) {
- \App\Jobs\SharedFolder\UpdateJob::dispatch($folderSetting->shared_folder_id);
+ $this->dispatchUpdateJob($folderSetting);
+ }
+
+ /**
+ * Dispatch shared folder update job (if needed).
+ *
+ * @param \App\SharedFolderSetting $folderSetting Settings object
+ */
+ private function dispatchUpdateJob(SharedFolderSetting $folderSetting): void
+ {
+ if (
+ (\config('app.with_ldap') && in_array($folderSetting->key, \App\Backends\LDAP::SHARED_FOLDER_SETTINGS))
+ || (\config('app.with_imap') && in_array($folderSetting->key, \App\Backends\IMAP::SHARED_FOLDER_SETTINGS))
+ ) {
+ $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/Observers/UserSettingObserver.php b/src/app/Observers/UserSettingObserver.php
--- a/src/app/Observers/UserSettingObserver.php
+++ b/src/app/Observers/UserSettingObserver.php
@@ -2,7 +2,6 @@
namespace App\Observers;
-use App\Backends\LDAP;
use App\UserSetting;
class UserSettingObserver
@@ -16,9 +15,7 @@
*/
public function created(UserSetting $userSetting)
{
- if (in_array($userSetting->key, LDAP::USER_SETTINGS)) {
- \App\Jobs\User\UpdateJob::dispatch($userSetting->user_id);
- }
+ $this->dispatchUpdateJob($userSetting);
}
/**
@@ -30,9 +27,7 @@
*/
public function updated(UserSetting $userSetting)
{
- if (in_array($userSetting->key, LDAP::USER_SETTINGS)) {
- \App\Jobs\User\UpdateJob::dispatch($userSetting->user_id);
- }
+ $this->dispatchUpdateJob($userSetting);
}
/**
@@ -44,7 +39,20 @@
*/
public function deleted(UserSetting $userSetting)
{
- if (in_array($userSetting->key, LDAP::USER_SETTINGS)) {
+ $this->dispatchUpdateJob($userSetting);
+ }
+
+ /**
+ * Dispatch the user update job (if needed).
+ *
+ * @param \App\UserSetting $userSetting Settings object
+ */
+ private function dispatchUpdateJob(UserSetting $userSetting): void
+ {
+ if (
+ (\config('app.with_ldap') && in_array($userSetting->key, \App\Backends\LDAP::USER_SETTINGS))
+ || (\config('app.with_imap') && in_array($userSetting->key, \App\Backends\IMAP::USER_SETTINGS))
+ ) {
\App\Jobs\User\UpdateJob::dispatch($userSetting->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/config/app.php b/src/config/app.php
--- a/src/config/app.php
+++ b/src/config/app.php
@@ -71,6 +71,8 @@
'currency' => \strtoupper(env('APP_CURRENCY', 'CHF')),
+ 'backends' => env('BACKENDS', 'imap,ldap'),
+
/*
|--------------------------------------------------------------------------
| Application Domain
@@ -253,6 +255,10 @@
'methods_recurring' => env('PAYMENT_METHODS_RECURRING', 'creditcard'),
],
+
+ 'with_ldap' => (bool) env('APP_LDAP', true),
+ 'with_imap' => (bool) env('APP_IMAP', false),
+
'with_admin' => (bool) env('APP_WITH_ADMIN', false),
'with_files' => (bool) env('APP_WITH_FILES', false),
'with_reseller' => (bool) env('APP_WITH_RESELLER', false),
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,226 @@
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);
+ $result = IMAP::verifyAccount($user->email);
+ $this->assertFalse($result);
+ }
+
+ /**
+ * 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)
*
@@ -19,8 +235,8 @@
$this->assertTrue($result);
// non-existing user
- $this->expectException(\Exception::class);
- IMAP::verifyAccount('non-existing@domain.tld');
+ $result = IMAP::verifyAccount('non-existing@domain.tld');
+ $this->assertFalse($result);
}
/**
@@ -38,4 +254,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
@@ -323,7 +323,11 @@
$this->assertFalse($json['isReady']);
$this->assertFalse($json['isDeleted']);
$this->assertTrue($json['isActive']);
- $this->assertCount(7, $json['process']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(7, $json['process']);
+ } else {
+ $this->assertCount(6, $json['process']);
+ }
$this->assertSame('resource-new', $json['process'][0]['label']);
$this->assertSame(true, $json['process'][0]['state']);
$this->assertSame('resource-ldap-ready', $json['process'][1]['label']);
@@ -339,25 +343,35 @@
$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->assertCount(7, $json['process']);
+ $this->assertFalse($json['isReady']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(7, $json['process']);
+ } else {
+ $this->assertCount(6, $json['process']);
+ }
$this->assertSame('resource-ldap-ready', $json['process'][1]['label']);
- $this->assertSame(true, $json['process'][1]['state']);
- $this->assertSame('resource-imap-ready', $json['process'][2]['label']);
+ $this->assertSame(false, $json['process'][1]['state']);
+ if (\config('app.with_imap')) {
+ $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 +380,20 @@
$json = $response->json();
- $this->assertTrue($json['isLdapReady']);
- $this->assertTrue($json['isReady']);
- $this->assertCount(7, $json['process']);
+ $this->assertFalse($json['isLdapReady']);
+ $this->assertFalse($json['isReady']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(7, $json['process']);
+ } else {
+ $this->assertCount(6, $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);
}
/**
@@ -392,7 +413,11 @@
$result = ResourcesController::statusInfo($resource);
$this->assertFalse($result['isReady']);
- $this->assertCount(7, $result['process']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(7, $result['process']);
+ } else {
+ $this->assertCount(6, $result['process']);
+ }
$this->assertSame('resource-new', $result['process'][0]['label']);
$this->assertSame(true, $result['process'][0]['state']);
$this->assertSame('resource-ldap-ready', $result['process'][1]['label']);
@@ -412,7 +437,11 @@
$result = ResourcesController::statusInfo($resource);
$this->assertTrue($result['isReady']);
- $this->assertCount(7, $result['process']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(7, $result['process']);
+ } else {
+ $this->assertCount(6, $result['process']);
+ }
$this->assertSame('resource-new', $result['process'][0]['label']);
$this->assertSame(true, $result['process'][0]['state']);
$this->assertSame('resource-ldap-ready', $result['process'][1]['label']);
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
@@ -330,7 +330,11 @@
$this->assertFalse($json['isReady']);
$this->assertFalse($json['isDeleted']);
$this->assertTrue($json['isActive']);
- $this->assertCount(7, $json['process']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(7, $json['process']);
+ } else {
+ $this->assertCount(6, $json['process']);
+ }
$this->assertSame('shared-folder-new', $json['process'][0]['label']);
$this->assertSame(true, $json['process'][0]['state']);
$this->assertSame('shared-folder-ldap-ready', $json['process'][1]['label']);
@@ -346,25 +350,35 @@
$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->assertCount(7, $json['process']);
+ $this->assertFalse($json['isReady']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(7, $json['process']);
+ } else {
+ $this->assertCount(6, $json['process']);
+ }
$this->assertSame('shared-folder-ldap-ready', $json['process'][1]['label']);
- $this->assertSame(true, $json['process'][1]['state']);
- $this->assertSame('shared-folder-imap-ready', $json['process'][2]['label']);
- $this->assertSame(true, $json['process'][2]['state']);
+ $this->assertSame(false, $json['process'][1]['state']);
+ if (\config('app.with_imap')) {
+ $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 +387,20 @@
$json = $response->json();
- $this->assertTrue($json['isLdapReady']);
- $this->assertTrue($json['isReady']);
- $this->assertCount(7, $json['process']);
+ $this->assertFalse($json['isLdapReady']);
+ $this->assertFalse($json['isReady']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(7, $json['process']);
+ } else {
+ $this->assertCount(6, $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);
}
/**
@@ -399,7 +420,11 @@
$result = SharedFoldersController::statusInfo($folder);
$this->assertFalse($result['isReady']);
- $this->assertCount(7, $result['process']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(7, $result['process']);
+ } else {
+ $this->assertCount(6, $result['process']);
+ }
$this->assertSame('shared-folder-new', $result['process'][0]['label']);
$this->assertSame(true, $result['process'][0]['state']);
$this->assertSame('shared-folder-ldap-ready', $result['process'][1]['label']);
@@ -419,7 +444,11 @@
$result = SharedFoldersController::statusInfo($folder);
$this->assertTrue($result['isReady']);
- $this->assertCount(7, $result['process']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(7, $result['process']);
+ } else {
+ $this->assertCount(6, $result['process']);
+ }
$this->assertSame('shared-folder-new', $result['process'][0]['label']);
$this->assertSame(true, $result['process'][0]['state']);
$this->assertSame('shared-folder-ldap-ready', $result['process'][1]['label']);
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
@@ -484,9 +484,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']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(6, $json['process']);
+ $this->assertSame('user-imap-ready', $json['process'][2]['label']);
+ $this->assertSame(false, $json['process'][2]['state']);
+ } else {
+ $this->assertCount(7, $json['process']);
+ }
$this->assertTrue(empty($json['status']));
$this->assertTrue(empty($json['message']));
@@ -495,29 +499,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 +508,17 @@
$this->assertFalse($json['isImapReady']);
$this->assertFalse($json['isReady']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(7, $json['process']);
+ $this->assertSame('user-imap-ready', $json['process'][2]['label']);
+ $this->assertSame(false, $json['process'][2]['state']);
+ } else {
+ $this->assertCount(6, $json['process']);
+ }
$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);
}
/**
@@ -551,13 +540,19 @@
$this->assertFalse($result['isReady']);
$this->assertSame([], $result['skus']);
- $this->assertCount(3, $result['process']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(3, $result['process']);
+ } else {
+ $this->assertCount(2, $result['process']);
+ }
$this->assertSame('user-new', $result['process'][0]['label']);
$this->assertSame(true, $result['process'][0]['state']);
$this->assertSame('user-ldap-ready', $result['process'][1]['label']);
$this->assertSame(false, $result['process'][1]['state']);
- $this->assertSame('user-imap-ready', $result['process'][2]['label']);
- $this->assertSame(false, $result['process'][2]['state']);
+ if (\config('app.with_imap')) {
+ $this->assertSame('user-imap-ready', $result['process'][2]['label']);
+ $this->assertSame(false, $result['process'][2]['state']);
+ }
$this->assertSame('running', $result['processState']);
$this->assertTrue($result['enableRooms']);
$this->assertFalse($result['enableBeta']);
@@ -575,13 +570,19 @@
$result = UsersController::statusInfo($user);
$this->assertTrue($result['isReady']);
- $this->assertCount(3, $result['process']);
+ if (\config('app.with_imap')) {
+ $this->assertCount(3, $result['process']);
+ } else {
+ $this->assertCount(2, $result['process']);
+ }
$this->assertSame('user-new', $result['process'][0]['label']);
$this->assertSame(true, $result['process'][0]['state']);
$this->assertSame('user-ldap-ready', $result['process'][1]['label']);
$this->assertSame(true, $result['process'][1]['state']);
- $this->assertSame('user-imap-ready', $result['process'][2]['label']);
- $this->assertSame(true, $result['process'][2]['state']);
+ if (\config('app.with_imap')) {
+ $this->assertSame('user-imap-ready', $result['process'][2]['label']);
+ $this->assertSame(true, $result['process'][2]['state']);
+ }
$this->assertSame('done', $result['processState']);
$domain->status |= Domain::STATUS_VERIFIED;
@@ -592,21 +593,38 @@
$this->assertFalse($result['isReady']);
$this->assertSame([], $result['skus']);
- $this->assertCount(7, $result['process']);
- $this->assertSame('user-new', $result['process'][0]['label']);
- $this->assertSame(true, $result['process'][0]['state']);
- $this->assertSame('user-ldap-ready', $result['process'][1]['label']);
- $this->assertSame(true, $result['process'][1]['state']);
- $this->assertSame('user-imap-ready', $result['process'][2]['label']);
- $this->assertSame(true, $result['process'][2]['state']);
- $this->assertSame('domain-new', $result['process'][3]['label']);
- $this->assertSame(true, $result['process'][3]['state']);
- $this->assertSame('domain-ldap-ready', $result['process'][4]['label']);
- $this->assertSame(false, $result['process'][4]['state']);
- $this->assertSame('domain-verified', $result['process'][5]['label']);
- $this->assertSame(true, $result['process'][5]['state']);
- $this->assertSame('domain-confirmed', $result['process'][6]['label']);
- $this->assertSame(false, $result['process'][6]['state']);
+
+ if (\config('app.with_imap')) {
+ $this->assertCount(7, $result['process']);
+ $this->assertSame('user-new', $result['process'][0]['label']);
+ $this->assertSame(true, $result['process'][0]['state']);
+ $this->assertSame('user-ldap-ready', $result['process'][1]['label']);
+ $this->assertSame(true, $result['process'][1]['state']);
+ $this->assertSame('user-imap-ready', $result['process'][2]['label']);
+ $this->assertSame(true, $result['process'][2]['state']);
+ $this->assertSame('domain-new', $result['process'][3]['label']);
+ $this->assertSame(true, $result['process'][3]['state']);
+ $this->assertSame('domain-ldap-ready', $result['process'][4]['label']);
+ $this->assertSame(false, $result['process'][4]['state']);
+ $this->assertSame('domain-verified', $result['process'][5]['label']);
+ $this->assertSame(true, $result['process'][5]['state']);
+ $this->assertSame('domain-confirmed', $result['process'][6]['label']);
+ $this->assertSame(false, $result['process'][6]['state']);
+ } else {
+ $this->assertCount(6, $result['process']);
+ $this->assertSame('user-new', $result['process'][0]['label']);
+ $this->assertSame(true, $result['process'][0]['state']);
+ $this->assertSame('user-ldap-ready', $result['process'][1]['label']);
+ $this->assertSame(true, $result['process'][1]['state']);
+ $this->assertSame('domain-new', $result['process'][2]['label']);
+ $this->assertSame(true, $result['process'][2]['state']);
+ $this->assertSame('domain-ldap-ready', $result['process'][3]['label']);
+ $this->assertSame(false, $result['process'][3]['state']);
+ $this->assertSame('domain-verified', $result['process'][4]['label']);
+ $this->assertSame(true, $result['process'][4]['state']);
+ $this->assertSame('domain-confirmed', $result['process'][5]['label']);
+ $this->assertSame(false, $result['process'][5]['state']);
+ }
// Test 'skus' property
$user->assignSku(Sku::withEnvTenantContext()->where('title', 'beta')->first());
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,12 @@
$resource->refresh();
$this->assertTrue($resource->isLdapReady());
+ if (\config('app.with_imap')) {
+ $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,18 +53,21 @@
$folder->refresh();
$this->assertTrue($folder->isLdapReady());
+ if (\config('app.with_imap')) {
+ $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();
$folder->refresh();
$this->assertFalse($folder->isLdapReady());
- $this->assertFalse($folder->isImapReady());
+ if (\config('app.with_imap')) {
+ $this->assertFalse($folder->isImapReady());
+ }
$this->assertTrue($folder->isDeleted());
// Test deleting already deleted folder
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,30 @@
$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());
+ if (\config('app.with_imap')) {
+ $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,100 @@
+<?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());
+
+ if (\config('app.with_imap')) {
+ 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
@@ -322,7 +322,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);
@@ -339,28 +339,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");
@@ -974,7 +952,7 @@
}
/**
- * Test User::hasSku() method
+ * Test User::hasSku() and countEntitlementsBySku() methods
*/
public function testHasSku(): void
{
@@ -984,6 +962,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'));
}
/**
@@ -1166,12 +1149,6 @@
return $userA->id === TestCase::getObjectProperty($job, 'userId');
}
);
- Queue::assertPushedWithChain(
- \App\Jobs\User\CreateJob::class,
- [
- \App\Jobs\User\VerifyJob::class,
- ]
- );
}
/**

File Metadata

Mime Type
text/plain
Expires
Mon, Apr 6, 9:08 AM (18 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18836330
Default Alt Text
D3785.1775466532.diff (126 KB)

Event Timeline