Page MenuHomePhorge

D3184.1775429212.diff
No OneTemporary

Authored By
Unknown
Size
56 KB
Referenced Files
None
Subscribers
None

D3184.1775429212.diff

diff --git a/src/app/Console/Command.php b/src/app/Console/Command.php
--- a/src/app/Console/Command.php
+++ b/src/app/Console/Command.php
@@ -29,6 +29,31 @@
*/
protected $dangerous = false;
+
+ /**
+ * Shortcut to creating a progress bar of a particular format with a particular message.
+ *
+ * @param int $count Number of progress steps
+ * @param string $message The description
+ *
+ * @return \Symfony\Component\Console\Helper\ProgressBar
+ */
+ protected function createProgressBar($count, $message = null)
+ {
+ $bar = $this->output->createProgressBar($count);
+ $bar->setFormat(
+ '%current:7s%/%max:7s% [%bar%] %percent:3s%% %elapsed:7s%/%estimated:-7s% %message% '
+ );
+
+ if ($message) {
+ $bar->setMessage("{$message}...");
+ }
+
+ $bar->start();
+
+ return $bar;
+ }
+
/**
* Find the domain.
*
diff --git a/src/app/Console/Commands/Data/Import/IP4NetsCommand.php b/src/app/Console/Commands/Data/Import/IP4NetsCommand.php
--- a/src/app/Console/Commands/Data/Import/IP4NetsCommand.php
+++ b/src/app/Console/Commands/Data/Import/IP4NetsCommand.php
@@ -57,11 +57,7 @@
continue;
}
- $bar = \App\Utils::createProgressBar(
- $this->output,
- $numLines,
- "Importing IPv4 Networks from {$file}"
- );
+ $bar = $this->createProgressBar($numLines, "Importing IPv4 Networks from {$file}");
$fp = fopen($file, 'r');
diff --git a/src/app/Console/Commands/Data/Import/IP6NetsCommand.php b/src/app/Console/Commands/Data/Import/IP6NetsCommand.php
--- a/src/app/Console/Commands/Data/Import/IP6NetsCommand.php
+++ b/src/app/Console/Commands/Data/Import/IP6NetsCommand.php
@@ -57,11 +57,7 @@
continue;
}
- $bar = \App\Utils::createProgressBar(
- $this->output,
- $numLines,
- "Importing IPv6 Networks from {$file}"
- );
+ $bar = $this->createProgressBar($numLines, "Importing IPv6 Networks from {$file}");
$fp = fopen($file, 'r');
diff --git a/src/app/Console/Commands/Data/Import/LdifCommand.php b/src/app/Console/Commands/Data/Import/LdifCommand.php
new file mode 100644
--- /dev/null
+++ b/src/app/Console/Commands/Data/Import/LdifCommand.php
@@ -0,0 +1,1000 @@
+<?php
+
+namespace App\Console\Commands\Data\Import;
+
+use App\Console\Command;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Schema;
+
+class LdifCommand extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'data:import:ldif {file} {owner} {--force}';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'Migrate data from an LDIF file';
+
+ /** @var array Aliases email addresses of the owner */
+ protected $aliases = [];
+
+ /** @var array List of imported domains */
+ protected $domains = [];
+
+ /** @var ?string LDAP DN of the account owner */
+ protected $ownerDN;
+
+ /** @var array Packages information */
+ protected $packages = [];
+
+ /** @var ?\App\Wallet A wallet of the account owner */
+ protected $wallet;
+
+ /** @var string Temp table name */
+ protected static $table = 'tmp_ldif_import';
+
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ ini_set("memory_limit", "2048M");
+
+ // (Re-)create temporary table
+ Schema::dropIfExists(self::$table);
+ Schema::create(
+ self::$table,
+ function (Blueprint $table) {
+ $table->bigIncrements('id');
+ $table->text('dn')->index();
+ $table->string('type')->nullable()->index();
+ $table->text('data')->nullable();
+ $table->text('error')->nullable();
+ $table->text('warning')->nullable();
+ }
+ );
+
+ // Import data from the file to the temp table
+ $this->loadFromFile();
+
+ // Check for errors in the data, print them and abort (if not using --force)
+ if ($this->printErrors()) {
+ return 1;
+ }
+
+ // Prepare packages/skus information
+ $this->preparePackagesAndSkus();
+
+ // Import the account owner first
+ $this->importOwner();
+
+ // Import domains first
+ $this->importDomains();
+
+ // Import other objects
+ $this->importUsers();
+ $this->importSharedFolders();
+ $this->importResources();
+ $this->importGroups();
+
+ // Print warnings collected in the whole process
+ $this->printWarnings();
+
+ // Finally, drop the temp table
+ Schema::dropIfExists(self::$table);
+ }
+
+ /**
+ * Check if a domain exists
+ */
+ protected function domainExists($domain): bool
+ {
+ return in_array($domain, $this->domains);
+ }
+
+ /**
+ * Load data from the LDIF file into the temp table
+ */
+ protected function loadFromFile(): void
+ {
+ $file = $this->argument('file');
+
+ $numLines = \App\Utils::countLines($file);
+
+ $bar = $this->createProgressBar($numLines, "Parsing input file");
+
+ $fh = fopen($file, 'r');
+
+ $inserts = [];
+ $entry = [];
+ $lastAttr = null;
+
+ $insertFunc = function ($limit = 0) use (&$entry, &$inserts) {
+ if (!empty($entry)) {
+ if ($entry = $this->parseLDAPEntry($entry)) {
+ $inserts[] = $entry;
+ }
+ $entry = [];
+ }
+
+ if (count($inserts) > $limit) {
+ DB::table(self::$table)->insert($inserts);
+ $inserts = [];
+ }
+ };
+
+ while (!feof($fh)) {
+ $line = rtrim(fgets($fh));
+
+ $bar->advance();
+
+ if (trim($line) === '' || $line[0] === '#') {
+ continue;
+ }
+
+ if (substr($line, 0, 3) == 'dn:') {
+ $insertFunc(20);
+ $entry['dn'] = strtolower(substr($line, 4));
+ $lastAttr = 'dn';
+ } elseif (substr($line, 0, 1) == ' ') {
+ if (is_array($entry[$lastAttr])) {
+ $elemNum = count($entry[$lastAttr]) - 1;
+ $entry[$lastAttr][$elemNum] .= ltrim($line);
+ } else {
+ $entry[$lastAttr] .= ltrim($line);
+ }
+ } else {
+ list ($attr, $remainder) = explode(':', $line, 2);
+ $attr = strtolower($attr);
+
+ if ($remainder[0] === ':') {
+ $remainder = base64_decode(substr($remainder, 2));
+ } else {
+ $remainder = ltrim($remainder);
+ }
+
+ if (array_key_exists($attr, $entry)) {
+ if (!is_array($entry[$attr])) {
+ $entry[$attr] = [$entry[$attr]];
+ }
+
+ $entry[$attr][] = $remainder;
+ } else {
+ $entry[$attr] = $remainder;
+ }
+
+ $lastAttr = $attr;
+ }
+ }
+
+ $insertFunc();
+
+ $bar->finish();
+
+ $this->info("DONE");
+ }
+
+ /**
+ * Import domains from the temp table
+ */
+ protected function importDomains(): void
+ {
+ $domains = DB::table(self::$table)->where('type', 'domain')->whereNull('error')->get();
+
+ $bar = $this->createProgressBar(count($domains), "Importing domains");
+
+ foreach ($domains as $_domain) {
+ $bar->advance();
+
+ $data = json_decode($_domain->data);
+
+ $domain = \App\Domain::withTrashed()->where('namespace', $data->namespace)->first();
+
+ if ($domain) {
+ $this->setImportWarning($_domain->id, "Domain already exists");
+ continue;
+ }
+
+ $domain = \App\Domain::create([
+ 'namespace' => $data->namespace,
+ 'type' => \App\Domain::TYPE_EXTERNAL,
+ ]);
+
+ // Entitlements
+ $domain->assignPackageAndWallet($this->packages['domain'], $this->wallet);
+
+ $this->domains[] = $domain->namespace;
+
+ if (!empty($data->aliases)) {
+ foreach ($data->aliases as $alias) {
+ $alias = strtolower($alias);
+ $domain = \App\Domain::withTrashed()->where('namespace', $alias)->first();
+
+ if ($domain) {
+ $this->setImportWarning($_domain->id, "Domain already exists");
+ continue;
+ }
+
+ $domain = \App\Domain::create([
+ 'namespace' => $alias,
+ 'type' => \App\Domain::TYPE_EXTERNAL,
+ ]);
+
+ // Entitlements
+ $domain->assignPackageAndWallet($this->packages['domain'], $this->wallet);
+
+ $this->domains[] = $domain->namespace;
+ }
+ }
+ }
+
+ $bar->finish();
+
+ $this->info("DONE");
+ }
+
+ /**
+ * Import groups from the temp table
+ */
+ protected function importGroups(): void
+ {
+ $groups = DB::table(self::$table)->where('type', 'group')->whereNull('error')->get();
+
+ $bar = $this->createProgressBar(count($groups), "Importing groups");
+
+ foreach ($groups as $_group) {
+ $bar->advance();
+
+ $data = json_decode($_group->data);
+
+ // Collect group member email addresses
+ $members = $this->resolveUserDNs($data->members);
+
+ if (empty($members)) {
+ $this->setImportWarning($_group->id, "Members resolve to an empty array");
+ continue;
+ }
+
+ $group = \App\Group::withTrashed()->where('email', $data->email)->first();
+
+ if ($group) {
+ $this->setImportWarning($_group->id, "Group already exists");
+ continue;
+ }
+
+ // Make sure the domain exists
+ if (!$this->domainExists($data->domain)) {
+ $this->setImportWarning($_group->id, "Domain not found");
+ continue;
+ }
+
+ $group = \App\Group::create([
+ 'name' => $data->name,
+ 'email' => $data->email,
+ 'members' => $members,
+ ]);
+
+ $group->assignToWallet($this->wallet);
+
+ // Sender policy
+ if (!empty($data->sender_policy)) {
+ $group->setSetting('sender_policy', json_encode($data->sender_policy));
+ }
+ }
+
+ $bar->finish();
+
+ $this->info("DONE");
+ }
+
+ /**
+ * Import resources from the temp table
+ */
+ protected function importResources(): void
+ {
+ $resources = DB::table(self::$table)->where('type', 'resource')->whereNull('error')->get();
+
+ $bar = $this->createProgressBar(count($resources), "Importing resources");
+
+ foreach ($resources as $_resource) {
+ $bar->advance();
+
+ $data = json_decode($_resource->data);
+
+ $resource = \App\Resource::withTrashed()
+ ->where('name', $data->name)
+ ->where('email', 'like', '%@' . $data->domain)
+ ->first();
+
+ if ($resource) {
+ $this->setImportWarning($_resource->id, "Resource already exists");
+ continue;
+ }
+
+ // Resource invitation policy
+ if (!empty($data->invitation_policy) && $data->invitation_policy == 'manual') {
+ $members = empty($data->owner) ? [] : $this->resolveUserDNs([$data->owner]);
+
+ if (empty($members)) {
+ $this->setImportWarning($_resource->id, "Failed to resolve the resource owner");
+ $data->invitation_policy = null;
+ } else {
+ $data->invitation_policy = 'manual:' . $members[0];
+ }
+ }
+
+ // Make sure the domain exists
+ if (!$this->domainExists($data->domain)) {
+ $this->setImportWarning($_resource->id, "Domain not found");
+ continue;
+ }
+
+ $resource = new \App\Resource();
+ $resource->name = $data->name;
+ $resource->domain = $data->domain;
+ $resource->save();
+
+ $resource->assignToWallet($this->wallet);
+
+ // Invitation policy
+ if (!empty($data->invitation_policy)) {
+ $resource->setSetting('invitation_policy', $data->invitation_policy);
+ }
+
+ // Target folder
+ if (!empty($data->folder)) {
+ $resource->setSetting('folder', $data->folder);
+ }
+ }
+
+ $bar->finish();
+
+ $this->info("DONE");
+ }
+
+ /**
+ * Import shared folders from the temp table
+ */
+ protected function importSharedFolders(): void
+ {
+ $folders = DB::table(self::$table)->where('type', 'sharedFolder')->whereNull('error')->get();
+
+ $bar = $this->createProgressBar(count($folders), "Importing shared folders");
+
+ foreach ($folders as $_folder) {
+ $bar->advance();
+
+ $data = json_decode($_folder->data);
+
+ $folder = \App\SharedFolder::withTrashed()
+ ->where('name', $data->name)
+ ->where('email', 'like', '%@' . $data->domain)
+ ->first();
+
+ if ($folder) {
+ $this->setImportWarning($_folder->id, "Folder already exists");
+ continue;
+ }
+
+ // Make sure the domain exists
+ if (!$this->domainExists($data->domain)) {
+ $this->setImportWarning($_folder->id, "Domain not found");
+ continue;
+ }
+
+ $folder = new \App\SharedFolder();
+ $folder->name = $data->name;
+ $folder->type = $data->type ?? 'mail';
+ $folder->domain = $data->domain;
+ $folder->save();
+
+ $folder->assignToWallet($this->wallet);
+
+ // Invitation policy
+ if (!empty($data->acl)) {
+ $folder->setSetting('acl', json_encode($data->acl));
+ }
+
+ // Target folder
+ if (!empty($data->folder)) {
+ $folder->setSetting('folder', $data->folder);
+ }
+ }
+
+ $bar->finish();
+
+ $this->info("DONE");
+ }
+
+ /**
+ * Import users from the temp table
+ */
+ protected function importUsers(): void
+ {
+ $users = DB::table(self::$table)->where('type', 'user')->whereNull('error');
+
+ // Skip the (already imported) account owner
+ if ($this->ownerDN) {
+ $users->whereNotIn('dn', [$this->ownerDN]);
+ }
+
+ // Import aliases of the owner, we got from importOwner() call
+ if (!empty($this->aliases) && $this->wallet) {
+ $this->setUserAliases($this->wallet->owner, $this->aliases);
+ }
+
+ $bar = $this->createProgressBar($users->count(), "Importing users");
+
+ foreach ($users->cursor() as $_user) {
+ $bar->advance();
+
+ $this->importSingleUser($_user);
+ }
+
+ $bar->finish();
+
+ $this->info("DONE");
+ }
+
+ /**
+ * Import the account owner (or find it among the existing accounts)
+ */
+ protected function importOwner(): void
+ {
+ // The owner email not found in the import data, try existing users
+ $user = $this->getUser($this->argument('owner'));
+
+ if (!$user && $this->ownerDN) {
+ // The owner email found in the import data
+ $bar = $this->createProgressBar(1, "Importing account owner");
+
+ $user = DB::table(self::$table)->where('dn', $this->ownerDN)->first();
+ $user = $this->importSingleUser($user);
+
+ // TODO: We should probably make sure the user's domain is to be imported too
+ // and/or create it automatically.
+
+ $bar->advance();
+ $bar->finish();
+
+ $this->info("DONE");
+ }
+
+ if (!$user) {
+ $this->error("Unable to find the specified account owner");
+ exit(1);
+ }
+
+ $this->wallet = $user->wallets->first();
+ }
+
+ /**
+ * A helper that imports a single user record
+ */
+ protected function importSingleUser($ldap_user)
+ {
+ $data = json_decode($ldap_user->data);
+
+ $user = \App\User::withTrashed()->where('email', $data->email)->first();
+
+ if ($user) {
+ $this->setImportWarning($ldap_user->id, "User already exists");
+ return;
+ }
+
+ // Make sure the domain exists
+ if ($this->wallet && !$this->domainExists($data->domain)) {
+ $this->setImportWarning($ldap_user->id, "Domain not found");
+ return;
+ }
+
+ $user = \App\User::create(['email' => $data->email]);
+
+ // Entitlements
+ $user->assignPackageAndWallet($this->packages['user'], $this->wallet ?: $user->wallets()->first());
+
+ if (!empty($data->quota)) {
+ $quota = ceil($data->quota / 1024 / 1024) - $this->packages['quota'];
+ if ($quota > 0) {
+ $user->assignSku($this->packages['storage'], $quota);
+ }
+ }
+
+ // User settings
+ if (!empty($data->settings)) {
+ $settings = [];
+ foreach ($data->settings as $key => $value) {
+ $settings[] = [
+ 'user_id' => $user->id,
+ 'key' => $key,
+ 'value' => $value,
+ ];
+ }
+
+ DB::table('user_settings')->insert($settings);
+ }
+
+ // Update password
+ if ($data->password != $user->password_ldap) {
+ \App\User::where('id', $user->id)->update(['password_ldap' => $data->password]);
+ }
+
+ // Import aliases
+ if (!empty($data->aliases)) {
+ if (!$this->wallet) {
+ // This is the account owner creation, at this point we likely do not have
+ // domain records yet, save the aliases to be inserted later (in importUsers())
+ $this->aliases = $data->aliases;
+ } else {
+ $this->setUserAliases($user, $data->aliases);
+ }
+ }
+
+ return $user;
+ }
+
+ /**
+ * Convert LDAP entry into an object supported by the migration tool
+ *
+ * @param array $entry LDAP entry attributes
+ *
+ * @return array Record data for inserting to the temp table
+ */
+ protected function parseLDAPEntry(array $entry): array
+ {
+ $type = null;
+ $data = null;
+ $error = null;
+
+ $ouTypeMap = [
+ 'Shared Folders' => 'sharedfolder',
+ 'Resources' => 'resource',
+ 'Groups' => 'group',
+ 'People' => 'user',
+ 'Domains' => 'domain',
+ ];
+
+ foreach ($ouTypeMap as $ou => $_type) {
+ if (stripos($entry['dn'], ",ou={$ou}")) {
+ $type = $_type;
+ break;
+ }
+ }
+
+ if (!$type) {
+ $error = "Unknown record type";
+ }
+
+ if (empty($error)) {
+ $method = 'parseLDAP' . ucfirst($type);
+ list($data, $error) = $this->{$method}($entry);
+
+ if (empty($data['domain']) && !empty($data['email'])) {
+ $data['domain'] = explode('@', $data['email'])[1];
+ }
+ }
+
+ return [
+ 'dn' => $entry['dn'],
+ 'type' => $type,
+ 'data' => json_encode($data),
+ 'error' => $error,
+ ];
+ }
+
+ /**
+ * Convert LDAP domain data into Kolab4 "format"
+ */
+ protected function parseLDAPDomain($entry)
+ {
+ $error = null;
+ $result = [];
+
+ if (empty($entry['associateddomain'])) {
+ $error = "Missing 'associatedDomain' attribute";
+ } elseif (!empty($entry['inetdomainstatus']) && $entry['inetdomainstatus'] == 'deleted') {
+ $error = "Domain deleted";
+ } else {
+ $result['namespace'] = strtolower($this->attrStringValue($entry, 'associateddomain'));
+
+ if (is_array($entry['associateddomain']) && count($entry['associateddomain']) > 1) {
+ $result['aliases'] = array_slice($entry['associateddomain'], 1);
+ }
+
+ // TODO: inetdomainstatus = suspended ???
+ }
+
+ return [$result, $error];
+ }
+
+ /**
+ * Convert LDAP group data into Kolab4 "format"
+ */
+ protected function parseLDAPGroup($entry)
+ {
+ $error = null;
+ $result = [];
+
+ if (empty($entry['cn'])) {
+ $error = "Missing 'cn' attribute";
+ } elseif (empty($entry['mail'])) {
+ $error = "Missing 'mail' attribute";
+ } elseif (empty($entry['uniquemember'])) {
+ $error = "Missing 'uniqueMember' attribute";
+ } else {
+ $result['name'] = $this->attrStringValue($entry, 'cn');
+ $result['email'] = strtolower($this->attrStringValue($entry, 'mail'));
+ $result['members'] = $this->attrArrayValue($entry, 'uniquemember');
+
+ if (!empty($entry['kolaballowsmtpsender'])) {
+ $policy = $this->attrArrayValue($entry, 'kolaballowsmtpsender');
+ $result['sender_policy'] = $this->parseSenderPolicy($policy);
+ }
+ }
+
+ return [$result, $error];
+ }
+
+ /**
+ * Convert LDAP resource data into Kolab4 "format"
+ */
+ protected function parseLDAPResource($entry)
+ {
+ $error = null;
+ $result = [];
+
+ if (empty($entry['cn'])) {
+ $error = "Missing 'cn' attribute";
+ } elseif (empty($entry['mail'])) {
+ $error = "Missing 'mail' attribute";
+ } else {
+ $result['name'] = $this->attrStringValue($entry, 'cn');
+ $result['email'] = strtolower($this->attrStringValue($entry, 'mail'));
+
+ if (!empty($entry['kolabtargetfolder'])) {
+ $result['folder'] = $this->attrStringValue($entry, 'kolabtargetfolder');
+ }
+
+ if (!empty($entry['owner'])) {
+ $result['owner'] = $this->attrStringValue($entry, 'owner');
+ }
+
+ if (!empty($entry['kolabinvitationpolicy'])) {
+ $policy = $this->attrArrayValue($entry, 'kolabinvitationpolicy');
+ $result['invitation_policy'] = $this->parseInvitationPolicy($policy);
+ }
+ }
+
+ return [$result, $error];
+ }
+
+ /**
+ * Convert LDAP shared folder data into Kolab4 "format"
+ */
+ protected function parseLDAPSharedFolder($entry)
+ {
+ $error = null;
+ $result = [];
+
+ if (empty($entry['cn'])) {
+ $error = "Missing 'cn' attribute";
+ } elseif (empty($entry['mail'])) {
+ $error = "Missing 'mail' attribute";
+ } else {
+ $result['name'] = $this->attrStringValue($entry, 'cn');
+ $result['email'] = strtolower($this->attrStringValue($entry, 'mail'));
+
+ if (!empty($entry['kolabfoldertype'])) {
+ $result['type'] = $this->attrStringValue($entry, 'kolabfoldertype');
+ }
+
+ if (!empty($entry['kolabtargetfolder'])) {
+ $result['folder'] = $this->attrStringValue($entry, 'kolabtargetfolder');
+ }
+
+ if (!empty($entry['acl'])) {
+ $result['acl'] = $this->parseACL($this->attrArrayValue($entry, 'acl'));
+ }
+ }
+
+ return [$result, $error];
+ }
+
+ /**
+ * Convert LDAP user data into Kolab4 "format"
+ */
+ protected function parseLDAPUser($entry)
+ {
+ $error = null;
+ $result = [];
+
+ $settingAttrs = [
+ 'givenname' => 'first_name',
+ 'sn' => 'last_name',
+ 'telephonenumber' => 'phone',
+ 'mailalternateaddress' => 'external_email',
+ 'mobile' => 'phone',
+ 'o' => 'organization',
+ // 'address' => 'billing_address'
+ ];
+
+ if (empty($entry['mail'])) {
+ $error = "Missing 'mail' attribute";
+ } else {
+ $result['email'] = strtolower($this->attrStringValue($entry, 'mail'));
+ $result['settings'] = [];
+ $result['aliases'] = [];
+
+ foreach ($settingAttrs as $attr => $setting) {
+ if (!empty($entry[$attr])) {
+ $result['settings'][$setting] = $this->attrStringValue($entry, $attr);
+ }
+ }
+
+ if (!empty($entry['alias'])) {
+ $result['aliases'] = $this->attrArrayValue($entry, 'alias');
+ }
+
+ if (!empty($entry['userpassword'])) {
+ $result['password'] = $this->attrStringValue($entry, 'userpassword');
+ }
+
+ if (!empty($entry['mailquota'])) {
+ $result['quota'] = $this->attrStringValue($entry, 'mailquota');
+ }
+
+ if ($result['email'] == $this->argument('owner')) {
+ $this->ownerDN = $entry['dn'];
+ }
+ }
+
+ return [$result, $error];
+ }
+
+ /**
+ * Print import errors
+ */
+ protected function printErrors(): bool
+ {
+ if ($this->option('force')) {
+ return false;
+ }
+
+ $errors = DB::table(self::$table)->whereNotNull('error')->orderBy('id')
+ ->get()
+ ->map(function ($record) {
+ $this->error("ERROR {$record->dn}: {$record->error}");
+ return $record->id;
+ })
+ ->all();
+
+ return !empty($errors);
+ }
+
+ /**
+ * Print import warnings (for records that do not have an error specified)
+ */
+ protected function printWarnings(): void
+ {
+ DB::table(self::$table)->whereNotNull('warning')->whereNull('error')->orderBy('id')
+ ->each(function ($record) {
+ $this->warn("WARNING {$record->dn}: {$record->warning}");
+ return $record->id;
+ });
+ }
+
+ /**
+ * Convert ldap attribute value to an array
+ */
+ protected static function attrArrayValue($entry, $attribute)
+ {
+ return is_array($entry[$attribute]) ? $entry[$attribute] : [$entry[$attribute]];
+ }
+
+ /**
+ * Convert ldap attribute to a string
+ */
+ protected static function attrStringValue($entry, $attribute)
+ {
+ return is_array($entry[$attribute]) ? $entry[$attribute][0] : $entry[$attribute];
+ }
+
+ /**
+ * Resolve a list of user DNs into email addresses. Makes sure
+ * the returned addresses exist in Kolab4 database.
+ */
+ protected function resolveUserDNs($user_dns): array
+ {
+ // Get email addresses from the import data
+ $users = DB::table(self::$table)->whereIn('dn', $user_dns)
+ ->where('type', 'user')
+ ->whereNull('error')
+ ->get()
+ ->map(function ($user) {
+ $mdata = json_decode($user->data);
+ return $mdata->email;
+ })
+ // Make sure to skip these with unknown domains
+ ->filter(function ($email) {
+ return $this->domainExists(explode('@', $email)[1]);
+ })
+ ->all();
+
+ // Get email addresses for existing Kolab4 users
+ if (!empty($users)) {
+ $users = \App\User::whereIn('email', $users)->get()->pluck('email')->all();
+ }
+
+ return $users;
+ }
+
+ /**
+ * Validate/convert acl to Kolab4 format
+ */
+ protected static function parseACL(array $acl): array
+ {
+ $map = [
+ 'lrswipkxtecdn' => 'full',
+ 'lrs' => 'read-only',
+ 'read' => 'read-only',
+ 'lrswitedn' => 'read-write',
+ ];
+
+ $supportedRights = ['full', 'read-only', 'read-write'];
+
+ foreach ($acl as $idx => $entry) {
+ $parts = explode(',', $entry);
+ $entry = null;
+
+ if (count($parts) == 2) {
+ $label = trim($parts[0]);
+ $rights = trim($parts[1]);
+ $rights = $map[$rights] ?? $rights;
+
+ if (in_array($rights, $supportedRights) && ($label === 'anyone' || strpos($label, '@'))) {
+ $entry = "{$label}, {$rights}";
+ }
+
+ // TODO: Throw an error or log a warning on unsupported acl entry?
+ }
+
+ $acl[$idx] = $entry;
+ }
+
+ return array_values(array_filter($acl));
+ }
+
+ /**
+ * Validate/convert invitation policy to Kolab4 format
+ */
+ protected static function parseInvitationPolicy(array $policies): ?string
+ {
+ foreach ($policies as $policy) {
+ if ($policy == 'ACT_MANUAL') {
+ // 'owner' attribute handling in another place
+ return 'manual';
+ }
+
+ if ($policy == 'ACT_ACCEPT_AND_NOTIFY') {
+ break; // use the default 'accept' (null) policy
+ }
+
+ if ($policy == 'ACT_REJECT') {
+ return 'reject';
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Validate/convert sender policy to Kolab4 format
+ */
+ protected static function parseSenderPolicy(array $rules): array
+ {
+ foreach ($rules as $idx => $rule) {
+ $entry = trim($rule);
+ $rule = null;
+
+ // 'deny' rules aren't supported
+ if (isset($entry[0]) && $entry[0] !== '-') {
+ $rule = $entry;
+ }
+
+ $rules[$idx] = $rule;
+ }
+
+ $rules = array_values(array_filter($rules));
+
+ if (!empty($rules) && $rules[count($rules) - 1] != '-') {
+ $rules[] = '-';
+ }
+
+ return $rules;
+ }
+
+ /**
+ * Get/prepare packages/skus information
+ */
+ protected function preparePackagesAndSkus(): void
+ {
+ // Find the tenant
+ if (empty($this->ownerDN)) {
+ if ($user = $this->getUser($this->argument('owner'))) {
+ $tenant_id = $user->tenant_id;
+ }
+ }
+
+ // TODO: Tenant id could be a command option
+
+ if (empty($tenant_id)) {
+ $tenant_id = \config('app.tenant_id');
+ }
+
+ // TODO: We should probably make package titles configurable with command options
+
+ $this->packages = [
+ 'user' => \App\Package::where('title', 'kolab')->where('tenant_id', $tenant_id)->first(),
+ 'domain' => \App\Package::where('title', 'domain-hosting')->where('tenant_id', $tenant_id)->first(),
+ ];
+
+ // Count storage skus
+ $sku = $this->packages['user']->skus()->where('title', 'storage')->first();
+
+ $this->packages['quota'] = $sku ? $sku->pivot->qty : 0;
+ $this->packages['storage'] = \App\Sku::where('title', 'storage')->where('tenant_id', $tenant_id)->first();
+ }
+
+ /**
+ * Set aliases for the user
+ */
+ protected function setUserAliases(\App\User $user, array $aliases = [])
+ {
+ if (!empty($aliases)) {
+ // Some users might have alias entry with their main address, remove it
+ $aliases = array_map('strtolower', $aliases);
+ $aliases = array_diff(array_unique($aliases), [$user->email]);
+
+ // Remove aliases for domains that do not exist
+ if (!empty($aliases)) {
+ $aliases = array_filter(
+ $aliases,
+ function ($alias) {
+ return $this->domainExists(explode('@', $alias)[1]);
+ }
+ );
+ }
+
+ if (!empty($aliases)) {
+ $user->setAliases($aliases);
+ }
+ }
+ }
+
+ /**
+ * Set error message for specified import data record
+ */
+ protected static function setImportError($id, $error): void
+ {
+ DB::table(self::$table)->where('id', $id)->update(['error' => $error]);
+ }
+
+ /**
+ * Set warning message for specified import data record
+ */
+ protected static function setImportWarning($id, $warning): void
+ {
+ DB::table(self::$table)->where('id', $id)->update(['warning' => $warning]);
+ }
+}
diff --git a/src/app/Console/Commands/MigratePrices.php b/src/app/Console/Commands/MigratePrices.php
--- a/src/app/Console/Commands/MigratePrices.php
+++ b/src/app/Console/Commands/MigratePrices.php
@@ -2,7 +2,7 @@
namespace App\Console\Commands;
-use Illuminate\Console\Command;
+use App\Console\Command;
class MigratePrices extends Command
{
@@ -33,7 +33,7 @@
private function updateSKUs()
{
- $bar = \App\Utils::createProgressBar($this->output, 8, "Updating SKUs");
+ $bar = $this->createProgressBar(8, "Updating SKUs");
// 1. Set the list price for the SKU 'mailbox' to 500.
$bar->advance();
@@ -88,7 +88,7 @@
{
$users = \App\User::all();
- $bar = \App\Utils::createProgressBar($this->output, count($users), "Updating entitlements");
+ $bar = $this->createProgressBar(count($users), "Updating entitlements");
$groupware_sku = \App\Sku::where('title', 'groupware')->first();
$activesync_sku = \App\Sku::where('title', 'activesync')->first();
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
@@ -16,7 +16,7 @@
public function creating(Resource $resource): void
{
if (empty($resource->email)) {
- if (!isset($resource->name)) {
+ if (!isset($resource->domain)) {
throw new \Exception("Missing 'domain' property for a new resource");
}
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
@@ -20,7 +20,7 @@
}
if (empty($folder->email)) {
- if (!isset($folder->name)) {
+ if (!isset($folder->domain)) {
throw new \Exception("Missing 'domain' property for a new shared folder");
}
diff --git a/src/app/User.php b/src/app/User.php
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -24,6 +24,7 @@
* @property string $email
* @property int $id
* @property string $password
+ * @property string $password_ldap
* @property int $status
* @property int $tenant_id
*/
diff --git a/src/app/Utils.php b/src/app/Utils.php
--- a/src/app/Utils.php
+++ b/src/app/Utils.php
@@ -65,32 +65,6 @@
return self::countryForIP($ip);
}
- /**
- * Shortcut to creating a progress bar of a particular format with a particular message.
- *
- * @param \Illuminate\Console\OutputStyle $output Console output object
- * @param int $count Number of progress steps
- * @param string $message The description
- *
- * @return \Symfony\Component\Console\Helper\ProgressBar
- */
- public static function createProgressBar($output, $count, $message = null)
- {
- $bar = $output->createProgressBar($count);
-
- $bar->setFormat(
- '%current:7s%/%max:7s% [%bar%] %percent:3s%% %elapsed:7s%/%estimated:-7s% %message% '
- );
-
- if ($message) {
- $bar->setMessage($message . " ...");
- }
-
- $bar->start();
-
- return $bar;
- }
-
/**
* Return the number of days in the month prior to this one.
*
diff --git a/src/tests/Feature/Console/Data/Import/LdifTest.php b/src/tests/Feature/Console/Data/Import/LdifTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Console/Data/Import/LdifTest.php
@@ -0,0 +1,432 @@
+<?php
+
+namespace Tests\Feature\Console\Data\Import;
+
+use Tests\TestCase;
+
+class LdifTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->deleteTestUser('owner@kolab3.com');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ $this->deleteTestUser('owner@kolab3.com');
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test the command
+ */
+ public function testHandle(): void
+ {
+ $code = \Artisan::call("data:import:ldif tests/data/kolab3.ldif owner@kolab3.com");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(1, $code);
+
+ $this->assertStringNotContainsString("Importing", $output);
+ $this->assertStringNotContainsString("WARNING", $output);
+ $this->assertStringContainsString(
+ "ERROR cn=error,ou=groups,ou=kolab3.com,dc=hosted,dc=com: Missing 'mail' attribute",
+ $output
+ );
+ $this->assertStringContainsString(
+ "ERROR cn=error,ou=resources,ou=kolab3.com,dc=hosted,dc=com: Missing 'mail' attribute",
+ $output
+ );
+
+ $code = \Artisan::call("data:import:ldif tests/data/kolab3.ldif owner@kolab3.com --force");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(0, $code);
+ $this->assertStringContainsString("Importing domains... DONE", $output);
+ $this->assertStringContainsString("Importing users... DONE", $output);
+ $this->assertStringContainsString("Importing resources... DONE", $output);
+ $this->assertStringContainsString("Importing shared folders... DONE", $output);
+ $this->assertStringContainsString("Importing groups... DONE", $output);
+ $this->assertStringNotContainsString("ERROR", $output);
+ $this->assertStringContainsString(
+ "WARNING cn=unknowndomain,ou=groups,ou=kolab3.org,dc=hosted,dc=com: Domain not found",
+ $output
+ );
+
+ $owner = \App\User::where('email', 'owner@kolab3.com')->first();
+
+ $this->assertNull($owner->password);
+ $this->assertSame(
+ '{SSHA512}g74+SECTLsM1x0aYkSrTG9sOFzEp8wjCflhshr2DjE7mi1G3iNb4ClH3ljorPRlTgZ105PsQGEpNtNr+XRjigg==',
+ $owner->password_ldap
+ );
+
+ // User settings
+ $this->assertSame('Aleksander', $owner->getSetting('first_name'));
+ $this->assertSame('Machniak', $owner->getSetting('last_name'));
+ $this->assertSame('123456789', $owner->getSetting('phone'));
+ $this->assertSame('external@gmail.com', $owner->getSetting('external_email'));
+ $this->assertSame('Organization AG', $owner->getSetting('organization'));
+
+ // User aliases
+ $aliases = $owner->aliases()->orderBy('alias')->pluck('alias')->all();
+ $this->assertSame(['alias@kolab3-alias.com', 'alias@kolab3.com'], $aliases);
+
+ // Wallet, entitlements
+ $wallet = $owner->wallets->first();
+
+ $this->assertEntitlements($owner, [
+ 'groupware',
+ 'mailbox',
+ 'storage', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage',
+ ]);
+
+ // Users
+ $this->assertSame(2, $owner->users(false)->count());
+ $user = $owner->users(false)->where('email', 'user@kolab3.com')->first();
+
+ // User settings
+ $this->assertSame('Jane', $user->getSetting('first_name'));
+ $this->assertSame('Doe', $user->getSetting('last_name'));
+ $this->assertSame('1234567890', $user->getSetting('phone'));
+ $this->assertSame('ext@gmail.com', $user->getSetting('external_email'));
+ $this->assertSame('Org AG', $user->getSetting('organization'));
+
+ // User aliases
+ $aliases = $user->aliases()->orderBy('alias')->pluck('alias')->all();
+ $this->assertSame(['alias2@kolab3.com'], $aliases);
+
+ $this->assertEntitlements($user, [
+ 'groupware',
+ 'mailbox',
+ 'storage', 'storage', 'storage', 'storage', 'storage',
+ ]);
+
+ // Domains
+ $domains = $owner->domains(false, false)->orderBy('namespace')->get();
+
+ $this->assertCount(2, $domains);
+ $this->assertSame('kolab3-alias.com', $domains[0]->namespace);
+ $this->assertSame('kolab3.com', $domains[1]->namespace);
+ $this->assertSame(\App\Domain::TYPE_EXTERNAL, $domains[0]->type);
+ $this->assertSame(\App\Domain::TYPE_EXTERNAL, $domains[1]->type);
+
+ $this->assertEntitlements($domains[0], ['domain-hosting']);
+ $this->assertEntitlements($domains[1], ['domain-hosting']);
+
+ // Shared folders
+ $folders = $owner->sharedFolders(false)->orderBy('email')->get();
+
+ $this->assertCount(2, $folders);
+ $this->assertMatchesRegularExpression('/^event-[0-9]+@kolab3\.com$/', $folders[0]->email);
+ $this->assertMatchesRegularExpression('/^mail-[0-9]+@kolab3\.com$/', $folders[1]->email);
+ $this->assertSame('Folder2', $folders[0]->name);
+ $this->assertSame('Folder1', $folders[1]->name);
+ $this->assertSame('event', $folders[0]->type);
+ $this->assertSame('mail', $folders[1]->type);
+ $this->assertSame('["anyone, read-only"]', $folders[0]->getSetting('acl'));
+ $this->assertSame('shared/Folder2@kolab3.com', $folders[0]->getSetting('folder'));
+ $this->assertSame('["anyone, read-write","owner@kolab3.com, full"]', $folders[1]->getSetting('acl'));
+ $this->assertSame('shared/Folder1@kolab3.com', $folders[1]->getSetting('folder'));
+
+ // Groups
+ $groups = $owner->groups(false)->orderBy('email')->get();
+
+ $this->assertCount(1, $groups);
+ $this->assertSame('Group', $groups[0]->name);
+ $this->assertSame('group@kolab3.com', $groups[0]->email);
+ $this->assertSame(['owner@kolab3.com', 'user@kolab3.com'], $groups[0]->members);
+ $this->assertSame('["sender@gmail.com","-"]', $groups[0]->getSetting('sender_policy'));
+
+ // Resources
+ $resources = $owner->resources(false)->orderBy('email')->get();
+
+ $this->assertCount(1, $resources);
+ $this->assertSame('Resource', $resources[0]->name);
+ $this->assertMatchesRegularExpression('/^resource-[0-9]+@kolab3\.com$/', $resources[0]->email);
+ $this->assertSame('shared/Resource@kolab3.com', $resources[0]->getSetting('folder'));
+ $this->assertSame('manual:user@kolab3.com', $resources[0]->getSetting('invitation_policy'));
+ }
+
+ /**
+ * Test parseACL() method
+ */
+ public function testParseACL(): void
+ {
+ $command = new \App\Console\Commands\Data\Import\LdifCommand();
+
+ $result = $this->invokeMethod($command, 'parseACL', [[]]);
+ $this->assertSame([], $result);
+
+ $acl = [
+ 'anyone, read-write',
+ 'read-only@kolab3.com, read-only',
+ 'read-only@kolab3.com, read',
+ 'full@kolab3.com,full',
+ 'lrswipkxtecdn@kolab3.com, lrswipkxtecdn', // full
+ 'lrs@kolab3.com, lrs', // read-only
+ 'lrswitedn@kolab3.com, lrswitedn', // read-write
+ // unsupported:
+ 'anonymous, read-only',
+ 'group:test, lrs',
+ 'test@kolab3.com, lrspkxtdn',
+ ];
+
+ $expected = [
+ 'anyone, read-write',
+ 'read-only@kolab3.com, read-only',
+ 'read-only@kolab3.com, read-only',
+ 'full@kolab3.com, full',
+ 'lrswipkxtecdn@kolab3.com, full',
+ 'lrs@kolab3.com, read-only',
+ 'lrswitedn@kolab3.com, read-write',
+ ];
+
+ $result = $this->invokeMethod($command, 'parseACL', [$acl]);
+ $this->assertSame($expected, $result);
+ }
+
+ /**
+ * Test parseInvitationPolicy() method
+ */
+ public function testParseInvitationPolicy(): void
+ {
+ $command = new \App\Console\Commands\Data\Import\LdifCommand();
+
+ $result = $this->invokeMethod($command, 'parseInvitationPolicy', [[]]);
+ $this->assertSame(null, $result);
+
+ $result = $this->invokeMethod($command, 'parseInvitationPolicy', [['UNKNOWN']]);
+ $this->assertSame(null, $result);
+
+ $result = $this->invokeMethod($command, 'parseInvitationPolicy', [['ACT_ACCEPT']]);
+ $this->assertSame(null, $result);
+
+ $result = $this->invokeMethod($command, 'parseInvitationPolicy', [['ACT_MANUAL']]);
+ $this->assertSame('manual', $result);
+
+ $result = $this->invokeMethod($command, 'parseInvitationPolicy', [['ACT_REJECT']]);
+ $this->assertSame('reject', $result);
+
+ $result = $this->invokeMethod($command, 'parseInvitationPolicy', [['ACT_ACCEPT_AND_NOTIFY', 'ACT_REJECT']]);
+ $this->assertSame(null, $result);
+ }
+
+ /**
+ * Test parseSenderPolicy() method
+ */
+ public function testParseSenderPolicy(): void
+ {
+ $command = new \App\Console\Commands\Data\Import\LdifCommand();
+
+ $result = $this->invokeMethod($command, 'parseSenderPolicy', [[]]);
+ $this->assertSame([], $result);
+
+ $result = $this->invokeMethod($command, 'parseSenderPolicy', [['test']]);
+ $this->assertSame(['test', '-'], $result);
+
+ $result = $this->invokeMethod($command, 'parseSenderPolicy', [['test', '-test2', 'test3', '']]);
+ $this->assertSame(['test', 'test3', '-'], $result);
+ }
+
+ /**
+ * Test parseLDAPDomain() method
+ */
+ public function testParseLDAPDomain(): void
+ {
+ $command = new \App\Console\Commands\Data\Import\LdifCommand();
+
+ $entry = [];
+ $result = $this->invokeMethod($command, 'parseLDAPDomain', [$entry]);
+ $this->assertSame([], $result[0]);
+ $this->assertSame("Missing 'associatedDomain' attribute", $result[1]);
+
+ $entry = ['associateddomain' => 'test.com'];
+ $result = $this->invokeMethod($command, 'parseLDAPDomain', [$entry]);
+ $this->assertSame(['namespace' => 'test.com'], $result[0]);
+ $this->assertSame(null, $result[1]);
+
+ $entry = ['associateddomain' => 'test.com', 'inetdomainstatus' => 'deleted'];
+ $result = $this->invokeMethod($command, 'parseLDAPDomain', [$entry]);
+ $this->assertSame([], $result[0]);
+ $this->assertSame("Domain deleted", $result[1]);
+ }
+
+ /**
+ * Test parseLDAPGroup() method
+ */
+ public function testParseLDAPGroup(): void
+ {
+ $command = new \App\Console\Commands\Data\Import\LdifCommand();
+
+ $entry = [];
+ $result = $this->invokeMethod($command, 'parseLDAPGroup', [$entry]);
+ $this->assertSame([], $result[0]);
+ $this->assertSame("Missing 'cn' attribute", $result[1]);
+
+ $entry = ['cn' => 'Test'];
+ $result = $this->invokeMethod($command, 'parseLDAPGroup', [$entry]);
+ $this->assertSame([], $result[0]);
+ $this->assertSame("Missing 'mail' attribute", $result[1]);
+
+ $entry = ['cn' => 'Test', 'mail' => 'test@domain.tld'];
+ $result = $this->invokeMethod($command, 'parseLDAPGroup', [$entry]);
+ $this->assertSame([], $result[0]);
+ $this->assertSame("Missing 'uniqueMember' attribute", $result[1]);
+
+ $entry = [
+ 'cn' => 'Test',
+ 'mail' => 'Test@domain.tld',
+ 'uniquemember' => 'uid=user@kolab3.com,ou=People,ou=kolab3.com,dc=hosted,dc=com',
+ 'kolaballowsmtpsender' => ['sender1@gmail.com', 'sender2@gmail.com'],
+ ];
+
+ $expected = [
+ 'name' => 'Test',
+ 'email' => 'test@domain.tld',
+ 'members' => ['uid=user@kolab3.com,ou=People,ou=kolab3.com,dc=hosted,dc=com'],
+ 'sender_policy' => ['sender1@gmail.com', 'sender2@gmail.com', '-'],
+ ];
+
+ $result = $this->invokeMethod($command, 'parseLDAPGroup', [$entry]);
+ $this->assertSame($expected, $result[0]);
+ $this->assertSame(null, $result[1]);
+ }
+
+ /**
+ * Test parseLDAPResource() method
+ */
+ public function testParseLDAPResource(): void
+ {
+ $command = new \App\Console\Commands\Data\Import\LdifCommand();
+
+ $entry = [];
+ $result = $this->invokeMethod($command, 'parseLDAPResource', [$entry]);
+ $this->assertSame([], $result[0]);
+ $this->assertSame("Missing 'cn' attribute", $result[1]);
+
+ $entry = ['cn' => 'Test'];
+ $result = $this->invokeMethod($command, 'parseLDAPResource', [$entry]);
+ $this->assertSame([], $result[0]);
+ $this->assertSame("Missing 'mail' attribute", $result[1]);
+
+ $entry = [
+ 'cn' => 'Test',
+ 'mail' => 'Test@domain.tld',
+ 'owner' => 'uid=user@kolab3.com,ou=People,ou=kolab3.com,dc=hosted,dc=com',
+ 'kolabtargetfolder' => 'Folder',
+ 'kolabinvitationpolicy' => 'ACT_REJECT'
+ ];
+
+ $expected = [
+ 'name' => 'Test',
+ 'email' => 'test@domain.tld',
+ 'folder' => 'Folder',
+ 'owner' => 'uid=user@kolab3.com,ou=People,ou=kolab3.com,dc=hosted,dc=com',
+ 'invitation_policy' => 'reject',
+ ];
+
+ $result = $this->invokeMethod($command, 'parseLDAPResource', [$entry]);
+ $this->assertSame($expected, $result[0]);
+ $this->assertSame(null, $result[1]);
+ }
+
+ /**
+ * Test parseLDAPSharedFolder() method
+ */
+ public function testParseLDAPSharedFolder(): void
+ {
+ $command = new \App\Console\Commands\Data\Import\LdifCommand();
+
+ $entry = [];
+ $result = $this->invokeMethod($command, 'parseLDAPSharedFolder', [$entry]);
+ $this->assertSame([], $result[0]);
+ $this->assertSame("Missing 'cn' attribute", $result[1]);
+
+ $entry = ['cn' => 'Test'];
+ $result = $this->invokeMethod($command, 'parseLDAPSharedFolder', [$entry]);
+ $this->assertSame([], $result[0]);
+ $this->assertSame("Missing 'mail' attribute", $result[1]);
+
+ $entry = [
+ 'cn' => 'Test',
+ 'mail' => 'Test@domain.tld',
+ 'kolabtargetfolder' => 'Folder',
+ 'kolabfoldertype' => 'event',
+ 'acl' => 'anyone, read-write',
+ ];
+
+ $expected = [
+ 'name' => 'Test',
+ 'email' => 'test@domain.tld',
+ 'type' => 'event',
+ 'folder' => 'Folder',
+ 'acl' => ['anyone, read-write'],
+ ];
+
+ $result = $this->invokeMethod($command, 'parseLDAPSharedFolder', [$entry]);
+ $this->assertSame($expected, $result[0]);
+ $this->assertSame(null, $result[1]);
+ }
+
+ /**
+ * Test parseLDAPUser() method
+ */
+ public function testParseLDAPUser(): void
+ {
+ // Note: If we do not initialize the command input we'll get an error
+ $args = [
+ 'file' => 'test.ldif',
+ 'owner' => 'test@domain.tld',
+ ];
+
+ $command = new \App\Console\Commands\Data\Import\LdifCommand();
+ $command->setInput(new \Symfony\Component\Console\Input\ArrayInput($args, $command->getDefinition()));
+
+ $entry = ['cn' => 'Test'];
+ $result = $this->invokeMethod($command, 'parseLDAPUser', [$entry]);
+ $this->assertSame([], $result[0]);
+ $this->assertSame("Missing 'mail' attribute", $result[1]);
+
+ $entry = [
+ 'dn' => 'user dn',
+ 'givenname' => 'Given',
+ 'mail' => 'Test@domain.tld',
+ 'sn' => 'Surname',
+ 'telephonenumber' => '123',
+ 'o' => 'Org',
+ 'mailalternateaddress' => 'test@ext.com',
+ 'alias' => ['test1@domain.tld', 'test2@domain.tld'],
+ 'userpassword' => 'pass',
+ 'mailquota' => '12345678',
+ ];
+
+ $expected = [
+ 'email' => 'test@domain.tld',
+ 'settings' => [
+ 'first_name' => 'Given',
+ 'last_name' => 'Surname',
+ 'phone' => '123',
+ 'external_email' => 'test@ext.com',
+ 'organization' => 'Org',
+ ],
+ 'aliases' => ['test1@domain.tld', 'test2@domain.tld'],
+ 'password' => 'pass',
+ 'quota' => '12345678',
+ ];
+
+ $result = $this->invokeMethod($command, 'parseLDAPUser', [$entry]);
+ $this->assertSame($expected, $result[0]);
+ $this->assertSame(null, $result[1]);
+ $this->assertSame($entry['dn'], $this->getObjectProperty($command, 'ownerDN'));
+ }
+}
diff --git a/src/tests/data/kolab3.ldif b/src/tests/data/kolab3.ldif
new file mode 100644
--- /dev/null
+++ b/src/tests/data/kolab3.ldif
@@ -0,0 +1,119 @@
+dn: associateddomain=kolab3.com,ou=Domains,dc=hosted,dc=com
+objectClass: top
+objectClass: domainrelatedobject
+objectClass: inetdomain
+inetDomainBaseDN: ou=kolab3.com,dc=hosted,dc=com
+associatedDomain: kolab3.com
+associatedDomain: kolab3-alias.com
+
+dn: uid=owner@kolab3.com,ou=People,ou=kolab3.com,dc=hosted,dc=com
+cn: Aleksander Machniak
+displayName: Machniak, Aleksander
+givenName: Aleksander
+sn: Machniak
+o: Organization AG
+l:: U2llbWlhbm93aWNlIMWabMSFc2tpZQ==
+mobile: 123456789
+c: PL
+objectClass: top
+objectClass: inetorgperson
+objectClass: kolabinetorgperson
+objectClass: organizationalperson
+objectClass: mailrecipient
+objectClass: country
+objectClass: person
+mail: owner@kolab3.com
+alias: alias@kolab3.com
+alias: alias@kolab3-alias.com
+mailAlternateAddress: external@gmail.com
+mailHost: imap.hosted.com
+mailQuota: 8388608
+uid: owner@kolab3.com
+userPassword:: e1NTSEE1MTJ9Zzc0K1NFQ1RMc00xeDBhWWtTclRHOXNPRnpFcDh3akNmbGhzaHIyRGpFN21pMUczaU5iNENsSDNsam9yUFJsVGdaMTA1UHNRR0VwTnROcitYUmppZ2c9PQ==
+nsUniqueID: 229dc10c-1b6a11f7-b7c1edc1-0e0f46c4
+createtimestamp: 20170407081419Z
+modifytimestamp: 20200915082359Z
+
+dn: uid=user@kolab3.com,ou=People,ou=kolab3.com,dc=hosted,dc=com
+cn: Jane Doe
+displayName: Doe, Jane
+givenName: Jane
+sn: Doe
+o: Org AG
+telephoneNumber: 1234567890
+objectClass: top
+objectClass: inetorgperson
+objectClass: kolabinetorgperson
+objectClass: organizationalperson
+objectClass: mailrecipient
+objectClass: country
+objectClass: person
+mail: user@kolab3.com
+alias: alias2@kolab3.com
+mailAlternateAddress: ext@gmail.com
+mailHost: imap.hosted.com
+mailQuota: 2097152
+uid: user@kolab3.com
+userPassword:: e1NTSEE1MTJ9Zzc0K1NFQ1RMc00xeDBhWWtTclRHOXNPRnpFcDh3akNmbGhzaHIyRGpFN21pMUczaU5iNENsSDNsam9yUFJsVGdaMTA1UHNRR0VwTnROcitYUmppZ2c9PQ==
+nsUniqueID: 229dc20c-1b6a11f7-b7c1edc1-0e0f46c4
+
+dn: cn=Group,ou=Groups,ou=kolab3.com,dc=hosted,dc=com
+cn: Group
+mail: group@kolab3.com
+objectClass: top
+objectClass: groupofuniquenames
+objectClass: kolabgroupofuniquenames
+uniqueMember: uid=user@kolab3.com,ou=People,ou=kolab3.com,dc=hosted,dc=com
+uniqueMember: uid=owner@kolab3.com,ou=People,ou=kolab3.com,dc=hosted,dc=com
+kolabAllowSMTPRecipient: recipient@kolab.org
+kolabAllowSMTPSender: sender@gmail.com
+
+dn: cn=Error,ou=Groups,ou=kolab3.com,dc=hosted,dc=com
+cn: Error
+uniqueMember: uid=user@kolab3.com,ou=People,ou=kolab3.com,dc=hosted,dc=com
+
+dn: cn=UnknownDomain,ou=Groups,ou=kolab3.org,dc=hosted,dc=com
+cn: UnknownDomain
+mail: unknowndomain@kolab3.org
+objectClass: top
+objectClass: groupofuniquenames
+objectClass: kolabgroupofuniquenames
+uniqueMember: uid=user@kolab3.com,ou=People,ou=kolab3.com,dc=hosted,dc=com
+uniqueMember: uid=owner@kolab3.com,ou=People,ou=kolab3.com,dc=hosted,dc=com
+
+dn: cn=Resource,ou=Resources,ou=kolab3.com,dc=hosted,dc=com
+cn: Resource
+mail: resource-car-resource@kolab3.com
+objectClass: top
+objectClass: kolabsharedfolder
+objectClass: kolabresource
+objectClass: mailrecipient
+owner: uid=user@kolab3.com,ou=People,ou=kolab3.com,dc=hosted,dc=com
+kolabAllowSMTPRecipient: recipient@kolab.org
+kolabAllowSMTPSender: sender@gmail.com
+kolabInvitationPolicy: ACT_MANUAL
+kolabTargetFolder: shared/Resource@kolab3.com
+
+dn: cn=Error,ou=Resources,ou=kolab3.com,dc=hosted,dc=com
+cn: Error
+
+dn: cn=Folder1,ou=Shared Folders,ou=kolab3.com,dc=hosted,dc=com
+cn: Folder1
+objectClass: kolabsharedfolder
+objectClass: mailrecipient
+objectClass: top
+kolabFolderType: mail
+kolabTargetFolder: shared/Folder1@kolab3.com
+mail: folder1@kolab3.com
+acl: anyone, read-write
+acl: owner@kolab3.com, full
+
+dn: cn=Folder2,ou=Shared Folders,ou=kolab3.com,dc=hosted,dc=com
+cn: Folder2
+objectClass: kolabsharedfolder
+objectClass: mailrecipient
+objectClass: top
+kolabFolderType: event
+kolabTargetFolder: shared/Folder2@kolab3.com
+mail: folder2@kolab3.com
+acl: anyone, read-only

File Metadata

Mime Type
text/plain
Expires
Sun, Apr 5, 10:46 PM (4 h, 2 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18834825
Default Alt Text
D3184.1775429212.diff (56 KB)

Event Timeline