Преглед изворни кода

Integration tests #5694 - accounts

Aleksandr Trofimov пре 1 месец
родитељ
комит
4f2f4db039

+ 1 - 0
tests/bootstrap.php

@@ -178,3 +178,4 @@ class BxDolTestCase extends \PHPUnit\Framework\TestCase
 require_once dirname(__FILE__) . '/units/modules/boonex/posts/BxPostsTestCase.php';
 require_once dirname(__FILE__) . '/units/modules/boonex/persons/BxPersonsTestCase.php';
 require_once dirname(__FILE__) . '/integration/BxDolIntegrationTestCase.php';
+require_once dirname(__FILE__) . '/integration/account/BxDolAccountTestCase.php';

+ 7 - 0
tests/integration/BxDolIntegrationTestCase.php

@@ -42,6 +42,13 @@ abstract class BxDolIntegrationTestCase extends BxDolTestCase
         $_COOKIE = $this->_aCookieBackup ?? [];
 
         BxDolForm::unSetObjectInstance('sys_login', 'sys_login');
+        foreach ([
+            'sys_account_create',
+            'sys_account_settings_email',
+            'sys_account_settings_info',
+            'sys_account_settings_del_account',
+        ] as $sDisplay)
+            BxDolForm::unSetObjectInstance('sys_account', $sDisplay);
 
         $GLOBALS['logged']['member'] = $this->_aLoggedBackup['member'] ?? false;
         $GLOBALS['logged']['admin'] = $this->_aLoggedBackup['admin'] ?? false;

+ 58 - 0
tests/integration/account/AccountLifecycleTest.php

@@ -0,0 +1,58 @@
+<?php
+
+use PHPUnit\Framework\Attributes\Group;
+
+/**
+ * Create an account, change its email and name, then delete it on a live install.
+ */
+#[Group('integration')]
+class AccountLifecycleTest extends BxDolAccountTestCase
+{
+    public function testCreateEditAndDeleteAccount(): void
+    {
+        $aCreated = $this->bxCreateLifecycleAccount();
+        $sEmail = $aCreated['email'];
+        $sEmailNew = 'lifecycle-' . bin2hex(random_bytes(4)) . '-new@example.com';
+        $sNameNew = 'Lifecycle Renamed';
+        $this->_aCreatedEmails[] = $sEmailNew;
+
+        $this->assertTrue(isLogged());
+        $this->assertSame($aCreated['id'], (int)getLoggedId());
+        $this->assertSame($sEmail, $aCreated['account']->getEmail());
+        $this->assertSame($aCreated['name'], $aCreated['account']->getDisplayName());
+
+        $sEmailResult = $this->bxSubmitEmailForm($aCreated['id'], $sEmailNew, $aCreated['password']);
+        $this->assertIsString($sEmailResult);
+        $this->assertStringContainsString(_t('_sys_account_settings_email_successfully_submitted'), $sEmailResult);
+
+        $oAccount = BxDolAccount::getInstance($aCreated['id'], true);
+        $this->assertInstanceOf(BxDolAccount::class, $oAccount);
+        $this->assertSame($sEmailNew, $oAccount->getEmail());
+        $this->assertSame(0, $this->bxAccountIdByEmail($sEmail));
+        $this->assertSame($aCreated['id'], $this->bxAccountIdByEmail($sEmailNew));
+
+        $sNameResult = $this->bxSubmitInfoForm($aCreated['id'], $sNameNew);
+        $this->assertIsString($sNameResult);
+        $this->assertStringContainsString(_t('_sys_account_settings_info_successfully_submitted'), $sNameResult);
+
+        $oAccount = BxDolAccount::getInstance($aCreated['id'], true);
+        $this->assertInstanceOf(BxDolAccount::class, $oAccount);
+        $this->assertSame($sNameNew, $oAccount->getDisplayName());
+        $this->assertSame($sEmailNew, $oAccount->getEmail());
+
+        $oDeleteForm = $this->bxSubmitDeleteForm($oAccount, $aCreated['password']);
+        $this->assertTrue($oDeleteForm->isSubmittedAndValid(), $oDeleteForm->getFormErrors() ?: 'delete account form should accept the posted values');
+
+        $this->assertTrue($oAccount->delete((int)$oDeleteForm->getCleanValue('delete_content') != 0, false));
+        $this->_iCreatedAccountId = 0;
+
+        $this->bxBecomeGuest();
+        $this->assertFalse(BxDolAccount::getInstance($sEmailNew, true));
+        $this->assertSame(0, $this->bxAccountIdByEmail($sEmail));
+        $this->assertSame(0, $this->bxAccountIdByEmail($sEmailNew));
+        $this->assertEmpty(BxDolAccountQuery::getInstance()->getInfoById($aCreated['id']));
+        $this->assertEmpty(BxDolDb::getInstance()->getColumn("SELECT `id` FROM `sys_profiles` WHERE `account_id` = :id", [
+            'id' => $aCreated['id'],
+        ]));
+    }
+}

