Page MenuHomePhorge

D5098.1775261397.diff
No OneTemporary

Authored By
Unknown
Size
10 KB
Referenced Files
None
Subscribers
None

D5098.1775261397.diff

diff --git a/src/app/DataMigrator/Driver/DAV.php b/src/app/DataMigrator/Driver/DAV.php
--- a/src/app/DataMigrator/Driver/DAV.php
+++ b/src/app/DataMigrator/Driver/DAV.php
@@ -39,6 +39,8 @@
*/
public function __construct(Account $account, Engine $engine)
{
+ // Note: This user impersonation method works only with Kolab v3 iRony when kolab_auth_proxy plugin is enabled.
+ // For Kolab v4 we need another approach - just use the kolab:// scheme for this.
$username = $account->username . ($account->loginas ? "**{$account->loginas}" : '');
$baseUri = preg_replace('|^dav|', 'http', $account->uri);
diff --git a/src/app/DataMigrator/Driver/IMAP.php b/src/app/DataMigrator/Driver/IMAP.php
--- a/src/app/DataMigrator/Driver/IMAP.php
+++ b/src/app/DataMigrator/Driver/IMAP.php
@@ -19,9 +19,12 @@
/** @const int Max number of items to migrate in one go */
protected const CHUNK_SIZE = 100;
- /** @var \rcube_imap_generic Imap backend */
+ /** @var ?\rcube_imap_generic Imap backend */
protected $imap;
+ /** @var array IMAP configuration */
+ protected $config;
+
/** @var Account Account to operate on */
protected $account;
@@ -36,10 +39,7 @@
{
$this->account = $account;
$this->engine = $engine;
-
- // TODO: Move this to self::authenticate()?
- $config = self::getConfig($account);
- $this->imap = static::initIMAP($config);
+ $this->config = self::getConfig($account);
}
/**
@@ -47,6 +47,10 @@
*/
public function __destruct()
{
+ if (!$this->imap) {
+ return;
+ }
+
try {
$this->imap->closeConnection();
} catch (\Throwable $e) {
@@ -60,6 +64,7 @@
*/
public function authenticate(): void
{
+ $this->initIMAP();
}
/**
@@ -71,6 +76,8 @@
*/
public function createFolder(Folder $folder): void
{
+ $this->initIMAP();
+
if ($folder->type != 'mail') {
throw new \Exception("IMAP does not support folder of type {$folder->type}");
}
@@ -122,6 +129,8 @@
*/
public function createItem(Item $item): void
{
+ $this->initIMAP();
+
$mailbox = self::toUTF7($item->folder->targetname);
if (strlen($item->content)) {
@@ -180,6 +189,8 @@
*/
public function fetchFolder(Folder $folder): void
{
+ $this->initIMAP();
+
$mailbox = self::toUTF7($folder->targetname);
// Get folder ACL
@@ -203,6 +214,8 @@
*/
public function fetchItem(Item $item): void
{
+ $this->initIMAP();
+
[$uid, $messageId] = explode(':', $item->id, 2);
$mailbox = self::toUTF7($item->folder->fullname);
@@ -269,6 +282,8 @@
*/
public function fetchItemList(Folder $folder, $callback, ImporterInterface $importer): void
{
+ $this->initIMAP();
+
// Get existing messages' headers from the destination mailbox
$existing = $importer->getItems($folder);
@@ -331,6 +346,8 @@
*/
public function getFolders($types = []): array
{
+ $this->initIMAP();
+
$folders = $this->imap->listMailboxes('', '');
if ($folders === false) {
@@ -367,6 +384,8 @@
*/
public function getItems(Folder $folder): array
{
+ $this->initIMAP();
+
$mailbox = self::toUTF7($folder->targetname ? $folder->targetname : $folder->fullname);
// TODO: We should probably first use SEARCH/SORT to skip messages marked as \Deleted
@@ -401,25 +420,37 @@
/**
* Initialize IMAP connection and authenticate the user
*/
- protected static function initIMAP(array $config): \rcube_imap_generic
+ protected function initIMAP(): void
{
- $imap = new \rcube_imap_generic();
+ if ($this->imap) {
+ return;
+ }
+
+ $this->imap = new \rcube_imap_generic();
if (\config('app.debug')) {
- $imap->setDebug(true, 'App\Backends\IMAP::logDebug');
+ $this->imap->setDebug(true, 'App\Backends\IMAP::logDebug');
}
- $imap->connect($config['host'], $config['user'], $config['password'], $config['options']);
+ $this->imap->connect(
+ $this->config['host'],
+ $this->config['user'],
+ $this->config['password'],
+ $this->config['options']
+ );
- if (!$imap->connected()) {
- $message = sprintf("Login failed for %s against %s. %s", $config['user'], $config['host'], $imap->error);
+ if (!$this->imap->connected()) {
+ $message = sprintf(
+ "Login failed for %s against %s. %s",
+ $this->config['user'],
+ $this->config['host'],
+ $this->imap->error
+ );
\Log::error($message);
throw new \Exception("Connection to IMAP failed");
}
-
- return $imap;
}
/**
diff --git a/src/app/DataMigrator/Driver/Kolab.php b/src/app/DataMigrator/Driver/Kolab.php
--- a/src/app/DataMigrator/Driver/Kolab.php
+++ b/src/app/DataMigrator/Driver/Kolab.php
@@ -8,6 +8,7 @@
use App\DataMigrator\Interface\ImporterInterface;
use App\DataMigrator\Interface\Item;
use App\DataMigrator\Interface\ItemSet;
+use App\User;
/**
* Data migration from/to a Kolab server
@@ -50,6 +51,18 @@
parent::__construct(new Account($uri), $engine);
+ // Support user impersonation in Kolab v4 DAV by issuing a token
+ // Note: This is not needed for Kolab v3 (iRony), but the kolab_auth_proxy plugin must be enabled there.
+ if ($account->loginas && $account->scheme != 'kolab3' && ($user = $account->getUser())) {
+ // Cyrus DAV does not support proxy authorization via DAV. Even though it has
+ // the Authorize-As header, it is used only for cummunication with Murder backends.
+ // We use a one-time token instead. It's valid for 6 hours, assume it's enough time
+ // to migrate an account.
+ $account->password = \App\Auth\Utils::tokenCreate((string) $user->id, 6 * 60 * 60);
+ $account->username = $account->loginas;
+ unset($account->loginas);
+ }
+
// Setup DAV connection
$uri = sprintf(
'davs://%s:%s@%s',
@@ -139,6 +152,7 @@
// Configuration (v3 tags)
if ($item->folder->type == Engine::TYPE_CONFIGURATION) {
+ $this->initIMAP();
Kolab\Tags::migrateKolab3Tag($this->imap, $item);
return;
}
@@ -186,12 +200,14 @@
// Files (IMAP)
if ($item->folder->type == Engine::TYPE_FILE) {
+ $this->initIMAP();
Kolab\Files::fetchKolab3File($this->imap, $item);
return;
}
// Configuration (v3 tags)
if ($item->folder->type == Engine::TYPE_CONFIGURATION) {
+ $this->initIMAP();
Kolab\Tags::fetchKolab3Tag($this->imap, $item);
return;
}
@@ -221,6 +237,8 @@
// Files
if ($folder->type == Engine::TYPE_FILE) {
+ $this->initIMAP();
+
// Get existing files from the destination account
$existing = $importer->getItems($folder);
@@ -234,6 +252,8 @@
// Configuration (v3 tags)
if ($folder->type == Engine::TYPE_CONFIGURATION) {
+ $this->initIMAP();
+
// Get existing tags from the destination account
$existing = $importer->getItems($folder);
@@ -251,6 +271,8 @@
*/
public function getFolders($types = []): array
{
+ $this->initIMAP();
+
// Note: No support for migration from Kolab4 yet.
// We use IMAP to get the list of all folders, but we'll get folder contents from IMAP and DAV.
// This will not work with Kolab4
@@ -387,6 +409,7 @@
// Configuration folder (tags)
if ($folder->type == Engine::TYPE_CONFIGURATION) {
+ $this->initIMAP();
return Kolab\Tags::getKolab4Tags($this->imap);
}
@@ -396,13 +419,11 @@
/**
* Initialize IMAP connection and authenticate the user
*/
- protected static function initIMAP(array $config): \rcube_imap_generic
+ protected function initIMAP(): void
{
- $imap = parent::initIMAP($config);
+ parent::initIMAP();
// Advertise itself as a Kolab client, in case Guam is in the way
- $imap->id(['name' => 'Cockpit/Kolab']);
-
- return $imap;
+ $this->imap->id(['name' => 'Cockpit/Kolab']);
}
}
diff --git a/src/tests/Feature/DataMigrator/KolabTest.php b/src/tests/Feature/DataMigrator/KolabTest.php
--- a/src/tests/Feature/DataMigrator/KolabTest.php
+++ b/src/tests/Feature/DataMigrator/KolabTest.php
@@ -4,8 +4,10 @@
use App\Backends\Storage;
use App\DataMigrator\Account;
+use App\DataMigrator\Driver\Kolab;
use App\DataMigrator\Driver\Kolab\Tags as KolabTags;
use App\DataMigrator\Engine;
+use App\DataMigrator\Interface\Folder;
use App\DataMigrator\Queue as MigratorQueue;
use App\Fs\Item as FsItem;
use Illuminate\Support\Facades\DB;
@@ -273,6 +275,41 @@
$this->assertSame('123', Storage::fileFetch($files['&kość.odt']));
}
+ /**
+ * Test user impersonation support
+ */
+ public function testUserImpersonation(): void
+ {
+ [$src, $src_imap, $src_dav, $dst, $dst_imap, $dst_dav] = $this->getTestAccounts();
+
+ $this->initAccount($dst_imap);
+ $this->initAccount($dst_dav);
+ $this->imapAppend($dst_imap, 'INBOX', 'mail/1.eml');
+ $this->davAppend($dst_dav, 'Calendar', ['event/3.ics'], Engine::TYPE_EVENT);
+
+ $adminUser = \config('services.imap.admin_login');
+ $adminPass = \config('services.imap.admin_password');
+ $dst_impersonated = str_replace('jack%40kolab.org:simple123', "{$adminUser}:{$adminPass}", (string) $dst)
+ . '&user=jack@kolab.org';
+
+ $migrator = new Engine();
+ $account = new Account($dst_impersonated);
+ $driver = new Kolab($account, $migrator);
+
+ // This involves IMAP and DAV authentication, so it should be enough,
+ // but we make some more assertions to make sure we're connected to the proper account.
+ $driver->authenticate();
+
+ $mail_items = $driver->getItems(Folder::fromArray(['fullname' => 'INBOX', 'type' => Engine::TYPE_MAIL]));
+ $this->assertCount(1, $mail_items);
+ $this->assertArrayHasKey('<sync1@kolab.org>', $mail_items);
+ $event_items = $driver->getItems(Folder::fromArray(['fullname' => 'Calendar', 'type' => Engine::TYPE_EVENT]));
+ $this->assertCount(1, $event_items);
+ $this->assertArrayHasKey('aaa-aaa', $event_items);
+
+ // TODO: We'll need a Kolab3 installation (iRony) to test if impersonation works there
+ }
+
/**
* Initialize accounts for tests
*/

File Metadata

Mime Type
text/plain
Expires
Sat, Apr 4, 12:09 AM (7 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18827374
Default Alt Text
D5098.1775261397.diff (10 KB)

Event Timeline