+ 246 - 0
tests/integration/account/AccountValidationTest.php

@@ -0,0 +1,246 @@
+<?php
+
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Group;
+
+/**
+ * Account forms reject invalid emails, weak/wrong passwords, and injection payloads.
+ */
+#[Group('integration')]
+class AccountValidationTest extends BxDolAccountTestCase
+{
+    static public function providerForRejectedCreateEmails(): array
+    {
+        return [
+            'empty' => [''],
+            'no-at' => ['not-an-email'],
+            'double-at' => ['a@b@c.com'],
+            'leading-dot' => ['.user@example.com'],
+            'underscore-domain' => ['user@exam_ple.com'],
+            'sql-or' => ["admin@example.com' OR '1'='1"],
+            'sql-comment' => ["' OR 1=1 --"],
+            'sql-union' => ["x' UNION SELECT password FROM sys_accounts --@example.com"],
+            'sql-drop' => ["'; DROP TABLE sys_accounts; --"],
+            'xss' => ['<script>alert(1)</script>@example.com'],
+        ];
+    }
+
+    static public function providerForRejectedCreatePasswords(): array
+    {
+        return [
+            'empty' => [''],
+            'too-short' => ['Ab1'],
+            'no-digit' => ['Password'],
+            'no-upper' => ['password1'],
+            'no-lower' => ['PASSWORD1'],
+            'sql-or' => ["' OR '1'='1"],
+            'sql-comment' => ["' OR 1=1 --"],
+        ];
+    }
+
+    static public function providerForRejectedCreateNames(): array
+    {
+        return [
+            'empty' => [''],
+            'at-prefix' => ['@admin'],
+        ];
+    }
+
+    static public function providerForMaliciousNames(): array
+    {
+        return [
+            'sql-or' => ["' OR '1'='1"],
+            'sql-drop' => ["'; DROP TABLE sys_accounts; --"],
+            'sql-delete' => ['1; DELETE FROM sys_accounts --'],
+            'xss-script' => ['<script>alert(1)</script>'],
+            'xss-img' => ['<img src=x onerror=alert(1)>'],
+        ];
+    }
+
+    static public function providerForRejectedPasswordsCurrent(): array
+    {
+        return [
+            'wrong' => ['not-the-password'],
+            'empty' => [''],
+            'sql-or' => ["' OR '1'='1"],
+            'sql-comment' => ["' OR 1=1 --"],
+            'sql-drop' => ["'; DROP TABLE sys_accounts; --"],
+        ];
+    }
+
+    #[DataProvider('providerForRejectedCreateEmails')]
+    public function testCreateFormRejectsInvalidEmail(string $sEmail): void
+    {
+        $this->bxBecomeGuest();
+        $sName = 'lifecycle-' . bin2hex(random_bytes(4));
+
+        $oForm = $this->bxSubmitCreateForm($sName, $sEmail, 'UnaTest1a');
+        $this->bxAssertFormRejected($oForm, 'email');
+
+        $iCreatedId = $this->bxAccountIdByName($sName);
+        if ($iCreatedId)
+            $this->_iCreatedAccountId = $iCreatedId;
+        $this->assertSame(0, $iCreatedId);
+        $this->bxAssertAccountsTableIntact();
+    }
+
+    #[DataProvider('providerForRejectedCreatePasswords')]
+    public function testCreateFormRejectsInvalidPassword(string $sPassword): void
+    {
+        $this->bxBecomeGuest();
+        $sEmail = 'lifecycle-' . bin2hex(random_bytes(4)) . '@example.com';
+
+        $oForm = $this->bxSubmitCreateForm('Lifecycle User', $sEmail, $sPassword);
+        $this->bxAssertFormRejected($oForm, 'password');
+        $this->assertSame(0, $this->bxAccountIdByEmail($sEmail));
+        $this->bxAssertAccountsTableIntact();
+    }
+
+    #[DataProvider('providerForRejectedCreateNames')]
+    public function testCreateFormRejectsInvalidName(string $sName): void
+    {
+        $this->bxBecomeGuest();
+        $sEmail = 'lifecycle-' . bin2hex(random_bytes(4)) . '@example.com';
+
+        $oForm = $this->bxSubmitCreateForm($sName, $sEmail, 'UnaTest1a');
+        $this->bxAssertFormRejected($oForm, 'name');
+        $this->assertSame(0, $this->bxAccountIdByEmail($sEmail));
+        $this->bxAssertAccountsTableIntact();
+    }
+
+    public function testCreateFormRejectsDuplicateEmail(): void
+    {
+        $this->bxBecomeGuest();
+        $sEmail = $this->bxTestEmail('admin');
+        $iExistingId = $this->bxAccountIdByEmail($sEmail);
+        $this->assertGreaterThan(0, $iExistingId);
+
+        $oForm = $this->bxSubmitCreateForm('Lifecycle User', $sEmail, 'UnaTest1a');
+        $this->bxAssertFormRejected($oForm, 'email');
+        $this->assertSame($iExistingId, $this->bxAccountIdByEmail($sEmail));
+        $this->bxAssertAccountsTableIntact();
+    }
+
+    #[DataProvider('providerForMaliciousNames')]
+    public function testCreateFormTreatsMaliciousNameAsData(string $sName): void
+    {
+        $this->bxBecomeGuest();
+        $sEmail = 'lifecycle-' . bin2hex(random_bytes(4)) . '@example.com';
+
+        $oForm = $this->bxSubmitCreateForm($sName, $sEmail, 'UnaTest1a');
+        if (!$oForm->isSubmittedAndValid()) {
+            $this->assertSame(0, $this->bxAccountIdByEmail($sEmail));
+            $this->bxAssertAccountsTableIntact();
+            return;
+        }
+
+        $this->_aCreatedEmails[] = $sEmail;
+        $iAccountId = $oForm->insert(['email_confirmed' => 0]);
+        $this->assertNotFalse($iAccountId);
+        $this->_iCreatedAccountId = (int)$iAccountId;
+        (new BxTemplAccountForms())->onAccountCreated($iAccountId, $oForm->isSetPendingApproval());
+
+        $this->assertSame((int)$iAccountId, $this->bxAccountIdByEmail($sEmail));
+        $this->bxAssertAccountsTableIntact();
+
+        $oAccount = BxDolAccount::getInstance($iAccountId, true);
+        $this->assertInstanceOf(BxDolAccount::class, $oAccount);
+        $this->assertSame($sEmail, $oAccount->getEmail());
+        $this->assertStringNotContainsString('<script', strtolower($oAccount->getDisplayName()));
+    }
+
+    #[DataProvider('providerForRejectedCreateEmails')]
+    public function testEmailFormRejectsInvalidEmail(string $sEmail): void
+    {
+        $aCreated = $this->bxCreateLifecycleAccount();
+        $sResult = $this->bxSubmitEmailForm($aCreated['id'], $sEmail, $aCreated['password']);
+
+        $this->assertIsString($sResult);
+        $this->assertStringNotContainsString(_t('_sys_account_settings_email_successfully_submitted'), $sResult);
+
+        $oAccount = BxDolAccount::getInstance($aCreated['id'], true);
+        $this->assertSame($aCreated['email'], $oAccount->getEmail());
+        $this->assertSame($aCreated['id'], $this->bxAccountIdByEmail($aCreated['email']));
+        $this->bxAssertAccountsTableIntact();
+    }
+
+    #[DataProvider('providerForRejectedPasswordsCurrent')]
+    public function testEmailFormRejectsWrongPassword(string $sPassword): void
+    {
+        $aCreated = $this->bxCreateLifecycleAccount();
+        $sEmailNew = 'lifecycle-' . bin2hex(random_bytes(4)) . '-new@example.com';
+        $sResult = $this->bxSubmitEmailForm($aCreated['id'], $sEmailNew, $sPassword);
+
+        $this->assertIsString($sResult);
+        $this->assertStringNotContainsString(_t('_sys_account_settings_email_successfully_submitted'), $sResult);
+
+        $oAccount = BxDolAccount::getInstance($aCreated['id'], true);
+        $this->assertSame($aCreated['email'], $oAccount->getEmail());
+        $this->assertSame(0, $this->bxAccountIdByEmail($sEmailNew));
+        $this->bxAssertAccountsTableIntact();
+    }
+
+    public function testEmailFormRejectsDuplicateEmail(): void
+    {
+        $aCreated = $this->bxCreateLifecycleAccount();
+        $sResult = $this->bxSubmitEmailForm($aCreated['id'], $this->bxTestEmail('admin'), $aCreated['password']);
+
+        $this->assertIsString($sResult);
+        $this->assertStringNotContainsString(_t('_sys_account_settings_email_successfully_submitted'), $sResult);
+
+        $oAccount = BxDolAccount::getInstance($aCreated['id'], true);
+        $this->assertSame($aCreated['email'], $oAccount->getEmail());
+        $this->bxAssertAccountsTableIntact();
+    }
+
+    #[DataProvider('providerForRejectedCreateNames')]
+    public function testInfoFormRejectsInvalidName(string $sName): void
+    {
+        $aCreated = $this->bxCreateLifecycleAccount();
+        $sResult = $this->bxSubmitInfoForm($aCreated['id'], $sName);
+
+        $this->assertIsString($sResult);
+        $this->assertStringNotContainsString(_t('_sys_account_settings_info_successfully_submitted'), $sResult);
+
+        $oAccount = BxDolAccount::getInstance($aCreated['id'], true);
+        $this->assertSame($aCreated['name'], $oAccount->getDisplayName());
+        $this->bxAssertAccountsTableIntact();
+    }
+
+    #[DataProvider('providerForMaliciousNames')]
+    public function testInfoFormTreatsMaliciousNameAsData(string $sName): void
+    {
+        $aCreated = $this->bxCreateLifecycleAccount();
+        $sResult = $this->bxSubmitInfoForm($aCreated['id'], $sName);
+        $oAccount = BxDolAccount::getInstance($aCreated['id'], true);
+
+        if (!str_contains((string)$sResult, _t('_sys_account_settings_info_successfully_submitted'))) {
+            $this->assertSame($aCreated['name'], $oAccount->getDisplayName());
+            $this->bxAssertAccountsTableIntact();
+            return;
+        }
+
+        $this->assertSame($aCreated['email'], $oAccount->getEmail());
+        $this->assertStringNotContainsString('<script', strtolower($oAccount->getDisplayName()));
+        $this->bxAssertAccountsTableIntact();
+    }
+
+    #[DataProvider('providerForRejectedPasswordsCurrent')]
+    public function testDeleteFormRejectsWrongPassword(string $sPassword): void
+    {
+        $aCreated = $this->bxCreateLifecycleAccount();
+        $oForm = $this->bxSubmitDeleteForm($aCreated['account'], $sPassword);
+        $this->bxAssertFormRejected($oForm, 'password_current');
+        $this->assertInstanceOf(BxDolAccount::class, BxDolAccount::getInstance($aCreated['id'], true));
+        $this->bxAssertAccountsTableIntact();
+    }
+
+    public function testDeleteFormRejectsMissingConfirm(): void
+    {
+        $aCreated = $this->bxCreateLifecycleAccount();
+        $oForm = $this->bxSubmitDeleteForm($aCreated['account'], $aCreated['password'], '');
+        $this->bxAssertFormRejected($oForm, 'delete_confirm');
+        $this->assertInstanceOf(BxDolAccount::class, BxDolAccount::getInstance($aCreated['id'], true));
+        $this->bxAssertAccountsTableIntact();
+    }
+}

+ 237 - 0
tests/integration/account/BxDolAccountTestCase.php

@@ -0,0 +1,237 @@
+<?php
+
+if (!function_exists('MsgBox'))
+    require_once BX_DIRECTORY_PATH_INC . 'design.inc.php';
+
+/**
+ * Pretend mail was sent so account create/edit does not block on SMTP.
+ */
+class BxTestSkipMailAlertsResponse extends BxDolAlertsResponse
+{
+    public function response($oAlert)
+    {
+        if ($oAlert->sUnit === 'system' && $oAlert->sAction === 'check_send_mail')
+            $oAlert->aExtras['override_result'] = true;
+    }
+}
+
+/**
+ * Shared helpers for account create/edit/delete integration tests.
+ */
+abstract class BxDolAccountTestCase extends BxDolIntegrationTestCase
+{
+    protected $_iCreatedAccountId = 0;
+
+    protected $_aCreatedEmails = [];
+
+    protected $_aAlertsCacheBackup;
+
+    protected $_sConfirmationBackup;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->_sConfirmationBackup = getParam('sys_account_confirmation_type');
+        $this->bxSetParamCache('sys_account_confirmation_type', BX_ACCOUNT_CONFIRMATION_NONE);
+        $this->bxInstallTestAlertHandlers();
+    }
+
+    protected function tearDown(): void
+    {
+        $this->bxCleanupCreatedAccount();
+        $this->bxRestoreTestAlertHandlers();
+        if ($this->_sConfirmationBackup !== null)
+            $this->bxSetParamCache('sys_account_confirmation_type', $this->_sConfirmationBackup);
+        parent::tearDown();
+    }
+
+    protected function bxAccountForm(string $sDisplay): BxTemplFormAccount
+    {
+        BxDolForm::unSetObjectInstance('sys_account', $sDisplay);
+        $oForm = BxDolForm::getObjectInstance('sys_account', $sDisplay);
+        $this->assertInstanceOf(BxTemplFormAccount::class, $oForm);
+        $oForm->aParams['csrf']['disable'] = true;
+
+        return $oForm;
+    }
+
+    protected function bxSubmitCreateForm(string $sName, string $sEmail, string $sPassword): BxTemplFormAccount
+    {
+        $oForm = $this->bxAccountForm('sys_account_create');
+        $_POST = [
+            'name' => $sName,
+            'email' => $sEmail,
+            'password' => $sPassword,
+            'receive_news' => '1',
+            'do_publish' => '1',
+        ];
+        $oForm->initChecker();
+
+        return $oForm;
+    }
+
+    protected function bxCreateLifecycleAccount(string $sName = 'Lifecycle User', string $sPassword = 'UnaTest1a'): array
+    {
+        $this->bxBecomeGuest();
+
+        $sEmail = 'lifecycle-' . bin2hex(random_bytes(4)) . '@example.com';
+        $this->_aCreatedEmails[] = $sEmail;
+
+        $oForm = $this->bxSubmitCreateForm($sName, $sEmail, $sPassword);
+        $this->assertTrue($oForm->isSubmittedAndValid(), $oForm->getFormErrors() ?: 'create account form should accept the posted values');
+
+        $iAccountId = $oForm->insert(['email_confirmed' => 0]);
+        $this->assertNotFalse($iAccountId);
+        $this->_iCreatedAccountId = (int)$iAccountId;
+
+        $iProfileId = (new BxTemplAccountForms())->onAccountCreated($iAccountId, $oForm->isSetPendingApproval());
+        $this->assertGreaterThan(0, (int)$iProfileId);
+
+        $oAccount = BxDolAccount::getInstance($iAccountId, true);
+        $this->assertInstanceOf(BxDolAccount::class, $oAccount);
+
+        $aInfo = $oAccount->getInfo();
+        $this->bxSyncSessionCookie();
+        $this->bxSetLoggedFlags((int)$aInfo['role']);
+
+        return [
+            'id' => (int)$iAccountId,
+            'profile_id' => (int)$iProfileId,
+            'email' => $sEmail,
+            'name' => $sName,
+            'password' => $sPassword,
+            'account' => $oAccount,
+        ];
+    }
+
+    protected function bxSubmitEmailForm(int $iAccountId, string $sEmail, string $sPassword): string
+    {
+        $this->bxAccountForm('sys_account_settings_email');
+        $_POST = [
+            'email' => $sEmail,
+            'password_current' => $sPassword,
+            'receive_updates' => '1',
+            'receive_news' => '1',
+            'do_submit' => '1',
+        ];
+
+        return (new BxTemplAccountForms())->editAccountEmailSettingsForm($iAccountId);
+    }
+
+    protected function bxSubmitInfoForm(int $iAccountId, string $sName): string
+    {
+        $this->bxAccountForm('sys_account_settings_info');
+        $_POST = [
+            'name' => $sName,
+            'do_submit' => '1',
+        ];
+
+        return (new BxTemplAccountForms())->editAccountInfoForm($iAccountId);
+    }
+
+    protected function bxSubmitDeleteForm(BxDolAccount $oAccount, string $sPassword, string $sConfirm = '1'): BxTemplFormAccount
+    {
+        $oForm = $this->bxAccountForm('sys_account_settings_del_account');
+        $_POST = [
+            'delete_content' => '0',
+            'delete_confirm' => $sConfirm,
+            'password_current' => $sPassword,
+            'do_submit' => '1',
+        ];
+        $oForm->initChecker($oAccount->getInfo());
+
+        return $oForm;
+    }
+
+    protected function bxAccountIdByEmail(string $sEmail): int
+    {
+        return (int)BxDolAccountQuery::getInstance()->getIdByEmail($sEmail);
+    }
+
+    protected function bxAccountIdByName(string $sName): int
+    {
+        return (int)BxDolDb::getInstance()->getOne('SELECT `id` FROM `sys_accounts` WHERE `name` = :name', [
+            'name' => $sName,
+        ]);
+    }
+
+    protected function bxAssertAccountsTableIntact(): void
+    {
+        $this->assertTrue(BxDolDb::getInstance()->isTableExists('sys_accounts'));
+        $this->assertGreaterThan(0, $this->bxAccountIdByEmail($this->bxTestEmail('admin')));
+    }
+
+    protected function bxAssertFormRejected(BxDolForm $oForm, string $sField = ''): void
+    {
+        $this->assertTrue($oForm->isSubmitted());
+        $this->assertFalse($oForm->isSubmittedAndValid(), $oForm->getFormErrors() ?: 'form should reject the posted values');
+        if ($sField !== '')
+            $this->assertNotEmpty($oForm->aInputs[$sField]['error'] ?? '', $sField . ' should have a checker error');
+    }
+
+    protected function bxSetParamCache(string $sKey, $mixedValue): void
+    {
+        $oProp = new ReflectionProperty(BxDolDb::class, '_aParams');
+        $aParams = $oProp->getValue();
+        $aParams[$sKey] = $mixedValue;
+        $oProp->setValue(null, $aParams);
+    }
+
+    protected function bxInstallTestAlertHandlers(): void
+    {
+        new BxDolAlerts('system', 'phpunit_init', 0, 0);
+
+        $oProp = new ReflectionProperty(BxDolAlerts::class, '_aCacheData');
+        $this->_aAlertsCacheBackup = $oProp->getValue();
+        $aCache = $this->_aAlertsCacheBackup;
+
+        foreach ($aCache['handlers'] as $mixedId => $aHandler) {
+            if (in_array($aHandler['class'] ?? '', ['BxSMTPAlertsResponse', 'BxMapShowAlertsResponse'], true))
+                $aCache['handlers'][$mixedId]['active'] = 0;
+        }
+
+        $sId = 'phpunit_skip_mail';
+        $aCache['handlers'][$sId] = [
+            'name' => $sId,
+            'class' => BxTestSkipMailAlertsResponse::class,
+            'file' => 'tests/integration/account/BxDolAccountTestCase.php',
+            'service_call' => '',
+            'active' => 1,
+        ];
+        $aCache['alerts']['system']['check_send_mail'][] = $sId;
+        $oProp->setValue(null, $aCache);
+    }
+
+    protected function bxRestoreTestAlertHandlers(): void
+    {
+        if ($this->_aAlertsCacheBackup === null)
+            return;
+
+        $oProp = new ReflectionProperty(BxDolAlerts::class, '_aCacheData');
+        $oProp->setValue(null, $this->_aAlertsCacheBackup);
+        $this->_aAlertsCacheBackup = null;
+    }
+
+    protected function bxCleanupCreatedAccount(): void
+    {
+        $aEmails = $this->_aCreatedEmails;
+        if ($this->_iCreatedAccountId)
+            $aEmails[] = $this->_iCreatedAccountId;
+
+        foreach (array_unique($aEmails) as $mixedId) {
+            $oAccount = BxDolAccount::getInstance($mixedId, true);
+            if (!$oAccount)
+                continue;
+
+            $sJob = 'account_delete_' . $oAccount->id();
+            $oJobs = BxDolBackgroundJobs::getInstance();
+            if ($oJobs->exists($sJob))
+                $oJobs->delete($sJob);
+
+            $oAccount->delete(false, false);
+        }
+
+        $this->_iCreatedAccountId = 0;
+        $this->_aCreatedEmails = [];
+    }
+}